# NACEF × Odoo POS — Gap Analysis (Step 2)

Scope: what Odoo 17 native **Point of Sale** (`pos.order`, `pos.order.line`, `pos.session`,
`pos.config`, `pos.payment`, `account.tax`, `res.company`) already covers vs. what the
`nacef_fiscal_integration` addon must add to satisfy the NACEF cahier des charges
(NACEF-CC-MDF-01) and pass the 21-step certification scenario (NACEF-PROCTEST-02).

Companion file: `traceability_matrix.csv` (all 74 E-codes + 12 S-MDF integration items,
each tagged NATIVE / PARTIAL / CUSTOM / N-A).

---

## 0. Multi-company vs multi-database (your explicit question)

**Recommendation: one Odoo database per taxpayer (contribuable) — DB-per-tenant, not
multi-company inside a shared DB.**

Why the NACEF model pushes this way:

1. **The fiscal unit is the taxpayer.** NACEF identity is `matricule fiscal → IMDF →
   certificate → one "S-MDF Client"`. The spec says *"une seule installation par
   contribuable / IMDF"* for the S-MDF Client. That 1:1 boundary maps cleanly onto one
   tenant = one DB.
2. **Inalterability & audit trail are per-taxpayer and must be independently
   exportable/verifiable** (E1101, E0904 filename = `matricule_IMDF_MAC`, E1301–E1304,
   E1702 USB export). A shared multi-company DB co-mingles hash chains, audit trails and
   archives across taxpayers — a tax auditor exporting one taxpayer's data would be
   crossing a confidentiality boundary the CDC explicitly protects.
3. **Gapless fiscal sequences (E0402) are fragile in multi-company.** Odoo sequences can
   be shared/interleaved; a per-DB `matricule/IMDF` counter is far easier to prove
   continuous and chronological.
4. **Retention / purge / archive / restore (E1401–E1601) operate per taxpayer.** DB-level
   isolation means one tenant's purge or restore can never touch another's data.
5. **Blast radius & confidentiality clause.** Corruption, restore, or a data-subject
   request stays inside one DB.

Trade-off: more databases to provision, upgrade and monitor. Mitigate with an
orchestration layer (per-DB provisioning, shared addon code, centralized upgrade
pipeline). This is standard Odoo SaaS practice (e.g. odoo.sh style) and is worth it here
because the fiscal isolation requirements are non-negotiable.

**Within one taxpayer DB:** multiple establishments (Store ID, E0506) and tills are handled
by `pos.config` per till and — only if the taxpayer is legally several companies —
`res.company`. So the IMDF field lives on **`res.company`** (primary) with an optional
override on `pos.config` for taxpayers whose establishments map to separate S-MDF
Clients/IMDFs. Keep it configurable; do not hard-code one IMDF per DB.

**Cloud topology note:** "Caisse en mode Cloud" + centralized S-MDF Client is orthogonal to
DB layout. The central S-MDF Client is still *per-taxpayer/IMDF*; each physical till runs the
Ministry S-MDF Agent (registered by MAC, HTTPS), relaying Odoo POS → central S-MDF Client.
DB-per-tenant keeps Odoo aligned with that per-taxpayer S-MDF Client.

---

## 1. Coverage summary

| Family | Codes | Native | Partial | Custom | N-A (process/docs) |
|---|---|---|---|---|---|
| Supplier obligations | E0101–E0103 | – | – | – | 3 |
| Documentation | E0201–E0209 | – | – | – | 9 |
| Tech modules | E0301–E0312 | – | 6 | 5 | 1 |
| Transactions | E0401–E0404 | – | 3 | 1 | – |
| Ticket | E0501–E0508 | 1 | 5 | 2 | – |
| Versions | E0601–E0606 | 1 | 2 | 1 | 2 |
| Access | E0701–E0703 | 3 | – | – | – |
| Config | E0801–E0803 | – | 1 | 2 | – |
| Audit trail | E0901–E0905 | – | 1 | 4 | – |
| Data recording | E1001–E1003 | – | 2 | 1 | – |
| Integrity | E1101–E1102 | – | – | 2 | – |
| Closing | E1201–E1205 | – | 1 | 4 | – |
| Archiving | E1301–E1304 | – | – | 4 | – |
| Purge | E1401–E1402 | – | – | 2 | – |
| Retention | E1501–E1503 | – | – | 3 | – |
| Backup/restore | E1601 | – | – | 1 | – |
| Fiscal-admin access | E1701–E1702 | – | – | 1 | 1 |
| **S-MDF integration** | SMDF-01–12 | – | – | 12 | – |

