sistema_ts — technical overview
Sistema Tessera Sanitaria expense submission for Italian clinics, phase 1 (issue #134, ADR 0026). Country-gated module beside billing in the verifactu shape: own tables, own Alembic branch (sistema_ts, depends_on billing / patients / catalog heads), nothing added to other modules. Runs next to sdi_it (ADR 0025): that one takes the B2B invoices, this one the patient invoices.
Trigger and flow
No BillingComplianceHook (the registry holds one hook per country and the trigger is payment, not issue) and no event handler (billing events are published before the request commits, so a handler on its own session would not see the invoice). services/documents.sync_clinic runs on every worker tick and is idempotent:
- paid invoices to natural persons (
billing_tax_idis not a partita IVA) →inserimento; - credit notes whose original was accepted →
rimborso; - voided invoices with an accepted
inserimento→cancellazione; PUT /opposition/{patient}→variazioneof the patient's accepted documents (queue_variazioni_for_patient).
services/documents.build_request renders a row into the SOAP request (services/xml_builder, element order from the kit's SoapUI samples, namespace http://documentospesap730.sanita.finanze.it), encrypting pincode, cfProprietario and cfCittadino with RSA/PKCS1v15 and the vendored certs/SanitelCF.cer (services/crypto; a clinic can upload a replacement). services/ts_client.send posts with basic auth to the test/prod DocumentoSpesa730pPort endpoint and parses esitoChiamata, protocollo and listaMessaggi by local name. services/submission_queue.process_clinic drains one clinic (rows locked FOR UPDATE SKIP LOCKED, commit before every network call, backoff 2 min → 1 h over eight attempts, credential failure pauses the clinic ten minutes); tasks.py schedules it every 120 s.
Data
sistema_ts_settings— credentials (Fernet at rest), identity (cf_proprietario, structure codes,dispositivo), defaulttipoSpesa,sync_from, environment.sistema_ts_documents— one operation per fiscal document: identity as sent,voci(JSONB), state,request_xml/response_xml,esito,protocollo, messages. Kept ten years; accepted rows block uninstall.sistema_ts_patient_opposition— one row per patient,revoked_at.sistema_ts_item_types—tipoSpesa(+ flag) per catalog item.
Frontend layer (phase 2)
frontend/plugins/settings.client.ts registers two Settings → Billing pages (SistemaTsSettingsPage.vue, SistemaTsDocumentsPage.vue); frontend/plugins/slots.client.ts mounts PatientOppositionCard.vue in patient.summary.cards (renders only for IT clinics) and InvoiceTsSlot.vue in invoice.detail.compliance (IT clinics, patient invoices only). composables/useSistemaTs.ts wraps the API. Permissions in frontend/app/config/permissions.ts → sistemaTs.
TLS to the test service
invioSS730pTest.sanita.finanze.it presents a certificate issued by the private Sogei Certification Authority Test and sends no chain, so the default certifi bundle rejects it (CERTIFICATE_VERIFY_FAILED). Point SISTEMA_TS_CA_BUNDLE at a PEM bundle that includes that CA (download it from the Sistema TS portal; the kit's CAAgenziadelleEntrateTest.pem is the signing CA, not the TLS one). Production chains to a public CA and needs nothing. The worker surfaces the failure as a TLS: error on the document and the settings page.
API surface
GET /api/v1/sistema_ts/settings,PUT /api/v1/sistema_ts/settingsGET /api/v1/sistema_ts/documents,GET /api/v1/sistema_ts/documents/by-invoice/{invoice_id}POST /api/v1/sistema_ts/documents/{document_id}/retryPOST /api/v1/sistema_ts/queue/process-nowGET /api/v1/sistema_ts/opposition/{patient_id},PUT /api/v1/sistema_ts/opposition/{patient_id}GET /api/v1/sistema_ts/item-types,PUT /api/v1/sistema_ts/item-types/{catalog_item_id}
Tests
backend/tests/modules/sistema_ts/: codice fiscale check character and partita IVA gate; RSA encryption against the vendored certificate and a generated one (decrypt round-trip); request shapes against the kit samples; client parsing (esito 0/1/2, fault, 401); document sync (grouping, cash → NO, B2B skipped, opposition, credit note after acceptance, void, missing CF, sync_from); worker (accepted, rejected, backoff, credential pause, disabled); router; Alembic round-trip.
Not yet
Async zip service, receipt PDFs, the AdE notification services.