feat: item attributes, line seller, contract reason, charge exemption codes - #128
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #128 +/- ##
==========================================
+ Coverage 81.39% 81.70% +0.31%
==========================================
Files 30 30
Lines 2408 2433 +25
==========================================
+ Hits 1960 1988 +28
+ Misses 285 279 -6
- Partials 163 166 +3 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
91d6ec2 to
69b43b0
Compare
There was a problem hiding this comment.
Pull request overview
This PR closes several EN16931/Peppol UBL mapping gaps discovered while implementing the French extended (EXTENDED-CTC-FR) profile, spanning line/item enrichment, contract metadata, tax exemption handling, and payee party construction.
Changes:
- Replace
org.Item.Metastring map usage with structuredorg.Item.Attributes, including support for measurable attributes viaAdditionalItemProperty/ValueQuantity(while still emittingValuefor BR-54). - Add/adjust UBL mappings for line seller (
cac:Item/cac:ManufacturerParty), line notes (subject-code formatting), line-level VAT point code propagation, and contract reason (cbc:DocumentType). - Extend allowance/charge tax category mapping to include VATEX exemption reason code/text and consolidate PayeeParty construction via
newPartywith UBL rule cleanups.
Reviewed changes
Copilot reviewed 17 out of 29 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| totals.go | Refactors findTaxNote to take tax.Extensions and updates totals exemption-reason lookup accordingly. |
| charges.go | Adds VATEX exemption reason code/text mapping onto allowance/charge TaxCategory. |
| common.go | Extends UBL Item with ManufacturerParty and adds ValueQuantity to AdditionalItemProperty. |
| ordering.go | Writes contract reason into ContractDocumentReference.DocumentType. |
| ordering_parse.go | Parses ContractDocumentReference.DocumentType into org.DocumentRef.Reason. |
| ordering_test.go | Adds round-trip test coverage for contract reason/document type mapping. |
| payment.go | Builds PayeeParty via newParty and strips disallowed fields / limits identifications per UBL rules. |
| payment_test.go | Adds round-trip test ensuring Payee inbox maps to EndpointID. |
| party.go | Adds ISO 6523 ICD fallback for 4-char identity labels; removes newPayeeParty. |
| party_test.go | Updates payee expectations due to payee now using newParty. |
| lines.go | Implements line note formatting reuse, line VAT point code propagation, item attributes mapping, and line seller mapping. |
| lines_test.go | Adds round-trip tests for line note subject codes, item attributes (incl. quantity), and line seller. |
| lines_parse.go | Parses item attributes into org.Item.Attributes, parses line notes via parseNote, and maps ManufacturerParty back to Line.Seller. |
| lines_parse_test.go | Updates parse assertions from Meta to Attributes. |
| invoice_parse.go | Wires options into line parsing (goblAddLines(out, o)). |
| invoice_test.go | Adds test coverage for line-level VAT point code conversion. |
| test/data/convert/peppol/out/invoice-with-contract-ref.xml | Golden update: emits cbc:DocumentType for contract reason. |
| test/data/convert/peppol/out/invoice-intra-comunity.xml | Golden update: emits cbc:TaxExemptionReasonCode for VATEX. |
| test/data/convert/peppol/invoice-with-contract-ref.json | Golden update: contract reason field. |
| test/data/convert/france-extended/out/invoice-payer.xml | Golden update: removes payee PartyIdentification to satisfy UBL constraints. |
| test/data/parse/peppol/out/Vat-category-S.json | Golden update: meta → attributes restructuring for item properties. |
| test/data/parse/peppol/out/sg-invoice.json | Golden update: meta → attributes restructuring for item properties. |
| test/data/parse/peppol/out/Allowance-example.json | Golden update: meta → attributes restructuring for item properties. |
| test/data/parse/france-cius/out/b2b-reg.json | Golden update: meta → attributes restructuring for item properties. |
| test/data/parse/en16931/out/ubl-example2.json | Golden update: meta → attributes restructuring for item properties. |
| test/data/parse/en16931/out/ubl-example5.json | Golden update: meta → attributes restructuring for item properties. |
| test/data/parse/en16931/out/ubl-example8.json | Golden update: meta → attributes restructuring for item properties. |
| test/data/parse/en16931/out/ubl-example9.json | Golden update: meta → attributes restructuring for item properties. |
| test/data/parse/en16931/out/ubl-example10.json | Golden update: meta → attributes restructuring for item properties. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| // BT-8: VAT point date code, same invoice-wide value as the header. | ||
| if inv.Tax != nil { | ||
| if code, ok := taxPointCodeMap[inv.Tax.Point]; ok { | ||
| invLine.InvoicePeriod.DescriptionCode = code | ||
| } |
There was a problem hiding this comment.
Intentional: the header InvoicePeriod (created by addTaxPoint when missing) is the authoritative BT-8 carrier. At line level the code is only echoed onto a period the line actually has (BG-26) — fabricating an empty InvoicePeriod on every line just to duplicate a document-level code would add noise without carrying any new information.
| prop.Value = attr.Amount.String() | ||
| if attr.Unit != "" { | ||
| prop.Value += " " + string(attr.Unit) | ||
| } | ||
| prop.ValueQuantity = &Quantity{ | ||
| Value: attr.Amount.String(), | ||
| UnitCode: string(attr.Unit.UNECE()), | ||
| } |
There was a problem hiding this comment.
Fixed in 8824758: ValueQuantity is now only emitted when the attribute has a unit. Unitless amounts are carried by the plain Value alone.
… codes Several EN16931/Peppol mapping gaps found while building out the French extended (EXTENDED-CTC-FR) profile, bundled together since they touch overlapping files: - Migrate org.Item.Meta (a lossy string map) to org.Item.Attributes, matching the GOBL API. AdditionalItemProperty gains ValueQuantity for measurable attribute values, alongside the existing Value (BR-54 requires a plain Value even when ValueQuantity is present). - Map bill.Line.Seller to/from cac:Item/cac:ManufacturerParty. - Apply the invoice-wide VAT point date code (BT-8) to each line's InvoicePeriod, not just the header. - Reuse the existing header-level note-subject-code formatting (formatNote/parseNote) for line notes too. - Map org.Contract.Reason to/from cbc:DocumentType on cac:ContractDocumentReference (was previously read into an unused Type field and never written on convert). - Map the CEF VATEX extension and matching tax note text onto AllowanceCharge/TaxCategory (TaxExemptionReasonCode/Reason), mirroring the same mapping totals.go already does for the invoice totals. findTaxNote now takes tax.Extensions directly so both call sites can share it. - Consolidate PayeeParty construction onto the shared newParty builder instead of a separate newPayeeParty, adding a 4-digit-label ISO 6523 ICD fallback to newParty's identity handling. addPayment strips PostalAddress/RegistrationName back off the result and keeps only one PartyIdentification entry, to preserve UBL-CR-272, UBL-CR-275 and UBL-SR-20, which the removed function existed for. Convert/parse goldens regenerated with -update; diffs are limited to the fields above (Meta->attributes restructuring, new TaxExemptionReasonCode/DocumentType/ManufacturerParty elements) plus one payee fixture that now points its SIREN at PartyLegalEntity.CompanyID instead of duplicating it into both PartyLegalEntity and PartyIdentification. Note: -update also touched a 1-cent tax base rounding (test/data/convert/*/invoice-prices-include-vat.json) that reproduces identically on main with zero code changes — it's latent drift from an earlier gobl version bump, unrelated to this branch, so it's left untouched here.
An org.Attribute with an amount but no unit was emitting ValueQuantity with an invalid empty unitCode attribute. The plain Value (BR-54) already carries the unitless amount. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
69b43b0 to
641fa0d
Compare
…ontext Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Identities must carry their scheme in the iso-scheme-id extension; a 4-character label is no longer assumed to be an ICD code. The self-billed payee fixture now uses the extension form. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
What
Several EN16931/Peppol mapping gaps found while building out the
French extended (EXTENDED-CTC-FR) profile. Bundled into one PR since
they touch overlapping files.
org.Item.Meta(a lossy string map) toorg.Item.Attributes, matching the current GOBL API.AdditionalItemPropertygainsValueQuantityfor measurableattribute values, alongside the existing
Value(BR-54 requires aplain
Valueeven whenValueQuantityis also present).bill.Line.Sellerto/fromcac:Item/cac:ManufacturerParty.date code to each line's
InvoicePeriod, not just the header.formatting (
formatNote/parseNote) for line notes too, instead ofemitting plain text.
org.Contract.Reasonto/fromcbc:DocumentTypeoncac:ContractDocumentReference— this waspreviously read into an unused
Typefield and never written onconvert.
matching tax note text onto
AllowanceCharge/TaxCategory(
TaxExemptionReasonCode/TaxExemptionReason), mirroring whattotals.goalready does for the invoice totals.findTaxNotenowtakes
tax.Extensionsdirectly so both call sites share it.PayeePartyis now built with theshared
newPartyinstead of a separatenewPayeeParty.addPaymentstrips
PostalAddress/RegistrationNameback off the result andkeeps only one
PartyIdentificationentry, to preserveUBL-CR-272, UBL-CR-275 and UBL-SR-20 — the rules
newPayeePartyexisted for.
newPayeeParty's guess that a 4-character identitylabel is an ISO 6523 ICD code is dropped rather than ported:
identities must carry their scheme in the
iso-scheme-idextension(the self-billed payee fixture now does).
Test data
Convert/parse goldens regenerated with
-update. Diffs are limited tothe fields above (
meta→attributesrestructuring, newTaxExemptionReasonCode/DocumentType/ManufacturerPartyelements),plus one payee fixture whose SIREN now lands only in
PartyLegalEntity.CompanyIDinstead of being duplicated into bothPartyLegalEntityandPartyIdentification.Note:
-updatealso touched a 1-cent tax base rounding(
test/data/convert/*/invoice-prices-include-vat.json) thatreproduces identically on
mainwith zero code changes — it's latentdrift from an earlier gobl version bump, unrelated to this branch, so
I left it untouched here. Worth a follow-up.
🤖 Generated with Claude Code