Headline: **almost nothing fiscal is free.** Odoo gives you the *cash register* (orders,
lines, taxes, sessions, payments, users, receipts, offline queue). Every *fiscal-protection*
behavior — inalterability, gapless numbering, fiscal closings, audit trail, archiving, and
the entire S-MDF handshake — is custom.

---

## 2. What Odoo native already gives you (lean on it)

- **Order + line + tax model.** `pos.order` / `pos.order.line` with `account.tax`,
  `amount_total`, `amount_tax`, per-line tax. Covers the *computation* behind E0506
  (n)(o)(p)(q) and E0401. You map A4 families → product categories and A5 tax codes →
  `account.tax`.
- **Payments.** `pos.payment` / `pos.payment.method` cover E0506(s)(t)(u) — cash, card,
  check, mobile, etc. — plus change (`amount_return`). Just relabel to the NACEF payment
  codes (`cash/check/bank_card/restaurant_ticket/mobile_payment/contre_bon/transfer`).
- **Users & rights (E0701–E0703): effectively native.** Odoo users/groups + POS security
  groups give unique user id, per-function rights, and user id captured on the order.
- **Refunds (E1002, E0501-REFUND): native +/-.** Odoo refunds are already corrective
  negative entries, not in-place edits — the E1002 "plus/moins" principle.
- **Offline capability (SMDF-08 substrate).** Odoo POS already queues orders offline in the
  browser and syncs on reconnect. You reuse that plumbing but gate it on the S-MDF
  `availableOfflineTickets` counter instead of "sync whenever."
- **Currency (E0507): native** — set company currency to TND.
- **Receipt rendering (E0506 surface): native OWL template** — `OrderReceipt` is the hook
  point to add IMDF, QR, fiscal id, client category, etc.
- **Module version (E0601/E0605): native** — addon manifest version.

---

## 3. What you must add (the real work)

### 3.1 S-MDF integration layer — the core of homologation (SMDF-01…12)
Not E-coded but it *is* what the 21-step demo tests. Entirely custom:
- `NacefClient` Python service wrapping the 5 endpoints (`GET /manifest/`,
  `POST /certificate/request/`, `POST /sync/request/`, `POST /signature/request/`,
  `POST /log/`) with dataclasses/Pydantic matching the exact JSON objects (SMDFManifest,
  SMDFCertificateInfo, SICCertificateRequest, SMDFTicketInfo, SMDFSignedTicket,
  SMDFSyncRequest, SICLogEntry, SMDFVersionsInfo, EquipmentVersionsInfo).
- **State machine** persisted + displayed: FACTORY → CERT_REQUESTED → (CERTIFICATE_GENERATED)
  → SYNCHRONIZED, plus SUSPENDED / NOT_SYNCHRONIZED / MAINTENANCE /
  SYNCHRONIZATION_IN_PROGRESS. Driven by `getManifest.status` +
  `certificateInfo.certRequestStatus`.
- **Sale guard (E0302, SMDF-07):** block order validation unless `status == SYNCHRONIZED`;
  render the state-specific message the demo expects (no cert / must request cert / must
  sync / suspended / revoked).
- **Ticket Zero** printing on first sync (steps 9/16).
- **QR + fiscal id** on the receipt (SMDF-09; ISO/IEC 18004, 170×220px, 300dpi).
- **Offline counter (SMDF-08):** track `availableOfflineTickets`; when exhausted the S-MDF
  goes NOT_SYNCHRONIZED → force resync (steps 11/14/15).
