Revision 1522aeb7
Von Johannes Grassler vor etwa 1 Jahr hinzugefügt
SL/Controller/ZUGFeRD.pm | ||
---|---|---|
163 | 163 |
|
164 | 164 |
if ( ! ($metadata{'ustid'} or $metadata{'taxnumber'}) ) { |
165 | 165 |
die t8("Cannot process this invoice: neither VAT ID nor tax ID present."); |
166 |
}
|
|
166 |
} |
|
167 | 167 |
|
168 | 168 |
$vendor = find_vendor($metadata{'ustid'}, $metadata{'taxnumber'}); |
169 | 169 |
|
... | ... | |
238 | 238 |
my $tax_rate = $item{'tax_rate'} / 100; # XML data is usually in percent |
239 | 239 |
|
240 | 240 |
my $taxes = SL::DB::Manager::Tax->get_all( |
241 |
where => [ chart_categories => { like => '%' . $default_ap_amount_chart->category . '%' }, |
|
242 |
rate => $tax_rate, |
|
243 |
], |
|
241 |
where => [ |
|
242 |
chart_categories => { like => '%' . $default_ap_amount_chart->category . '%' }, |
|
243 |
rate => $tax_rate, |
|
244 |
], |
|
244 | 245 |
); |
245 | 246 |
|
246 | 247 |
# If we really can't find any tax definition (a simple rounding error may |
... | ... | |
254 | 255 |
|
255 | 256 |
my $tax = ${$taxes}[0]; |
256 | 257 |
|
257 |
my $item_obj = SL::DB::RecordTemplateItem |
|
258 |
->new(amount1 => $net_total,
|
|
259 |
record_template_id => $template_ap->id,
|
|
260 |
chart_id => $default_ap_amount_chart->id,
|
|
261 |
tax_id => $tax->id,
|
|
262 |
);
|
|
258 |
my $item_obj = SL::DB::RecordTemplateItem->new(
|
|
259 |
amount1 => $net_total, |
|
260 |
record_template_id => $template_ap->id, |
|
261 |
chart_id => $default_ap_amount_chart->id, |
|
262 |
tax_id => $tax->id, |
|
263 |
); |
|
263 | 264 |
$item_obj->save; |
264 | 265 |
} |
265 | 266 |
|
SL/XMLInvoice.pm | ||
---|---|---|
1 | 1 |
package SL::XMLInvoice; |
2 | 2 |
|
3 |
use strict; |
|
4 |
use warnings; |
|
5 |
|
|
6 |
use XML::LibXML; |
|
7 |
|
|
8 |
use SL::XMLInvoice::UBL; |
|
9 |
use SL::XMLInvoice::CrossIndustryInvoice; |
|
10 |
|
|
11 |
use constant RES_OK => 0; |
|
12 |
use constant RES_XML_PARSING_FAILED => 1; |
|
13 |
use constant RES_UNKNOWN_ROOT_NODE_TYPE => 2; |
|
14 |
|
|
3 | 15 |
=head1 NAME |
4 | 16 |
|
5 | 17 |
SL::XMLInvoice - Top level factory class for XML Invoice parsers. |
... | ... | |
27 | 39 |
|
28 | 40 |
=cut |
29 | 41 |
|
30 |
use strict; |
|
31 |
use warnings; |
|
32 |
|
|
33 |
use XML::LibXML; |
|
34 |
|
|
35 | 42 |
=head1 ATTRIBUTES |
36 | 43 |
|
37 | 44 |
=over 4 |
... | ... | |
67 | 74 |
|
68 | 75 |
=cut |
69 | 76 |
|
70 |
use constant RES_OK => 0; |
|
71 |
use constant RES_XML_PARSING_FAILED => 1; |
|
72 |
use constant RES_UNKNOWN_ROOT_NODE_TYPE => 2; |
|
73 |
|
|
74 | 77 |
=head1 METHODS |
75 | 78 |
|
76 | 79 |
=head2 Data structure definition methods (only in C<SL::XMLInvoice>) |
... | ... | |
163 | 166 |
sub metadata { |
164 | 167 |
my $self = shift; |
165 | 168 |
die "Children of $self must implement a metadata() method returning the bill's metadata as a hash."; |
166 |
}
|
|
169 |
} |
|
167 | 170 |
|
168 | 171 |
=item items() |
169 | 172 |
|
... | ... | |
178 | 181 |
sub items { |
179 | 182 |
my $self = shift; |
180 | 183 |
die "Children of $self must implement a item() method returning the bill's items as a hash."; |
181 |
}
|
|
184 |
} |
|
182 | 185 |
|
183 | 186 |
=item parse_xml() |
184 | 187 |
|
... | ... | |
195 | 198 |
sub parse_xml { |
196 | 199 |
my $self = shift; |
197 | 200 |
die "Children of $self must implement a parse_xml() method."; |
198 |
}
|
|
201 |
} |
|
199 | 202 |
|
200 | 203 |
=head2 Internal methods |
201 | 204 |
|
... | ... | |
213 | 216 |
|
214 | 217 |
=cut |
215 | 218 |
|
216 |
use SL::XMLInvoice::UBL; |
|
217 |
use SL::XMLInvoice::CrossIndustryInvoice; |
|
218 |
|
|
219 | 219 |
sub _document_nodenames { |
220 | 220 |
return { |
221 | 221 |
'rsm:CrossIndustryInvoice' => 'SL::XMLInvoice::CrossIndustryInvoice', |
... | ... | |
235 | 235 |
sub _data_keys { |
236 | 236 |
my $self = shift; |
237 | 237 |
die "Children of $self must implement a _data_keys() method returning the keys an invoice item hash will contain."; |
238 |
}
|
|
238 |
} |
|
239 | 239 |
|
240 | 240 |
=item _item_keys() |
241 | 241 |
|
... | ... | |
253 | 253 |
sub _item_keys { |
254 | 254 |
my $self = shift; |
255 | 255 |
die "Children of $self must implement a _item_keys() method returning the keys an invoice item hash will contain."; |
256 |
}
|
|
256 |
} |
|
257 | 257 |
|
258 | 258 |
|
259 |
sub new |
|
260 |
{ |
|
259 |
sub new { |
|
261 | 260 |
my ($self, $xml_data) = @_; |
262 | 261 |
my $type = undef; |
263 | 262 |
$self = {}; |
... | ... | |
271 | 270 |
$self->{message} = $::locale->text("Parsing the XML data failed: $xml_data"); |
272 | 271 |
$self->{result} = RES_XML_PARSING_FAILED; |
273 | 272 |
return $self; |
274 |
}
|
|
273 |
} |
|
275 | 274 |
|
276 | 275 |
# Determine parser class to use |
277 | 276 |
my $document_nodename = $self->{dom}->documentElement->nodeName; |
278 | 277 |
if ( ${$self->_document_nodenames}{$document_nodename} ) { |
279 | 278 |
$type = ${$self->_document_nodenames}{$document_nodename} |
280 |
}
|
|
279 |
} |
|
281 | 280 |
|
282 | 281 |
unless ( $type ) { |
283 | 282 |
$self->{result} = RES_UNKNOWN_ROOT_NODE_TYPE; |
... | ... | |
286 | 285 |
$node_types, |
287 | 286 |
$document_nodename); |
288 | 287 |
return $self; |
289 |
}
|
|
288 |
} |
|
290 | 289 |
|
291 | 290 |
bless $self, $type; |
292 | 291 |
|
293 | 292 |
# Implementation sanity check for child classes: make sure they are aware of |
294 | 293 |
# the keys the hash returned by their metadata() method must contain. |
295 |
my @missing_data_keys = (); |
|
296 |
foreach my $data_key ( @{$self->data_keys} ) |
|
297 |
{ |
|
298 |
unless ( ${$self->_data_keys}{$data_key}) { push @missing_data_keys, $data_key; } |
|
299 |
} |
|
294 |
my @missing_data_keys = grep { !${$self->_data_keys}{$data_key} } @{ $self->data_keys }; |
|
300 | 295 |
if ( scalar(@missing_data_keys) > 0 ) { |
301 | 296 |
die "Incomplete implementation: the following metadata keys appear to be missing from $type: " . join(", ", @missing_data_keys); |
302 | 297 |
} |
... | ... | |
304 | 299 |
# Implementation sanity check for child classes: make sure they are aware of |
305 | 300 |
# the keys the hashes returned by their items() method must contain. |
306 | 301 |
my @missing_item_keys = (); |
307 |
foreach my $item_key ( @{$self->item_keys} ) |
|
308 |
{ |
|
302 |
foreach my $item_key ( @{$self->item_keys} ) { |
|
309 | 303 |
unless ( ${$self->_item_keys}{$item_key}) { push @missing_item_keys, $item_key; } |
310 |
}
|
|
304 |
} |
|
311 | 305 |
if ( scalar(@missing_item_keys) > 0 ) { |
312 | 306 |
die "Incomplete implementation: the following item keys appear to be missing from $type: " . join(", ", @missing_item_keys); |
313 | 307 |
} |
... | ... | |
320 | 314 |
|
321 | 315 |
$self->{result} = RES_OK; |
322 | 316 |
return $self; |
323 |
}
|
|
317 |
} |
|
324 | 318 |
|
325 | 319 |
1; |
326 | 320 |
|
SL/XMLInvoice/CrossIndustryInvoice.pm | ||
---|---|---|
1 | 1 |
package SL::XMLInvoice::CrossIndustryInvoice; |
2 |
|
|
3 |
use strict; |
|
4 |
use warnings; |
|
5 |
|
|
2 | 6 |
use parent qw(SL::XMLInvoice); |
3 | 7 |
|
8 |
use constant ITEMS_XPATH => '//ram:IncludedSupplyChainTradeLineItem'; |
|
9 |
|
|
4 | 10 |
=head1 NAME |
5 | 11 |
|
6 | 12 |
SL::XMLInvoice::FakturX - XML parser for UN/CEFACT Cross Industry Invoice |
... | ... | |
48 | 54 |
|
49 | 55 |
=cut |
50 | 56 |
|
51 |
use strict; |
|
52 |
use constant ITEMS_XPATH => '//ram:IncludedSupplyChainTradeLineItem'; |
|
53 |
|
|
54 | 57 |
# XML XPath expressions for global metadata |
55 | 58 |
sub scalar_xpaths { |
56 | 59 |
return { |
... | ... | |
172 | 175 |
push @items, \%line_item; |
173 | 176 |
} |
174 | 177 |
|
175 |
|
|
176 | 178 |
} |
177 | 179 |
|
178 | 180 |
1; |
SL/XMLInvoice/UBL.pm | ||
---|---|---|
1 | 1 |
package SL::XMLInvoice::UBL; |
2 | 2 |
|
3 |
use strict; |
|
4 |
use warnings; |
|
5 |
|
|
6 |
use parent qw(SL::XMLInvoice); |
|
7 |
|
|
8 |
use constant ITEMS_XPATH => '//cac:InvoiceLine'; |
|
9 |
|
|
3 | 10 |
=head1 NAME |
4 | 11 |
|
5 | 12 |
SL::XMLInvoice::UBL - XML parser for Universal Business Language invoices |
... | ... | |
47 | 54 |
|
48 | 55 |
=cut |
49 | 56 |
|
50 |
use strict; |
|
51 |
use parent qw(SL::XMLInvoice); |
|
52 |
|
|
53 |
use constant ITEMS_XPATH => '//cac:InvoiceLine'; |
|
54 |
|
|
55 | 57 |
# XML XPath expression for |
56 | 58 |
sub scalar_xpaths { |
57 | 59 |
return { |
... | ... | |
145 | 147 |
# have to guess whether it's a tax ID or VAT ID (not using |
146 | 148 |
# SL::VATIDNr->validate here to keep this code portable): |
147 | 149 |
|
148 |
if ( ${$self->{_metadata}}{'ustid'} =~ qr"/" ) |
|
149 |
{ |
|
150 |
if ( ${$self->{_metadata}}{'ustid'} =~ qr"/" ) { |
|
150 | 151 |
# Unset this since the 'taxid' key has been retrieved with the same xpath |
151 | 152 |
# expression. |
152 | 153 |
${$self->{_metadata}}{'ustid'} = undef; |
153 |
} else {
|
|
154 |
} else { |
|
154 | 155 |
# Unset this since the 'ustid' key has been retrieved with the same xpath |
155 | 156 |
# expression. |
156 | 157 |
${$self->{_metadata}}{'taxnumber'} = undef; |
157 |
}
|
|
158 |
} |
|
158 | 159 |
|
159 | 160 |
my @items; |
160 | 161 |
$self->{_items} = \@items; |
SL/ZUGFeRD.pm | ||
---|---|---|
20 | 20 |
use constant PROFILE_FACTURX_EXTENDED => 0; |
21 | 21 |
use constant PROFILE_XRECHNUNG => 1; |
22 | 22 |
|
23 |
use constant RES_OK => 0;
|
|
24 |
use constant RES_ERR_FILE_OPEN => -1;
|
|
25 |
use constant RES_ERR_NO_ATTACHMENT => -2;
|
|
23 |
use constant RES_OK => 0; |
|
24 |
use constant RES_ERR_FILE_OPEN => -1; |
|
25 |
use constant RES_ERR_NO_ATTACHMENT => -2; |
|
26 | 26 |
|
27 | 27 |
our @customer_settings = ( |
28 | 28 |
[ 0, t8('Do not create Factur-X/ZUGFeRD invoices') ], |
... | ... | |
89 | 89 |
if ( $parser->{status} == SL::XMLInvoice::RES_OK ){ |
90 | 90 |
return $parser; |
91 | 91 |
} else { |
92 |
push @res, t8("Could not parse PDF embedded attachment #1: #2", |
|
93 |
$k, |
|
94 |
$parser->{result}); |
|
92 |
push @res, t8( |
|
93 |
"Could not parse PDF embedded attachment #1: #2", |
|
94 |
$k, |
|
95 |
$parser->{result} |
|
96 |
); |
|
95 | 97 |
} |
96 | 98 |
} |
97 | 99 |
} |
... | ... | |
101 | 103 |
# this point - if there were no attachments at all, we would have bailed out |
102 | 104 |
# a lot earlier. |
103 | 105 |
|
104 |
%res_fail = ( result => RES_ERR_FILE_OPEN(), |
|
105 |
message => join("; ", @res), |
|
106 |
%res_fail = ( |
|
107 |
result => RES_ERR_FILE_OPEN(), |
|
108 |
message => join("; ", @res), |
|
106 | 109 |
); |
107 | 110 |
|
108 | 111 |
return \%res_fail; |
Auch abrufbar als: Unified diff
Einrueckungen und weitere Stilprobleme repariert