Revision 72f19f83
Von Moritz Bunkus vor mehr als 9 Jahren hinzugefügt
SL/Controller/EmailJournal.pm | ||
---|---|---|
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;
|
SL/DB/EmailJournal.pm | ||
---|---|---|
|
||
use SL::DB::MetaSetup::EmailJournal;
|
||
use SL::DB::Manager::EmailJournal;
|
||
use SL::DB::Helper::AttrSorted;
|
||
|
||
__PACKAGE__->meta->add_relationship(
|
||
attachments => {
|
||
... | ... | |
|
||
__PACKAGE__->meta->initialize;
|
||
|
||
__PACKAGE__->attr_sorted('attachments');
|
||
|
||
1;
|
SL/DB/Manager/EmailJournal.pm | ||
---|---|---|
|
||
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;
|
css/email_journal.css | ||
---|---|---|
/* 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;
|
||
}
|
locale/de/all | ||
---|---|---|
'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',
|
||
... | ... | |
'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)',
|
||
... | ... | |
'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.',
|
||
... | ... | |
'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',
|
||
... | ... | |
'Receipt, payment, reconciliation' => 'Zahlungseingang, Zahlungsausgang, Kontenabgleich',
|
||
'Receipts' => 'Zahlungseingänge',
|
||
'Receivables' => 'Forderungen',
|
||
'Recipients' => 'EmpfängerInnen',
|
||
'Reconcile' => 'Abgleichen',
|
||
'Reconciliation' => 'Kontenabgleich',
|
||
'Reconciliation with bank' => 'Kontenabgleich mit Bank',
|
||
... | ... | |
'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',
|
||
... | ... | |
'Since bin is not enforced in the parts data, please specify a bin where goods without a specified bin will be put.' => 'Da Lagerplä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',
|
||
... | ... | |
'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ält momentan keine Einträ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:',
|
||
... | ... | |
'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: ',
|
||
... | ... | |
'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ählt:',
|
||
'You have never worked with currencies.' => 'Sie haben noch nie mit Währungen gearbeitet.',
|
menus/user/00-erp.yaml | ||
---|---|---|
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
|
templates/webpages/email_journal/_filter.html | ||
---|---|---|
[%- 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>
|
templates/webpages/email_journal/list.html | ||
---|---|---|
[% 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 %]
|
templates/webpages/email_journal/show.html | ||
---|---|---|
[% 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>
|
Auch abrufbar als: Unified diff
E-Mail-Journal: Journal anzeigen, Eintrag anzeigen, Anhänge herunterladen