fix: require open drawer to complete transactions, fix product price field
Some checks failed
CI / ci (pull_request) Failing after 20s
CI / e2e (pull_request) Has been skipped

- Backend enforces open drawer at location before completing any transaction
- Frontend disables payment buttons when drawer is closed with warning message
- Fix product price field name (price, not sellingPrice) in POS API types
- Fix seed UUIDs to use valid UUID v4 format (version nibble must be 1-8)
- Fix Vite allowedHosts for dev.lunarfront.tech access
- Add e2e test for drawer enforcement (39 POS tests now pass)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
ryan
2026-04-04 19:54:07 +00:00
parent bd3a25aa1c
commit 1673e18fe8
8 changed files with 125 additions and 44 deletions

View File

@@ -18,6 +18,9 @@ export function POSCartPanel({ transaction }: POSCartPanelProps) {
const [paymentMethod, setPaymentMethod] = useState<string | null>(null)
const lineItems = transaction?.lineItems ?? []
const drawerSessionId = usePOSStore((s) => s.drawerSessionId)
const drawerOpen = !!drawerSessionId
const removeItemMutation = useMutation({
mutationFn: (lineItemId: string) =>
posMutations.removeLineItem(currentTransactionId!, lineItemId),
@@ -126,41 +129,46 @@ export function POSCartPanel({ transaction }: POSCartPanelProps) {
</div>
{/* Payment buttons */}
<div className="p-3 grid grid-cols-2 gap-2">
<Button
className="h-12 text-sm gap-2"
disabled={!hasItems || !isPending}
onClick={() => setPaymentMethod('cash')}
>
<Banknote className="h-4 w-4" />
Cash
</Button>
<Button
className="h-12 text-sm gap-2"
disabled={!hasItems || !isPending}
onClick={() => setPaymentMethod('card_present')}
>
<CreditCard className="h-4 w-4" />
Card
</Button>
<Button
variant="outline"
className="h-12 text-sm gap-2"
disabled={!hasItems || !isPending}
onClick={() => setPaymentMethod('check')}
>
<FileText className="h-4 w-4" />
Check
</Button>
<Button
variant="destructive"
className="h-12 text-sm gap-2"
disabled={!hasItems || !isPending}
onClick={() => voidMutation.mutate()}
>
<Ban className="h-4 w-4" />
Void
</Button>
<div className="p-3 space-y-2">
{!drawerOpen && hasItems && (
<p className="text-xs text-destructive text-center">Open the drawer before accepting payment</p>
)}
<div className="grid grid-cols-2 gap-2">
<Button
className="h-12 text-sm gap-2"
disabled={!hasItems || !isPending || !drawerOpen}
onClick={() => setPaymentMethod('cash')}
>
<Banknote className="h-4 w-4" />
Cash
</Button>
<Button
className="h-12 text-sm gap-2"
disabled={!hasItems || !isPending || !drawerOpen}
onClick={() => setPaymentMethod('card_present')}
>
<CreditCard className="h-4 w-4" />
Card
</Button>
<Button
variant="outline"
className="h-12 text-sm gap-2"
disabled={!hasItems || !isPending || !drawerOpen}
onClick={() => setPaymentMethod('check')}
>
<FileText className="h-4 w-4" />
Check
</Button>
<Button
variant="destructive"
className="h-12 text-sm gap-2"
disabled={!hasItems || !isPending}
onClick={() => voidMutation.mutate()}
>
<Ban className="h-4 w-4" />
Void
</Button>
</div>
</div>
</div>

View File

@@ -48,11 +48,12 @@ export function POSItemPanel({ transaction }: POSItemPanelProps) {
productId: product.id,
description: product.name,
qty: 1,
unitPrice: parseFloat(product.sellingPrice ?? '0'),
unitPrice: parseFloat(product.price ?? '0'),
})
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: posKeys.transaction(currentTransactionId ?? '') })
const txnId = usePOSStore.getState().currentTransactionId
queryClient.invalidateQueries({ queryKey: posKeys.transaction(txnId ?? '') })
},
onError: (err) => toast.error(err.message),
})
@@ -76,7 +77,8 @@ export function POSItemPanel({ transaction }: POSItemPanelProps) {
})
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: posKeys.transaction(currentTransactionId ?? '') })
const txnId = usePOSStore.getState().currentTransactionId
queryClient.invalidateQueries({ queryKey: posKeys.transaction(txnId ?? '') })
setCustomOpen(false)
setCustomDesc('')
setCustomPrice('')
@@ -102,7 +104,7 @@ export function POSItemPanel({ transaction }: POSItemPanelProps) {
productId: product.id,
description: product.name,
qty: 1,
unitPrice: parseFloat(product.sellingPrice ?? '0'),
unitPrice: parseFloat(product.price ?? '0'),
})
},
onSuccess: () => {
@@ -158,7 +160,7 @@ export function POSItemPanel({ transaction }: POSItemPanelProps) {
>
<span className="font-medium text-sm line-clamp-2">{product.name}</span>
<div className="mt-auto flex items-center justify-between w-full pt-1">
<span className="text-base font-semibold">${parseFloat(product.sellingPrice ?? '0').toFixed(2)}</span>
<span className="text-base font-semibold">${parseFloat(product.price ?? '0').toFixed(2)}</span>
{product.sku && <span className="text-xs text-muted-foreground">{product.sku}</span>}
</div>
{product.isSerialized ? (