// 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 get database async { if (_database != null) return _database!; _database = await _initDB('customer_assist.db'); return _database!; } Future _initDB(String filePath) async { final dbPath = await getDatabasesPath(); final path = join(dbPath, filePath); return await openDatabase( path, version: 1, onCreate: _createDB, ); } Future _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 _insertTestData(Database db) async { // customers テーブルにテストデータを挿入(既に存在しない場合のみ) final existingCustomerCodes = (await db.query('customers', columns: ['customer_code'])) .map((e) => e['customer_code'] as String?) .whereType() .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() .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() .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() .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() .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 insert(Customer customer) async { final db = await instance.database; return await db.insert('customers', customer.toMap()); } Future> getCustomers() async { final db = await instance.database; final List> maps = await db.query('customers'); return (maps as List) .map((json) => Customer.fromMap(json)) .where((c) => c.isDeleted == 0) .toList(); } Future 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 update(Customer customer) async { final db = await instance.database; return await db.update( 'customers', customer.toMap(), where: 'id = ?', whereArgs: [customer.id], ); } Future delete(int id) async { final db = await instance.database; return await db.update( 'customers', {'is_deleted': 1}, // Soft delete where: 'id = ?', whereArgs: [id], ); } Future 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 close() async { final db = await instance.database; db.close(); } }