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

319 lines
No EOL
10 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.

// DatabaseHelper - シンプルデータベースアクセスヘルパーsqflite 直接操作)
// NOTE: データベース更新メソッドは簡素化のため、update() を使用していません
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<void> init() async {
if (_database != null) return;
try {
String dbPath;
if (Platform.isAndroid || Platform.isIOS) {
// モバイル環境sqflite の標準パスを使用
final dbDir = await getDatabasesPath();
dbPath = '$dbDir/sales.db';
} else {
// デスクトップ/開発環境:現在のフォルダを使用
dbPath = Directory.current.path + '/data/db/sales.db';
}
// 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 テーブル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,
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 = <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');
// DateTime オブジェクトを文字列に変換してから Product からマップ
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;
}
/// クライアント ID での顧客検索(エラー時は null を返す)
static Future<Customer?> 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;
}
/// 製品を挿入簡素版return を省略)
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]);
}
/// 顧客を挿入簡素版return を省略)
static Future<void> insertCustomer(Customer customer) async {
await instance.insert('customers', {
'customer_code': customer.customerCode,
'name': customer.name,
'address': customer.address,
'phone': customer.phone,
'email': customer.email,
'created_at': DateTime.now().toIso8601String(),
'updated_at': DateTime.now().toIso8601String(),
});
}
/// 顧客を削除(簡素版)
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 {
// 既存の 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<String> getDbPath() async {
return Directory.current.path + '/data/db/sales.db';
}
}