]> wagnertech.de Git - mfinanz.git/blob - SL/BackgroundJob/ImportRecordEmails.pm
kivitendo 3.9.2-0.2
[mfinanz.git] / SL / BackgroundJob / ImportRecordEmails.pm
1 package SL::BackgroundJob::ImportRecordEmails;
2
3 use strict;
4 use warnings;
5
6 use parent qw(SL::BackgroundJob::Base);
7
8 use SL::IMAPClient;
9 use SL::DB::EmailJournal;
10 use SL::DB::Manager::EmailImport;
11 use SL::Helper::EmailProcessing;
12 use SL::Presenter::Tag qw(link_tag);
13 use SL::Locale::String qw(t8);
14
15 use List::MoreUtils qw(any);
16 use Params::Validate qw(:all);
17 use Try::Tiny;
18
19 sub sync_record_email_folder {
20   my ($self, $config) = @_;
21
22   my %imap_config;
23   foreach my $key (qw(enabled hostname port ssl username password)) {
24     if (defined $config->{$key}) {
25       $imap_config{$key} = $config->{$key};
26     }
27   }
28
29   my $imap_client = SL::IMAPClient->new(%imap_config);
30
31   my $email_import = $imap_client->update_emails_from_folder(
32     folder => $config->{folder},
33     email_journal_params => {
34       record_type => $config->{record_type},
35     }
36   );
37   return "No emails to import." unless $email_import;
38   if ($config->{imported_imap_flag}) {
39     foreach my $email_journal (@{$email_import->email_journals}) {
40       $imap_client->set_flag_for_email(
41             email_journal => $email_journal,
42             flag          => $config->{imported_imap_flag},
43       );
44     }
45   }
46   my $result = "Created email import with id " . $email_import->id . " for ". scalar @{ $email_import->email_journals } . " emails.";
47
48   if ($config->{process_imported_emails}) {
49     my @function_names =
50       ref $config->{process_imported_emails} eq 'ARRAY' ?
51           @{$config->{process_imported_emails}}
52         : ($config->{process_imported_emails});
53     foreach my $email_journal (@{$email_import->email_journals}) {
54       my $created_records = 0;
55       foreach my $function_name (@function_names) {
56         eval {
57           my $processed = SL::Helper::EmailProcessing->process_attachments($function_name, $email_journal);
58           $created_records += $processed;
59           1;
60         } or do {
61           # # TODO: link not shown as link
62           # my $email_journal_link = link_tag(
63           #   $ENV{HTTP_ORIGIN} . $ENV{REQUEST_URI}
64           #   . '?action=EmailJournal/show'
65           #   . '&id=' . $email_journal->id
66           #   # text
67           #   , $email_journal->id
68           # );
69           my $email_journal_id = $email_journal->id;
70           $result .= t8("Error while processing email journal ('#1') attachments with '#2': ", $email_journal_id, $function_name) . $@ . ".";
71         };
72       }
73       if ($created_records  && $config->{processed_imap_flag}) {
74         $imap_client->set_flag_for_email(
75           email_journal => $email_journal,
76           flag          => $config->{processed_imap_flag},
77         );
78       } elsif ($config->{not_processed_imap_flag}) {
79         $imap_client->set_flag_for_email(
80           email_journal => $email_journal,
81           flag          => $config->{not_processed_imap_flag},
82         );
83       }
84     }
85     $result .= "Processed attachments with "
86       . join(', ', @function_names) . "."
87       if scalar @function_names;
88   }
89
90   return $result;
91 }
92
93 sub delete_email_imports {
94   my ($self, $email_import_ids_to_delete) = @_;
95
96   my @not_found_email_import_ids;
97   my @deleted_email_import_ids;
98   foreach my $email_import_id (@$email_import_ids_to_delete) {
99     my $email_import = SL::DB::Manager::EmailImport->find_by(id => $email_import_id);
100     unless ($email_import) {
101       push @not_found_email_import_ids, $email_import_id;
102       next;
103     }
104     $email_import->delete(cascade => 1);
105     push @deleted_email_import_ids, $email_import_id;
106   }
107
108   my $result = "";
109
110   $result .= t8("Deleted email import(s): ")
111     . join(', ', @deleted_email_import_ids) . "."
112     if scalar @deleted_email_import_ids;
113
114   $result .= t8("Could not find email import(s): ")
115     . join(', ', @not_found_email_import_ids) . " for deletion."
116     if scalar @not_found_email_import_ids;
117
118   return $result;
119 }
120
121 sub run {
122   my ($self, $job_obj) = @_;
123   $self->{job_obj} = $job_obj;
124
125   my $data;
126
127   try {
128     $data = $job_obj->data_as_hash;
129   } catch { die t8("Invalid YAML Configuration for this job. Reason: malformed YAML Data: #1. Please consult: Program -> Documentation -> HTML -> Configuration of Background-Jobs.", $_ ); };
130
131   my @config_params = %{$data};
132
133   my %config = validate_with(
134     params => \@config_params,
135     spec   => {
136       folder => {
137         type     => SCALAR,
138         optional => 1,
139       },
140       record_type => {
141         optional  => 1,
142         default   => 'catch_all',
143         callbacks => {
144           'valid record type' => sub {
145             my $valid_record_types = SL::DB::EmailJournal->meta->{columns}->{record_type}->{check_in};
146             unless (any {$_[0] eq $_} @$valid_record_types) {
147               die "record_type '$_[0]' is not valid. Possible values:\n- " . join("\n- ", @$valid_record_types);
148             }
149           },
150         },
151       },
152       process_imported_emails => {
153         type      => SCALAR | ARRAYREF,
154         optional  => 1,
155         callbacks => {
156           'function is implemented' => sub {
157             foreach my $function_name (ref $_[0] eq 'ARRAY' ? @{$_[0]} : ($_[0])) {
158               !!SL::Helper::EmailProcessing->can_function($function_name) or
159                 die "Function '$function_name' not implemented in SL::Helper::EmailProcessing";
160             }
161             1;
162           }
163         }
164       },
165       processed_imap_flag        => { type => SCALAR,   optional => 1, },
166       not_processed_imap_flag    => { type => SCALAR,   optional => 1, },
167       email_import_ids_to_delete => { type => ARRAYREF, optional => 1, },
168       imported_imap_flag         => { type => SCALAR,   optional => 1, },
169       # email config
170       hostname    => { type => SCALAR,  },
171       port        => { type => SCALAR,  optional => 1},
172       ssl         => { type => BOOLEAN, default  => 1},
173       username    => { type => SCALAR,  },
174       password    => { type => SCALAR,  },
175       base_folder => { type => SCALAR,  optional => 1},
176
177     },
178     called => "YAML Configuration for this Background Job invalid. Please consult: Program -> Documentation -> HTML -> Configuration of Background-Jobs.",
179   );
180
181   my @results;
182   if (scalar $config{email_import_ids_to_delete}) {
183     push @results, $self->delete_email_imports($config{email_import_ids_to_delete});
184   }
185
186   push @results, $self->sync_record_email_folder(\%config);
187
188   return join("\n", grep { $_ ne ''} @results);
189 }
190
191 1;
192
193 __END__
194
195 =encoding utf8
196
197 =head1 NAME
198
199 SL::BackgroundJob::ImportPurchaseInvoiceEmails - Background job for syncing
200 emails from a folder for records.
201
202 =head1 SYNOPSIS
203
204 This background job imports emails from an imap folder. The emails are
205 imported into the email journal and can be processed with functions from
206 SL::Helper::EmailProcessing.
207
208 =head1 CONFIGURATION
209
210 The data field in the backgroundjob config contains all configuration values:
211
212 =over 4
213
214 =item hostname
215
216 required, hostname of IMAP server
217
218 =item username
219
220 required, login for IMAP server
221
222 =item password
223
224 required, password for login of IMAP server
225
226 =item port
227
228 optional Parameter IMAP port
229
230 =item folder
231
232 required, The IMAP folder to import emails from. Sub folders are separated by a forward slash,
233 e.g. 'INBOX/Archive'. Subfolders are not synced. Default is 'INBOX'.
234
235 =item record_type
236
237 optional, The record type to set for each imported email in the email journal.
238 Default is catch-all. Valid types are the well-known types of kivitendo records, ie ar_transaction, ap_transaction
239
240 =item process_imported_emails
241
242 optional, more processing can be automatically done in the job.
243 Valid actions are defined in SL::Helper::EmailProcessing.pm
244
245 Take a look at currently supported actions with
246
247  perldoc SL/Helper/EmailProcessing.pm
248
249
250 =item processed_imap_flag
251
252 Optional, requires a valid value in process_imported_emails
253
254 If process_imported_emails is set and the process is successfully
255 executed this custom IMAP Flag is added to the processed email.
256
257 =item not_processed_imap_flag
258
259 Optional, requires a valid value in process_imported_emails
260
261 If process_imported_emails is set and the process is NOT
262 successfully executed this custom IMAP Flag is added
263 to the processed email.
264
265 =item imported_imap_flag
266
267 Optional
268
269 If the import is successfully executed this custom IMAP Flag
270 is added to the imported email.
271
272
273 =back
274
275 =head1 YAML Configuration example with ZUGFeRD Processing
276
277     hostname: meinedomain.de
278     username: eingangsrechnung@meinedomain.de
279     password: secret
280     folder: INBOX/vollimport
281     record_type: ap_transaction
282     process_imported_emails: zugferd
283     processed_imap_flag: $Label8
284     not_processed_imap_flag: $Label1
285
286 =head1 BUGS
287
288 Nothing here yet.
289
290 =head1 AUTHOR
291
292 Tamino Steinert E<lt>tamino.steinert@tamino.stE<gt>
293
294 =cut