Revision b8dfb10a
Von Tamino Steinert vor 12 Monaten hinzugefügt
SL/BackgroundJob/ImportRecordEmails.pm | ||
---|---|---|
use SL::DB::Manager::EmailImport;
|
||
use SL::Helper::EmailProcessing;
|
||
use SL::Presenter::Tag qw(link_tag);
|
||
use SL::Locale::String qw(t8);
|
||
|
||
use Params::Validate qw(:all);
|
||
use List::MoreUtils qw(any);
|
||
... | ... | |
# , $email_journal->id
|
||
# );
|
||
my $email_journal_id = $email_journal->id;
|
||
$result .= "Error while processing email journal $email_journal_id attachments with $function_name: $@";
|
||
$result .= t8("Error while processing email journal ('#1') attachments with '#2': ", $email_journal_id, $function_name) . $@ . ".";
|
||
};
|
||
}
|
||
if ($created_records && $config->{processed_imap_flag}) {
|
||
... | ... | |
|
||
my $result = "";
|
||
|
||
$result .= "Deleted email import(s): "
|
||
$result .= t8("Deleted email import(s): ")
|
||
. join(', ', @deleted_email_import_ids) . "."
|
||
if scalar @deleted_email_import_ids;
|
||
|
||
$result .= "Could not find email import(s): "
|
||
$result .= t8("Could not find email import(s): ")
|
||
. join(', ', @not_found_email_import_ids) . " for deletion."
|
||
if scalar @not_found_email_import_ids;
|
||
|
SL/Controller/EmailJournal.pm | ||
---|---|---|
my $record_type = $::form->{"record_type"};
|
||
die "ZUGFeRD-Import only implemented for ap transaction templates" unless $record_type == 'ap_transaction';
|
||
|
||
my $attachment_id = $::form->{attachment_id};
|
||
my $attachment_id = $::form->{attachment_id};
|
||
|
||
my $form_defaults;
|
||
if ($attachment_id) {
|
SL/Controller/ZUGFeRD.pm | ||
---|---|---|
|
||
unless ( defined $ap_chart_id ) {
|
||
# If no default account is configured, just use the first AP account found.
|
||
my $ap_chart = SL::DB::Manager::Chart->get_all(
|
||
my ($ap_chart) = @{SL::DB::Manager::Chart->get_all(
|
||
where => [ link => 'AP' ],
|
||
sort_by => [ 'accno' ],
|
||
);
|
||
$ap_chart_id = ${$ap_chart}[0]->id;
|
||
)};
|
||
$ap_chart_id = $ap_chart->id;
|
||
}
|
||
|
||
my $currency = SL::DB::Manager::Currency->find_by(
|
SL/DB/Helper/ZUGFeRD.pm | ||
---|---|---|
|
||
# Try to fill in AP account to book against
|
||
my $ap_chart_id = $::instance_conf->get_ap_chart_id;
|
||
my $ap_chart = SL::DB::Manager::Chart->find_by(id => $ap_chart_id);
|
||
|
||
unless ( defined $ap_chart ) {
|
||
my $ap_chart;
|
||
unless ( defined $ap_chart_id ) {
|
||
# If no default account is configured, just use the first AP account found.
|
||
my $ap_charts = SL::DB::Manager::Chart->get_all(
|
||
($ap_chart) = @{SL::DB::Manager::Chart->get_all(
|
||
where => [ link => 'AP' ],
|
||
sort_by => [ 'accno' ],
|
||
);
|
||
$ap_chart = ${$ap_charts}[0];
|
||
)};
|
||
} else {
|
||
$ap_chart = SL::DB::Manager::Chart->find_by(id => $ap_chart_id);
|
||
}
|
||
|
||
my $currency = SL::DB::Manager::Currency->find_by(
|
||
... | ... | |
my %template_params;
|
||
my $template_ap = SL::DB::Manager::RecordTemplate->get_first(where => [vendor_id => $vendor->id]);
|
||
if ($template_ap) {
|
||
$template_params{globalproject_id} = $template_ap->globalproject_id;
|
||
$template_params{globalproject_id} = $template_ap->project_id;
|
||
$template_params{payment_id} = $template_ap->payment_id;
|
||
$template_params{department_id} = $template_ap->department_id;
|
||
$template_params{ordnumber} = $template_ap->ordnumber;
|
||
... | ... | |
taxzone_id => $vendor->taxzone_id,
|
||
currency_id => $currency->id,
|
||
direct_debit => $metadata{'direct_debit'},
|
||
invnumber => $invnumber,
|
||
transdate => $metadata{transdate} || $today->to_kivitendo,
|
||
duedate => $metadata{duedate} || $today->to_kivitendo,
|
||
taxincluded => 0,
|
||
intnotes => $intnotes,
|
||
invnumber => $invnumber,
|
||
transdate => $metadata{transdate} || $today->to_kivitendo,
|
||
duedate => $metadata{duedate} || $today->to_kivitendo,
|
||
taxincluded => 0,
|
||
intnotes => $intnotes,
|
||
transactions => [],
|
||
%template_params,
|
||
);
|
||
... | ... | |
$self->assign_attributes(%params);
|
||
|
||
# parse items
|
||
my $template_item;
|
||
if ($template_ap && scalar @{$template_ap->items}) {
|
||
my $template_item = $template_ap->items->[0];
|
||
}
|
||
foreach my $i (@items) {
|
||
my %item = %{$i};
|
||
|
||
my $net_total = $item{'subtotal'};
|
||
|
||
# set default values for items
|
||
my ($tax, $chart, $project_id);
|
||
if ($template_ap) {
|
||
my $template_item = $template_ap->items->[0];
|
||
$tax = SL::DB::Tax->new(id => $template_item->tax_id)->load();
|
||
$chart = SL::DB::Chart->new(id => $template_item->chart_id)->load();
|
||
$project_id = $template_item->project_id;
|
||
my %line_params;
|
||
$line_params{amount} = $net_total;
|
||
if ($template_item) {
|
||
$line_params{tax_id} = $template_item->tax->id;
|
||
$line_params{chart} = $template_item->chart;
|
||
$line_params{project_id} = $template_item->project_id;
|
||
} else {
|
||
|
||
my $tax_rate = $item{'tax_rate'};
|
||
$tax_rate /= 100 if $tax_rate > 1; # XML data is usually in percent
|
||
|
||
$tax = first { $tax_rate == $_->rate } @{ $taxes };
|
||
$tax //= first { $active_taxkey->tax_id == $_->id } @{ $taxes };
|
||
$tax //= $taxes->[0];
|
||
|
||
$chart = $default_ap_amount_chart;
|
||
my $tax_rate = $item{'tax_rate'};
|
||
$tax_rate /= 100 if $tax_rate > 1; # XML data is usually in percent
|
||
my $tax = first { $tax_rate == $_->rate } @{ $taxes };
|
||
$tax //= first { $active_taxkey->tax_id == $_->id } @{ $taxes };
|
||
$tax //= $taxes->[0];
|
||
$line_params{tax_id} = $tax->id;
|
||
$line_params{chart} = $default_ap_amount_chart;
|
||
}
|
||
|
||
my %line_params = (
|
||
amount => $net_total,
|
||
tax_id => $tax->id,
|
||
chart => $chart,
|
||
);
|
||
|
||
$self->add_ap_amount_row(%line_params);
|
||
}
|
||
$self->recalculate_amounts();
|
SL/Helper/EmailProcessing.pm | ||
---|---|---|
use XML::LibXML;
|
||
|
||
use SL::ZUGFeRD;
|
||
use SL::Locale::String qw(t8);
|
||
|
||
use SL::DB::PurchaseInvoice;
|
||
|
||
sub process_attachments {
|
||
my ($self, $function_name, $email_journal, %params) = @_;
|
||
|
||
unless ($self->can("process_attachments_$function_name")) {
|
||
my $full_function_name = "process_attachments_$function_name";
|
||
unless ($self->can($full_function_name)) {
|
||
croak "Function not implemented for: $function_name";
|
||
}
|
||
$function_name = "process_attachments_$function_name";
|
||
|
||
my $processed_count = 0;
|
||
my @processed_files;
|
||
my @errors;
|
||
foreach my $attachment (@{$email_journal->attachments_sorted}) {
|
||
my $processed = $self->$function_name($email_journal, $attachment, %params);
|
||
$processed_count += $processed;
|
||
my $attachment_name = $attachment->name;
|
||
my $error = $self->$full_function_name($email_journal, $attachment, %params);
|
||
if ($error) {
|
||
push @errors, "$attachment_name: $error.";
|
||
} else {
|
||
push @processed_files, $attachment_name;
|
||
}
|
||
}
|
||
return $processed_count;
|
||
my $extended_status = t8("Processed attachments with function '#1':", $function_name);
|
||
if (scalar @processed_files) {
|
||
$extended_status .= "\n" . t8("Processed successfully: ")
|
||
. join(', ', @processed_files);
|
||
}
|
||
if (scalar @errors) {
|
||
$extended_status .= "\n" . t8("Errors while processing: ")
|
||
. "\n" . join("\n", @errors);
|
||
}
|
||
unless (scalar @processed_files || scalar @errors) {
|
||
$extended_status .= "\n" . t8("No attachments.");
|
||
}
|
||
$email_journal->extended_status(
|
||
join "\n", $email_journal->extended_status, $extended_status
|
||
);
|
||
$email_journal->save;
|
||
return scalar @processed_files;
|
||
}
|
||
|
||
sub can_function {
|
||
... | ... | |
my ($self, $email_journal, $attachment, %params) = @_;
|
||
|
||
my $content = $attachment->content; # scalar ref
|
||
my $name = $attachment->name;
|
||
|
||
return 0 unless $content =~ m/^%PDF|<\?xml/;
|
||
return t8("Not a PDF or XML file") unless $content =~ m/^%PDF|<\?xml/;
|
||
|
||
my %res;
|
||
if ( $content =~ m/^%PDF/ ) {
|
||
... | ... | |
}
|
||
|
||
unless ($res{'result'} == SL::ZUGFeRD::RES_OK()) {
|
||
my $error = $res{'message'};
|
||
$email_journal->extended_status(
|
||
join "\n", $email_journal->extended_status,
|
||
"Error processing ZUGFeRD attachment $name: $error"
|
||
)->save;
|
||
return 0;
|
||
# my $error = $res{'message'}; # technical error
|
||
my $error = t8('No vaild Factur-X/ZUGFeRD file');
|
||
return $error;
|
||
}
|
||
|
||
my $purchase_invoice;
|
||
... | ... | |
1;
|
||
} or do {
|
||
my $error = $@;
|
||
$email_journal->update_attributes(
|
||
extended_status =>
|
||
join "\n", $email_journal->extended_status,
|
||
"Error processing ZUGFeRD attachment $name: $error"
|
||
);
|
||
return 0;
|
||
return $error;
|
||
};
|
||
|
||
$self->_add_attachment_to_record($email_journal, $attachment, $purchase_invoice);
|
||
|
||
return 1;
|
||
return 0;
|
||
}
|
||
|
||
sub _add_attachment_to_record {
|
locale/de/all | ||
---|---|---|
'Could not create new project #1' => 'Neues Projekt #1 kann nicht angelegt werden',
|
||
'Could not extract Factur-X/ZUGFeRD data, data and error message:' => 'Konnte keine Factur-X-/ZUGFeRD-Daten extrahieren, folgende Fehlermeldung und das PDF:',
|
||
'Could not find an entry for this part in the pricegroup.' => 'Konnte keinen Eintrag für diesen Artikel in der Preisgruppe finden.',
|
||
'Could not find email import(s): ' => 'Konnte folgende E-Mailimport(e) nicht finden: ',
|
||
'Could not load class #1 (#2): "#3"' => 'Konnte Klasse #1 (#2) nicht laden: "#3"',
|
||
'Could not load class #1, #2' => 'Konnte Klasse #1 nicht laden: "#2"',
|
||
'Could not load employee' => 'Konnte Benutzer nicht laden',
|
||
... | ... | |
'Delete text block' => 'Textblock löschen',
|
||
'Delete transaction' => 'Buchung löschen',
|
||
'Deleted' => 'Gelöscht',
|
||
'Deleted email import(s): ' => 'Gelöschte E-Mailimport(e): ',
|
||
'Deleting this type of record has been disabled in the configuration.' => 'Das Löschen von dieser Belegart ist in der Konfiguration deaktiviert.',
|
||
'Delivered' => 'Geliefert',
|
||
'Delivered amount' => 'Gelieferter Betrag',
|
||
... | ... | |
'Error when saving: #1' => 'Fehler beim Speichern: #1',
|
||
'Error while applying year-end bookings!' => 'Fehler beim Durchführen der Abschlußbuchungen!',
|
||
'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!',
|
||
'Error while processing email journal (\'#1\') attachments with \'#2\': ' => 'Fehler beim Verarbeiten der E-Mail-Journals (\'#1\') Anhänge mit \'#2\': ',
|
||
'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.',
|
||
'Error with default taxzone' => 'Ungültige Standardsteuerzone',
|
||
'Error!' => 'Fehler!',
|
||
... | ... | |
'Errors during printing:' => 'Druckfehler:',
|
||
'Errors in GL transaction:' => 'Fehler in Dialogbuchung:',
|
||
'Errors while deleting record:' => 'Fehler beim Speichern des Belegs:',
|
||
'Errors while processing: ' => 'Fehler bei Verarbeitung: ',
|
||
'Errors: #1' => 'Fehler: #1',
|
||
'Ertrag' => 'Ertrag',
|
||
'Ertrag prozentual' => 'Ertrag prozentual',
|
||
... | ... | |
'No articles have been added yet.' => 'Es wurden noch keine Artikel hinzugefügt.',
|
||
'No assembly has been selected yet.' => 'Es wurde noch kein Erzeugnis ausgewahlt.',
|
||
'No attachment' => 'Kein Anhang',
|
||
'No attachments.' => 'Keine Anhänge',
|
||
'No background job has been created yet.' => 'Es wurden noch keine Hintergrund-Jobs angelegt.',
|
||
'No bank account chosen!' => 'Kein Bankkonto ausgewählt!',
|
||
'No bank account configured for bank code/BIC #1, account number/IBAN #2.' => 'Kein Bankkonto für BLZ/BIC #1, Kontonummer/IBAN #2 konfiguriert.',
|
||
... | ... | |
'No transactions yet.' => 'Bisher keine Buchungen.',
|
||
'No transfers were executed in this export.' => 'In diesem SEPA-Export wurden keine Überweisungen ausgeführt.',
|
||
'No users have been created yet.' => 'Es wurden noch keine Benutzer angelegt.',
|
||
'No vaild Factur-X/ZUGFeRD file' => 'Keine valide Factur-X/ZUGFeRD Datei',
|
||
'No valid invoice(s) found' => 'Keine gültige(n) Rechnung(en) gefunden.',
|
||
'No valid number entered for pricegroup "#1".' => 'Für Preisgruppe "#1" wurde keine gültige Nummer eingegeben.',
|
||
'No vendor available' => 'Kein Lieferant verfügbar',
|
||
... | ... | |
'Normalize Customer / Vendor names' => 'Normalisierung Kunden- / Lieferantennamen',
|
||
'Normalize part description and part notes' => 'Normalisierung Artikelbeschreibung und Artikellangtext (Bemerkung)',
|
||
'Not Discountable' => 'Nicht rabattierfähig',
|
||
'Not a PDF or XML file' => 'Keine PDF oder XML Datei',
|
||
'Not delivered' => 'Nicht geliefert',
|
||
'Not delivered amount' => 'nicht gelieferte Menge',
|
||
'Not done yet' => 'Noch nicht fertig',
|
||
... | ... | |
'Private E-mail' => 'Private E-Mail',
|
||
'Private Phone' => 'Privates Tel.',
|
||
'Problem' => 'Problem',
|
||
'Processed attachments with function \'#1\':' => 'Anhänge verarbeitet mit Funktion \'#1\':',
|
||
'Processed successfully: ' => 'Erfolgreich verarbeitet: ',
|
||
'Produce' => 'Fertigen',
|
||
'Produce Assembly' => 'Erzeugnis fertigen',
|
||
'Produce Assembly Configuration' => 'Konfiguration für Erzeugnis fertigen',
|
locale/en/all | ||
---|---|---|
'Could not create new project #1' => '',
|
||
'Could not extract Factur-X/ZUGFeRD data, data and error message:' => '',
|
||
'Could not find an entry for this part in the pricegroup.' => '',
|
||
'Could not find email import(s): ' => '',
|
||
'Could not load class #1 (#2): "#3"' => '',
|
||
'Could not load class #1, #2' => '',
|
||
'Could not load employee' => '',
|
||
... | ... | |
'Delete text block' => '',
|
||
'Delete transaction' => '',
|
||
'Deleted' => '',
|
||
'Deleted email import(s): ' => '',
|
||
'Deleting this type of record has been disabled in the configuration.' => '',
|
||
'Delivered' => '',
|
||
'Delivered amount' => '',
|
||
... | ... | |
'Error when saving: #1' => '',
|
||
'Error while applying year-end bookings!' => '',
|
||
'Error while creating project with project number of new order number, project number #1 already exists!' => '',
|
||
'Error while processing email journal (\'#1\') attachments with \'#2\': ' => '',
|
||
'Error while saving shop order #1. DB Error #2. Generic exception #3.' => '',
|
||
'Error with default taxzone' => '',
|
||
'Error!' => '',
|
||
... | ... | |
'Errors during printing:' => '',
|
||
'Errors in GL transaction:' => '',
|
||
'Errors while deleting record:' => '',
|
||
'Errors while processing: ' => '',
|
||
'Errors: #1' => '',
|
||
'Ertrag' => '',
|
||
'Ertrag prozentual' => '',
|
||
... | ... | |
'No articles have been added yet.' => '',
|
||
'No assembly has been selected yet.' => '',
|
||
'No attachment' => '',
|
||
'No attachments.' => '',
|
||
'No background job has been created yet.' => '',
|
||
'No bank account chosen!' => '',
|
||
'No bank account configured for bank code/BIC #1, account number/IBAN #2.' => '',
|
||
... | ... | |
'No transactions yet.' => '',
|
||
'No transfers were executed in this export.' => '',
|
||
'No users have been created yet.' => '',
|
||
'No vaild Factur-X/ZUGFeRD file' => '',
|
||
'No valid invoice(s) found' => '',
|
||
'No valid number entered for pricegroup "#1".' => '',
|
||
'No vendor available' => '',
|
||
... | ... | |
'Normalize Customer / Vendor names' => '',
|
||
'Normalize part description and part notes' => '',
|
||
'Not Discountable' => '',
|
||
'Not a PDF or XML file' => '',
|
||
'Not delivered' => '',
|
||
'Not delivered amount' => '',
|
||
'Not done yet' => '',
|
||
... | ... | |
'Private E-mail' => '',
|
||
'Private Phone' => '',
|
||
'Problem' => '',
|
||
'Processed attachments with function \'#1\':' => '',
|
||
'Processed successfully: ' => '',
|
||
'Produce' => '',
|
||
'Produce Assembly' => '',
|
||
'Produce Assembly Configuration' => '',
|
Auch abrufbar als: Unified diff
BJ:ImportRecordEmails: Status vom auto. ZUGFeRD-Import in erw. Status