# Ledger Integration Tests Refactor Specification

## Objective

Refactor existing ledger integration tests to validate **semantic correctness** of accounting
entries, not just numerical balance. Current tests verify totals match but ignore which accounts are
debited/credited—the essence of double-entry bookkeeping.

## Scope

- Files to modify:
  - `packages/server/src/modules/ledger/__tests__/ledger-scenario-a.integration.test.ts`
  - `packages/server/src/modules/ledger/__tests__/ledger-scenario-b.integration.test.ts`
- Scenarios covered:
  - Scenario A: Simple ILS receipt expense (500 ILS office supplies)
  - Scenario B: Foreign currency USD invoice expense (200 USD consulting @ 3.5 ILS/USD)

## High-Level Goals

1. Ensure each ledger record correctly assigns debit/credit entities (accounts / tax categories /
   counterparties).
2. Preserve and validate foreign currency amounts and implied exchange rates where applicable.
3. Validate dates, metadata, and ownership integrity.
4. Assert per-record internal balance (not only aggregate balance).
5. Prevent orphaned amounts (amount without entity) and improper null/zero usage.
6. Verify business logic expectations (expense side vs payment side placement).
7. Validate audit trail fields and systemic correctness (locked flag, timestamps).
8. Confirm error-free generation and completeness of expectation data.

## Detailed Validation Requirements

### 1. Account / Entity Assignment (Critical)

For each scenario:

- Locate records by `debit_entity*` / `credit_entity*` matching deterministic UUIDs produced by
  `makeUUID`.
- Scenario A:
  - Debit: `expense-general`
  - Credit: `bank-account-tax-category`
  - Secondary entities (`entity2`) must be `NULL`.
- Scenario B:
  - Debit: `expense-consulting`
  - Credit: `usd-account-tax-category`
  - Validate optional presence of supplier (`supplier-us-vendor-llc`) if system maps payable logic.
    If supplier not present, document assumption inline.
- Assert no unexpected entities appear.

### 2. Foreign Currency Preservation (Scenario B)

- `debit_foreign_amount1` and/or `credit_foreign_amount1` must equal `200.00` (USD) as strings.
- `currency` must be `USD` for all generated records in Scenario B, `ILS` in Scenario A.
- Local amounts must equal foreign × exchange rate (200 × 3.5 = 700.00 ILS).
- Secondary foreign amounts (`*_foreign_amount2`) must be `NULL` unless business logic creates
  multi-leg entries.
- Scenario A: All foreign amount fields must be `NULL`.

### 3. Dates Accuracy

- `invoice_date` should match fixture document date.
- `value_date` should match transaction event/value date.
- Reasonable bounds: Not in future and not pre-2020 (configurable constant if desired).
- Verify `invoice_date <= value_date + credit terms tolerance` (e.g., 30 days) or document
  deviation.

### 4. Description & Reference Metadata

- `description` non-empty, meaningful (not generic placeholders). Optionally pattern match keywords
  (e.g., "office", "consulting").
- `reference1` must contain or equal document serial number or external identifier (e.g., invoice
  number).
- At least one ledger record must include the document serial number reference.

### 5. Owner Assignment

- All records: `owner_id === adminContext.ownerId` for the given test execution.
- No `NULL` owner IDs.

### 6. Individual Record Internal Balance

For every record:

- Sum of debit local amounts (`debit_local_amount1 + debit_local_amount2`) equals sum of credit
  local amounts (`credit_local_amount1 + credit_local_amount2`).
- Allow rounding tolerance (`< 0.01`).
- Total of all amounts per record must be > 0 (avoid empty records).

### 7. Orphaned Amount Prevention

- Any non-zero local or foreign amount implies presence of corresponding entity (`debit_entityX` or
  `credit_entityX`).
- If entity field is `NULL`, corresponding amount fields (`*_local_amountX`, `*_foreign_amountX`)
  must be `NULL`.

### 8. Amount Sign & Positivity

- All magnitude fields stored as positive numbers; debit/credit classification provides semantic
  direction.
- Foreign and local amounts must be positive or `NULL`.
- Negative numeric string values should not appear; if they do, test must fail.

### 9. Null vs Zero Semantics

- Unused `*_amount2` fields must be `NULL`, not zero.
- Required primary amounts (`debit_local_amount1`, `credit_local_amount1`) cannot be `NULL`.
- Foreign amounts in local currency scenarios must be `NULL` (not zero).

### 10. Audit Trail Fields

