# Moloni integration

Moloni is an optional per-company ERP adapter behind the neutral integration
interfaces and ledger. PostgreSQL remains Sibyla's source of operational truth.
The implementation supports locally testable OAuth/configuration foundations,
purchase push, sales pull, and safe purchase-write reconciliation. It has not been
accepted against a Moloni sandbox or live tenant.

## Setup and activation

1. Keep the company connection disabled while configuring it.
2. Select `Moloni` in the company's integration-provider setting.
3. Enter the developer identifier, write-only client secret, registered HTTPS
   redirect URI, and the intended Moloni company. Complete OAuth through the
   browser flow; never enter or store a Moloni user password in Sibyla.
4. Select the document set, payment method, maturity date, and warehouse defaults.
   Product tax/category/unit mappings must be present in approved extraction data.
5. Run the connection test. Enable only when the tested configuration version
   equals the current version and reauthorization is not required.
6. Before a controlled pilot, apply the already-committed migrations through the
   normal deployment process, run the local test evidence below, provision alerting,
   and complete the pending sandbox acceptance.

The protected client secret and refresh token use the shared ASP.NET Data Protection
key ring/certificate configuration. Operators see only booleans indicating whether
each secret is configured. The API callback state is short-lived, one-time, hashed,
and tied to the configuration version.

## Deny-by-default rules

- Dispatch requires the company's selected provider to be `Moloni`.
- A connection must belong to the same internal company, be enabled, have a
  successful test for its current configuration version, and not require
  reauthorization.
- Reconciliation additionally binds the neutral ledger provider, internal company,
  immutable connection ID, and Moloni `company_id`. A mismatch stops before token
  acquisition or a remote read.
- Purchase push accepts approved EUR purchase invoices, invoice-receipts, credit
  notes, and debit notes with consistent line/header net, tax, and gross totals.
  Foreign currency, deductions/retentions, standalone receipts, missing mappings,
  and required attachments are rejected.
- Documents are inserted as drafts (`status = 0`). Attachment upload is not a
  Moloni capability.
- A payment write requires explicit approval. Because the current supplier receipt
  payload has no unique reconciliation reference, an ambiguous payment is never
  retried or auto-confirmed.

## Purchase writes and idempotency

Every external insert is preceded by a durable neutral `IntegrationOperation` in
`Writing`, including a request hash and provider-scoped operation key. Confirmed
operations return their recorded external ID. A definite application rejection,
including an HTTP-success payload with `valid: 0`, becomes `Failed`. A timeout,
transport loss, ambiguous server response, cancellation after dispatch, or failure
to persist a returned write result becomes `WriteUnknown` using a non-cancelable
recovery save.

Supplier and product recovery uses exact VAT/reference lookups. Purchase-document
recovery pages through the corresponding supplier document list and requires one
and only one candidate matching:

- the scoped Moloni company and supplier;
- document endpoint/type, supplier reference, and issue date;
- `EUR` currency;
- independently calculated net, tax, and gross amounts.

Multiple matches, a matching identity with different totals/currency/date, malformed
responses, or an exhausted page bound fail closed. No reconciliation path performs
an insert.

## Reconciliation and recovery

Queue the provider-neutral route:

`POST /api/integrations/commits/{commit-id}/reconcile`

The existing Cegid compatibility route remains Cegid-only. The shared job resolves
the reconciler from the commit provider and current company setting.

Outcomes:

- `Confirmed`: idempotent; no Moloni call is made.
- `Writing`, `Reconciling`, or `WriteUnknown`: treated as potentially dispatched.
  A unique match confirms it; zero candidates remains `WriteUnknown` for manual
  review and fails closed; ambiguity also remains `WriteUnknown`. Reconciliation
  never changes a zero-match operation to `Prepared`.
- `Failed`: remains failed and is not retried by reconciliation.
- ambiguous supplier receipt/payment or another unsupported operation: remains
  `WriteUnknown` for investigation.

Manual external-ID confirmation is deliberately disabled for Moloni because an
unverified ID could cross-link companies or documents. The job postcondition rejects
all unresolved `Writing`, `Reconciling`, and `WriteUnknown` states.

## Sales pull

Moloni sales sync uses the provider-neutral `SyncProviderSales` job, paged list/detail
reads, payload hashes, receipt allocations, and immutable PDF storage versions. PDF
URLs are validated and never persisted or returned as credentials. Sales polling and
the financial workbook projection remain subject to the existing capability,
mapping, and configuration guards.

## Security and logging

- Never put a client secret, access token, refresh token, connection string, OAuth
  authorization code, or signed PDF URL in configuration committed to source,
  request/exception text, logs, test output, or support tickets.
- Moloni clients that must carry access tokens in query strings have framework HTTP
  logging removed. Do not re-enable request URI logging.
- Restrict Data Protection key-ring and certificate private-key access to the API,
  Web, and Worker service identities; back up both according to the deployment
  standard.
- Keep database access least-privileged and retain audit records for connection
  configuration and OAuth actions.

## Monitoring

Alert on:

- `ReauthorizationRequired`, refresh-token expiry approaching, token refresh failure,
  and a Worker outage long enough to risk refresh-token expiry;
- growing or old `IntegrationCommit`/`IntegrationOperation` rows in `Writing`,
  `Reconciling`, or `WriteUnknown`;
- `moloni_document_reconciliation_ambiguous`,
  `moloni_document_reconciliation_mismatch`, application rejection, malformed
  responses, and pagination-bound exhaustion;
- sync-run failures, PDF recovery failures, polling lag, mapping blocks, and rate
  limiting.

Rate limits and safe backoff values are not documented locally and must be measured
in the sandbox before production.

## Rollback

1. Stop new Moloni work by disabling the affected Moloni company connection through
   the supported administration flow. Do not change provider while an active
   connection or unresolved ledger work exists.
2. Leave all neutral ledger rows intact. Never delete or reset `Writing`/
   `WriteUnknown` rows and never replay them blindly.
3. Drain or quarantine already queued work through the normal job controls, inspect
   reconciliation state, and preserve audit evidence.
4. Roll application binaries back through the standard deployment process only if
   the target version understands the schema already installed. Sprint 8 migration
   `20260723190000_MoloniLedgerConcurrency` adds `Version` columns to
   `IntegrationCommits` and `IntegrationOperations`.
5. Re-enable only after configuration is retested and unresolved writes have been
   uniquely reconciled or formally accepted for manual investigation.

## Local acceptance evidence and remaining gates

The repository acceptance commands are:

```text
dotnet build GOTT.Sibyla.slnx --no-restore
dotnet test tests/Sibyla.Tests/Sibyla.Tests.csproj --no-restore --filter "<focused filter>"
dotnet test tests/Sibyla.Tests/Sibyla.Tests.csproj --no-restore
git diff --check
```

Fake-only tests cover write durability, no duplicate insert, unique/ambiguous and
paged matching, provider/company/account scope, financial identity, application
rejection, cancellation, stale states, and postconditions. These tests are local
evidence only. Moloni OAuth credentials, migration execution in each environment,
sandbox/live connectivity, rate-limit characterization, operational alerting, and
a controlled draft-invoice acceptance remain pending.
