diff --git a/backlog/features.json b/backlog/features.json index 0d1b0ad..2d15a3c 100644 --- a/backlog/features.json +++ b/backlog/features.json @@ -8192,6 +8192,22 @@ }, "phase": "pos", "completed_at": "2026-08-26T20:58:39Z" + }, + { + "id": "POS-PAYMENT-OVERPAY-FIX", + "type": "bug", + "title": "POS selfpay: payments exceed total", + "description": "In selfpay flow, after selecting payment method the frontend reports payments exceed total. Investigate discrepancy between frontend totals.total and backend totalCents.", + "priority": "high", + "risk": "med", + "status": "in_progress", + "created_at": "2026-08-26", + "gates": { + "reviewer": false, + "security": false, + "qa": false + }, + "phase": "pos" } ] } diff --git a/project/apps/pos/src/app/(terminal)/page.tsx b/project/apps/pos/src/app/(terminal)/page.tsx index 5d3bb39..a1ae457 100644 --- a/project/apps/pos/src/app/(terminal)/page.tsx +++ b/project/apps/pos/src/app/(terminal)/page.tsx @@ -1906,15 +1906,17 @@ export default function RegisterPage() { key={method.id} type="button" onClick={() => { - // POS-SELFPAY-FLOW: no partial payments — directly add full payment and confirm + // POS-SELFPAY-FLOW: no partial payments — directly add full payment and confirm. + // Clamp to totals.total to avoid 1-cent rounding mismatches with the backend. setShowSelfpayPaymentMethods(false); + const amountCents = Math.min(remainingCents, totals.total); const allocation: PaymentAllocation = { id: generateIdempotencyKey(), methodCode: method.code, methodLabel: method.label, kind: method.kind, - amountCents: remainingCents, - ...(method.kind === 'cash' ? { tenderedCents: remainingCents } : {}), + amountCents, + ...(method.kind === 'cash' ? { tenderedCents: amountCents } : {}), changeCents: 0, }; void confirmSaleWith([allocation]); diff --git a/project/src/modules/pos/application/create-pos-sale.ts b/project/src/modules/pos/application/create-pos-sale.ts index cc574d5..7ffb0a7 100644 --- a/project/src/modules/pos/application/create-pos-sale.ts +++ b/project/src/modules/pos/application/create-pos-sale.ts @@ -111,7 +111,10 @@ export function validatePaymentAllocations( }; }); const allocatedCents = validated.reduce((sum, payment) => sum + payment.amountCents, 0); - if (allocatedCents > totalCents) { + // Allow up to 1 cent tolerance for rounding differences between the front-end + // POS calculation (which uses cached prices) and the back-end canonical total + // (which re-reads the catalogue under a transaction). + if (allocatedCents - totalCents > 1) { throw new AppError( 400, 'POS_PAYMENT_OVERPAYMENT', diff --git a/work/runtime-status.json b/work/runtime-status.json index 421ed8b..3659e78 100644 --- a/work/runtime-status.json +++ b/work/runtime-status.json @@ -2,11 +2,11 @@ "feature_id": "CHECKOUT-STOCK-RECHECK", "stage": "build", "agent": "implementer", - "action": "Implementar desglose IVA en backend + ReceiptModal", + "action": "Fix payment overpayment", "state": "running", "next_agent": "reviewer", "waiting_for": "build", - "updated_at": "2026-08-26T20:57:05.300295Z", + "updated_at": "2026-08-26T21:25:27.891229Z", "timeline": [ { "ts": "2026-08-26T17:21:37Z",