Revision ba929776
Von Johannes Grassler vor etwa 1 Jahr hinzugefügt
SL/Controller/ZUGFeRD.pm | ||
---|---|---|
129 | 129 |
my %res; # result data structure returned by SL::ZUGFeRD->extract_from_{pdf,xml}() |
130 | 130 |
my $parser; # SL::XMLInvoice object created by SL::ZUGFeRD->extract_from_{pdf,xml}() |
131 | 131 |
my $dom; # DOM object for parsed XML data |
132 |
my $template_ap; # SL::DB::RecordTemplate object |
|
133 | 132 |
my $vendor; # SL::DB::Vendor object |
134 | 133 |
|
135 | 134 |
my $ibanmessage; # Message to display if vendor's database and invoice IBANs don't match up |
... | ... | |
145 | 144 |
|
146 | 145 |
if ($res{'result'} != SL::ZUGFeRD::RES_OK()) { |
147 | 146 |
# An error occurred; log message from parser: |
148 |
$::lxdebug->message(LXDebug::DEBUG1(), "Could not extract ZUGFeRD data, error message: " . $res{'message'}); |
|
149 | 147 |
die(t8("Could not extract Factur-X/ZUGFeRD data, data and error message:") . " $res{'message'}"); |
150 | 148 |
} |
151 | 149 |
|
... | ... | |
175 | 173 |
) unless $vendor; |
176 | 174 |
|
177 | 175 |
|
178 |
# Create a record template for this imported invoice |
|
179 |
$template_ap = SL::DB::RecordTemplate->new( |
|
180 |
vendor_id=>$vendor->id, |
|
181 |
); |
|
182 |
|
|
183 | 176 |
# Check IBAN specified on bill matches the one we've got in |
184 | 177 |
# the database for this vendor. |
185 | 178 |
$ibanmessage = $iban ne $vendor->iban ? "Record IBAN $iban doesn't match vendor IBAN " . $vendor->iban : $iban if $iban; |
... | ... | |
220 | 213 |
name => $metadata{'currency'}, |
221 | 214 |
); |
222 | 215 |
|
223 |
$template_ap->assign_attributes( |
|
224 |
template_name => t8("Faktur-X/ZUGFeRD/XRechnung import #1, #2", $vendor->name, $invnumber), |
|
225 |
template_type => 'ap_transaction', |
|
226 |
direct_debit => $metadata{'direct_debit'}, |
|
227 |
notes => "Faktur-X/ZUGFeRD/XRechnung Import. Type: $metadata{'type'}\nIBAN: " . $ibanmessage, |
|
228 |
taxincluded => 0, |
|
229 |
currency_id => $currency->id, |
|
230 |
ar_ap_chart_id => $ap_chart_id, |
|
231 |
); |
|
232 |
|
|
233 |
$template_ap->save; |
|
234 |
|
|
235 |
my $default_ap_amount_chart = SL::DB::Manager::Chart->find_by(charttype => 'A'); |
|
236 |
|
|
237 |
foreach my $i ( @items ) |
|
238 |
{ |
|
239 |
my %item = %{$i}; |
|
240 |
|
|
241 |
my $net_total = $item{'subtotal'}; |
|
242 |
my $desc = $item{'description'}; |
|
243 |
my $tax_rate = $item{'tax_rate'} / 100; # XML data is usually in percent |
|
244 |
|
|
245 |
my $taxes = SL::DB::Manager::Tax->get_all( |
|
246 |
where => [ |
|
247 |
chart_categories => { like => '%' . $default_ap_amount_chart->category . '%' }, |
|
248 |
rate => $tax_rate, |
|
249 |
], |
|
250 |
); |
|
251 |
|
|
252 |
# If we really can't find any tax definition (a simple rounding error may |
|
253 |
# be sufficient for that to happen), grab the first tax fitting the default |
|
254 |
# category, just like the AP form would do it for manual entry. |
|
255 |
if ( scalar @{$taxes} == 0 ) { |
|
256 |
$taxes = SL::DB::Manager::Tax->get_all( |
|
257 |
where => [ chart_categories => { like => '%' . $default_ap_amount_chart->category . '%' } ], |
|
258 |
); |
|
259 |
} |
|
260 |
|
|
261 |
my $tax = ${$taxes}[0]; |
|
262 |
|
|
263 |
my $item_obj = SL::DB::RecordTemplateItem->new( |
|
264 |
amount1 => $net_total, |
|
265 |
record_template_id => $template_ap->id, |
|
266 |
chart_id => $default_ap_amount_chart->id, |
|
267 |
tax_id => $tax->id, |
|
268 |
); |
|
269 |
$item_obj->save; |
|
270 |
} |
|
271 |
|
|
272 | 216 |
$self->redirect_to( |
273 | 217 |
controller => 'ap.pl', |
274 |
action => 'load_record_template', |
|
275 |
id => $template_ap->id, |
|
218 |
action => 'load_zugferd', |
|
276 | 219 |
'form_defaults.no_payment_bookings' => 0, |
277 | 220 |
'form_defaults.paid_1_suggestion' => $::form->format_amount(\%::myconfig, $metadata{'total'}, 2), |
278 | 221 |
'form_defaults.invnumber' => $invnumber, |
222 |
'form_defaults.AP_chart_id' => $ap_chart_id, |
|
223 |
'form_defaults.currency' => $currency->name, |
|
279 | 224 |
'form_defaults.duedate' => $metadata{'duedate'}, |
280 | 225 |
'form_defaults.transdate' => $metadata{'transdate'}, |
281 | 226 |
'form_defaults.notes' => "ZUGFeRD Import. Type: $metadata{'type'}\nIBAN: " . $ibanmessage, |
282 | 227 |
'form_defaults.taxincluded' => 0, |
283 | 228 |
'form_defaults.direct_debit' => $metadata{'direct_debit'}, |
229 |
'form_defaults.vendor' => $vendor->name, |
|
230 |
'form_defaults.vendor_id' => $vendor->id, |
|
284 | 231 |
'form_defaults.zugferd_session_file' => $file_name, |
285 | 232 |
); |
286 | 233 |
|
bin/mozilla/ap.pl | ||
---|---|---|
56 | 56 |
use SL::DB::ValidityToken; |
57 | 57 |
use SL::Presenter::ItemsList; |
58 | 58 |
use SL::Webdav; |
59 |
use SL::ZUGFeRD; |
|
59 | 60 |
use SL::Locale::String qw(t8); |
60 | 61 |
|
61 | 62 |
require "bin/mozilla/common.pl"; |
... | ... | |
109 | 110 |
$::form->show_generic_error($::locale->text("You do not have the permissions to access this function.")) if ! $cache->{_may_view_or_edit_this_invoice}; |
110 | 111 |
} |
111 | 112 |
|
113 |
sub load_zugferd { |
|
114 |
$::auth->assert('ap_transactions'); |
|
115 |
|
|
116 |
my $data; # buffer for holding file contents |
|
117 |
|
|
118 |
my $form_defaults = $::form->{form_defaults}; |
|
119 |
my $file = SL::SessionFile->new($form_defaults->{zugferd_session_file}, mode => '<'); |
|
120 |
my $file_name = $file->file_name; |
|
121 |
|
|
122 |
$::form->{$_} = $form_defaults->{$_} for keys %{ $form_defaults // {} }; |
|
123 |
|
|
124 |
# Defaults |
|
125 |
$::form->{title} = "Add"; |
|
126 |
$::form->{paidaccounts} = 1; |
|
127 |
|
|
128 |
$file->open('<'); |
|
129 |
if ( ! defined($file->fh->read($data, -s $file->fh)) ) { |
|
130 |
SL::Helper::Flash::flash_later('error', |
|
131 |
t8('Could not open ZUGFeRD file for reading: #1', $!)); |
|
132 |
} else { |
|
133 |
|
|
134 |
my %res; # result data structure returned by SL::ZUGFeRD->extract_from_{pdf,xml}() |
|
135 |
my $parser; # SL::XMLInvoice object created by SL::ZUGFeRD->extract_from_{pdf,xml}() |
|
136 |
my $template_ap; # SL::DB::RecordTemplate object |
|
137 |
my $vendor; # SL::DB::Vendor object |
|
138 |
my %metadata; # structured data extracted from XML payload |
|
139 |
my @items; # list of invoice items |
|
140 |
my $default_ap_amount_chart; |
|
141 |
|
|
142 |
if ( $data =~ m/^%PDF/ ) { |
|
143 |
%res = %{SL::ZUGFeRD->extract_from_pdf($data)}; |
|
144 |
} else { |
|
145 |
%res = %{SL::ZUGFeRD->extract_from_xml($data)}; |
|
146 |
} |
|
147 |
|
|
148 |
|
|
149 |
$parser = $res{'invoice_xml'}; |
|
150 |
%metadata = %{$parser->metadata}; |
|
151 |
@items = @{$parser->items}; |
|
152 |
|
|
153 |
$default_ap_amount_chart = SL::DB::Manager::Chart->find_by(charttype => 'A'); |
|
154 |
|
|
155 |
my $row = 0; |
|
156 |
foreach my $i (@items) { |
|
157 |
$row++; |
|
158 |
|
|
159 |
my %item = %{$i}; |
|
160 |
|
|
161 |
my $net_total = $::form->format_amount(\%::myconfig, $item{'subtotal'}, 2); |
|
162 |
my $desc = $item{'description'}; |
|
163 |
my $tax_rate = $item{'tax_rate'} / 100; # XML data is usually in percent |
|
164 |
|
|
165 |
my $taxes = SL::DB::Manager::Tax->get_all( |
|
166 |
where => [ |
|
167 |
chart_categories => { like => '%' . $default_ap_amount_chart->category . '%' }, |
|
168 |
rate => $tax_rate, |
|
169 |
], |
|
170 |
); |
|
171 |
|
|
172 |
# If we really can't find any tax definition (a simple rounding error may |
|
173 |
# be sufficient for that to happen), grab the first tax fitting the default |
|
174 |
# AP amount chart, just like the AP form would do it for manual entry. |
|
175 |
if ( scalar @{$taxes} == 0 ) { |
|
176 |
$taxes = SL::DB::Manager::Tax->get_all( |
|
177 |
where => [ chart_categories => { like => '%' . $default_ap_amount_chart->category . '%' } ], |
|
178 |
); |
|
179 |
} |
|
180 |
|
|
181 |
my $tax = ${$taxes}[0]; |
|
182 |
|
|
183 |
if (!$tax) { |
|
184 |
$row--; |
|
185 |
next; |
|
186 |
} |
|
187 |
|
|
188 |
$::form->{"AP_amount_chart_id_${row}"} = $default_ap_amount_chart->id; # FIXME: add heuristic for picking a better one |
|
189 |
$::form->{"previous_AP_amount_chart_id_${row}"} = $default_ap_amount_chart->id; # FIXME: add heuristic for picking a better one |
|
190 |
$::form->{"amount_${row}"} = $net_total; |
|
191 |
$::form->{"taxchart_${row}"} = $tax->id . '--' . $tax->rate; |
|
192 |
} |
|
193 |
|
|
194 |
flash('info', $::locale->text("The ZUGFeRD/Factur-X invoice '#1' has been loaded.", $file_name)); |
|
195 |
|
|
196 |
$::form->{form_validity_token} = SL::DB::ValidityToken->create(scope => SL::DB::ValidityToken::SCOPE_PURCHASE_INVOICE_POST())->token; |
|
197 |
$::form->{rowcount} = $row; |
|
198 |
|
|
199 |
update( |
|
200 |
keep_rows_without_amount => 1, |
|
201 |
dont_add_new_row => 1, |
|
202 |
); |
|
203 |
} |
|
204 |
} |
|
205 |
|
|
112 | 206 |
sub load_record_template { |
113 | 207 |
$::auth->assert('ap_transactions'); |
114 | 208 |
|
Auch abrufbar als: Unified diff
ZUGFeRD: Importiere via SessionFile
Benutze das SessionFile aus
um die strukturierten Daten der ZUGFeRD/Faktur-X-Rechnung in
das Formular fuer die Kreditorenbuchung zu uebertragen. Diese
Loesung ist deutlich eleganter als die bisherige, die fuer
jeden Import ein RecordTemplate erzeugte und es nach dem
Import in der Datenbank beliess.