From bde3ad64fd783c45fb953d9f2c072c0b9672f9ec Mon Sep 17 00:00:00 2001 From: Ryan Moon Date: Tue, 31 Mar 2026 05:08:01 -0500 Subject: [PATCH] Fix code review items: atomic qty increment, unit updatedAt, suppliers/all endpoint, SKU unique index --- packages/admin/src/api/inventory.ts | 8 ++++++++ .../src/db/migrations/0038_product_sku_unique.sql | 2 ++ packages/backend/src/routes/v1/inventory.ts | 5 +++++ .../backend/src/services/inventory.service.ts | 8 ++++++++ packages/backend/src/services/product.service.ts | 15 ++++++--------- 5 files changed, 29 insertions(+), 9 deletions(-) create mode 100644 packages/backend/src/db/migrations/0038_product_sku_unique.sql diff --git a/packages/admin/src/api/inventory.ts b/packages/admin/src/api/inventory.ts index d6e6b16..8dc9b8e 100644 --- a/packages/admin/src/api/inventory.ts +++ b/packages/admin/src/api/inventory.ts @@ -36,6 +36,7 @@ export const categoryMutations = { export const supplierKeys = { all: ['suppliers'] as const, + allSuppliers: ['suppliers', 'all'] as const, list: (params: PaginationInput) => [...supplierKeys.all, 'list', params] as const, detail: (id: string) => [...supplierKeys.all, 'detail', id] as const, } @@ -47,6 +48,13 @@ export function supplierListOptions(params: PaginationInput) { }) } +export function supplierAllOptions() { + return queryOptions({ + queryKey: supplierKeys.allSuppliers, + queryFn: () => api.get<{ data: Supplier[] }>('/v1/suppliers/all'), + }) +} + export const supplierMutations = { create: (data: Record) => api.post('/v1/suppliers', data), update: (id: string, data: Record) => api.patch(`/v1/suppliers/${id}`, data), diff --git a/packages/backend/src/db/migrations/0038_product_sku_unique.sql b/packages/backend/src/db/migrations/0038_product_sku_unique.sql new file mode 100644 index 0000000..b40d166 --- /dev/null +++ b/packages/backend/src/db/migrations/0038_product_sku_unique.sql @@ -0,0 +1,2 @@ +-- Add unique index on products.sku (null values are excluded from uniqueness) +CREATE UNIQUE INDEX IF NOT EXISTS products_sku_unique ON product (sku) WHERE sku IS NOT NULL; diff --git a/packages/backend/src/routes/v1/inventory.ts b/packages/backend/src/routes/v1/inventory.ts index ac28624..ea3a86f 100644 --- a/packages/backend/src/routes/v1/inventory.ts +++ b/packages/backend/src/routes/v1/inventory.ts @@ -73,6 +73,11 @@ export const inventoryRoutes: FastifyPluginAsync = async (app) => { return reply.send(result) }) + app.get('/suppliers/all', { preHandler: [app.authenticate, app.requirePermission('inventory.view')] }, async (request, reply) => { + const rows = await SupplierService.listAll(app.db) + return reply.send({ data: rows }) + }) + app.get('/suppliers/:id', { preHandler: [app.authenticate, app.requirePermission('inventory.view')] }, async (request, reply) => { const { id } = request.params as { id: string } const supplier = await SupplierService.getById(app.db, id) diff --git a/packages/backend/src/services/inventory.service.ts b/packages/backend/src/services/inventory.service.ts index 35436f5..a0ce26d 100644 --- a/packages/backend/src/services/inventory.service.ts +++ b/packages/backend/src/services/inventory.service.ts @@ -131,6 +131,14 @@ export const SupplierService = { return paginatedResponse(data, total, params.page, params.limit) }, + async listAll(db: PostgresJsDatabase) { + return db + .select() + .from(suppliers) + .where(eq(suppliers.isActive, true)) + .orderBy(suppliers.name) + }, + async update(db: PostgresJsDatabase, id: string, input: SupplierUpdateInput) { const [supplier] = await db .update(suppliers) diff --git a/packages/backend/src/services/product.service.ts b/packages/backend/src/services/product.service.ts index b59debb..ca3620b 100644 --- a/packages/backend/src/services/product.service.ts +++ b/packages/backend/src/services/product.service.ts @@ -225,7 +225,7 @@ export const InventoryUnitService = { if (!valid) throw new ValidationError(`Invalid status: "${input.status}"`) } - const updates: Record = { ...input } + const updates: Record = { ...input, updatedAt: new Date() } if (input.purchaseCost !== undefined) updates.purchaseCost = input.purchaseCost.toString() const [unit] = await db @@ -317,14 +317,11 @@ export const StockReceiptService = { }) .returning() - // For non-serialized products, increment qty_on_hand - const [product] = await db.select().from(products).where(eq(products.id, productId)).limit(1) - if (product && !product.isSerialized) { - await db - .update(products) - .set({ qtyOnHand: product.qtyOnHand + input.qty, updatedAt: new Date() }) - .where(eq(products.id, productId)) - } + // For non-serialized products, increment qty_on_hand atomically + await db + .update(products) + .set({ qtyOnHand: sql`${products.qtyOnHand} + ${input.qty}`, updatedAt: new Date() }) + .where(and(eq(products.id, productId), eq(products.isSerialized, false))) return receipt },