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
2 changes: 2 additions & 0 deletions .claude/agent-memory/docs-writer/MEMORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
- `guides/household-items/` -- index, creating-editing-items, budget-and-invoices, work-item-linking, delivery-and-dependencies
- `guides/diary/` -- index, manual-entries, automatic-events, signatures
- `guides/dashboard/` -- index
- `guides/feeds/` -- index, subscribing
- `guides/backup/` -- index (BACKUP_DIR/CADENCE/RETENTION env vars, manual + scheduled backups, restore flow, off-site guidance) -- EPIC-19
- `guides/appearance/` -- dark-mode
- `development/` -- index, tech-stack, agentic/overview, agentic/agent-team, agentic/workflow, agentic/setup

Expand Down
11 changes: 11 additions & 0 deletions .claude/agent-memory/e2e-test-engineer/MEMORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,17 @@
> Detailed notes live in topic files. This index links to them.
> See: `e2e-pom-patterns.md`, `e2e-parallel-isolation.md`, `story-epic08-e2e.md`, `story-933-dav-vendor-contacts.md`, `milestones-e2e.md`, `story-1248-mass-move.md`

## Budget Overview Hero Card Removed (Issues #1389/#1390, 2026-04-29)

- `<section aria-label="Budget overview">` (heroCard) is **gone** from BudgetOverviewPage.tsx after #1389.
- `BudgetOverviewPage.POM.waitForLoaded()` now races on `costBreakdownCard` instead of `heroCard`.
- `heroCard` locator kept in POM for historical reference but never matches on-page elements.
- Tests that asserted `heroCard.toBeVisible()` were removed from: `budget-overview.spec.ts`, `budget-overview-print.spec.ts`, `budget-source-filter.spec.ts`.
- New spec: `e2e/tests/budget/budget-overview-no-hero-card.spec.ts` (smoke, @smoke tag).
- Source badge (`aria-label="Budget source: {name}"`) is on Level 3 rows only — must expand Work Items → area → item to reveal budget lines.
- `BreakdownBudgetLine` fields: `id`, `description`, `plannedAmount`, `confidence`, `actualCost`, `hasInvoice`, `isQuotation`, `budgetSourceId` (NOT `sourceId`/`sourceName`).
- BudgetSources API mock response: `{ budgetSources: [{ id, name, ... }] }` — component only reads `s.id` and `s.name`.

## Budget Source Filter E2E (Story #1360, 2026-04-25 — server-side filter)

- **Story #1360** rewrote filter from client-side to server-side. `BudgetSourceSummaryBreakdown` now has `subsidyPaybackMin/Max` NOT `subsidyPayback`.
Expand Down
1 change: 1 addition & 0 deletions .claude/agent-memory/product-owner/MEMORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ All 12 stories merged. Paperless-ngx links for invoices are EPIC-08; budget repo

- **2026-02-27** — 11 issues #328-#338 (EPIC-06 + EPIC-05 sub-issues, 6 bugs / 5 stories)
- **2026-04-28** — 5 standalone UI bugs #1369-#1373 (no active parent epic; all related epics closed): #1369 hide-linked filter on Paperless picker, #1370 disable scroll-wheel on numeric inputs, #1371 "Includes VAT" parity for direct-amount budget lines, #1372 vendor in invoice picker, #1373 "Claimed" total on Budget Invoices summary. All Todo. Only EPIC-16 (Floor Plans) is currently open and is unrelated to these.
- **2026-04-29** — 2 standalone Budget Overview bugs #1389-#1390 (no parent epic): #1389 remove Budget Health hero card from `/budget/overview` (full deletion incl. helpers, state, CSS classes, i18n keys, hero-card-only tests), #1390 source-name badge missing from print preview (mobile media query hides label on print-width pages). Both Todo.

## Patterns and Conventions

