Revision f9422f02
Von Werner Hahn vor 4 Monaten hinzugefügt
- ID f9422f02f97d4de7deca35c4de0758e5b006873e
- Vorgänger 48fa6dcc
SL/XMLInvoice.pm | ||
---|---|---|
return $self;
|
||
}
|
||
|
||
# Determine parser class to use
|
||
# Determine parser class and namespaces to use
|
||
my $type = first {
|
||
$_->check_signature($self->{dom})
|
||
} @document_modules;
|
||
... | ... | |
);
|
||
return $self;
|
||
}
|
||
|
||
bless $self, $type;
|
||
|
||
my $namespaces = $self->namespaces($self->{dom});
|
||
|
||
$self->{namespaces} = $namespaces;
|
||
|
||
# Implementation sanity check for child classes: make sure they are aware of
|
||
# the keys the hash returned by their metadata() method must contain.
|
||
my @missing_data_keys = grep { !${$self->_data_keys}{$_} } @{ $self->data_keys };
|
SL/XMLInvoice/Base.pm | ||
---|---|---|
=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)
|
||
... | ... | |
=cut
|
||
|
||
sub item_keys {
|
||
my $self = shift;
|
||
my @keys = (
|
||
'currency',
|
||
'description',
|
||
... | ... | |
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
|
||
... | ... | |
=head1 AUTHOR
|
||
|
||
Johannes Grassler <info@computer-grassler.de>
|
||
Werner Hahn <wh@futureworldsearch.net>
|
||
|
||
=cut
|
||
|
SL/XMLInvoice/CrossIndustryDocument.pm | ||
---|---|---|
|
||
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
|
||
... | ... | |
=head1 AUTHOR
|
||
|
||
Johannes Grassler <info@computer-grassler.de>
|
||
Werner Hahn <wh@futureworldsearch.net>
|
||
|
||
=cut
|
||
|
||
... | ... | |
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 {
|
||
... | ... | |
$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}} ) {
|
||
... | ... | |
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;
|
||
... | ... | |
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}} ) {
|
SL/XMLInvoice/CrossIndustryInvoice.pm | ||
---|---|---|
|
||
use parent qw(SL::XMLInvoice::Base);
|
||
|
||
use constant ITEMS_XPATH => '//ram:IncludedSupplyChainTradeLineItem';
|
||
|
||
=head1 NAME
|
||
|
||
... | ... | |
=head1 AUTHOR
|
||
|
||
Johannes Grassler <info@computer-grassler.de>
|
||
Werner Hahn <wh@futureworldsearch.net>
|
||
|
||
=cut
|
||
|
||
... | ... | |
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 {
|
||
... | ... | |
$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};
|
||
... | ... | |
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;
|
||
... | ... | |
}
|
||
}
|
||
|
||
|
||
# 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};
|
Auch abrufbar als: Unified diff
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.