Projekt

Allgemein

Profil

« Zurück | Weiter » 

Revision 7e62d6a2

Von Tamino Steinert vor etwa 1 Jahr hinzugefügt

  • ID 7e62d6a218ea47f8e1878791160c80ff16b71b22
  • Vorgänger 70b4058b
  • Nachfolger ca5695e4

ImportRecordEmails: BJ zum importieren von Emails als Beleg-Grundlage

Verallgemeinerung und Ersetzung von BJ "ImportPurchaseInvoiceEmails"

Unterschiede anzeigen:

SL/BackgroundJob/ImportPurchaseInvoiceEmails.pm
package SL::BackgroundJob::ImportPurchaseInvoiceEmails;
use strict;
use warnings;
use parent qw(SL::BackgroundJob::Base);
use SL::IMAPClient;
use SL::DB::Manager::EmailImport;
sub sync_email_folder {
my ($self) = @_;
my $email_import = $self->{imap_client}->update_emails_from_folder(
$self->{folder},
{
email_journal => {
extended_status => 'purchase_invoice_import',
},
}
);
$self->{email_import} = $email_import;
return unless $email_import;
return "Created email import: " . $email_import->id;
}
sub delete_email_imports {
my ($self) = @_;
my $job_obj = $self->{job_obj};
my $email_import_ids_to_delete =
$job_obj->data_as_hash->{email_import_ids_to_delete} || [];
my @deleted_email_imports_ids;
foreach my $email_import_id (@$email_import_ids_to_delete) {
my $email_import = SL::DB::Manager::EmailImport->find_by(id => $email_import_id);
next unless $email_import;
$email_import->delete(cascade => 1);
push @deleted_email_imports_ids, $email_import_id;
}
return unless @deleted_email_imports_ids;
return "Deleted email import(s): " . join(', ', @deleted_email_imports_ids);
}
sub clean_up_imported_emails {
my ($self) = @_;
$self->{imap_client}->clean_up_imported_emails_from_folder($self->{folder});
return "Cleaned imported emails";
}
sub process_imported_purchase_invoice_emails {
my ($self) = @_;
return unless $self->{email_import};
my $emails = $self->{email_import}->email_journals;
foreach my $email (@$emails) {
$email->process_attachments_as_purchase_invoices();
}
return "Processed imported emails";
}
sub run {
my ($self, $job_obj) = @_;
$self->{job_obj} = $job_obj;
$self->{imap_client} = SL::IMAPClient->new(%{$::lx_office_conf{purchase_invoice_emails_imap}});
$self->{folder} = $self->{job_obj}->data_as_hash->{folder};
my @results;
push @results, $self->delete_email_imports();
push @results, $self->sync_email_folder();
if ($self->{job_obj}->data_as_hash->{clean_up_imported_emails}) {
push @results, $self->clean_up_imported_emails();
}
if ($self->{job_obj}->data_as_hash->{process_imported_purchase_invoice_emails}) {
push @results, $self->process_imported_purchase_invoice_emails();
}
return join(". ", grep { $_ ne ''} @results);
}
1;
__END__
=encoding utf8
=head1 NAME
SL::BackgroundJob::ImportPurchaseInvoiceEmails - Background job for syncing
emails from a folder for purchase invoices .
=head1 SYNOPSIS
This background job is used to sync emails from a folder with purchase invoices.
It can be used to sync emails from a folder on a regular basis for multiple
folders . The folder to sync is specified in the data field 'folder' of the
background job, by default the folder 'base_folder' from
[purchase_invoice_emails_imap] in kivitendo.conf is used. Sub folders are
separated by a forward slash, e.g. 'INBOX/Archive'. Subfolders are not synced.
It can also remove emails from the folder which have been imported into kivitendo
by setting the data field 'clean_up_imported_emails' to a true value.
=head1 BUGS
Nothing here yet.
=head1 AUTHOR
Tamino Steinert E<lt>tamino.steinert@tamino.stE<gt>
=cut
SL/BackgroundJob/ImportRecordEmails.pm
package SL::BackgroundJob::ImportRecordEmails;
use strict;
use warnings;
use parent qw(SL::BackgroundJob::Base);
use SL::IMAPClient;
use SL::DB::Manager::EmailImport;
use SL::Helper::EmailProcessing;
use SL::Presenter::Tag qw(link_tag);
use SL::Presenter::EscapedText qw(escape);
sub sync_record_email_folder {
my ($self, $imap_client, $record_type, $folder) = @_;
my $email_import = $imap_client->update_emails_from_folder(
$folder,
{
email_journal => {
# TODO: status => 'record_import',
extended_status => 'record_import_' . "$record_type",
},
}
);
return $email_import;
}
sub delete_email_imports {
my ($self) = @_;
my $job_obj = $self->{job_obj};
my $email_import_ids_to_delete =
$job_obj->data_as_hash->{email_import_ids_to_delete} || [];
my @deleted_email_imports_ids;
foreach my $email_import_id (@$email_import_ids_to_delete) {
my $email_import = SL::DB::Manager::EmailImport->find_by(id => $email_import_id);
next unless $email_import;
$email_import->delete(cascade => 1);
push @deleted_email_imports_ids, $email_import_id;
}
return unless @deleted_email_imports_ids;
return "Deleted email import(s): " . join(', ', @deleted_email_imports_ids) . ".\n";
}
sub run {
my ($self, $job_obj) = @_;
$self->{job_obj} = $job_obj;
my $data = $job_obj->data_as_hash;
my %configs = map { $_ => {
%{$data->{records}->{$_}},
config => $::lx_office_conf{"record_emails_imap/record/$_"}
|| $::lx_office_conf{record_emails_imap}
|| {},
} } keys %{$data->{records}};
my @results = ();
push @results, $self->delete_email_imports();
foreach my $import_key (keys %configs) {
my @record_results = ();
my $record_config = $configs{$import_key};
my $imap_client = SL::IMAPClient->new(%{$record_config->{config}});
my $record_folder = $record_config->{folder};
my $email_import = $self->sync_record_email_folder(
$imap_client, $import_key, $record_folder,
);
unless ($email_import) {
push @results, "$import_key No emails to import";
next;
}
push @record_results, "Created email import with id " . $email_import->id;
if ($record_config->{process_imported_emails}) {
my @function_names =
ref $record_config->{process_imported_emails} eq 'ARRAY' ?
@{$record_config->{process_imported_emails}}
: ($record_config->{process_imported_emails});
foreach my $email_journal (@{$email_import->email_journals}) {
my $created_records = 0;
foreach my $function_name (@function_names) {
eval {
my $processed = SL::Helper::EmailProcessing->process_attachments($function_name, $email_journal);
$created_records += $processed;
1;
} or do {
# TODO: link not shown as link
my $email_journal_link = link_tag(
$ENV{HTTP_ORIGIN} . $ENV{REQUEST_URI}
. '?action=EmailJournal/show'
. '&id=' . escape($email_journal->id)
# text
, $email_journal->id
);
push @record_results, "Error while processing email journal $email_journal_link attachments with $function_name: $@";
};
}
if ($created_records) {
$imap_client->set_flag_for_email(
$email_journal, $record_config->{processed_imap_flag});
} else {
$imap_client->set_flag_for_email(
$email_journal, $record_config->{not_processed_imap_flag});
}
}
push @record_results, "Processed attachments with " . join(', ', @function_names) . ".";
}
push @results, join("\n- ", "$import_key :", @record_results);
}
return join("\n", grep { $_ ne ''} @results);
}
1;
__END__
=encoding utf8
=head1 NAME
SL::BackgroundJob::ImportPurchaseInvoiceEmails - Background job for syncing
emails from a folder for records.
=head1 SYNOPSIS
This background job syncs emails from a folder for records. The emails are
imported as email journals and can be processed with functions from
SL::Helper::EmailProcessing.
=head1 CONFIGURATION
In kivitendo.conf the settings for the IMAP server must be specified. The
default config is under [record_emails_imap]. The config for a specific record
type is under [record_emails_imap/record/<record_type>]. The config for a
specific record type overrides the default config.
In the data field 'records' of the background job, the record types to sync
emails for are specified. The key is the record type, the value is a hashref.
The hashref contains the following keys:
=over 4
=item folder
The folder to sync emails from. Sub folders are separated by a forward slash,
e.g. 'INBOX/Archive'. Subfolders are not synced.
=item process_imported_emails
The function name(s) to process the imported emails with. Multiple function
names can be specified as an arrayref. The function names are passed to
SL::Helper::EmailProcessing->process_attachments. The function names must be
implemented in SL::Helper::EmailProcessing.
=item processed_imap_flag
The IMAP flag to set for emails that were processed successfully.
=item not_processed_imap_flag
The IMAP flag to set for emails that were not processed successfully.
=back
=head1 METHODS
=head1 BUGS
Nothing here yet.
=head1 AUTHOR
Tamino Steinert E<lt>tamino.steinert@tamino.stE<gt>
=cut
SL/DB/EmailJournalAttachment.pm
use strict;
use XML::LibXML;
use SL::ZUGFeRD;
use SL::DB::PurchaseInvoice;
use SL::DB::MetaSetup::EmailJournalAttachment;
use SL::DB::Manager::EmailJournalAttachment;
......
__PACKAGE__->meta->initialize;
sub create_ap_invoice {
my ($self) = @_;
my $content = $self->content; # scalar ref
return unless $content =~ m/^%PDF/;
my $zugferd_info = SL::ZUGFeRD->extract_from_pdf($content);
return unless $zugferd_info->{result} == SL::ZUGFeRD::RES_OK();
my $zugferd_xml = XML::LibXML->load_xml(string => $zugferd_info->{invoice_xml});
return SL::DB::PurchaseInvoice->create_from_zugferd_xml($zugferd_xml)->save();
}
1;
SL/DB/PurchaseInvoice.pm
my $ap_invoice = $class->new();
$ap_invoice->import_zugferd_xml($zugferd_xml)->save();
$ap_invoice->import_zugferd_xml($zugferd_xml);
}
sub create_ap_row {
SL/Helper/EmailProcessing.pm
package SL::Helper::EmailProcessing;
use strict;
use warnings;
use Carp;
use XML::LibXML;
use SL::ZUGFeRD;
use SL::Webdav;
use SL::File;
use SL::DB::PurchaseInvoice;
sub process_attachments {
my ($self, $function_name, $email_journal, %params) = @_;
unless ($self->can("process_attachments_$function_name")) {
croak "Function not implemented for: $function_name";
}
$function_name = "process_attachments_$function_name";
my $processed_count = 0;
foreach my $attachment (@{$email_journal->attachments_sorted}) {
my $processed = $self->$function_name($email_journal, $attachment, %params);
$processed_count += $processed;
}
return $processed_count;
}
sub process_attachments_zugferd {
my ($self, $email_journal, $attachment, %params) = @_;
my $content = $attachment->content; # scalar ref
return 0 unless $content =~ m/^%PDF/;
my $zugferd_info = SL::ZUGFeRD->extract_from_pdf($content);
return 0 unless $zugferd_info->{result} == SL::ZUGFeRD::RES_OK();
my $zugferd_xml = XML::LibXML->load_xml(string => $zugferd_info->{invoice_xml});
my $purchase_invoice = SL::DB::PurchaseInvoice->create_from_zugferd_xml($zugferd_xml)->save();
$self->_add_attachment_to_record($email_journal, $attachment, $purchase_invoice);
return 1;
}
sub _add_attachment_to_record {
my ($self, $email_journal, $attachment, $record) = @_;
# link to email journal
$email_journal->link_to_record($record);
# copy file to webdav folder
if ($::instance_conf->get_webdav_documents) {
my $record_type = $record->record_type;
# TODO: file and webdav use different types for ap_transaction
$record_type = 'accounts_payable' if $record_type eq 'ap_transaction';
my $webdav = SL::Webdav->new(
type => $record_type,
number => $record->record_number,
);
my $webdav_file = SL::Webdav::File->new(
webdav => $webdav,
filename => $attachment->name,
);
eval {
$webdav_file->store(data => \$attachment->content);
1;
} or do {
die 'Storing the attachment file to the WebDAV folder failed: ' . $@;
};
}
# copy file to doc storage
if ($::instance_conf->get_doc_storage) {
my $record_type = $record->record_type;
# TODO: file and webdav use different types for ap_invoice
$record_type = 'purchase_invoice' if $record_type eq 'ap_transaction';
eval {
SL::File->save(
object_id => $record->id,
object_type => $record_type,
source => 'uploaded',
file_type => 'document',
file_name => $attachment->name,
file_contents => $attachment->content,
mime_type => $attachment->mime_type,
);
1;
} or do {
die 'Storing the ZUGFeRD file in the storage backend failed: ' . $@;
};
}
my $new_ext_status = join(' ', $email_journal->extended_status,
'created_record_' . $record->record_type);
$email_journal->update_attributes(extended_status => $new_ext_status);
# TODO: hardlink in db to email_journal
}
1;
config/kivitendo.conf.default
# If SSL is used, default port is 993
ssl = 1
# Import emails for purchase invoices
[purchase_invoice_emails_imap]
# Import emails for records with BackgroundJob ImportRecordEmails
# Config can specified per type with [record_emails_imap/record/<record_type>]
# More configuration is possible in the data field of the BJ, for more see
# SL::BackgroundJob::ImportRecordEmails
[record_emails_imap]
enabled = 0
hostname = localhost
username =
password =
# This folder can be managed with kivitendo through the background
# ImportPurchaseInvoiceEmails. Create no subfolder in the base folder by hand.
# Use / for subfolders.
# Use / for subfolders. Subfolders are not imported.
base_folder = INBOX
# Port only needs to be changed if it is not the default port.
# port = 993

Auch abrufbar als: Unified diff