# Imported Pre-Package (LPCO / TANCIS)

LPCO applications received from TRA's TANCIS platform, matched to customs declarations,
billed through GePG, physically inspected, and reported back.

Design documents live in `imported-ppg-implementations/` at the repository root - one per
screen, plus the foundation documents (data model, backend architecture, TANCIS integration,
billing, frontend, settings).

## Running the tests

```bash
php artisan test --testsuite=Modules
```

**Feature tests need a MySQL/MariaDB database**, not SQLite. This is not a preference: the
application's schema cannot be built on SQLite - `customers` carries a fulltext index and
`wma_bill.outstanding_amount` is a `STORED GENERATED` column. Substituting plain columns for
those would give a suite that passes while computing a bill's outstanding balance by
different rules than production.

Create it once:

```sql
CREATE DATABASE react_app_testing CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
```

It defaults to `<DB_DATABASE>_testing`; override with `DB_TEST_DATABASE` in `.env`. The
connection is `mysql_testing` in `config/database.php`. **It is dropped and rebuilt on every
run**, so never point it at a database with data you want.

Tests that touch the database extend `Modules\ImportedPrePackage\Tests\DatabaseTestCase`,
which switches the connection. Tests that do not should extend PHPUnit's `TestCase` directly
and stay fast - most of this module's rules are pure and need no database at all.

## Talking to TANCIS

`TANCIS_DRIVER` selects the client:

| Value  | Behaviour |
| ------ | --------- |
| `http` | The real service. Production. |
| `fake` | `FakeTancisClient` - answers with the shape the real service answers with, including a business rejection code carried inside an HTTP 200. |

The fake exists so no UI or workflow work is ever blocked on the integration being live. It
is deliberately faithful about the one thing that matters most: **customs answers HTTP 200
and puts the rejection in the body**, so code that reads `successful()` and calls it a day
fails here rather than in production.

## Settings

Nothing reads the settings table except `ImportedPrePackageSettings`. Services take typed
policy objects (`LpcoFeePolicy`, `MatchingPolicy`, …), which is what keeps the fee and score
calculators pure - and therefore what lets the settings screen preview a proposed change by
running the real engine.

The policies enforce their own invariants in their constructors, not only in form requests,
so a policy built from a fixture or a hand-edited row cannot hold a nonsense configuration.
The one to know about: a UCR match alone must auto-match, and a UCR mismatch must be unable
to reach the review threshold.

Config defaults in `config/imported-pre-package.php` are the floor. A missing table, missing
row, or blank value all fall back to them, so the module cannot be configured into a state
where it refuses to bill.
