# Odoo integration (read-only)

Tracks OVER-468. The service reads the month's **Software** customer invoices from
Odoo; it never writes. The client (`service/app/odoo/client.py`) is read-only by
construction — it issues only `search`/`search_read`/`read`/`search_count`, and a
hard guard raises `PermissionError` on any other method.

## What's done (no credentials required)

- `JsonRpcOdooClient` — native Odoo `/jsonrpc` `object.execute_kw`. Pulls posted
  `out_invoice` moves with `invoice_date` in the month, filtered to Software,
  returning `amount_untaxed` (the VAT-exclusive matched figure), `amount_total`,
  customer and the sent flag.
- `OcaRestOdooClient` — clear stub for the OCA REST flavour (open question 2).
- Software filter is configurable via `ODOO_SOFTWARE_FILTER` (`journal:<name>` |
  `category:<name>` | `analytic:<tag>`).
- `check_connection(year, month)` + a CLI self-test.
- Mock-based unit tests (`service/tests/test_odoo_client.py`) covering the domain
  built, invoice mapping, the read-only guard, and the client factory.

## One-command verification (once credentials are set)

Set `ODOO_URL`, `ODOO_DB`, `ODOO_USERNAME`, `ODOO_API_KEY` (and
`ODOO_SOFTWARE_FILTER`) in `.env`, then:

```
# in the service container, or any env with the deps installed
python -m app.odoo.client --check --month 2026-10
```

It authenticates and prints, e.g.:

```json
{ "authenticated": true, "uid": 7, "month": "2026-10",
  "filter_kind": "journal", "filter_value": "Software",
  "posted_out_invoices": 41, "software_invoices": 37 }
```

A failure prints `{"authenticated": false, "error": ..., "type": ...}` and exits 1.

## What's needed to finish OVER-468

1. **A read-only Odoo account** — `ODOO_URL`, `ODOO_DB`, `ODOO_USERNAME`,
   `ODOO_API_KEY` with invoice-read scope only.
2. **The Software-invoice rule (PRD open question 1).** Confirm exactly how a
   Software invoice is identified — journal name, product category, or analytic
   tag — and the exact value, so `ODOO_SOFTWARE_FILTER` is right. The PRD
   screenshot didn't come through.
3. **Confirm the REST flavour (open question 2)** — native JSON-RPC (assumed) or
   the OCA REST module. If OCA, the stub needs implementing.
4. **Confirm the "sent" rule.** The PRD counts an invoice once it is *posted and
   emailed*. The client currently returns all posted Software invoices and
   captures the `is_move_sent` flag per invoice (field name varies by Odoo
   version). Confirm whether to hard-filter to sent-only in the query, and the
   correct field, against the live instance.

Once 1–2 land, the `--check` above verifies the pull end to end against a manual
Odoo export (the OVER-468 acceptance test).
