Files
lunarfront-app/packages/backend/src/db/schema/inventory.ts
ryan 9d51fb2118 feat: repair-POS integration, receipt formats, manager overrides, price adjustments
- Add thermal/full-page receipt format toggle (per-device, localStorage)
- Full-page receipt uses clean invoice layout matching repair PDF style
- Settings page reorganized into tabbed sections (Store, Locations, Modules, Receipt, POS Security, Advanced)
- Manager override system: configurable PIN prompt for void, refund, discount, cash in/out
- Discount threshold setting: require manager approval above X%
- Consumable product type: tracked for internal job costing, excluded from POS search, receipts, and customer-facing totals
- Repair line item dialog: product picker dropdown for parts/consumables from inventory
- Repair → POS checkout: load ready-for-pickup tickets into repair_payment transactions with proper tax categories (labor=service, parts=goods)
- Transaction completion auto-updates repair ticket status to picked_up
- POS Repairs dialog with Pickup and New Intake tabs, customer account lookup
- Inline price adjustment on cart items: % off, $ off, or set price with live preview
- Order-level discount button with same three input modes
- Backend: migration 0043 (consumable enum + is_consumable flag), createFromRepairTicket service, ready-for-pickup endpoint
- Fix: backend dev script uses --env-file for turbo compatibility

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 01:32:28 +00:00

165 lines
7.1 KiB
TypeScript

