Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<img src="https://img.shields.io/badge/NestJS-framework-E0234E?logo=nestjs&logoColor=white" alt="NestJS" />
<img src="https://img.shields.io/badge/PostgreSQL-database-4169E1?logo=postgresql&logoColor=white" alt="PostgreSQL" />
<img src="https://img.shields.io/badge/License-Apache%202.0-blue" alt="Apache 2.0 License" />
<img src="https://img.shields.io/badge/Tests-2474%20passing-brightgreen" alt="2474 Tests Passing" /> <img src="https://img.shields.io/badge/AI%20Agents-12%20built--in-blueviolet" alt="12 AI Agents" />
<img src="https://img.shields.io/badge/Tests-2476%20passing-brightgreen" alt="2476 Tests Passing" /> <img src="https://img.shields.io/badge/AI%20Agents-12%20built--in-blueviolet" alt="12 AI Agents" />
</p>

<p align="center">
Expand Down Expand Up @@ -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 |
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
```

Expand Down
99 changes: 99 additions & 0 deletions apps/dashboard/src/pages/Cashier.test.tsx
Original file line number Diff line number Diff line change
@@ -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(
<MemoryRouter initialEntries={[`/cashier/report/${sessionId}`]}>
<QueryClientProvider client={queryClient}>
<Routes>
<Route path="/cashier/*" element={<Cashier />} />
</Routes>
</QueryClientProvider>
</MemoryRouter>,
);
}

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');
});
});
8 changes: 6 additions & 2 deletions apps/dashboard/src/pages/Cashier.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<div>
Expand All @@ -294,8 +298,8 @@ function SessionReport() {
<div className="bg-white rounded-xl shadow-sm p-5 grid grid-cols-2 md:grid-cols-4 gap-4 text-sm">
<div><p className="text-xs text-telivity-mid-grey">{t('cashier.openingFloat')}</p><p className="font-semibold">${session?.openingFloat ?? '0.00'}</p></div>
<div><p className="text-xs text-telivity-mid-grey">{t('cashier.expected')}</p><p className="font-semibold">${report.expectedBalance ?? '0.00'}</p></div>
<div><p className="text-xs text-telivity-mid-grey">{t('cashier.counted')}</p><p className="font-semibold">${session?.countedBalance ?? '—'}</p></div>
<div><p className="text-xs text-telivity-mid-grey">{t('cashier.variance')}</p><p className="font-semibold">${session?.variance ?? '0.00'}</p></div>
<div><p className="text-xs text-telivity-mid-grey">{t('cashier.counted')}</p><p className="font-semibold">${countedBalance ?? '—'}</p></div>
<div><p className="text-xs text-telivity-mid-grey">{t('cashier.variance')}</p><p className="font-semibold">${variance ?? '0.00'}</p></div>
</div>

<div className="bg-white rounded-xl shadow-sm overflow-hidden">
Expand Down
6 changes: 3 additions & 3 deletions docs/test-stats.json
Original file line number Diff line number Diff line change
@@ -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"
}
Loading