Revision b8dfb10a
Von Tamino Steinert vor 9 Monaten hinzugefügt
SL/BackgroundJob/ImportRecordEmails.pm | ||
---|---|---|
10 | 10 |
use SL::DB::Manager::EmailImport; |
11 | 11 |
use SL::Helper::EmailProcessing; |
12 | 12 |
use SL::Presenter::Tag qw(link_tag); |
13 |
use SL::Locale::String qw(t8); |
|
13 | 14 |
|
14 | 15 |
use Params::Validate qw(:all); |
15 | 16 |
use List::MoreUtils qw(any); |
... | ... | |
58 | 59 |
# , $email_journal->id |
59 | 60 |
# ); |
60 | 61 |
my $email_journal_id = $email_journal->id; |
61 |
$result .= "Error while processing email journal $email_journal_id attachments with $function_name: $@";
|
|
62 |
$result .= t8("Error while processing email journal ('#1') attachments with '#2': ", $email_journal_id, $function_name) . $@ . ".";
|
|
62 | 63 |
}; |
63 | 64 |
} |
64 | 65 |
if ($created_records && $config->{processed_imap_flag}) { |
... | ... | |
98 | 99 |
|
99 | 100 |
my $result = ""; |
100 | 101 |
|
101 |
$result .= "Deleted email import(s): "
|
|
102 |
$result .= t8("Deleted email import(s): ")
|
|
102 | 103 |
. join(', ', @deleted_email_import_ids) . "." |
103 | 104 |
if scalar @deleted_email_import_ids; |
104 | 105 |
|
105 |
$result .= "Could not find email import(s): "
|
|
106 |
$result .= t8("Could not find email import(s): ")
|
|
106 | 107 |
. join(', ', @not_found_email_import_ids) . " for deletion." |
107 | 108 |
if scalar @not_found_email_import_ids; |
108 | 109 |
|
SL/Controller/EmailJournal.pm | ||
---|---|---|
406 | 406 |
my $record_type = $::form->{"record_type"}; |
407 | 407 |
die "ZUGFeRD-Import only implemented for ap transaction templates" unless $record_type == 'ap_transaction'; |
408 | 408 |
|
409 |
my $attachment_id = $::form->{attachment_id};
|
|
409 |
my $attachment_id = $::form->{attachment_id}; |
|
410 | 410 |
|
411 | 411 |
my $form_defaults; |
412 | 412 |
if ($attachment_id) { |
SL/Controller/ZUGFeRD.pm | ||
---|---|---|
227 | 227 |
|
228 | 228 |
unless ( defined $ap_chart_id ) { |
229 | 229 |
# If no default account is configured, just use the first AP account found. |
230 |
my $ap_chart = SL::DB::Manager::Chart->get_all(
|
|
230 |
my ($ap_chart) = @{SL::DB::Manager::Chart->get_all(
|
|
231 | 231 |
where => [ link => 'AP' ], |
232 | 232 |
sort_by => [ 'accno' ], |
233 |
); |
|
234 |
$ap_chart_id = ${$ap_chart}[0]->id;
|
|
233 |
)};
|
|
234 |
$ap_chart_id = $ap_chart->id;
|
|
235 | 235 |
} |
236 | 236 |
|
237 | 237 |
my $currency = SL::DB::Manager::Currency->find_by( |
SL/DB/Helper/ZUGFeRD.pm | ||
---|---|---|
764 | 764 |
|
765 | 765 |
# Try to fill in AP account to book against |
766 | 766 |
my $ap_chart_id = $::instance_conf->get_ap_chart_id; |
767 |
my $ap_chart = SL::DB::Manager::Chart->find_by(id => $ap_chart_id); |
|
768 |
|
|
769 |
unless ( defined $ap_chart ) { |
|
767 |
my $ap_chart; |
|
768 |
unless ( defined $ap_chart_id ) { |
|
770 | 769 |
# If no default account is configured, just use the first AP account found. |
771 |
my $ap_charts = SL::DB::Manager::Chart->get_all(
|
|
770 |
($ap_chart) = @{SL::DB::Manager::Chart->get_all(
|
|
772 | 771 |
where => [ link => 'AP' ], |
773 | 772 |
sort_by => [ 'accno' ], |
774 |
); |
|
775 |
$ap_chart = ${$ap_charts}[0]; |
|
773 |
)}; |
|
774 |
} else { |
|
775 |
$ap_chart = SL::DB::Manager::Chart->find_by(id => $ap_chart_id); |
|
776 | 776 |
} |
777 | 777 |
|
778 | 778 |
my $currency = SL::DB::Manager::Currency->find_by( |
... | ... | |
799 | 799 |
my %template_params; |
800 | 800 |
my $template_ap = SL::DB::Manager::RecordTemplate->get_first(where => [vendor_id => $vendor->id]); |
801 | 801 |
if ($template_ap) { |
802 |
$template_params{globalproject_id} = $template_ap->globalproject_id;
|
|
802 |
$template_params{globalproject_id} = $template_ap->project_id; |
|
803 | 803 |
$template_params{payment_id} = $template_ap->payment_id; |
804 | 804 |
$template_params{department_id} = $template_ap->department_id; |
805 | 805 |
$template_params{ordnumber} = $template_ap->ordnumber; |
... | ... | |
813 | 813 |
taxzone_id => $vendor->taxzone_id, |
814 | 814 |
currency_id => $currency->id, |
815 | 815 |
direct_debit => $metadata{'direct_debit'}, |
816 |
invnumber => $invnumber, |
|
817 |
transdate => $metadata{transdate} || $today->to_kivitendo, |
|
818 |
duedate => $metadata{duedate} || $today->to_kivitendo, |
|
819 |
taxincluded => 0, |
|
820 |
intnotes => $intnotes, |
|
816 |
invnumber => $invnumber,
|
|
817 |
transdate => $metadata{transdate} || $today->to_kivitendo,
|
|
818 |
duedate => $metadata{duedate} || $today->to_kivitendo,
|
|
819 |
taxincluded => 0,
|
|
820 |
intnotes => $intnotes,
|
|
821 | 821 |
transactions => [], |
822 | 822 |
%template_params, |
823 | 823 |
); |
... | ... | |
825 | 825 |
$self->assign_attributes(%params); |
826 | 826 |
|
827 | 827 |
# parse items |
828 |
my $template_item; |
|
829 |
if ($template_ap && scalar @{$template_ap->items}) { |
|
830 |
my $template_item = $template_ap->items->[0]; |
|
831 |
} |
|
828 | 832 |
foreach my $i (@items) { |
829 | 833 |
my %item = %{$i}; |
830 | 834 |
|
831 | 835 |
my $net_total = $item{'subtotal'}; |
832 | 836 |
|
833 | 837 |
# set default values for items |
834 |
my ($tax, $chart, $project_id);
|
|
835 |
if ($template_ap) {
|
|
836 |
my $template_item = $template_ap->items->[0];
|
|
837 |
$tax = SL::DB::Tax->new(id => $template_item->tax_id)->load();
|
|
838 |
$chart = SL::DB::Chart->new(id => $template_item->chart_id)->load();
|
|
839 |
$project_id = $template_item->project_id;
|
|
838 |
my %line_params;
|
|
839 |
$line_params{amount} = $net_total;
|
|
840 |
if ($template_item) {
|
|
841 |
$line_params{tax_id} = $template_item->tax->id;
|
|
842 |
$line_params{chart} = $template_item->chart;
|
|
843 |
$line_params{project_id} = $template_item->project_id;
|
|
840 | 844 |
} else { |
841 |
|
|
842 |
my $tax_rate = $item{'tax_rate'}; |
|
843 |
$tax_rate /= 100 if $tax_rate > 1; # XML data is usually in percent |
|
844 |
|
|
845 |
$tax = first { $tax_rate == $_->rate } @{ $taxes }; |
|
846 |
$tax //= first { $active_taxkey->tax_id == $_->id } @{ $taxes }; |
|
847 |
$tax //= $taxes->[0]; |
|
848 |
|
|
849 |
$chart = $default_ap_amount_chart; |
|
845 |
my $tax_rate = $item{'tax_rate'}; |
|
846 |
$tax_rate /= 100 if $tax_rate > 1; # XML data is usually in percent |
|
847 |
my $tax = first { $tax_rate == $_->rate } @{ $taxes }; |
|
848 |
$tax //= first { $active_taxkey->tax_id == $_->id } @{ $taxes }; |
|
849 |
$tax //= $taxes->[0]; |
|
850 |
$line_params{tax_id} = $tax->id; |
|
851 |
$line_params{chart} = $default_ap_amount_chart; |
|
850 | 852 |
} |
851 | 853 |
|
852 |
my %line_params = ( |
|
853 |
amount => $net_total, |
|
854 |
tax_id => $tax->id, |
|
855 |
chart => $chart, |
|
856 |
); |
|
857 |
|
|
858 | 854 |
$self->add_ap_amount_row(%line_params); |
859 | 855 |
} |
860 | 856 |
$self->recalculate_amounts(); |
SL/Helper/EmailProcessing.pm | ||
---|---|---|
8 | 8 |
use XML::LibXML; |
9 | 9 |
|
10 | 10 |
use SL::ZUGFeRD; |
11 |
use SL::Locale::String qw(t8); |
|
11 | 12 |
|
12 | 13 |
use SL::DB::PurchaseInvoice; |
13 | 14 |
|
14 | 15 |
sub process_attachments { |
15 | 16 |
my ($self, $function_name, $email_journal, %params) = @_; |
16 | 17 |
|
17 |
unless ($self->can("process_attachments_$function_name")) { |
|
18 |
my $full_function_name = "process_attachments_$function_name"; |
|
19 |
unless ($self->can($full_function_name)) { |
|
18 | 20 |
croak "Function not implemented for: $function_name"; |
19 | 21 |
} |
20 |
$function_name = "process_attachments_$function_name"; |
|
21 | 22 |
|
22 |
my $processed_count = 0; |
|
23 |
my @processed_files; |
|
24 |
my @errors; |
|
23 | 25 |
foreach my $attachment (@{$email_journal->attachments_sorted}) { |
24 |
my $processed = $self->$function_name($email_journal, $attachment, %params); |
|
25 |
$processed_count += $processed; |
|
26 |
my $attachment_name = $attachment->name; |
|
27 |
my $error = $self->$full_function_name($email_journal, $attachment, %params); |
|
28 |
if ($error) { |
|
29 |
push @errors, "$attachment_name: $error."; |
|
30 |
} else { |
|
31 |
push @processed_files, $attachment_name; |
|
32 |
} |
|
26 | 33 |
} |
27 |
return $processed_count; |
|
34 |
my $extended_status = t8("Processed attachments with function '#1':", $function_name); |
|
35 |
if (scalar @processed_files) { |
|
36 |
$extended_status .= "\n" . t8("Processed successfully: ") |
|
37 |
. join(', ', @processed_files); |
|
38 |
} |
|
39 |
if (scalar @errors) { |
|
40 |
$extended_status .= "\n" . t8("Errors while processing: ") |
|
41 |
. "\n" . join("\n", @errors); |
|
42 |
} |
|
43 |
unless (scalar @processed_files || scalar @errors) { |
|
44 |
$extended_status .= "\n" . t8("No attachments."); |
|
45 |
} |
|
46 |
$email_journal->extended_status( |
|
47 |
join "\n", $email_journal->extended_status, $extended_status |
|
48 |
); |
|
49 |
$email_journal->save; |
|
50 |
return scalar @processed_files; |
|
28 | 51 |
} |
29 | 52 |
|
30 | 53 |
sub can_function { |
... | ... | |
36 | 59 |
my ($self, $email_journal, $attachment, %params) = @_; |
37 | 60 |
|
38 | 61 |
my $content = $attachment->content; # scalar ref |
39 |
my $name = $attachment->name; |
|
40 | 62 |
|
41 |
return 0 unless $content =~ m/^%PDF|<\?xml/;
|
|
63 |
return t8("Not a PDF or XML file") unless $content =~ m/^%PDF|<\?xml/;
|
|
42 | 64 |
|
43 | 65 |
my %res; |
44 | 66 |
if ( $content =~ m/^%PDF/ ) { |
... | ... | |
48 | 70 |
} |
49 | 71 |
|
50 | 72 |
unless ($res{'result'} == SL::ZUGFeRD::RES_OK()) { |
51 |
my $error = $res{'message'}; |
|
52 |
$email_journal->extended_status( |
|
53 |
join "\n", $email_journal->extended_status, |
|
54 |
"Error processing ZUGFeRD attachment $name: $error" |
|
55 |
)->save; |
|
56 |
return 0; |
|
73 |
# my $error = $res{'message'}; # technical error |
|
74 |
my $error = t8('No vaild Factur-X/ZUGFeRD file'); |
|
75 |
return $error; |
|
57 | 76 |
} |
58 | 77 |
|
59 | 78 |
my $purchase_invoice; |
... | ... | |
62 | 81 |
1; |
63 | 82 |
} or do { |
64 | 83 |
my $error = $@; |
65 |
$email_journal->update_attributes( |
|
66 |
extended_status => |
|
67 |
join "\n", $email_journal->extended_status, |
|
68 |
"Error processing ZUGFeRD attachment $name: $error" |
|
69 |
); |
|
70 |
return 0; |
|
84 |
return $error; |
|
71 | 85 |
}; |
72 | 86 |
|
73 | 87 |
$self->_add_attachment_to_record($email_journal, $attachment, $purchase_invoice); |
74 | 88 |
|
75 |
return 1;
|
|
89 |
return 0;
|
|
76 | 90 |
} |
77 | 91 |
|
78 | 92 |
sub _add_attachment_to_record { |
locale/de/all | ||
---|---|---|
852 | 852 |
'Could not create new project #1' => 'Neues Projekt #1 kann nicht angelegt werden', |
853 | 853 |
'Could not extract Factur-X/ZUGFeRD data, data and error message:' => 'Konnte keine Factur-X-/ZUGFeRD-Daten extrahieren, folgende Fehlermeldung und das PDF:', |
854 | 854 |
'Could not find an entry for this part in the pricegroup.' => 'Konnte keinen Eintrag für diesen Artikel in der Preisgruppe finden.', |
855 |
'Could not find email import(s): ' => 'Konnte folgende E-Mailimport(e) nicht finden: ', |
|
855 | 856 |
'Could not load class #1 (#2): "#3"' => 'Konnte Klasse #1 (#2) nicht laden: "#3"', |
856 | 857 |
'Could not load class #1, #2' => 'Konnte Klasse #1 nicht laden: "#2"', |
857 | 858 |
'Could not load employee' => 'Konnte Benutzer nicht laden', |
... | ... | |
1189 | 1190 |
'Delete text block' => 'Textblock löschen', |
1190 | 1191 |
'Delete transaction' => 'Buchung löschen', |
1191 | 1192 |
'Deleted' => 'Gelöscht', |
1193 |
'Deleted email import(s): ' => 'Gelöschte E-Mailimport(e): ', |
|
1192 | 1194 |
'Deleting this type of record has been disabled in the configuration.' => 'Das Löschen von dieser Belegart ist in der Konfiguration deaktiviert.', |
1193 | 1195 |
'Delivered' => 'Geliefert', |
1194 | 1196 |
'Delivered amount' => 'Gelieferter Betrag', |
... | ... | |
1573 | 1575 |
'Error when saving: #1' => 'Fehler beim Speichern: #1', |
1574 | 1576 |
'Error while applying year-end bookings!' => 'Fehler beim Durchführen der Abschlußbuchungen!', |
1575 | 1577 |
'Error while creating project with project number of new order number, project number #1 already exists!' => 'Fehler beim Erstellen eines Projekts mit der Projektnummer der neuen Auftragsnummer, Projektnummer #1 existiert bereits!', |
1578 |
'Error while processing email journal (\'#1\') attachments with \'#2\': ' => 'Fehler beim Verarbeiten der E-Mail-Journals (\'#1\') Anhänge mit \'#2\': ', |
|
1576 | 1579 |
'Error while saving shop order #1. DB Error #2. Generic exception #3.' => 'Fehler beim Speichern der Shop-Bestellung #1. DB Fehler #2. Genereller Fehler #3.', |
1577 | 1580 |
'Error with default taxzone' => 'Ungültige Standardsteuerzone', |
1578 | 1581 |
'Error!' => 'Fehler!', |
... | ... | |
1668 | 1671 |
'Errors during printing:' => 'Druckfehler:', |
1669 | 1672 |
'Errors in GL transaction:' => 'Fehler in Dialogbuchung:', |
1670 | 1673 |
'Errors while deleting record:' => 'Fehler beim Speichern des Belegs:', |
1674 |
'Errors while processing: ' => 'Fehler bei Verarbeitung: ', |
|
1671 | 1675 |
'Errors: #1' => 'Fehler: #1', |
1672 | 1676 |
'Ertrag' => 'Ertrag', |
1673 | 1677 |
'Ertrag prozentual' => 'Ertrag prozentual', |
... | ... | |
2487 | 2491 |
'No articles have been added yet.' => 'Es wurden noch keine Artikel hinzugefügt.', |
2488 | 2492 |
'No assembly has been selected yet.' => 'Es wurde noch kein Erzeugnis ausgewahlt.', |
2489 | 2493 |
'No attachment' => 'Kein Anhang', |
2494 |
'No attachments.' => 'Keine Anhänge', |
|
2490 | 2495 |
'No background job has been created yet.' => 'Es wurden noch keine Hintergrund-Jobs angelegt.', |
2491 | 2496 |
'No bank account chosen!' => 'Kein Bankkonto ausgewählt!', |
2492 | 2497 |
'No bank account configured for bank code/BIC #1, account number/IBAN #2.' => 'Kein Bankkonto für BLZ/BIC #1, Kontonummer/IBAN #2 konfiguriert.', |
... | ... | |
2572 | 2577 |
'No transactions yet.' => 'Bisher keine Buchungen.', |
2573 | 2578 |
'No transfers were executed in this export.' => 'In diesem SEPA-Export wurden keine Überweisungen ausgeführt.', |
2574 | 2579 |
'No users have been created yet.' => 'Es wurden noch keine Benutzer angelegt.', |
2580 |
'No vaild Factur-X/ZUGFeRD file' => 'Keine valide Factur-X/ZUGFeRD Datei', |
|
2575 | 2581 |
'No valid invoice(s) found' => 'Keine gültige(n) Rechnung(en) gefunden.', |
2576 | 2582 |
'No valid number entered for pricegroup "#1".' => 'Für Preisgruppe "#1" wurde keine gültige Nummer eingegeben.', |
2577 | 2583 |
'No vendor available' => 'Kein Lieferant verfügbar', |
... | ... | |
2592 | 2598 |
'Normalize Customer / Vendor names' => 'Normalisierung Kunden- / Lieferantennamen', |
2593 | 2599 |
'Normalize part description and part notes' => 'Normalisierung Artikelbeschreibung und Artikellangtext (Bemerkung)', |
2594 | 2600 |
'Not Discountable' => 'Nicht rabattierfähig', |
2601 |
'Not a PDF or XML file' => 'Keine PDF oder XML Datei', |
|
2595 | 2602 |
'Not delivered' => 'Nicht geliefert', |
2596 | 2603 |
'Not delivered amount' => 'nicht gelieferte Menge', |
2597 | 2604 |
'Not done yet' => 'Noch nicht fertig', |
... | ... | |
3036 | 3043 |
'Private E-mail' => 'Private E-Mail', |
3037 | 3044 |
'Private Phone' => 'Privates Tel.', |
3038 | 3045 |
'Problem' => 'Problem', |
3046 |
'Processed attachments with function \'#1\':' => 'Anhänge verarbeitet mit Funktion \'#1\':', |
|
3047 |
'Processed successfully: ' => 'Erfolgreich verarbeitet: ', |
|
3039 | 3048 |
'Produce' => 'Fertigen', |
3040 | 3049 |
'Produce Assembly' => 'Erzeugnis fertigen', |
3041 | 3050 |
'Produce Assembly Configuration' => 'Konfiguration für Erzeugnis fertigen', |
locale/en/all | ||
---|---|---|
852 | 852 |
'Could not create new project #1' => '', |
853 | 853 |
'Could not extract Factur-X/ZUGFeRD data, data and error message:' => '', |
854 | 854 |
'Could not find an entry for this part in the pricegroup.' => '', |
855 |
'Could not find email import(s): ' => '', |
|
855 | 856 |
'Could not load class #1 (#2): "#3"' => '', |
856 | 857 |
'Could not load class #1, #2' => '', |
857 | 858 |
'Could not load employee' => '', |
... | ... | |
1189 | 1190 |
'Delete text block' => '', |
1190 | 1191 |
'Delete transaction' => '', |
1191 | 1192 |
'Deleted' => '', |
1193 |
'Deleted email import(s): ' => '', |
|
1192 | 1194 |
'Deleting this type of record has been disabled in the configuration.' => '', |
1193 | 1195 |
'Delivered' => '', |
1194 | 1196 |
'Delivered amount' => '', |
... | ... | |
1573 | 1575 |
'Error when saving: #1' => '', |
1574 | 1576 |
'Error while applying year-end bookings!' => '', |
1575 | 1577 |
'Error while creating project with project number of new order number, project number #1 already exists!' => '', |
1578 |
'Error while processing email journal (\'#1\') attachments with \'#2\': ' => '', |
|
1576 | 1579 |
'Error while saving shop order #1. DB Error #2. Generic exception #3.' => '', |
1577 | 1580 |
'Error with default taxzone' => '', |
1578 | 1581 |
'Error!' => '', |
... | ... | |
1668 | 1671 |
'Errors during printing:' => '', |
1669 | 1672 |
'Errors in GL transaction:' => '', |
1670 | 1673 |
'Errors while deleting record:' => '', |
1674 |
'Errors while processing: ' => '', |
|
1671 | 1675 |
'Errors: #1' => '', |
1672 | 1676 |
'Ertrag' => '', |
1673 | 1677 |
'Ertrag prozentual' => '', |
... | ... | |
2486 | 2490 |
'No articles have been added yet.' => '', |
2487 | 2491 |
'No assembly has been selected yet.' => '', |
2488 | 2492 |
'No attachment' => '', |
2493 |
'No attachments.' => '', |
|
2489 | 2494 |
'No background job has been created yet.' => '', |
2490 | 2495 |
'No bank account chosen!' => '', |
2491 | 2496 |
'No bank account configured for bank code/BIC #1, account number/IBAN #2.' => '', |
... | ... | |
2571 | 2576 |
'No transactions yet.' => '', |
2572 | 2577 |
'No transfers were executed in this export.' => '', |
2573 | 2578 |
'No users have been created yet.' => '', |
2579 |
'No vaild Factur-X/ZUGFeRD file' => '', |
|
2574 | 2580 |
'No valid invoice(s) found' => '', |
2575 | 2581 |
'No valid number entered for pricegroup "#1".' => '', |
2576 | 2582 |
'No vendor available' => '', |
... | ... | |
2591 | 2597 |
'Normalize Customer / Vendor names' => '', |
2592 | 2598 |
'Normalize part description and part notes' => '', |
2593 | 2599 |
'Not Discountable' => '', |
2600 |
'Not a PDF or XML file' => '', |
|
2594 | 2601 |
'Not delivered' => '', |
2595 | 2602 |
'Not delivered amount' => '', |
2596 | 2603 |
'Not done yet' => '', |
... | ... | |
3035 | 3042 |
'Private E-mail' => '', |
3036 | 3043 |
'Private Phone' => '', |
3037 | 3044 |
'Problem' => '', |
3045 |
'Processed attachments with function \'#1\':' => '', |
|
3046 |
'Processed successfully: ' => '', |
|
3038 | 3047 |
'Produce' => '', |
3039 | 3048 |
'Produce Assembly' => '', |
3040 | 3049 |
'Produce Assembly Configuration' => '', |
Auch abrufbar als: Unified diff
BJ:ImportRecordEmails: Status vom auto. ZUGFeRD-Import in erw. Status