# NACEF Fiscal POS - Odoo 17 · Multi-tenant SaaS

A Tunisian **fiscal cash register** (caisse enregistreuse fiscale) built on Odoo 17
Point of Sale, packaged as a **multi-tenant SaaS**, compliant with the NACEF
cahier des charges (`NACEF-CC-MDF-01`) and its S-MDF web-service contract.

Every taxpayer (contribuable) gets their own Odoo database; a separate operator
console (control plane) manages all of them. Sales are signed by the Ministry
**S-MDF**, sealed against tampering, and printed with a fiscal id + QR code.

> **Status:** backend fiscal core + POS UI complete and green - **95 automated
> tests, 0 failures** (74 in Odoo 17 + 21 standalone), ~5,800 lines across 6
> add-ons. Real S-MDF integration is wired and contract-accurate; switching from
> the built-in mock to the live S-MDF is one setting.

---

## Table of contents
1. [What this is](#1-what-this-is)
2. [Architecture](#2-architecture)
3. [The add-ons](#3-the-add-ons)
4. [Requirement coverage](#4-requirement-coverage)
5. [Quick start (Docker)](#5-quick-start-docker)
6. [First-time onboarding journey](#6-first-time-onboarding-journey)
7. [How to use - by role](#7-how-to-use--by-role)
8. [Connecting the S-MDF (certificate flow)](#8-connecting-the-s-mdf-certificate-flow)
9. [The A3 ticket & receipt (+ schema E0803)](#9-the-a3-ticket--receipt)
10. [Offline behavior](#10-offline-behavior)
11. [Configuration reference](#11-configuration-reference)
12. [Testing](#12-testing)
13. [Credentials (demo)](#13-credentials-demo)
14. [What's remaining](#14-whats-remaining)
15. [Project layout](#15-project-layout)
16. [Key design decisions](#16-key-design-decisions)

---

## 1. What this is

NACEF (Système National pour l'Administration des Caisses Enregistreuses
Fiscales) requires that every point-of-sale transaction of a service-consumption
taxpayer be protected by a **MDF** (Module de Données Fiscales). We use the
software MDF (**S-MDF**), a Ministry-provided "black box" with two parts:

- **S-MDF Client** - one per taxpayer/IMDF, signs tickets and talks to NACEF.
- **S-MDF Agent** - one per till (registered by MAC), a local relay listening on
  `http://localhost:10006`.

This project is the **cash software (logiciel de caisse)** that:
- drives the S-MDF (certificate request, synchronisation, signature, logging);
- **blocks any sale** unless the S-MDF is `SYNCHRONIZED` (E0302);
- builds a schema-valid **A3 fiscal ticket** and prints the fiscal id + QR;
- keeps the encaissement data **inalterable** (hash-chained), **gapless**
  (E0402), **audited** (append-only piste d'audit), with **fiscal closings**,
  **archives** and **archive-first purge**;
- runs as a **SaaS**: DB-per-taxpayer + an operator control plane.

Reference documents live in `document/`:
`NACEF-CC-MDF-01.pdf` (cahier des charges), `NACEF-PROCTEST-02.pdf` (21-step
certification procedure), and `nacef-smdf-api-1.2.0.json` (the official S-MDF
OpenAPI spec, incl. the `Ticket` schema, downloaded from developers.nacef.tn).
Planning artefacts: `planning/traceability_matrix.csv`, `planning/02_gap_analysis.md`.

---

## 2. Architecture

### Multi-tenant: one database per taxpayer
Each taxpayer = one Odoo database. This is deliberate (see
`planning/02_gap_analysis.md §0`): NACEF's identity chain is
`matricule fiscal → IMDF → certificate → one S-MDF Client per contribuable`, and
the inalterability hash chains, audit trails, gapless sequences and archives are
per-taxpayer and must be independently auditable. A shared multi-company DB would
co-mingle them.

### Two planes
```
        ┌───────────── CONTROL PLANE (master DB, the operator / "superadmin") ─────────────┐
        │  Tenant registry · provisioning · IMDF/agent(MAC) registries · fleet health      │
        │  NACEF supplier back-office (CEF sales E0102, maintenance E0103)                  │
        └───────▲──────────────────────────────────────────────────────────────────────────┘
                │  health beacons (metadata only)          provisioning (orchestrator)
      ┌─────────┴────────┐  ┌──────────────────┐  ┌──────────────────┐
      │ tenant A (Odoo)  │  │ tenant B (Odoo)  │  │ tenant C (Odoo)  │  … one isolated DB each
      └──────────────────┘  └──────────────────┘  └──────────────────┘
```

### Per-till signing path (cloud deployment)
```
  Till (OWL POS in browser) ──sign──▶ S-MDF Agent (localhost:10006, MAC-registered)
                                              │
                                              ▼
                                     S-MDF Client (per taxpayer) ──▶ NACEF platform
```
The Odoo backend enforces the guard by **signature-as-authority**: a fiscal sale
is only valid if it carries an S-MDF signature, which can only exist while the
S-MDF was `SYNCHRONIZED`. "No signature → no sale."

---

## 3. The add-ons

Dependency order (tenant DB): `nacef_smdf_client → nacef_fiscal_core → nacef_pos`,
plus `nacef_theme` and `nacef_cockpit`. `nacef_control_plane` is independent and
installs in its **own master DB**.

| Add-on | Purpose | Tests |
|---|---|---|
| **nacef_smdf_client** | Pure-Python library over the 5 S-MDF web services: exact dataclasses, enums, full error catalog (103-614), `NacefClient` interface + real HTTP client (`AgentRestNacefClient`, paths under `/sic/external`, default `localhost:10006`) + `MockNacefClient` (in-memory state machine). | 21 (standalone) |
| **nacef_fiscal_core** | POS-agnostic fiscal backbone (below). | 35 |
| **nacef_pos** | Ties Odoo POS to the S-MDF (below). | 21 |
| **nacef_control_plane** | SaaS operator console: tenants, business types, IMDF/agent registries, fleet-health beacons, supplier back-office. Own master DB, depends only on `base`. | 14 |
| **nacef_theme** | Modern teal theme (replaces Odoo purple), branded login, product logo. | - |
| **nacef_cockpit** | Company experience: KPI home dashboard, Cashier/Manager roles, hides Discuss, POS as first app. | 4 |

### `nacef_fiscal_core` models
- `nacef.smdf.state` - persisted mirror of the S-MDF Manifest; drives the sale
  guard + 30-day cert-expiry warning (E0606); **action buttons** to Request
  certificate / Simulate (mock) / Synchronize / Refresh; logs ONLINE/OFFLINE
  transitions (E0901).
- `nacef.audit.log` - piste d'audit; `write`/`unlink` disabled (E0902), SHA-256
  hash chained, `verify_chain()` tamper detection (E1102), `export_ascii()`
  (E0904, filename `matricule_IMDF_MAC`).
- `nacef.fiscal.sequence` - row-locked, gapless, per-IMDF counter (E0402).
- `nacef.fiscal.closing` - daily/monthly/annual closings with period + perpetual
  totals (E1204/E1205), immutable once closed, hash-chained; blocks recording in
  a closed period (E1202).
- `nacef.fiscal.archive` - open-format (JSON / E0904 ASCII) archive of
  encaissement + audit + closings (≤ 1 year, E1301), SHA-256 integrity seal
  (E1303), downloadable (E1702).
- `nacef.fiscal.purge` - archive-first purge (E1401): generates + verifies a full
  archive before deleting, preserves counters + audit (E1402).
- `nacef.smdf.agent` - agent registry by MAC (accepts `200DB01F69EB` or
  `20:0D:B0:1F:69:EB`); + fleet-health **beacon push** cron.
- `res.company` / `pos.config` - IMDF, matricule, store id, CE serial, agent URL,
  client mode, accreditation reference. TND activated on install.

### `nacef_pos` behavior
- **Sign-or-block guard** (E0302) in `_process_order`; runs elevated so a cashier
  can sign without write access to fiscal models.
- **Gapless fiscal reference** assigned atomically (rolled back with the sale if
  signing fails → no gap).
- **Per-order inalterability chain** (E1101/E1102): SHA-256 chained per
  company+IMDF; protected fields can't be modified, order can't be deleted;
  `nacef_verify_inalterability()`.
- **A3 ticket JSON** - the real `Ticket` schema **1.1.4**, validated before
  signing by a bundled dependency-free validator (integer millimes, A5 tax codes
  `10`/`11`/`20`, all enums/patterns/minLengths). See section 9.1.
- **Receipt (OWL)** - fiscal id + real QR PNG; mandatory **mentions**
  (Ticket copie / formation / remboursement, E0503/E0504/E0505).
- **TRAINING mode** toggle (E0403/E1003) - recorded, not signed.
- **DUPLICATE "Ticket copie"** + **reprint lockdown** (E0505); **refund
  auto-detect** (E0504).
- Every sale traced in the piste d'audit and mirrored to `/log/`.

---

## 4. Requirement coverage

**Implemented & tested:** E0302, E0402, E0403, E0501-E0508 (ticket data + mentions),
E0505, E0507/E0508 (TND millimes), E0606, E0801, E0901-E0905 (incl. ONLINE/OFFLINE,
CASHING, SIGN/SYNC/CERT_REQUEST, PURGE), E1001-E1003, E1101/E1102, E1201-E1205,
E1301-E1304, E1401/E1402, E1702, the **A3 ticket schema**, and the S-MDF
integration set (manifest, cert request, sync, signature, log; states; offline
budget; agent-by-MAC; full error handling) - SMDF-01…12.

**Not code (deliverables):** the documentation dossiers E0201-E0209 / E1701.

**Remaining (see §14):** USB one-click export UI, backup/restore traceability
(E1601), memory supervision (E1503), and the live agent-mode end-to-end (needs
the Ministry S-MDF binaries + a test certificate).

Full matrix: `planning/traceability_matrix.csv`.

---

## 5. Quick start (Docker)

Requirements: Docker. This brings up PostgreSQL + two Odoo 17 instances (a tenant
POS and the operator control plane) with the add-ons mounted.

```bash
cd <this-repo>

# 1. network + shared filestore volume + Postgres
docker network create nacef-net
docker volume create nacef-filestore
docker run -d --name nacef-db --network nacef-net \
  -e POSTGRES_USER=odoo -e POSTGRES_PASSWORD=odoo -e POSTGRES_DB=postgres postgres:16

# 2. install the tenant DB (POS) and the master DB (control plane)
docker run --rm --network nacef-net -e HOST=nacef-db -e USER=odoo -e PASSWORD=odoo \
  -v nacef-filestore:/var/lib/odoo -v "$PWD/addons":/mnt/extra-addons odoo:17 \
  odoo -d testdb   -i nacef_pos,nacef_cockpit,nacef_theme --stop-after-init
docker run --rm --network nacef-net -e HOST=nacef-db -e USER=odoo -e PASSWORD=odoo \
  -v nacef-filestore:/var/lib/odoo -v "$PWD/addons":/mnt/extra-addons odoo:17 \
  odoo -d masterdb -i nacef_control_plane --stop-after-init

# 3. run the two servers
docker run -d --name nacef-odoo-web --network nacef-net -p 8069:8069 \
  -e HOST=nacef-db -e USER=odoo -e PASSWORD=odoo \
  -v nacef-filestore:/var/lib/odoo -v "$PWD/addons":/mnt/extra-addons odoo:17 \
  odoo -d testdb --db-filter='^testdb$'
docker run -d --name nacef-cp-web --network nacef-net -p 8070:8069 \
  -e HOST=nacef-db -e USER=odoo -e PASSWORD=odoo \
  -v nacef-filestore:/var/lib/odoo -v "$PWD/addons":/mnt/extra-addons odoo:17 \
  odoo -d masterdb --db-filter='^masterdb$'
```

- Company POS → http://localhost:8069 (db `testdb`)
- Control plane → http://localhost:8070 (db `masterdb`)

> **IMPORTANT - filestore:** every Odoo container (server, `-u`/`-i`, `shell`)
> MUST mount the same `-v nacef-filestore:/var/lib/odoo`. Running an update in a
> throwaway container without it writes the compiled JS/CSS into a discarded
> filestore → 500 on assets → white screen.

**Teardown:** `docker rm -f nacef-odoo-web nacef-cp-web nacef-db && docker network rm nacef-net && docker volume rm nacef-filestore`

---

## 6. First-time onboarding journey

One-time setup, then daily selling. Three actors: **operator**, **company admin**,
**cashier**.

### Phase 0 - NACEF portal prerequisites *(homologation.nacef.tn, external)*
1. Submit a **test-environment request** (component = "Logiciel de caisse serveur",
   name, version, **fixed Tunisian IPv4**, OS) → wait for IP validation.
2. **Declare equipment** (serial + MAC) → receive the **IMDF**.
3. **Download + install** the Ministry **S-MDF Client + Agent** (Agent on
   `localhost:10006`).
4. Obtain a **pairing code**.

### Phase 1 - Operator onboards the taxpayer *(control plane :8070)*
`NACEF Control Plane → Tenants → New`: name, matricule, **business type**, legal
rep → **Provision** → register the **IMDF** + **S-MDF Agent (MAC)** → declare the
**CEF sale** (E0102).

### Phase 2 - Company admin configures *(POS :8069, Manager)*
Company identity (matricule `^\d{7}[A-Z]$`, nom commercial, Store ID,
accreditation ref) · till (IMDF, CE serial, **Client mode** = Mock/Agent, Agent
URL) · products + VAT (7%/19%) + categories · create cashier users.

### Phase 3 - Activate the S-MDF (certificate)
`NACEF Fiscal → S-MDF State → <IMDF>` (FACTORY): **Request certificate** (enter
IMDF + pairing + OTP + PIN in the S-MDF popup) → registration unit issues the
cert → **Synchronize** (PIN change + OTP) → **SYNCHRONIZED** + Ticket Zéro.
Until SYNCHRONIZED, sales are blocked (E0302).

### Phase 4 - First sale
Point of Sale → open session → ring items → Validate → signed → receipt with
fiscal id + QR.

### Phase 5 - Daily use
Cashiers sell; managers monitor KPIs + S-MDF state, run closings, handle
offline/resync, print Ticket copie for reprints.

---

## 7. How to use - by role

**Operator (control plane, :8070)** - onboard/provision tenants, monitor
**Fleet Health** beacons, manage IMDF/agent registries, file supplier
declarations (E0102/E0103).

**Company Admin / Manager (:8069)** - everything: POS, **NACEF Fiscal** (S-MDF
state, audit trail, closings, archives, purge), users/branches, inventory,
products. Lands on the **Accueil** KPI dashboard (today/month sales, S-MDF status,
offline tickets, cert expiry, data integrity).

**Cashier (:8069)** - sell only. No NACEF Fiscal menu, reduced dashboard.
`Formation` toggle available for training tickets.

---

## 8. Connecting the S-MDF (certificate flow)

The certificate is **not a file you upload**. It is requested through the S-MDF
and issued by NACEF. Two modes, switched on the **S-MDF State** form
(*Client mode*): **Mock** (in-memory, the demo and QR) and **Agent** (the real
Ministry S-MDF).

| Step | Mock (testing) | Real (agent mode) |
|---|---|---|
| Request | **Request certificate** to CERT_REQUESTED | **Request certificate** opens the SIC's own windows (pairing + IMDF, then OTP, then PIN) |
| Issue | **Simulate cert generation** | registration unit to NACEF (portal *Mes Certificats de tests* / email under 24h) |
| Activate | **Synchronize** to SYNCHRONIZED | **Synchronize** (choose PIN + OTP) to SYNCHRONIZED |

### 8.1 The two Ministry services (on the till / server)

| Service | Process | Port | Role |
|---|---|---|---|
| **SMDF Server** | `java -jar smdf.jar` (`smdf.service`) | **10004** | the fiscal black box; talks to NACEF |
| **SIC Agent** | `java -jar sic.jar` | **10006** | local REST API we call (`/sic/external/*`); forwards to the SMDF |

**Agent URL must point to the SIC (`10006`)**, not the SMDF.

### 8.2 localhost-only and the bridge / connector

The SIC **only accepts requests from `localhost`**. If Odoo runs **in Docker or
the cloud**, it cannot call `localhost:10006` directly. Two helpers ship in
`tools/` (install once as a service):

- **`nacef_sic_bridge.py`** (`nacef-sic-bridge.service`) for server-side
  signing: forwards `172.20.0.1:10106` to `127.0.0.1:10006`. Set **Agent URL to
  `http://172.20.0.1:10106`**.
- **`nacef_sic_connector.py`** (`nacef-sic-connector.service`) for browser-side
  (cloud page to local SIC via CORS/PNA) on `:10016`.

```bash
sudo cp tools/nacef-sic-bridge.service /etc/systemd/system/
sudo systemctl daemon-reload && sudo systemctl enable --now nacef-sic-bridge
```
A native on-prem Odoo needs none of this: `localhost` already works, use
`http://localhost:10006`. One-shot installer for all three (SMDF + SIC +
connector): `tools/install-nacef-agent.sh`.

### 8.3 Step-by-step: go live on a real S-MDF

1. **Install** SIC + SMDF on the machine; confirm `10004` and `10006` are listening.
2. **Portal** (`homologation.nacef.tn`): declare the equipment by its **MAC**,
   get the **IMDF** and **pairing code**. The declared **fixed IP must equal the
   machine's real public IP and be validated** (a mismatch gives **error 512**).
3. **Odoo**: *Client mode = Agent*, *Agent URL* = the bridge (or
   `localhost:10006` native), fill matricule, IMDF, accreditation.
4. **Test S-MDF connection** returns `401 joignable, non autorisé`: reachable,
   not paired (expected at this point).
5. **Request certificate**: the SIC pops native windows on the machine's screen:
   pairing code + IMDF, then **OTP** (SMS), then **PIN** (captcha). Status moves
   to `CERT_REQUESTED`, cert request `PIN_VALIDATED`.
6. Registration unit issues the certificate, then **Refresh manifest** to
   `CERTIFICATE_GENERATED`.
7. **Synchronize**: SIC window (new PIN + OTP), device becomes **`SYNCHRONIZED`**,
   *Can Sign* true.
8. **Test connection** returns 200. Real sales now sign (real QR).

### 8.4 Troubleshooting

| Symptom | Cause | Fix |
|---|---|---|
| injoignable on `:10106` | bridge not running | start / install `nacef-sic-bridge` |
| injoignable on `localhost:10006` from Odoo | Docker `localhost` is not the host | use the bridge URL `172.20.0.1:10106` |
| `401 joignable, non autorisé` | not paired (localhost is handled by the bridge) | finish **Request certificate** then **Synchronize** |
| `error 512` / "not an SMDF gateway" | declared fixed IP does not match the real public IP (or unvalidated) | fix and validate the IP on the portal |

Ports at a glance: **SIC 10006, SMDF 10004, bridge 10106, connector 10016**.

---

## 9. The A3 ticket & receipt

Each sale builds the official **`Ticket`** JSON (schema 1.1.4), base64-encoded and
sent to `/sic/external/sign/request`. Money is integer **millimes** (TND, 3
decimals). Structure: `transaction{operation, originator}`, `merchant_identity`,
`customer_identity` (PP/NP), `sale_details[]{product, taxation, quantity,
discount}`, `tax_summary[]` (A5 codes `10`=7%, `11`=19%, `20`=stamp),
`payment_details`, `sale_summary`, `delivery_details`.

The printed **receipt** shows: mandatory mention (Ticket copie/formation/
remboursement), IMDF, Réf. fiscale (gapless), Réf. Transaction MDF (S-MDF id),
the **SA/AA** fiscal-advantage line, and the **QR code** (ISO/IEC 18004).

### 9.1 Respecting the schema (E0803)

Every ticket is **validated against the official `Ticket` schema (v1.1.4) before
signing** by a bundled, dependency-free validator
(`nacef_smdf_client/api/schema.py` + `ticket_schema_1_1_4.json`; the `jsonschema`
package is **not** in the Odoo image). A non-conformant ticket is **blocked**
("Ticket fiscal non conforme au schéma NACEF (E0803): ...") **and audited**. You
cannot sign an invalid ticket.

For a sale to pass, the taxpayer/till config must be **complete and valid**:

| Field | Where | Rule |
|---|---|---|
| IMDF | `res.company` / `pos.config` `nacef_imdf` | **14 to 16** chars |
| Matricule fiscal | `res.company.nacef_matricule_fiscal` | `^\d{7}[A-Z]$` (8 chars) |
| Accreditation reference | `res.company.nacef_accreditation_reference` | **8+** chars |
| CE serial number | `pos.config.nacef_ce_serial` | **8+** chars |
| Commercial name | `res.company` `nacef_nom_commercial`/`name` | 3+ chars |
| Address / City | `res.company` (partner) `street` / `city` | 8+ / 3+ chars |
| Store reference | `pos.config`/`res.company` `nacef_store_id` | exactly 3 digits `^\d{3}$` (falls back to `000`) |
| Agent MAC | `nacef.smdf.agent.mac_address` | 3+ chars (declared per till) |
| **Family code (A4)** | `pos.category.nacef_family_code` | **2 to 8** chars. Set the real **Annex-A4** code per POS category; if empty, a code is derived from the category name |
| Product name | `product.display_name` | 3+ chars |
| VAT tax code (A5) | `account.tax` | 7% -> `10`, 19% -> `11`, stamp -> `20` |
| A DUPLICATE ticket | - | must carry `duplicated_transaction_identifier` (auto-set from the original) |

If any field is missing or short, signing fails with the field named in the
error. Fix it and retry. **In production these come from real onboarding**; the
demo uses valid placeholders. Enter your real **Annex A4/A5 codes** on each POS
category (*Code famille (A4)*) and tax to replace the derived fallbacks.

---

## 10. Offline behavior

When NACEF is unreachable, the **S-MDF itself** signs offline (using its
`availableOfflineTickets` budget) and **buffers** the signed tickets, re-sending
them on the next **Synchronize** (CDC H01006). That data-keeping is the Ministry
black box's job, not the cash software's.

Our software: records every encaissement (never lost; Odoo POS also queues orders
if the server is down), tracks Online/Offline + offline budget, **logs
ONLINE/OFFLINE/NOT_SYNCHRONIZED transitions** (E0901), **blocks** when the offline
budget is exhausted → forces resync (E0302), and resyncs via the Synchronize
action.

---

## 11. Configuration reference

| Where | Setting | Meaning |
|---|---|---|
| `res.company` | `nacef_imdf`, `nacef_matricule_fiscal`, `nacef_nom_commercial`, `nacef_store_id`, `nacef_accreditation_reference` | Fiscal identity on every ticket |
| `res.company` | `nacef_client_mode` = `mock` \| `agent` | Mock state machine vs real S-MDF |
| `res.company` | `nacef_smdf_agent_default_url` | Agent URL (default `http://localhost:10006`) |
| `pos.config` | `nacef_imdf` (override), `nacef_ce_serial`, `nacef_smdf_agent_url` | Per-till (Agent URL: `http://172.20.0.1:10106` bridge in Docker, `http://localhost:10006` native) |
| `pos.category` | `nacef_family_code` | Annex-A4 family code (2 to 8 chars), printed as `family_code` |
| `ir.config_parameter` | `nacef.control_plane_url`, `nacef.beacon_token` | Fleet-health beacon push target |
| `ir.config_parameter` | `nacef.client_mode` | Fallback if company field unset |

Real deployment note: install the **Tunisia localization (`l10n_tn`)** so TND is
the company currency natively (the demo force-sets it).

---

## 12. Testing

```bash
# standalone S-MDF client library (no Odoo) - 21 tests incl. the 21-step scenario
cd addons/nacef_smdf_client && python3 -m unittest tests.test_api -v

# full Odoo suite on a throwaway DB (74 tests)
docker run --rm --network nacef-net -e HOST=nacef-db -e USER=odoo -e PASSWORD=odoo \
  -v nacef-filestore:/var/lib/odoo -v "$PWD/addons":/mnt/extra-addons odoo:17 \
  odoo -d cleandb -i nacef_pos,nacef_cockpit,nacef_control_plane \
  --test-enable --test-tags /nacef_smdf_client,/nacef_fiscal_core,/nacef_pos,/nacef_cockpit,/nacef_control_plane \
  --stop-after-init --no-http --log-level=test
# then: docker run ... psql ... -c "DROP DATABASE cleandb;"
```

Latest: **`0 failed, 0 error(s)`** - fiscal_core 35, pos 21, control_plane 14,
cockpit 4 (Odoo) + smdf_client 21 (standalone) = **95 tests**. Run tests on a
throwaway DB, never against the demo `testdb`.

---

## 13. Credentials (demo)

| Instance | URL | DB | Logins |
|---|---|---|---|
| Company POS | http://localhost:8069 | `testdb` | `admin`/`admin`, `manager`/`manager`, `cashier`/`cashier` |
| Control plane | http://localhost:8070 | `masterdb` | `operator`/`operator`, `admin`/`admin` |

Demo passwords - change before any real use.

---

## 14. What's remaining

- **USB fiscal-admin export** - one-click action + manual (export logic exists,
  E1701/E1702).
- **Backup/restore traceability** (E1601) and **memory supervision** (E1503, low
  priority for cloud).
- **Documentation dossiers** (E0201-E0209) - homologation paperwork.
- **Live agent-mode end-to-end** - needs the Ministry S-MDF binaries + a test
  certificate; the code path is complete and contract-accurate.
- **Cross-instance provisioning** - the operator "Provision" currently models the
  lifecycle; wiring it to actually create the tenant DB is an orchestration step.

---

## 15. Project layout

```
addons/
  nacef_smdf_client/   api/ (models, enums, errors, client) + tests
  nacef_fiscal_core/   models/ (state, audit, sequence, closing, archive, purge,
                       agent, company, config) + security + views + data + tests
  nacef_pos/           models/pos_order.py + static/src/app (OWL js + receipt xml)
                       + views + tests
  nacef_control_plane/ models/ (tenant, imdf, agent, beacon, backoffice) +
                       controllers + security + views + tests
  nacef_theme/         static/src/scss + static/img (logo)
  nacef_cockpit/       models/dashboard + security(roles) + views + data + tests
  README.md            (add-on quick reference)
document/              the CDC + PROCTEST PDFs + nacef-smdf-api-1.2.0.json
planning/              traceability_matrix.csv + 02_gap_analysis.md
```

---

## 16. Key design decisions

- **DB-per-taxpayer**, not multi-company - fiscal isolation, per-tenant
  inalterability/audit/archives, gapless sequences, confidentiality.
- **Signature-as-authority** guard - a sale is valid only if S-MDF-signed;
  server-enforced without needing live S-MDF access on every read.
- **Mock-first** - `MockNacefClient` drives the entire flow (incl. the 21-step
  PROCTEST scenario) so everything is buildable/testable before the Ministry
  binaries and certificate exist. Switch to `agent` mode with one setting.
- **Operator isolation** - the control plane manages tenant lifecycle + metadata,
  never fiscal content; health arrives via metadata-only beacons.
- **Odoo 17 Community** baseline; **stdlib dataclasses** (no external deps in the
  client); **TND** (millimes) throughout.
```
