Skip to content

feat: item attributes, line seller, contract reason, charge exemption codes - #128

Merged
alvarolivie merged 5 commits into
mainfrom
fr-extended-mappings
Aug 19, 2026
Merged

feat: item attributes, line seller, contract reason, charge exemption codes#128
alvarolivie merged 5 commits into
mainfrom
fr-extended-mappings

Conversation

@alvarolivie

@alvarolivie alvarolivie commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

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.

  • Item attributes. Migrate org.Item.Meta (a lossy string map) to
    org.Item.Attributes, matching the current GOBL API.
    AdditionalItemProperty gains ValueQuantity for measurable
    attribute values, alongside the existing Value (BR-54 requires a
    plain Value even when ValueQuantity is also present).
  • Line seller. Map bill.Line.Seller to/from
    cac:Item/cac:ManufacturerParty.
  • Line-level tax point (BT-8). Apply the invoice-wide VAT point
    date code to each line's InvoicePeriod, not just the header.
  • Line notes. Reuse the existing header-level note-subject-code
    formatting (formatNote/parseNote) for line notes too, instead of
    emitting plain text.
  • Contract reason. Map org.Contract.Reason to/from
    cbc:DocumentType on cac:ContractDocumentReference — this was
    previously read into an unused Type field and never written on
    convert.
  • Charge/discount exemption codes. Map the CEF VATEX extension and
    matching tax note text onto AllowanceCharge/TaxCategory
    (TaxExemptionReasonCode/TaxExemptionReason), mirroring what
    totals.go already does for the invoice totals. findTaxNote now
    takes tax.Extensions directly so both call sites share it.
  • Payee party consolidation. PayeeParty is now built with the
    shared newParty instead of a separate newPayeeParty. 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 — the rules newPayeeParty
    existed for. newPayeeParty's guess that a 4-character identity
    label is an ISO 6523 ICD code is dropped rather than ported:
    identities must carry their scheme in the iso-scheme-id extension
    (the self-billed payee fixture now does).

Test data

Convert/parse goldens regenerated with -update. Diffs are limited to
the fields above (metaattributes restructuring, new
TaxExemptionReasonCode/DocumentType/ManufacturerParty elements),
plus one payee fixture whose SIREN now lands only in
PartyLegalEntity.CompanyID instead of being duplicated 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
I left it untouched here. Worth a follow-up.

🤖 Generated with Claude Code

@codecov-commenter

codecov-commenter commented Aug 12, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 85.39326% with 13 lines in your changes missing coverage. Please review.
✅ Project coverage is 81.70%. Comparing base (20fd626) to head (69754ae).

Files with missing lines Patch % Lines
lines_parse.go 68.75% 6 Missing and 4 partials ⚠️
charges.go 85.71% 1 Missing and 1 partial ⚠️
invoice_parse.go 0.00% 0 Missing and 1 partial ⚠️
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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.Meta string map usage with structured org.Item.Attributes, including support for measurable attributes via AdditionalItemProperty/ValueQuantity (while still emitting Value for 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 newParty with 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: metaattributes restructuring for item properties.
test/data/parse/peppol/out/sg-invoice.json Golden update: metaattributes restructuring for item properties.
test/data/parse/peppol/out/Allowance-example.json Golden update: metaattributes restructuring for item properties.
test/data/parse/france-cius/out/b2b-reg.json Golden update: metaattributes restructuring for item properties.
test/data/parse/en16931/out/ubl-example2.json Golden update: metaattributes restructuring for item properties.
test/data/parse/en16931/out/ubl-example5.json Golden update: metaattributes restructuring for item properties.
test/data/parse/en16931/out/ubl-example8.json Golden update: metaattributes restructuring for item properties.
test/data/parse/en16931/out/ubl-example9.json Golden update: metaattributes restructuring for item properties.
test/data/parse/en16931/out/ubl-example10.json Golden update: metaattributes restructuring for item properties.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread lines.go
Comment on lines +103 to +107
// 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
}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread lines.go
Comment on lines +160 to +167
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()),
}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 8824758: ValueQuantity is now only emitted when the attribute has a unit. Unitless amounts are carried by the plain Value alone.

alvarolivie and others added 3 commits August 18, 2026 17:26
… 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>
@alvarolivie
alvarolivie force-pushed the fr-extended-mappings branch from 69b43b0 to 641fa0d Compare August 18, 2026 17:44
alvarolivie and others added 2 commits August 19, 2026 08:05
…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>
@alvarolivie
alvarolivie merged commit b87384d into main Aug 19, 2026
2 checks passed
@alvarolivie
alvarolivie deleted the fr-extended-mappings branch August 19, 2026 10:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants