diff --git a/docs/architecture/frontend-boundary.md b/docs/architecture/frontend-boundary.md index 4f28bfa..279f572 100644 --- a/docs/architecture/frontend-boundary.md +++ b/docs/architecture/frontend-boundary.md @@ -23,7 +23,8 @@ ## UI Workflow Coverage - POS transaction summary lookup with open balance visibility -- Transaction detail inspection for a selected transaction id +- Transaction detail inspection for a selected payable check or transaction id - Recent payment activity review -- Payment capture with retry-ready local session history +- Payment capture with retry-ready local session history and lifecycle-aware payment hints - Protected route shell for summary, payment capture, and session inspection +- POS actions are presented as the final step after kitchen and floor service complete the restaurant order. diff --git a/docs/runbooks/local-development.md b/docs/runbooks/local-development.md index 1ac6f1d..9b37bb3 100644 --- a/docs/runbooks/local-development.md +++ b/docs/runbooks/local-development.md @@ -22,6 +22,7 @@ npm run dev - Business calls are gated behind session checks. - Session cookies are sent with `credentials: include`. - Summary, detail, recent-payment, and capture actions all surface session-expired guidance before retry. +- UI copy assumes POS only acts on checks that became payable after restaurant service completion. ## Build diff --git a/docs/runbooks/testing.md b/docs/runbooks/testing.md index 3924511..fa1b6c4 100644 --- a/docs/runbooks/testing.md +++ b/docs/runbooks/testing.md @@ -17,7 +17,7 @@ npm run test:ci - `src/api/client.test.ts`: runtime-config precedence and fallback behavior. - `src/api/dashboardApi.test.ts`: endpoint path/query composition and payload mapping. - `src/auth/oidcLogin.test.ts`: OIDC start-url generation and safe return-url fallback. -- `src/App.test.tsx`: central login screen, protected summary/detail flow, payment capture, and session-expired recovery guidance. +- `src/App.test.tsx`: central login screen, lifecycle-aware payable-check summary/detail flow, payment capture, and session-expired recovery guidance. ## Notes diff --git a/src/App.test.tsx b/src/App.test.tsx index 9131809..8a5ce57 100644 --- a/src/App.test.tsx +++ b/src/App.test.tsx @@ -56,7 +56,7 @@ describe('POS Transactions App', () => { }); vi.mocked(loadDashboard).mockResolvedValue({ contextId: 'demo-context', - summary: '2 payments awaiting reconciliation', + summary: '2 served orders are payable', openBalance: 52.3, currency: 'USD', recentPayments: [] @@ -86,7 +86,7 @@ describe('POS Transactions App', () => { paymentMethod: 'cash', amount: 10, currency: 'USD', - status: 'Captured', + status: 'Payable', capturedAtUtc: '2026-03-27T11:00:00Z' } ] @@ -103,9 +103,11 @@ describe('POS Transactions App', () => { await waitFor(() => expect(loadTransactionDetail).toHaveBeenCalledWith('demo-context', 'POS-9001')); await waitFor(() => expect(loadRecentPayments).toHaveBeenCalledWith('demo-context')); - expect(await screen.findByText('2 payments awaiting reconciliation')).toBeInTheDocument(); + expect(await screen.findByText('2 served orders are payable')).toBeInTheDocument(); + expect(screen.getByText('Only restaurant orders that have completed service should appear here as payable checks for capture.')).toBeInTheDocument(); expect(await screen.findByText('POS-22')).toBeInTheDocument(); expect(await screen.findByText('POS-21')).toBeInTheDocument(); + expect(await screen.findByText('ready for payment capture after service completion')).toBeInTheDocument(); }); it('captures payments and records retry-ready history', async () => { @@ -154,6 +156,7 @@ describe('POS Transactions App', () => { await waitFor(() => expect(capturePosPayment).toHaveBeenCalledTimes(1)); expect(await screen.findAllByText('POS-2200')).toHaveLength(2); + expect(screen.getAllByText('payment is complete and the restaurant workflow can close').length).toBeGreaterThan(0); expect(screen.getByRole('button', { name: 'Retry Last Capture' })).not.toBeDisabled(); }); diff --git a/src/App.tsx b/src/App.tsx index d06a195..bd6346b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -67,9 +67,10 @@ const paymentColumns = [ { title: 'Status', render: (_: unknown, record: PosPaymentActivity) => ( - {record.status} + {record.status} ) }, + { title: 'Lifecycle Step', render: (_: unknown, record: PosPaymentActivity) => paymentProgressHint(record.status) }, { title: 'Captured At', render: (_: unknown, record: PosPaymentActivity) => formatUtc(record.capturedAtUtc) } ]; @@ -240,7 +241,7 @@ function PosTransactionsShell() { POS Transactions - Protected POS workflows for summary lookup, transaction detail inspection, capture retries, and recent payment activity. + Protected POS workflows for payable restaurant checks, capture retries, and recent payment activity once service is complete. {session.error && } {workflowState.error && } @@ -296,9 +297,12 @@ function PosTransactionsShell() { {summaryPayload.openBalance.toFixed(2)} {summaryPayload.currency} + + Only restaurant orders that have completed service should appear here as payable checks for capture. + ) : ( - + )} @@ -313,11 +317,14 @@ function PosTransactionsShell() { {detailPayload.transaction.amount.toFixed(2)} {detailPayload.transaction.currency} - {detailPayload.transaction.status} + + {detailPayload.transaction.status} + + {paymentProgressHint(detailPayload.transaction.status)} {formatUtc(detailPayload.transaction.capturedAtUtc)} ) : ( - + )} @@ -381,7 +388,7 @@ function PosTransactionsShell() { - Use retry when a payment attempt fails after verifying the transaction payload and operator session. + Capture should be used only after kitchen and floor service complete the order, making the check payable. @@ -392,7 +399,10 @@ function PosTransactionsShell() { {paymentResponse.contextId} {paymentResponse.transactionId} {String(paymentResponse.succeeded)} - {paymentResponse.status} + + {paymentResponse.status} + + {paymentProgressHint(paymentResponse.status)} {formatUtc(paymentResponse.capturedAtUtc)} {paymentResponse.summary} @@ -417,10 +427,11 @@ function PosTransactionsShell() { { title: 'Status', render: (_, record) => ( - {record.response.status} + {record.response.status} ) }, - { title: 'Summary', render: (_, record) => record.response.summary } + { title: 'Summary', render: (_, record) => record.response.summary }, + { title: 'Lifecycle Step', render: (_, record) => paymentProgressHint(record.response.status) } ]} /> @@ -455,6 +466,44 @@ function providerLabel(provider: IdentityProvider) { } } +function paymentStatusColor(status: string) { + switch (status.toLowerCase()) { + case 'captured': + case 'paid': + return 'green'; + case 'payable': + case 'ready': + return 'blue'; + case 'pending': + case 'authorized': + return 'orange'; + case 'failed': + case 'blocked': + return 'red'; + default: + return 'default'; + } +} + +function paymentProgressHint(status: string) { + switch (status.toLowerCase()) { + case 'payable': + case 'ready': + return 'ready for payment capture after service completion'; + case 'pending': + case 'authorized': + return 'payment is in progress and should be monitored before retrying'; + case 'captured': + case 'paid': + return 'payment is complete and the restaurant workflow can close'; + case 'failed': + case 'blocked': + return 'operator review is required before attempting another capture'; + default: + return 'payment state is being resolved against the restaurant lifecycle'; + } +} + function formatUtc(value: string) { return new Date(value).toLocaleString('en-US', { dateStyle: 'medium',