// Version: 1.4 (estimate テーブル定義修正・CRUD API 実装) import 'package:sqflite/sqflite.dart'; import 'package:path/path.dart'; import 'dart:convert'; import '../models/customer.dart'; import '../models/product.dart'; import '../models/estimate.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: 2, // Estimate テーブル追加でバージョンアップ 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 ) '''); // estimate テーブル(見積書用) await db.execute(''' CREATE TABLE estimates ( id INTEGER PRIMARY KEY AUTOINCREMENT, estimate_no TEXT NOT NULL UNIQUE, customer_id INTEGER, customer_name TEXT NOT NULL, date TEXT NOT NULL, total_amount REAL NOT NULL, tax_rate REAL DEFAULT 10, items_json TEXT NOT NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL ) '''); // sales テーブル(売用书) await db.execute(''' CREATE TABLE sales ( id INTEGER PRIMARY KEY AUTOINCREMENT, sale_no TEXT NOT NULL UNIQUE, customer_id INTEGER, customer_name TEXT NOT NULL, date TEXT NOT NULL, total_amount REAL NOT NULL, tax_rate REAL DEFAULT 10, items_json TEXT NOT NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL ) '''); // 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 { 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 { 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 { 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 { 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 { 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]); } // ========== Estimate CRUD ====== Future insertEstimate(String estimateNo, String customerName, DateTime date, List items) async { final db = await instance.database; // 見積番号の重複チェック final existing = await db.query('estimates', where: 'estimate_no = ?', whereArgs: [estimateNo]); if (existing.isNotEmpty) { throw ArgumentError('見積番号「$estimateNo」は既に存在します'); } final itemsJson = jsonEncode(items.map((e) => e.toMap()).toList()); final totalAmount = items.fold(0, (sum, item) => sum + item.total); return await db.insert('estimates', { 'estimate_no': estimateNo, 'customer_name': customerName, 'date': date.toIso8601String(), 'total_amount': totalAmount, 'tax_rate': 10, 'items_json': itemsJson, 'created_at': DateTime.now().toIso8601String(), 'updated_at': DateTime.now().toIso8601String(), }); } Future> getEstimates() async { final db = await instance.database; final List> maps = await db.query('estimates', orderBy: 'created_at DESC'); return (maps as List) .map((json) => Estimate.fromMap(json)) .toList(); } Future getEstimate(int id) async { final db = await instance.database; final maps = await db.query('estimates', where: 'id = ?', whereArgs: [id]); if (maps.isEmpty) return null; return Estimate.fromMap(maps.first); } Future saveSnapshot(Estimate estimate) async { //_estimate_snapshots テーブルも必要に応じて追加可 } // ========== 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 close() async { final db = await instance.database; db.close(); } }