- `created_at` and `updated_at` present.
- For freshly created records: `created_at === updated_at`.
- `created_at` timestamp within a reasonable recent window (e.g., within test run timeframe;
  configurable tolerance).
- `locked === false` for newly generated records.

### 11. Exchange Rate Implied Validation (Scenario B)

- Implied rate: `local_amount1 / foreign_amount1 ≈ 3.5` (tolerance: 0.01 or fewer decimal places as
  needed).
- Consistency across all records with foreign amounts.
- No divergent implied rates per side.

### 12. Charge Linkage

- All records: `charge_id === expected deterministic charge UUID` per fixture.
- No stray records referencing other charges.

### 13. Business Logic Assertions

Expense scenario expectations:

- Expense accounts appear exclusively on debit side (increase in expense).
- Bank (or cash) accounts appear on credit side (decrease in asset due to payment).
- Totals aggregated by entity role confirm direction (sum of expense debits > 0, sum of bank
  credits > 0).

### 14. Error Array Validation

- `result.errors` must be an empty array.
- If non-empty, fail fast with error context.

### 15. Optional / Future: VAT Handling (Not currently in fixtures)

- If VAT introduced: split debits (net expense + VAT receivable) vs full credit (payment).
- Validate VAT percentage (e.g., 17%) and rounding.
- Ensure `debit_entity2` corresponds to VAT account.

### 16. Optional / Future: Supplier / Payables Logic

- If system maps payables: presence of supplier ledger line on credit side before cash settlement.
- Distinguish between invoice recognition (supplier credit) and payment (bank credit, supplier
  debit) if multi-stage entries are adopted.

### 17. Test Structure Refactor

Introduce grouped describes:

```typescript
describe('Core Ledger Validations', ...)
describe('Amounts', ...)
describe('Metadata', ...)
describe('Business Logic', ...)
describe('Foreign Currency (Scenario B)', ...)
```

Each block isolates concerns for easier maintenance and targeted failures.

### 18. Helper Extraction (Recommended)

Create utility functions in a shared test helper (e.g.,
`packages/server/src/modules/ledger/__tests__/helpers/ledger-assertions.ts`):

- `assertEntityAssignments(records, scenarioType)`
- `assertForeignCurrency(records, expectedRate, expectedForeignAmount)`
- `assertRecordInternalBalance(record)`
- `assertNoOrphanedAmounts(record)`
- `assertAuditTrail(record)`
- `assertOwner(record, ownerId)`
- `collectTotalsByEntity(records)` for summarizing business logic tests.

### 19. Deterministic UUID Reliance

Continue using `makeUUID` seeds; add explicit comments noting reliance for reproducibility.

### 20. Rounding / Tolerance Strategy

Centralize numeric comparison with helper:

```typescript
function expectClose(actual: number, expected: number, tolerance = 0.01) { ... }
```

Use for:

- Exchange rate checks
- Per-record balance comparisons

### 21. Failure Messaging

Use descriptive assertion messages:

- Include record index, entity UUID, field name.
- Example: `Record 1: debit_local_amount1 present without debit_entity1`.

### 22. Non-Functional Considerations

- Keep test runtime reasonable by reusing DB setup already implemented.
- Avoid re-querying ledger table repeatedly; query once per scenario and pass to helpers.
- Cleanup logic remains unchanged.

### 23. Success Criteria

Refactor complete when:

- All new assertions pass for both scenarios.
- Failure of any semantic rule yields clear message.
- Coverage includes each requirement above (except optional future VAT/payables logic).

### 24. Out-of-Scope (Explicit)

- Introducing VAT splits where not in fixtures.
- Changing generation algorithm.
- Modifying production ledger schema.
- Locking mechanism tests beyond initial state.

## Implementation Phases

1. Add helper assertion module.
2. Refactor Scenario A tests applying core + metadata + logic validations.
3. Extend Scenario B tests with foreign currency & exchange rate validations.
4. Consolidate repetitive inline checks into helpers for readability.
5. Run and adjust tolerance issues (rounding, string vs numeric conversions).

## Risk & Mitigation

- Rounding differences: Use `Number()` + tolerance helper.
- Null vs zero confusion: Enforce explicit checks early to catch fixture drift.
- Schema evolution: Keep helper referencing interface rather than raw property names when possible.

## Future Enhancements (Documented for Follow-Up)

- Introduce dedicated VAT scenario.
- Add payable cycle test (invoice creation vs payment clearing).
- Snapshot testing of ledger record JSON structure.
- Add test ensuring ledger lock mutations prevent alteration (once implemented).

---

**End of Specification**
