Files
stockfill/src/db/index.ts
T

214 lines
7.2 KiB
TypeScript

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<Product>;
areas!: Table<Area>;
pickLists!: Table<PickList>;
pickItems!: Table<PickItem>;
categories!: Table<Category>;
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<string>();
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<PickItem> & {
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);
};