- **Agent-by-MAC registry (SMDF-10):** per-till agent record keyed on MAC, HTTPS relay to
  the central S-MDF Client (your "Caisse en mode Cloud" topology).
- **Error-code handling (SMDF-11/12):** full map of 103–116 / 500–554 to retry / message /
  contact-supplier / re-request-cert behaviors.

### 3.2 Inalterability & integrity (E1101, E1102) — CRITICAL
Odoo **Community has no fiscal inalterability.** Odoo *Enterprise* ships hash-chaining for
France (`l10n_fr_pos_cert`) and Germany — **reuse that pattern**: a per-order secure hash
chained to the previous order, stored immutably, with an integrity-report action. Document
the mechanism in the E0205/E1303 dossier.

### 3.3 Gapless fiscal numbering (E0402) — CRITICAL
`pos_reference` is **not** guaranteed gapless/chronological. Add a dedicated
continuous per-IMDF fiscal counter, irreversible and intangible, decoupled from Odoo's
internal sequences.

### 3.4 Reprint lockdown (E0505) — CRITICAL / easy to fail
Odoo lets a cashier **reprint any receipt freely** — a direct violation. You must disable
plain reprint and implement a **DUPLICATE ("Ticket copie")** transaction: new number, stamped
"Ticket copie", referencing the origin (Réf. Origine in A3), each copy a recorded transaction.

### 3.5 Fiscal closing (E1201–E1205)
`pos.session` close ≠ fiscal closing. `pos.session` is a cashier shift with cash control.
You need **daily/monthly/annual fiscal closings** that store a **period cumulative total**
and a **perpetual grand total** (never reset, survives upgrades — E1205), block recording in
a closed period (E1202), and run manually or by cron (E1203). This is the "Ticket zéro /
clôture" Z-report notion, verified around steps 9/17.

### 3.6 Audit trail (E0901–E0905)
Odoo's `mail.tracking` / logging is not a fiscal piste d'audit. Build an **append-only** model
(no `write`, no `unlink`, DB-enforced) with the exact fields (datetime `YYYYMMDD-HH24MNSS`,
module code+label, operation code+label, INFO/ERREUR, message), an **ASCII export** (`:`
separated, message=JSON, filename `matricule_IMDF_MAC`), and **mirror each entry to
`POST /log/`** (SMDF-05). Operations to trace are enumerated in the CDC
(UPGRADE/CASHING/PURGE/BACKUP/RESTORE/OFFLINE/ONLINE/PARAMETERS/PRINTER/CERT_REQUEST/
SYNC_REQUEST/SIGN_REQUEST/USER_LOGIN).

### 3.7 Training mode (E0403 TRAINING, E1003)
No Odoo equivalent. Add manager-gated training mode: transactions recorded+secured like real
ones but flagged, visible on screen, and every ticket stamped "Formation".

### 3.8 Config surfaces (E0801–E0803)
- IMDF field on `res.company` (+ optional `pos.config`) with an update flow (step 2).
- A4/A5 mapping UI: product categories → activity/family codes; `account.tax` → A5 codes
  (10=TVA7%, 11=TVA19%, 20=timbre 0.100 DT).
- JSON normative validator (E0803) run on the ticket before signature (no special chars).

### 3.9 Ticket content & template (E0506, E0508, E0503/E0504)
Extend the OWL receipt to carry: IMDF, MDF transaction ref, QR, fiscal id, CE serial+version
(`{SERIE}-{VERSION}`), Store ID, Agent Id, client category NP/PP, avantage fiscal reference,
transaction type label, VAT as NN.NN. Most *values* exist; the *fields and layout* per A3 do
not.

### 3.10 Archiving / purge / retention / backup / fiscal-admin export (E1301–E1702)
All custom, lower-priority for the provisional verification but required for full
homologation: open-format archives with independent integrity proof, purge-with-mandatory-
archive that protects counters, retention config, backup/restore traced to the audit trail,
and USB/downloadable export of encaissement data + audit trails for the fiscal
administration.

