Projekt

Allgemein

Profil

« Zurück | Weiter » 

Revision ea01c174

Von Johannes Grassler vor etwa 1 Jahr hinzugefügt

  • ID ea01c174215812c60286249e8237a356201cd57d
  • Vorgänger 652aebcf
  • Nachfolger 7ea6c1b8

SL::XMLInvoice::CrossindustryDocument hinzugefuegt

Dieses Modul ist der Parser fuer Eingangsrechnungen im Format
ZUGFeRD 1.0 / CrossIndustryDocument.

Unterschiede anzeigen:

SL/XMLInvoice.pm
250 250

  
251 251
sub _document_modules {
252 252
  return (
253
    'SL::XMLInvoice::CrossIndustryDocument',
253 254
    'SL::XMLInvoice::CrossIndustryInvoice',
254 255
    'SL::XMLInvoice::UBL',
255 256
  );
SL/XMLInvoice/CrossIndustryDocument.pm
1
package SL::XMLInvoice::CrossIndustryDocument;
2

  
3
use strict;
4
use warnings;
5

  
6
use parent qw(SL::XMLInvoice);
7

  
8
use constant ITEMS_XPATH => '//ram:IncludedSupplyChainTradeLineItem';
9

  
10
=head1 NAME
11

  
12
SL::XMLInvoice::CrossIndustryDocument - XML parser for UN/CEFACT Cross Industry Document
13

  
14
=head1 DESCRIPTION
15

  
16
C<SL::XMLInvoice::CrossIndustryInvoice> parses XML invoices in UN/CEFACT Cross
17
Industry Document format (also known as ZUgFeRD 1p0 or ZUgFeRD 1.0)  and makes
18
their data available through the interface defined by C<SL::XMLInvoice>. Refer
19
to L<SL::XMLInvoice> for a detailed description of that interface.
20

  
21
See L<https://unece.org/trade/uncefact/xml-schemas> for that format's
22
specification.
23

  
24
=head1 OPERATION
25

  
26
This module is fairly simple. It keeps two hashes of XPath statements exposed
27
by methods:
28

  
29
=over 4
30

  
31
=item scalar_xpaths()
32

  
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.
35

  
36
=item item_xpaths()
37

  
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.
40

  
41
=back
42

  
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.
46

  
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.
50

  
51
=head1 AUTHOR
52

  
53
  Johannes Grassler <info@computer-grassler.de>
54

  
55
=cut
56

  
57
sub supported {
58
  my @supported = ( "UN/CEFACT Cross Industry Document/ZUGFeRD 1.0 (urn:ferd:CrossIndustryDocument:invoice:1p0)" );
59
  return @supported;
60
}
61

  
62
sub check_signature {
63
  my ($self, $dom) = @_;
64

  
65
  my $rootnode = $dom->documentElement;
66

  
67
  foreach my $attr ( $rootnode->attributes ) {
68
    if ( $attr->getData =~ m/urn:ferd:CrossIndustryDocument:invoice:1p0/ ) {
69
      return 1;
70
      }
71
    }
72

  
73
  return 0;
74
}
75

  
76
# XML XPath expressions for global metadata
77
sub scalar_xpaths {
78
  return {
79
    currency => ['//ram:InvoiceCurrencyCode'],
80
    direct_debit => ['//ram:SpecifiedTradeSettlementPaymentMeans/ram:TypeCode'],
81
    duedate => ['//ram:DueDateDateTime/udt:DateTimeString', '//ram:EffectiveSpecifiedPeriod/ram:CompleteDateTime/udt:DateTimeString'],
82
    gross_total => ['//ram:DuePayableAmount'],
83
    iban => ['//ram:SpecifiedTradeSettlementPaymentMeans/ram:PayeePartyCreditorFinancialAccount/ram:IBANID'],
84
    invnumber => ['//rsm:HeaderExchangedDocument/ram:ID'],
85
    net_total => ['//ram:TaxBasisTotalAmount'],
86
    transdate => ['//ram:IssueDateTime/udt:DateTimeString'],
87
    taxnumber => ['//ram:SellerTradeParty/ram:SpecifiedTaxRegistration/ram:ID[@schemeID="FC"]'],
88
    type => ['//rsm:HeaderExchangedDocument/ram:TypeCode'],
89
    ustid => ['//ram:SellerTradeParty/ram:SpecifiedTaxRegistration/ram:ID[@schemeID="VA"]'],
90
    vendor_name => ['//ram:SellerTradeParty/ram:Name'],
91
  };
92
}
93

  
94
sub item_xpaths {
95
  return {
96
    'currency' => ['./ram:SpecifiedSupplyChainTradeAgreement/ram:GrossPriceProductTradePrice/ram:ChargeAmount[attribute::currencyID]',
97
                   './ram:SpecifiedSupplyChainTradeAgreement/ram:GrossPriceProductTradePrice/ram:BasisAmount'],
98
    'price' => ['./ram:SpecifiedSupplyChainTradeAgreement/ram:GrossPriceProductTradePrice/ram:ChargeAmount',
99
               './ram:SpecifiedSupplyChainTradeAgreement/ram:GrossPriceProductTradePrice/ram:BasisAmount'],
100
    'description' => ['./ram:SpecifiedTradeProduct/ram:Name'],
101
    'quantity' => ['./ram:SpecifiedSupplyChainTradeDelivery/ram:BilledQuantity',],
102
    'subtotal' => ['./ram:SpecifiedSupplyChainTradeSettlement/ram:SpecifiedTradeSettlementMonetarySummation/ram:LineTotalAmount'],
103
    'tax_rate' => ['./ram:SpecifiedSupplyChainTradeSettlement/ram:ApplicableTradeTax/ram:ApplicablePercent'],
104
    'tax_scheme' => ['./ram:SpecifiedSupplyChainTradeSettlement/ram:ApplicableTradeTax/ram:TypeCode'],
105
    'vendor_partno' => ['./ram:SpecifiedTradeProduct/ram:SellerAssignedID'],
106
  };
107
}
108

  
109

  
110
# Metadata accessor method
111
sub metadata {
112
  my $self = shift;
113
  return $self->{_metadata};
114
}
115

  
116
# Item list accessor method
117
sub items {
118
  my $self = shift;
119
  return $self->{_items};
120
}
121

  
122
# Data keys we return
123
sub _data_keys {
124
  my $self = shift;
125
  my %keys;
126

  
127
  map { $keys{$_} = 1; } keys %{$self->scalar_xpaths};
128

  
129
  return \%keys;
130
}
131

  
132
# Item keys we return
133
sub _item_keys {
134
  my $self = shift;
135
  my %keys;
136

  
137
  map { $keys{$_} = 1; } keys %{$self->item_xpaths};
138

  
139
  return \%keys;
140
}
141

  
142
# Main parser subroutine for retrieving XML data
143
sub parse_xml {
144
  my $self = shift;
145
  $self->{_metadata} = {};
146
  $self->{_items} = ();
147

  
148
  # Retrieve scalar metadata from DOM
149
  foreach my $key ( keys %{$self->scalar_xpaths} ) {
150
    foreach my $xpath ( @{${$self->scalar_xpaths}{$key}} ) {
151
      unless ( $xpath ) {
152
        # Skip keys without xpath list
153
        ${$self->{_metadata}}{$key} = undef;
154
        next;
155
      }
156
      my $value = $self->{dom}->findnodes($xpath);
157
      if ( $value ) {
158
        # Get rid of extraneous white space
159
        $value = $value->string_value;
160
        $value =~ s/\n|\r//g;
161
        $value =~ s/\s{2,}/ /g;
162
        ${$self->{_metadata}}{$key} = $value;
163
        last; # first matching xpath wins
164
      } else {
165
        ${$self->{_metadata}}{$key} = undef;
166
      }
167
    }
168
  }
169

  
170

  
171
  # Convert payment code metadata field to Boolean
172
  # See https://service.unece.org/trade/untdid/d16b/tred/tred4461.htm for other valid codes.
173
  ${$self->{_metadata}}{'direct_debit'} = ${$self->{_metadata}}{'direct_debit'} == 59 ? 1 : 0;
174

  
175
  my @items;
176
  $self->{_items} = \@items;
177

  
178
  foreach my $item ( $self->{dom}->findnodes(ITEMS_XPATH)) {
179
    my %line_item;
180
    foreach my $key ( keys %{$self->item_xpaths} ) {
181
      foreach my $xpath ( @{${$self->item_xpaths}{$key}} ) {
182
        unless ( $xpath ) {
183
          # Skip keys without xpath list
184
          $line_item{$key} = undef;
185
          next;
186
        }
187
        my $value = $item->findnodes($xpath);
188
        if ( $value ) {
189
          # Get rid of extraneous white space
190
          $value = $value->string_value;
191
          $value =~ s/\n|\r//g;
192
          $value =~ s/\s{2,}/ /g;
193
          $line_item{$key} = $value;
194
          last; # first matching xpath wins
195
        } else {
196
          $line_item{$key} = undef;
197
        }
198
      }
199
    }
200
    push @items, \%line_item;
201
  }
202

  
203
}
204

  
205
1;

Auch abrufbar als: Unified diff