1 package SL::XMLInvoice::CrossIndustryInvoice;
6 use parent qw(SL::XMLInvoice::Base);
8 use constant ITEMS_XPATH => '//ram:IncludedSupplyChainTradeLineItem';
12 SL::XMLInvoice::CrossIndustryInvoice - XML parser for UN/CEFACT Cross Industry Invoice
16 C<SL::XMLInvoice::CrossIndustryInvoice> parses XML invoices in UN/CEFACT Cross
17 Industry Invoice format and makes their data available through the interface
18 defined by C<SL::XMLInvoice>. Refer to L<SL::XMLInvoice> for a detailed
19 description of that interface.
21 See L<https://unece.org/trade/uncefact/xml-schemas> for that format's
26 This module is fairly simple. It keeps two hashes of XPath statements exposed
33 This hash is keyed by the keywords C<data_keys> mandates. Values are XPath
34 statements specifying the location of this field in the invoice XML document.
38 This hash is keyed by the keywords C<item_keys> mandates. Values are XPath
39 statements specifying the location of this field inside a line item.
43 When invoked by the C<SL::XMLInvoice> constructor, C<parse_xml()> will first
44 use the XPath statements from the C<scalar_xpaths()> hash to populate the hash
45 returned by the C<metadata()> method.
47 After that, it will use the XPath statements from the C<scalar_xpaths()> hash
48 to iterate over the invoice's line items and populate the array of hashes
49 returned by the C<items()> method.
53 Johannes Grassler <info@computer-grassler.de>
58 my @supported = ( "UN/CEFACT Cross Industry Invoice (urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100)" );
63 my ($self, $dom) = @_;
65 my $rootnode = $dom->documentElement;
67 foreach my $attr ( $rootnode->attributes ) {
68 if ( $attr->getData =~ m/urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100/ ) {
76 # XML XPath expressions for global metadata
79 currency => '//ram:InvoiceCurrencyCode',
80 direct_debit => '//ram:SpecifiedTradeSettlementPaymentMeans/ram:TypeCode',
81 duedate => '//ram:DueDateDateTime/udt:DateTimeString',
82 gross_total => '//ram:DuePayableAmount',
83 iban => '//ram:SpecifiedTradeSettlementPaymentMeans/ram:PayeePartyCreditorFinancialAccount/ram:IBANID',
84 invnumber => '//rsm:ExchangedDocument/ram:ID',
85 net_total => '//ram:SpecifiedTradeSettlementHeaderMonetarySummation' . '//ram:TaxBasisTotalAmount',
86 transdate => '//ram:IssueDateTime/udt:DateTimeString',
87 taxnumber => '//ram:SellerTradeParty/ram:SpecifiedTaxRegistration/ram:ID[@schemeID="FC"]',
88 type => '//rsm:ExchangedDocument/ram:TypeCode',
89 ustid => '//ram:SellerTradeParty/ram:SpecifiedTaxRegistration/ram:ID[@schemeID="VA"]',
90 vendor_name => '//ram:SellerTradeParty/ram:Name',
96 'currency' => undef, # Only global currency in CrossIndustryInvoice
97 'price' => './ram:SpecifiedLineTradeAgreement/ram:NetPriceProductTradePrice',
98 'description' => './ram:SpecifiedTradeProduct/ram:Name',
99 'quantity' => './ram:SpecifiedLineTradeDelivery/ram:BilledQuantity',
100 'subtotal' => './ram:SpecifiedLineTradeSettlement/ram:SpecifiedTradeSettlementLineMonetarySummation/ram:LineTotalAmount',
101 'tax_rate' => './ram:SpecifiedLineTradeSettlement/ram:ApplicableTradeTax/ram:RateApplicablePercent',
102 'tax_scheme' => './ram:SpecifiedLineTradeSettlement/ram:ApplicableTradeTax/ram:TypeCode',
103 'vendor_partno' => './ram:SpecifiedTradeProduct/ram:SellerAssignedID',
108 # Metadata accessor method
111 return $self->{_metadata};
114 # Item list accessor method
117 return $self->{_items};
121 my $xc = XML::LibXML::XPathContext->new;
122 $xc->registerNs(udt => 'urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100');
123 $xc->registerNs(ram => 'urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100');
124 $xc->registerNs(rsm => 'urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100');
128 # Data keys we return
133 map { $keys{$_} = 1; } keys %{$self->scalar_xpaths};
138 # Item keys we return
143 map { $keys{$_} = 1; } keys %{$self->item_xpaths};
148 # Main parser subroutine for retrieving XML data
151 $self->{_metadata} = {};
152 $self->{_items} = ();
154 my $xc = _xpath_context();
156 # Retrieve scalar metadata from DOM
157 foreach my $key ( keys %{$self->scalar_xpaths} ) {
158 my $xpath = ${$self->scalar_xpaths}{$key};
160 # Skip keys without xpath expression
161 ${$self->{_metadata}}{$key} = undef;
164 my $value = $xc->find($xpath, $self->{dom});
166 # Get rid of extraneous white space
167 $value = $value->string_value;
168 $value =~ s/\n|\r//g;
169 $value =~ s/\s{2,}/ /g;
170 ${$self->{_metadata}}{$key} = $value;
172 ${$self->{_metadata}}{$key} = undef;
177 # Convert payment code metadata field to Boolean
178 # See https://service.unece.org/trade/untdid/d16b/tred/tred4461.htm for other valid codes.
179 if (${$self->{_metadata}}{'direct_debit'}) {
180 ${$self->{_metadata}}{'direct_debit'} = ${$self->{_metadata}}{'direct_debit'} == 59 ? 1 : 0;
184 $self->{_items} = \@items;
186 foreach my $item ( $xc->findnodes(ITEMS_XPATH, $self->{dom}) ) {
188 foreach my $key ( keys %{$self->item_xpaths} ) {
189 my $xpath = ${$self->item_xpaths}{$key};
191 # Skip keys without xpath expression
192 $line_item{$key} = undef;
195 my $value = $xc->find($xpath, $item);
197 # Get rid of extraneous white space
198 $value = $value->string_value;
199 $value =~ s/\n|\r//g;
200 $value =~ s/\s{2,}/ /g;
201 $line_item{$key} = $value;
203 $line_item{$key} = undef;
206 push @items, \%line_item;