---

## 4. Top risks / red flags (fail-the-demo candidates)

1. **E0505 free reprint** — Odoo default behavior violates it out of the box.
2. **E0402 gapless numbering** — Odoo `pos_reference` gaps will not pass "séquence continue".
3. **E1101 inalterability** — absent in Community; must port the Enterprise hash-chain
   pattern and *document* it (E0205/E1303).
4. **E0302 hard block** — the demo (steps 4/7/8/15/19/21) explicitly checks that a sale is
   *impossible* in every non-SYNCHRONIZED state with the correct message. The guard must be
   server-enforced, not just a JS check.
5. **Offline counter semantics (SMDF-08)** — steps 11/14/15 verify that exhausting offline
   tickets forces a resync and blocks further sales.

---

## 5. Open external dependencies (blockers to resolve before coding)

- **Annexe A3 JSON schema is NOT in the PDF.** The CDC says the ticket JSON schema +
  examples are downloaded from `homologation.nacef.tn`. Get the actual schema before
  building `SMDFTicketInfo.base64Ticket` — our field list (E0506/A3) is the model, not the
  authoritative schema.
- **S-MDF Client + S-MDF Agent binaries** are Ministry-provided (per environment/OS).
  Needed to test `NacefClient` against a real S-MDF.
- **Editor account + test environment + test certificate** on `homologation.nacef.tn`
  (PROCTEST §III: create éditeur account, request test env with a fixed public IPv4 in
  Tunisia, declare equipment by serial+MAC, request test certificate).
- **Confirm S-MDF type**: your cloud topology = **Server** type (central S-MDF Client, many
  Agents by MAC). Certificate/sync can be initiated from any registered Agent but must be
  completed on the same device; concurrent init returns `SMDF_LOCKED_CERT_SYNC_REQUEST`
  (10-min lock).

---

## 6. Traceability to the 21-step certification scenario (PROCTEST-02)

| Steps | Feature under test | Addon components |
|---|---|---|
| 1 | Access separation (supplier/taxpayer/agent) | POS security groups; gate NACEF actions to taxpayer role |
| 2 | Config UI: IMDF, A4/A5 families/VAT, JSON validity | Config surfaces (3.8) |
| 3 | S-MDF state = 401 UNAUTHORIZED (FACTORY) | Manifest read + state display |
| 4 | Sale impossible, "must get certificate" | Sale guard (3.1) |
| 5 | Launch certificate request | `POST /certificate/request/` wizard |
| 6 | State CERT_REQUESTED + friendly info (state/online-offline/offline count/cert validity) | Fiscal-info screen |
| 7,8 | Sale still impossible (no cert / must sync) | Guard messages |
| 9 | Sync → Ticket Zero printed | `POST /sync/request/` + ticket zero print |
| 10 | 2 online sales, tickets reach NACEF, printed per A3 | Signature hook + A3 receipt |
| 11 | 3 offline sales, printed per A3 | Offline signing + counter |
| 12 | Back online: SYNCHRONIZED / ONLINE / availableOfflineTicket correct | Manifest refresh |
| 13 | 1 online sale | Signature hook |
| 14 | 7 offline sales | Offline counter |
| 15 | Online sale blocked → must sync | Counter-exhaustion guard |
| 16 | Sync | `POST /sync/request/` |
| 17 | State SYNCHRONIZED | Manifest + closing counters |
| 18 | Suspend from platform | Handle SUSPENDED state |
| 19 | Sale blocked, "suspended" message | Guard |
| 20 | Revoke certificate | Handle revoked (519) |
| 21 | Sale blocked, "revoked, re-request" message | Guard + re-request flow |

---

## Next steps (after your review of Step 1 + Step 2)
3. Addon structure (`nacef_fiscal_integration`): models, controllers, views, POS JS override
   points.
4. `NacefClient` service + Pydantic/dataclass models for the 5 endpoints.
5. Exact hook points: order-confirmation guard (block unless SYNCHRONIZED) and
   receipt-template signing + QR plug-in.
