h-1.flutter.4/lib/services/database_helper.dart
joe 8ea10dad79 chore: 工程管理ドキュメントを追加し、UI リファクタリング</new_task>feat: docs/requirements.md, docs/project_plan.md の追加
- 機能要件・非機能要件定義
- 短期長期プロジェクト計画の策定

docs: UI ライティングリファクタリング
- 編集 SnackBar から Cancel ボタン削除
- タイル表示からプレースホルダメッセージへ</new_task>chore: README のドキュメント活用方法追記</new_task>
2026-03-07 14:30:12 +09:00

375 lines
No EOL
11 KiB
Dart

// Version: 1.0.0
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
import 'dart:convert';
import '../models/customer.dart';
class DatabaseHelper {
static final DatabaseHelper instance = DatabaseHelper._init();
static Database? _database;
DatabaseHelper._init();
Future<Database> get database async {
if (_database != null) return _database!;
_database = await _initDB('customer_assist.db');
return _database!;
}
Future<Database> _initDB(String filePath) async {
final dbPath = await getDatabasesPath();
final path = join(dbPath, filePath);
return await openDatabase(
path,
version: 1,
onCreate: _createDB,
);
}
Future<void> _createDB(Database db, int version) async {
const textType = 'TEXT NOT NULL';
// customers テーブル作成(テストデータ挿入含む)
await db.execute('''
CREATE TABLE customers (
id INTEGER PRIMARY KEY AUTOINCREMENT,
customer_code TEXT $textType,
name TEXT $textType,
phone_number TEXT $textType,
email TEXT NOT NULL,
address TEXT $textType,
sales_person_id INTEGER,
tax_rate INTEGER DEFAULT 8,
discount_rate INTEGER DEFAULT 0,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
)
''');
// employee テーブル(マスタ用)
await db.execute('''
CREATE TABLE employees (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
position TEXT,
phone_number TEXT,
is_deleted INTEGER DEFAULT 0
)
''');
// warehouse テーブル(マスタ用)
await db.execute('''
CREATE TABLE warehouses (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
address TEXT,
manager TEXT
)
''');
// supplier テーブル(マスタ用)
await db.execute('''
CREATE TABLE suppliers (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
tax_rate INTEGER DEFAULT 8
)
''');
// product テーブル
await db.execute('''
CREATE TABLE products (
id INTEGER PRIMARY KEY AUTOINCREMENT,
product_code TEXT NOT NULL,
name TEXT NOT NULL,
price INTEGER NOT NULL,
stock INTEGER DEFAULT 0,
category TEXT,
unit TEXT
)
''');
// customer_snapshots テーブル(イベントソーシング用)
await db.execute('''
CREATE TABLE customer_snapshots (
id INTEGER PRIMARY KEY AUTOINCREMENT,
customer_id INTEGER NOT NULL,
data_json TEXT NOT NULL,
created_at TEXT NOT NULL
)
''');
// employee_snapshots テーブル(イベントソーシング用)
await db.execute('''
CREATE TABLE employee_snapshots (
id INTEGER PRIMARY KEY AUTOINCREMENT,
employee_id INTEGER NOT NULL,
data_json TEXT NOT NULL,
created_at TEXT NOT NULL
)
''');
// テストデータ初期化(各テーブルが空の場合のみ)
await _insertTestData(db);
}
Future<void> _insertTestData(Database db) async {
// customers テーブルにテストデータを挿入(既に存在しない場合のみ)
final existingCustomerCodes = (await db.query('customers', columns: ['customer_code']))
.map((e) => e['customer_code'] as String?)
.whereType<String>()
.toList();
if (existingCustomerCodes.isEmpty) {
// テスト顧客データ
await db.insert('customers', {
'customer_code': 'C00001',
'name': 'テスト株式会社 Alpha',
'phone_number': '03-1234-5678',
'email': 'alpha@test.com',
'address': '東京都港区_test 1-1-1',
'sales_person_id': null,
'tax_rate': 8,
'discount_rate': 0,
'created_at': DateTime.now().toIso8601String(),
'updated_at': DateTime.now().toIso8601String(),
});
await db.insert('customers', {
'customer_code': 'C00002',
'name': 'テスト株式会社 Beta',
'phone_number': '06-8765-4321',
'email': 'beta@test.com',
'address': '大阪府大阪市_test 2-2-2',
'sales_person_id': null,
'tax_rate': 9,
'discount_rate': 5,
'created_at': DateTime.now().toIso8601String(),
'updated_at': DateTime.now().toIso8601String(),
});
await db.insert('customers', {
'customer_code': 'C00003',
'name': 'テスト株式会社 Gamma',
'phone_number': '011-1111-2222',
'email': 'gamma@test.com',
'address': '北海道札幌市_test 3-3-3',
'sales_person_id': null,
'tax_rate': 10,
'discount_rate': 10,
'created_at': DateTime.now().toIso8601String(),
'updated_at': DateTime.now().toIso8601String(),
});
// employees テーブル(存在する場合はスキップ)
try {
await db.execute('CREATE TABLE IF NOT EXISTS employees (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, position TEXT, phone_number TEXT)');
final existingEmployees = (await db.query('employees', columns: ['name']))
.map((e) => e['name'] as String?)
.whereType<String>()
.toList();
if (existingEmployees.isEmpty) {
await db.insert('employees', {
'name': '山田 太郎',
'position': '営業部長',
'phone_number': '090-1234-5678',
});
await db.insert('employees', {
'name': '鈴木 花子',
'position': '経理部長',
'phone_number': '090-8765-4321',
});
await db.insert('employees', {
'name': '田中 次郎',
'position': '技術部長',
'phone_number': '090-1111-2222',
});
}
} catch (e) {}
// warehouses テーブル(存在する場合はスキップ)
try {
await db.execute('CREATE TABLE IF NOT EXISTS warehouses (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, address TEXT, manager TEXT)');
final existingWarehouses = (await db.query('warehouses', columns: ['name']))
.map((e) => e['name'] as String?)
.whereType<String>()
.toList();
if (existingWarehouses.isEmpty) {
await db.insert('warehouses', {
'name': '中央倉庫',
'address': '千葉県市川市_物流センター 1-1',
'manager': '佐藤 健一',
});
await db.insert('warehouses', {
'name': '東京支庫',
'address': '東京都品川区_倉庫棟 2-2',
'manager': '高橋 美咲',
});
}
} catch (e) {}
// suppliers テーブル(存在する場合はスキップ)
try {
await db.execute('CREATE TABLE IF NOT EXISTS suppliers (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, tax_rate INTEGER DEFAULT 8)');
final existingSuppliers = (await db.query('suppliers', columns: ['name']))
.map((e) => e['name'] as String?)
.whereType<String>()
.toList();
if (existingSuppliers.isEmpty) {
await db.insert('suppliers', {
'name': '仕入元 Alpha',
'tax_rate': 8,
});
await db.insert('suppliers', {
'name': '仕入元 Beta',
'tax_rate': 10,
});
await db.insert('suppliers', {
'name': '仕入元 Gamma',
'tax_rate': 10,
});
}
} catch (e) {}
// products テーブル(存在する場合はスキップ)
try {
await db.execute('''
CREATE TABLE IF NOT EXISTS products (
id INTEGER PRIMARY KEY AUTOINCREMENT,
product_code TEXT NOT NULL,
name TEXT NOT NULL,
price INTEGER NOT NULL,
stock INTEGER DEFAULT 0,
category TEXT,
unit TEXT
)
''');
final existingProducts = (await db.query('products', columns: ['product_code']))
.map((e) => e['product_code'] as String?)
.whereType<String>()
.toList();
if (existingProducts.isEmpty) {
await db.insert('products', {
'product_code': 'PRD001',
'name': 'テスト商品 A',
'price': 3500,
'stock': 100,
'category': '食品',
'unit': '',
});
await db.insert('products', {
'product_code': 'PRD002',
'name': 'テスト商品 B',
'price': 5500,
'stock': 50,
'category': '家電',
'unit': '',
});
await db.insert('products', {
'product_code': 'PRD003',
'name': 'テスト商品 C',
'price': 2800,
'stock': 200,
'category': '文具',
'unit': '',
});
}
} catch (e) {}
}
}
Future<int> insert(Customer customer) async {
final db = await instance.database;
return await db.insert('customers', customer.toMap());
}
Future<List<Customer>> getCustomers() async {
final db = await instance.database;
final List<Map<String, dynamic>> maps = await db.query('customers');
return (maps as List)
.map((json) => Customer.fromMap(json))
.where((c) => c.isDeleted == 0)
.toList();
}
Future<Customer?> getCustomer(int id) async {
final db = await instance.database;
final maps = await db.query(
'customers',
where: 'id = ?',
whereArgs: [id],
);
if (maps.isEmpty) return null;
return Customer.fromMap(maps.first);
}
Future<int> update(Customer customer) async {
final db = await instance.database;
return await db.update(
'customers',
customer.toMap(),
where: 'id = ?',
whereArgs: [customer.id],
);
}
Future<int> delete(int id) async {
final db = await instance.database;
return await db.update(
'customers',
{'is_deleted': 1}, // Soft delete
where: 'id = ?',
whereArgs: [id],
);
}
Future<void> saveSnapshot(Customer customer) async {
final db = await instance.database;
final id = customer.id;
final now = DateTime.now().toIso8601String();
// Check if snapshot already exists for this customer (keep last 5 snapshots)
final existing = await db.query(
'customer_snapshots',
where: 'customer_id = ?',
whereArgs: [id],
limit: 6,
);
if (existing.length > 5) {
// Delete oldest snapshot
final oldestId = existing.first['id'] as int;
await db.delete('customer_snapshots', where: 'id = ?', whereArgs: [oldestId]);
}
// Insert new snapshot
await db.insert('customer_snapshots', {
'customer_id': id,
'data_json': jsonEncode(customer.toMap()),
'created_at': now,
});
}
Future<void> close() async {
final db = await instance.database;
db.close();
}
}