commit ba486db5677d155df96d54f3441de7e0404cdb88
Author: Tamino Steinert <tamino.steinert@tamino.st>
Date:   Thu Sep 21 20:46:22 2023 +0200

    EmailJournal: Basisfunktionalität fürs Verlinken und Neu erstellen von Belegen

diff --git a/SL/Controller/EmailJournal.pm b/SL/Controller/EmailJournal.pm
index 98771079f..60e176737 100644
--- a/SL/Controller/EmailJournal.pm
+++ b/SL/Controller/EmailJournal.pm
@@ -8,6 +8,7 @@ use SL::Controller::Helper::GetModels;
 use SL::DB::Employee;
 use SL::DB::EmailJournal;
 use SL::DB::EmailJournalAttachment;
+use SL::DB::Order;
 use SL::Helper::Flash;
 use SL::Locale::String;
 use SL::System::TaskServer;
@@ -22,6 +23,32 @@ use Rose::Object::MakeMethods::Generic
 __PACKAGE__->run_before('add_stylesheet');
 __PACKAGE__->run_before('add_js');
 
+my %RECORD_TYPES_INFO = (
+  # Order
+  Order => {
+    controller => 'Order',
+    model      => 'SL::DB::Order',
+    types => [
+      'purchase_order',
+      'purchase_quotation_intake',
+      'request_quotation',
+      'sales_order',
+      'sales_order_intake',
+      'sales_quotation',
+    ],
+  },
+);
+my %RECORD_TYPE_TO_CONTROLLER =
+  map {
+    my $controller = $RECORD_TYPES_INFO{$_}->{controller};
+    map { $_ => $controller } @{ $RECORD_TYPES_INFO{$_}->{types} }
+  } keys %RECORD_TYPES_INFO;
+my %RECORD_TYPE_TO_MODEL =
+  map {
+    my $model = $RECORD_TYPES_INFO{$_}->{model};
+    map { $_ => $model } @{ $RECORD_TYPES_INFO{$_}->{types} }
+  } keys %RECORD_TYPES_INFO;
+
 #
 # actions
 #
@@ -56,7 +83,7 @@ sub action_show {
 
   $self->setup_show_action_bar;
   $self->render('email_journal/show',
-                title   => $::locale->text('View sent email'),
+                title   => $::locale->text('View email'),
                 back_to => $back_to);
 }
 
@@ -78,6 +105,50 @@ sub action_download_attachment {
   $self->send_file($ref, name => $attachment->name, type => $attachment->mime_type);
 }
 
