Add products, inventory units, stock receipts, and price history
- product table (catalog definition, no cost column — cost tracked per receipt/unit) - inventory_unit table (serialized items with serial number, condition, status) - stock_receipt table (FIFO cost tracking — records every stock receive event with cost_per_unit, supplier, date) - price_history table (logs every retail price change for margin analysis over time) - product_supplier join table (many-to-many, tracks supplier SKU and preferred supplier) - Full CRUD routes + search (name, SKU, UPC, brand) - Inventory unit routes nested under products - Price changes auto-logged on product update - 33 tests passing
This commit is contained in:
@@ -25,3 +25,48 @@ export type SupplierCreateInput = z.infer<typeof SupplierCreateSchema>
|
||||
|
||||
export const SupplierUpdateSchema = SupplierCreateSchema.partial()
|
||||
export type SupplierUpdateInput = z.infer<typeof SupplierUpdateSchema>
|
||||
|
||||
export const ItemCondition = z.enum(['new', 'excellent', 'good', 'fair', 'poor'])
|
||||
export const UnitStatus = z.enum(['available', 'sold', 'rented', 'in_repair', 'retired'])
|
||||
|
||||
export const ProductCreateSchema = z.object({
|
||||
sku: z.string().max(100).optional(),
|
||||
upc: z.string().max(100).optional(),
|
||||
name: z.string().min(1).max(255),
|
||||
description: z.string().optional(),
|
||||
brand: z.string().max(255).optional(),
|
||||
model: z.string().max(255).optional(),
|
||||
categoryId: z.string().uuid().optional(),
|
||||
locationId: z.string().uuid().optional(),
|
||||
isSerialized: z.boolean().default(false),
|
||||
isRental: z.boolean().default(false),
|
||||
isDualUseRepair: z.boolean().default(false),
|
||||
price: z.number().min(0).optional(),
|
||||
minPrice: z.number().min(0).optional(),
|
||||
rentalRateMonthly: z.number().min(0).optional(),
|
||||
qtyOnHand: z.number().int().min(0).default(0),
|
||||
qtyReorderPoint: z.number().int().min(0).optional(),
|
||||
})
|
||||
export type ProductCreateInput = z.infer<typeof ProductCreateSchema>
|
||||
|
||||
export const ProductUpdateSchema = ProductCreateSchema.partial()
|
||||
export type ProductUpdateInput = z.infer<typeof ProductUpdateSchema>
|
||||
|
||||
export const ProductSearchSchema = z.object({
|
||||
q: z.string().min(1).max(255),
|
||||
})
|
||||
|
||||
export const InventoryUnitCreateSchema = z.object({
|
||||
productId: z.string().uuid(),
|
||||
locationId: z.string().uuid().optional(),
|
||||
serialNumber: z.string().max(255).optional(),
|
||||
condition: ItemCondition.default('new'),
|
||||
status: UnitStatus.default('available'),
|
||||
purchaseDate: z.string().date().optional(),
|
||||
purchaseCost: z.number().min(0).optional(),
|
||||
notes: z.string().optional(),
|
||||
})
|
||||
export type InventoryUnitCreateInput = z.infer<typeof InventoryUnitCreateSchema>
|
||||
|
||||
export const InventoryUnitUpdateSchema = InventoryUnitCreateSchema.omit({ productId: true }).partial()
|
||||
export type InventoryUnitUpdateInput = z.infer<typeof InventoryUnitUpdateSchema>
|
||||
|
||||
Reference in New Issue
Block a user