diff --git a/README.md b/README.md index f0abf152..6ab1ca5c 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ NestJS PostgreSQL Apache 2.0 License - 2474 Tests Passing 12 AI Agents + 2476 Tests Passing 12 AI Agents

@@ -510,7 +510,7 @@ Operator notes for activating existing adapters, metasearch landings on the dire | OTA Channels | Booking.com + Expedia (EQC) + SiteMinder + DerbySoft | Direct + aggregated OTA connectivity (ARI + content) | | XML Processing | fast-xml-parser | Booking.com OTA XML protocol | | Package Manager | pnpm workspaces | Monorepo management | -| Testing | Vitest (2474 passing tests across 286 files with passing tests) | Unit and integration tests | +| Testing | Vitest (2476 passing tests across 287 files with passing tests) | Unit and integration tests | | Build | tsup (packages) + Vite (dashboard) + nest build (API) | Fast builds | | Containers | Docker + docker-compose | Local dev and production deployment | | CI/CD | GitHub Actions | Automated testing, builds, and releases | @@ -648,7 +648,7 @@ Before going live, verify the items in [`docs/deployment.md`](./docs/deployment. ### Run tests ```bash -# Passing-test count: 2474 test cases across 286 files (skipped excluded) +# Passing-test count: 2476 test cases across 287 files (skipped excluded) # API tests only pnpm --filter @telivityhaip/api test @@ -1197,7 +1197,7 @@ HAIP is built in public and contributions are welcome. pnpm install # Install dependencies pnpm build # Build all workspace packages pnpm dev # Start API in dev mode (hot reload) -pnpm test # Run all tests (2474 passing, 286 files with passes; skipped excluded) +pnpm test # Run all tests (2476 passing, 287 files with passes; skipped excluded) pnpm lint # ESLint ``` diff --git a/apps/dashboard/src/pages/Cashier.test.tsx b/apps/dashboard/src/pages/Cashier.test.tsx new file mode 100644 index 00000000..71857f51 --- /dev/null +++ b/apps/dashboard/src/pages/Cashier.test.tsx @@ -0,0 +1,99 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { render, screen, waitFor } from '@testing-library/react'; +import { MemoryRouter, Routes, Route } from 'react-router-dom'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import Cashier from './Cashier'; + +vi.mock('../context/PropertyContext', () => ({ + useProperty: () => ({ + propertyId: 'prop-1', + currencyCode: 'USD', + properties: [{ id: 'prop-1', name: 'Test property' }], + }), +})); + +vi.mock('../lib/api', () => ({ + api: { get: vi.fn(), post: vi.fn(), patch: vi.fn(), delete: vi.fn() }, +})); + +import { api } from '../lib/api'; + +function renderReport(sessionId = 'sess-1') { + const queryClient = new QueryClient({ + defaultOptions: { queries: { retry: false, gcTime: 0 }, mutations: { retry: false } }, + }); + return render( + + + + } /> + + + , + ); +} + +describe('Cashier SessionReport', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('reads countedBalance and variance from the report root (#380)', async () => { + vi.mocked(api.get).mockResolvedValue({ + data: { + session: { + id: 'sess-1', + cashDrawerId: 'drawer-1', + cashierUserId: 'user-1', + status: 'closed', + openingFloat: '200.00', + openedAt: '2026-06-01T08:00:00Z', + closedAt: '2026-06-01T16:00:00Z', + }, + movementSummary: { + payment: { count: 2, total: '250.00' }, + refund: { count: 0, total: '0.00' }, + paid_out: { count: 1, total: '52.50' }, + drop: { count: 0, total: '0.00' }, + }, + expectedBalance: '397.50', + countedBalance: '350.00', + variance: '-47.50', + }, + } as never); + + renderReport(); + + await waitFor(() => { + expect(screen.getByText('Variance').closest('div')).toHaveTextContent('$-47.50'); + }); + expect(screen.getByText('Counted').closest('div')).toHaveTextContent('$350.00'); + expect(screen.getByText('Expected').closest('div')).toHaveTextContent('$397.50'); + expect(api.get).toHaveBeenCalledWith('/v1/cash/sessions/sess-1/report', { + params: { propertyId: 'prop-1' }, + }); + }); + + it('falls back to session.countedBalance / session.variance for older shapes', async () => { + vi.mocked(api.get).mockResolvedValue({ + data: { + session: { + id: 'sess-1', + status: 'closed', + openingFloat: '200.00', + countedBalance: '350.00', + variance: '-47.50', + }, + expectedBalance: '397.50', + movementSummary: {}, + }, + } as never); + + renderReport(); + + await waitFor(() => { + expect(screen.getByText('Variance').closest('div')).toHaveTextContent('$-47.50'); + }); + expect(screen.getByText('Counted').closest('div')).toHaveTextContent('$350.00'); + }); +}); diff --git a/apps/dashboard/src/pages/Cashier.tsx b/apps/dashboard/src/pages/Cashier.tsx index e82bfd22..7c589ed8 100644 --- a/apps/dashboard/src/pages/Cashier.tsx +++ b/apps/dashboard/src/pages/Cashier.tsx @@ -277,6 +277,10 @@ function SessionReport() { const report = data?.data ?? data; const session = report?.session; const summary = report?.movementSummary ?? {}; + // API puts countedBalance / variance on the report root (see cashierReport). + // Fall back to session.* for older response shapes that nested them. + const countedBalance = report?.countedBalance ?? session?.countedBalance ?? null; + const variance = report?.variance ?? session?.variance ?? null; return (

@@ -294,8 +298,8 @@ function SessionReport() {

{t('cashier.openingFloat')}

${session?.openingFloat ?? '0.00'}

{t('cashier.expected')}

${report.expectedBalance ?? '0.00'}

-

{t('cashier.counted')}

${session?.countedBalance ?? '—'}

-

{t('cashier.variance')}

${session?.variance ?? '0.00'}

+

{t('cashier.counted')}

${countedBalance ?? '—'}

+

{t('cashier.variance')}

${variance ?? '0.00'}

diff --git a/docs/test-stats.json b/docs/test-stats.json index 7fc7eccc..362afe1a 100644 --- a/docs/test-stats.json +++ b/docs/test-stats.json @@ -1,7 +1,7 @@ { - "tests": 2474, - "files": 286, + "tests": 2476, + "files": 287, "scope": "all workspace packages with a test script", "semantics": "passed test cases and files containing at least one passed test; skipped test cases and skipped-only files are excluded", - "updatedAt": "2026-10-06T14:53:03.799Z" + "updatedAt": "2026-10-15T20:33:59.778Z" }