426 lines
No EOL
14 KiB
Dart
426 lines
No EOL
14 KiB
Dart
// 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<Database> get database async {
|
|
if (_database != null) return _database!;
|
|
_database = await _initDB('customer_assist.db');
|
|
return _database!;
|
|
}
|
|
|
|
Future<Database> _initDB(String filePath) async {
|
|
final dbPath = await getDatabasesPath();
|
|
final path = join(dbPath, filePath);
|
|
|
|
return await openDatabase(
|
|
path,
|
|
version: 2, // Estimate テーブル追加でバージョンアップ
|
|
onCreate: _createDB,
|
|
);
|
|
}
|
|
|
|
Future<void> _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<void> _insertTestData(Database db) async {
|
|
final existingCustomerCodes = (await db.query('customers', columns: ['customer_code']))
|
|
.map((e) => e['customer_code'] as String?)
|
|
.whereType<String>()
|
|
.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<String>()
|
|
.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<String>()
|
|
.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<String>()
|
|
.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<String>()
|
|
.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<int> insert(Customer customer) async {
|
|
final db = await instance.database;
|
|
return await db.insert('customers', customer.toMap());
|
|
}
|
|
|
|
Future<List<Customer>> getCustomers() async {
|
|
final db = await instance.database;
|
|
final List<Map<String, dynamic>> maps = await db.query('customers');
|
|
|
|
return (maps as List)
|
|
.map((json) => Customer.fromMap(json))
|
|
.where((c) => c.isDeleted == 0)
|
|
.toList();
|
|
}
|
|
|
|
Future<Customer?> 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<int> update(Customer customer) async {
|
|
final db = await instance.database;
|
|
return await db.update('customers', customer.toMap(), where: 'id = ?', whereArgs: [customer.id]);
|
|
}
|
|
|
|
Future<int> delete(int id) async {
|
|
final db = await instance.database;
|
|
return await db.update('customers', {'is_deleted': 1}, where: 'id = ?', whereArgs: [id]);
|
|
}
|
|
|
|
// ========== Estimate CRUD ======
|
|
|
|
Future<int> insertEstimate(String estimateNo, String customerName, DateTime date, List<EstimateItem> 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<List<Estimate>> getEstimates() async {
|
|
final db = await instance.database;
|
|
final List<Map<String, dynamic>> maps = await db.query('estimates', orderBy: 'created_at DESC');
|
|
|
|
return (maps as List)
|
|
.map((json) => Estimate.fromMap(json))
|
|
.toList();
|
|
}
|
|
|
|
Future<Estimate?> 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<void> saveSnapshot(Estimate estimate) async {
|
|
//_estimate_snapshots テーブルも必要に応じて追加可
|
|
}
|
|
|
|
// ========== Sales CRUD ======
|
|
|
|
Future<int> insertSales(String saleNo, String customerName, DateTime date, List<dynamic> items) async {
|
|
final db = await instance.database;
|
|
|
|
// 売上番号の重複チェック
|
|
final existing = await db.query('sales', where: 'sale_no = ?', whereArgs: [saleNo]);
|
|
if (existing.isNotEmpty) {
|
|
throw ArgumentError('売上番号「$saleNo」は既に存在します');
|
|
}
|
|
|
|
final itemsJson = jsonEncode(items);
|
|
final totalAmount = items.fold(0, (sum, item) => sum + (item['amount'] as num).toDouble());
|
|
|
|
return await db.insert('sales', {
|
|
'sale_no': saleNo,
|
|
'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<List<dynamic>> getSales() async {
|
|
final db = await instance.database;
|
|
final List<Map<String, dynamic>> maps = await db.query('sales', orderBy: 'created_at DESC');
|
|
|
|
return (maps as List).map((json) => json);
|
|
}
|
|
|
|
Future<dynamic?> getSales(int id) async {
|
|
final db = await instance.database;
|
|
final maps = await db.query('sales', where: 'id = ?', whereArgs: [id]);
|
|
|
|
if (maps.isEmpty) return null;
|
|
return maps.first;
|
|
}
|
|
|
|
// ========== Product CRUD ======
|
|
|
|
Future<int> insert(Product product) async {
|
|
final db = await instance.database;
|
|
return await db.insert('products', product.toMap());
|
|
}
|
|
|
|
Future<List<Product>> getProducts() async {
|
|
final db = await instance.database;
|
|
final List<Map<String, dynamic>> 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<Product?> 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<int> update(Product product) async {
|
|
final db = await instance.database;
|
|
return await db.update('products', product.toMap(), where: 'id = ?', whereArgs: [product.id]);
|
|
}
|
|
|
|
Future<int> delete(int id) async {
|
|
final db = await instance.database;
|
|
return await db.update('products', {'is_deleted': 1}, where: 'id = ?', whereArgs: [id]);
|
|
}
|
|
|
|
Future<void> close() async {
|
|
final db = await instance.database;
|
|
db.close();
|
|
}
|
|
} |