h-1.flutter.4/lib/services/database_helper.dart

288 lines
No EOL
9.2 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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';
}
}