E-Mail-Journal: Journal anzeigen, Eintrag anzeigen, Anhänge herunterladen
authorMoritz Bunkus <m.bunkus@linet-services.de>
Thu, 24 Sep 2015 09:42:15 +0000 (11:42 +0200)
committerMoritz Bunkus <m.bunkus@linet-services.de>
Thu, 24 Sep 2015 09:44:14 +0000 (11:44 +0200)
SL/Controller/EmailJournal.pm [new file with mode: 0644]
SL/DB/EmailJournal.pm
SL/DB/Manager/EmailJournal.pm
css/email_journal.css [new file with mode: 0644]
locale/de/all
menus/user/00-erp.yaml
templates/webpages/email_journal/_filter.html [new file with mode: 0644]
templates/webpages/email_journal/list.html [new file with mode: 0644]
templates/webpages/email_journal/show.html [new file with mode: 0644]

diff --git a/SL/Controller/EmailJournal.pm b/SL/Controller/EmailJournal.pm
new file mode 100644 (file)
index 0000000..7c40a0d
--- /dev/null
@@ -0,0 +1,124 @@
+package SL::Controller::EmailJournal;
+
+use strict;
+
+use parent qw(SL::Controller::Base);
+
+use SL::Controller::Helper::GetModels;
+use SL::DB::Employee;
+use SL::DB::EmailJournal;
+use SL::DB::EmailJournalAttachment;
+use SL::Helper::Flash;
+use SL::Locale::String;
+use SL::System::TaskServer;
+
+use Rose::Object::MakeMethods::Generic
+(
+  scalar                  => [ qw(entry) ],
+  'scalar --get_set_init' => [ qw(models can_view_all filter_summary) ],
+);
+
+__PACKAGE__->run_before('add_stylesheet');
+
+#
+# actions
+#
+
+sub action_list {
+  my ($self) = @_;
+
+  $self->render('email_journal/list',
+                title   => $::locale->text('Email journal'),
+                ENTRIES => $self->models->get,
+                MODELS  => $self->models);
+}
+
+sub action_show {
+  my ($self) = @_;
+
+  my $back_to = $::form->{back_to} || $self->url_for(action => 'list');
+
+  $self->entry(SL::DB::EmailJournal->new(id => $::form->{id})->load);
+
+  if (!$self->can_view_all && ($self->entry->sender_id != SL::DB::Manager::Emplyee->current->id)) {
+    $::form->error(t8('You do not have permission to access this entry.'));
+  }
+
+  $self->render('email_journal/show',
+                title   => $::locale->text('View sent email'),
+                back_to => $back_to);
+}
+
+sub action_download_attachment {
+  my ($self) = @_;
+
+  my $attachment = SL::DB::EmailJournalAttachment->new(id => $::form->{id})->load;
+
+  if (!$self->can_view_all && ($attachment->email_journal->sender_id != SL::DB::Manager::Emplyee->current->id)) {
+    $::form->error(t8('You do not have permission to access this entry.'));
+  }
+
+  $self->send_file(\$attachment->content, name => $attachment->name, type => $attachment->mime_type);
+}
+
+#
+# filters
+#
+
+sub add_stylesheet {
+  $::request->{layout}->use_stylesheet('email_journal.css');
+}
+
+#
+# helpers
+#
+
+sub init_can_view_all { $::auth->assert('admin', 1) }
+
+sub init_models {
+  my ($self) = @_;
+
+  my @where;
+  push @where, (sender_id => SL::DB::Manager::Employee->current->id) if !$self->can_view_all;
+
+  SL::Controller::Helper::GetModels->new(
+    controller        => $self,
+    query             => \@where,
+    with_objects      => [ 'sender' ],
+    sorted            => {
+      sender          => t8('Sender'),
+      from            => t8('From'),
+      recipients      => t8('Recipients'),
+      subject         => t8('Subject'),
+      sent_on         => t8('Sent on'),
+      status          => t8('Status'),
+      extended_status => t8('Extended status'),
+    },
+  );
+}
+
+sub init_filter_summary {
+  my ($self)  = @_;
+
+  my $filter  = $::form->{filter} || {};
+  my @filters = (
+    [ "from:substr::ilike",       $::locale->text('From')                                         ],
+    [ "recipients:substr::ilike", $::locale->text('Recipients')                                   ],
+    [ "sent_on:date::ge",         $::locale->text('Sent on') . " " . $::locale->text('From Date') ],
+    [ "sent_on:date::le",         $::locale->text('Sent on') . " " . $::locale->text('To Date')   ],
+  );
+
+  my @filter_strings = grep { $_ }
+                       map  { $filter->{ $_->[0] } ? $_->[1] . ' ' . $filter->{ $_->[0] } : undef }
+                       @filters;
+
+  my %status = (
+    failed  => $::locale->text('failed'),
+    ok      => $::locale->text('succeeded'),
+  );
+  push @filter_strings, $status{ $filter->{'status:eq_ignore_empty'} } if $filter->{'status:eq_ignore_empty'};
+
+  return join ', ', @filter_strings;
+}
+
+1;
index 6875b52..9920d59 100644 (file)
@@ -4,6 +4,7 @@ use strict;
 
 use SL::DB::MetaSetup::EmailJournal;
 use SL::DB::Manager::EmailJournal;
