import Dexie, { Table } from 'dexie'; import { v4 as uuidv4 } from 'uuid'; import { Area } from '../models/Area'; import { Category } from '../models/Category'; import { PickItem } from '../models/PickItem'; import { PickList } from '../models/PickList'; import { Product } from '../models/Product'; import { applyMigrations } from './migrations'; import { seedDatabase } from './seed'; export class StockFillDB extends Dexie { products!: Table; areas!: Table; pickLists!: Table; pickItems!: Table; categories!: Table; constructor() { super('stockfill'); this.version(1).stores({ products: 'id, name, category, barcode, archived, created_at, updated_at', areas: 'id, name, created_at, updated_at', pickLists: 'id, area_id, created_at, completed_at', pickItems: 'id, pick_list_id, product_id, status, created_at, updated_at', }); this.version(2) .stores({ products: 'id, name, category, barcode, archived, created_at, updated_at', areas: 'id, name, created_at, updated_at', pickLists: 'id, area_id, created_at, completed_at', pickItems: 'id, pick_list_id, product_id, status, created_at, updated_at', categories: 'id, name, created_at, updated_at', }) .upgrade(async (tx) => { const existingCategories = await tx.table('categories').count(); if (existingCategories > 0) return; const products = await tx.table('products').toArray(); const uniqueCategories = Array.from(new Set(products.map((product) => product.category))); if (uniqueCategories.length === 0) return; const now = Date.now(); await tx.table('categories').bulkAdd( uniqueCategories.map((name: string) => ({ id: uuidv4(), name, created_at: now, updated_at: now, })), ); }); this.version(3) .stores({ products: 'id, name, category, barcode, archived, created_at, updated_at', areas: 'id, name, created_at, updated_at', pickLists: 'id, area_id, created_at, completed_at', pickItems: 'id, pick_list_id, product_id, status, created_at, updated_at', categories: 'id, name, created_at, updated_at', }) .upgrade(async (tx) => { const products = await tx.table('products').toArray(); const seenBarcodes = new Set(); const now = Date.now(); await Promise.all( products.map((product) => { if (!product.barcode) return undefined; if (seenBarcodes.has(product.barcode)) { return tx.table('products').update(product.id, { barcode: undefined, updated_at: now, }); } seenBarcodes.add(product.barcode); return undefined; }), ); }); this.version(4).stores({ products: 'id, name, category, &barcode, archived, created_at, updated_at', areas: 'id, name, created_at, updated_at', pickLists: 'id, area_id, created_at, completed_at', pickItems: 'id, pick_list_id, product_id, status, created_at, updated_at', categories: 'id, name, created_at, updated_at', }); this.version(5) .stores({ products: 'id, name, category, &barcode, archived, created_at, updated_at', areas: 'id, name, created_at, updated_at', pickLists: 'id, area_id, created_at, completed_at', pickItems: 'id, pick_list_id, product_id, status, is_carton, quantity, created_at, updated_at', categories: 'id, name, created_at, updated_at', }) .upgrade(async (tx) => { const items = await tx.table('pickItems').toArray(); const now = Date.now(); await Promise.all( items.map(async (item) => { const hasNewFields = typeof (item as PickItem).quantity === 'number' && typeof (item as PickItem).is_carton === 'boolean'; if (hasNewFields) return undefined; const legacyUnits = Number( (item as PickItem & { quantity_units?: number }).quantity_units ?? 0, ); const legacyBulk = Number( (item as PickItem & { quantity_bulk?: number }).quantity_bulk ?? 0, ); if (legacyUnits > 0 && legacyBulk > 0) { await tx.table('pickItems').update(item.id, { quantity: legacyUnits, is_carton: false, updated_at: now, }); return tx.table('pickItems').add({ ...item, id: uuidv4(), quantity: legacyBulk, is_carton: true, created_at: (item as PickItem).created_at ?? now, updated_at: now, }); } if (legacyBulk > 0) { return tx.table('pickItems').update(item.id, { quantity: legacyBulk, is_carton: true, updated_at: now, }); } return tx.table('pickItems').update(item.id, { quantity: legacyUnits, is_carton: false, updated_at: now, }); }), ); }); this.version(6) .stores({ products: 'id, name, category, &barcode, archived, created_at, updated_at', areas: 'id, name, created_at, updated_at', pickLists: 'id, area_id, created_at, completed_at', pickItems: 'id, pick_list_id, product_id, status, is_carton, quantity, created_at, updated_at', categories: 'id, name, created_at, updated_at', }) .upgrade(async (tx) => { const items = await tx.table('pickItems').toArray(); const now = Date.now(); await Promise.all( items.map(async (item) => { const legacyUnits = Number((item as PickItem & { quantity_units?: number }).quantity_units ?? 0); const legacyBulk = Number((item as PickItem & { quantity_bulk?: number }).quantity_bulk ?? 0); const hasQuantity = typeof (item as PickItem).quantity === 'number'; const hasCartonFlag = typeof (item as PickItem).is_carton === 'boolean'; const updateData: Partial & { quantity_units?: undefined; quantity_bulk?: undefined; } = {}; if (!hasQuantity) { updateData.quantity = legacyBulk > 0 ? legacyBulk : legacyUnits; } if (!hasCartonFlag) { updateData.is_carton = legacyBulk > 0 && legacyUnits === 0; } if ('quantity_units' in item) { updateData.quantity_units = undefined; } if ('quantity_bulk' in item) { updateData.quantity_bulk = undefined; } if (Object.keys(updateData).length === 0) return undefined; updateData.updated_at = now; return tx.table('pickItems').update(item.id, updateData); }), ); }); } } export const db = new StockFillDB(); export const initializeDatabase = async () => { await applyMigrations(db); await seedDatabase(db); };