Merge branch 'b-3.6.1' of ../kivitendo-erp_20220811
[kivitendo-erp.git] / SL / DN.pm
index 2bead22..76ee1b8 100644 (file)
--- a/SL/DN.pm
+++ b/SL/DN.pm
@@ -25,7 +25,8 @@
 # GNU General Public License for more details.
 # You should have received a copy of the GNU General Public License
 # along with this program; if not, write to the Free Software
-# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA 02110-1335, USA.
 #======================================================================
 #
 # Dunning process module
@@ -36,7 +37,10 @@ package DN;
 
 use SL::Common;
 use SL::DBUtils;
+use SL::DB::AuthUser;
 use SL::DB::Default;
+use SL::DB::Employee;
+use SL::File;
 use SL::GenericTranslations;
 use SL::IS;
 use SL::Mailer;
@@ -45,6 +49,12 @@ use SL::Template;
 use SL::DB::Printer;
 use SL::DB::Language;
 use SL::TransNumber;
+use SL::Util qw(trim);
+use SL::DB;
+use SL::Webdav;
+
+use File::Copy;
+use File::Slurp qw(read_file);
 
 use strict;
 
@@ -54,7 +64,7 @@ sub get_config {
   my ($self, $myconfig, $form) = @_;
 
   # connect to database
-  my $dbh = $form->dbconnect($myconfig);
+  my $dbh = SL::DB->client->dbh;
 
   my $query =
     qq|SELECT * | .
@@ -68,22 +78,28 @@ sub get_config {
   }
 
   $query =
-    qq|SELECT dunning_ar_amount_fee, dunning_ar_amount_interest, dunning_ar
+    qq|SELECT dunning_ar_amount_fee, dunning_ar_amount_interest, dunning_ar, dunning_creator
        FROM defaults|;
-  ($form->{AR_amount_fee}, $form->{AR_amount_interest}, $form->{AR}) = selectrow_query($form, $dbh, $query);
-
-  $dbh->disconnect();
+  ($form->{AR_amount_fee}, $form->{AR_amount_interest}, $form->{AR}, $form->{dunning_creator})
+    = selectrow_query($form, $dbh, $query);
 
   $main::lxdebug->leave_sub();
 }
 
 sub save_config {
+  my ($self, $myconfig, $form) = @_;
   $main::lxdebug->enter_sub();
 
+  my $rc = SL::DB->client->with_transaction(\&_save_config, $self, $myconfig, $form);
+
+  $::lxdebug->leave_sub;
+  return $rc;
+}
+
+sub _save_config {
   my ($self, $myconfig, $form) = @_;
 
-  # connect to database
-  my $dbh = $form->dbconnect_noauto($myconfig);
+  my $dbh = SL::DB->client->dbh;
 
   my ($query, @values);
 
@@ -98,7 +114,8 @@ sub save_config {
                  $form->{"template_$i"}, $form->{"fee_$i"}, $form->{"interest_rate_$i"},
                  $form->{"active_$i"} ? 't' : 'f', $form->{"auto_$i"} ? 't' : 'f', $form->{"email_$i"} ? 't' : 'f',
                  $form->{"email_attachment_$i"} ? 't' : 'f', conv_i($form->{"payment_terms_$i"}), conv_i($form->{"terms_$i"}),
-                 $form->{"create_invoices_for_fees_$i"} ? 't' : 'f');
+                 $form->{"create_invoices_for_fees_$i"} ? 't' : 'f',
+                 $form->{"print_original_invoice_$i"} ? 't' : 'f');
       if ($form->{"id_$i"}) {
         $query =
           qq|UPDATE dunning_config SET
@@ -107,7 +124,8 @@ sub save_config {
                template = ?, fee = ?, interest_rate = ?,
                active = ?, auto = ?, email = ?,
                email_attachment = ?, payment_terms = ?, terms = ?,
-               create_invoices_for_fees = ?
+               create_invoices_for_fees = ?,
+               print_original_invoice = ?
              WHERE id = ?|;
         push(@values, conv_i($form->{"id_$i"}));
       } else {
@@ -115,8 +133,9 @@ sub save_config {
           qq|INSERT INTO dunning_config
                (dunning_level, dunning_description, email_subject, email_body,
                 template, fee, interest_rate, active, auto, email,
-                email_attachment, payment_terms, terms, create_invoices_for_fees)
-             VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)|;
+                email_attachment, payment_terms, terms, create_invoices_for_fees,
+                print_original_invoice)
+             VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)|;
       }
       do_query($form, $dbh, $query, @values);
     }
@@ -127,14 +146,13 @@ sub save_config {
     }
   }
 
-  $query  = qq|UPDATE defaults SET dunning_ar_amount_fee = ?, dunning_ar_amount_interest = ?, dunning_ar = ?|;
-  @values = (conv_i($form->{AR_amount_fee}), conv_i($form->{AR_amount_interest}), conv_i($form->{AR}));
+  $query  = qq|UPDATE defaults SET dunning_ar_amount_fee = ?, dunning_ar_amount_interest = ?, dunning_ar = ?,
+               dunning_creator = ?|;
+  @values = (conv_i($form->{AR_amount_fee}), conv_i($form->{AR_amount_interest}), conv_i($form->{AR}),
+             $form->{dunning_creator});
   do_query($form, $dbh, $query, @values);
 
