288 lines
No EOL
9.2 KiB
Dart
288 lines
No EOL
9.2 KiB
Dart
// Version: 1.0 - シンプルデータベースアクセスヘルパー(sqflite 直接操作)
|
||
// NOTE: データベース更新メソッドは簡素化のため、update() を使用していません
|
||
|
||
import 'dart:io';
|
||
import 'package:flutter/foundation.dart';
|
||
import 'package:sqflite/sqflite.dart';
|
||
import '../models/product.dart';
|
||
|
||
class DatabaseHelper {
|
||
static Database? _database;
|
||
|
||
/// データベース初期化(サンプルデータ付き)
|
||
static Future<void> init() async {
|
||
if (_database != null) return;
|
||
|
||
try {
|
||
String dbPath;
|
||
|
||
if (Platform.isAndroid || Platform.isIOS) {
|
||
final dbDir = await getDatabasesPath();
|
||
dbPath = '$dbDir/sales.db';
|
||
} else {
|
||
dbPath = Directory.current.path + '/data/db/sales.db';
|
||
}
|
||
|
||
await Directory(dbPath).parent.create(recursive: true);
|
||
|
||
_database = await _initDatabase(dbPath);
|
||
print('[DatabaseHelper] DB initialized successfully at $dbPath');
|
||
|
||
} catch (e) {
|
||
print('DB init error: $e');
|
||
throw Exception('Database initialization failed: $e');
|
||
}
|
||
}
|
||
|
||
static Future<Database> _initDatabase(String path) async {
|
||
return await openDatabase(
|
||
path,
|
||
version: 1,
|
||
onCreate: _onCreateTableWithSampleData,
|
||
);
|
||
}
|
||
|
||
static Future<void> _onCreateTableWithSampleData(Database db, int version) async {
|
||
// products テーブル
|
||
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,
|
||
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 テーブル
|
||
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 = <Map<String, dynamic>>[
|
||
{'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<List<Product>> getProducts() async {
|
||
final result = await instance.query('products', orderBy: 'id DESC');
|
||
|
||
return List.generate(result.length, (index) {
|
||
final item = Map<String, dynamic>.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<Product?> getProduct(int id) async {
|
||
final result = await instance.query(
|
||
'products',
|
||
where: 'id = ?',
|
||
whereArgs: [id],
|
||
);
|
||
|
||
if (result.isNotEmpty) {
|
||
final item = Map<String, dynamic>.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<Product?> getProductByCode(String code) async {
|
||
final result = await instance.query(
|
||
'products',
|
||
where: 'product_code = ?',
|
||
whereArgs: [code],
|
||
);
|
||
|
||
if (result.isNotEmpty) {
|
||
final item = Map<String, dynamic>.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;
|
||
}
|
||
|
||
/// 顧客一覧を取得(非アクティブ除外)
|
||
static Future<List<Map<String, dynamic>>> getCustomers() async {
|
||
final result = await instance.query('customers', where: 'is_inactive = ?', whereArgs: [false]);
|
||
|
||
return List.generate(result.length, (index) {
|
||
final item = Map<String, dynamic>.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 item;
|
||
});
|
||
}
|
||
|
||
/// 製品を挿入(簡素版)
|
||
static Future<void> insertProduct(Product product) async {
|
||
await instance.insert('products', {
|
||
'product_code': product.productCode,
|
||
'name': product.name,
|
||
'unit_price': product.unitPrice,
|
||
'quantity': product.quantity,
|
||
'stock': product.stock,
|
||
'created_at': DateTime.now().toIso8601String(),
|
||
'updated_at': DateTime.now().toIso8601String(),
|
||
});
|
||
}
|
||
|
||
/// 製品を削除(簡素版)
|
||
static Future<void> deleteProduct(int id) async {
|
||
await instance.delete('products', where: 'id = ?', whereArgs: [id]);
|
||
}
|
||
|
||
/// 顧客を挿入(簡素版)
|
||
static Future<void> insertCustomer(Map<String, dynamic> customer) async {
|
||
await instance.insert('customers', {
|
||
'customer_code': customer['customerCode'],
|
||
'name': customer['name'],
|
||
'address': customer['address'],
|
||
'phone': customer['phoneNumber'],
|
||
'email': customer['email'],
|
||
});
|
||
}
|
||
|
||
/// 顧客を更新(簡素版:削除後再挿入)
|
||
static Future<void> updateCustomer(Map<String, dynamic> customer) async {
|
||
await deleteCustomer(customer['id'] ?? 0);
|
||
await insertCustomer(customer);
|
||
}
|
||
|
||
/// 顧客を削除(簡素版)
|
||
static Future<void> deleteCustomer(int id) async {
|
||
await instance.delete('customers', where: 'id = ?', whereArgs: [id]);
|
||
}
|
||
|
||
/// DB をクリア
|
||
static Future<void> clearDatabase() async {
|
||
await instance.delete('products');
|
||
await instance.delete('customers');
|
||
await instance.delete('sales');
|
||
await instance.delete('estimates');
|
||
}
|
||
|
||
/// データベースを回復(全削除 + リセット + テーブル再作成)
|
||
static Future<void> recover() async {
|
||
try {
|
||
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');
|
||
}
|
||
}
|
||
|
||
static Future<String> getDbPath() async {
|
||
return Directory.current.path + '/data/db/sales.db';
|
||
}
|
||
} |