Expand Down
4 changes: 4 additions & 0 deletions .claude/agent-memory/qa-integration-tester/MEMORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
> Detailed notes live in topic files. This index links to them.
> See: `budget-categories-story-142.md`, `e2e-pom-patterns.md`, `e2e-parallel-isolation.md`, `story-358-document-linking.md`, `story-360-document-a11y.md`, `story-epic08-e2e.md`, `story-509-manage-page.md`, `story-471-dashboard.md`

## Systemic jest.unstable_mockModule Issue in This Worktree (2026-04-29)

ALL client tests using `jest.unstable_mockModule('../../lib/formatters.js', ...)` fail locally in this worktree with `useLocale must be used within a LocaleProvider`. This is a pre-existing environment issue — tests pass in CI. **Do not attempt to fix by changing mocks or adding LocaleProvider** — the tests are structurally correct and the mock works in CI. Just commit and let CI validate. The issue is specific to this worktree's Jest module resolution environment.

## Story #1360 — Server-Side Source Filter Tests (2026-04-25)

**CostBreakdownTable.test.tsx**: Replaced the 12-test `describe('Source filter — aggregate consistency (#1358)')` block with 4-test `describe('Server-driven render path (#1360)')`. The 12 old tests tested deleted client-side helpers (`computePerSourcePayback`, `computeFilteredAggregates`, `visibleLineIds`). Removal strategy: Python `content.replace()` on large block — incremental Edit tool calls left orphaned code. The `buildBreakdownWithTwoSources()` helper was replaced by `buildServerFilteredBreakdown()`.
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,11 @@ docker run -d \
--name cornerstone \
-p 3000:3000 \
-v cornerstone-data:/app/data \
-v cornerstone-backups:/backups \
steilerdev/cornerstone:latest
```

Open `http://localhost:3000` -- the setup wizard will guide you through creating your admin account. See the [full deployment guide](https://steilerDev.github.io/cornerstone/getting-started/docker-setup) for Docker Compose, reverse proxy, and OIDC configuration.
Open `http://localhost:3000` -- the setup wizard will guide you through creating your admin account. See the [full deployment guide](https://steilerDev.github.io/cornerstone/getting-started/docker-setup) for Docker Compose, reverse proxy, OIDC, and [scheduled-backup configuration](https://steilerDev.github.io/cornerstone/guides/backup/).

## Roadmap

Expand Down
38 changes: 28 additions & 10 deletions RELEASE_SUMMARY.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,42 @@
# v2.4.2 Release Summary
# v2.5.0 Release Summary

A small bug-fix release that tightens up the Budget and Document workflows introduced in v2.4. No new features, no breaking changes, no migration required -- pull and restart.
A focused release that adds **Backup & Restore**, tightens budget VAT handling, and slims down the Budget Overview page. Migration 0031 backfills `includes_vat` on existing budget lines, runs automatically on first start, and requires no manual intervention.

## What's New

- **Backup & Restore** -- Cornerstone now ships with a built-in backup feature that snapshots your entire app data directory (SQLite database + diary photos) into a single `tar.gz` archive. Trigger backups manually from the admin UI, run them on a cron schedule, set a retention limit, and restore from any archive in two clicks. Mount a separate volume to `/backups` (or wherever you point `BACKUP_DIR`) and you're set. See the [Backups guide](https://steilerDev.github.io/cornerstone/guides/backup) for setup, scheduling, and restore steps. (#1386)

## Improvements

- **Consistent VAT handling across budget lines** (#1385) -- Direct pricing mode now applies the same VAT multiplier as unit pricing (quantity × unit price), so the **Price includes VAT** checkbox behaves identically regardless of which pricing mode you use. Planned amounts are now always stored as gross internally, which means the Budget Overview, Available Funds row, and printed reports compare every line on a like-for-like basis. The `includes_vat` flag is now `NOT NULL` at the database level (defaults to `true`); migration 0031 backfills any pre-existing `NULL` values.

## Bug Fixes

- **Budget source filter now drives every total on the page.** The "Available Funds" and "Remaining Budget" columns in the Cost Breakdown table -- as well as the Pending, Paid, and Quotation summary cards above the table -- now correctly reflect the active source filter. Previously they always showed unfiltered totals, which made the per-source filter misleading when you only wanted to see the picture for a single source.
- **Document picker shows all documents by default.** The "Hide already-linked documents" checkbox in the document picker now starts unchecked, so every document is immediately visible. You no longer have to clear the filter before you can re-link a document that is already attached elsewhere.
- **Mouse wheel no longer changes numeric fields.** Scrolling the page with your mouse wheel while the cursor sits over an Amount or budget field will not accidentally increment or decrement the value -- a common source of silent edits when scrolling long invoice forms.
- **VAT checkbox round-trips correctly.** The VAT / tax checkbox on invoices now preserves its state when you reopen an invoice for editing. Previously the saved state was not always reflected in the form.
- **Vendor picker no longer clears on blur.** Selecting a vendor in the Add Invoice form and then clicking elsewhere on the page (e.g. into the Amount field) no longer clears the selection.
- **Budget Overview summary cards refresh with the source filter.** The Pending, Paid, and Quotation summary cards on the Budget Overview page now refresh correctly when you toggle the source filter, so the headline numbers always match the rows below them.
- **Budget Overview is now the breakdown** (#1389) -- The Budget Health hero card has been removed from the top of the page. The overview now goes straight from the title bar into the Cost Breakdown Table. The Min / Avg / Max perspective toggle, source-filter, and Available Funds row all live inside the table and remain unchanged. The page is faster, prints cleaner, and removes a layer of summary metrics that mostly duplicated what the breakdown already shows.
- **Source name now prints on the Budget Overview** (#1390) -- Print viewports (around 600-720 px) used to trigger the mobile breakpoint, which collapsed the source attribution badge to just a colored dot -- great on a phone, useless on a printout. Print mode now forces the full source name visible with a border-based color treatment, so the printed report shows the actual source attached to each budget line.
- **Broken docs links on the Budget Overview page** (#1384) -- The "Related Pages" links to Work Items and Household Items pointed to non-existent `/overview` sub-paths and now point to the correct guide indices.

## What to Update

```bash
docker pull steilerdev/cornerstone:latest
```

Restart your container -- no database migration is required.
Restart your container. Migration 0031 runs automatically on first start.

If you want to enable the new backup feature, mount a backup volume:

```bash
docker run -d \
--name cornerstone \
-p 3000:3000 \
-v cornerstone-data:/app/data \
-v cornerstone-backups:/backups \
steilerdev/cornerstone:latest
```

Optionally set `BACKUP_CADENCE` (e.g., `0 2 * * *` for daily at 2 AM) and `BACKUP_RETENTION` (e.g., `7` to keep a week's worth of archives).

---

_Released: 2026-04-28_
_Released: 2026-05-03_
Original file line number Diff line number Diff line change
Expand Up @@ -664,6 +664,24 @@
.rowSourceDetailToggle[aria-pressed='false'] {
display: none !important;
}

/* Issue #1390: Print viewports map to ~600-720px which triggers the mobile
breakpoint and hides .sourceBadgeLabel. Force the label visible and the
dot hidden in print regardless of viewport width. */
.sourceBadgeLabel {
display: inline-flex !important;
}

.sourceBadgeDot {
display: none !important;
}

/* Make source badge legible without background-color in print using a border. */
.sourceBadgeLabel > * {
background: transparent !important;
border: 1pt solid var(--color-border-strong) !important;
print-color-adjust: exact;
}
}

/* ============================================================
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* @jest-environment jsdom
*/
import { jest, describe, it, expect, beforeAll } from '@jest/globals';
import { render, screen, fireEvent, within } from '@testing-library/react';

Check failure on line 5 in client/src/components/CostBreakdownTable/CostBreakdownTable.test.tsx

View workflow job for this annotation

GitHub Actions / Auto Fix Lint & Format

'within' is defined but never used. Allowed unused vars must match /^_/u

Check failure on line 5 in client/src/components/CostBreakdownTable/CostBreakdownTable.test.tsx

View workflow job for this annotation

GitHub Actions / Auto Fix Lint & Format

'within' is defined but never used. Allowed unused vars must match /^_/u
import { MemoryRouter } from 'react-router-dom';
import type { CostBreakdownTable as CostBreakdownTableType } from './CostBreakdownTable.js';
import type { BudgetBreakdown, BudgetOverview } from '@cornerstone/shared';
Expand Down Expand Up @@ -62,7 +62,7 @@
formatDateTime: fmtDateTime,
formatPercent: (n: number) => `${n.toFixed(2)}%`,
computeActualDuration: () => null,
useFormatters: () => ({

Check failure on line 65 in client/src/components/CostBreakdownTable/CostBreakdownTable.test.tsx

View workflow job for this annotation

GitHub Actions / Auto Fix Lint & Format

Do not define hook 'useFormatters' inside a function. Hooks should be defined at the module level. Move it to the top level

Check warning on line 65 in client/src/components/CostBreakdownTable/CostBreakdownTable.test.tsx

View workflow job for this annotation

GitHub Actions / Auto Fix Lint & Format

If your function doesn't call any Hooks, avoid the 'use' prefix. Instead, write it as a regular function without the 'use' prefix

Check failure on line 65 in client/src/components/CostBreakdownTable/CostBreakdownTable.test.tsx

View workflow job for this annotation

GitHub Actions / Auto Fix Lint & Format

Do not define hook 'useFormatters' inside a function. Hooks should be defined at the module level. Move it to the top level

Check warning on line 65 in client/src/components/CostBreakdownTable/CostBreakdownTable.test.tsx

View workflow job for this annotation

GitHub Actions / Auto Fix Lint & Format

If your function doesn't call any Hooks, avoid the 'use' prefix. Instead, write it as a regular function without the 'use' prefix
formatCurrency: fmtCurrency,
formatDate: fmtDate,
formatTime: fmtTime,
Expand Down Expand Up @@ -4473,3 +4473,40 @@
expect(remainingCostCell!.textContent?.replace(/\s+/g, '')).toContain('€200,000.00');
});
});

// ── Source badge print visibility (#1390) ─────────────────────────────────────

describe('source badge print visibility (#1390)', () => {
// Verifies the DOM structure that the print-mode CSS in CostBreakdownTable.module.css depends on.
// The CSS toggles .sourceBadgeDot off and .sourceBadgeLabel on inside @media print;
// jsdom does not evaluate @media, so this test asserts both elements are present in the DOM,
// which is the contract the CSS relies on.
it('renders both .sourceBadgeDot and .sourceBadgeLabel with the badge inside the label for each budget line', () => {
const sourceId = 'src-1';
const sourceName = 'Bank Loan';
const breakdown = buildBreakdownWithSourcedWI({
budgetSourceId: sourceId,
budgetSources: [buildSourceSummary({ id: sourceId, name: sourceName })],
});

const { container } = renderWithRouter(breakdown, buildOverview());

// Expand WI section → area → item so BudgetLineRow renders in the DOM
fireEvent.click(getButtonByControls(container, 'wi-section-categories'));
fireEvent.click(getButtonByControls(container, 'area:No Area'));
fireEvent.click(getButtonByLabel('Expand Sourced Work Item'));

// .sourceBadgeDot — screen-visible colored dot, hidden in print via CSS
const dot = container.querySelector('[class*="sourceBadgeDot"]');
expect(dot).not.toBeNull();
expect(dot!.getAttribute('aria-hidden')).toBe('true');

// .sourceBadgeLabel — wraps the Badge; hidden on screen, shown in print via CSS
const label = container.querySelector('[class*="sourceBadgeLabel"]');
expect(label).not.toBeNull();

// The Badge inside has aria-label set by the source-badge code path.
const badgeChild = label!.querySelector('[aria-label]');
expect(badgeChild).not.toBeNull();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ function makeLine(overrides: Partial<BudgetSourceBudgetLine> = {}): BudgetSource
quantity: null,
unit: null,
unitPrice: null,
includesVat: null,
includesVat: true,
createdBy: null,
hasClaimedInvoice: false,
createdAt: '2026-01-01T00:00:00.000Z',
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/budget/BudgetSection.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ function buildLine(id: string, invoiceLink: BudgetLineInvoiceLink | null = null)
quantity: null,
unit: null,
unitPrice: null,
includesVat: null,
includesVat: true,
};
}

Expand Down
5 changes: 4 additions & 1 deletion client/src/components/budget/BudgetSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,10 @@ export function BudgetSection<T extends BaseBudgetLine>({
(sum, line) => sum + (line.invoiceLink?.itemizedAmount || 0),
0,
);
const plannedTotal = groupLines.reduce((sum, line) => sum + effectivePlannedAmount(line), 0);
const plannedTotal = groupLines.reduce(
(sum, line) => sum + effectivePlannedAmount(line),
0,
);

return (
<InvoiceGroup
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/budget/InvoiceGroup.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ function buildLine(id: string, invoiceLink: BudgetLineInvoiceLink | null = null)
quantity: null,
unit: null,
unitPrice: null,
includesVat: null,
includesVat: true,
};
}

Expand Down
2 changes: 1 addition & 1 deletion client/src/hooks/useBudgetSection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const makeLine = (overrides: Partial<TestBudgetLine> = {}): TestBudgetLine => ({
quantity: null,
unit: null,
unitPrice: null,
includesVat: null,
includesVat: true,
...overrides,
});

Expand Down
2 changes: 2 additions & 0 deletions client/src/hooks/useBudgetSection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,8 @@ export function useBudgetSection<T extends BaseBudgetLine>(
setBudgetFormError('Planned amount must be a valid non-negative number.');
return;
}
const multiplier = budgetForm.includesVat ? 1 : 1.19;
plannedAmount = Math.round(plannedAmount * multiplier * 100) / 100;
} else {
// Unit pricing mode
const qty = parseFloat(budgetForm.quantity);
Expand Down
29 changes: 0 additions & 29 deletions client/src/i18n/de/budget.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,36 +12,7 @@
"addInvoice": "Rechnung Hinzufügen",
"addVendor": "Auftragnehmer Hinzufügen"
},
"availableFunds": "Verfügbare Mittel",
"projectedCostRange": "Geplanter Kostenbereich",
"expectedPayback": "Erwartete Rückzahlung",
"paybackCapped": "Einige Förderprogramme sind überzeichnet – Rückzahlungswerte sind begrenzt",
"remaining": "Verbleibend",
"remainingDetail": "Verbleibendes Budget – tippen Sie für Details",
"categories": "Kategorien",
"allCategories": "Alle Kategorien",
"noCategories": "Keine Kategorien",
"selectAll": "Alle auswählen",
"clearAll": "Alle deaktivieren",
"bars": {
"claimedInvoices": "Eingereichte Rechnungen",
"paidInvoices": "Bezahlte Rechnungen",
"pendingInvoices": "Ausstehende Rechnungen",
"projectedOptimistic": "Projiziert (optimistisch)",
"projectedPessimistic": "Projiziert (pessimistisch)",
"overflow": "Überlauf"
},
"remainingPerspectives": {
"vsMinPlanned": "Verbleibend vs. Min. Geplant",
"vsMaxPlanned": "Verbleibend vs. Max. Geplant",
"vsProjectedMin": "Verbleibend vs. Projiz. Min.",
"vsProjectedMax": "Verbleibend vs. Projiz. Max.",
"vsActualCost": "Verbleibend vs. Tatsächliche Kosten",
"vsActualPaid": "Verbleibend vs. Tatsächlich Bezahlt",
"vsMinPlannedWithPayback": "Verbleibend vs. Min. Geplant (inkl. Rückzahlung)",
"vsMaxPlannedWithPayback": "Verbleibend vs. Max. Geplant (inkl. Rückzahlung)"
},
"ofAvailableFunds": "der verfügbaren Mittel",
"costBreakdown": {
"tableCaption": "Kostenaufschlüsselung nach Bereich und Element",
"loading": "Kostenaufschlüsselung wird geladen…",
Expand Down
29 changes: 0 additions & 29 deletions client/src/i18n/en/budget.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,36 +12,7 @@
"addInvoice": "Add Invoice",
"addVendor": "Add Vendor"
},
"availableFunds": "Available Funds",
"projectedCostRange": "Projected Cost Range",
"expectedPayback": "Expected Payback",
"paybackCapped": "Some subsidies are oversubscribed — payback values are capped",
"remaining": "Remaining",
"remainingDetail": "Remaining budget — tap for details",
"categories": "Categories",
"allCategories": "All categories",
"noCategories": "No categories",
"selectAll": "Select All",
"clearAll": "Clear All",
"bars": {
"claimedInvoices": "Claimed Invoices",
"paidInvoices": "Paid Invoices",
"pendingInvoices": "Pending Invoices",
"projectedOptimistic": "Projected (optimistic)",
"projectedPessimistic": "Projected (pessimistic)",
"overflow": "Overflow"
},
"remainingPerspectives": {
"vsMinPlanned": "Remaining vs Min Planned",
"vsMaxPlanned": "Remaining vs Max Planned",
"vsProjectedMin": "Remaining vs Projected Min",
"vsProjectedMax": "Remaining vs Projected Max",
"vsActualCost": "Remaining vs Actual Cost",
"vsActualPaid": "Remaining vs Actual Paid",
"vsMinPlannedWithPayback": "Remaining vs Min Planned (incl. payback)",
"vsMaxPlannedWithPayback": "Remaining vs Max Planned (incl. payback)"
},
"ofAvailableFunds": "of available funds",
"costBreakdown": {
"tableCaption": "Budget cost breakdown by area and item",
"loading": "Loading cost breakdown…",
Expand Down
2 changes: 1 addition & 1 deletion client/src/lib/budgetApiFactory.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const makeLine = (overrides: Partial<TestBudgetLine> = {}): TestBudgetLine => ({
quantity: null,
unit: null,
unitPrice: null,
includesVat: null,
includesVat: true,
...overrides,
});

Expand Down
2 changes: 1 addition & 1 deletion client/src/lib/budgetConstants.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const makeLine = (overrides: Partial<BaseBudgetLine> = {}): BaseBudgetLine => ({
quantity: null,
unit: null,
unitPrice: null,
includesVat: null,
includesVat: true,
...overrides,
});

Expand Down
2 changes: 1 addition & 1 deletion client/src/lib/budgetSourcesApi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -678,7 +678,7 @@ describe('budgetSourcesApi', () => {
quantity: null,
unit: null,
unitPrice: null,
includesVat: null,
includesVat: true,
createdBy: null,
hasClaimedInvoice: false,
createdAt: '2026-01-01T00:00:00.000Z',
Expand Down
8 changes: 4 additions & 4 deletions client/src/lib/householdItemBudgetsApi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ describe('householdItemBudgetsApi', () => {
quantity: null,
unit: null,
unitPrice: null,
includesVat: null,
includesVat: true,
};

mockFetch.mockResolvedValueOnce({
Expand Down Expand Up @@ -128,7 +128,7 @@ describe('householdItemBudgetsApi', () => {
quantity: null,
unit: null,
unitPrice: null,
includesVat: null,
includesVat: true,
};

mockFetch.mockResolvedValueOnce({
Expand Down Expand Up @@ -184,7 +184,7 @@ describe('householdItemBudgetsApi', () => {
quantity: null,
unit: null,
unitPrice: null,
includesVat: null,
includesVat: true,
};

mockFetch.mockResolvedValueOnce({
Expand Down Expand Up @@ -291,7 +291,7 @@ describe('householdItemBudgetsApi', () => {
quantity: null,
unit: null,
unitPrice: null,
includesVat: null,
includesVat: true,
};

mockFetch.mockResolvedValueOnce({
Expand Down
Loading
Loading