-  $dbh->commit();
-  $dbh->disconnect();
-
-  $main::lxdebug->leave_sub();
+  return 1;
 }
 
 sub create_invoice_for_fees {
@@ -177,7 +195,8 @@ sub create_invoice_for_fees {
              AND (d_interest.dunning_id <> ?)
              AND NOT (d_interest.fee_interest_ar_id ISNULL)
          ), 0)
-         AS max_previous_interest
+         AS max_previous_interest,
+         d.id AS link_id
        FROM dunning d
        WHERE dunning_id = ?|;
   @values = ($dunning_id, $dunning_id, $dunning_id);
@@ -186,6 +205,8 @@ sub create_invoice_for_fees {
   my ($fee_remaining, $interest_remaining) = (0, 0);
   my ($fee_total, $interest_total) = (0, 0);
 
+  my @link_ids;
+
   while (my $ref = $sth->fetchrow_hashref()) {
     $fee_remaining      += $form->round_amount($ref->{fee}, 2);
     $fee_remaining      -= $form->round_amount($ref->{max_previous_fee}, 2);
@@ -193,6 +214,7 @@ sub create_invoice_for_fees {
     $interest_remaining += $form->round_amount($ref->{interest}, 2);
     $interest_remaining -= $form->round_amount($ref->{max_previous_interest}, 2);
     $interest_total     += $form->round_amount($ref->{interest}, 2);
+    push @link_ids, $ref->{link_id};
   }
 
   $sth->finish();
@@ -256,6 +278,15 @@ sub create_invoice_for_fees {
              $::myconfig{login});   # employee_id
   do_query($form, $dbh, $query, @values);
 
+  RecordLinks->create_links(
+    'dbh'        => $dbh,
+    'mode'       => 'ids',
+    'from_table' => 'dunning',
+    'from_ids'   => \@link_ids,
+    'to_table'   => 'ar',
+    'to_id'      => $ar_id,
+  );
+
   $query =
     qq|INSERT INTO acc_trans (trans_id, chart_id, amount, transdate, gldate, taxkey, tax_id, chart_link)
        VALUES (?, ?, ?, current_date, current_date, 0,
@@ -282,12 +313,45 @@ sub create_invoice_for_fees {
   $main::lxdebug->leave_sub();
 }
 
+
 sub save_dunning {
+  my ($self, $myconfig, $form, $rows) = @_;
   $main::lxdebug->enter_sub();
 
+  $form->{DUNNING_PDFS_STORAGE} = [];
+
+  # Catch any error, either exception or a call to form->error
+  # and return it to the calling function.
+  my ($error, $rc);
+  eval {
+    local $form->{__ERROR_HANDLER} = sub { die @_ };
+    $rc = SL::DB->client->with_transaction(\&_save_dunning, $self, $myconfig, $form, $rows);
+    1;
+  } or do {
+    $error = $@;
+  };
+
+  # Save PDFs in filemanagement and webdav after transation succeeded,
+  # because otherwise files in the storage may exists if the transaction
+  # failed. Ignore all errros.
+  # Todo: Maybe catch errros and display them as warnings or non fatal errors in the status.
+  if (!$error && $form->{DUNNING_PDFS_STORAGE} && scalar @{ $form->{DUNNING_PDFS_STORAGE} }) {
+    _store_pdf_to_webdav_and_filemanagement($_->{dunning_id}, $_->{path}, $_->{name}) for @{ $form->{DUNNING_PDFS_STORAGE} };
+  }
+
+  $error       = 'unknown errror' if !$error && !$rc;
+  $rc->{error} = $error           if $error;
+
+  $::lxdebug->leave_sub;
+
+  return $rc;
+}
+
+
+sub _save_dunning {
   my ($self, $myconfig, $form, $rows) = @_;
-  # connect to database
-  my $dbh = $form->dbconnect_noauto($myconfig);
+
+  my $dbh = SL::DB->client->dbh;
 
   my ($query, @values);
 
@@ -297,9 +361,9 @@ sub save_dunning {
   my $h_update_ar = prepare_query($form, $dbh, $q_update_ar);
 
   my $q_insert_dunning =
-    qq|INSERT INTO dunning (dunning_id, dunning_config_id, dunning_level, trans_id,
-                            fee,        interest,          transdate,     duedate)
-       VALUES (?, ?,
+    qq|INSERT INTO dunning (id,  dunning_id, dunning_config_id, dunning_level, trans_id,
+                            fee, interest,   transdate,         duedate,       original_invoice_printed)
+       VALUES (?, ?, ?,
                (SELECT dunning_level FROM dunning_config WHERE id = ?),
                ?,
                (SELECT SUM(fee)
@@ -309,14 +373,25 @@ sub save_dunning {
                  * (SELECT interest_rate FROM dunning_config WHERE id = ?)
                  / 360,
                current_date,
-               current_date + (SELECT payment_terms FROM dunning_config WHERE id = ?))|;
+               current_date + (SELECT payment_terms FROM dunning_config WHERE id = ?),
+               ?)|;
   my $h_insert_dunning = prepare_query($form, $dbh, $q_insert_dunning);
 
   my @invoice_ids;
   my ($next_dunning_config_id, $customer_id);
-  my $send_email = 0;
+  my ($send_email, $print_invoice) = (0, 0);
 
   foreach my $row (@{ $rows }) {
+    if ($row->{credit_note}) {
+      my $i = $row->{row};
+      %{ $form->{LIST_CREDIT_NOTES}{$row->{customer_id}}{$row->{invoice_id}} } = (
+        open_amount => $form->{"open_amount_$i"},
+        amount      => $form->{"amount_$i"},
+        invnumber   => $form->{"invnumber_$i"},
+        invdate     => $form->{"invdate_$i"},
+      );
+      next;
+    }
     push @invoice_ids, $row->{invoice_id};
     $next_dunning_config_id = $row->{next_dunning_config_id};
     $customer_id            = $row->{customer_id};
@@ -324,16 +399,31 @@ sub save_dunning {
     @values = ($row->{next_dunning_config_id}, $row->{invoice_id});
     do_statement($form, $h_update_ar, $q_update_ar, @values);
 
-    $send_email |= $row->{email};
+    $send_email       |= $row->{email};
+    $print_invoice    |= $row->{print_invoice};
 
+    my ($row_id)       = selectrow_query($form, $dbh, qq|SELECT nextval('id')|);
     my $next_config_id = conv_i($row->{next_dunning_config_id});
     my $invoice_id     = conv_i($row->{invoice_id});
 
-    @values = ($dunning_id,     $next_config_id, $next_config_id,
-               $invoice_id,     $next_config_id, $invoice_id,
-               $next_config_id, $next_config_id);
+    @values = ($row_id,         $dunning_id,     $next_config_id,
+               $next_config_id, $invoice_id,     $next_config_id,
+               $invoice_id,     $next_config_id, $next_config_id,
+               $print_invoice);
     do_statement($form, $h_insert_dunning, $q_insert_dunning, @values);
+
+    RecordLinks->create_links(
+      'dbh'        => $dbh,
+      'mode'       => 'ids',
+      'from_table' => 'ar',
+      'from_ids'   => $invoice_id,
+      'to_table'   => 'dunning',
+      'to_id'      => $row_id,
+    );
   }
+  # die this transaction, because for this customer only credit notes are
+  # selected ...
+  die "only credit notes are selected for this customer\n" unless $customer_id;
 
   $h_update_ar->finish();
   $h_insert_dunning->finish();
@@ -347,15 +437,15 @@ sub save_dunning {
   $self->print_invoice_for_fees($myconfig, $form, $dunning_id, $dbh);
   $self->print_dunning($myconfig, $form, $dunning_id, $dbh);
 
+  if ($print_invoice) {
+    $self->print_original_invoice($myconfig, $form, $dunning_id, $_) for @invoice_ids;
+  }
 
   if ($send_email) {
     $self->send_email($myconfig, $form, $dunning_id, $dbh);
   }
 
-  $dbh->commit();
-  $dbh->disconnect();
-
-  $main::lxdebug->leave_sub();
+  return ({dunning_id => $dunning_id, print_original_invoice => $print_invoice, send_email => $send_email});
 }
 
 sub send_email {
@@ -366,8 +456,8 @@ sub send_email {
   my $query =
     qq|SELECT
          dcfg.email_body,     dcfg.email_subject, dcfg.email_attachment,
-         c.email AS recipient
-
+         COALESCE (NULLIF(c.invoice_mail, ''), c.email) AS recipient, c.name,
+         (SELECT login from employee where id = ar.employee_id) as invoice_employee_login
        FROM dunning d
        LEFT JOIN dunning_config dcfg ON (d.dunning_config_id = dcfg.id)
        LEFT JOIN ar                  ON (d.trans_id          = ar.id)
@@ -376,19 +466,42 @@ sub send_email {
        LIMIT 1|;
   my $ref = selectfirst_hashref_query($form, $dbh, $query, $dunning_id);
 
-  if (!$ref || !$ref->{recipient} || !$myconfig->{email}) {
+  # without a recipient, we cannot send a mail
+  if (!$ref || !$ref->{recipient}) {
     $main::lxdebug->leave_sub();
-    return;
+    die $main::locale->text("No email recipient for customer #1 defined.", $ref->{name});
+  }
+
+  # without a sender we cannot send a mail
+  # two cases: check mail from 1. current user OR  2. employee who created the invoice
+  my ($from, $sign);
+  if ($::instance_conf->get_dunning_creator eq 'current_employee') {
+    $from = $myconfig->{email};
+    die $main::locale->text('No email for current user #1 defined.', $myconfig->{name}) unless $from;
+  } else {
+    eval {
+      $from = SL::DB::Manager::AuthUser->find_by(login =>  $ref->{invoice_employee_login})->get_config_value("email");
+      $sign = SL::DB::Manager::AuthUser->find_by(login =>  $ref->{invoice_employee_login})->get_config_value("signature");
+      die unless ($from);
+      1;
+    } or die $main::locale->text('No email for user with login #1 defined.', $ref->{invoice_employee_login});
   }
 
+  my $html_template = SL::Template::create(type => 'HTML',      form => $form, myconfig => $myconfig);
+  $html_template->set_tag_style('&lt;%', '%&gt;');
+
   my $template     = SL::Template::create(type => 'PlainText', form => $form, myconfig => $myconfig);
   my $mail         = Mailer->new();
-  $mail->{from}    = $myconfig->{email};
+  $mail->{bcc}     = $form->get_bcc_defaults($myconfig, $form->{bcc});
+  $mail->{from}    = $from;
   $mail->{to}      = $ref->{recipient};
   $mail->{subject} = $template->parse_block($ref->{email_subject});
-  $mail->{message} = $template->parse_block($ref->{email_body});
-
+  $mail->{message} = $html_template->parse_block($ref->{email_body});
+  $mail->{content_type} = 'text/html';
+  my $sign_backup  = $::myconfig{signature};
+  $::myconfig{signature} = $sign if $sign;
   $mail->{message} .= $form->create_email_signature();
+  $::myconfig{signature} = $sign_backup if $sign;
 
   $mail->{message} =~ s/\r\n/\n/g;
 
@@ -396,6 +509,11 @@ sub send_email {
     $mail->{attachments} = $form->{DUNNING_PDFS_EMAIL};
   }
 
+  $query  = qq|SELECT id FROM dunning WHERE dunning_id = ?|;
+  my @ids = selectall_array_query($form, $dbh, $query, $dunning_id);
+  $mail->{record_id}   = \@ids;
+  $mail->{record_type} = 'dunning';
+
   $mail->send();
 
   $main::lxdebug->leave_sub();
@@ -460,11 +578,11 @@ sub set_template_options {
   # prepare meta information for template introspection
   $form->{template_meta} = {
     formname  => $form->{formname},
-    language  => SL::DB::Manager::Language->find_by_or_create(id => $form->{language_id}),
+    language  => SL::DB::Manager::Language->find_by_or_create(id => $form->{language_id} || undef),
     format    => $form->{format},
     media     => $form->{media},
     extension => $extension,
-    printer   => SL::DB::Manager::Printer->find_by_or_create(id => $form->{printer_id}),
+    printer   => SL::DB::Manager::Printer->find_by_or_create(id => $form->{printer_id} || undef),
     today     => DateTime->today,
   };
 
@@ -478,7 +596,7 @@ sub get_invoices {
   my ($self, $myconfig, $form) = @_;
 
   # connect to database
-  my $dbh = $form->dbconnect($myconfig);
+  my $dbh = SL::DB->client->dbh;
 
   my $where;
   my @values;
@@ -491,7 +609,12 @@ sub get_invoices {
 
   } elsif ($form->{customer}) {
     $where .= qq| AND (ct.name ILIKE ?)|;
-    push(@values, '%' . $form->{customer} . '%');
+    push(@values, like($form->{customer}));
+  }
+
+  if ($form->{department_id}) {
+    $where .= qq| AND (a.department_id = ?)|;
+    push(@values, $form->{department_id});
   }
 
   my %columns = (
@@ -503,7 +626,7 @@ sub get_invoices {
   foreach my $key (keys(%columns)) {
     next unless ($form->{$key});
     $where .= qq| AND $columns{$key} ILIKE ?|;
-    push(@values, '%' . $form->{$key} . '%');
+    push(@values, like($form->{$key}));
   }
 
   if ($form->{dunning_level}) {
@@ -514,7 +637,7 @@ sub get_invoices {
   $form->{minamount} = $form->parse_amount($myconfig,$form->{minamount});
   if ($form->{minamount}) {
     $where .= qq| AND ((a.amount - a.paid) > ?) |;
-    push(@values, $form->{minamount});
+    push(@values, trim($form->{minamount}));
   }
 
   my $query =
@@ -526,6 +649,7 @@ sub get_invoices {
   if (!$form->{l_include_direct_debit}) {
     $where .= qq| AND NOT COALESCE(a.direct_debit, FALSE) |;
   }
+  my $paid = ($form->{l_include_credit_notes}) ? "WHERE (a.paid <> a.amount)" : "WHERE (a.paid < a.amount)";
 
   $query =
     qq|SELECT
@@ -533,7 +657,9 @@ sub get_invoices {
          ct.name AS customername, a.customer_id, a.duedate,
          a.amount - a.paid AS open_amount,
          a.direct_debit,
-
+         pt.description as payment_term,
+         dep.description as departmentname,
+         ct.invoice_mail AS cv_email,
          cfg.dunning_description, cfg.dunning_level,
 
          d.transdate AS dunning_date, d.duedate AS dunning_duedate,
@@ -545,11 +671,13 @@ sub get_invoices {
 
          nextcfg.dunning_description AS next_dunning_description,
          nextcfg.id AS next_dunning_config_id,
-         nextcfg.terms, nextcfg.active, nextcfg.email
+         nextcfg.terms, nextcfg.active, nextcfg.email, nextcfg.print_original_invoice
 
        FROM ar a
 
        LEFT JOIN customer ct ON (a.customer_id = ct.id)
+       LEFT JOIN department dep ON (a.department_id = dep.id)
+       LEFT JOIN payment_terms pt ON (a.payment_id = pt.id)
        LEFT JOIN dunning_config cfg ON (a.dunning_config_id = cfg.id)
        LEFT JOIN dunning_config nextcfg ON
          (nextcfg.id =
@@ -572,9 +700,8 @@ sub get_invoices {
          WHERE (d2.trans_id      = a.id)
            AND (d2.dunning_level = cfg.dunning_level)
        ))
-
-       WHERE (a.paid < a.amount)
-         AND (a.duedate < current_date)
+        $paid
+        AND (a.duedate < current_date)
 
        $where
 
@@ -585,7 +712,7 @@ sub get_invoices {
 
   while (my $ref = $sth->fetchrow_hashref("NAME_lc")) {
     next if ($ref->{pastdue} < $ref->{terms});
-
+    $ref->{credit_note} = 1 if ($ref->{amount} < 0 && $form->{l_include_credit_notes});
     $ref->{interest} = $form->round_amount($ref->{interest}, 2);
     push(@{ $form->{DUNNINGS} }, $ref);
   }
@@ -595,7 +722,6 @@ sub get_invoices {
   $query = qq|SELECT id, dunning_description FROM dunning_config ORDER BY dunning_level|;
   $form->{DUNNING_CONFIG} = selectall_hashref_query($form, $dbh, $query);
 
-  $dbh->disconnect;
   $main::lxdebug->leave_sub();
 }
 
@@ -606,7 +732,7 @@ sub get_dunning {
   my ($self, $myconfig, $form) = @_;
 
   # connect to database
-  my $dbh = $form->dbconnect($myconfig);
+  my $dbh = SL::DB->client->dbh;
 
   my $where = qq| WHERE (da.trans_id = a.id)|;
 
@@ -618,7 +744,7 @@ sub get_dunning {
 
   } elsif ($form->{customer}) {
     $where .= qq| AND (ct.name ILIKE ?)|;
-    push(@values, '%' . $form->{customer} . '%');
+    push(@values, like($form->{customer}));
   }
 
   my %columns = (
@@ -629,7 +755,12 @@ sub get_dunning {
   foreach my $key (keys(%columns)) {
     next unless ($form->{$key});
     $where .= qq| AND $columns{$key} ILIKE ?|;
-    push(@values, '%' . $form->{$key} . '%');
+    push(@values, like($form->{$key}));
+  }
+
+  if ($form->{dunning_id}) {
+    $where .= qq| AND da.dunning_id = ?|;
+    push(@values, conv_i($form->{dunning_id}));
   }
 
   if ($form->{dunning_level}) {
@@ -675,13 +806,14 @@ sub get_dunning {
   }
 
   my %sort_columns = (
-    'dunning_description' => [ qw(dn.dunning_description customername invnumber) ],
-    'customername'        => [ qw(customername invnumber) ],
+    'dunning_description' => [ qw(dn.dunning_description da.dunning_id customername invnumber) ],
+    'customername'        => [ qw(customername da.dunning_id invnumber) ],
     'invnumber'           => [ qw(a.invnumber) ],
     'transdate'           => [ qw(a.transdate a.invnumber) ],
     'duedate'             => [ qw(a.duedate a.invnumber) ],
-    'dunning_date'        => [ qw(dunning_date a.invnumber) ],
-    'dunning_duedate'     => [ qw(dunning_duedate a.invnumber) ],
+    'dunning_date'        => [ qw(dunning_date da.dunning_id a.invnumber) ],
+    'dunning_duedate'     => [ qw(dunning_duedate da.dunning_id a.invnumber) ],
+    'dunning_id'          => [ qw(dunning_id a.invnumber) ],
     'salesman'            => [ qw(salesman) ],
     );
 
@@ -692,8 +824,9 @@ sub get_dunning {
   my $query =
     qq|SELECT a.id, a.ordnumber, a.invoice, a.transdate, a.invnumber, a.amount, a.language_id,
          ct.name AS customername, ct.id AS customer_id, a.duedate, da.fee,
-         da.interest, dn.dunning_description, da.transdate AS dunning_date,
+         da.interest, dn.dunning_description, dn.dunning_level, da.transdate AS dunning_date,
          da.duedate AS dunning_duedate, da.dunning_id, da.dunning_config_id,
+         da.id AS dunning_table_id,
          e2.name AS salesman
        FROM ar a
        JOIN customer ct ON (a.customer_id = ct.id)
@@ -708,7 +841,6 @@ sub get_dunning {
     map { $ref->{$_} = $form->format_amount($myconfig, $ref->{$_}, 2)} qw(amount fee interest);
   }
 
-  $dbh->disconnect;
   $main::lxdebug->leave_sub();
 }
 
@@ -716,7 +848,7 @@ sub melt_pdfs {
 
   $main::lxdebug->enter_sub();
 
-  my ($self, $myconfig, $form, $copies) = @_;
+  my ($self, $myconfig, $form, $copies, %params) = @_;
 
   # Don't allow access outside of $spool.
   map { $_ =~ s|.*/||; } @{ $form->{DUNNING_PDFS} };
@@ -732,23 +864,30 @@ sub melt_pdfs {
   my $in = IO::File->new($::lx_office_conf{applications}->{ghostscript} . " -dBATCH -dNOPAUSE -q -sDEVICE=pdfwrite -sOutputFile=- $inputfiles |");
   $form->error($main::locale->text('Could not spawn ghostscript.')) unless $in;
 
-  if ($form->{media} eq 'printer') {
-    $form->get_printer_code($myconfig);
-    my $out;
-    if ($form->{printer_command}) {
-      $out = IO::File->new("| $form->{printer_command}");
-    }
+  my $dunning_filename    = $form->get_formname_translation('dunning');
+  my $attachment_filename = "${dunning_filename}_${dunning_id}.pdf";
+  my $content;
+  if ($params{return_content}) {
+    $content = read_file($in);
 
-    $::locale->with_raw_io($out, sub { $out->print($_) while <$in> });
+  } else {
+    if ($form->{media} eq 'printer') {
+      $form->get_printer_code($myconfig);
+      my $out;
+      if ($form->{printer_command}) {
+        $out = IO::File->new("| $form->{printer_command}");
+      }
 
-    $form->error($main::locale->text('Could not spawn the printer command.')) unless $out;
+      $form->error($main::locale->text('Could not spawn the printer command.')) unless $out;
 
-  } else {
-    my $dunning_filename = $form->get_formname_translation('dunning');
-    print qq|Content-Type: Application/PDF\n| .
-          qq|Content-Disposition: attachment; filename="${dunning_filename}_${dunning_id}.pdf"\n\n|;
+      $::locale->with_raw_io($out, sub { $out->print($_) while <$in> });
 
-    $::locale->with_raw_io(\*STDOUT, sub { print while <$in> });
+    } else {
+      print qq|Content-Type: Application/PDF\n| .
+            qq|Content-Disposition: attachment; filename=$attachment_filename\n\n|;
+
+      $::locale->with_raw_io(\*STDOUT, sub { print while <$in> });
+    }
   }
 
   $in->close();
@@ -756,6 +895,7 @@ sub melt_pdfs {
   map { unlink("$spool/$_") } @{ $form->{DUNNING_PDFS} };
 
   $main::lxdebug->leave_sub();
+  return ($attachment_filename, $content) if $params{return_content};
 }
 
 sub print_dunning {
@@ -764,7 +904,7 @@ sub print_dunning {
   my ($self, $myconfig, $form, $dunning_id, $provided_dbh) = @_;
 
   # connect to database
-  my $dbh = $provided_dbh ? $provided_dbh : $form->dbconnect_noauto($myconfig);
+  my $dbh = $provided_dbh || SL::DB->client->dbh;
 
   $dunning_id =~ s|[^\d]||g;
 
@@ -790,7 +930,9 @@ sub print_dunning {
          ar.transdate,       ar.duedate,      ar.customer_id,
          ar.invnumber,       ar.ordnumber,    ar.cp_id,
          ar.amount,          ar.netamount,    ar.paid,
-         (SELECT cu.name FROM currencies cu WHERE cu.id=ar.currency_id) AS curr,
+         ar.employee_id,     ar.salesman_id,
+         (SELECT cu.name FROM currencies cu WHERE cu.id = ar.currency_id) AS curr,
+         (SELECT description from department WHERE id = ar.department_id) AS department,
          ar.amount - ar.paid AS open_amount,
          ar.amount - ar.paid + da.fee + da.interest AS linetotal
 
@@ -813,19 +955,29 @@ sub print_dunning {
   }
   $sth->finish();
 
+  # if we have some credit notes to add, do a safety check on the first customer id
+  # and add one entry for each credit note
+  if ($form->{LIST_CREDIT_NOTES} && $form->{LIST_CREDIT_NOTES}->{$form->{TEMPLATE_ARRAYS}->{"dn_customer_id"}[0]}) {
+    my $first_customer_id = $form->{TEMPLATE_ARRAYS}->{"dn_customer_id"}[0];
+    while ( my ($cred_id, $value) = each(%{ $form->{LIST_CREDIT_NOTES}->{$first_customer_id} } ) ) {
+      map { push @{ $form->{TEMPLATE_ARRAYS}->{"dn_$_"} }, $value->{$_} } keys %{ $value };
+    }
+  }
   $query =
     qq|SELECT
          c.id AS customer_id, c.name,         c.street,       c.zipcode,   c.city,
          c.country,           c.department_1, c.department_2, c.email,     c.customernumber,
          c.greeting,          c.contact,      c.phone,        c.fax,       c.homepage,
          c.email,             c.taxincluded,  c.business_id,  c.taxnumber, c.iban,
-         c,ustid,             e.name as salesman_name,
+         c.ustid,             c.currency_id,  curr.name as currency,
+         ar.id AS invoice_id,
          co.*
        FROM dunning d
        LEFT JOIN ar          ON (d.trans_id = ar.id)
        LEFT JOIN customer c  ON (ar.customer_id = c.id)
        LEFT JOIN contacts co ON (ar.cp_id = co.cp_id)
        LEFT JOIN employee e  ON (ar.salesman_id = e.id)
+       LEFT JOIN currencies curr ON (c.currency_id = curr.id)
        WHERE (d.dunning_id = ?)
        LIMIT 1|;
   my $ref = selectfirst_hashref_query($form, $dbh, $query, $dunning_id);
@@ -833,7 +985,7 @@ sub print_dunning {
 
   $query =
     qq|SELECT
-         cfg.interest_rate, cfg.template AS formname,
+         cfg.interest_rate, cfg.template AS formname, cfg.dunning_level,
          cfg.email_subject, cfg.email_body, cfg.email_attachment,
          d.transdate AS dunning_date,
          (SELECT SUM(fee)
@@ -861,8 +1013,16 @@ sub print_dunning {
   $form->{interest_rate}     = $form->format_amount($myconfig, $ref->{interest_rate} * 100);
   $form->{fee}               = $form->format_amount($myconfig, $ref->{fee}, 2);
   $form->{total_interest}    = $form->format_amount($myconfig, $form->round_amount($ref->{total_interest}, 2), 2);
-  $form->{total_open_amount} = $form->format_amount($myconfig, $form->round_amount($ref->{total_open_amount}, 2), 2);
-  $form->{total_amount}      = $form->format_amount($myconfig, $form->round_amount($ref->{fee} + $ref->{total_interest} + $ref->{total_open_amount}, 2), 2);
+  my $total_open_amount      = $ref->{total_open_amount};
+  if ($form->{l_include_credit_notes}) {
+    # a bit stupid, but redo calc because of credit notes
+    $total_open_amount      = 0;
+    foreach my $amount (@{ $form->{TEMPLATE_ARRAYS}->{dn_open_amount} }) {
+      $total_open_amount += $form->parse_amount($myconfig, $amount, 2);
+    }
+  }
+  $form->{total_open_amount} = $form->format_amount($myconfig, $form->round_amount($total_open_amount, 2), 2);
+  $form->{total_amount}      = $form->format_amount($myconfig, $form->round_amount($ref->{fee} + $ref->{total_interest} + $total_open_amount, 2), 2);
 
   $::form->format_dates($output_dateformat, $output_longdates,
     qw(dn_dunning_date dn_dunning_duedate dn_transdate dn_duedate
@@ -885,13 +1045,29 @@ sub print_dunning {
 
   delete $form->{tmpfile};
 
-  push @{ $form->{DUNNING_PDFS} }, $filename;
-  push @{ $form->{DUNNING_PDFS_EMAIL} }, { 'filename' => "${spool}/$filename",
-                                           'name'     => $form->get_formname_translation('dunning') . "_${dunning_id}.pdf" };
+  my $employee_id = ($::instance_conf->get_dunning_creator eq 'invoice_employee') ?
+                      $form->{employee_id}                                        :
+                      SL::DB::Manager::Employee->current->id;
+
+  $form->get_employee_data('prefix' => 'employee', 'id' => $employee_id);
+  $form->get_employee_data('prefix' => 'salesman', 'id' => $form->{salesman_id});
+
+  $form->{attachment_type}    = "dunning";
+  if ( $form->{dunning_level} ) {
+    $form->{attachment_type} .= $form->{dunning_level} if $form->{dunning_level} < 4;
+  }
+  $form->{attachment_filename} = $form->get_formname_translation($form->{attachment_type}) . "_${dunning_id}.pdf";
+  $form->{attachment_id} = $form->{invoice_id};
 
+  # this generates the file in the spool directory
   $form->parse_template($myconfig);
 
-  $dbh->disconnect() unless $provided_dbh;
+  push @{ $form->{DUNNING_PDFS} }        , $filename;
+  push @{ $form->{DUNNING_PDFS_EMAIL} }  , { 'path'       => "${spool}/$filename",
+                                             'name'       => $form->get_formname_translation('dunning') . "_${dunning_id}.pdf" };
+  push @{ $form->{DUNNING_PDFS_STORAGE} }, { 'dunning_id' => $dunning_id,
+                                             'path'       => "${spool}/$filename",
+                                             'name'       => $form->get_formname_translation('dunning') . "_${dunning_id}.pdf" };
 
   $main::lxdebug->leave_sub();
 }
@@ -901,18 +1077,20 @@ sub print_invoice_for_fees {
 
   my ($self, $myconfig, $form, $dunning_id, $provided_dbh) = @_;
 
-  my $dbh = $provided_dbh ? $provided_dbh : $form->dbconnect($myconfig);
+  my $dbh = $provided_dbh || SL::DB->client->dbh;
 
   my ($query, @values, $sth);
 
   $query =
     qq|SELECT
          d.fee_interest_ar_id,
-         dcfg.template
+         d.trans_id AS invoice_id,
+         dcfg.template,
+         dcfg.dunning_level
        FROM dunning d
        LEFT JOIN dunning_config dcfg ON (d.dunning_config_id = dcfg.id)
        WHERE d.dunning_id = ?|;
-  my ($ar_id, $template) = selectrow_query($form, $dbh, $query, $dunning_id);
+  my ($ar_id, $invoice_id, $template, $dunning_level) = selectrow_query($form, $dbh, $query, $dunning_id);
 
   if (!$ar_id) {
     $main::lxdebug->leave_sub();
@@ -971,7 +1149,7 @@ sub print_invoice_for_fees {
   $self->set_customer_cvars($myconfig, $form);
   $self->set_template_options($myconfig, $form);
 
-  my $filename = Common::unique_id() . "dunning_invoice_${dunning_id}.pdf";
+  my $filename = Common::unique_id() . "dunning_invoice_" . $form->{invnumber} . ".pdf";
 
   my $spool             = $::lx_office_conf{paths}->{spool};
   $form->{OUT}          = "$spool/$filename";
@@ -980,15 +1158,20 @@ sub print_invoice_for_fees {
 
   map { delete $form->{$_} } grep /^[a-z_]+_\d+$/, keys %{ $form };
 
+  my $attachment_filename      = $form->get_formname_translation('dunning_invoice') . "_" . $form->{invnumber} . ".pdf";
+  $form->{attachment_filename} = $attachment_filename;
+  $form->{attachment_type}     = "dunning";
+  $form->{attachment_id}       = $invoice_id;
   $form->parse_template($myconfig);
 
   restore_form($saved_form);
 
-  push @{ $form->{DUNNING_PDFS} }, $filename;
-  push @{ $form->{DUNNING_PDFS_EMAIL} }, { 'filename' => "${spool}/$filename",
-                                           'name'     => "dunning_invoice_${dunning_id}.pdf" };
-
-  $dbh->disconnect() unless $provided_dbh;
+  push @{ $form->{DUNNING_PDFS} },         $filename;
+  push @{ $form->{DUNNING_PDFS_EMAIL} },   { 'path'       => "${spool}/$filename",
+                                             'name'       => $attachment_filename };
+  push @{ $form->{DUNNING_PDFS_STORAGE} }, { 'dunning_id' => $dunning_id,
+                                             'path'       => "${spool}/$filename",
+                                             'name'       => $attachment_filename };
 
   $main::lxdebug->leave_sub();
 }
@@ -1005,6 +1188,108 @@ sub set_customer_cvars {
                                                   translation_type => 'greetings::' . ($form->{cp_gender} eq 'f' ? 'female' : 'male'),
                                                   language_id      => $form->{language_id},
                                                   allow_fallback   => 1);
+  if ($form->{cp_id}) {
+    $custom_variables = CVar->get_custom_variables(dbh      => $form->get_standard_dbh,
+                                                   module   => 'Contacts',
+                                                   trans_id => $form->{cp_id});
+    $form->{"cp_cvar_$_->{name}"} = $_->{value} for @{ $custom_variables };
+  }
+
 }
 
+sub print_original_invoice {
+  my ($self, $myconfig, $form, $dunning_id, $invoice_id) = @_;
+  # get one invoice as object and print to pdf
+  my $invoice = SL::DB::Invoice->new(id => $invoice_id)->load;
+
+  die "Invalid invoice object" unless ref($invoice) eq 'SL::DB::Invoice';
+
+  my $print_form          = Form->new('');
+  $print_form->{type}     = 'invoice';
+  $print_form->{formname} = 'invoice',
+  $print_form->{format}   = 'pdf',
+  $print_form->{media}    = 'file';
+  # no language override, should always be the object's language
+  $invoice->flatten_to_form($print_form, format_amounts => 1);
+  for my $i (1 .. $print_form->{rowcount}) {
+    $print_form->{"sellprice_$i"} = $print_form->{"fxsellprice_$i"};
+  }
+  $print_form->prepare_for_printing;
+
+  my $filename = SL::Helper::CreatePDF->create_pdf(
+                   template               => 'invoice.tex',
+                   variables              => $print_form,
+                   return                 => 'file_name',
+                   variable_content_types => {
+                     longdescription => 'html',
+                     partnotes       => 'html',
+                     notes           => 'html',
+                     $print_form->get_variable_content_types_for_cvars,
+                   },
+  );
+
+  my $spool       = $::lx_office_conf{paths}->{spool};
+  my ($volume, $directory, $file_name) = File::Spec->splitpath($filename);
+  my $full_file_name                   = File::Spec->catfile($spool, $file_name);
+
+  move($filename, $full_file_name) or die "The move operation failed: $!";
+
+  # form get_formname_translation should use language_id_$i
+  my $saved_reicpient_locale = $form->{recipient_locale};
+  $form->{recipient_locale}  = $invoice->language;
+
+  my $attachment_filename    = $form->get_formname_translation('invoice') . "_" . $invoice->invnumber . ".pdf";
+
+  push @{ $form->{DUNNING_PDFS} },         $file_name;
+  push @{ $form->{DUNNING_PDFS_EMAIL} },   { 'path'       => "${spool}/$file_name",
+                                             'name'       => $attachment_filename };
+  push @{ $form->{DUNNING_PDFS_STORAGE} }, { 'dunning_id' => $dunning_id,
+                                             'path'       => "${spool}/$file_name",
+                                             'name'       => $attachment_filename };
+
+  $form->{recipient_locale}  = $saved_reicpient_locale;
+}
+
+sub _store_pdf_to_webdav_and_filemanagement {
+  my ($dunning_id, $path, $name) =@_;
+
+  my @errors;
+
+  if ($::instance_conf->get_doc_storage) {
+    eval {
+      SL::File->save(
+        object_id   => $dunning_id,
+        object_type => 'dunning',
+        mime_type   => 'application/pdf',
+        source      => 'created',
+        file_type   => 'document',
+        file_name   => $name,
+        file_path   => $path,
+      );
+      1;
+    } or do {
+      push @errors, $::locale->text('Storing PDF in storage backend failed: #1', $@);
+    };
+  }
+
+  if ($::instance_conf->get_webdav_documents) {
+    eval {
+      my $webdav = SL::Webdav->new(
+        type     => 'dunning',
+        number   => $dunning_id,
+      );
+      my $webdav_file = SL::Webdav::File->new(
+        webdav   => $webdav,
+        filename => $name,
+      );
+      $webdav_file->store(file => $path);
+    } or do {
+      push @errors, $::locale->text('Storing PDF to webdav folder failed: #1', $@);
+    };
+  }
+
+  return @errors;
+}
+
+
 1;