From 95cf017b4be32f13bba8801b968205506e71bb10 Mon Sep 17 00:00:00 2001 From: ryan Date: Sun, 5 Apr 2026 01:32:28 +0000 Subject: [PATCH] feat: repair-POS integration, receipt formats, manager overrides, price adjustments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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) --- packages/admin/src/api/pos.ts | 5 +- .../src/components/inventory/product-form.tsx | 7 + .../src/components/pos/pos-cart-panel.tsx | 343 ++++++++++++++++-- .../src/components/pos/pos-drawer-dialog.tsx | 35 +- .../src/components/pos/pos-item-panel.tsx | 5 +- .../components/pos/pos-manager-override.tsx | 199 ++++++++++ .../src/components/pos/pos-payment-dialog.tsx | 7 +- .../admin/src/components/pos/pos-receipt.tsx | 220 +++++++++-- .../src/components/pos/pos-repair-dialog.tsx | 263 ++++++++++++++ .../admin/src/components/pos/pos-top-bar.tsx | 16 +- .../pos/pos-transactions-dialog.tsx | 8 +- .../src/components/repairs/generate-pdf.ts | 9 +- packages/admin/src/components/ui/popover.tsx | 46 +++ .../_authenticated/repairs/$ticketId.tsx | 69 +++- .../src/routes/_authenticated/settings.tsx | 314 ++++++++++------ packages/admin/src/stores/pos.store.ts | 13 + packages/admin/src/types/inventory.ts | 1 + packages/admin/src/types/repair.ts | 4 +- packages/backend/package.json | 2 +- .../migrations/0043_repair-pos-consumable.sql | 5 + .../src/db/migrations/meta/_journal.json | 7 + packages/backend/src/db/schema/inventory.ts | 1 + packages/backend/src/db/schema/repairs.ts | 1 + packages/backend/src/routes/v1/products.ts | 1 + packages/backend/src/routes/v1/repairs.ts | 7 + .../backend/src/routes/v1/transactions.ts | 8 + .../backend/src/services/product.service.ts | 4 + .../backend/src/services/repair.service.ts | 19 + packages/backend/src/services/tax.service.ts | 2 + .../src/services/transaction.service.ts | 82 +++++ .../shared/src/schemas/inventory.schema.ts | 1 + packages/shared/src/schemas/repairs.schema.ts | 2 +- 32 files changed, 1507 insertions(+), 199 deletions(-) create mode 100644 packages/admin/src/components/pos/pos-manager-override.tsx create mode 100644 packages/admin/src/components/pos/pos-repair-dialog.tsx create mode 100644 packages/admin/src/components/ui/popover.tsx create mode 100644 packages/backend/src/db/migrations/0043_repair-pos-consumable.sql diff --git a/packages/admin/src/api/pos.ts b/packages/admin/src/api/pos.ts index 871e1ee..d881a18 100644 --- a/packages/admin/src/api/pos.ts +++ b/packages/admin/src/api/pos.ts @@ -136,7 +136,7 @@ export function currentDrawerOptions(locationId: string | null) { export function productSearchOptions(search: string) { return queryOptions({ queryKey: posKeys.products(search), - queryFn: () => api.get<{ data: Product[]; pagination: { page: number; limit: number; total: number; totalPages: number } }>('/v1/products', { q: search, limit: 24, isActive: true }), + queryFn: () => api.get<{ data: Product[]; pagination: { page: number; limit: number; total: number; totalPages: number } }>('/v1/products', { q: search, limit: 24, isActive: true, isConsumable: false }), enabled: search.length >= 1, }) } @@ -183,4 +183,7 @@ export const posMutations = { getAdjustments: (drawerId: string) => api.get<{ data: DrawerAdjustment[] }>(`/v1/drawer/${drawerId}/adjustments`), + + createFromRepair: (ticketId: string, locationId?: string) => + api.post(`/v1/transactions/from-repair/${ticketId}`, { locationId }), } diff --git a/packages/admin/src/components/inventory/product-form.tsx b/packages/admin/src/components/inventory/product-form.tsx index bca21f7..2314807 100644 --- a/packages/admin/src/components/inventory/product-form.tsx +++ b/packages/admin/src/components/inventory/product-form.tsx @@ -34,6 +34,7 @@ export function ProductForm({ defaultValues, onSubmit, loading }: Props) { isSerialized: defaultValues?.isSerialized ?? false, isRental: defaultValues?.isRental ?? false, isDualUseRepair: defaultValues?.isDualUseRepair ?? false, + isConsumable: defaultValues?.isConsumable ?? false, isActive: defaultValues?.isActive ?? true, }, }) @@ -42,6 +43,7 @@ export function ProductForm({ defaultValues, onSubmit, loading }: Props) { const isRental = watch('isRental') const isSerialized = watch('isSerialized') const isDualUseRepair = watch('isDualUseRepair') + const isConsumable = watch('isConsumable') const isActive = watch('isActive') function handleFormSubmit(data: Record) { @@ -61,6 +63,7 @@ export function ProductForm({ defaultValues, onSubmit, loading }: Props) { isSerialized: data.isSerialized, isRental: data.isRental, isDualUseRepair: data.isDualUseRepair, + isConsumable: data.isConsumable, isActive: data.isActive, }) } @@ -158,6 +161,10 @@ export function ProductForm({ defaultValues, onSubmit, loading }: Props) { setValue('isDualUseRepair', e.target.checked)} className="h-4 w-4" /> Available as Repair Line Item +