// Version: 1.0.0 import 'package:sqflite/sqflite.dart'; import 'package:path/path.dart'; import 'dart:convert'; import '../models/customer.dart'; import '../models/product.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 ) '''); // product_snapshots テーブル(イベントソーシング用) await db.execute(''' CREATE TABLE product_snapshots ( id INTEGER PRIMARY KEY AUTOINCREMENT, product_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) {} } } // ========== Customer CRUD ========== 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}, where: 'id = ?', whereArgs: [id]); } Future saveSnapshot(Customer customer) async { final db = await instance.database; final id = customer.id; final now = DateTime.now().toIso8601String(); final existing = await db.query('customer_snapshots', where: 'customer_id = ?', whereArgs: [id], limit: 6); if (existing.length > 5) { final oldestId = existing.first['id'] as int; await db.delete('customer_snapshots', where: 'id = ?', whereArgs: [oldestId]); } await db.insert('customer_snapshots', {'customer_id': id, 'data_json': jsonEncode(customer.toMap()), 'created_at': now}); } // ========== Product CRUD ========== Future insert(Product product) async { final db = await instance.database; return await db.insert('products', product.toMap()); } Future> getProducts() async { final db = await instance.database; final List> maps = await db.query('products', orderBy: 'product_code ASC'); return (maps as List) .map((json) => Product.fromMap(json)) .where((p) => p.isDeleted == 0) .toList(); } Future getProduct(int id) async { final db = await instance.database; final maps = await db.query('products', where: 'id = ?', whereArgs: [id]); if (maps.isEmpty) return null; return Product.fromMap(maps.first); } Future update(Product product) async { final db = await instance.database; return await db.update('products', product.toMap(), where: 'id = ?', whereArgs: [product.id]); } Future delete(int id) async { final db = await instance.database; return await db.update('products', {'is_deleted': 1}, where: 'id = ?', whereArgs: [id]); } Future saveSnapshot(Product product) async { final db = await instance.database; final id = product.id; final now = DateTime.now().toIso8601String(); final existing = await db.query('product_snapshots', where: 'product_id = ?', whereArgs: [id], limit: 6); if (existing.length > 5) { final oldestId = existing.first['id'] as int; await db.delete('product_snapshots', where: 'id = ?', whereArgs: [oldestId]); } await db.insert('product_snapshots', {'product_id': id, 'data_json': jsonEncode(product.toMap()), 'created_at': now}); } Future close() async { final db = await instance.database; db.close(); } }