+use SL::DB::Helper::AttrSorted;
 
 __PACKAGE__->meta->add_relationship(
   attachments  => {
@@ -15,4 +16,6 @@ __PACKAGE__->meta->add_relationship(
 
 __PACKAGE__->meta->initialize;
 
+__PACKAGE__->attr_sorted('attachments');
+
 1;
index d387a7b..b558487 100644 (file)
@@ -4,8 +4,21 @@ use strict;
 
 use parent qw(SL::DB::Helper::Manager);
 
+use SL::DB::Helper::Paginated;
+use SL::DB::Helper::Sorted;
+
 sub object_class { 'SL::DB::EmailJournal' }
 
 __PACKAGE__->make_manager_methods;
 
+sub _sort_spec {
+  return (
+    default => [ 'sent_on', 1 ],
+    columns => {
+      SIMPLE => 'ALL',
+      sender => 'sender.name',
+    },
+  );
+}
+
 1;
diff --git a/css/email_journal.css b/css/email_journal.css
new file mode 100644 (file)
index 0000000..5e5bf8f
--- /dev/null
@@ -0,0 +1,13 @@
+/* E-Mail-Journal */
+.email_journal_details tbody pre {
+  margin: 0px;
+}
+
+.email_journal_details tbody th {
+  text-align: right;
+  vertical-align: top;
+}
+
+.email_journal_details tbody td {
+  vertical-align: top;
+}
index 74ca674..d2884bd 100755 (executable)
@@ -302,6 +302,7 @@ $self->{texts} = {
   'Attach PDF:'                 => 'PDF anhängen',
   'Attachment'                  => 'als Anhang',
   'Attachment name'             => 'Name des Anhangs',
+  'Attachments'                 => 'Dateianhänge',
   'Attempt to call an undefined sub named \'%s\'' => 'Es wurde versucht, eine nicht definierte Unterfunktion namens \'%s\' aufzurufen.',
   'Audit Control'               => 'Bücherkontrolle',
   'Aug'                         => 'Aug',
@@ -1074,6 +1075,7 @@ $self->{texts} = {
   'Editable'                    => 'Bearbeitbar',
   'Either there are no open invoices, or you have already initiated bank transfers with the open amounts for those that are still open.' => 'Entweder gibt es keine offenen Rechnungen, oder es wurden bereits Überweisungen über die offenen Beträge aller offenen Rechnungen erstellt.',
   'Element disabled'            => 'Element deaktiviert',
+  'Email journal'               => 'E-Mail-Journal',
   'Employee'                    => 'Bearbeiter',
   'Employee #1 saved!'          => 'Benutzer #1 gespeichert!',
   'Employee (database ID)'      => 'Bearbeiter (Datenbank-ID)',
@@ -1184,6 +1186,7 @@ $self->{texts} = {
   'Export date to'              => 'Exportdatum bis',
   'Extend automatically by n months' => 'Automatische Verlängerung um x Monate',
   'Extended'                    => 'Gesamt',
+  'Extended status'             => 'Erweiterter Status',
   'Extension Of Time'           => 'Dauerfristverlängerung',
   'Factor'                      => 'Faktor',
   'Factor missing!'             => 'Der Faktor fehlt.',
@@ -1292,6 +1295,7 @@ $self->{texts} = {
   'Hardcopy'                    => 'Seite drucken',
   'Has item type'               => 'Hat Regeltypen',
   'Has serial number'           => 'Hat eine Serienummer',
+  'Headers'                     => 'Kopfzeilen',
   'Heading'                     => 'Überschrift',
   'Help Template Variables'     => 'Hilfe zu Dokumenten-Variablen',
   'Help on column names'        => 'Hilfe zu Spaltennamen',
@@ -2125,6 +2129,7 @@ $self->{texts} = {
   'Receipt, payment, reconciliation' => 'Zahlungseingang, Zahlungsausgang, Kontenabgleich',
   'Receipts'                    => 'Zahlungseingänge',
   'Receivables'                 => 'Forderungen',
+  'Recipients'                  => 'EmpfängerInnen',
   'Reconcile'                   => 'Abgleichen',
   'Reconciliation'              => 'Kontenabgleich',
   'Reconciliation with bank'    => 'Kontenabgleich mit Bank',
@@ -2344,6 +2349,9 @@ $self->{texts} = {
   'Sellprice for price group \'#1\'' => 'Verkaufspreis für Preisgruppe \'#1\'',
   'Sellprice significant places' => 'Verkaufspreis: Nachkommastellen',
   'Semicolon'                   => 'Semikolon',
+  'Send PDF to support contract\'s contact person' => 'PDFs an Ansprechpersonen der Wartungsverträge schicken',
+  'Sender'                      => 'AbsenderIn',
+  'Sent on'                     => 'Verschickt am',
   'Sep'                         => 'Sep',
   'Separator'                   => 'Trennzeichen',
   'Separator chararacter'       => 'Feldtrennzeichen',
@@ -2428,6 +2436,7 @@ $self->{texts} = {
   'Since bin is not enforced in the parts data, please specify a bin where goods without a specified bin will be put.' => 'Da Lagerpl&auml;tze kein Pflichtfeld sind, geben Sie bitte einen Lagerplatz an, in dem Waren ohne spezifizierten Lagerplatz eingelagert werden sollen.',
   'Single quotes'               => 'Einfache Anführungszeichen',
   'Single values in item mode, cumulated values in invoice mode' => 'Einzelwerte im Artikelmodus, kumulierte Werte im Rechnungsmodus',
+  'Size'                        => 'Größe',
   'Sketch'                      => 'Skizze',
   'Skip'                        => 'Überspringen',
   'Skip entry'                  => 'Eintrag überspringen',
@@ -2874,6 +2883,7 @@ $self->{texts} = {
   'There are invalid transactions in your database.' => 'Sie haben ungültige Buchungen in Ihrer Datenbank.',
   'There are invoices which could not be paid by bank transaction #1 (Account number: #2, bank code: #3)!' => 'Einige Rechnungen konnten nicht durch die Bankbewegung #1 (Kontonummer: #2, Bankleitzahl: #3) bezahlt werden!',
   'There are no entries in the background job history.' => 'Es gibt keine Einträge im Hintergrund-Job-Verlauf.',
+  'There are no entries that match the filter.' => 'Es gibt keine Einträge, auf die der Filter zutrifft.',
   'There are no items in stock.' => 'Dieser Artikel ist nicht eingelagert.',
   'There are no items on your TODO list at the moment.' => 'Ihre Aufgabenliste enth&auml;lt momentan keine Eintr&auml;ge.',
   'There are several options you can handle this problem, please select one:' => 'Bitte wählen Sie eine der folgenden Optionen, um mit dem Problem umzugehen:',
@@ -3131,6 +3141,7 @@ $self->{texts} = {
   'View background job execution result' => 'Verlauf der Hintergrund-Job-Ausführungen anzeigen',
   'View background job history' => 'Hintergrund-Job-Verlauf anzeigen',
   'View background jobs'        => 'Hintergrund-Jobs anzeigen',
+  'View sent email'             => 'Verschickte E-Mail anzeigen',
   'View warehouse content'      => 'Lagerbestand ansehen',
   'View/edit all employees sales documents' => 'Bearbeiten/ansehen der Verkaufsdokumente aller Mitarbeiter',
   'Von Konto: '                 => 'von Konto: ',
@@ -3196,6 +3207,7 @@ $self->{texts} = {
   'You cannot create an invoice for delivery orders from different vendors.' => 'Sie können keine Rechnung aus Lieferscheinen von verschiedenen Lieferanten erstellen.',
   'You cannot modify individual assigments from additional articles to line items.' => 'Eine individuelle Zuordnung der zusätzlichen Artikel zu Positionen kann nicht vorgenommen werden.',
   'You cannot paste function blocks or sub function blocks if there is no section.' => 'Sie können keine Funktionsblöcke oder Unterfunktionsblöcke einfügen, wenn es noch keinen Abschnitt gibt.',
+  'You do not have permission to access this entry.' => 'Sie verfügen nicht über die Berechtigung, auf diesen Eintrag zuzugreifen.',
   'You do not have the permissions to access this function.' => 'Sie verfügen nicht über die notwendigen Rechte, um auf diese Funktion zuzugreifen.',
   'You have entered or selected the following shipping address for this customer:' => 'Sie haben die folgende Lieferadresse eingegeben oder ausgew&auml;hlt:',
   'You have never worked with currencies.' => 'Sie haben noch nie  mit Währungen gearbeitet.',
index 64d3d71..9f025e1 100644 (file)
   module: fu.pl
   params:
     action: search
+- parent: productivity_reports
+  id: productivity_reports_email_journal
+  name: Email journal
+  order: 200
+  module: controller.pl
+  params:
+    action: EmailJournal/list
 - id: system
   name: System
   icon: system
diff --git a/templates/webpages/email_journal/_filter.html b/templates/webpages/email_journal/_filter.html
new file mode 100644 (file)
index 0000000..282fd09
--- /dev/null
@@ -0,0 +1,43 @@
+[%- USE L %][%- USE LxERP %][%- USE HTML %]
+<form action="controller.pl" method="post">
+ <div class="filter_toggle">
+  <a href="#" onClick="javascript:$('.filter_toggle').toggle()">[% LxERP.t8('Show Filter') %]</a>
+  [% IF SELF.filter_summary %]([% LxERP.t8("Current filter") %]: [% SELF.filter_summary %])[% END %]
+ </div>
+
+ <div class="filter_toggle" style="display:none">
+  <a href="#" onClick="javascript:$('.filter_toggle').toggle()">[% LxERP.t8('Hide Filter') %]</a>
+  <table id="filter_table">
+   <tr>
+    <th align="right">[% LxERP.t8("From") %]</th>
+    <td>[% L.input_tag("filter.from:substr::ilike", filter.from_substr__ilike, size = 20) %]</td>
+   </tr>
+   <tr>
+    <th align="right">[% LxERP.t8("Recipients") %]</th>
+    <td>[% L.input_tag("filter.recipients:substr::ilike", filter.recipients_substr__ilike, size = 20) %]</td>
+   </tr>
+   <tr>
+    <th align="right">[% LxERP.t8("Sent on") %]</th>
+    <td>
+     [% L.date_tag("filter.sent_on:date::ge", filter.sent_on_date__ge) %]
+     [% LxERP.t8("To Date") %]
+     [% L.date_tag("filter.sent_on:date::le", filter.sent_on_date__le) %]
+    </td>
+   </tr>
+   <tr>
+    <th align="right">[% LxERP.t8("Status") %]</th>
+    <td>[% L.select_tag("filter.status:eq_ignore_empty", [ [ "", "" ], [ "failed", LxERP.t8("failed") ], [ "ok", LxERP.t8("succeeded") ] ], default=filter.status_eq_ignore_empty) %]</td>
+   </tr>
+  </table>
+
+  [% L.hidden_tag("action", "EmailJournal/dispatch") %]
+  [% L.hidden_tag("sort_by", FORM.sort_by) %]
+  [% L.hidden_tag("sort_dir", FORM.sort_dir) %]
+  [% L.hidden_tag("page", FORM.page) %]
+  [% L.submit_tag("action_list", LxERP.t8("Continue"))%]
+
+  <a href="#" onClick="javascript:$('#filter_table input,#filter_table select').val("");">[% LxERP.t8("Reset") %]</a>
+
+ </div>
+
+</form>
diff --git a/templates/webpages/email_journal/list.html b/templates/webpages/email_journal/list.html
new file mode 100644 (file)
index 0000000..f169aed
--- /dev/null
@@ -0,0 +1,68 @@
+[% USE HTML %][% USE L %][% USE LxERP %]
+
+<h1>[% FORM.title %]</h1>
+
+[%- INCLUDE 'common/flash.html' %]
+
+[%- PROCESS 'email_journal/_filter.html' filter=SELF.models.filtered.laundered %]
+
+[% IF !ENTRIES.size %]
+ <p>
+  [%- LxERP.t8('There are no entries that match the filter.') %]
+ </p>
+
+[%- ELSE %]
+ <table id="email_journal_list" width="100%">
+  <thead>
+   <tr class="listheading">
+    [% IF SELF.can_view_all %]
+     <th>[% L.sortable_table_header("sender") %]</th>
+    [% END %]
+    <th>[% L.sortable_table_header("from") %]</th>
+    <th>[% L.sortable_table_header("recipients") %]</th>
+    <th>[% L.sortable_table_header("subject") %]</th>
+    <th>[% L.sortable_table_header("sent_on") %]</th>
+    <th>[% L.sortable_table_header("status") %]</th>
+    <th>[% L.sortable_table_header("extended_status") %]</th>
+   </tr>
+  </thead>
+
+  <tbody>
+  [%- FOREACH entry = ENTRIES %]
+  <tr class="listrow[% IF entry.status != 'ok' %]_error[% END %]" id="email_journal_id_[% entry.id %]">
+   [% IF SELF.can_view_all %]
+    <td>
+     [% IF entry.sender %]
+      [% HTML.escape(entry.sender.name) %]
+     [% ELSE %]
+      [% LxERP.t8("kivitendo") %]
+     [% END %]
+    </td>
+   [% END %]
+   <td>
+    <a href="[% SELF.url_for(action => 'show', id => entry.id, back_to => SELF.get_callback) %]">
+     [%- HTML.escape(entry.from) %]
+    </a>
+   </td>
+   <td>[%- HTML.escape(entry.recipients) %]</td>
+   <td>
+    <a href="[% SELF.url_for(action => 'show', id => entry.id, back_to => SELF.get_callback) %]">
+     [%- HTML.escape(entry.subject) %]
+    </a>
+   </td>
+   <td>[%- HTML.escape(entry.sent_on.to_lxoffice('precision' => 'second')) %]</td>
+   <td>
+    [%- IF entry.status == 'ok' %]
+     [%- LxERP.t8('succeeded') %]
+    [% ELSE %]
+     [%- LxERP.t8('failed') %]
+    [%- END %]
+   </td>
+   <td>[%- HTML.escape(entry.extended_status) %]</td>
+  </tr>
+  [%- END %]
+  </tbody>
+ </table>
+[%- END %]
+
+[% L.paginate_controls %]
diff --git a/templates/webpages/email_journal/show.html b/templates/webpages/email_journal/show.html
new file mode 100644 (file)
index 0000000..67d7621
--- /dev/null
@@ -0,0 +1,83 @@
+[% USE HTML %][% USE L %][% USE LxERP %]
+
+ <h1>[% FORM.title %]</h1>
+
+[%- INCLUDE 'common/flash.html' %]
+
+ <table id="email_journal_details" class="email_journal_details">
+  <tbody>
+   <tr class="listrow">
+    <th>[%- LxERP.t8("From") %]</th>
+    <td>[%- HTML.escape(SELF.entry.from) %]</td>
+   </tr>
+
+   <tr class="listrow">
+    <th>[%- LxERP.t8("Recipients") %]</th>
+    <td>[%- HTML.escape(SELF.entry.recipients) %]</td>
+   </tr>
+
+   <tr class="listrow">
+    <th>[%- LxERP.t8("Subject") %]</th>
+    <td>[%- HTML.escape(SELF.entry.subject) %]</td>
+   </tr>
+
+   <tr class="listrow">
+    <th>[%- LxERP.t8("Sent on") %]</th>
+    <td>[%- HTML.escape(SELF.entry.sent_on.to_lxoffice("precision" => "second")) %]</td>
+   </tr>
+
+   <tr class="listrow">
+    <th>[%- LxERP.t8("Status") %]</th>
+    <td>
+     [%- IF SELF.entry.status == "ok" %]
+      [%- LxERP.t8("succeeded") %]
+     [%- ELSE %]
+      [%- LxERP.t8("failed") %]
+     [%- END %]
+    </td>
+   </tr>
+
+   <tr class="listrow">
+    <th>[%- LxERP.t8("Extended status") %]</th>
+    <td><pre>[%- HTML.escape(SELF.entry.extended_status) %]</pre></td>
+   </tr>
+
+   <tr class="listrow">
+    <th>[%- LxERP.t8("Headers") %]</th>
+    <td><pre>[% HTML.escape(SELF.entry.headers) %]</pre></td>
+   </tr>
+
+   <tr class="listrow">
+    <th>[%- LxERP.t8("Body") %]</th>
+    <td><pre>[% HTML.escape(SELF.entry.body) %]</pre></td>
+   </tr>
+ </table>
+
+ [% SET attachments = SELF.entry.attachments_sorted %]
+ [% IF attachments.size %]
+  <h2>[% LxERP.t8("Attachments") %]</h2>
+
+  <table id="email_journal_details" class="email_journal_details">
+   <thead>
+    <tr>
+     <th>[% LxERP.t8("Attachment name") %]</th>
+     <th>[% LxERP.t8("MIME type") %]</th>
+     <th>[% LxERP.t8("Size") %]</th>
+    </tr>
+   </thead>
+
+   <tbody>
+    [% FOREACH attachment = attachments %]
+     <tr class="listrow">
+      <td>[% L.link(SELF.url_for(action="download_attachment", id=attachment.id), attachment.name) %]</td>
+      <td>[% HTML.escape(attachment.mime_type) %]</td>
+      <td>[% HTML.escape(LxERP.format_amount(attachment.content.length, 0)) %]</td>
+     </tr>
+    [% END %]
+   </tbody>
+  </table>
+ [% END %]
+
+ <p>
+  <a href="[% back_to %]">[%- LxERP.t8("Back") %]</a>
+ </p>