+sub action_apply_record_action {
+  my ($self) = @_;
+  my $email_journal_id = $::form->{email_journal_id};
+  my $attachment_id = $::form->{attachment_id};
+  my $record_action = $::form->{record_action};
+  my $vendor_id = $::form->{vendor_id};
+  my $customer_id = $::form->{customer_id};
+
+  if ( $record_action =~ s/^link_// ) { # remove prefix
+
+    # Load record
+    my $record_type = $record_action;
+    my $record_id = $::form->{$record_type . "_id"};
+    my $record_type_model = $RECORD_TYPE_TO_MODEL{$record_type};
+    my $record = $record_type_model->new(id => $record_id)->load;
+    my $email_journal = SL::DB::EmailJournal->new(id => $email_journal_id)->load;
+
+    if ($attachment_id) {
+      my $attachment = SL::DB::EmailJournalAttachment->new(id => $attachment_id)->load;
+      $attachment->add_file_to_record($record);
+    }
+
+    $email_journal->link_to_record($record);
+
+    return $self->js->flash('info',  $::locale->text('Linked to e-mail ') . $record->displayable_name)->render();
+  }
+
+  my %additional_params = ();
+  if ( $record_action =~ s/^customer_// ) {  # remove prefix
+    $additional_params{customer_id} = $customer_id;
+  } elsif ( $record_action =~ s/^vendor_// ) { # remove prefix
+    $additional_params{vendor_id} = $vendor_id;
+  }
+  $additional_params{type} = $record_action;
+  $additional_params{controller} = $RECORD_TYPE_TO_CONTROLLER{$record_action};
+
+  $self->redirect_to(
+    action           => 'add',
+    email_journal_id => $email_journal_id,
+    attachment_id    => $attachment_id,
+    %additional_params,
+  );
+}
+
 sub action_update_attachment_preview {
   my ($self) = @_;
   $::auth->assert('email_journal');
@@ -92,7 +163,7 @@ sub action_update_attachment_preview {
     ->replaceWith('#attachment_preview',
       SL::Presenter::EmailJournal::attachment_preview(
         $attachment,
-        style => "width:489px;border:1px solid black;margin:9px"
+        style => "width:655px;border:1px solid black;margin:9px"
       )
     )
     ->render();
@@ -110,6 +181,67 @@ sub add_stylesheet {
 # helpers
 #
 
+sub find_cv_from_email {
+  my ($self, $cv_type, $email_journal) = @_;
+  my $email_address = $email_journal->from;
+
+  # search for customer or vendor or both
+  my $customer;
+  my $vendor;
+  if ($cv_type ne 'vendor') {
+    $customer = SL::DB::Manager::Customer->get_first(
+      where => [
+        or => [
+          email => $email_address,
+          cc    => $email_address,
+          bcc   => $email_address,
+          'contacts.cp_email' => $email_address,
+          'contacts.cp_privatemail' => $email_address,
+          'shipto.shiptoemail' => $email_address,
+        ],
+      ],
+      with_objects => [ 'contacts', 'shipto' ],
+    );
+  } elsif ($cv_type ne 'customer') {
+    $vendor = SL::DB::Manager::Vendor->get_first(
+      where => [
+        or => [
+          email => $email_address,
+          cc    => $email_address,
+          bcc   => $email_address,
+          'contacts.cp_email' => $email_address,
+          'contacts.cp_privatemail' => $email_address,
+          'shipto.shiptoemail' => $email_address,
+        ],
+      ],
+      with_objects => [ 'contacts', 'shipto' ],
+    );
+  }
+
+  return $customer || $vendor;
+}
+
+sub find_customer_from_email {
+  my ($self, $email_journal) = @_;
+  my $email_address = $email_journal->from;
+
+  my $customer = SL::DB::Manager::Customer->get_first(
+    where => [
+      or => [
+        email => $email_address,
+        cc    => $email_address,
+        bcc   => $email_address,
+        'contacts.cp_email' => $email_address,
+        'contacts.cp_privatemail' => $email_address,
+        'shipto.shiptoemail' => $email_address,
+      ],
+    ],
+    with_objects => [ 'contacts', 'shipto' ],
+  );
+
+  return $customer;
+}
+
 sub add_js {
   $::request->{layout}->use_javascript("${_}.js") for qw(
     kivi.EmailJournal
diff --git a/js/kivi.EmailJournal.js b/js/kivi.EmailJournal.js
index 17ad28d78..e12d2e24f 100644
--- a/js/kivi.EmailJournal.js
+++ b/js/kivi.EmailJournal.js
@@ -2,7 +2,7 @@ namespace('kivi.EmailJournal', function(ns) {
   'use strict';
 
   ns.update_attachment_preview = function() {
-    let $form = $('#attachment_form');
+    let $form = $('#record_action_form');
     if ($form == undefined) { return; }
 
     let data = $form.serializeArray();
@@ -10,4 +10,52 @@ namespace('kivi.EmailJournal', function(ns) {
 
     $.post("controller.pl", data, kivi.eval_json_result);
   }
+
+  ns.update_extra_div_selection = function() {
+    let record_action = $('#record_action').val();
+    if (record_action == undefined) { return; }
+
+    $('#customer_div').hide();
+    $('#vendor_div').hide();
+
+    $('#link_sales_quotation_div').hide();
+    $('#link_sales_order_intake_div').hide();
+    $('#link_sales_order_div').hide();
+    $('#link_request_quotation_div').hide();
+    $('#link_purchase_quotation_intake_div').hide();
+    $('#link_purchase_order_div').hide();
+
+    $('#placeholder_div').hide();
+
+    // customer vendor
+    if (record_action.match(/^customer/)) {
+      $('#customer_div').show();
+    } else if (record_action.match(/^vendor/)) {
+      $('#vendor_div').show();
+    // link
+    } else if (record_action.match(/^link_/)) {
+      $('#'+record_action+'_div').show();
+    // placeholder
+    } else {
+      $('#placeholder_div').show();
+    }
+  }
+
+  ns.apply_record_action = function() {
+    let record_action = $('#record_action').val();
+    if (record_action == '') {
+      alert(kivi.t8('Please select an action.'));
+      return;
+    }
+
+    let data = $('#record_action_form').serializeArray();
+    data.push({ name: 'action', value: 'EmailJournal/apply_record_action' });
+
+    $.post("controller.pl", data, kivi.eval_json_result);
+  }
+});
+
+$(function() {
+  kivi.EmailJournal.update_attachment_preview();
+  kivi.EmailJournal.update_extra_div_selection();
 });
diff --git a/templates/design40_webpages/email_journal/show.html b/templates/design40_webpages/email_journal/show.html
index de51e8e35..e37dc75b3 100644
--- a/templates/design40_webpages/email_journal/show.html
+++ b/templates/design40_webpages/email_journal/show.html
@@ -2,65 +2,76 @@
 [% USE L %]
 [% USE LxERP %]
 [% USE P %]
+[% USE T8 %]
 
-<h1>[% FORM.title %]</h1>
-
-[% INCLUDE 'common/flash.html' %]
-
-<div class="wrapper" id="wrapper-0">
-
-<div class="input-panel" style="vertical-align:top;" id="wrapper-1">
-
+[% BLOCK filter_toggle_panel # can't change name %]
 <table id="email_journal_details" class="tbl-horizontal">
   <tbody>
     <tr>
-      <th>[% LxERP.t8("From") %]</th>
-      <td>[% L.input_tag('from', HTML.escape(SELF.entry.from), style="color:black", class="wi-verywide", disabled=1) %]</td>
+      <th>[% 'From' | $T8 %]</th>
+      <td>[% L.input_tag('from',  SELF.entry.from, style="color:black", class="wi-verywide", disabled=1) %]</td>
     </tr>
     <tr>
-      <th>[% LxERP.t8("Recipients") %]</th>
-      <td>[% L.input_tag('recipients', HTML.escape(SELF.entry.recipients), style="color:black", class="wi-verywide", disabled=1) %]</td>
+      <th>[% 'Recipients' | $T8 %]</th>
+      <td>[% L.input_tag('recipients', SELF.entry.recipients, style="color:black", class="wi-verywide", disabled=1) %]</td>
     </tr>
     <tr>
-      <th>[% LxERP.t8("Subject") %]</th>
-      <td>[% L.input_tag('subject', HTML.escape(SELF.entry.subject), style="color:black", class="wi-verywide", disabled=1) %]</td>
+      <th>[% 'Subject' | $T8 %]</th>
+      <td>[% L.input_tag('subject', SELF.entry.subject, style="color:black", class="wi-verywide", disabled=1) %]</td>
     </tr>
     <tr>
-      <th>[% LxERP.t8("Sent on") %]</th>
-      <td>[% L.input_tag('sent_on', HTML.escape(SELF.entry.sent_on.to_lxoffice("precision" => "second")), style="color:black", class="wi-verywide", disabled=1) %]</td>
+      <th>[% 'Sent on' | $T8 %]</th>
+      <td>[% L.input_tag('sent_on', SELF.entry.sent_on.to_lxoffice("precision" => "second"), style="color:black", class="wi-verywide", disabled=1) %]</td>
     </tr>
     <tr>
-      <th>[% LxERP.t8("Status") %]</th>
+      <th>[% 'Status' | $T8 %]</th>
       <td>[% L.input_tag('status', P.email_journal.entry_status(SELF.entry), style="color:black", class="wi-verywide", disabled=1) %]</td>
     </tr>
     <tr>
-      <th>[% LxERP.t8("Extended status") %]</th>
+      <th>[% 'Extended status' | $T8 %]</th>
       <td>
-        [% L.textarea_tag('extended_status', HTML.escape(SELF.entry.extended_status), wrap="soft", style="color:black; height:25px", class="wi-verywide", disabled=1) %]
+        [% L.textarea_tag('extended_status', SELF.entry.extended_status, wrap="soft", style="color:black; height:25px", class="wi-verywide", disabled=1) %]
       </td>
     </tr>
     <tr>
-      <th>[% LxERP.t8("Headers") %]</th>
+      <th>[% 'Headers' | $T8 %]</th>
       <td>
         [% L.textarea_tag('headers', HTML.escape(SELF.entry.headers), wrap="soft", style="color:black; height:25px", class="wi-verywide", disabled=1) %]
       </td>
     </tr>
     <tr>
-      <th>[% LxERP.t8("Body") %]</th>
+      <th>[% 'Body' | $T8 %]</th>
       <td>
         [% IF SELF.entry.headers.match('(?i)content-type:.*text/html') %]
           <div style="border:1px solid black;">
           [% P.restricted_html(SELF.entry.body) %]
           </div>
         [% ELSE %]
-          [% L.textarea_tag('body', HTML.escape(SELF.entry.body), wrap="soft", style="color:black; height:200px", class="wi-verywide", disabled=1) %]
+          [% L.textarea_tag('body', SELF.entry.body, wrap="soft", style="color:black; height:200px", class="wi-verywide", disabled=1) %]
         [% END %]
       </td>
     </tr>
   </tbody>
 </table>
+[% END %]
+
+
+
+<h1>[% FORM.title %]</h1>
+
+[% INCLUDE 'common/flash.html' %]
+
+<div class="wrapper" id="wrapper-0">
+
+<div class="wrapper">
+  [%
+    display_status = 'open';
+    button_closed  = LxERP.t8('Show Email');
+    button_open    = LxERP.t8('Hide Email');
+    INCLUDE 'common/toggle_panel.html';
+  %]
+</div><!-- /.wrapper -->
 
-</div> <!-- wrapper-1 -->
 
 [% SET attachments = SELF.entry.attachments_sorted %]
 
@@ -68,12 +79,12 @@
 
 [% IF attachments.size %]
 <table id="email_journal_details" class="tbl-list">
-  <caption>[% LxERP.t8("Attachments") %]</caption>
+  <caption>[% 'Attachments' | $T8 %]</caption>
   <thead>
     <tr>
-      <th>[% LxERP.t8("Attachment name") %]</th>
-      <th>[% LxERP.t8("MIME type") %]</th>
-      <th>[% LxERP.t8("Size") %]</th>
+      <th>[% 'Attachment name' | $T8 %]</th>
+      <th>[% 'MIME type' | $T8 %]</th>
+      <th>[% 'Size' | $T8 %]</th>
     </tr>
   </thead>
   <tbody>
@@ -88,23 +99,92 @@
 </table>
 [% END %]
 
-<form method="post" action="controller.pl" id="attachment_form">
-  [% L.hidden_tag('id', SELF.entry.id) %]
-  [% L.select_tag('attachment_id',
-       attachments, value_key='id', title_key='name',
-       with_empty=1, empty_value='', empty_title=LxERP.t8("No attachment"),
-       'data-title'=LxERP.t8("Attachment"), class="wi-normal",
-       onchange='kivi.EmailJournal.update_attachment_preview();'
-       )
-    %]
-  [% L.select_tag('record_type', [ ["value", "Name"] ], class="wi-normal") %]
-
-  [% L.button_tag('kivi.EmailJournal.create_record();', LxERP.t8('Create Record with Attachment')) %]
+[% IF SELF.entry.status == 'imported' || SELF.entry.status == 'record_imported' %]
+<form method="post" action="controller.pl" id="record_action_form">
+  [% L.hidden_tag('email_journal_id', SELF.entry.id) %]
+
+  <div id="action_div" class="input-panel">
+
+    <div class="col">
+    [% L.select_tag('attachment_id',
+         attachments, value_key='id', title_key='name',
+         default = attachments.0.id,
+         with_empty=1, empty_value='', empty_title=LxERP.t8("No attachment"),
+         'data-title'=LxERP.t8("Attachment"), class="wi-normal",
+         onchange='kivi.EmailJournal.update_attachment_preview();'
+         )
+      %]
+    </div>
+
+    <div class="col">
+    [% L.select_tag('record_action',
+       # id has to start with customer_ or vendor_ for picker selection
+       [
+         [ LxERP.t8("Hard linking"), [
+           {id => "link_sales_quotation",           name => LxERP.t8("Link to sales quotation")},
+           {id => "link_sales_order_intake",        name => LxERP.t8("Link to sales order intake")},
+           {id => "link_sales_order",               name => LxERP.t8("Link to sales order")},
+           {id => "link_request_quotation",         name => LxERP.t8("Link to request quotation")},
+           {id => "link_purchase_quotation_intake", name => LxERP.t8("Link to purchase quotation intake")},
+           {id => "link_purchase_order",            name => LxERP.t8("Link to purchase order")},
+
+         ] ],
+         [ LxERP.t8("Sales"), [
+             {id => "customer_sales_order",  name => LxERP.t8("Create sales order")},
+             {id => "customer_sales_order_intake",  name => LxERP.t8("Create sales order intake")},
+             {id => "customer_sales_quotation",  name => LxERP.t8("Create sales quotation")},
+         ] ],
+         [ LxERP.t8("Purchase"), [
+             {id => "vendor_purchase_order", name => LxERP.t8("Create purchase order")},
+             {id => "vendor_purchase_quotation_intake", name => LxERP.t8("Create purchase quotation intake")},
+             {id => "vendor_request_quotation", name => LxERP.t8("Create request quotation")},
+
+         ] ],
+       ],
+       value_key='id', title_key='name',
+       with_empty=1, empty_value='', empty_title=LxERP.t8("No action"),
+       with_optgroups=1,
+       class="wi-normal",
+       onchange='kivi.EmailJournal.update_extra_div_selection();'
+       ) %]
+    </div>
+
+    [% FOREACH cv_type_name = [['customer', 'Customer'], ['vendor', 'Vendor']] %]
+    [% SET cv_type = cv_type_name.0 %]
+    [% SET cv_name = cv_type_name.1 %]
+    <div id="[% cv_type _ "_div" %]" class="col" style="display:none">
+      [% P.customer_vendor.picker(
+           cv_type _ "_id", SELF.find_cv_from_email(cv_type, SELF.entry),
+           type=cv_type, class="wi-normal", placeholder=LxERP.t8(cv_name)
+           ) %]
+    </div>
+    [% END %]
+    [% FOREACH record_type  = [
+         'sales_quotation', 'sales_order_intake', 'sales_order',
+         'request_quotation', 'purchase_quotation_intake', 'purchase_order'
+         ] %]
+    <div id="[% "link_" _ record_type _ "_div" %]" class="col" style="display:none">
+      [% L.input_tag(record_type _ "_id", '',
+           style="color:black", class="wi-normal",
+           placeholder=record_type _ " id") %]
+    </div>
+    [% END %]
+    <div id="placeholder_div" class="col" style="display:block">
+      [% L.input_tag('cv_placeholder', '',
+           style="color:black", class="wi-normal", disabled=1,
+           placeholder=LxERP.t8("Select action first"),
+           ) %]
+    </div>
+
+    <div class="col">
+      [% L.button_tag('kivi.EmailJournal.apply_record_action();', LxERP.t8('Apply with Attachment')) %]
+    </div>
+  </div> <!-- action_div -->
 </form>
 
-<!--  <div id="attachment_preview"> -->
-[% P.email_journal.attachment_preview(attachments.0,
-     style="width:489px;border:1px solid black;margin:9px") %]
+<!-- kivi.EmailJournal.update_attachment_preview -->
+<div id="attachment_preview"></div>
+[% END %]
 
 </div> <!-- wrapper-2 -->
 
diff --git a/templates/webpages/email_journal/show.html b/templates/webpages/email_journal/show.html
index 8a8496bd7..4b62870bf 100644
--- a/templates/webpages/email_journal/show.html
+++ b/templates/webpages/email_journal/show.html
@@ -79,22 +79,91 @@
    </tbody>
   </table>
  [% END %]
-<div>
-  <form method="post" action="controller.pl" id="attachment_form">
-    [% L.hidden_tag('id', SELF.entry.id) %]
-    [% L.select_tag('attachment_id',
-         attachments, value_key='id', title_key='name',
-         with_empty=1, empty_value='', empty_title=LxERP.t8("No attachment"),
-         'data-title'=LxERP.t8("Attachment"), class="wi-normal",
-         onchange='kivi.EmailJournal.update_attachment_preview();'
-         )
-      %]
-    [% L.select_tag('record_type', [ ["value", "Name"] ], class="wi-normal") %]
-
-    [% L.button_tag('kivi.EmailJournal.create_record();', LxERP.t8('Create Record with Attachment')) %]
-  </form>
-
-  <!--  <div id="attachment_preview"> -->
-  [% P.email_journal.attachment_preview(attachments.0,
-       style="width:489px;border:1px solid black;margin:9px") %]
-</div>
+
+[% IF SELF.entry.status == 'imported' || SELF.entry.status == 'record_imported' %]
+<form method="post" action="controller.pl" id="record_action_form">
+  [% L.hidden_tag('email_journal_id', SELF.entry.id) %]
+
+  <table> <tr>
+    <td>
+      [% L.select_tag('attachment_id',
+           attachments, value_key='id', title_key='name',
+           default = attachments.0.id,
+           with_empty=1, empty_value='', empty_title=LxERP.t8("No attachment"),
+           'data-title'=LxERP.t8("Attachment"), class="wi-normal",
+           onchange='kivi.EmailJournal.update_attachment_preview();'
+           )
+        %]
+    </td>
+
+    <td>
+      [% L.select_tag('record_action',
+         # id has to start with customer_ or vendor_ for picker selection
+         [
+           [ LxERP.t8("Hard linking"), [
+             {id => "link_sales_quotation",           name => LxERP.t8("Link to sales quotation")},
+             {id => "link_sales_order_intake",        name => LxERP.t8("Link to sales order intake")},
+             {id => "link_sales_order",               name => LxERP.t8("Link to sales order")},
+             {id => "link_request_quotation",         name => LxERP.t8("Link to request quotation")},
+             {id => "link_purchase_quotation_intake", name => LxERP.t8("Link to purchase quotation intake")},
+             {id => "link_purchase_order",            name => LxERP.t8("Link to purchase order")},
+
+           ] ],
+           [ LxERP.t8("Sales"), [
+               {id => "customer_sales_order",  name => LxERP.t8("Create sales order")},
+               {id => "customer_sales_order_intake",  name => LxERP.t8("Create sales order intake")},
+               {id => "customer_sales_quotation",  name => LxERP.t8("Create sales quotation")},
+           ] ],
+           [ LxERP.t8("Purchase"), [
+               {id => "vendor_purchase_order", name => LxERP.t8("Create purchase order")},
+               {id => "vendor_purchase_quotation_intake", name => LxERP.t8("Create purchase quotation intake")},
+               {id => "vendor_request_quotation", name => LxERP.t8("Create request quotation")},
+
+           ] ],
+         ],
+         value_key='id', title_key='name',
+         with_empty=1, empty_value='', empty_title=LxERP.t8("No action"),
+         with_optgroups=1,
+         class="wi-normal",
+         onchange='kivi.EmailJournal.update_extra_div_selection();'
+         ) %]
+    </td>
+
+    <td>
+      [% FOREACH cv_type_name = [['customer', 'Customer'], ['vendor', 'Vendor']] %]
+      [% SET cv_type = cv_type_name.0 %]
+      [% SET cv_name = cv_type_name.1 %]
+      <div id="[% cv_type _ "_div" %]" style="display:none">
+        [% P.customer_vendor.picker(
+             cv_type _ "_id", SELF.find_cv_from_email(cv_type, SELF.entry),
+             type=cv_type, class="wi-normal", placeholder=LxERP.t8(cv_name)
+             ) %]
+      </div>
+      [% END %]
+      [% FOREACH record_type  = [
+           'sales_quotation', 'sales_order_intake', 'sales_order',
+           'request_quotation', 'purchase_quotation_intake', 'purchase_order'
+           ] %]
+      <div id="[% "link_" _ record_type _ "_div" %]" style="display:none">
+        [% L.input_tag(record_type _ "_id", '',
+             style="color:black", class="wi-normal",
+             placeholder=record_type _ " id") %]
+      </div>
+      [% END %]
+      <div id="placeholder_div" style="display:block">
+        [% L.input_tag('cv_placeholder', '',
+             style="color:black", class="wi-normal", disabled=1,
+             placeholder=LxERP.t8("Select action first"),
+             ) %]
+      </div>
+    </td>
+
+    <td>
+      [% L.button_tag('kivi.EmailJournal.apply_record_action();', LxERP.t8('Apply with Attachment')) %]
+    </td>
+  </tr> </table>
+</form>
+
+<!-- kivi.EmailJournal.update_attachment_preview -->
+<div id="attachment_preview"></div>
+[% END %]
