import 'package:sqflite/sqflite.dart'; import 'package:path/path.dart'; class DatabaseHelper { static const _databaseVersion = 37; static final DatabaseHelper _instance = DatabaseHelper._internal(); static Database? _database; factory DatabaseHelper() => _instance; DatabaseHelper._internal(); Future get database async { if (_database != null) return _database!; _database = await _initDatabase(); return _database!; } Future _initDatabase() async { String path = join(await getDatabasesPath(), 'gemi_invoice.db'); return await openDatabase( path, version: _databaseVersion, onCreate: _onCreate, onUpgrade: _onUpgrade, ); } Future _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'); } if (oldVersion < 36) { await _safeAddColumn(db, 'sales_entries', 'settlement_method TEXT'); await _safeAddColumn(db, 'sales_entries', 'settlement_card_company TEXT'); await _safeAddColumn(db, 'sales_entries', 'settlement_due_date TEXT'); } if (oldVersion < 37) { await _createPurchaseEntryTables(db); } } Future _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 _createPurchaseEntryTables(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 _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 _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 _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 _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 _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 _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 _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 _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 _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 _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, settlement_method TEXT, settlement_card_company TEXT, settlement_due_date 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 ) '''); } Future _createPurchaseEntryTables(Database db) async { await db.execute(''' CREATE TABLE IF NOT EXISTS purchase_entries ( id TEXT PRIMARY KEY, supplier_id TEXT, supplier_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(supplier_id) REFERENCES suppliers(id) ) '''); await db.execute('CREATE INDEX IF NOT EXISTS idx_purchase_entries_supplier ON purchase_entries(supplier_id)'); await db.execute('CREATE INDEX IF NOT EXISTS idx_purchase_entries_issue_date ON purchase_entries(issue_date)'); await db.execute(''' CREATE TABLE IF NOT EXISTS purchase_line_items ( id TEXT PRIMARY KEY, purchase_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 DEFAULT 0, FOREIGN KEY(purchase_entry_id) REFERENCES purchase_entries(id) ON DELETE CASCADE, FOREIGN KEY(product_id) REFERENCES products(id) ) '''); await db.execute('CREATE INDEX IF NOT EXISTS idx_purchase_line_items_entry ON purchase_line_items(purchase_entry_id)'); await db.execute(''' CREATE TABLE IF NOT EXISTS purchase_receipts ( id TEXT PRIMARY KEY, supplier_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(supplier_id) REFERENCES suppliers(id) ) '''); await db.execute('CREATE INDEX IF NOT EXISTS idx_purchase_receipts_supplier ON purchase_receipts(supplier_id)'); await db.execute('CREATE INDEX IF NOT EXISTS idx_purchase_receipts_payment_date ON purchase_receipts(payment_date)'); await db.execute(''' CREATE TABLE IF NOT EXISTS purchase_receipt_links ( receipt_id TEXT NOT NULL, purchase_entry_id TEXT NOT NULL, allocated_amount INTEGER NOT NULL, PRIMARY KEY(receipt_id, purchase_entry_id), FOREIGN KEY(receipt_id) REFERENCES purchase_receipts(id) ON DELETE CASCADE, FOREIGN KEY(purchase_entry_id) REFERENCES purchase_entries(id) ON DELETE CASCADE ) '''); await db.execute('CREATE INDEX IF NOT EXISTS idx_purchase_receipt_links_entry ON purchase_receipt_links(purchase_entry_id)'); } }