Skip to content

[Invoices]: Inbound e-invoice reception — receive purchase documents from e-invoicing providers #658

Open

Description

@nielsdrost7

Parent issue: #126 — its fetchAcknowledgements/webhooks cover status of outbound documents only. This issue ports the v1.80 inbound pipeline: application/modules/integrations/controllers/Integrations.php:157-206 (receive($merchantClientId)), application/modules/integrations/controllers/Incoming.php (listing + sync), Merchant_responses_model::create_inbound with MerchantResponseDirection::In / MerchantResponseType::IncomingInvoice, and the Peppol document-type registry application/modules/integrations/libraries/PeppolDocumentType.php (BillingInvoice, BillingCreditNote, Order, OrderResponse, Catalogue, MessageLevelResponse — full urn:oasis:... IDs).

A Peppol participant receives documents (supplier invoices, credit notes, MLRs) as well as sending them. v1.80 pulls inbound documents from the configured provider, stores payloads under uploads/integrations/incoming/, logs each in ip_merchant_responses (direction in, status received), and lists them on an Incoming screen. v2 has nothing inbound.

Specific

  1. ProviderInterface ([Invoices]: Peppol e-invoicing — pluggable multi-provider architecture #126) gains fetchInboundDocuments(?string $since): array (LetsPeppol exposes this via its Document/Transmission endpoints — see v1 providers/LetsPeppol/Endpoints/LetsPeppolDocumentEndpoint.php).
  2. Scheduled job FetchInboundPeppolDocumentsJob per enabled integration: downloads new documents, stores the raw XML via the existing Upload/storage layer (company-scoped, private disk), records a row in a peppol_inbound_documents table (integration id, external id, document type urn → PeppolDocumentType enum, sender participant id, received_at, status received|processed|error, payload path).
  3. Filament "Incoming documents" page (Company panel, Invoices or Expenses module) listing inbound documents with download-XML action and a manual "Sync now" button (v1 Incoming::sync).
  4. Idempotency: re-syncing must not duplicate documents (unique on integration + external id).
  5. Out of scope (follow-up): parsing inbound UBL into draft Expenses/purchase invoices.

Measurable

  • With a faked provider returning 2 documents, sync creates exactly 2 inbound rows and 2 stored XML files; a second sync creates 0.
  • The Incoming page lists them with sender, type label (from the urn enum), and date.
  • Provider errors mark the sync attempt logged with status error without losing prior rows.

Achievable

Builds directly on #126's provider architecture, peppol_integrations config, and LogsPeppolActivity; storage via the existing Core Upload model; UI is a standard Filament resource/page.

Relevant

Belgium mandates B2B e-invoicing via Peppol from 2026-01 — receiving is as mandatory as sending. v1.80 users switching to v2 would lose inbound documents entirely.

Time-Bound

Sprint 6+ (after #126 Sprint 5 outbound scope), before any "v1.80 integrations parity" release claim.

Arrange

  • Enabled Peppol integration for the company; Http::fake() provider returning two inbound UBL invoices with distinct external ids.

Act

  • Run FetchInboundPeppolDocumentsJob (and once more for idempotency); open the Incoming page.

Assert

  • Two peppol_inbound_documents rows exist with direction-in semantics, correct external ids, and PeppolDocumentType::BillingInvoice resolved from the urn.
  • Raw XML stored on the company-scoped private disk and downloadable from the page.
  • Second run creates zero new rows (unique integration+external id).
  • Manual "Sync now" action triggers the same fetch and notifies success with the count.
  • Tenant isolation: a user of another company sees no documents.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    refinedRefined with SMART plan / test suggestions

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions