h-1.flutter.0/lib/services/database_helper.dart
2026-03-04 14:55:40 +09:00

715 lines
24 KiB
Dart

import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
class DatabaseHelper {
static const _databaseVersion = 35;
static final DatabaseHelper _instance = DatabaseHelper._internal();
static Database? _database;
factory DatabaseHelper() => _instance;
DatabaseHelper._internal();
Future<Database> get database async {
if (_database != null) return _database!;
_database = await _initDatabase();
return _database!;
}
Future<Database> _initDatabase() async {
String path = join(await getDatabasesPath(), 'gemi_invoice.db');
return await openDatabase(
path,
version: _databaseVersion,
onCreate: _onCreate,
onUpgrade: _onUpgrade,
);
}
Future<void> _onUpgrade(Database db, int oldVersion, int newVersion) async {
if (oldVersion < 2) {
await db.execute('ALTER TABLE invoices ADD COLUMN tax_rate REAL DEFAULT 0.10');
}
if (oldVersion < 3) {
await db.execute('''
CREATE TABLE company_info (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
zip_code TEXT,
address TEXT,
tel TEXT,
fax TEXT,
email TEXT,
url TEXT,
default_tax_rate REAL DEFAULT 0.10,
seal_path TEXT
)
''');
}
if (oldVersion < 4) {
await db.execute('ALTER TABLE products ADD COLUMN barcode TEXT');
}
if (oldVersion < 5) {
await db.execute('ALTER TABLE invoices ADD COLUMN customer_formal_name TEXT');
}
if (oldVersion < 6) {
await db.execute('ALTER TABLE products ADD COLUMN category TEXT');
await db.execute('CREATE INDEX idx_products_name ON products(name)');
await db.execute('CREATE INDEX idx_products_barcode ON products(barcode)');
await db.execute('''
CREATE TABLE activity_logs (
id TEXT PRIMARY KEY,
action TEXT NOT NULL,
target_type TEXT NOT NULL,
target_id TEXT,
details TEXT,
timestamp TEXT NOT NULL
)
''');
}
if (oldVersion < 7) {
await db.execute('ALTER TABLE products ADD COLUMN stock_quantity INTEGER DEFAULT 0');
await db.execute('ALTER TABLE invoices ADD COLUMN document_type TEXT DEFAULT "invoice"');
await db.execute('ALTER TABLE invoice_items ADD COLUMN product_id TEXT');
}
if (oldVersion < 8) {
await db.execute('ALTER TABLE invoices ADD COLUMN latitude REAL');
await db.execute('ALTER TABLE invoices ADD COLUMN longitude REAL');
await db.execute('''
CREATE TABLE app_gps_history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
latitude REAL NOT NULL,
longitude REAL NOT NULL,
timestamp TEXT NOT NULL
)
''');
}
if (oldVersion < 9) {
await db.execute('ALTER TABLE company_info ADD COLUMN tax_display_mode TEXT DEFAULT "normal"');
}
if (oldVersion < 10) {
await db.execute('ALTER TABLE invoices ADD COLUMN terminal_id TEXT DEFAULT "T1"');
await db.execute('ALTER TABLE invoices ADD COLUMN content_hash TEXT');
}
if (oldVersion < 11) {
await db.execute('ALTER TABLE invoices ADD COLUMN is_draft INTEGER DEFAULT 0');
}
if (oldVersion < 12) {
await db.execute('ALTER TABLE invoices ADD COLUMN subject TEXT');
}
if (oldVersion < 13) {
await db.execute('ALTER TABLE company_info ADD COLUMN registration_number TEXT');
}
if (oldVersion < 14) {
await _safeAddColumn(db, 'invoices', 'subject TEXT');
}
if (oldVersion < 15) {
await _safeAddColumn(db, 'invoices', 'is_locked INTEGER DEFAULT 0');
await _safeAddColumn(db, 'customers', 'is_locked INTEGER DEFAULT 0');
await _safeAddColumn(db, 'products', 'is_locked INTEGER DEFAULT 0');
}
if (oldVersion < 16) {
await db.execute('''
CREATE TABLE customer_contacts (
id TEXT PRIMARY KEY,
customer_id TEXT NOT NULL,
email TEXT,
tel TEXT,
address TEXT,
version INTEGER NOT NULL,
is_active INTEGER DEFAULT 1,
created_at TEXT NOT NULL,
FOREIGN KEY(customer_id) REFERENCES customers(id) ON DELETE CASCADE
)
''');
await db.execute('CREATE INDEX idx_customer_contacts_cust ON customer_contacts(customer_id)');
// 既存顧客の連絡先を初期バージョンとしてコピー
final existing = await db.query('customers');
final now = DateTime.now().toIso8601String();
for (final row in existing) {
final contactId = "${row['id']}_v1";
await db.insert('customer_contacts', {
'id': contactId,
'customer_id': row['id'],
'email': null,
'tel': row['tel'],
'address': row['address'],
'version': 1,
'is_active': 1,
'created_at': now,
});
}
}
if (oldVersion < 17) {
await _safeAddColumn(db, 'invoices', 'contact_version_id INTEGER');
await _safeAddColumn(db, 'invoices', 'contact_email_snapshot TEXT');
await _safeAddColumn(db, 'invoices', 'contact_tel_snapshot TEXT');
await _safeAddColumn(db, 'invoices', 'contact_address_snapshot TEXT');
}
if (oldVersion < 20) {
await _safeAddColumn(db, 'company_info', 'fax TEXT');
await _safeAddColumn(db, 'company_info', 'email TEXT');
await _safeAddColumn(db, 'company_info', 'url TEXT');
}
if (oldVersion < 18) {
await _safeAddColumn(db, 'customers', 'contact_version_id INTEGER');
}
if (oldVersion < 19) {
await _safeAddColumn(db, 'customers', 'head_char1 TEXT');
await _safeAddColumn(db, 'customers', 'head_char2 TEXT');
await db.execute('CREATE INDEX IF NOT EXISTS idx_customers_head1 ON customers(head_char1)');
await db.execute('CREATE INDEX IF NOT EXISTS idx_customers_head2 ON customers(head_char2)');
}
if (oldVersion < 20) {
await _safeAddColumn(db, 'customers', 'email TEXT');
}
if (oldVersion < 22) {
await db.execute('''
CREATE TABLE IF NOT EXISTS app_settings (
key TEXT PRIMARY KEY,
value TEXT
)
''');
}
if (oldVersion < 23) {
await _safeAddColumn(db, 'customers', 'is_hidden INTEGER DEFAULT 0');
await _safeAddColumn(db, 'products', 'is_hidden INTEGER DEFAULT 0');
await db.execute('CREATE INDEX IF NOT EXISTS idx_customers_hidden ON customers(is_hidden)');
await db.execute('CREATE INDEX IF NOT EXISTS idx_products_hidden ON products(is_hidden)');
}
if (oldVersion < 24) {
await db.execute('''
CREATE TABLE IF NOT EXISTS master_hidden (
master_type TEXT NOT NULL,
master_id TEXT NOT NULL,
is_hidden INTEGER DEFAULT 0,
PRIMARY KEY(master_type, master_id)
)
''');
await db.execute('CREATE INDEX IF NOT EXISTS idx_master_hidden_type ON master_hidden(master_type)');
}
if (oldVersion < 25) {
await _safeAddColumn(db, 'invoices', 'company_snapshot TEXT');
await _safeAddColumn(db, 'invoices', 'company_seal_hash TEXT');
await _safeAddColumn(db, 'invoices', 'meta_json TEXT');
await _safeAddColumn(db, 'invoices', 'meta_hash TEXT');
}
if (oldVersion < 26) {
await db.execute('''
CREATE TABLE IF NOT EXISTS chat_messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
message_id TEXT UNIQUE NOT NULL,
client_id TEXT NOT NULL,
direction TEXT NOT NULL,
body TEXT NOT NULL,
created_at INTEGER NOT NULL,
synced INTEGER DEFAULT 0,
delivered_at INTEGER
)
''');
await db.execute('CREATE INDEX IF NOT EXISTS idx_chat_messages_created_at ON chat_messages(created_at)');
}
if (oldVersion < 27) {
await _createSalesOrderTables(db);
}
if (oldVersion < 28) {
await _createShipmentTables(db);
}
if (oldVersion < 29) {
await _createInventoryTables(db);
}
if (oldVersion < 30) {
await _createReceivableTables(db);
}
if (oldVersion < 31) {
await _safeAddColumn(db, 'invoices', 'previous_chain_hash TEXT');
await _safeAddColumn(db, 'invoices', 'chain_hash TEXT');
await _safeAddColumn(db, 'invoices', "chain_status TEXT DEFAULT 'pending'");
}
if (oldVersion < 32) {
await _createSupplierTables(db);
await _createDepartmentTables(db);
await _createStaffTables(db);
await _createTaxSettingsTable(db);
}
if (oldVersion < 33) {
await _safeAddColumn(db, 'shipments', 'tracking_url TEXT');
await _safeAddColumn(db, 'shipments', 'label_pdf_path TEXT');
}
if (oldVersion < 34) {
await _createSalesEntryTables(db);
}
if (oldVersion < 35) {
await _safeAddColumn(db, 'products', 'wholesale_price INTEGER DEFAULT 0');
await _safeAddColumn(db, 'sales_line_items', 'cost_amount INTEGER DEFAULT 0');
await _safeAddColumn(db, 'sales_line_items', 'cost_is_provisional INTEGER DEFAULT 0');
}
}
Future<void> _onCreate(Database db, int version) async {
await db.execute('''
CREATE TABLE customers (
id TEXT PRIMARY KEY,
display_name TEXT NOT NULL,
formal_name TEXT NOT NULL,
title TEXT DEFAULT '',
department TEXT,
address TEXT,
tel TEXT,
email TEXT,
contact_version_id INTEGER,
odoo_id TEXT,
head_char1 TEXT,
head_char2 TEXT,
is_locked INTEGER DEFAULT 0,
is_hidden INTEGER DEFAULT 0,
is_synced INTEGER DEFAULT 0,
updated_at TEXT NOT NULL
)
''');
await db.execute('''
CREATE TABLE customer_gps_history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
customer_id TEXT NOT NULL,
latitude REAL NOT NULL,
longitude REAL NOT NULL,
timestamp TEXT NOT NULL,
FOREIGN KEY (customer_id) REFERENCES customers (id) ON DELETE CASCADE
)
''');
await db.execute('''
CREATE TABLE customer_contacts (
id TEXT PRIMARY KEY,
customer_id TEXT NOT NULL,
email TEXT,
tel TEXT,
address TEXT,
version INTEGER NOT NULL,
is_active INTEGER DEFAULT 1,
created_at TEXT NOT NULL,
FOREIGN KEY(customer_id) REFERENCES customers(id) ON DELETE CASCADE
)
''');
await db.execute('CREATE INDEX idx_customer_contacts_cust ON customer_contacts(customer_id)');
// 商品マスター
await db.execute('''
CREATE TABLE products (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
default_unit_price INTEGER,
wholesale_price INTEGER DEFAULT 0,
barcode TEXT,
category TEXT,
stock_quantity INTEGER DEFAULT 0,
is_locked INTEGER DEFAULT 0,
is_hidden INTEGER DEFAULT 0,
odoo_id TEXT
)
''');
await db.execute('CREATE INDEX idx_products_name ON products(name)');
await db.execute('CREATE INDEX idx_products_barcode ON products(barcode)');
await db.execute('''
CREATE TABLE master_hidden (
master_type TEXT NOT NULL,
master_id TEXT NOT NULL,
is_hidden INTEGER DEFAULT 0,
PRIMARY KEY(master_type, master_id)
)
''');
await db.execute('CREATE INDEX idx_master_hidden_type ON master_hidden(master_type)');
await _createSupplierTables(db);
await _createDepartmentTables(db);
await _createStaffTables(db);
await _createTaxSettingsTable(db);
// 伝票マスター
await db.execute('''
CREATE TABLE invoices (
id TEXT PRIMARY KEY,
customer_id TEXT NOT NULL,
date TEXT NOT NULL,
notes TEXT,
subject TEXT,
file_path TEXT,
total_amount INTEGER,
tax_rate REAL DEFAULT 0.10,
document_type TEXT DEFAULT "invoice",
customer_formal_name TEXT,
odoo_id TEXT,
is_synced INTEGER DEFAULT 0,
updated_at TEXT NOT NULL,
latitude REAL,
longitude REAL,
terminal_id TEXT DEFAULT "T1",
content_hash TEXT,
is_draft INTEGER DEFAULT 0,
is_locked INTEGER DEFAULT 0,
contact_version_id INTEGER,
contact_email_snapshot TEXT,
contact_tel_snapshot TEXT,
contact_address_snapshot TEXT,
company_snapshot TEXT,
company_seal_hash TEXT,
meta_json TEXT,
meta_hash TEXT,
FOREIGN KEY (customer_id) REFERENCES customers (id)
)
''');
await db.execute('''
CREATE TABLE app_gps_history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
latitude REAL NOT NULL,
longitude REAL NOT NULL,
timestamp TEXT NOT NULL
)
''');
// 伝票明細
await db.execute('''
CREATE TABLE invoice_items (
id TEXT PRIMARY KEY,
invoice_id TEXT NOT NULL,
product_id TEXT,
description TEXT NOT NULL,
quantity INTEGER NOT NULL,
unit_price INTEGER NOT NULL,
FOREIGN KEY (invoice_id) REFERENCES invoices (id) ON DELETE CASCADE
)
''');
await db.execute('''
CREATE TABLE company_info (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
zip_code TEXT,
address TEXT,
tel TEXT,
default_tax_rate REAL DEFAULT 0.10,
seal_path TEXT,
tax_display_mode TEXT DEFAULT "normal",
registration_number TEXT
)
''');
await db.execute('''
CREATE TABLE activity_logs (
id TEXT PRIMARY KEY,
action TEXT NOT NULL,
target_type TEXT NOT NULL,
target_id TEXT,
details TEXT,
timestamp TEXT NOT NULL
)
''');
await db.execute('''
CREATE TABLE app_settings (
key TEXT PRIMARY KEY,
value TEXT
)
''');
await db.execute('''
CREATE TABLE chat_messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
message_id TEXT UNIQUE NOT NULL,
client_id TEXT NOT NULL,
direction TEXT NOT NULL,
body TEXT NOT NULL,
created_at INTEGER NOT NULL,
synced INTEGER DEFAULT 0,
delivered_at INTEGER
)
''');
await db.execute('CREATE INDEX idx_chat_messages_created_at ON chat_messages(created_at)');
await _createSalesOrderTables(db);
await _createShipmentTables(db);
await _createInventoryTables(db);
await _createReceivableTables(db);
await _createSalesEntryTables(db);
await _safeAddColumn(db, 'invoices', 'previous_chain_hash TEXT');
await _safeAddColumn(db, 'invoices', 'chain_hash TEXT');
await _safeAddColumn(db, 'invoices', "chain_status TEXT DEFAULT 'pending'");
}
Future<void> _safeAddColumn(Database db, String table, String columnDef) async {
try {
await db.execute('ALTER TABLE $table ADD COLUMN $columnDef');
} catch (_) {
// Ignore if the column already exists.
}
}
Future<void> _createSalesOrderTables(Database db) async {
await db.execute('''
CREATE TABLE IF NOT EXISTS sales_orders (
id TEXT PRIMARY KEY,
order_number TEXT,
customer_id TEXT NOT NULL,
customer_name_snapshot TEXT,
order_date TEXT NOT NULL,
requested_ship_date TEXT,
status TEXT NOT NULL,
subtotal INTEGER DEFAULT 0,
tax_amount INTEGER DEFAULT 0,
total_amount INTEGER DEFAULT 0,
notes TEXT,
assigned_to TEXT,
workflow_stage TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
FOREIGN KEY(customer_id) REFERENCES customers(id)
)
''');
await db.execute('CREATE INDEX IF NOT EXISTS idx_sales_orders_customer ON sales_orders(customer_id)');
await db.execute('CREATE INDEX IF NOT EXISTS idx_sales_orders_status ON sales_orders(status)');
await db.execute('CREATE INDEX IF NOT EXISTS idx_sales_orders_date ON sales_orders(order_date)');
await db.execute('''
CREATE TABLE IF NOT EXISTS sales_order_items (
id TEXT PRIMARY KEY,
order_id TEXT NOT NULL,
product_id TEXT,
description TEXT NOT NULL,
quantity INTEGER NOT NULL,
unit_price INTEGER NOT NULL,
tax_rate REAL DEFAULT 0,
sort_index INTEGER DEFAULT 0,
FOREIGN KEY(order_id) REFERENCES sales_orders(id) ON DELETE CASCADE
)
''');
await db.execute('CREATE INDEX IF NOT EXISTS idx_sales_order_items_order ON sales_order_items(order_id)');
}
Future<void> _createShipmentTables(Database db) async {
await db.execute('''
CREATE TABLE IF NOT EXISTS shipments (
id TEXT PRIMARY KEY,
order_id TEXT,
order_number_snapshot TEXT,
customer_name_snapshot TEXT,
scheduled_ship_date TEXT,
actual_ship_date TEXT,
status TEXT NOT NULL,
carrier_name TEXT,
tracking_number TEXT,
tracking_url TEXT,
label_pdf_path TEXT,
notes TEXT,
picking_completed_at TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
FOREIGN KEY(order_id) REFERENCES sales_orders(id)
)
''');
await db.execute('CREATE INDEX IF NOT EXISTS idx_shipments_status ON shipments(status)');
await db.execute('CREATE INDEX IF NOT EXISTS idx_shipments_order ON shipments(order_id)');
await db.execute('''
CREATE TABLE IF NOT EXISTS shipment_items (
id TEXT PRIMARY KEY,
shipment_id TEXT NOT NULL,
order_item_id TEXT,
description TEXT NOT NULL,
quantity INTEGER NOT NULL,
FOREIGN KEY(shipment_id) REFERENCES shipments(id) ON DELETE CASCADE,
FOREIGN KEY(order_item_id) REFERENCES sales_order_items(id)
)
''');
await db.execute('CREATE INDEX IF NOT EXISTS idx_shipment_items_shipment ON shipment_items(shipment_id)');
}
Future<void> _createInventoryTables(Database db) async {
await db.execute('''
CREATE TABLE IF NOT EXISTS inventory_movements (
id TEXT PRIMARY KEY,
product_id TEXT NOT NULL,
product_name_snapshot TEXT,
movement_type TEXT NOT NULL,
quantity INTEGER NOT NULL,
quantity_delta INTEGER NOT NULL,
reference TEXT,
notes TEXT,
created_at TEXT NOT NULL,
FOREIGN KEY(product_id) REFERENCES products(id)
)
''');
await db.execute('CREATE INDEX IF NOT EXISTS idx_inventory_movements_product ON inventory_movements(product_id)');
await db.execute('CREATE INDEX IF NOT EXISTS idx_inventory_movements_created ON inventory_movements(created_at)');
}
Future<void> _createReceivableTables(Database db) async {
await db.execute('''
CREATE TABLE IF NOT EXISTS receivable_payments (
id TEXT PRIMARY KEY,
invoice_id TEXT NOT NULL,
amount INTEGER NOT NULL,
payment_date TEXT NOT NULL,
method TEXT NOT NULL,
notes TEXT,
created_at TEXT NOT NULL,
FOREIGN KEY(invoice_id) REFERENCES invoices(id) ON DELETE CASCADE
)
''');
await db.execute('CREATE INDEX IF NOT EXISTS idx_receivable_payments_invoice ON receivable_payments(invoice_id)');
await db.execute('CREATE INDEX IF NOT EXISTS idx_receivable_payments_date ON receivable_payments(payment_date)');
}
Future<void> _createSupplierTables(Database db) async {
await db.execute('''
CREATE TABLE IF NOT EXISTS suppliers (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
contact_person TEXT,
email TEXT,
tel TEXT,
address TEXT,
closing_day INTEGER,
payment_site_days INTEGER DEFAULT 30,
notes TEXT,
is_hidden INTEGER DEFAULT 0,
updated_at TEXT NOT NULL
)
''');
await db.execute('CREATE INDEX IF NOT EXISTS idx_suppliers_name ON suppliers(name)');
}
Future<void> _createDepartmentTables(Database db) async {
await db.execute('''
CREATE TABLE IF NOT EXISTS departments (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
code TEXT,
description TEXT,
is_active INTEGER DEFAULT 1,
updated_at TEXT NOT NULL
)
''');
await db.execute('CREATE INDEX IF NOT EXISTS idx_departments_name ON departments(name)');
}
Future<void> _createStaffTables(Database db) async {
await db.execute('''
CREATE TABLE IF NOT EXISTS staff_members (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
email TEXT,
tel TEXT,
role TEXT,
department_id TEXT,
permission_level TEXT,
is_active INTEGER DEFAULT 1,
updated_at TEXT NOT NULL,
FOREIGN KEY(department_id) REFERENCES departments(id) ON DELETE SET NULL
)
''');
await db.execute('CREATE INDEX IF NOT EXISTS idx_staff_department ON staff_members(department_id)');
}
Future<void> _createTaxSettingsTable(Database db) async {
await db.execute('''
CREATE TABLE IF NOT EXISTS tax_settings (
id TEXT PRIMARY KEY,
rate REAL NOT NULL,
rounding_mode TEXT NOT NULL,
updated_at TEXT NOT NULL
)
''');
final existing = await db.query('tax_settings', limit: 1);
if (existing.isEmpty) {
await db.insert('tax_settings', {
'id': 'default',
'rate': 0.1,
'rounding_mode': 'round',
'updated_at': DateTime.now().toIso8601String(),
});
}
}
Future<void> _createSalesEntryTables(Database db) async {
await db.execute('''
CREATE TABLE IF NOT EXISTS sales_entries (
id TEXT PRIMARY KEY,
customer_id TEXT,
customer_name_snapshot TEXT,
subject TEXT,
issue_date TEXT NOT NULL,
status TEXT NOT NULL,
amount_tax_excl INTEGER DEFAULT 0,
tax_amount INTEGER DEFAULT 0,
amount_tax_incl INTEGER DEFAULT 0,
notes TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
FOREIGN KEY(customer_id) REFERENCES customers(id)
)
''');
await db.execute('CREATE INDEX IF NOT EXISTS idx_sales_entries_date ON sales_entries(issue_date)');
await db.execute('CREATE INDEX IF NOT EXISTS idx_sales_entries_status ON sales_entries(status)');
await db.execute('''
CREATE TABLE IF NOT EXISTS sales_line_items (
id TEXT PRIMARY KEY,
sales_entry_id TEXT NOT NULL,
product_id TEXT,
description TEXT NOT NULL,
quantity INTEGER NOT NULL,
unit_price INTEGER NOT NULL,
tax_rate REAL DEFAULT 0,
line_total INTEGER NOT NULL,
cost_amount INTEGER DEFAULT 0,
cost_is_provisional INTEGER DEFAULT 0,
source_invoice_id TEXT,
source_invoice_item_id TEXT,
FOREIGN KEY(sales_entry_id) REFERENCES sales_entries(id) ON DELETE CASCADE
)
''');
await db.execute('CREATE INDEX IF NOT EXISTS idx_sales_line_items_entry ON sales_line_items(sales_entry_id)');
await db.execute('''
CREATE TABLE IF NOT EXISTS sales_entry_sources (
id TEXT PRIMARY KEY,
sales_entry_id TEXT NOT NULL,
invoice_id TEXT NOT NULL,
imported_at TEXT NOT NULL,
invoice_hash_snapshot TEXT,
FOREIGN KEY(sales_entry_id) REFERENCES sales_entries(id) ON DELETE CASCADE,
FOREIGN KEY(invoice_id) REFERENCES invoices(id)
)
''');
await db.execute('CREATE UNIQUE INDEX IF NOT EXISTS idx_sales_entry_sources_unique ON sales_entry_sources(sales_entry_id, invoice_id)');
await db.execute('''
CREATE TABLE IF NOT EXISTS sales_receipts (
id TEXT PRIMARY KEY,
customer_id TEXT,
payment_date TEXT NOT NULL,
method TEXT,
amount INTEGER NOT NULL,
notes TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
FOREIGN KEY(customer_id) REFERENCES customers(id)
)
''');
await db.execute('CREATE INDEX IF NOT EXISTS idx_sales_receipts_date ON sales_receipts(payment_date)');
await db.execute('''
CREATE TABLE IF NOT EXISTS sales_receipt_links (
receipt_id TEXT NOT NULL,
sales_entry_id TEXT NOT NULL,
allocated_amount INTEGER NOT NULL,
PRIMARY KEY(receipt_id, sales_entry_id),
FOREIGN KEY(receipt_id) REFERENCES sales_receipts(id) ON DELETE CASCADE,
FOREIGN KEY(sales_entry_id) REFERENCES sales_entries(id) ON DELETE CASCADE
)
''');
}
}