// DatabaseHelper - シンプルデータベースアクセスヘルパー(sqflite 直接操作) import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:sqflite/sqflite.dart'; import '../models/product.dart'; // Customer モデル class Customer { final int? id; final String? customerCode; final String? name; final String? address; final String? phone; final String? email; final bool isInactive; Customer({ this.id, this.customerCode, this.name, this.address, this.phone, this.email, this.isInactive = false, }); } class DatabaseHelper { static Database? _database; /// データベース初期化(サンプルデータ付き) static Future init() async { if (_database != null) return; try { // アプリの現在のフォルダを DB パスに使用(開発/テスト用) final dbPath = Directory.current.path + '/data/db/sales.db'; _database = await _initDatabase(dbPath); print('[DatabaseHelper] DB initialized successfully'); } catch (e) { print('DB init error: $e'); throw Exception('Database initialization failed: $e'); } } /// テーブル作成時にサンプルデータを自動的に挿入 static Future _initDatabase(String path) async { return await openDatabase( path, version: 2, // バージョンアップ(仕入先情報カラム追加用) onCreate: _onCreateTableWithSampleData, ); } /// テーブル作成用関数 + サンプルデータ自動挿入 static Future _onCreateTableWithSampleData(Database db, int version) async { // products テーブル(Product モデルと整合性を取る)+ 仕入先情報カラム追加 await db.execute(''' CREATE TABLE products ( id INTEGER PRIMARY KEY AUTOINCREMENT, product_code TEXT UNIQUE NOT NULL, name TEXT NOT NULL, unit_price REAL DEFAULT 0.0, quantity INTEGER DEFAULT 0, stock INTEGER DEFAULT 0, supplier_contact_name TEXT, supplier_phone_number TEXT, email TEXT, address TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ) '''); // customers テーブル await db.execute(''' CREATE TABLE customers ( id INTEGER PRIMARY KEY AUTOINCREMENT, customer_code TEXT UNIQUE NOT NULL, name TEXT NOT NULL, address TEXT, phone TEXT, email TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ) '''); // sales テーブル await db.execute(''' CREATE TABLE sales ( id INTEGER PRIMARY KEY AUTOINCREMENT, customer_id INTEGER, product_id INTEGER REFERENCES products(id), quantity INTEGER NOT NULL, unit_price REAL NOT NULL, total_amount REAL NOT NULL, tax_rate REAL DEFAULT 8.0, tax_amount REAL, grand_total REAL NOT NULL, status TEXT DEFAULT 'completed', payment_status TEXT DEFAULT 'paid', invoice_number TEXT UNIQUE, notes TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ) '''); // estimates テーブル(Estimate モデルと整合性を取る) await db.execute(''' CREATE TABLE estimates ( id INTEGER PRIMARY KEY AUTOINCREMENT, quote_number TEXT UNIQUE, customer_id INTEGER REFERENCES customers(id), product_id INTEGER REFERENCES products(id), quantity INTEGER NOT NULL, unit_price REAL NOT NULL, discount_percent REAL DEFAULT 0.0, total_amount REAL NOT NULL, tax_rate REAL DEFAULT 8.0, tax_amount REAL, grand_total REAL NOT NULL, status TEXT DEFAULT 'pending', payment_status TEXT DEFAULT 'unpaid', expiry_date TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ) '''); // インデックス await db.execute('CREATE INDEX idx_products_code ON products(product_code)'); await db.execute('CREATE INDEX idx_customers_code ON customers(customer_code)'); // サンプル製品データを挿入(テーブル作成時に自動的に実行) final sampleProducts = >[ {'product_code': 'TEST001', 'name': 'サンプル商品 A', 'unit_price': 1000.0, 'quantity': 50, 'stock': 50}, {'product_code': 'TEST002', 'name': 'サンプル商品 B', 'unit_price': 2500.0, 'quantity': 30, 'stock': 30}, {'product_code': 'TEST003', 'name': 'サンプル商品 C', 'unit_price': 5000.0, 'quantity': 20, 'stock': 20}, ]; for (final data in sampleProducts) { await db.insert('products', data); } print('[DatabaseHelper] Sample products inserted'); } /// データベースインスタンスへのアクセス static Database get instance => _database!; /// 製品一覧を取得(非アクティブ除外) static Future> getProducts() async { final result = await instance.query('products', orderBy: 'id DESC'); // DateTime オブジェクトを文字列に変換してから Product からマップ return List.generate(result.length, (index) { final item = Map.from(result[index]); if (item['created_at'] is DateTime) { item['created_at'] = (item['created_at'] as DateTime).toIso8601String(); } if (item['updated_at'] is DateTime) { item['updated_at'] = (item['updated_at'] as DateTime).toIso8601String(); } return Product.fromMap(item); }); } /// 製品を ID で取得(エラー時は null を返す) static Future getProduct(int id) async { final result = await instance.query( 'products', where: 'id = ?', whereArgs: [id], ); if (result.isNotEmpty) { final item = Map.from(result[0]); if (item['created_at'] is DateTime) { item['created_at'] = (item['created_at'] as DateTime).toIso8601String(); } if (item['updated_at'] is DateTime) { item['updated_at'] = (item['updated_at'] as DateTime).toIso8601String(); } return Product.fromMap(item); } return null; } /// 製品を productCode で取得(エラー時は null を返す) static Future getProductByCode(String code) async { final result = await instance.query( 'products', where: 'product_code = ?', whereArgs: [code], ); if (result.isNotEmpty) { final item = Map.from(result[0]); if (item['created_at'] is DateTime) { item['created_at'] = (item['created_at'] as DateTime).toIso8601String(); } if (item['updated_at'] is DateTime) { item['updated_at'] = (item['updated_at'] as DateTime).toIso8601String(); } return Product.fromMap(item); } return null; } /// クライアント ID での顧客検索(エラー時は null を返す) static Future getCustomerById(int id) async { final result = await instance.query( 'customers', where: 'id = ?', whereArgs: [id], ); if (result.isNotEmpty) { return Customer( id: result[0]['id'] as int?, customerCode: result[0]['customer_code'] as String?, name: result[0]['name'] as String?, address: result[0]['address'] as String?, phone: result[0]['phone'] as String?, email: result[0]['email'] as String?, isInactive: (result[0]['is_inactive'] as bool?) ?? false, ); } return null; } /// 顧客をコードで検索(エラー時は null を返す) static Future getCustomerByCode(String code) async { final result = await instance.query( 'customers', where: 'customer_code = ?', whereArgs: [code], ); if (result.isNotEmpty) { return Customer( id: result[0]['id'] as int?, customerCode: result[0]['customer_code'] as String?, name: result[0]['name'] as String?, address: result[0]['address'] as String?, phone: result[0]['phone'] as String?, email: result[0]['email'] as String?, isInactive: (result[0]['is_inactive'] as bool?) ?? false, ); } return null; } /// 顧客を insert static Future insertCustomer(Customer customer) async { final id = await instance.insert('customers', customer.toMap()); print('[DatabaseHelper] Customer inserted: $id'); return id; } /// 顧客を更新 static Future updateCustomer(Customer customer) async { await instance.update( 'customers', {'name': customer.name, 'address': customer.address, 'phone': customer.phone, 'email': customer.email}, where: 'id = ?', whereArgs: [customer.id], ); print('[DatabaseHelper] Customer updated'); } /// 顧客を削除 static Future deleteCustomer(int id) async { await instance.delete('customers', where: 'id = ?', whereArgs: [id]); print('[DatabaseHelper] Customer deleted'); } /// 製品を insert static Future insertProduct(Product product) async { final id = await instance.insert('products', product.toMap()); print('[DatabaseHelper] Product inserted: $id'); return id; } /// 製品を更新 static Future updateProduct(Product product) async { await instance.update( 'products', { 'name': product.name, 'unit_price': product.unitPrice, 'quantity': product.quantity, 'stock': product.stock, 'supplier_contact_name': product.supplierContactName, 'supplier_phone_number': product.supplierPhoneNumber, 'email': product.email, 'address': product.address, }, where: 'id = ?', whereArgs: [product.id], ); print('[DatabaseHelper] Product updated'); } /// 製品を削除 static Future deleteProduct(int id) async { await instance.delete('products', where: 'id = ?', whereArgs: [id]); print('[DatabaseHelper] Product deleted'); } /// DB をクリア(サンプルデータは保持しない) static Future clearDatabase() async { await instance.delete('products'); await instance.delete('customers'); await instance.delete('sales'); await instance.delete('estimates'); } /// データベースを回復(全削除 + リセット + テーブル再作成) static Future recover() async { try { // 既存の DB ファイルを削除 final dbPath = Directory.current.path + '/data/db/sales.db'; final file = File(dbPath); if (await file.exists()) { await file.delete(); print('[DatabaseHelper] recover: DB ファイルを削除'); } else { print('[DatabaseHelper] recover: DB ファイルが見つからない'); } // 初期化を再実行(テーブル作成時にサンプルデータが自動的に挿入される) await init(); } catch (e) { print('[DatabaseHelper] recover error: $e'); } } /// DB パスを取得 static Future getDbPath() async { return Directory.current.path + '/data/db/sales.db'; } }