import {
pgTable,
uuid,
varchar,
text,
timestamp,
boolean,
integer,
numeric,
date,
pgEnum,
} from 'drizzle-orm/pg-core'
import { locations } from './stores.js'
export const categories = pgTable('category', {
id: uuid('id').primaryKey().defaultRandom(),
parentId: uuid('parent_id'),
name: varchar('name', { length: 255 }).notNull(),
description: text('description'),
sortOrder: integer('sort_order').notNull().default(0),
isActive: boolean('is_active').notNull().default(true),
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
})
export const suppliers = pgTable('supplier', {
id: uuid('id').primaryKey().defaultRandom(),
name: varchar('name', { length: 255 }).notNull(),
contactName: varchar('contact_name', { length: 255 }),
email: varchar('email', { length: 255 }),
phone: varchar('phone', { length: 50 }),
website: varchar('website', { length: 255 }),
accountNumber: varchar('account_number', { length: 100 }),
paymentTerms: varchar('payment_terms', { length: 100 }),
notes: text('notes'),
isActive: boolean('is_active').notNull().default(true),
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
})
// NOTE: item_condition and unit_status pgEnums replaced by lookup tables.
// See lookups.ts for inventory_unit_status and item_condition tables.
// Columns below use varchar referencing the lookup slug.
export const taxCategoryEnum = pgEnum('tax_category', ['goods', 'service', 'exempt'])
export const products = pgTable('product', {
id: uuid('id').primaryKey().defaultRandom(),
locationId: uuid('location_id').references(() => locations.id),
sku: varchar('sku', { length: 100 }),
upc: varchar('upc', { length: 100 }),
name: varchar('name', { length: 255 }).notNull(),
description: text('description'),
brand: varchar('brand', { length: 255 }),
model: varchar('model', { length: 255 }),
categoryId: uuid('category_id').references(() => categories.id),
isSerialized: boolean('is_serialized').notNull().default(false),
isRental: boolean('is_rental').notNull().default(false),
isDualUseRepair: boolean('is_dual_use_repair').notNull().default(false),
isConsumable: boolean('is_consumable').notNull().default(false),
taxCategory: taxCategoryEnum('tax_category').notNull().default('goods'),
price: numeric('price', { precision: 10, scale: 2 }),
minPrice: numeric('min_price', { precision: 10, scale: 2 }),
rentalRateMonthly: numeric('rental_rate_monthly', { precision: 10, scale: 2 }),
qtyOnHand: integer('qty_on_hand').notNull().default(0),
qtyReorderPoint: integer('qty_reorder_point'),
isActive: boolean('is_active').notNull().default(true),
legacyId: varchar('legacy_id', { length: 255 }),
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
})
export const inventoryUnits = pgTable('inventory_unit', {
id: uuid('id').primaryKey().defaultRandom(),
productId: uuid('product_id')
.notNull()
.references(() => products.id),
locationId: uuid('location_id').references(() => locations.id),
serialNumber: varchar('serial_number', { length: 255 }),
condition: varchar('condition', { length: 100 }).notNull().default('new'),
status: varchar('status', { length: 100 }).notNull().default('available'),
purchaseDate: date('purchase_date'),
purchaseCost: numeric('purchase_cost', { precision: 10, scale: 2 }),
notes: text('notes'),
legacyId: varchar('legacy_id', { length: 255 }),
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
})
export const productSuppliers = pgTable('product_supplier', {
id: uuid('id').primaryKey().defaultRandom(),
productId: uuid('product_id')
.notNull()
.references(() => products.id),
supplierId: uuid('supplier_id')
.notNull()
.references(() => suppliers.id),
supplierSku: varchar('supplier_sku', { length: 100 }),
isPreferred: boolean('is_preferred').notNull().default(false),
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
})
export type Category = typeof categories.$inferSelect
export type CategoryInsert = typeof categories.$inferInsert
export type Supplier = typeof suppliers.$inferSelect
export type SupplierInsert = typeof suppliers.$inferInsert
export const stockReceipts = pgTable('stock_receipt', {
id: uuid('id').primaryKey().defaultRandom(),
locationId: uuid('location_id').references(() => locations.id),
productId: uuid('product_id')
.notNull()
.references(() => products.id),
supplierId: uuid('supplier_id').references(() => suppliers.id),
inventoryUnitId: uuid('inventory_unit_id').references(() => inventoryUnits.id),
qty: integer('qty').notNull().default(1),
costPerUnit: numeric('cost_per_unit', { precision: 10, scale: 2 }).notNull(),
totalCost: numeric('total_cost', { precision: 10, scale: 2 }).notNull(),
receivedDate: date('received_date').notNull(),
receivedBy: uuid('received_by'),
invoiceNumber: varchar('invoice_number', { length: 100 }),
notes: text('notes'),
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
})
export const priceHistory = pgTable('price_history', {
id: uuid('id').primaryKey().defaultRandom(),
productId: uuid('product_id')
.notNull()
.references(() => products.id),
previousPrice: numeric('previous_price', { precision: 10, scale: 2 }),
newPrice: numeric('new_price', { precision: 10, scale: 2 }).notNull(),
previousMinPrice: numeric('previous_min_price', { precision: 10, scale: 2 }),
newMinPrice: numeric('new_min_price', { precision: 10, scale: 2 }),
reason: text('reason'),
changedBy: uuid('changed_by'),
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
})
export type PriceHistory = typeof priceHistory.$inferSelect
export type StockReceipt = typeof stockReceipts.$inferSelect
export type StockReceiptInsert = typeof stockReceipts.$inferInsert
export const consignmentDetails = pgTable('consignment_detail', {
id: uuid('id').primaryKey().defaultRandom(),
productId: uuid('product_id')
.notNull()
.references(() => products.id),
consignorAccountId: uuid('consignor_account_id').notNull(),
commissionPercent: numeric('commission_percent', { precision: 5, scale: 2 }).notNull(),
minPrice: numeric('min_price', { precision: 10, scale: 2 }),
agreementDate: date('agreement_date'),
notes: text('notes'),
isActive: boolean('is_active').notNull().default(true),
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
})
export type ConsignmentDetail = typeof consignmentDetails.$inferSelect
export type ConsignmentDetailInsert = typeof consignmentDetails.$inferInsert
export type Product = typeof products.$inferSelect
export type ProductInsert = typeof products.$inferInsert
export type InventoryUnit = typeof inventoryUnits.$inferSelect
export type InventoryUnitInsert = typeof inventoryUnits.$inferInsert
export type ProductSupplier = typeof productSuppliers.$inferSelect