Context
FA(3) supports declaring a Payer that is a different legal entity than the buyer (Nabywca). Typical real world scenarios: parent company settling invoices issued to a subsidiary, leasing company paying for a lessee, factoring, intra group cost allocation. Today the module emits only Podmiot1 (seller, buildPodmiot1) and Podmiot2 (buyer, buildPodmiot2) in class/fa3_builder.class.php around lines 125 and 126. There is no third party node, so the payer information cannot reach KSeF in structured form.
The TP flag (P set to 1 in line 618) is not a substitute. TP is a boolean marker for related party transactions, not a carrier for the payer identity, NIP and address.
What I would like to see
A first class Payer concept in the module:
- On the invoice card a dedicated picker that selects a third party (societe) from the existing Dolibarr customer database. Reusing the standard form->select_company helper would be the cleanest path so the picker behaves like the Nabywca picker (search by name or NIP, status filter, exclusions honored).
- Persisted as a foreign key to llx_societe, ideally either as a typed column on llx_facture_extrafields or as a module specific table keyed by fk_facture. A scalar rowid is enough, no need to denormalize.
- At XML build time, buildPodmiot3 (analogous to buildPodmiot2) loads the societe row by rowid and emits the full structured block: NIP, PrefiksPodatnika if applicable, PelnaNazwa, Adres (KodKraju, AdresL1, AdresL2 or Ulica/NrDomu/NrLokalu/KodPocztowy/Miejscowosc depending on what is present), DaneKontaktowe. Same xmlSafe and helpers that buildPodmiot2 already uses.
- Visualization PDF: an optional Payer block in renderHeader next to Seller and Buyer when present. The existing column layout in ksef_invoice_pdf.class.php can accommodate a third column or wrap to a second row.
- Setup screen: in admin/setup_outgoing.php expose a new assignment target Payer (Podmiot3) in the extrafields assignment table, available only for extrafields whose underlying type resolves to a societe reference (sellist with societe target, or a future native picker). Persist as a new const, e.g. KSEF_PODMIOT3_SOURCE, in the same pattern as KSEF_NR_ZAMOWIENIA_SOURCE and KSEF_TP_SOURCE.
Why the current sellist workaround does not work
I tried wiring this up without code changes: created a facture extrafield platnik of type sellist with param societe:nom:rowid::status=1, then in module settings assigned it to dodatkowy (DodatkowyOpis). On the invoice card the picker works and persists the rowid, but the value forwarded to KSeF is the raw rowid because formatExtraFieldValue in class/fa3_builder.class.php (around line 1678) has no case for sellist and falls through to strip_tags((string) rawValue). End result: DodatkowyOpis ends up with platnik: 27 instead of the company data, and there is no Podmiot3 emitted anyway.
A minimal fix for the sellist case alone (resolve rowid to a label via the sellist param) would make the DodatkowyOpis readable but it is still semantically wrong, because the payer belongs in a Podmiot node, not in free form notes. That is why I am asking for a proper Podmiot3 path rather than a sellist patch.
Suggested implementation outline
Minimal viable slice that does not require schema migrations:
- Add KSEF_PODMIOT3_SOURCE const, same shape as KSEF_TP_SOURCE (extrafield:fieldname or disabled).
- In setup_outgoing.php extend the extrafield assignment dropdown to offer podmiot3 when the extrafield is sellist with table societe, or text holding a rowid, or the future native picker.
- In fa3_builder.class.php:
a. Read KSEF_PODMIOT3_SOURCE, resolve the value to a societe rowid.
b. If non empty, instantiate Societe, fetch the row, and call a new private buildPodmiot3 that mirrors buildPodmiot2 (same address/contact helpers, same xmlSafe usage).
c. Insert the appendChild after Podmiot2 in buildFa.
- In ksef_invoice_pdf.class.php render the Payer header block conditionally.
- Translations: add KSEF_PODMIOT3, KSEF_PAYER, KSEF_ASSIGN_PODMIOT3, etc.
For the correction flow, Podmiot3K should be emitted only when the original XML contained Podmiot3 and the current data differs, following the same pattern as buildPodmiot1K and buildPodmiot2K already in the file.
Validation
Happy to test on production data in TEST environment and provide sample XMLs and screenshots once a draft branch is up. I can also help review the PR.
Thanks for the module, it is in daily production use here.
Context
FA(3) supports declaring a Payer that is a different legal entity than the buyer (Nabywca). Typical real world scenarios: parent company settling invoices issued to a subsidiary, leasing company paying for a lessee, factoring, intra group cost allocation. Today the module emits only Podmiot1 (seller, buildPodmiot1) and Podmiot2 (buyer, buildPodmiot2) in class/fa3_builder.class.php around lines 125 and 126. There is no third party node, so the payer information cannot reach KSeF in structured form.
The TP flag (P set to 1 in line 618) is not a substitute. TP is a boolean marker for related party transactions, not a carrier for the payer identity, NIP and address.
What I would like to see
A first class Payer concept in the module:
Why the current sellist workaround does not work
I tried wiring this up without code changes: created a facture extrafield platnik of type sellist with param societe:nom:rowid::status=1, then in module settings assigned it to dodatkowy (DodatkowyOpis). On the invoice card the picker works and persists the rowid, but the value forwarded to KSeF is the raw rowid because formatExtraFieldValue in class/fa3_builder.class.php (around line 1678) has no case for sellist and falls through to strip_tags((string) rawValue). End result: DodatkowyOpis ends up with platnik: 27 instead of the company data, and there is no Podmiot3 emitted anyway.
A minimal fix for the sellist case alone (resolve rowid to a label via the sellist param) would make the DodatkowyOpis readable but it is still semantically wrong, because the payer belongs in a Podmiot node, not in free form notes. That is why I am asking for a proper Podmiot3 path rather than a sellist patch.
Suggested implementation outline
Minimal viable slice that does not require schema migrations:
a. Read KSEF_PODMIOT3_SOURCE, resolve the value to a societe rowid.
b. If non empty, instantiate Societe, fetch the row, and call a new private buildPodmiot3 that mirrors buildPodmiot2 (same address/contact helpers, same xmlSafe usage).
c. Insert the appendChild after Podmiot2 in buildFa.
For the correction flow, Podmiot3K should be emitted only when the original XML contained Podmiot3 and the current data differs, following the same pattern as buildPodmiot1K and buildPodmiot2K already in the file.
Validation
Happy to test on production data in TEST environment and provide sample XMLs and screenshots once a draft branch is up. I can also help review the PR.
Thanks for the module, it is in daily production use here.