-
Notifications
You must be signed in to change notification settings - Fork 55
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
XMLInvoice: gültige namespaces aus der xml holen ...
Die namespaces CrossIndustryInvoice, ReusableAggregateBusinessInformationEntity, UnqualifiedDataType können beliebig sein und sind von ZUGfERD nicht festgelegt. Deswegen werden die ns jetzt vorher ausgelesen.
- Loading branch information
1 parent
48fa6dc
commit f9422f0
Showing
4 changed files
with
150 additions
and
50 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -55,6 +55,7 @@ to discover the metadata keys guaranteed to be present. | |
=cut | ||
|
||
sub data_keys { | ||
my $self = shift; | ||
my @keys = ( | ||
'currency', # The bill's currency, such as "EUR" | ||
'direct_debit', # Boolean: whether the bill will get paid by direct debit (1) or not (0) | ||
|
@@ -85,6 +86,7 @@ to discover the metadata keys guaranteed to be present. | |
=cut | ||
|
||
sub item_keys { | ||
my $self = shift; | ||
my @keys = ( | ||
'currency', | ||
'description', | ||
|
@@ -144,6 +146,17 @@ sub check_signature { | |
die "Children of $self must implement a check_signature() method returning 1 for supported XML, 0 for unsupported XML."; | ||
} | ||
|
||
=item namespaces($dom) | ||
This static method takes a DOM object and returns an ArrayofHashes[ data => localname ]. C<SL::XMLInvoice> uses this method to determine which ns is valid for wich data. All child classes must implement this method. | ||
=cut | ||
|
||
sub namespaces { | ||
my $self = shift; | ||
die "Children of $self must implement a namespaces() method returning an aoh with the namespaces"; | ||
} | ||
|
||
=item supported() | ||
This static method returns an array of free-form strings describing XML invoice | ||
|
@@ -227,6 +240,7 @@ C<item_keys>. Omitting this method from a child class will cause an exception. | |
=head1 AUTHOR | ||
Johannes Grassler <[email protected]> | ||
Werner Hahn <[email protected]> | ||
=cut | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,8 +5,6 @@ use warnings; | |
|
||
use parent qw(SL::XMLInvoice::Base); | ||
|
||
use constant ITEMS_XPATH => '//ram:IncludedSupplyChainTradeLineItem'; | ||
|
||
=head1 NAME | ||
SL::XMLInvoice::CrossIndustryDocument - XML parser for UN/CEFACT Cross Industry Document | ||
|
@@ -51,6 +49,7 @@ returned by the C<items()> method. | |
=head1 AUTHOR | ||
Johannes Grassler <[email protected]> | ||
Werner Hahn <[email protected]> | ||
=cut | ||
|
||
|
@@ -73,39 +72,75 @@ sub check_signature { | |
return 0; | ||
} | ||
|
||
sub namespaces { | ||
my ($self, $dom) = @_; | ||
my $rootnode = $dom->documentElement; | ||
my @nodes = $rootnode->findnodes('namespace::*'); | ||
my @namespaces = map {[ $_->getData, $_->getLocalName]} @nodes; | ||
return \@namespaces; | ||
} | ||
|
||
# XML XPath expressions for global metadata | ||
sub scalar_xpaths { | ||
my ($self) = @_; | ||
|
||
my $rsm = $self->{namespaces}->{'urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100'}; | ||
my $ram = $self->{namespaces}->{'urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100'}; | ||
my $udt = $self->{namespaces}->{'urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100'}; | ||
$ram .= ":" if $ram; | ||
$rsm .= ":" if $rsm; | ||
$udt .= ":" if $udt; | ||
|
||
return { | ||
currency => ['//ram:InvoiceCurrencyCode'], | ||
direct_debit => ['//ram:SpecifiedTradeSettlementPaymentMeans/ram:TypeCode'], | ||
duedate => ['//ram:DueDateDateTime/udt:DateTimeString', '//ram:EffectiveSpecifiedPeriod/ram:CompleteDateTime/udt:DateTimeString'], | ||
gross_total => ['//ram:DuePayableAmount'], | ||
iban => ['//ram:SpecifiedTradeSettlementPaymentMeans/ram:PayeePartyCreditorFinancialAccount/ram:IBANID'], | ||
invnumber => ['//rsm:HeaderExchangedDocument/ram:ID'], | ||
net_total => ['//ram:TaxBasisTotalAmount'], | ||
transdate => ['//ram:IssueDateTime/udt:DateTimeString'], | ||
taxnumber => ['//ram:SellerTradeParty/ram:SpecifiedTaxRegistration/ram:ID[@schemeID="FC"]'], | ||
type => ['//rsm:HeaderExchangedDocument/ram:TypeCode'], | ||
ustid => ['//ram:SellerTradeParty/ram:SpecifiedTaxRegistration/ram:ID[@schemeID="VA"]'], | ||
vendor_name => ['//ram:SellerTradeParty/ram:Name'], | ||
currency => ['//' . $ram . 'InvoiceCurrencyCode'], | ||
direct_debit => ['//' . $ram . 'SpecifiedTradeSettlementPaymentMeans/' . $ram . 'TypeCode'], | ||
duedate => ['//' . $ram . 'DueDateDateTime/' . $udt . 'DateTimeString', '//' . $ram . 'EffectiveSpecifiedPeriod/' . $ram . 'CompleteDateTime/' . $udt . 'DateTimeString'], | ||
gross_total => ['//' . $ram . 'DuePayableAmount'], | ||
iban => ['//' . $ram . 'SpecifiedTradeSettlementPaymentMeans/' . $ram . 'PayeePartyCreditorFinancialAccount/' . $ram . 'IBANID'], | ||
invnumber => ['//' . $rsm . 'HeaderExchangedDocument/' . $ram . 'ID'], | ||
net_total => ['//' . $ram . 'TaxBasisTotalAmount'], | ||
transdate => ['//' . $ram . 'IssueDateTime/' . $udt . 'DateTimeString'], | ||
taxnumber => ['//' . $ram . 'SellerTradeParty/' . $ram . 'SpecifiedTaxRegistration/' . $ram . 'ID[@schemeID="FC"]'], | ||
type => ['//' . $rsm . 'HeaderExchangedDocument/' . $ram . 'TypeCode'], | ||
ustid => ['//' . $ram . 'SellerTradeParty/' . $ram . 'SpecifiedTaxRegistration/' . $ram . 'ID[@schemeID="VA"]'], | ||
vendor_name => ['//' . $ram . 'SellerTradeParty/' . $ram . 'Name'], | ||
}; | ||
} | ||
|
||
sub item_xpaths { | ||
my ($self) = @_; | ||
|
||
my $rsm = $self->{namespaces}->{'urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100'}; | ||
my $ram = $self->{namespaces}->{'urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100'}; | ||
my $udt = $self->{namespaces}->{'urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100'}; | ||
$ram .= ":" if $ram; | ||
$rsm .= ":" if $rsm; | ||
$udt .= ":" if $udt; | ||
|
||
return { | ||
'currency' => ['./ram:SpecifiedSupplyChainTradeAgreement/ram:GrossPriceProductTradePrice/ram:ChargeAmount[attribute::currencyID]', | ||
'./ram:SpecifiedSupplyChainTradeAgreement/ram:GrossPriceProductTradePrice/ram:BasisAmount'], | ||
'price' => ['./ram:SpecifiedSupplyChainTradeAgreement/ram:GrossPriceProductTradePrice/ram:ChargeAmount', | ||
'./ram:SpecifiedSupplyChainTradeAgreement/ram:GrossPriceProductTradePrice/ram:BasisAmount'], | ||
'description' => ['./ram:SpecifiedTradeProduct/ram:Name'], | ||
'quantity' => ['./ram:SpecifiedSupplyChainTradeDelivery/ram:BilledQuantity',], | ||
'subtotal' => ['./ram:SpecifiedSupplyChainTradeSettlement/ram:SpecifiedTradeSettlementMonetarySummation/ram:LineTotalAmount'], | ||
'tax_rate' => ['./ram:SpecifiedSupplyChainTradeSettlement/ram:ApplicableTradeTax/ram:ApplicablePercent'], | ||
'tax_scheme' => ['./ram:SpecifiedSupplyChainTradeSettlement/ram:ApplicableTradeTax/ram:TypeCode'], | ||
'vendor_partno' => ['./ram:SpecifiedTradeProduct/ram:SellerAssignedID'], | ||
'currency' => ['./' . $ram . ':SpecifiedSupplyChainTradeAgreement/' . $ram . ':GrossPriceProductTradePrice/' . $ram . ':ChargeAmount[attribute::currencyID]', | ||
'./' . $ram . ':SpecifiedSupplyChainTradeAgreement/' . $ram . ':GrossPriceProductTradePrice/' . $ram . ':BasisAmount'], | ||
'price' => ['./' . $ram . ':SpecifiedSupplyChainTradeAgreement/' . $ram . ':GrossPriceProductTradePrice/' . $ram . ':ChargeAmount', | ||
'./' . $ram . ':SpecifiedSupplyChainTradeAgreement/' . $ram . ':GrossPriceProductTradePrice/' . $ram . ':BasisAmount'], | ||
'description' => ['./' . $ram . ':SpecifiedTradeProduct/' . $ram . ':Name'], | ||
'quantity' => ['./' . $ram . ':SpecifiedSupplyChainTradeDelivery/' . $ram . ':BilledQuantity',], | ||
'subtotal' => ['./' . $ram . ':SpecifiedSupplyChainTradeSettlement/' . $ram . ':SpecifiedTradeSettlementMonetarySummation/' . $ram . ':LineTotalAmount'], | ||
'tax_rate' => ['./' . $ram . ':SpecifiedSupplyChainTradeSettlement/' . $ram . ':ApplicableTradeTax/' . $ram . ':ApplicablePercent'], | ||
'tax_scheme' => ['./' . $ram . ':SpecifiedSupplyChainTradeSettlement/' . $ram . ':ApplicableTradeTax/' . $ram . ':TypeCode'], | ||
'vendor_partno' => ['./' . $ram . ':SpecifiedTradeProduct/' . $ram . ':SellerAssignedID'], | ||
}; | ||
} | ||
|
||
sub items_xpath { | ||
my ($self) = @_; | ||
my $rsm = $self->{namespaces}->{'urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100'}; | ||
my $ram = $self->{namespaces}->{'urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100'}; | ||
my $udt = $self->{namespaces}->{'urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100'}; | ||
$ram .= ":" if $ram; | ||
$rsm .= ":" if $rsm; | ||
$udt .= ":" if $udt; | ||
return '//' . $ram . 'IncludedSupplyChainTradeLineItem'; | ||
} | ||
|
||
# Metadata accessor method | ||
sub metadata { | ||
|
@@ -145,6 +180,9 @@ sub parse_xml { | |
$self->{_metadata} = {}; | ||
$self->{_items} = (); | ||
|
||
my $ram = $self->{namespaces}->{'urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100'}; | ||
$ram .= ":" if $ram; | ||
my $udt = $self->{namespaces}->{'urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100'}; | ||
# Retrieve scalar metadata from DOM | ||
foreach my $key ( keys %{$self->scalar_xpaths} ) { | ||
foreach my $xpath ( @{${$self->scalar_xpaths}{$key}} ) { | ||
|
@@ -154,6 +192,10 @@ sub parse_xml { | |
next; | ||
} | ||
my $value = $self->{dom}->findnodes($xpath); | ||
unless ($udt) { | ||
$value = $self->{dom}->findnodes('//' . $ram . 'DueDateDateTime','DateTimeString') if $key eq 'duedate'; | ||
$value = $self->{dom}->findnodes('//' . $ram . 'IssueDateTime','DateTimeString') if $key eq 'transdate'; | ||
} | ||
if ( $value ) { | ||
# Get rid of extraneous white space | ||
$value = $value->string_value; | ||
|
@@ -175,7 +217,7 @@ sub parse_xml { | |
my @items; | ||
$self->{_items} = \@items; | ||
|
||
foreach my $item ( $self->{dom}->findnodes(ITEMS_XPATH)) { | ||
foreach my $item ( $self->{dom}->findnodes($self->items_xpath)) { | ||
my %line_item; | ||
foreach my $key ( keys %{$self->item_xpaths} ) { | ||
foreach my $xpath ( @{${$self->item_xpaths}{$key}} ) { | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,7 +5,6 @@ use warnings; | |
|
||
use parent qw(SL::XMLInvoice::Base); | ||
|
||
use constant ITEMS_XPATH => '//ram:IncludedSupplyChainTradeLineItem'; | ||
|
||
=head1 NAME | ||
|
@@ -51,6 +50,7 @@ returned by the C<items()> method. | |
=head1 AUTHOR | ||
Johannes Grassler <[email protected]> | ||
Werner Hahn <[email protected]> | ||
=cut | ||
|
||
|
@@ -73,37 +73,71 @@ sub check_signature { | |
return 0; | ||
} | ||
|
||
sub namespaces { | ||
my ($self, $dom) = @_; | ||
my $rootnode = $dom->documentElement; | ||
my @nodes = $rootnode->findnodes('namespace::*'); | ||
my %namespaces = map { $_->getData => $_->getLocalName} @nodes; | ||
return \%namespaces; | ||
} | ||
|
||
# XML XPath expressions for global metadata | ||
sub scalar_xpaths { | ||
my ($self) = @_; | ||
|
||
my $rsm = $self->{namespaces}->{'urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100'}; | ||
my $ram = $self->{namespaces}->{'urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100'}; | ||
my $udt = $self->{namespaces}->{'urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100'}; | ||
$ram .= ":" if $ram; | ||
$rsm .= ":" if $rsm; | ||
$udt .= ":" if $udt; | ||
|
||
return { | ||
currency => '//ram:InvoiceCurrencyCode', | ||
direct_debit => '//ram:SpecifiedTradeSettlementPaymentMeans/ram:TypeCode', | ||
duedate => '//ram:DueDateDateTime/udt:DateTimeString', | ||
gross_total => '//ram:DuePayableAmount', | ||
iban => '//ram:SpecifiedTradeSettlementPaymentMeans/ram:PayeePartyCreditorFinancialAccount/ram:IBANID', | ||
invnumber => '//rsm:ExchangedDocument/ram:ID', | ||
net_total => '//ram:SpecifiedTradeSettlementHeaderMonetarySummation' . '//ram:TaxBasisTotalAmount', | ||
transdate => '//ram:IssueDateTime/udt:DateTimeString', | ||
taxnumber => '//ram:SellerTradeParty/ram:SpecifiedTaxRegistration/ram:ID[@schemeID="FC"]', | ||
type => '//rsm:ExchangedDocument/ram:TypeCode', | ||
ustid => '//ram:SellerTradeParty/ram:SpecifiedTaxRegistration/ram:ID[@schemeID="VA"]', | ||
vendor_name => '//ram:SellerTradeParty/ram:Name', | ||
currency => '//' . $ram . 'InvoiceCurrencyCode', | ||
direct_debit => '//' . $ram . 'SpecifiedTradeSettlementPaymentMeans/' . $ram . 'TypeCode', | ||
duedate => '//' . $ram . 'DueDateDateTime/' . $udt . 'DateTimeString', | ||
gross_total => '//' . $ram . 'DuePayableAmount', | ||
iban => '//' . $ram . 'SpecifiedTradeSettlementPaymentMeans/' . $ram . 'PayeePartyCreditorFinancialAccount/' . $ram . 'IBANID', | ||
invnumber => '//' . $rsm . 'ExchangedDocument/' . $ram . 'ID', | ||
net_total => '//' . $ram . 'SpecifiedTradeSettlementHeaderMonetarySummation' . '//' . $ram . 'TaxBasisTotalAmount', | ||
transdate => '//' . $ram . 'IssueDateTime/' . $udt . 'DateTimeString', | ||
taxnumber => '//' . $ram . 'SellerTradeParty/' . $ram . 'SpecifiedTaxRegistration/' . $ram . 'ID[@schemeID="FC"]', | ||
type => '//' . $rsm . 'ExchangedDocument/' . $ram . 'TypeCode', | ||
ustid => '//' . $ram . 'SellerTradeParty/' . $ram . 'SpecifiedTaxRegistration/' . $ram . 'ID[@schemeID="VA"]', | ||
vendor_name => '//' . $ram . 'SellerTradeParty/' . $ram . 'Name', | ||
}; | ||
} | ||
|
||
sub item_xpaths { | ||
my ($self) = @_; | ||
my $rsm = $self->{namespaces}->{'urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100'}; | ||
my $ram = $self->{namespaces}->{'urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100'}; | ||
my $udt = $self->{namespaces}->{'urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100'}; | ||
$ram .= ":" if $ram; | ||
$rsm .= ":" if $rsm; | ||
$udt .= ":" if $udt; | ||
return { | ||
'currency' => undef, # Only global currency in CrossIndustryInvoice | ||
'price' => './ram:SpecifiedLineTradeAgreement/ram:NetPriceProductTradePrice', | ||
'description' => './ram:SpecifiedTradeProduct/ram:Name', | ||
'quantity' => './ram:SpecifiedLineTradeDelivery/ram:BilledQuantity', | ||
'subtotal' => './ram:SpecifiedLineTradeSettlement/ram:SpecifiedTradeSettlementLineMonetarySummation/ram:LineTotalAmount', | ||
'tax_rate' => './ram:SpecifiedLineTradeSettlement/ram:ApplicableTradeTax/ram:RateApplicablePercent', | ||
'tax_scheme' => './ram:SpecifiedLineTradeSettlement/ram:ApplicableTradeTax/ram:TypeCode', | ||
'vendor_partno' => './ram:SpecifiedTradeProduct/ram:SellerAssignedID', | ||
'currency' => undef, # Only global currency in CrossIndustryInvoice | ||
'price' => './' . $ram . 'SpecifiedLineTradeAgreement/' . $ram . 'NetPriceProductTradePrice', | ||
'description' => './' . $ram . 'SpecifiedTradeProduct/' . $ram . 'Name', | ||
'quantity' => './' . $ram . 'SpecifiedLineTradeDelivery/' . $ram . 'BilledQuantity', | ||
'subtotal' => './' . $ram . 'SpecifiedLineTradeSettlement/' . $ram . 'SpecifiedTradeSettlementLineMonetarySummation/' . $ram . 'LineTotalAmount', | ||
'tax_rate' => './' . $ram . 'SpecifiedLineTradeSettlement/' . $ram . 'ApplicableTradeTax/' . $ram . 'RateApplicablePercent', | ||
'tax_scheme' => './' . $ram . 'SpecifiedLineTradeSettlement/' . $ram . 'ApplicableTradeTax/' . $ram . 'TypeCode', | ||
'vendor_partno' => './' . $ram . 'SpecifiedTradeProduct/' . $ram . 'SellerAssignedID', | ||
}; | ||
} | ||
|
||
sub items_xpath { | ||
my ($self) = @_; | ||
my $rsm = $self->{namespaces}->{'urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100'}; | ||
my $ram = $self->{namespaces}->{'urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100'}; | ||
my $udt = $self->{namespaces}->{'urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100'}; | ||
$ram .= ":" if $ram; | ||
$rsm .= ":" if $rsm; | ||
$udt .= ":" if $udt; | ||
return '//' . $ram . 'IncludedSupplyChainTradeLineItem'; | ||
} | ||
|
||
# Metadata accessor method | ||
sub metadata { | ||
|
@@ -143,6 +177,10 @@ sub parse_xml { | |
$self->{_metadata} = {}; | ||
$self->{_items} = (); | ||
|
||
my $ram = $self->{namespaces}->{'urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100'}; | ||
$ram .= ":" if $ram; | ||
my $udt = $self->{namespaces}->{'urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100'}; | ||
#foreach my $namespace (@{$self->{namespaces}}( | ||
# Retrieve scalar metadata from DOM | ||
foreach my $key ( keys %{$self->scalar_xpaths} ) { | ||
my $xpath = ${$self->scalar_xpaths}{$key}; | ||
|
@@ -152,6 +190,10 @@ sub parse_xml { | |
next; | ||
} | ||
my $value = $self->{dom}->findnodes($xpath); | ||
unless ($udt) { | ||
$value = $self->{dom}->findnodes('//' . $ram . 'DueDateDateTime','DateTimeString') if $key eq 'duedate'; | ||
$value = $self->{dom}->findnodes('//' . $ram . 'IssueDateTime','DateTimeString') if $key eq 'transdate'; | ||
} | ||
if ( $value ) { | ||
# Get rid of extraneous white space | ||
$value = $value->string_value; | ||
|
@@ -163,15 +205,14 @@ sub parse_xml { | |
} | ||
} | ||
|
||
|
||
# Convert payment code metadata field to Boolean | ||
# See https://service.unece.org/trade/untdid/d16b/tred/tred4461.htm for other valid codes. | ||
${$self->{_metadata}}{'direct_debit'} = ${$self->{_metadata}}{'direct_debit'} == 59 ? 1 : 0; | ||
|
||
my @items; | ||
$self->{_items} = \@items; | ||
|
||
foreach my $item ( $self->{dom}->findnodes(ITEMS_XPATH) ) { | ||
foreach my $item ( $self->{dom}->findnodes($self->items_xpath) ) { | ||
my %line_item; | ||
foreach my $key ( keys %{$self->item_xpaths} ) { | ||
my $xpath = ${$self->item_xpaths}{$key}; | ||
|