Merge pull request #11 from freiphone/patch-3
authorMoritz Bunkus <moritz@bunkus.org>
Thu, 24 Aug 2017 06:20:39 +0000 (08:20 +0200)
committerGitHub <noreply@github.com>
Thu, 24 Aug 2017 06:20:39 +0000 (08:20 +0200)
Einkaufsrechnungen: Bearbeiter*in & Verkäufer*in mit aktueller Benutz…

62 files changed:
SL/ClientJS.pm
SL/Controller/BankTransaction.pm
SL/Controller/CsvImport/Base.pm
SL/Controller/CsvImport/BaseMulti.pm
SL/Controller/DeliveryValueReport.pm
SL/Controller/Helper/GetModels/Paginated.pm
SL/Controller/Helper/ReportGenerator.pm
SL/Controller/Order.pm
SL/DATEV.pm
SL/DB/BankTransaction.pm
SL/DB/Helper/CustomVariables.pm
SL/Dev/ALL.pm
SL/Dev/CustomerVendor.pm
SL/Dev/File.pm
SL/Dev/Inventory.pm
SL/Dev/Part.pm
SL/Dev/Payment.pm
SL/Dev/Record.pm
SL/Form.pm
SL/Helper/ShippedQty.pm
SL/IS.pm
SL/WH.pm
bin/mozilla/ap.pl
bin/mozilla/ar.pl
bin/mozilla/do.pl
bin/mozilla/generictranslations.pl
bin/mozilla/gl.pl
bin/mozilla/io.pl
doc/changelog
locale/de/all
locale/de/special_chars
locale/en/special_chars
menus/user/00-erp.yaml
scripts/console
t/background_job/create_periodic_invoices.t
t/bank/bank_transactions.t
t/controllers/csvimport/artransactions.t
t/controllers/csvimport/parts.t
t/controllers/financial_controlling/sales_order_with_periodic_invoices_config.t
t/controllers/financial_overview/sales_orders.t
t/controllers/project/project_linked_records.t
t/datev/invoices.t
t/db_helper/convert_invoice.t
t/db_helper/payment.t
t/db_helper/price_tax_calculator.t
t/db_helper/with_transaction.t
t/file/filesystem.t
t/helper/shipped_qty.t
t/part/assembly.t
t/part/assortment.t
t/part/stock.t
t/wh/transfer.t
templates/webpages/bank_transactions/_template_list.html
templates/webpages/bank_transactions/create_invoice.html
templates/webpages/common/_send_email_dialog.html
templates/webpages/common/flash.html
templates/webpages/datev/export_bewegungsdaten.html
templates/webpages/delivery_plan/_filter.html
templates/webpages/delivery_value_report/_filter.html
templates/webpages/do/form_header.html
templates/webpages/generictranslations/edit_email_strings.html [new file with mode: 0644]
templates/webpages/ic/search.html

index 6ef639d..a134cad 100644 (file)
@@ -75,7 +75,7 @@ my %supported_methods = (
 
   # ## jQuery UI dialog plugin ## pattern: $(<TARGET>).dialog('<FUNCTION>')
 
-  # Opening and closing and closing a popup
+  # Opening and closing a popup
   'dialog:open'          => 1, # kivi.popup_dialog(<TARGET>)
   'dialog:close'         => 1,
 
@@ -352,7 +352,7 @@ This module enables the generation of jQuery-using JavaScript code on
 the server side. That code is then evaluated in a safe way on the
 client side.
 
-The workflow is usally that the client creates an AJAX request, the
+The workflow is usually that the client creates an AJAX request, the
 server creates some actions and sends them back, and the client then
 implements each of these actions.
 
@@ -662,7 +662,7 @@ C<select_node>, C<deselect_node>, C<deselect_all>
 
 =head1 ADDING SUPPORT FOR ADDITIONAL FUNCTIONS
 
-In order not having to maintain two files (this one and
+In order to not have to maintain two files (this one and
 C<js/client_js.js>) there's a script that can parse this file's
 C<%supported_methods> definition and generate the file
 C<js/client_js.js> accordingly. The steps are:
index 0382673..e772406 100644 (file)
@@ -101,7 +101,6 @@ sub action_list {
       @where
     ],
   );
-  $main::lxdebug->message(LXDebug->DEBUG2(),"count bt=".scalar(@{$bank_transactions}." bank_account=".$bank_account->id." chart=".$bank_account->chart_id));
 
   # credit notes have a negative amount, treat differently
   my $all_open_ar_invoices = SL::DB::Manager::Invoice        ->get_all(where => [ or => [ amount => { gt => \'paid' },
@@ -115,43 +114,30 @@ sub action_list {
   my $all_open_ap_invoices = SL::DB::Manager::PurchaseInvoice->get_all(where => [amount => { ne => \'paid' }], with_objects => ['vendor'  ,'payment_terms']);
   my $all_open_sepa_export_items = SL::DB::Manager::SepaExportItem->get_all(where => [chart_id => $bank_account->chart_id ,
                                                                              'sepa_export.executed' => 0, 'sepa_export.closed' => 0 ], with_objects => ['sepa_export']);
-  $main::lxdebug->message(LXDebug->DEBUG2(),"count sepaexport=".scalar(@{$all_open_sepa_export_items}));
 
   my @all_open_invoices;
   # filter out invoices with less than 1 cent outstanding
   push @all_open_invoices, map { $_->{is_ar}=1 ; $_ } grep { abs($_->amount - $_->paid) >= 0.01 } @{ $all_open_ar_invoices };
   push @all_open_invoices, map { $_->{is_ar}=0 ; $_ } grep { abs($_->amount - $_->paid) >= 0.01 } @{ $all_open_ap_invoices };
-  $main::lxdebug->message(LXDebug->DEBUG2(),"bank_account=".$::form->{filter}{bank_account}." invoices: ".scalar(@{ $all_open_ar_invoices }).
-                              " + ".scalar(@{ $all_open_ap_invoices })." non fully paid=".scalar(@all_open_invoices)." transactions=".scalar(@{ $bank_transactions }));
 
-  my @all_sepa_invoices;
-  my @all_non_sepa_invoices;
   my %sepa_exports;
   # first collect sepa export items to open invoices
   foreach my $open_invoice (@all_open_invoices){
-    #    my @items =  grep { $_->ap_id == $open_invoice->id ||  $_->ar_id == $open_invoice->id } @{$all_open_sepa_export_items};
     $open_invoice->{realamount}  = $::form->format_amount(\%::myconfig,$open_invoice->amount,2);
     $open_invoice->{skonto_type} = 'without_skonto';
     foreach ( @{$all_open_sepa_export_items}) {
-      if ( $_->ap_id == $open_invoice->id ||  $_->ar_id == $open_invoice->id ) {
-        my $factor = ($_->ar_id == $open_invoice->id?1:-1);
-        $main::lxdebug->message(LXDebug->DEBUG2(),"exitem=".$_->id." for invoice ".$open_invoice->id." factor=".$factor);
+      if (($_->ap_id && $_->ap_id == $open_invoice->id) || ($_->ar_id && $_->ar_id == $open_invoice->id)) {
+        my $factor                   = ($_->ar_id == $open_invoice->id ? 1 : -1);
+        #$main::lxdebug->message(LXDebug->DEBUG2(),"sepa_exitem=".$_->id." for invoice ".$open_invoice->id." factor=".$factor);
         $open_invoice->{realamount}  = $::form->format_amount(\%::myconfig,$open_invoice->amount*$factor,2);
-        $open_invoice->{sepa_export_item} = $_ ;
         $open_invoice->{skonto_type} = $_->payment_type;
         $sepa_exports{$_->sepa_export_id} ||= { count => 0, is_ar => 0, amount => 0, proposed => 0, invoices => [], item => $_ };
-        $sepa_exports{$_->sepa_export_id}->{count}++ ;
+        $sepa_exports{$_->sepa_export_id}->{count}++;
         $sepa_exports{$_->sepa_export_id}->{is_ar}++ if  $_->ar_id == $open_invoice->id;
         $sepa_exports{$_->sepa_export_id}->{amount} += $_->amount * $factor;
         push @{ $sepa_exports{$_->sepa_export_id}->{invoices} }, $open_invoice;
-        #$main::lxdebug->message(LXDebug->DEBUG2(),"amount for export id ".$_->sepa_export_id." = ".
-        #                          $sepa_exports{$_->sepa_export_id}->{amount}." count = ".
-        #                          $sepa_exports{$_->sepa_export_id}->{count}." is_ar = ".
-        #                          $sepa_exports{$_->sepa_export_id}->{is_ar} );
-        push @all_sepa_invoices , $open_invoice;
       }
     }
-    push @all_non_sepa_invoices , $open_invoice if ! $open_invoice->{sepa_export_item};
   }
 
   # try to match each bank_transaction with each of the possible open invoices
@@ -162,55 +148,26 @@ sub action_list {
     ## 5 Stellen hinter dem Komma auf 2 Stellen reduzieren
     $bt->amount($bt->amount*1);
     $bt->invoice_amount($bt->invoice_amount*1);
-    $main::lxdebug->message(LXDebug->DEBUG2(),"BT ".$bt->id." amount=".$bt->amount." invoice_amount=".$bt->invoice_amount." remote=". $bt->{remote_name});
 
     $bt->{proposals}    = [];
     $bt->{rule_matches} = [];
 
     $bt->{remote_name} .= $bt->{remote_name_1} if $bt->{remote_name_1};
 
-    if ( $self->is_collective_transaction($bt) ) {
+    if ( $bt->is_batch_transaction ) {
       foreach ( keys  %sepa_exports) {
-        #$main::lxdebug->message(LXDebug->DEBUG2(),"Exp ID=".$_." compare sum amount ".($sepa_exports{$_}->{amount} *1) ." == ".($bt->amount * 1));
-        if ( $bt->transaction_code eq '191' && abs(($sepa_exports{$_}->{amount} * 1) - ($bt->amount * 1)) < 0.01 ) {
+        if ( abs(($sepa_exports{$_}->{amount} * 1) - ($bt->amount * 1)) < 0.01 ) {
           ## jupp
           @{$bt->{proposals}} = @{$sepa_exports{$_}->{invoices}};
-          $bt->{agreement}    = 20;
-          push(@{$bt->{rule_matches}},'sepa_export_item(20)');
+          $bt->{sepa_export_ok} = 1;
           $sepa_exports{$_}->{proposed}=1;
-          #$main::lxdebug->message(LXDebug->DEBUG2(),"has ".scalar($bt->{proposals})." invoices");
           push(@proposals, $bt);
           next;
         }
       }
-    }
-    next unless $bt->{remote_name};  # bank has no name, usually fees, use create invoice to assign
-
-    foreach ( @{$all_open_sepa_export_items}) {
-      last if scalar (@all_sepa_invoices) == 0;
-      foreach my $open_invoice (@all_sepa_invoices){
-        $open_invoice->{agreement}    = 0;
-        $open_invoice->{rule_matches} ='';
-        if ( $_->ap_id == $open_invoice->id ||  $_->ar_id == $open_invoice->id ) {
-          #$main::lxdebug->message(LXDebug->DEBUG2(),"exitem2=".$_->id." for invoice ".$open_invoice->id);
-          my $factor = ( $_->ar_id == $open_invoice->id?1:-1);
-          $_->amount($_->amount*1);
-          #$main::lxdebug->message(LXDebug->DEBUG2(),"remote account '".$bt->{remote_account_number}."' bt_amount=".$bt->amount." factor=".$factor);
-          #$main::lxdebug->message(LXDebug->DEBUG2(),"compare with   '".$_->vc_iban."'    amount=".$_->amount);
-          if ( $bt->{remote_account_number} eq $_->vc_iban && abs(abs($_->amount) - abs($bt->amount)) < 0.01 ) {
-            my $iban;
-            $iban = $open_invoice->customer->iban if $open_invoice->is_sales;
-            $iban = $open_invoice->vendor->iban   if ! $open_invoice->is_sales;
-            if($bt->{remote_account_number} eq $iban && abs(abs($open_invoice->amount) - abs($bt->amount)) < 0.01 ) {
-              ($open_invoice->{agreement}, $open_invoice->{rule_matches}) = $bt->get_agreement_with_invoice($open_invoice);
-              $open_invoice->{agreement} += 5;
-              $open_invoice->{rule_matches} .= 'sepa_export_item(5) ';
-              $main::lxdebug->message(LXDebug->DEBUG2(),"sepa invoice_id=".$open_invoice->id." agreement=".$open_invoice->{agreement}." rules matches=".$open_invoice->{rule_matches});
-              $open_invoice->{realamount} = $::form->format_amount(\%::myconfig,$open_invoice->amount*$factor,2);
-            }
-          }
-        }
-      }
+      # batch transaction has no remotename !!
+    } else {
+      next unless $bt->{remote_name};  # bank has no name, usually fees, use create invoice to assign
     }
 
     # try to match the current $bt to each of the open_invoices, saving the
@@ -222,7 +179,7 @@ sub action_list {
     # the arrays $bt->{proposals} and $bt->{rule_matches}, and the agreement
     # score is stored in $bt->{agreement}
 
-    foreach my $open_invoice (@all_non_sepa_invoices, @all_sepa_invoices) {
+    foreach my $open_invoice (@all_open_invoices) {
       ($open_invoice->{agreement}, $open_invoice->{rule_matches}) = $bt->get_agreement_with_invoice($open_invoice);
       $open_invoice->{realamount} = $::form->format_amount(\%::myconfig,
                                       $open_invoice->amount * ($open_invoice->{is_ar} ? 1 : -1), 2);
@@ -259,11 +216,21 @@ sub action_list {
                                           : abs(@{ $_->{proposals} }[0]->amount + $_->amount) < 0.01)
   } @{ $bank_transactions };
 
-  push ( @proposals, @otherproposals);
+  push @proposals, @otherproposals;
 
   # sort bank transaction proposals by quality (score) of proposal
-  $bank_transactions = [ sort { $a->{agreement} <=> $b->{agreement} } @{ $bank_transactions } ] if $::form->{sort_by} eq 'proposal' and $::form->{sort_dir} == 1;
-  $bank_transactions = [ sort { $b->{agreement} <=> $a->{agreement} } @{ $bank_transactions } ] if $::form->{sort_by} eq 'proposal' and $::form->{sort_dir} == 0;
+  if ($::form->{sort_by} && $::form->{sort_by} eq 'proposal') {
+    if ($::form->{sort_dir}) {
+      $bank_transactions = [ sort { $a->{agreement} <=> $b->{agreement} } @{ $bank_transactions } ];
+    } else {
+      $bank_transactions = [ sort { $b->{agreement} <=> $a->{agreement} } @{ $bank_transactions } ];
+    }
+  }
+
+  # for testing with t/bank/banktransaction.t :
+  if ( $::form->{dont_render_for_test} ) {
+    return $bank_transactions;
+  }
 
   $::request->layout->add_javascripts("kivi.BankTransaction.js");
   $self->render('bank_transactions/list',
@@ -291,16 +258,28 @@ sub action_create_invoice {
 
   $self->transaction(SL::DB::Manager::BankTransaction->find_by(id => $::form->{bt_id}));
 
-  my $vendor_of_transaction = SL::DB::Manager::Vendor->find_by(account_number => $self->transaction->{remote_account_number});
+  # This was dead code: We compared vendor.account_name with bank_transaction.iban.
+  # This did never match (Kontonummer != IBAN). It's kivis 09/02 (2013) day
+  # If refactored/improved, also consider that vendor.iban should be normalized
+  # user may like to input strings like: 'AT 3333 3333 2222 1111' -> can be checked strictly
+  # at Vendor code because we need the correct data for all sepa exports.
+
+  my $vendor_of_transaction = SL::DB::Manager::Vendor->find_by(iban => $self->transaction->{remote_account_number});
   my $use_vendor_filter     = $self->transaction->{remote_account_number} && $vendor_of_transaction;
 
-  my $templates             = SL::DB::Manager::RecordTemplate->get_all(
+  my $templates_ap = SL::DB::Manager::RecordTemplate->get_all(
     where        => [ template_type => 'ap_transaction' ],
     with_objects => [ qw(employee vendor) ],
   );
+  my $templates_gl = SL::DB::Manager::RecordTemplate->get_all(
+    query        => [ template_type => 'gl_transaction',
+                      chart_id      => SL::DB::Manager::BankAccount->find_by(id => $self->transaction->local_bank_account_id)->chart_id,
+                    ],
+    with_objects => [ qw(employee record_template_items) ],
+  );
 
-  #Filter templates
-  $templates = [ grep { $_->vendor_id == $vendor_of_transaction->id } @{ $templates } ] if $use_vendor_filter;
+  # pre filter templates_ap, if we have a vendor match (IBAN eq IBAN) - show and allow user to edit this via gui!
+  $templates_ap = [ grep { $_->vendor_id == $vendor_of_transaction->id } @{ $templates_ap } ] if $use_vendor_filter;
 
   $self->callback($self->url_for(
     action                => 'list',
@@ -312,10 +291,10 @@ sub action_create_invoice {
   $self->render(
     'bank_transactions/create_invoice',
     { layout => 0 },
-    title       => t8('Create invoice'),
-    TEMPLATES   => $templates,
-    vendor_id   => $use_vendor_filter ? $vendor_of_transaction->id   : undef,
-    vendor_name => $use_vendor_filter ? $vendor_of_transaction->name : undef,
+    title        => t8('Create invoice'),
+    TEMPLATES_GL => $use_vendor_filter ? undef : $templates_gl,
+    TEMPLATES_AP => $templates_ap,
+    vendor_name  => $use_vendor_filter ? $vendor_of_transaction->name : undef,
   );
 }
 
@@ -349,16 +328,23 @@ sub action_filter_templates {
   my ($self) = @_;
 
   $self->{transaction}      = SL::DB::Manager::BankTransaction->find_by(id => $::form->{bt_id});
-  my $vendor_of_transaction = SL::DB::Manager::Vendor->find_by(account_number => $self->{transaction}->{remote_account_number});
 
   my @filter;
-  push @filter, ('vendor.id'   => $::form->{vendor_id})                       if $::form->{vendor_id};
-  push @filter, ('vendor.name' => { ilike => '%' . $::form->{vendor} . '%' }) if $::form->{vendor};
+  push @filter, ('vendor.name'   => { ilike => '%' . $::form->{vendor} . '%' })    if $::form->{vendor};
+  push @filter, ('template_name' => { ilike => '%' . $::form->{template} . '%' })  if $::form->{template};
+  push @filter, ('reference'     => { ilike => '%' . $::form->{reference} . '%' }) if $::form->{reference};
 
-  my $templates = SL::DB::Manager::RecordTemplate->get_all(
-    where        => [ template_type => 'ap_transaction', (or => \@filter) x !!@filter ],
+  my $templates_ap = SL::DB::Manager::RecordTemplate->get_all(
+    where        => [ template_type => 'ap_transaction', (and => \@filter) x !!@filter ],
     with_objects => [ qw(employee vendor) ],
   );
+  my $templates_gl = SL::DB::Manager::RecordTemplate->get_all(
+    query        => [ template_type => 'gl_transaction',
+                      chart_id      => SL::DB::Manager::BankAccount->find_by(id => $self->transaction->local_bank_account_id)->chart_id,
+                      (and => \@filter) x !!@filter
+                    ],
+    with_objects => [ qw(employee record_template_items) ],
+  );
 
   $::form->{filter} //= {};
 
@@ -372,7 +358,8 @@ sub action_filter_templates {
   my $output  = $self->render(
     'bank_transactions/_template_list',
     { output => 0 },
-    TEMPLATES => $templates,
+    TEMPLATES_AP => $templates_ap,
+    TEMPLATES_GL => $templates_gl,
   );
 
   $self->render(\to_json({ html => $output }), { type => 'json', process => 0 });
@@ -545,11 +532,6 @@ sub action_save_proposals {
 
 }
 
-sub is_collective_transaction {
-  my ($self, $bt) = @_;
-  return $bt->transaction_code eq "191";
-}
-
 sub save_single_bank_transaction {
   my ($self, %params) = @_;
 
@@ -903,6 +885,19 @@ sub load_ap_record_template_url {
   );
 }
 
+sub load_gl_record_template_url {
+  my ($self, $template) = @_;
+
+  return $self->url_for(
+    controller                           => 'gl.pl',
+    action                               => 'load_record_template',
+    id                                   => $template->id,
+    'form_defaults.amount_1'             => abs($self->transaction->amount), # always positive
+    'form_defaults.transdate'            => $self->transaction->transdate_as_date,
+    'form_defaults.callback'             => $self->callback,
+  );
+}
+
 sub setup_search_action_bar {
   my ($self, %params) = @_;
 
index 43906bf..85eb6fa 100644 (file)
@@ -34,7 +34,7 @@ sub run {
   $self->controller->track_progress(phase => 'parsing csv', progress => 0);
 
   my $profile = $self->profile;
-  $self->csv(SL::Helper::Csv->new(file                   => $self->file->file_name,
+  $self->csv(SL::Helper::Csv->new(file                   => ('SCALAR' eq ref $self->file)? $self->file: $self->file->file_name,
                                   encoding               => $self->controller->profile->get('charset'),
                                   profile                => [{ profile => $profile, class => $self->class, mapping => $self->controller->mappings_for_profile }],
                                   ignore_unknown_columns => 1,
index d4bb745..b975394 100644 (file)
@@ -22,7 +22,7 @@ sub run {
 
   my $profile = $self->profile;
 
-  $self->csv(SL::Helper::Csv->new(file                    => $self->file->file_name,
+  $self->csv(SL::Helper::Csv->new(file                   => ('SCALAR' eq ref $self->file)? $self->file: $self->file->file_name,
                                   encoding                => $self->controller->profile->get('charset'),
                                   profile                 => $profile,
                                   ignore_unknown_columns  => 1,
index 20e0286..0a714d2 100644 (file)
@@ -11,7 +11,8 @@ use SL::Controller::Helper::ReportGenerator;
 use SL::Locale::String;
 use SL::Helper::ShippedQty;
 use SL::AM;
-use SL::DBUtils ();
+use SL::DBUtils qw(selectall_as_map);
+use List::MoreUtils qw(uniq);
 use Carp;
 use Data::Dumper;
 
@@ -38,6 +39,8 @@ my %sort_columns = (
   netto_shipped_qty       => t8('Net Value in delivery orders'),
   delivered_qty           => t8('transferred in / out'),
   netto_delivered_qty     => t8('Net value transferred in / out'),
+  do_closed_qty           => t8('Qty in closed delivery orders'),
+  netto_do_closed_qty     => t8('Net value in closed delivery orders'),
 );
 
 
@@ -68,7 +71,7 @@ sub prepare_report {
 
   my @columns     = qw(reqdate customer vendor ordnumber partnumber description unit qty netto_qty
                        not_shipped_qty netto_not_shipped_qty shipped_qty netto_shipped_qty delivered_qty
-                       netto_delivered_qty);
+                       netto_delivered_qty do_closed_qty netto_do_closed_qty);
 
 
   my @sortable    = qw(reqdate customer vendor ordnumber partnumber description);
@@ -82,26 +85,18 @@ sub prepare_report {
                            obj_link => sub { $self->link_to($_[0]->part)                                      } },
     partnumber        => {      sub => sub { $_[0]->part->partnumber                                          },
                            obj_link => sub { $self->link_to($_[0]->part)                                      } },
-    qty               => {      sub => sub { $_[0]->qty_as_number .
-                                             ($rp_csv_mod ? '' : ' ' .  $_[0]->unit)                           } },
-    netto_qty         => {      sub => sub { $::form->format_amount(\%::myconfig,
-                                              ($_[0]->qty * $_[0]->sellprice * (1 - $_[0]->discount) /
-                                                                         ($_[0]->price_factor || 1), 2))       },},
-    unit              => {      sub => sub {  $_[0]->unit                                                      },
-                            visible => $rp_csv_mod                                                             },
-    shipped_qty       => {      sub => sub { $::form->format_amount(\%::myconfig, $_[0]{shipped_qty}, 2) .
-                                             ($rp_csv_mod ? '' : ' ' .  $_[0]->unit)                           } },
-    netto_shipped_qty => {      sub => sub { $::form->format_amount(\%::myconfig, $_[0]{netto_shipped_qty}, 2) },},
-    not_shipped_qty   => {      sub => sub { $::form->format_amount(\%::myconfig, $_[0]->qty - $_[0]{shipped_qty}
-                                               - $_[0]{delivered_qty} - $_[0]{do_closed_qty}, 2) .
-                                             ($rp_csv_mod ? '' : ' ' .  $_[0]->unit)                           } },
-    delivered_qty     => {      sub => sub { $::form->format_amount(\%::myconfig, $_[0]{delivered_qty}, 2) .
-                                             ($rp_csv_mod ? '' : ' ' .  $_[0]->unit)                           } },
-    netto_delivered_qty => {    sub => sub { $::form->format_amount(\%::myconfig, $_[0]{netto_delivered_qty}, 2) },},
-    netto_not_shipped_qty => {  sub => sub { $::form->format_amount(\%::myconfig,(($_[0]->qty -
-                                             $_[0]{shipped_qty} - $_[0]{delivered_qty} - $_[0]{do_closed_qty})
-                                             * ($_[0]->sellprice * (1 - $_[0]->discount) /
-                                                                             ($_[0]->price_factor || 1)), 2))  },},
+    qty               => {      sub => sub { _format_qty($_[0], 'qty', $rp_csv_mod)                           } },
+    netto_qty         => {      sub => sub { _format_val($_[0], 'qty')                                        },},
+    unit              => {      sub => sub {  $_[0]->unit                                                     },
+                            visible => $rp_csv_mod                                                              },
+    shipped_qty       => {      sub => sub { _format_qty($_[0], 'shipped_qty', $rp_csv_mod)                   } },
+    netto_shipped_qty => {      sub => sub { _format_val($_[0], 'shipped_qty')                                },},
+    not_shipped_qty   => {      sub => sub { _format_qty($_[0], 'not_shipped_qty', $rp_csv_mod)               } },
+    netto_not_shipped_qty => {  sub => sub { _format_val($_[0], 'not_shipped_qty')                            },},
+    delivered_qty     => {      sub => sub { _format_qty($_[0], 'delivered_qty', $rp_csv_mod)                 } },
+    netto_delivered_qty => {    sub => sub { _format_val($_[0], 'delivered_qty')                              },},
+    do_closed_qty     => {      sub => sub { _format_qty($_[0], 'do_closed_qty', $rp_csv_mod)                 },},
+    netto_do_closed_qty => {    sub => sub { _format_val($_[0], 'do_closed_qty')                              },},
     ordnumber         => {      sub => sub { $_[0]->order->ordnumber                                           },
                            obj_link => sub { $self->link_to($_[0]->order)                                      } },
     vendor            => {      sub => sub { $_[0]->order->vendor->name                                        },
@@ -248,6 +243,18 @@ sub link_to {
   }
 }
 
+sub _format_qty {
+  my ($item, $col, $csv_mod) = @_;
+
+  $::form->format_amount(\%::myconfig, $item->{$col}, 2) .  ($csv_mod ? '' : ' ' .  $item->unit)
+}
+
+sub _format_val {
+  my ($item, $col) = @_;
+
+  $::form->format_amount(\%::myconfig, $item->{$col} * $item->sellprice * (1 - $item->discount) / ($item->price_factor || 1), 2)
+}
+
 
 sub calc_qtys_price {
   my ($self, $orderitems) = @_;
@@ -261,17 +268,31 @@ sub calc_qtys_price {
 
   $_->{delivered_qty} = delete $_->{shipped_qty} for @$orderitems;
 
-  SL::Helper::ShippedQty
-    ->new(require_stock_out => 0)
+  my $helper = SL::Helper::ShippedQty
+    ->new(require_stock_out => 0, keep_matches => 1)
     ->calculate($orderitems)
     ->write_to_objects;
 
   for my $item (@$orderitems) {
     $item->{not_shipped_qty} = $item->qty - $item->{shipped_qty};
+    $item->{do_closed_qty}   = 0;
 
     my $price_factor = $item->price_factor || 1;
-    $item->{netto_shipped_qty}   = $item->{shipped_qty}   * $item->sellprice * (1 - $item->discount) / $price_factor;
-    $item->{netto_delivered_qty} = $item->{delivered_qty} * $item->sellprice * (1 - $item->discount) / $price_factor;
+  }
+
+  if (my @all_doi_ids = uniq map { $_->[1] } @{ $helper->matches }) {
+    my %oi_by_id = map { $_->id => $_ } @$orderitems;
+    my $query    = sprintf <<'', join ', ', ("?")x@all_doi_ids;
+      SELECT DISTINCT doi.id, closed FROM delivery_orders
+      LEFT JOIN delivery_order_items doi ON (doi.delivery_order_id = delivery_orders.id)
+      WHERE doi.id IN (%s)
+
+    my %doi_is_closed = selectall_as_map($::form, SL::DB->client->dbh, $query, (id => 'closed'), @all_doi_ids);
+
+    for my $match (@{ $helper->matches }) {
+      next unless $doi_is_closed{$match->[1]};
+      $oi_by_id{$match->[0]}->{do_closed_qty} += $match->[2];
+    }
   }
 }
 
index d900c18..86b248c 100644 (file)
@@ -57,8 +57,6 @@ sub finalize {
     # try to use Filtered if available and nothing else is configured, but don't
     # blow up if the controller does not use Filtered
     my %paginate_args     = ref($self->paginate_args) eq 'CODE'       ? %{ $self->paginate_args->($self) }
-                          :     $self->paginate_args  eq '__FILTER__'
-                             && $self->get_models->filtered ? $self->get_models->filtered->read_params
                           :     $self->paginate_args  ne '__FILTER__' ? do { my $sub = $self->paginate_args; %{ $self->get_models->controller->$sub() } }
                           :                                               ();
 
index 5a83be4..41f0c6c 100644 (file)
@@ -22,16 +22,18 @@ sub _setup_action_bar {
   my $key   = $::form->{CONTROLLER_DISPATCH} ? 'action'                             : 'report_generator_form.report_generator_dispatch_to';
   my $value = $::form->{CONTROLLER_DISPATCH} ? $::form->{CONTROLLER_DISPATCH} . "/" : '';
 
-  $::request->layout->get('actionbar')->add(
-    action => [
-      $type eq 'pdf' ? $::locale->text('PDF export') : $::locale->text('CSV export'),
-      submit => [ '#report_generator_form', { $key => "${value}report_generator_export_as_${type}" } ],
-    ],
-    action => [
-      $::locale->text('Back'),
-      submit => [ '#report_generator_form', { $key => "${value}report_generator_back" } ],
-    ],
-  );
+  for my $bar ($::request->layout->get('actionbar')) {
+    $bar->add(
+      action => [
+        $type eq 'pdf' ? $::locale->text('PDF export') : $::locale->text('CSV export'),
+        submit => [ '#report_generator_form', { $key => "${value}report_generator_export_as_${type}" } ],
+      ],
+      action => [
+        $::locale->text('Back'),
+        submit => [ '#report_generator_form', { $key => "${value}report_generator_back" } ],
+      ],
+    );
+  }
 }
 
 sub action_report_generator_export_as_pdf {
index bc7fc5f..ab4401e 100644 (file)
@@ -211,7 +211,7 @@ sub action_print {
     }
   }
   if ($self->order->ordnumber && $::instance_conf->get_doc_storage) {
-    SL::File->store( object_id     => $self->order->id,
+    SL::File->save(  object_id     => $self->order->id,
                      object_type   => $self->type,
                      mime_type     => 'application/pdf',
                      source        => 'created',
index c39ba34..c5e081a 100644 (file)
@@ -383,6 +383,17 @@ sub generate_datev_data {
     $gl_department_id_filter = " AND gl.department_id = ? ";
   }
 
+  my ($gl_itime_filter, $ar_itime_filter, $ap_itime_filter);
+  if ( $form->{gldatefrom} ) {
+    $gl_itime_filter = " AND gl.itime >= ? ";
+    $ar_itime_filter = " AND ar.itime >= ? ";
+    $ap_itime_filter = " AND ap.itime >= ? ";
+  } else {
+    $gl_itime_filter = "";
+    $ar_itime_filter = "";
+    $ap_itime_filter = "";
+  }
+
   if ( $self->{trans_id} ) {
     # ignore dates when trans_id is passed so that the entire transaction is
     # checked, not just either the initial bookings or the subsequent payments
@@ -419,6 +430,7 @@ sub generate_datev_data {
        WHERE (ar.id IS NOT NULL)
          AND $fromto
          $trans_id_filter
+         $ar_itime_filter
          $ar_department_id_filter
          $filter
 
@@ -443,6 +455,7 @@ sub generate_datev_data {
        WHERE (ap.id IS NOT NULL)
          AND $fromto
          $trans_id_filter
+         $ap_itime_filter
          $ap_department_id_filter
          $filter
 
@@ -466,14 +479,25 @@ sub generate_datev_data {
        WHERE (gl.id IS NOT NULL)
          AND $fromto
          $trans_id_filter
+         $gl_itime_filter
          $gl_department_id_filter
          $filter
 
        ORDER BY trans_id, acc_trans_id|;
 
   my @query_args;
-  if ( $form->{department_id} ) {
-    push(@query_args, ($form->{department_id}) x 3);
+  if ( $form->{gldatefrom} or $form->{department_id} ) {
+
+    for ( 1 .. 3 ) {
+      if ( $form->{gldatefrom} ) {
+        my $glfromdate = $::locale->parse_date_to_object($form->{gldatefrom});
+        die "illegal data" unless ref($glfromdate) eq 'DateTime';
+        push(@query_args, $glfromdate);
+      }
+      if ( $form->{department_id} ) {
+        push(@query_args, $form->{department_id});
+      }
+    }
   }
 
   my $sth = prepare_execute_query($form, $self->dbh, $query, @query_args);
index 6ad0348..e4cf67c 100644 (file)
@@ -47,6 +47,11 @@ sub linked_invoices {
   return [ @linked_invoices ];
 }
 
+sub is_batch_transaction {
+  ($_[0]->transaction_code // '') eq "191";
+}
+
+
 sub get_agreement_with_invoice {
   my ($self, $invoice) = @_;
 
@@ -73,10 +78,16 @@ sub get_agreement_with_invoice {
     skonto_exact_amount         => 5,
     wrong_sign                  => -1,
     sepa_export_item            => 5,
+    batch_sepa_transaction      => 20,
   );
 
   my ($agreement,$rule_matches);
 
+  if ( $self->is_batch_transaction && $self->{sepa_export_ok}) {
+    $agreement += $points{batch_sepa_transaction};
+    $rule_matches .= 'batch_sepa_transaction(' . $points{'batch_sepa_transaction'} . ') ';
+  }
+
   # compare banking arrangements
   my ($iban, $bank_code, $account_number);
   $bank_code      = $invoice->customer->bank_code      if $invoice->is_sales;
@@ -88,11 +99,11 @@ sub get_agreement_with_invoice {
   if ( $bank_code eq $self->remote_bank_code && $account_number eq $self->remote_account_number ) {
     $agreement += $points{remote_account_number};
     $rule_matches .= 'remote_account_number(' . $points{'remote_account_number'} . ') ';
-  };
+  }
   if ( $iban eq $self->remote_account_number ) {
     $agreement += $points{remote_account_number};
     $rule_matches .= 'remote_account_number(' . $points{'remote_account_number'} . ') ';
-  };
+  }
 
   my $datediff = $self->transdate->{utc_rd_days} - $invoice->transdate->{utc_rd_days};
   $invoice->{datediff} = $datediff;
@@ -101,19 +112,19 @@ sub get_agreement_with_invoice {
   if (abs(abs($invoice->amount) - abs($self->amount)) < 0.01) {
     $agreement += $points{exact_amount};
     $rule_matches .= 'exact_amount(' . $points{'exact_amount'} . ') ';
-  };
+  }
 
   # compare open amount, preventing double points when open amount = invoice amount
   if ( $invoice->amount != $invoice->open_amount && abs(abs($invoice->open_amount) - abs($self->amount)) < 0.01) {
     $agreement += $points{exact_open_amount};
     $rule_matches .= 'exact_open_amount(' . $points{'exact_open_amount'} . ') ';
-  };
+  }
 
   if ( $invoice->skonto_date && abs(abs($invoice->amount_less_skonto) - abs($self->amount)) < 0.01) {
     $agreement += $points{skonto_exact_amount};
     $rule_matches .= 'skonto_exact_amount(' . $points{'skonto_exact_amount'} . ') ';
     $invoice->{skonto_type} = 'with_skonto_pt';
-  };
+  }
 
   #search invoice number in purpose
   my $invnumber = $invoice->invnumber;
@@ -132,11 +143,11 @@ sub get_agreement_with_invoice {
   if ( $invoice->is_sales && $self->amount < 0 ) {
     $agreement += $points{wrong_sign};
     $rule_matches .= 'wrong_sign(' . $points{'wrong_sign'} . ') ';
-  };
+  }
   if ( ! $invoice->is_sales && $self->amount > 0 ) {
     $agreement += $points{wrong_sign};
     $rule_matches .= 'wrong_sign(' . $points{'wrong_sign'} . ') ';
-  };
+  }
 
   # search customer/vendor number in purpose
   my $cvnumber;
@@ -154,7 +165,7 @@ sub get_agreement_with_invoice {
   if ( $cvname && $self->purpose =~ /\b\Q$cvname\E\b/i ) {
     $agreement += $points{cust_vend_name_in_purpose};
     $rule_matches .= 'cust_vend_name_in_purpose(' . $points{'cust_vend_name_in_purpose'} . ') ';
-  };
+  }
 
   # compare depositorname, don't try to match empty depositors
   my $depositorname;
@@ -163,24 +174,24 @@ sub get_agreement_with_invoice {
   if ( $depositorname && $self->remote_name =~ /$depositorname/ ) {
     $agreement += $points{depositor_matches};
     $rule_matches .= 'depositor_matches(' . $points{'depositor_matches'} . ') ';
-  };
+  }
 
   #Check if words in remote_name appear in cvname
   my $check_string_points = _check_string($self->remote_name,$cvname);
   if ( $check_string_points ) {
     $agreement += $check_string_points;
     $rule_matches .= 'remote_name(' . $check_string_points . ') ';
-  };
+  }
 
   # transdate prefilter: compare transdate of bank_transaction with transdate of invoice
   if ( $datediff < -5 ) { # this might conflict with advance payments
     $agreement += $points{payment_before_invoice};
     $rule_matches .= 'payment_before_invoice(' . $points{'payment_before_invoice'} . ') ';
-  };
+  }
   if ( $datediff < 30 ) {
     $agreement += $points{payment_within_30_days};
     $rule_matches .= 'payment_within_30_days(' . $points{'payment_within_30_days'} . ') ';
-  };
+  }
 
   # only if we already have a good agreement, let date further change value of agreement.
   # this is so that if there are several plausible open invoices which are all equal
@@ -205,9 +216,9 @@ sub get_agreement_with_invoice {
       $agreement += $points{datebonus_negative};
       $rule_matches .= 'datebonus_negative(' . $points{'datebonus_negative'} . ') ';
     } else {
-  # e.g. datediff > 120
-    };
-  };
+      # e.g. datediff > 120
+    }
+  }
 
   # if there is exactly one non-executed sepa_export_item for the invoice
   if ( my $seis = $invoice->find_sepa_export_items({ executed => 0 }) ) {
@@ -216,8 +227,6 @@ sub get_agreement_with_invoice {
 
       # test for amount and id matching only, sepa transfer date and bank
       # transaction date needn't match
-      my $arap = $invoice->is_sales ? 'ar' : 'ap';
-
       if (abs($self->amount) == ($sei->amount) && $invoice->id == $sei->arap_id) {
         $agreement    += $points{sepa_export_item};
         $rule_matches .= 'sepa_export_item(' . $points{'sepa_export_item'} . ') ';
index d23e46e..e80d6fd 100644 (file)
@@ -422,7 +422,7 @@ passed to import.
 
 =item C<cvars_by_config>
 
-Thi will return a list of CVars with the following changes over the standard accessor:
+This will return a list of CVars with the following changes over the standard accessor:
 
 =over 4
 
@@ -522,11 +522,11 @@ If the Manager for the calling C<SL::DB::Object> has included the helper L<SL::D
 Prolonged use has shown that users expect all methods to be present or none.
 Future versions of this will likely remove the optional aliasing.
 
-=item * Sematics need to be updated
+=item * Semantics need to be updated
 
 There are a few transitions that are currently neither supported nor well
-defined, most of the happening when the config of a cvar gets changed which
-instances are already saved. This needs to be cleaned up.
+defined, most of them happening when the config of a cvar gets changed, but
+whose instances have already been saved. This needs to be cleaned up.
 
 =back
 
index 988ab81..aa0e8ba 100644 (file)
@@ -2,12 +2,21 @@ package SL::Dev::ALL;
 
 use strict;
 
+use Exporter;
 use SL::Dev::Part;
 use SL::Dev::CustomerVendor;
 use SL::Dev::Inventory;
 use SL::Dev::Record;
 use SL::Dev::Payment;
 
+sub import {
+  no strict "refs";
+  for (qw(Part CustomerVendor Inventory Record Payment)) {
+    Exporter::export_to_level("SL::Dev::$_", 1, @_);
+  }
+}
+
+
 1;
 
 __END__
index 6bf976e..0ea6263 100644 (file)
@@ -2,13 +2,14 @@ package SL::Dev::CustomerVendor;
 
 use strict;
 use base qw(Exporter);
-our @EXPORT = qw(create_customer create_vendor);
+our @EXPORT_OK = qw(new_customer new_vendor);
+our %EXPORT_TAGS = (ALL => \@EXPORT_OK);
 
 use SL::DB::TaxZone;
 use SL::DB::Currency;
 use SL::DB::Customer;
 
-sub create_customer {
+sub new_customer {
   my (%params) = @_;
 
   my $taxzone    = _check_taxzone(delete $params{taxzone_id});
@@ -22,7 +23,7 @@ sub create_customer {
   return $customer;
 }
 
-sub create_vendor {
+sub new_vendor {
   my (%params) = @_;
 
   my $taxzone    = _check_taxzone(delete $params{taxzone_id});
@@ -69,7 +70,7 @@ SL::Dev::CustomerVendor - create customer and vendor objects for testing, with m
 
 =head1 FUNCTIONS
 
-=head2 C<create_customer %PARAMS>
+=head2 C<new_customer %PARAMS>
 
 Creates a new customer.
 
@@ -87,7 +88,7 @@ Complex usage, overwriting some defaults, and save to database:
 If neither taxzone_id or currency_id (both are NOT NULL) are passed as params
 then default values are used.
 
-=head2 C<create_vendor %PARAMS>
+=head2 C<new_vendor %PARAMS>
 
 Creates a new vendor.
 
index ff1ea11..62228ea 100644 (file)
@@ -2,10 +2,16 @@ package SL::Dev::File;
 
 use strict;
 use base qw(Exporter);
-our @EXPORT = qw(create_scanned create_uploaded create_created get_all_count get_all get_all_versions delete_all);
+our @EXPORT_OK = qw(create_scanned create_uploaded create_created);
+our %EXPORT_TAGS = (ALL => \@EXPORT_OK);
 
 use SL::DB::File;
 
+my %common_params = (
+  object_id   => 1,
+  object_type => 'sales_order',
+);
+
 sub create_scanned {
   my (%params) = @_;
   $params{source}    = 'scanner1';
@@ -38,8 +44,7 @@ sub _create_file {
   my (%params) = @_;
 
   my $fileobj = SL::File->save(
-    object_id          => 1,
-    object_type        => 'sales_order',
+    %common_params,
     mime_type          => 'text/plain',
     description        => 'Test File',
     file_type          => $params{file_type},
@@ -51,33 +56,11 @@ sub _create_file {
   return $fileobj;
 }
 
-sub get_all_count {
-  my ($class,%params) = @_;
-  $params{object_id}   = 1;
-  $params{object_type} = 'sales_order';
-  return SL::File->get_all_count(%params);
-}
-
-sub get_all {
-  my ($class,%params) = @_;
-  $params{object_id}   = 1;
-  $params{object_type} = 'sales_order';
-  SL::File->get_all(%params);
-}
-
-sub get_all_versions {
-  my ($class,%params) = @_;
-  $params{object_id}   = 1;
-  $params{object_type} = 'sales_order';
-  SL::File->get_all_versions(%params);
-}
+sub get_all          { SL::File->get_all         (%common_params, @_) }
+sub get_all_count    { SL::File->get_all_count   (%common_params, @_) }
+sub get_all_versions { SL::File->get_all_versions(%common_params, @_) }
+sub delete_all       { SL::File->delete_all      (%common_params, @_) }
 
-sub delete_all {
-  my ($class,%params) = @_;
-  $params{object_id}   = 1;
-  $params{object_type} = 'sales_order';
-  SL::File->delete_all(%params);
-}
 1;
 
 __END__
@@ -94,8 +77,6 @@ SL::Dev::File - create file objects for testing, with minimal defaults
 
 =head2 C<create_created %PARAMS>
 
-=head2 C<delete_all>
-
 =head1 AUTHOR
 
 Martin Helmling E<lt>martin.helmling@opendynamic.deE<gt>
index e7aaee5..4432d86 100644 (file)
@@ -2,11 +2,12 @@ package SL::Dev::Inventory;
 
 use strict;
 use base qw(Exporter);
-our @EXPORT = qw(
+our @EXPORT_OK = qw(
   create_warehouse_and_bins set_stock transfer_stock
   transfer_sales_delivery_order transfer_purchase_delivery_order
   transfer_delivery_order_item transfer_in transfer_out
 );
+our %EXPORT_TAGS = (ALL => \@EXPORT_OK);
 
 use SL::DB::Warehouse;
 use SL::DB::Bin;
index 45976b6..aa69c0d 100644 (file)
@@ -2,13 +2,14 @@ package SL::Dev::Part;
 
 use strict;
 use base qw(Exporter);
-our @EXPORT = qw(create_part create_service create_assembly create_assortment);
+our @EXPORT_OK = qw(new_part new_service new_assembly new_assortment);
+our %EXPORT_TAGS = (ALL => \@EXPORT_OK);
 
 use SL::DB::Part;
 use SL::DB::Unit;
 use SL::DB::Buchungsgruppe;
 
-sub create_part {
+sub new_part {
   my (%params) = @_;
 
   my $part = SL::DB::Part->new_part(
@@ -22,7 +23,7 @@ sub create_part {
   return $part;
 }
 
-sub create_service {
+sub new_service {
   my (%params) = @_;
 
   my $part = SL::DB::Part->new_service(
@@ -36,7 +37,7 @@ sub create_service {
   return $part;
 }
 
-sub create_assembly {
+sub new_assembly {
   my (%params) = @_;
 
   my $assnumber       = delete $params{assnumber};
@@ -48,9 +49,9 @@ sub create_assembly {
     $assembly_items = delete $params{assembly_items};
   } else {
     for my $i ( 1 .. delete $params{number_of_parts} || 3) {
-      my $part = SL::Dev::Part::create_part(partnumber  => "$base_partnumber $i",
-                                            description => "Testpart $i",
-                                           )->save;
+      my $part = new_part(partnumber  => "$base_partnumber $i",
+                             description => "Testpart $i",
+                            )->save;
       push( @{$assembly_items}, SL::DB::Assembly->new(parts_id => $part->id,
                                                       qty      => 1,
                                                       position => $i,
@@ -71,7 +72,7 @@ sub create_assembly {
   return $assembly;
 }
 
-sub create_assortment {
+sub new_assortment {
   my (%params) = @_;
 
   my $assnumber       = delete $params{assnumber};
@@ -83,9 +84,9 @@ sub create_assortment {
     $assortment_items = delete $params{assortment_items};
   } else {
     for my $i ( 1 .. delete $params{number_of_parts} || 3) {
-      my $part = SL::Dev::Part::create_part(partnumber  => "$base_partnumber $i",
-                                            description => "Testpart $i",
-                                           )->save;
+      my $part = new_part(partnumber  => "$base_partnumber $i",
+                             description => "Testpart $i",
+                            )->save;
       push( @{$assortment_items}, SL::DB::AssortmentItem->new(parts_id => $part->id,
                                                               qty      => 1,
                                                               position => $i,
@@ -126,87 +127,121 @@ __END__
 
 SL::Dev::Part - create part objects for testing, with minimal defaults
 
+=head1 SYNOPSIS
+
+  use SL::DEV::Part qw(new_part new_assembly new_service new_assortment);
+
+  # simple default objects
+  my $part     = new_part()->save;
+  my $assembly = new_assembly()->save;
+  my $service  = new_service()->save;
+  my $assortment = new_assortment()->save;
+
+  # pass additional params to the generated object
+  # see individual functions for special parameters
+  my $part     = new_part(
+    partnumber   => 'Test 001',
+    warehouse_id => $bin->warehouse->id,
+    bin_id       => $bin->id,
+  );
+
 =head1 FUNCTIONS
 
-=head2 C<create_part %PARAMS>
+=head2 C<new_part %PARAMS>
 
 Creates a new part (part_type = part).
 
-Minimal usage, default values, without saving to database:
+=head2 C<new_service %PARAMS>
+
+Creates a new service (part_type = service).
 
-  my $part = SL::Dev::Part::create_part();
+=head2 C<new_assembly %PARAMS>
 
-Create a test part with a default warehouse and bin and save it:
+Create a new assembly (part_type = assembly).
 
-  my $wh    = SL::Dev::Inventory::create_warehouse_and_bins()->save;
-  my $part1 = SL::Dev::Part::create_part(partnumber   => 'a123',
-                                         description  => 'Testpart 1',
-                                         warehouse_id => $wh->id,
-                                         bin_id       => $wh->bins->[0]->id,
-                                        )->save;
+Special params:
 
-=head2 C<create_service %PARAMS>
+=over 2
 
-Creates a new service (part_type = service).
+=item * C<number_of_parts>
 
-Minimal usage, default values, without saving to database:
+The number of automatically created assembly parts.
 
-  my $part = SL::Dev::Part::create_service();
+=item * C<assnumber>
 
-=head2 C<create_assembly %PARAMS>
+the partnumber of the assembly
 
-Create a new assembly (part_type = assembly).
+=item * C<partnumber>
 
-Params: assnumber:  the partnumber of the assembly
-        partnumber: the partnumber of the first assembly part to be created
+the partnumber of the first assembly part to be created
 
-By default 3 parts (p1, p2, p3) are created and saved as an assembly (as1).
+=back
 
-  my $assembly = SL::Dev::Part::create_assembly->save;
+By default 3 parts (p1, p2, p3) are created and saved as an assembly (as1).
 
 Create a new assembly with 10 parts, the assembly gets partnumber 'Ass1' and the
 parts get partnumbers 'Testpart 1' to 'Testpart 10':
 
-  my $assembly = SL::Dev::Part::create_assembly(number_of_parts => 10,
-                                                partnumber      => 'Testpart',
-                                                assnumber       => 'Ass1'
-                                               )->save;
+  my $assembly = SL::Dev::Part::new_assembly(
+    number_of_parts => 10,
+    partnumber      => 'Testpart',
+    assnumber       => 'Ass1'
+  )->save;
 
 Create an assembly with specific parts:
+
   my $assembly_item_1 = SL::DB::Assembly->new( parts_id => $part1->id, qty => 3, position => 1);
   my $assembly_item_2 = SL::DB::Assembly->new( parts_id => $part2->id, qty => 3, position => 2);
-  my $assembly_part   = SL::Dev::Part::create_assembly( assnumber      => 'Assembly 1',
-                                                        description    => 'Assembly test',
-                                                        sellprice      => $part1->sellprice + $part2->sellprice,
-                                                        assembly_items => [ $assembly_item_1, $assembly_item_2 ],
-                                                      )->save;
+  my $assembly_part   = new_assembly(
+    assnumber      => 'Assembly 1',
+    description    => 'Assembly test',
+    sellprice      => $part1->sellprice + $part2->sellprice,
+    assembly_items => [ $assembly_item_1, $assembly_item_2 ],
+  );
 
-=head2 C<create_assortment %PARAMS>
+=head2 C<new_assortment %PARAMS>
 
 Create a new assortment (part_type = assortment).
 
-By default 3 parts (p1, p2, p3) are created and saved as an assortment.
+Special params:
+
+=over 2
+
+=item * C<number_of_parts>
+
+The number of automatically created assembly parts.
 
-  my $assortment = SL::Dev::Part::create_assortment->save;
+=item * C<assnumber>
+
+the partnumber of the assortment
+
+=item * C<partnumber>
+
+the partnumber of the first assembly part to be created
+
+=back
+
+By default 3 parts (p1, p2, p3) are created and saved as an assortment.
 
 Create a new assortment with 10 automatically created parts using the
 number_of_parts param:
 
-  my $assortment = SL::Dev::Part::create_assortment(number_of_parts => 10)->save;
+  my $assortment = new_assortment(number_of_parts => 10)->save;
 
 Create an assortment with a certain name and pass some assortment_item Objects
 from newly created parts:
 
-  my $part1             = SL::Dev::Part::create_part( sellprice => '7.77')->save;
-  my $part2             = SL::Dev::Part::create_part( sellprice => '6.66')->save;
+  my $part1             = new_part(sellprice => 7.77)->save;
+  my $part2             = new_part(sellprice => 6.66)->save;
   my $assortment_item_1 = SL::DB::AssortmentItem->new( parts_id => $part1->id, qty => 3, unit => $part1->unit, position => 1);
   my $assortment_item_2 = SL::DB::AssortmentItem->new( parts_id => $part2->id, qty => 3, unit => $part2->unit, position => 2);
-  my $assortment_part   = SL::Dev::Part::create_assortment( assnumber        => 'Assortment 1',
-                                                            description      => 'assortment test',
-                                                            sellprice        => (3*$part1->sellprice + 3*$part2->sellprice),
-                                                            lastcost         => (3*$part1->lastcost  + 3*$part2->lastcost),
-                                                            assortment_items => [ $assortment_item_1, $assortment_item_2 ],
-                                                          )->save;
+  my $assortment_part   = SL::Dev::Part::new_assortment(
+    assnumber        => 'Assortment 1',
+    description      => 'assortment test',
+    sellprice        => (3*$part1->sellprice + 3*$part2->sellprice),
+    lastcost         => (3*$part1->lastcost  + 3*$part2->lastcost),
+    assortment_items => [ $assortment_item_1, $assortment_item_2 ],
+  )->save;
 
 =head1 TODO
 
index e1acac6..0fdb3a4 100644 (file)
@@ -2,7 +2,8 @@ package SL::Dev::Payment;
 
 use strict;
 use base qw(Exporter);
-our @EXPORT = qw(create_payment_terms create_bank_account create_bank_transaction);
+our @EXPORT_OK = qw(create_payment_terms create_bank_account create_bank_transaction create_sepa_export create_sepa_export_item);
+our %EXPORT_TAGS = (ALL => \@EXPORT_OK);
 
 use SL::DB::PaymentTerm;
 use SL::DB::BankAccount;
@@ -39,6 +40,30 @@ sub create_bank_account {
   $bank_account->save;
 }
 
+sub create_sepa_export {
+  my (%params) = @_;
+  my $sepa_export = SL::DB::SepaExport->new(
+    closed       => 0,
+    employee_id  => $params{employee_id} // SL::DB::Manager::Employee->current->id,
+    executed     => 0,
+    vc           => 'customer',
+  );
+  $sepa_export->assign_attributes(%params) if %params;
+  $sepa_export->save;
+}
+
+sub create_sepa_export_item {
+  my (%params) = @_;
+  my $sepa_exportitem = SL::DB::SepaExportItem->new(
+    chart_id     => delete $params{chart_id} // $::instance_conf->get_ar_paid_accno_id,
+    payment_type => 'without_skonto',
+    our_bic      => 'BANK1234',
+    our_iban     => 'DE12500105170648489890',
+  );
+  $sepa_exportitem->assign_attributes(%params) if %params;
+  $sepa_exportitem->save;
+}
+
 sub create_bank_transaction {
  my (%params) = @_;
 
index 649c6b2..5fbbd87 100644 (file)
@@ -2,13 +2,14 @@ package SL::Dev::Record;
 
 use strict;
 use base qw(Exporter);
-our @EXPORT = qw(create_invoice_item create_sales_invoice create_credit_note create_order_item  create_sales_order create_purchase_order create_delivery_order_item create_sales_delivery_order create_purchase_delivery_order create_project);
+our @EXPORT_OK = qw(create_invoice_item create_sales_invoice create_credit_note create_order_item  create_sales_order create_purchase_order create_delivery_order_item create_sales_delivery_order create_purchase_delivery_order create_project);
+our %EXPORT_TAGS = (ALL => \@EXPORT_OK);
 
 use SL::DB::Invoice;
 use SL::DB::InvoiceItem;
 use SL::DB::Employee;
-use SL::Dev::Part;
-use SL::Dev::CustomerVendor;
+use SL::Dev::Part qw(new_part);
+use SL::Dev::CustomerVendor qw(new_vendor new_customer);
 use SL::DB::Project;
 use SL::DB::ProjectStatus;
 use SL::DB::ProjectType;
@@ -28,7 +29,7 @@ sub create_sales_invoice {
   my $invoiceitems = delete $params{invoiceitems} // _create_two_items($record_type);
   _check_items($invoiceitems, $record_type);
 
-  my $customer = delete $params{customer} // SL::Dev::CustomerVendor::create_customer(name => 'Testcustomer')->save;
+  my $customer = delete $params{customer} // new_customer(name => 'Testcustomer')->save;
   die "illegal customer" unless defined $customer && ref($customer) eq 'SL::DB::Customer';
 
   my $invoice = SL::DB::Invoice->new(
@@ -59,7 +60,7 @@ sub create_credit_note {
   my $invoiceitems = delete $params{invoiceitems} // _create_two_items($record_type);
   _check_items($invoiceitems, $record_type);
 
-  my $customer = delete $params{customer} // SL::Dev::CustomerVendor::create_customer(name => 'Testcustomer')->save;
+  my $customer = delete $params{customer} // new_customer(name => 'Testcustomer')->save;
   die "illegal customer" unless defined $customer && ref($customer) eq 'SL::DB::Customer';
 
   # adjust qty for credit note items
@@ -93,7 +94,7 @@ sub create_sales_delivery_order {
   my $orderitems = delete $params{orderitems} // _create_two_items($record_type);
   _check_items($orderitems, $record_type);
 
-  my $customer = $params{customer} // SL::Dev::CustomerVendor::create_customer(name => 'Testcustomer')->save;
+  my $customer = $params{customer} // new_customer(name => 'Testcustomer')->save;
   die "illegal customer" unless ref($customer) eq 'SL::DB::Customer';
 
   my $delivery_order = SL::DB::DeliveryOrder->new(
@@ -121,7 +122,7 @@ sub create_purchase_delivery_order {
   my $orderitems = delete $params{orderitems} // _create_two_items($record_type);
   _check_items($orderitems, $record_type);
 
-  my $vendor = $params{vendor} // SL::Dev::CustomerVendor::create_vendor(name => 'Testvendor')->save;
+  my $vendor = $params{vendor} // new_vendor(name => 'Testvendor')->save;
   die "illegal customer" unless ref($vendor) eq 'SL::DB::Vendor';
 
   my $delivery_order = SL::DB::DeliveryOrder->new(
@@ -151,7 +152,7 @@ sub create_sales_order {
 
   my $save = delete $params{save} // 0;
 
-  my $customer = $params{customer} // SL::Dev::CustomerVendor::create_customer(name => 'Testcustomer')->save;
+  my $customer = $params{customer} // new_customer(name => 'Testcustomer')->save;
   die "illegal customer" unless ref($customer) eq 'SL::DB::Customer';
 
   my $order = SL::DB::Order->new(
@@ -182,7 +183,7 @@ sub create_purchase_order {
 
   my $save = delete $params{save} // 0;
 
-  my $vendor = $params{vendor} // SL::Dev::CustomerVendor::create_vendor(name => 'Testvendor')->save;
+  my $vendor = $params{vendor} // new_vendor(name => 'Testvendor')->save;
   die "illegal vendor" unless ref($vendor) eq 'SL::DB::Vendor';
 
   my $order = SL::DB::Order->new(
@@ -263,12 +264,12 @@ sub _create_item {
 sub _create_two_items {
   my ($record_type) = @_;
 
-  my $part1 = SL::Dev::Part::create_part(description => 'Testpart 1',
-                                         sellprice   => 12,
-                                        )->save;
-  my $part2 = SL::Dev::Part::create_part(description => 'Testpart 2',
-                                         sellprice   => 10,
-                                        )->save;
+  my $part1 = new_part(description => 'Testpart 1',
+                       sellprice   => 12,
+                      )->save;
+  my $part2 = new_part(description => 'Testpart 2',
+                       sellprice   => 10,
+                      )->save;
   my $item1 = _create_item(record_type => $record_type, part => $part1, qty => 5);
   my $item2 = _create_item(record_type => $record_type, part => $part2, qty => 8);
   return [ $item1, $item2 ];
@@ -383,7 +384,7 @@ Required params: record_type (sales_invoice, sales_order, sales_delivery_order)
 
 Example including creation of part and of invoice:
   my $part    = SL::Dev::Part::create_part(  partnumber  => 'T4254')->save;
-  my $item    = SL::Dev::Record::create_item(record_type => 'sales_invoice', part => $part, qty => 2.5);
+  my $item    = SL::Dev::Record::create_invoice_item(part => $part, qty => 2.5);
   my $invoice = SL::Dev::Record::create_sales_invoice(
     taxincluded  => 0,
     invoiceitems => [ $item ],
index 470809f..f29c601 100644 (file)
@@ -1375,6 +1375,38 @@ sub generate_email_subject {
   return $subject;
 }
 
+sub generate_email_body {
+  $main::lxdebug->enter_sub();
+  my ($self) = @_;
+  # simple german and english will work grammatically (most european languages as well)
+  # Dear Mr Alan Greenspan:
+  # Sehr geehrte Frau Meyer,
+  # A l’attention de Mme Villeroy,
+  # Gentile Signora Ferrari,
+  my $body = '';
+
+  if ($self->{cp_id}) {
+    my $givenname = SL::DB::Contact->load_cached($self->{cp_id})->cp_givenname; # for qw(gender givename name);
+    my $name      = SL::DB::Contact->load_cached($self->{cp_id})->cp_name; # for qw(gender givename name);
+    my $gender    = SL::DB::Contact->load_cached($self->{cp_id})->cp_gender; # for qw(gender givename name);
+    my $mf = $gender eq 'f' ? 'female' : 'male';
+    $body  = GenericTranslations->get(translation_type => "salutation_$mf", language_id => $self->{language_id});
+    $body .= ' ' . $givenname . ' ' . $name if $body;
+  } else {
+    $body  = GenericTranslations->get(translation_type => "salutation_general", language_id => $self->{language_id});
+  }
+
+  return undef unless $body;
+
+  $body   .= GenericTranslations->get(translation_type =>"salutation_punctuation_mark", language_id => $self->{language_id}) . "\n";
+  $body   .= GenericTranslations->get(translation_type =>"preset_text_$self->{formname}", language_id => $self->{language_id});
+
+  $body = $main::locale->unquote_special_chars('HTML', $body);
+
+  $main::lxdebug->leave_sub();
+  return $body;
+}
+
 sub cleanup {
   $main::lxdebug->enter_sub();
 
index e9c8e4e..1b3fe81 100644 (file)
@@ -3,21 +3,22 @@ package SL::Helper::ShippedQty;
 use strict;
 use parent qw(Rose::Object);
 
-use SL::AM;
+use Carp;
 use Scalar::Util qw(blessed);
-use SL::DBUtils qw(selectall_hashref_query selectall_as_map);
 use List::Util qw(min);
 use List::MoreUtils qw(any all uniq);
 use List::UtilsBy qw(partition_by);
+use SL::AM;
+use SL::DBUtils qw(selectall_hashref_query selectall_as_map);
 use SL::Locale::String qw(t8);
 
 use Rose::Object::MakeMethods::Generic (
-  'scalar'                => [ qw(objects objects_or_ids shipped_qty ) ],
-  'scalar --get_set_init' => [ qw(oe_ids dbh require_stock_out fill_up item_identity_fields oi2oe oi_qty delivered) ],
+  'scalar'                => [ qw(objects objects_or_ids shipped_qty keep_matches) ],
+  'scalar --get_set_init' => [ qw(oe_ids dbh require_stock_out fill_up item_identity_fields oi2oe oi_qty delivered matches) ],
 );
 
 my $no_stock_item_links_query = <<'';
-  SELECT oi.trans_id, oi.id AS oi_id, oi.qty AS oi_qty, oi.unit AS oi_unit, doi.qty AS doi_qty, doi.unit AS doi_unit
+  SELECT oi.trans_id, oi.id AS oi_id, oi.qty AS oi_qty, oi.unit AS oi_unit, doi.id AS doi_id, doi.qty AS doi_qty, doi.unit AS doi_unit
   FROM record_links rl
   INNER JOIN orderitems oi            ON oi.id = rl.from_id AND rl.from_table = 'orderitems'
   INNER JOIN delivery_order_items doi ON doi.id = rl.to_id AND rl.to_table = 'delivery_order_items'
@@ -50,7 +51,7 @@ my $no_stock_fill_up_doi_query = <<'';
       AND to_id = doi.id)
 
 my $stock_item_links_query = <<'';
-  SELECT oi.trans_id, oi.id AS oi_id, oi.qty AS oi_qty, oi.unit AS oi_unit,
+  SELECT oi.trans_id, oi.id AS oi_id, oi.qty AS oi_qty, oi.unit AS oi_unit, doi.id AS doi_id,
     (CASE WHEN doe.customer_id > 0 THEN -1 ELSE 1 END) * i.qty AS doi_qty, p.unit AS doi_unit
   FROM record_links rl
   INNER JOIN orderitems oi                   ON oi.id = rl.from_id AND rl.from_table = 'orderitems'
@@ -102,13 +103,11 @@ my %item_identity_fields = (
 sub calculate {
   my ($self, $data) = @_;
 
-  die 'Need exactly one argument, either id, object or arrayref of ids or objects.' unless 2 == @_;
-
-  return if !$data || ('ARRAY' eq ref $data && !@$data);
+  croak 'Need exactly one argument, either id, object or arrayref of ids or objects.' unless 2 == @_;
 
   $self->normalize_input($data);
 
-  return unless @{ $self->oe_ids };
+  return $self unless @{ $self->oe_ids };
 
   $self->calculate_item_links;
   $self->calculate_fill_up if $self->fill_up;
@@ -128,10 +127,13 @@ sub calculate_item_links {
   my $data = selectall_hashref_query($::form, $self->dbh, $query, @oe_ids);
 
   for (@$data) {
+    my $qty = $_->{doi_qty} * AM->convert_unit($_->{doi_unit} => $_->{oi_unit});
     $self->shipped_qty->{$_->{oi_id}} //= 0;
-    $self->shipped_qty->{$_->{oi_id}} += $_->{doi_qty} * AM->convert_unit($_->{doi_unit} => $_->{oi_unit});
+    $self->shipped_qty->{$_->{oi_id}} += $qty;
     $self->oi2oe->{$_->{oi_id}}        = $_->{trans_id};
     $self->oi_qty->{$_->{oi_id}}       = $_->{oi_qty};
+
+    push @{ $self->matches }, [ $_->{oi_id}, $_->{doi_id}, $qty, 1 ] if $self->keep_matches;
   }
 }
 
@@ -194,6 +196,7 @@ sub calculate_fill_up {
 
         $self->shipped_qty->{$oi->{id}} += $min_qty;
         $doi->{qty}                     -= $min_qty / $factor;  # TODO: find a way to avoid float rounding
+        push @{ $self->matches }, [ $oi->{id}, $doi->{id}, $min_qty, 0 ] if $self->keep_matches;
       }
     }
   }
@@ -205,7 +208,7 @@ sub calculate_fill_up {
 sub write_to {
   my ($self, $objects) = @_;
 
-  die 'expecting array of objects' unless 'ARRAY' eq ref $objects;
+  croak 'expecting array of objects' unless 'ARRAY' eq ref $objects;
 
   my $shipped_qty = $self->shipped_qty;
 
@@ -214,7 +217,7 @@ sub write_to {
       $obj->{shipped_qty} = $shipped_qty->{$obj->id} //= 0;
       $obj->{delivered}   = $shipped_qty->{$obj->id} == $obj->qty;
     } elsif ('SL::DB::Order' eq ref $obj) {
-      if (exists $obj->{orderitems}) {
+      if (defined $obj->{orderitems}) {
         $self->write_to($obj->{orderitems});
         $obj->{delivered} = all { $_->{delivered} } @{ $obj->{orderitems} };
       } else {
@@ -231,7 +234,9 @@ sub write_to {
 sub write_to_objects {
   my ($self) = @_;
 
-  die 'Can only use write_to_objects, when calculate was called with objects. Use write_to instead.' unless $self->objects_or_ids;
+  return unless @{ $self->oe_ids };
+
+  croak 'Can only use write_to_objects, when calculate was called with objects. Use write_to instead.' unless $self->objects_or_ids;
 
   $self->write_to($self->objects);
 }
@@ -250,10 +255,11 @@ sub normalize_input {
   $self->objects_or_ids(!!blessed($data->[0]));
 
   if ($self->objects_or_ids) {
-    die 'unblessed object in data while expecting object' if any { !blessed($_) } @$data;
+    croak 'unblessed object in data while expecting object' if any { !blessed($_) } @$data;
     $self->objects($data);
   } else {
-    die 'object or reference in data while expecting ids' if any { ref($_) } @$data;
+    croak 'object or reference in data while expecting ids' if any { ref($_) } @$data;
+    croak 'ids need to be numbers'                          if any { ! ($_ * 1) } @$data;
     $self->oe_ids($data);
   }
 
@@ -267,9 +273,9 @@ sub available_item_identity_fields {
 sub init_oe_ids {
   my ($self) = @_;
 
-  die 'oe_ids not initialized in id mode'            if !$self->objects_or_ids;
-  die 'objects not initialized before accessing ids' if $self->objects_or_ids && !defined $self->objects;
-  die 'objects need to be Order or OrderItem'        if any  {  ref($_) !~ /^SL::DB::Order(?:Item)?$/ } @{ $self->objects };
+  croak 'oe_ids not initialized in id mode'            if !$self->objects_or_ids;
+  croak 'objects not initialized before accessing ids' if $self->objects_or_ids && !defined $self->objects;
+  croak 'objects need to be Order or OrderItem'        if any  {  ref($_) !~ /^SL::DB::Order(?:Item)?$/ } @{ $self->objects };
 
   [ uniq map { ref($_) =~ /Item/ ? $_->trans_id : $_->id } @{ $self->objects } ]
 }
@@ -278,6 +284,7 @@ sub init_dbh { SL::DB->client->dbh }
 
 sub init_oi2oe { {} }
 sub init_oi_qty { {} }
+sub init_matches { [] }
 sub init_delivered {
   my ($self) = @_;
   my $d = { };
@@ -442,6 +449,14 @@ default is a client setting. Possible values include:
 
 =back
 
+=item * C<keep_matches>
+
+Boolean. If set to true the internal matchings of OrderItems and
+DeliveryOrderItems will be kept for later postprocessing, in case you need more
+than this modules provides.
+
+See C<matches> for the returned format.
+
 =back
 
 =item C<calculate OBJECTS>
@@ -472,6 +487,13 @@ was found it will be set to zero.
 C<delivered> is guaranteed only to be the correct boolean value, but not
 any specific value.
 
+Note: C<write_to> will avoid loading unnecessary objects. This means if it is
+called with an Order object that has not loaded its orderitems yet, only
+C<delivered> will be set in the Order object. A subsequent C<<
+$order->orderitems->[0]->{delivered} >> will return C<undef>, and C<<
+$order->orderitems->[0]->shipped_qty >> will invoke another implicit
+calculation.
+
 =item C<shipped_qty>
 
 Valid after L</calculate>. Returns a hasref with shipped qtys by orderitems id.
@@ -483,6 +505,32 @@ linked elements were found.
 
 Valid after L</calculate>. Returns a hashref with a delivered flag by order id.
 
+=item C<matches>
+
+Valid after L</calculate> with C<with_matches> set. Returns an arrayref of
+individual matches. Each match is an arrayref with these fields:
+
+=over 4
+
+=item *
+
+The id of the OrderItem.
+
+=item *
+
+The id of the DeliveryOrderItem.
+
+=item *
+
+The qty that was matched between the two converted to the unit of the OrderItem.
+
+=item *
+
+A boolean flag indicating if this match was found with record_item links. If
+false, the match was made in the fill up stage.
+
+=back
+
 =back
 
 =head1 REPLACED FUNCTIONALITY
index bbf53e3..1df0eb2 100644 (file)
--- a/SL/IS.pm
+++ b/SL/IS.pm
@@ -2130,6 +2130,11 @@ sub get_customer {
   my $payment_id;
 
   # get customer
+  my $where = '';
+  if ($cid) {
+    $where .= 'AND c.id = ?';
+    push @values, $cid;
+  }
   $query =
     qq|SELECT
          c.id AS customer_id, c.name AS customer, c.discount as customer_discount, c.creditlimit,
@@ -2141,8 +2146,7 @@ sub get_customer {
        FROM customer c
        LEFT JOIN business b ON (b.id = c.business_id)
        LEFT JOIN currencies cu ON (c.currency_id=cu.id)
-       WHERE c.id = ?|;
-  push @values, $cid;
+       WHERE 1 = 1 $where|;
   $ref = selectfirst_hashref_query($form, $dbh, $query, @values);
 
   delete $ref->{salesman_id} if !$ref->{salesman_id};
index 895db95..bcdffa5 100644 (file)
--- a/SL/WH.pm
+++ b/SL/WH.pm
@@ -312,9 +312,7 @@ sub transfer_assembly {
    }
     # gibt die Fehlermeldung zurück. A.) Keine Teile definiert
     #                                B.) Artikel und Anzahl der fehlenden Teile/Dienstleistungen
-    if ($kannNichtFertigen) {
-      return 0;
-    }
+    die "<br><br>" . $kannNichtFertigen if ($kannNichtFertigen);
 
     # soweit alles gut. Jetzt noch die wirkliche Lagerbewegung für das Erzeugnis ausführen ...
     my $transferAssemblySQL = qq|INSERT INTO inventory (parts_id, warehouse_id, bin_id, chargenumber, bestbefore,
index 790cb69..fe978cb 100644 (file)
@@ -826,7 +826,11 @@ sub post {
     # no restore_from_session_id needed. we like to have a newly generated
     # list of invoices for bank transactions
     print $form->redirect_header($form->{callback}) if ($form->{callback} =~ /BankTransaction/);
-    $form->redirect($locale->text('AP transaction posted.')) unless $inline;
+    $form->redirect($locale->text('AP transaction posted.') . ' ' . $locale->text('ID') . ': ' . $form->{id}) unless $inline;
+    # TODO Add callback/return flag in myconfig
+    # With version 3.5 we can add documents, but only after posting. there should be a flag in myconfig for the user
+    # $form->{callback} ||= 'ap.pl?action=edit&id=' . $form->{id} if $myconfig{no_reset_arap};
+
   } else {
     $form->error($locale->text('Cannot post transaction!'));
   }
index 9d1784f..ec028e8 100644 (file)
@@ -792,7 +792,7 @@ sub post {
   }
   # /saving the history
 
-  $form->redirect($locale->text("AR transaction posted.")) unless $inline;
+  $form->redirect($locale->text('AR transaction posted.') . ' ' . $locale->text('ID') . ': ' . $form->{id}) unless $inline;
 
   $main::lxdebug->leave_sub();
 }
index 4652e7c..1a304e7 100644 (file)
@@ -296,7 +296,7 @@ sub setup_do_action_bar {
         action => [
           t8('Transfer out via default'),
           submit   => [ '#form', { action => "transfer_out_default" } ],
-          checks   => [ @req_trans_desc, @transfer_qty ],
+          checks   => [ @req_trans_desc ],
           disabled => $::form->{delivered} ? t8('This record has already been delivered.') : undef,
           only_if  => $is_customer && $::instance_conf->get_transfer_default,
         ],
@@ -310,7 +310,7 @@ sub setup_do_action_bar {
         action => [
           t8('Transfer in via default'),
           submit   => [ '#form', { action => "transfer_in_default" } ],
-          checks   => [ @req_trans_desc, @transfer_qty ],
+          checks   => [ @req_trans_desc ],
           disabled => $::form->{delivered} ? t8('This record has already been delivered.') : undef,
           only_if  => !$is_customer && $::instance_conf->get_transfer_default,
         ],
@@ -401,6 +401,7 @@ sub form_header {
   my $class       = "SL::DB::" . ($form->{vc} eq 'customer' ? 'Customer' : 'Vendor');
   $form->{VC_OBJ} = $class->load_cached($form->{ $form->{vc} . '_id' });
 
+  $form->{CONTACT_OBJ}   = $form->{cp_id} ? SL::DB::Contact->load_cached($form->{cp_id}) : undef;
   my $current_employee   = SL::DB::Manager::Employee->current;
   $form->{employee_id}   = $form->{old_employee_id} if $form->{old_employee_id};
   $form->{salesman_id}   = $form->{old_salesman_id} if $form->{old_salesman_id};
@@ -457,7 +458,7 @@ sub form_header {
   $::request->{layout}->add_javascripts_inline("\$(function(){$dispatch_to_popup});");
 
 
-  $form->{follow_up_trans_info} = $form->{donumber} .'('. $form->{VC_OBJ}->name .')';
+  $form->{follow_up_trans_info} = $form->{donumber} .'('. $form->{VC_OBJ}->name .')' if $form->{VC_OBJ};
 
   $::request->{layout}->use_javascript(map { "${_}.js" } qw(kivi.File kivi.MassDeliveryOrderPrint kivi.SalesPurchase kivi.Part ckeditor/ckeditor ckeditor/adapters/jquery kivi.io autocomplete_customer));
 
@@ -968,7 +969,9 @@ sub invoice {
   $main::auth->assert($form->{type} eq 'purchase_delivery_order' ? 'vendor_invoice_edit' : 'invoice_edit');
 
   $form->{convert_from_do_ids} = $form->{id};
-  $form->{deliverydate}        = $form->{transdate};
+  # if we have a reqdate (Liefertermin), this is definetely the preferred
+  # deliverydate for invoices
+  $form->{deliverydate}        = $form->{reqdate} || $form->{transdate};
   $form->{transdate}           = $form->{invdate} = $form->current_date(\%myconfig);
   $form->{duedate}             = $form->current_date(\%myconfig, $form->{invdate}, $form->{terms} * 1);
   $form->{defaultcurrency}     = $form->get_default_currency(\%myconfig);
@@ -1805,7 +1808,7 @@ sub transfer_in_out_default {
       # ... and do not create a hash entry in %qty_parts below (will skip check for bins for the transfer == out case)
       # ... and push only a empty (undef) element to @all_requests (will skip check for bin_id and warehouse_id and will not alter the row)
 
-      $qty = 0 if (!$::instance_conf->get_transfer_default_services && !defined($part_info_map{$form->{"id_$i"}}->{inventory_accno_id}) && !$part_info_map{$form->{"id_$i"}}->{assembly});
+      $qty = 0 if (!$::instance_conf->get_transfer_default_services && $part_info_map{$form->{"id_$i"}}->{part_type} eq 'service');
       $qty_parts{$form->{"id_$i"}} += $qty;
       if ($qty == 0) {
         delete $qty_parts{$form->{"id_$i"}} unless $qty_parts{$form->{"id_$i"}};
index 5d94556..d3341f2 100644 (file)
@@ -5,6 +5,23 @@ use SL::Locale::String qw(t8);
 
 use strict;
 
+# convention:
+# preset_text_$formname will generate a input textarea
+# and will be preset in $form email dialog if the form name matches
+
+my %mail_strings = (
+  salutation_male                     => t8('Salutation male'),
+  salutation_female                   => t8('Salutation female'),
+  salutation_general                  => t8('Salutation general'),
+  salutation_punctuation_mark         => t8('Salutation punctuation mark'),
+  preset_text_sales_quotation         => t8('Preset email text for sales quotations'),
+  preset_text_sales_order             => t8('Preset email text for sales orders'),
+  preset_text_sales_delivery_order    => t8('Preset email text for sales delivery orders'),
+  preset_text_invoice                 => t8('Preset email text for sales invoices'),
+  preset_text_request_quotation       => t8('Preset email text for requests (rfq)'),
+  preset_text_purchase_order          => t8('Preset email text for purchase orders'),
+);
+
 sub edit_greetings {
   $main::lxdebug->enter_sub();
 
@@ -135,6 +152,61 @@ sub save_sepa_strings {
 
   $main::lxdebug->leave_sub();
 }
+sub edit_email_strings {
+  $main::lxdebug->enter_sub();
+
+  $main::auth->assert('config');
+
+  my $form     = $main::form;
+  my $locale   = $main::locale;
+
+  $form->get_lists('languages' => 'LANGUAGES');
+  unshift @{ $form->{LANGUAGES} }, { 'id' => 'default', };
+
+  my (%translations, $translation_list);
+  foreach (keys %mail_strings)  {
+    $translation_list = GenericTranslations->list(translation_type => $_);
+    %translations     = map { ( ($_->{language_id} || 'default') => $_->{translation} ) } @{ $translation_list };
+
+    foreach my $language (@{ $form->{LANGUAGES} }) {
+      $language->{$_} = $translations{$language->{id}};
+    }
+  }
+  setup_generictranslations_edit_email_strings_action_bar();
+
+  $form->{title} = $locale->text('Edit preset email strings');
+  $form->header();
+  print $form->parse_html_template('generictranslations/edit_email_strings',{ 'MAIL_STRINGS' => \%mail_strings });
+
+  $main::lxdebug->leave_sub();
+}
+
+sub save_email_strings {
+  $main::lxdebug->enter_sub();
+
+  $main::auth->assert('config');
+
+  my $form     = $main::form;
+  my $locale   = $main::locale;
+
+  $form->get_lists('languages' => 'LANGUAGES');
+
+  unshift @{ $form->{LANGUAGES} }, { };
+  foreach my $language (@{ $form->{LANGUAGES} }) {
+    foreach (keys %mail_strings)  {
+      GenericTranslations->save('translation_type' => $_,
+                                'translation_id'   => undef,
+                                'language_id'      => $language->{id},
+                                'translation'      => $form->{"translation__" . ($language->{id} || 'default') . "__" . $_},
+                               );
+    }
+  }
+  $form->{message} = $locale->text('The Mail strings have been saved.');
+
+  edit_email_strings();
+
+  $main::lxdebug->leave_sub();
+}
 
 sub setup_generictranslations_edit_greetings_action_bar {
   my %params = @_;
@@ -163,5 +235,18 @@ sub setup_generictranslations_edit_sepa_strings_action_bar {
     );
   }
 }
+sub setup_generictranslations_edit_email_strings_action_bar {
+  my %params = @_;
+
+  for my $bar ($::request->layout->get('actionbar')) {
+    $bar->add(
+      action => [
+        t8('Save'),
+        submit    => [ '#form', { action => "save_email_strings" } ],
+        accesskey => 'enter',
+      ],
+    );
+  }
+}
 
 1;
index 6766116..6dfa457 100644 (file)
@@ -49,7 +49,6 @@ use SL::DBUtils qw(selectrow_query selectall_hashref_query);
 use SL::Webdav;
 use SL::Locale::String qw(t8);
 use SL::Helper::GlAttachments qw(count_gl_attachments);
-
 require "bin/mozilla/common.pl";
 require "bin/mozilla/reportgenerator.pl";
 
@@ -94,6 +93,7 @@ sub load_record_template {
   die "invalid template type" unless $template->template_type eq 'gl_transaction';
 
   $template->substitute_variables;
+  my $payment_suggestion =  $::form->{form_defaults}->{amount_1};
 
   # Clean the current $::form before rebuilding it from the template.
   my $form_defaults = delete $::form->{form_defaults};
@@ -133,8 +133,8 @@ sub load_record_template {
 
     $::form->{"accno_id_${row}"}          = $item->chart_id;
     $::form->{"previous_accno_id_${row}"} = $item->chart_id;
-    $::form->{"debit_${row}"}             = $::form->format_amount(\%::myconfig, $item->amount1, 2) if $item->amount1 * 1;
-    $::form->{"credit_${row}"}            = $::form->format_amount(\%::myconfig, $item->amount2, 2) if $item->amount2 * 1;
+    $::form->{"debit_${row}"}             = $::form->format_amount(\%::myconfig, ($payment_suggestion ? $payment_suggestion : $item->amount1), 2) if $item->amount1 * 1;
+    $::form->{"credit_${row}"}            = $::form->format_amount(\%::myconfig, ($payment_suggestion ? $payment_suggestion : $item->amount2), 2) if $item->amount2 * 1;
     $::form->{"taxchart_${row}"}          = $item->tax_id . '--' . $tax->rate;
     $::form->{"${_}_${row}"}              = $item->$_ for qw(source memo project_id);
   }
@@ -1317,7 +1317,6 @@ sub post_transaction {
 
     $form->error($err[$errno]);
   }
-  undef($form->{callback});
   # saving the history
   if(!exists $form->{addition} && $form->{id} ne "") {
     $form->{snumbers} = qq|gltransaction_| . $form->{id};
@@ -1327,6 +1326,12 @@ sub post_transaction {
   }
   # /saving the history
 
+  if ($form->{callback} =~ /BankTransaction/) {
+    print $form->redirect_header($form->{callback});
+    $form->redirect($locale->text('GL transaction posted.') . ' ' . $locale->text('ID') . ': ' . $form->{id});
+  }
+  # remove or clarify
+  undef($form->{callback});
   $main::lxdebug->leave_sub();
 }
 
index 5bd5a25..778c718 100644 (file)
@@ -1670,6 +1670,7 @@ sub _update_part_information {
 }
 
 sub _update_ship {
+  return unless $::form->{id};
   my $helper = SL::Helper::ShippedQty->new->calculate($::form->{id});
 
   for my $i (1..$::form->{rowcount}) {
@@ -1968,14 +1969,19 @@ sub show_sales_purchase_email_dialog {
   my $email = '';
   if ($::form->{cp_id}) {
     $email = SL::DB::Contact->load_cached($::form->{cp_id})->cp_email;
-  } elsif ($::form->{vc} && $::form->{vc_id}) {
+  }
+
+  if (!$email && $::form->{vc} && $::form->{vc_id}) {
     $email = SL::DB::Customer->load_cached($::form->{vc_id})->email if 'customer' eq $::form->{vc};
     $email = SL::DB::Vendor  ->load_cached($::form->{vc_id})->email if 'vendor'   eq $::form->{vc};
   }
 
+  $email = '' if $::form->{type} eq 'purchase_delivery_order';
+
   my $email_form = {
     to                  => $email,
     subject             => $::form->generate_email_subject,
+    message             => $::form->generate_email_body,
     attachment_filename => $::form->generate_attachment_filename,
   };
 
index b36292e..2ee4ca8 100644 (file)
@@ -9,6 +9,15 @@ kleinere neue Features und Detailverbesserungen:
   - SEPA-XML: alle Sonderzeichen filtern
   - SEPA-Export: Export wieder rückgängig machen, falls noch Status offen
   - Stammdaten -> Berichte -> Artikel: Standardlager und Lagerplatz optional anzeigen
+  - Vorbelegte Texte inkl. Ansprechpartner für den E-Mail-Versand bei allen Workflows hinzugefügt.
+  - DATEV-Export: Buchungen für einen bestimmten Zeitraum ab einem
+    Buchungsdatum filtern. Z.B. wenn man einen DATEV-Export für Januar schon
+    exportiert hat, und im Juni noch ein Buchung für Januar nachbucht, kann man
+    mit "Erfassungsdatum Von: 01.06.2017" nur diese eine Buchung aus Januar
+    exportieren.
+  - Kontoauszug verbuchen -> Buchung erstellen um Dialogbuchungen erweitert.
+    Vom Kontoimport ist es jetzt auch möglich in Vorlagen aus der Dialog-
+    Buchungsmaske zu buchen und nicht nur in Kreditorenbuchungsvorlagen
 
 2017-07-17 - Release 3.5.0
 
index f742c36..7b33f1d 100755 (executable)
@@ -36,6 +36,7 @@ $self->{texts} = {
   '...done'                     => '...fertig',
   '...on the TODO list'         => '...auf der Aufgabenliste',
   '0% tax with taxkey'          => '0% Steuer mit Steuerschl&uuml;ssel ',
+  '1)'                          => '1)',
   '1. Quarter'                  => '1. Quartal',
   '2 years'                     => '2 Jahre',
   '2. Quarter'                  => '2. Quartal',
@@ -72,6 +73,7 @@ $self->{texts} = {
   'AP Transaction Storno (one letter abbreviation)' => 'S',
   'AP Transaction with Storno (abbreviation)' => 'K(S)',
   'AP Transactions'             => 'Kreditorenbuchungen',
+  'AP template suggestions'     => 'Vorschlag Kreditorenbuchung',
   'AP transaction posted.'      => 'Kreditorenbuchung verbucht.',
   'AP transactions changeable'  => 'Änderbarkeit von Kreditorenbuchungen',
   'AP transactions with sales taxkeys and/or AR transactions with input taxkeys' => 'Kreditorenbuchungen mit Umsatzsteuer-Steuerschlüsseln und/oder Debitorenbuchungen mit Vorsteuer-Steuerschlüsseln',
@@ -1132,6 +1134,7 @@ $self->{texts} = {
   'Edit payment term'           => 'Zahlungsbedingungen bearbeiten',
   'Edit picture'                => 'Bild bearbeiten',
   'Edit pre-defined text'       => 'Vordefinierten Textblock bearbeiten',
+  'Edit preset email strings'   => 'Vorbelegte Texte für E-Mails editieren',
   'Edit price rule'             => 'Preisregel bearbeiten',
   'Edit pricegroup'             => 'Preisgruppe bearbeiten',
   'Edit prices and discount (if not used, textfield is ONLY set readonly)' => 'Preise und Rabatt in Formularen frei anpassen (falls deaktiviert, wird allerdings NUR das textfield auf READONLY gesetzt / kann je nach Browserversion und technischen Fähigkeiten des Anwenders noch umgangen werden)',
@@ -1341,7 +1344,6 @@ $self->{texts} = {
   'Filter for customer variables' => 'Filter für benutzerdefinierte Kundenvariablen',
   'Filter for item variables'   => 'Filter für benutzerdefinierte Artikelvariablen',
   'Filter parts'                => 'Artikel filtern',
-  'Filter vendors'              => 'Lieferanten filtern',
   'Financial Controlling'       => 'Finanzcontrolling',
   'Financial Controlling Report' => 'Finanzcontrollingbericht',
   'Financial Overview'          => 'Finanzübersicht',
@@ -1402,6 +1404,7 @@ $self->{texts} = {
   'GL Transaction (abbreviation)' => 'DB',
   'GL Transactions'             => 'Dialogbuchungen',
   'GL search'                   => 'FiBu Suche',
+  'GL template suggestions'     => 'Vorschlag Dialogbuchung',
   'GL transactions changeable'  => 'Änderbarkeit von Dialogbuchungen',
   'GLN'                         => 'GLN',
   'Gegenkonto'                  => 'Gegenkonto',
@@ -1660,7 +1663,7 @@ $self->{texts} = {
   'Last Dunning'                => 'Letzte Mahnung',
   'Last Invoice Number'         => 'Letzte Rechnungsnummer',
   'Last Purchase Delivery Order Number' => 'Letzte Lieferscheinnummer (Einkauf)',
-  'Last Purchase Order Number'  => 'Letzte Lieferantenautragsnummer',
+  'Last Purchase Order Number'  => 'Letzte Lieferantenauftragsnummer',
   'Last RFQ Number'             => 'Letzte Anfragenummer',
   'Last Sales Delivery Order Number' => 'Letzte Lieferscheinnummer (Verkauf)',
   'Last Sales Order Number'     => 'Letzte Auftragsnummer',
@@ -1829,6 +1832,7 @@ $self->{texts} = {
   'Net amount (for verification)' => 'Nettobetrag (zur Überprüfung)',
   'Net amounts differ too much' => 'Nettobeträge weichen zu sehr ab.',
   'Net value in Order'          => 'Netto Auftrag',
+  'Net value in closed delivery orders' => 'Netto in geschlossenen Lieferscheinen',
   'Net value transferred in / out' => 'Netto ein- /ausgelagert',
   'Net value without delivery orders' => 'Netto ohne Lieferschein',
   'Netherlands'                 => 'Niederlande',
@@ -1853,9 +1857,11 @@ $self->{texts} = {
   'No'                          => 'Nein',
   'No %s was found matching the search parameters.' => 'Es wurde kein %s gefunden, auf den die Suchparameter zutreffen.',
   'No 1:n or n:1 relation'      => 'Keine 1:n oder n:1 Beziehung',
+  'No AP template was found.'   => 'Keine Kreditorenbuchungsvorlage gefunden.',
   'No Company Address given'    => 'Keine Firmenadresse hinterlegt!',
   'No Company Name given'       => 'Kein Firmenname hinterlegt!',
   'No Customer was found matching the search parameters.' => 'Zu dem Suchbegriff wurde kein Endkunde gefunden',
+  'No GL template was found.'   => 'Keine Dialogbuchungsvorlage gefunden.',
   'No Journal'                  => 'Kein Journal',
   'No Vendor was found matching the search parameters.' => 'Zu dem Suchbegriff wurde kein Händler gefunden',
   'No action defined.'          => 'Keine Aktion definiert.',
@@ -1911,7 +1917,6 @@ $self->{texts} = {
   'No such job #1 in the database.' => 'Hintergrund-Job #1 existiert nicht mehr.',
   'No summary account'          => 'Kein Sammelkonto',
   'No template has been selected yet.' => 'Es wurde noch keine Vorlage ausgewählt.',
-  'No template was found.'      => 'Es wurde keine Vorlage gefunden.',
   'No text blocks have been created for this position.' => 'Für diese Position wurden noch keine Textblöcke angelegt.',
   'No text has been entered yet.' => 'Es wurde noch kein Text eingegeben.',
   'No title yet'                => 'Bisher ohne Titel',
@@ -2200,6 +2205,13 @@ $self->{texts} = {
   'Prepare bank collection via SEPA XML' => 'Einzug via SEPA XML vorbereiten',
   'Prepare bank transfer via SEPA XML' => 'Überweisung via SEPA XML vorbereiten',
   'Prepayment'                  => 'Vorauszahlung',
+  'Preset email strings'        => 'Vorbelegte E-Mail-Texte',
+  'Preset email text for purchase orders' => 'Vorbelegter E-Mail-Text für Einkaufsaufträge',
+  'Preset email text for requests (rfq)' => 'Vorbelegter E-Mail-Text für Anfragen',
+  'Preset email text for sales delivery orders' => 'Vorbelegter E-Mail-Text für Verkaufs-Lieferscheine',
+  'Preset email text for sales invoices' => 'Vorbelegter E-Mail-Text für Rechnungen',
+  'Preset email text for sales orders' => 'Vorbelegter E-Mail-Text für Aufträge',
+  'Preset email text for sales quotations' => 'Vorbelegter E-Mail-Text für Angebote',
   'Preview'                     => 'Vorschau',
   'Preview Mode'                => 'Vorschaumodus',
   'Previous transdate text'     => 'wurde gespeichert am',
@@ -2314,6 +2326,7 @@ $self->{texts} = {
   'Qty equals #1'               => 'Menge ist #1',
   'Qty in Order'                => 'Menge Auftrag',
   'Qty in Selected Records'     => 'Menge in gewählten Belegen',
+  'Qty in closed delivery orders' => 'Menge in geschlossenen Lieferscheinen',
   'Qty in delivery orders'      => 'Menge mit Lieferschein',
   'Qty in stock'                => 'Lagerbestand',
   'Qty less than #1'            => 'Menge weniger als #1',
@@ -2377,6 +2390,7 @@ $self->{texts} = {
   'Reference'                   => 'Referenz',
   'Reference / Invoice Number'  => 'Referenz / Rechnungsnummer',
   'Reference day'               => 'Referenztag',
+  'Reference filter for transaction templates' => 'Dialogbuchungs-Referenz',
   'Reference missing!'          => 'Referenz fehlt!',
   'Release From Stock'          => 'Lagerausgang',
   'Remaining'                   => 'Rest',
@@ -2530,10 +2544,13 @@ $self->{texts} = {
   'Sales quotation #1 has been deleted.' => 'Angebot #1 wurde gelöscht.',
   'Sales quotation #1 has been updated.' => 'Angebot #1 wurde aktualisiert.',
   'Salesman'                    => 'Verkäufer/in',
-  'Salesman (ID)'               => 'Verkäufer/in (ID)',
   'Salesman (database ID)'      => 'Verkäufer/in (Datenbank-ID)',
   'Salesman (login)'            => 'Verkäufer/in (Login)',
   'Salesperson'                 => 'Verkäufer',
+  'Salutation female'           => 'Anrede weiblich',
+  'Salutation general'          => 'Anrede anonym (personenlos)',
+  'Salutation male'             => 'Anrede männlich',
+  'Salutation punctuation mark' => 'Zeichensetzungs-Trenner nach der Anrede-Formel (Punkt, Ausrufezeichen, etc)',
   'Same Filename !'             => 'unveränderter Dateiname !',
   'Same as the quote character' => 'Wie Anf&uuml;hrungszeichen',
   'Sat. Fax'                    => 'Sat. Fax',
@@ -2867,9 +2884,9 @@ $self->{texts} = {
   'Telephone'                   => 'Telefon',
   'Template'                    => 'Druckvorlage',
   'Template Code'               => 'Vorlagenkürzel',
+  'Template Description'        => 'Name der Vorlage',
   'Template database'           => 'Datenbankvorlage',
   'Template date'               => 'Vorlagendatum',
-  'Template suggestions'        => 'Vorschläge für Vorlagen',
   'Templates'                   => 'Vorlagen',
   'Terms missing in row '       => '+Tage fehlen in Zeile ',
   'Test database connectivity'  => 'Datenbankverbindung testen',
@@ -2901,6 +2918,7 @@ $self->{texts} = {
   'The ID #1 is not a valid database ID.' => 'Die ID #1 ist keine gültige Datenbank-ID.',
   'The LDAP server "#1:#2" is unreachable. Please check config/kivitendo.conf.' => 'Der LDAP-Server "#1:#2" ist nicht erreichbar. Bitte &uuml;berpr&uuml;fen Sie die Angaben in config/kivitendo.conf.',
   'The MT940 import needs an import profile called MT940' => 'Der MT940 Import benötigt ein Importprofil mit dem Namen "MT940"',
+  'The Mail strings have been saved.' => 'Die vorbelegten E-Mail-Texte wurden gespeichert.',
   'The PDF has been created'    => 'Die PDF-Datei wurde erstellt.',
   'The PDF has been printed'    => 'Das PDF-Dokument wurde gedruckt.',
   'The SEPA export has been created.' => 'Der SEPA-Export wurde erstellt',
@@ -3298,6 +3316,7 @@ $self->{texts} = {
   'Time/cost estimate actions'  => 'Aktionen für Kosten-/Zeitabschätzung',
   'Timerange'                   => 'Zeitraum',
   'Timestamp'                   => 'Uhrzeit',
+  'Tired of copying always nice phrases for this message? Click here to use the new preset message option!' => 'Müde vom vielen Copy & Paste aus vorherigen Anschreiben? Hier klicken, um E-Mail-Texte vorzudefinieren!',
   'Title'                       => 'Titel',
   'To'                          => 'An',
   'To (email)'                  => 'An',
index 7747ed2..6041443 100644 (file)
@@ -33,7 +33,7 @@ order=< > \n
 \n=<br>
 
 [Template/LaTeX]
-order=\\ <pagebreak> & \n \r " $ <bullet> % _ # ^ { } < > £ ± ² ³ ° § ® © \xad \xa0 ➔ → ← | − ≤ ≥ ‐ ​ Ω μ Δ
+order=\\ <pagebreak> & \n \r " $ <bullet> % _ # ^ { } < > £ ± ² ³ ° § ® © \xad \xa0 ➔ → ← | − ≤ ≥ ‐ ​ Ω μ Δ ~
 \\=\\textbackslash\s
 <pagebreak>=
 "=''
@@ -74,6 +74,7 @@ _=\\_
 μ={\\textmu}
 Δ=$\\Delta$
 Ω=$\\Omega$
+~={\\textasciitilde}
 
 [Template/OpenDocument]
 order=& < > " ' \x80 \n \r
index 20dbcda..b363c90 100644 (file)
@@ -29,7 +29,7 @@ order=< > \n
 \n=<br>
 
 [Template/LaTeX]
-order=\\ <pagebreak> & \n \r " $ <bullet> % _ # ^ { } < > £ ± ² ³ ° § ® © ‐ Ω ~ μ Δ
+order=\\ <pagebreak> & \n \r " $ <bullet> % _ # ^ { } < > £ ± ² ³ ° § ® © \xad \xa0 ➔ → ← | − ≤ ≥ ‐ ​ Ω μ Δ ~
 \\=\\textbackslash\s
 <pagebreak>=
 "=''
@@ -38,7 +38,9 @@ $=\\$
 <bullet>=$\\bullet$
 %=\\%
 _=\\_
-#=\\#
+# A hash mark starts a comment; therefore the line is ignored. So use
+# its hex code instead.
+\x23=\\#
 {=\\{
 }=\\}
 <=$<$
@@ -51,14 +53,24 @@ _=\\_
 ²=$^2$
 ³=$^3$
 °=$^\\circ$
-§=\\S
-®=\\textregistered
-©=\\textcopyright
+§=\\S\s
+®={\\textregistered}
+©={\\textcopyright}
+\xad=\\-
+➔=$\\rightarrow$
+→=$\\rightarrow$
+←=$\\leftarrow$
+\xa0=~
+|={\\textbar}
+−={\\textemdash}
+≤=$\\leq$
+≥=$\\geq$
 ‐={}-{}
-~=${}$~
+​={\\hspace{0pt}}
 μ={\\textmu}
 Δ=$\\Delta$
 Ω=$\\Omega$
+~={\\textasciitilde}
 
 [Template/OpenDocument]
 order=& < > " ' \x80 \n \r
index c520c4b..eae8faf 100644 (file)
   module: generictranslations.pl
   params:
     action: edit_sepa_strings
+- parent: system_languages_and_translations
+  id: system_languages_and_translations_email_strings
+  name: Preset email strings
+  order: 500
+  module: generictranslations.pl
+  params:
+    action: edit_email_strings
 - parent: system
   id: system_payment_terms
   name: Payment Terms
index 0c06dbf..cb08e2f 100755 (executable)
@@ -288,6 +288,12 @@ Print the manual page and exit.
 Log in as C<username>. The default is to use the value from the
 configuration file and C<demo> if none is set there.
 
+=item B<-c>, B<--client>=C<client>
+
+Use the database for client C<client>. C<client> can be a client's
+database ID or its name. The default is to use the value from the
+configuration file.
+
 =item B<-o>, B<--log-file>=C<filename>
 
 Use C<filename> as the log file. The default is to use the value from
index 783f7c5..1ce5fe6 100644 (file)
@@ -14,7 +14,7 @@ sub today_local {
 
 package main;
 
-use Test::More tests => 80;
+use Test::More tests => 56;
 
 use lib 't';
 use strict;
@@ -22,6 +22,7 @@ use utf8;
 
 use Carp;
 use Support::TestSetup;
+use SL::Dev::ALL qw(:ALL);
 
 use_ok 'SL::BackgroundJob::CreatePeriodicInvoices';
 use_ok 'SL::DB::Chart';
@@ -34,15 +35,11 @@ use_ok 'SL::DB::TaxZone';
 
 Support::TestSetup::login();
 
-our ($ar_chart, $buchungsgruppe, $currency_id, $customer, $employee, $order, $part, $tax_zone, $unit, @invoices);
+our ($ar_chart, $customer, $order, $part, $unit, @invoices);
 
 sub init_common_state {
-  $ar_chart       = SL::DB::Manager::Chart->find_by(accno => '1400')                        || croak "No AR chart";
-  $buchungsgruppe = SL::DB::Manager::Buchungsgruppe->find_by(description => 'Standard 19%') || croak "No accounting group";
-  $currency_id    = SL::DB::Default->get->currency_id;
-  $employee       = SL::DB::Manager::Employee->current                                      || croak "No employee";
-  $tax_zone       = SL::DB::Manager::TaxZone->find_by( description => 'Inland')             || croak "No taxzone";
-  $unit           = SL::DB::Manager::Unit->find_by(name => 'psch')                          || croak "No unit";
+  $ar_chart = SL::DB::Manager::Chart->find_by(accno => '1400') || croak "No AR chart";
+  $unit     = SL::DB::Manager::Unit->find_by(name => 'psch')   || croak "No unit";
 }
 
 sub clear_up {
@@ -57,39 +54,31 @@ sub create_invoices {
   # Clean up: remove invoices, orders, parts and customers
   clear_up();
 
-  $customer     = SL::DB::Customer->new(
-    name        => 'Test Customer',
-    currency_id => $currency_id,
-    taxzone_id  => $tax_zone->id,
+  $customer = new_customer(
+    name => 'Test Customer',
     %{ $params{customer} }
   )->save;
 
-  $part = SL::DB::Part->new(
-    partnumber         => 'T4254',
-    description        => 'Fourty-two fifty-four',
-    lastcost           => 222.22,
-    sellprice          => 333.33,
-    part_type          => 'part',
-    buchungsgruppen_id => $buchungsgruppe->id,
-    unit               => $unit->name,
+  $part = new_part(
+    partnumber  => 'T4254',
+    description => 'Fourty-two fifty-four',
+    lastcost    => 222.22,
+    sellprice   => 333.33,
+    unit        => $unit->name,
     %{ $params{part} }
   )->save;
-  $part->load;
 
-  $order                     = SL::DB::Order->new(
-    customer_id              => $customer->id,
-    currency_id              => $currency_id,
-    taxzone_id               => $tax_zone->id,
+  $order = create_sales_order(
+    save                     => 1,
+    customer                 => $customer,
     transaction_description  => '<%period_start_date%>',
     orderitems               => [
-      { parts_id             => $part->id,
-        description          => $part->description,
-        lastcost             => $part->lastcost,
-        sellprice            => $part->sellprice,
-        qty                  => 1,
-        unit                 => $unit->name,
+      SL::Dev::Record::create_order_item(
+        part => $part,
+        qty  => 1,
+        unit => $unit->name,
         %{ $params{orderitem} },
-      },
+      ),
     ],
     periodic_invoices_config => {
       active                 => 1,
@@ -99,10 +88,6 @@ sub create_invoices {
     %{ $params{order} },
   );
 
-  $order->calculate_prices_and_taxes;
-
-  ok($order->save(cascade => 1));
-
   SL::BackgroundJob::CreatePeriodicInvoices->new->run(SL::DB::BackgroundJob->new);
 
   @invoices = @{ SL::DB::Manager::Invoice->get_all(sort_by => [ qw(id) ]) };
index fd97575..69028b1 100644 (file)
@@ -1,4 +1,4 @@
-use Test::More tests => 105;
+use Test::More tests => 130;
 
 use strict;
 
@@ -23,7 +23,7 @@ use SL::DB::PaymentTerm;
 use SL::DB::PurchaseInvoice;
 use SL::DB::BankTransaction;
 use SL::Controller::BankTransaction;
-use SL::Dev::ALL;
+use SL::Dev::ALL qw(:ALL);
 use Data::Dumper;
 
 my ($customer, $vendor, $currency_id, $unit, $tax, $tax7, $tax_9, $payment_terms, $bank_account);
@@ -41,6 +41,8 @@ sub clear_up {
   SL::DB::Manager::Part->delete_all(all => 1);
   SL::DB::Manager::Customer->delete_all(all => 1);
   SL::DB::Manager::Vendor->delete_all(all => 1);
+  SL::DB::Manager::SepaExportItem->delete_all(all => 1);
+  SL::DB::Manager::SepaExport->delete_all(all => 1);
   SL::DB::Manager::BankAccount->delete_all(all => 1);
   SL::DB::Manager::PaymentTerm->delete_all(all => 1);
   SL::DB::Manager::Currency->delete_all(where => [ name => 'CUR' ]);
@@ -79,6 +81,9 @@ test_ap_payment_transaction();
 test_ap_payment_part_transaction();
 test_neg_sales_invoice();
 
+test_bt_rule1();
+test_sepa_export();
+
 # remove all created data at end of test
 clear_up();
 
@@ -112,7 +117,7 @@ sub reset_state {
     name            => SL::DB::Manager::Chart->find_by(description => 'Bank')->description,
   )->save;
 
-  $customer = SL::Dev::CustomerVendor::create_customer(
+  $customer = new_customer(
     name                      => 'Test Customer',
     iban                      => 'DE12500105170648489890',
     bic                       => 'TESTBIC',
@@ -125,9 +130,9 @@ sub reset_state {
     customernumber            => 'CUST1704',
   )->save;
 
-  $payment_terms = SL::Dev::Payment::create_payment_terms;
+  $payment_terms = create_payment_terms();
 
-  $vendor = SL::Dev::CustomerVendor::create_vendor(
+  $vendor = new_vendor(
     name           => 'Test Vendor',
     payment_id     => $payment_terms->id,
     iban           => 'DE12500105170648489890',
@@ -232,7 +237,7 @@ sub test1 {
 
   $ar_transaction = test_ar_transaction(invnumber => 'salesinv1');
 
-  my $bt = SL::Dev::Payment::create_bank_transaction(record => $ar_transaction) or die "Couldn't create bank_transaction";
+  my $bt = create_bank_transaction(record => $ar_transaction) or die "Couldn't create bank_transaction";
 
   $::form->{invoice_ids} = {
     $bt->id => [ $ar_transaction->id ]
@@ -256,10 +261,10 @@ sub test_skonto_exact {
                                         payment_id => $payment_terms->id,
                                        );
 
-  my $bt = SL::Dev::Payment::create_bank_transaction(record        => $ar_transaction,
-                                                     bank_chart_id => $bank->id,
-                                                     amount        => $ar_transaction->amount_less_skonto
-                                                    ) or die "Couldn't create bank_transaction";
+  my $bt = create_bank_transaction(record        => $ar_transaction,
+                                   bank_chart_id => $bank->id,
+                                   amount        => $ar_transaction->amount_less_skonto
+                                  ) or die "Couldn't create bank_transaction";
 
   $::form->{invoice_ids} = {
     $bt->id => [ $ar_transaction->id ]
@@ -285,11 +290,11 @@ sub test_two_invoices {
   my $ar_transaction_1 = test_ar_transaction(invnumber => 'salesinv_1');
   my $ar_transaction_2 = test_ar_transaction(invnumber => 'salesinv_2');
 
-  my $bt = SL::Dev::Payment::create_bank_transaction(record        => $ar_transaction_1,
-                                                     amount        => ($ar_transaction_1->amount + $ar_transaction_2->amount),
-                                                     purpose       => "Rechnungen " . $ar_transaction_1->invnumber . " und " . $ar_transaction_2->invnumber,
-                                                     bank_chart_id => $bank->id,
-                                                    ) or die "Couldn't create bank_transaction";
+  my $bt = create_bank_transaction(record        => $ar_transaction_1,
+                                   amount        => ($ar_transaction_1->amount + $ar_transaction_2->amount),
+                                   purpose       => "Rechnungen " . $ar_transaction_1->invnumber . " und " . $ar_transaction_2->invnumber,
+                                   bank_chart_id => $bank->id,
+                                  ) or die "Couldn't create bank_transaction";
 
   my ($agreement, $rule_matches) = $bt->get_agreement_with_invoice($ar_transaction_1);
   is($agreement, 16, "points for ar_transaction_1 in test_two_invoices ok");
@@ -319,10 +324,10 @@ sub test_overpayment {
   $ar_transaction = test_ar_transaction(invnumber => 'salesinv overpaid');
 
   # amount 135 > 119
-  my $bt = SL::Dev::Payment::create_bank_transaction(record        => $ar_transaction,
-                                                     bank_chart_id => $bank->id,
-                                                     amount        => 135
-                                                    ) or die "Couldn't create bank_transaction";
+  my $bt = create_bank_transaction(record        => $ar_transaction,
+                                   bank_chart_id => $bank->id,
+                                   amount        => 135
+                                  ) or die "Couldn't create bank_transaction";
 
   $::form->{invoice_ids} = {
     $bt->id => [ $ar_transaction->id ]
@@ -349,15 +354,15 @@ sub test_overpayment_with_partialpayment {
 
   $ar_transaction = test_ar_transaction(invnumber => 'salesinv overpaid partial');
 
-  my $bt_1 = SL::Dev::Payment::create_bank_transaction(record        => $ar_transaction,
-                                                       bank_chart_id => $bank->id,
-                                                       amount        =>  10
-                                                      ) or die "Couldn't create bank_transaction";
-  my $bt_2 = SL::Dev::Payment::create_bank_transaction(record        => $ar_transaction,
-                                                       amount        => 119,
-                                                       transdate     => DateTime->today->add(days => 5),
-                                                       bank_chart_id => $bank->id,
-                                                      ) or die "Couldn't create bank_transaction";
+  my $bt_1 = create_bank_transaction(record        => $ar_transaction,
+                                     bank_chart_id => $bank->id,
+                                     amount        =>  10
+                                    ) or die "Couldn't create bank_transaction";
+  my $bt_2 = create_bank_transaction(record        => $ar_transaction,
+                                     amount        => 119,
+                                     transdate     => DateTime->today->add(days => 5),
+                                     bank_chart_id => $bank->id,
+                                    ) or die "Couldn't create bank_transaction";
 
   $::form->{invoice_ids} = {
     $bt_1->id => [ $ar_transaction->id ]
@@ -386,10 +391,10 @@ sub test_partial_payment {
   $ar_transaction = test_ar_transaction(invnumber => 'salesinv partial payment');
 
   # amount 100 < 119
-  my $bt = SL::Dev::Payment::create_bank_transaction(record        => $ar_transaction,
-                                                     bank_chart_id => $bank->id,
-                                                     amount        => 100
-                                                    ) or die "Couldn't create bank_transaction";
+  my $bt = create_bank_transaction(record        => $ar_transaction,
+                                   bank_chart_id => $bank->id,
+                                   amount        => 100
+                                  ) or die "Couldn't create bank_transaction";
 
   $::form->{invoice_ids} = {
     $bt->id => [ $ar_transaction->id ]
@@ -409,17 +414,17 @@ sub test_credit_note {
 
   my $testname = 'test_credit_note';
 
-  my $part1 = SL::Dev::Part::create_part(   partnumber => 'T4254')->save;
-  my $part2 = SL::Dev::Part::create_service(partnumber => 'Serv1')->save;
-  my $credit_note = SL::Dev::Record::create_credit_note(
+  my $part1 = new_part(   partnumber => 'T4254')->save;
+  my $part2 = new_service(partnumber => 'Serv1')->save;
+  my $credit_note = create_credit_note(
     invnumber    => 'cn 1',
     customer     => $customer,
     taxincluded  => 0,
-    invoiceitems => [ SL::Dev::Record::create_invoice_item(part => $part1, qty =>  3, sellprice => 70),
-                      SL::Dev::Record::create_invoice_item(part => $part2, qty => 10, sellprice => 50),
+    invoiceitems => [ create_invoice_item(part => $part1, qty =>  3, sellprice => 70),
+                      create_invoice_item(part => $part2, qty => 10, sellprice => 50),
                     ]
   );
-  my $bt            = SL::Dev::Payment::create_bank_transaction(record        => $credit_note,
+  my $bt            = create_bank_transaction(record        => $credit_note,
                                                                 amount        => $credit_note->amount,
                                                                 bank_chart_id => $bank->id,
                                                                 transdate     => DateTime->today->add(days => 10),
@@ -471,10 +476,10 @@ sub test_neg_ap_transaction {
   is($invoice->netamount, -20  , "$testname: netamount ok");
   is($invoice->amount   , -23.8, "$testname: amount ok");
 
-  my $bt            = SL::Dev::Payment::create_bank_transaction(record        => $invoice,
-                                                                amount        => $invoice->amount,
-                                                                bank_chart_id => $bank->id,
-                                                                transdate     => DateTime->today->add(days => 10),
+  my $bt            = create_bank_transaction(record        => $invoice,
+                                              amount        => $invoice->amount,
+                                              bank_chart_id => $bank->id,
+                                              transdate     => DateTime->today->add(days => 10),
                                                                );
   my ($agreement, $rule_matches) = $bt->get_agreement_with_invoice($invoice);
   is($agreement, 15, "points for negative ap transaction ok");
@@ -526,11 +531,11 @@ sub test_ap_payment_transaction {
   is($invoice->netamount, 115  , "$testname: netamount ok");
   is($invoice->amount   , 136.85, "$testname: amount ok");
 
-  my $bt            = SL::Dev::Payment::create_bank_transaction(record        => $invoice,
-                                                                amount        => $invoice->amount,
-                                                                bank_chart_id => $bank->id,
-                                                                transdate     => DateTime->today->add(days => 10),
-                                                               );
+  my $bt            = create_bank_transaction(record        => $invoice,
+                                              amount        => $invoice->amount,
+                                              bank_chart_id => $bank->id,
+                                              transdate     => DateTime->today->add(days => 10),
+                                             );
   $::form->{invoice_ids} = {
     $bt->id => [ $invoice->id ]
   };
@@ -579,11 +584,11 @@ sub test_ap_payment_part_transaction {
   is($invoice->netamount, 115  , "$testname: netamount ok");
   is($invoice->amount   , 136.85, "$testname: amount ok");
 
-  my $bt            = SL::Dev::Payment::create_bank_transaction(record        => $invoice,
-                                                                amount        => $invoice->amount-100,
-                                                                bank_chart_id => $bank->id,
-                                                                transdate     => DateTime->today->add(days => 10),
-                                                               );
+  my $bt            = create_bank_transaction(record        => $invoice,
+                                              amount        => $invoice->amount-100,
+                                              bank_chart_id => $bank->id,
+                                              transdate     => DateTime->today->add(days => 10),
+                                             );
   $::form->{invoice_ids} = {
     $bt->id => [ $invoice->id ]
   };
@@ -599,11 +604,11 @@ sub test_ap_payment_part_transaction {
   is($invoice->paid     ,  '36.85000', "$testname: paid ok");
   is($bt->invoice_amount, '-36.85000', "$testname: bt invoice amount for ap was assigned");
 
-  my $bt2           = SL::Dev::Payment::create_bank_transaction(record        => $invoice,
-                                                                amount        => 100,
-                                                                bank_chart_id => $bank->id,
-                                                                transdate     => DateTime->today->add(days => 10),
-                                                               );
+  my $bt2           = create_bank_transaction(record        => $invoice,
+                                              amount        => 100,
+                                              bank_chart_id => $bank->id,
+                                              transdate     => DateTime->today->add(days => 10),
+                                             );
   $::form->{invoice_ids} = {
     $bt2->id => [ $invoice->id ]
   };
@@ -625,18 +630,18 @@ sub test_neg_sales_invoice {
 
   my $testname = 'test_neg_sales_invoice';
 
-  my $part1 = SL::Dev::Part::create_part(   partnumber => 'Funkenhaube öhm')->save;
-  my $part2 = SL::Dev::Part::create_service(partnumber => 'Service-Pauschale Pasch!')->save;
+  my $part1 = new_part(   partnumber => 'Funkenhaube öhm')->save;
+  my $part2 = new_service(partnumber => 'Service-Pauschale Pasch!')->save;
 
-  my $neg_sales_inv = SL::Dev::Record::create_sales_invoice(
+  my $neg_sales_inv = create_sales_invoice(
     invnumber    => '20172201',
     customer     => $customer,
     taxincluded  => 0,
-    invoiceitems => [ SL::Dev::Record::create_invoice_item(part => $part1, qty =>  3, sellprice => 70),
-                      SL::Dev::Record::create_invoice_item(part => $part2, qty => 10, sellprice => -50),
+    invoiceitems => [ create_invoice_item(part => $part1, qty =>  3, sellprice => 70),
+                      create_invoice_item(part => $part2, qty => 10, sellprice => -50),
                     ]
   );
-  my $bt            = SL::Dev::Payment::create_bank_transaction(record        => $neg_sales_inv,
+  my $bt            = create_bank_transaction(record        => $neg_sales_inv,
                                                                 amount        => $neg_sales_inv->amount,
                                                                 bank_chart_id => $bank->id,
                                                                 transdate     => DateTime->today,
@@ -656,4 +661,75 @@ sub test_neg_sales_invoice {
   is($bt->invoice_amount      , '-345.10000', "$testname: bt invoice_amount ok");
 }
 
+sub test_bt_rule1 {
+
+  my $testname = 'test_bt_rule1';
+
+  $ar_transaction = test_ar_transaction(invnumber => 'bt_rule1');
+
+  my $bt = create_bank_transaction(record => $ar_transaction) or die "Couldn't create bank_transaction";
+
+  $ar_transaction->load;
+  $bt->load;
+  is($ar_transaction->paid   , '0.00000' , "$testname: not paid");
+  is($bt->invoice_amount     , '0.00000' , "$testname: bt invoice amount was not assigned");
+
+  my $bt_controller = SL::Controller::BankTransaction->new;
+  $::form->{dont_render_for_test} = 1;
+  $::form->{filter}{bank_account} = $bank_account->id;
+  my $bt_transactions = $bt_controller->action_list;
+
+  is(scalar(@$bt_transactions)         , 1  , "$testname: one bank_transaction");
+  is($bt_transactions->[0]->{agreement}, 20 , "$testname: agreement == 20");
+  my $match = join ( ' ',@{$bt_transactions->[0]->{rule_matches}});
+  #print "rule_matches='".$match."'\n";
+  is($match,
+     "remote_account_number(3) exact_amount(4) own_invnumber_in_purpose(5) depositor_matches(2) remote_name(2) payment_within_30_days(1) datebonus0(3) ",
+     "$testname: rule_matches ok");
+  $bt->invoice_amount($bt->amount);
+  $bt->save;
+  is($bt->invoice_amount     , '119.00000' , "$testname: bt invoice amount now set");
+};
+
+sub test_sepa_export {
+
+  my $testname = 'test_sepa_export';
+
+  $ar_transaction = test_ar_transaction(invnumber => 'sepa1');
+
+  my $bt  = create_bank_transaction(record => $ar_transaction) or die "Couldn't create bank_transaction";
+  my $se  = create_sepa_export();
+  my $sei = create_sepa_export_item(
+    chart_id       => $bank->id,
+    ar_id          => $ar_transaction->id,
+    sepa_export_id => $se->id,
+    vc_iban        => $customer->iban,
+    vc_bic         => $customer->bic,
+    vc_mandator_id => $customer->mandator_id,
+    vc_depositor   => $customer->depositor,
+    amount         => $ar_transaction->amount,
+  );
+
+  $ar_transaction->load;
+  $bt->load;
+  $sei->load;
+  is($ar_transaction->paid   , '0.00000' , "$testname: sepa1 not paid");
+  is($bt->invoice_amount     , '0.00000' , "$testname: bt invoice amount was not assigned");
+  is($bt->amount             , '119.00000' , "$testname: bt amount ok");
+  is($sei->amount            , '119.00000' , "$testname: sepa export amount ok");
+
+  my $bt_controller = SL::Controller::BankTransaction->new;
+  $::form->{dont_render_for_test} = 1;
+  $::form->{filter}{bank_account} = $bank_account->id;
+  my $bt_transactions = $bt_controller->action_list;
+
+  is(scalar(@$bt_transactions)         , 1  , "$testname: one bank_transaction");
+  is($bt_transactions->[0]->{agreement}, 25 , "$testname: agreement == 25");
+  my $match = join ( ' ',@{$bt_transactions->[0]->{rule_matches}});
+  is($match,
+     "remote_account_number(3) exact_amount(4) own_invnumber_in_purpose(5) depositor_matches(2) remote_name(2) payment_within_30_days(1) datebonus0(3) sepa_export_item(5) ",
+     "$testname: rule_matches ok");
+};
+
+
 1;
index dd0a8f1..670b5ab 100644 (file)
@@ -65,7 +65,16 @@ reset_state(customer => {id => 960, customernumber => 2});
 sub test_import {
   my $file = shift;
 
-  my $controller = SL::Controller::CsvImport->new();
+  my $controller = SL::Controller::CsvImport->new(
+    type => 'ar_transactions'
+  );
+  $controller->load_default_profile;
+  $controller->profile->set(
+    charset      => 'utf-8',
+    sep_char     => ',',
+    quote_char   => '"',
+    numberformat => $::myconfig{numberformat},
+  );
 
   my $csv_artransactions_import = SL::Controller::CsvImport::ARTransaction->new(
     settings    => {'ar_column'          => 'Rechnung',
@@ -77,56 +86,7 @@ sub test_import {
   );
 
   # $csv_artransactions_import->init_vc_by;
-  $csv_artransactions_import->test_run(0);
-  $csv_artransactions_import->csv(SL::Helper::Csv->new(file                    => $csv_artransactions_import->file,
-                                                       profile                 => $csv_artransactions_import->profile,
-                                                       encoding                => 'utf-8',
-                                                       ignore_unknown_columns  => 1,
-                                                       strict_profile          => 1,
-                                                       case_insensitive_header => 1,
-                                                       sep_char                => ',',
-                                                       quote_char              => '"',
-                                                       ignore_unknown_columns  => 1,
-                                                     ));
-
-  $csv_artransactions_import->csv->parse;
-
-  $csv_artransactions_import->controller->errors([ $csv_artransactions_import->csv->errors ]) if $csv_artransactions_import->csv->errors;
-
-  return if ( !$csv_artransactions_import->csv->header || $csv_artransactions_import->csv->errors );
-
-  my $headers;
-  my $i = 0;
-  foreach my $header (@{ $csv_artransactions_import->csv->header }) {
-
-    my $profile   = $csv_artransactions_import->csv->profile->[$i]->{profile};
-    my $row_ident = $csv_artransactions_import->csv->profile->[$i]->{row_ident};
-
-    my $h = { headers => [ grep { $profile->{$_} } @{ $header } ] };
-    $h->{methods} = [ map { $profile->{$_} } @{ $h->{headers} } ];
-    $h->{used}    = { map { ($_ => 1) }      @{ $h->{headers} } };
-
-    $headers->{$row_ident} = $h;
-    $i++;
-  }
-
-  $csv_artransactions_import->controller->headers($headers);
-
-  my $raw_data_headers;
-  my $info_headers;
-  foreach my $p (@{ $csv_artransactions_import->csv->profile }) {
-    my $ident = $p->{row_ident};
-    $raw_data_headers->{$ident} = { used => { }, headers => [ ] };
-    $info_headers->{$ident}     = { used => { }, headers => [ ] };
-  }
-  $csv_artransactions_import->controller->raw_data_headers($raw_data_headers);
-  $csv_artransactions_import->controller->info_headers($info_headers);
-
-  my $objects  = $csv_artransactions_import->csv->get_objects;
-  my @raw_data = @{ $csv_artransactions_import->csv->get_data };
-
-  $csv_artransactions_import->controller->data([ pairwise { no warnings 'once'; { object => $a, raw_data => $b, errors => [], information => [], info_data => {} } } @$objects, @raw_data ]);
-  $csv_artransactions_import->check_objects;
+  $csv_artransactions_import->run(test => 0);
 
   # don't try and save objects that have errors
   $csv_artransactions_import->save_objects unless scalar @{$csv_artransactions_import->controller->data->[0]->{errors}};
index b7f1a9c..86a3c78 100644 (file)
@@ -96,8 +96,17 @@ reset_state();
 #####
 sub test_import {
   my ($file,$settings) = @_;
-  my @profiles;
-  my $controller = SL::Controller::CsvImport->new();
+
+  my $controller = SL::Controller::CsvImport->new(
+    type => 'parts'
+  );
+  $controller->load_default_profile;
+  $controller->profile->set(
+    charset      => 'utf-8',
+    sep_char     => ';',
+    quote_char   => '"',
+    numberformat => $::myconfig{numberformat},
+  );
 
   my $csv_part_import = SL::Controller::CsvImport::Part->new(
     settings   => $settings,
@@ -106,39 +115,7 @@ sub test_import {
   );
   #print "profile param type=".$csv_part_import->settings->{parts_type}."\n";
 
-  $csv_part_import->test_run(0);
-  $csv_part_import->csv(SL::Helper::Csv->new(file                    => $csv_part_import->file,
-                                             profile                 => [{ profile => $csv_part_import->profile,
-                                                                           class   => $csv_part_import->class,
-                                                                           mapping => $csv_part_import->controller->mappings_for_profile }],
-                                             encoding                => 'utf-8',
-                                             ignore_unknown_columns  => 1,
-                                             strict_profile          => 1,
-                                             case_insensitive_header => 1,
-                                             sep_char                => ';',
-                                             quote_char              => '"',
-                                             ignore_unknown_columns  => 1,
-                                            ));
-
-  $csv_part_import->csv->parse;
-
-  $csv_part_import->controller->errors([ $csv_part_import->csv->errors ]) if $csv_part_import->csv->errors;
-
-  return if ( !$csv_part_import->csv->header || $csv_part_import->csv->errors );
-
-  my $headers         = { headers => [ grep { $csv_part_import->csv->dispatcher->is_known($_, 0) } @{ $csv_part_import->csv->header } ] };
-  $headers->{methods} = [ map { $_->{path} } @{ $csv_part_import->csv->specs->[0] } ];
-  $headers->{used}    = { map { ($_ => 1) }  @{ $headers->{headers} } };
-  $csv_part_import->controller->headers($headers);
-  $csv_part_import->controller->raw_data_headers({ used => { }, headers => [ ] });
-  $csv_part_import->controller->info_headers({ used => { }, headers => [ ] });
-
-  my $objects  = $csv_part_import->csv->get_objects;
-  my @raw_data = @{ $csv_part_import->csv->get_data };
-
-  $csv_part_import->controller->data([ pairwise { no warnings 'once'; { object => $a, raw_data => $b, errors => [], information => [], info_data => {} } } @$objects, @raw_data ]);
-
-  $csv_part_import->check_objects;
+  $csv_part_import->run(test => 0);
 
   # don't try and save objects that have errors
   $csv_part_import->save_objects unless scalar @{$csv_part_import->controller->data->[0]->{errors}};
index 8541cb7..5414e90 100644 (file)
@@ -22,6 +22,7 @@ use utf8;
 
 use Carp;
 use Support::TestSetup;
+use SL::Dev::ALL qw(:ALL);
 
 use_ok 'SL::BackgroundJob::CreatePeriodicInvoices';
 use_ok 'SL::Controller::FinancialControllingReport';
@@ -35,63 +36,51 @@ use_ok 'SL::DB::TaxZone';
 
 Support::TestSetup::login();
 
-our ($ar_chart, $buchungsgruppe, $ctrl, $currency_id, $customer, $employee, $order, $part, $tax_zone, $unit, @invoices);
+our ($ar_chart, $ctrl, $customer, $order, $part, $unit, @invoices);
 
 sub cleanup {
   "SL::DB::Manager::${_}"->delete_all(all => 1) for qw(InvoiceItem Invoice OrderItem Order Customer Part);
 }
 
 sub init_common_state {
-  $ar_chart       = SL::DB::Manager::Chart->find_by(accno => '1400')                        || croak "No AR chart";
-  $buchungsgruppe = SL::DB::Manager::Buchungsgruppe->find_by(description => 'Standard 19%') || croak "No accounting group";
-  $currency_id    = SL::DB::Default->get->currency_id;
-  $employee       = SL::DB::Manager::Employee->current                                      || croak "No employee";
-  $tax_zone       = SL::DB::Manager::TaxZone->find_by( description => 'Inland')             || croak "No taxzone";
-  $unit           = SL::DB::Manager::Unit->find_by(name => 'psch')                          || croak "No unit";
+  $ar_chart       = SL::DB::Manager::Chart->find_by(accno => '1400') || croak "No AR chart";
+  $unit           = SL::DB::Manager::Unit->find_by(name => 'psch')   || croak "No unit";
 }
 
-sub create_sales_order {
+sub make_sales_order {
   my %params = @_;
 
   cleanup();
 
-  $params{$_} ||= {} for qw(customer part tax order orderitem);
+  $params{$_} ||= {} for qw(customer part order orderitem);
 
-  $customer     = SL::DB::Customer->new(
+  $customer     = new_customer(
     name        => 'Test Customer',
-    currency_id => $currency_id,
-    taxzone_id  => $tax_zone->id,
     %{ $params{customer} }
   )->save;
 
-  $part = SL::DB::Part->new(
+  $part = new_part(
     partnumber         => 'T4254',
     description        => 'Fourty-two fifty-four',
     lastcost           => 222.22,
     sellprice          => 333.33,
-    part_type          => 'part',
-    buchungsgruppen_id => $buchungsgruppe->id,
     unit               => $unit->name,
     %{ $params{part} }
   )->save;
   $part->load;
 
-  $order                     = SL::DB::Order->new(
-    customer_id              => $customer->id,
-    currency_id              => $currency_id,
-    taxzone_id               => $tax_zone->id,
+  $order                     = create_sales_order(
+    save                     => 1,
+    customer                 => $customer,
     transaction_description  => '<%period_start_date%>',
     transdate                => DateTime->from_kivitendo('01.03.2014'),
     orderitems               => [
-      { parts_id             => $part->id,
-        description          => $part->description,
-        lastcost             => $part->lastcost,
-        sellprice            => $part->sellprice,
-        qty                  => 1,
-        unit                 => $unit->name,
-        %{ $params{orderitem} },
-      },
-    ],
+                                  create_order_item(
+                                    part => $part,
+                                    qty  => 1,
+                                    %{ $params{orderitem} },
+                                  ),
+                                ],
     periodic_invoices_config => $params{periodic_invoices_config} ? {
       active                 => 1,
       ar_chart_id            => $ar_chart->id,
@@ -100,10 +89,6 @@ sub create_sales_order {
     %{ $params{order} },
   );
 
-  $order->calculate_prices_and_taxes;
-
-  ok($order->save(cascade => 1));
-
   $::form = Support::TestSetup->create_new_form;
   $ctrl   = SL::Controller::FinancialControllingReport->new;
 
@@ -118,7 +103,7 @@ my @columns = qw(net_amount         other_amount
 sub run_tests {
   my ($msg, $num_orders, $values, %order_params) = @_;
 
-  create_sales_order(%order_params);
+  make_sales_order(%order_params);
 
   is($num_orders, scalar @{ $ctrl->orders }, "${msg}, #orders");
   is_deeply([ map { ($ctrl->orders->[0]->{$_} // 0) * 1 } @columns ],
index abfcf7e..4c613ee 100644 (file)
@@ -14,7 +14,7 @@ sub today_local {
 
 package main;
 
-use Test::More tests => 49;
+use Test::More tests => 43;
 
 use lib 't';
 use strict;
@@ -22,6 +22,9 @@ use utf8;
 
 use Carp;
 use Support::TestSetup;
+use SL::Dev::Record qw(create_sales_order create_order_item);
+use SL::Dev::CustomerVendor qw(new_customer);
+use SL::Dev::Part qw(new_part);
 
 use_ok 'SL::BackgroundJob::CreatePeriodicInvoices';
 use_ok 'SL::Controller::FinancialOverview';
@@ -31,68 +34,48 @@ use_ok 'SL::DB::Default';
 use_ok 'SL::DB::Invoice';
 use_ok 'SL::DB::Order';
 use_ok 'SL::DB::Part';
-use_ok 'SL::DB::TaxZone';
 
 Support::TestSetup::login();
 
-our ($ar_chart, $buchungsgruppe, $ctrl, $currency_id, $customer, $employee, $order, $part, $tax_zone, $unit, @invoices);
+our ($ar_chart, $ctrl, $customer, $order, $part, $unit, @invoices);
 
 sub clear_up {
   "SL::DB::Manager::${_}"->delete_all(all => 1) for qw(InvoiceItem Invoice OrderItem Order Customer Part);
 };
 
 sub init_common_state {
-  $ar_chart       = SL::DB::Manager::Chart->find_by(accno => '1400')                        || croak "No AR chart";
-  $buchungsgruppe = SL::DB::Manager::Buchungsgruppe->find_by(description => 'Standard 19%') || croak "No accounting group";
-  $currency_id    = SL::DB::Default->get->currency_id;
-  $employee       = SL::DB::Manager::Employee->current                                      || croak "No employee";
-  $tax_zone       = SL::DB::Manager::TaxZone->find_by( description => 'Inland')             || croak "No taxzone";
-  $unit           = SL::DB::Manager::Unit->find_by(name => 'psch')                          || croak "No unit";
+  $ar_chart       = SL::DB::Manager::Chart->find_by(accno => '1400') || croak "No AR chart";
+  $unit           = SL::DB::Manager::Unit->find_by(name => 'psch')   || croak "No unit";
 }
 
-sub create_sales_order {
+sub make_sales_order {
   my %params = @_;
 
-  $params{$_} ||= {} for qw(customer part tax order orderitem);
+  $params{$_} ||= {} for qw(customer part order orderitem);
 
   # Clean up: remove invoices, orders, parts and customers
   clear_up();
 
-  $customer     = SL::DB::Customer->new(
+  $customer     = new_customer(
     name        => 'Test Customer',
-    currency_id => $currency_id,
-    taxzone_id  => $tax_zone->id,
     %{ $params{customer} }
   )->save;
 
-  $part = SL::DB::Part->new(
+  $part = new_part(
     partnumber         => 'T4254',
     description        => 'Fourty-two fifty-four',
     lastcost           => 222.22,
     sellprice          => 333.33,
-    part_type          => 'part',
-    buchungsgruppen_id => $buchungsgruppe->id,
-    unit               => $unit->name,
     %{ $params{part} }
   )->save;
   $part->load;
 
-  $order                     = SL::DB::Order->new(
-    customer_id              => $customer->id,
-    currency_id              => $currency_id,
-    taxzone_id               => $tax_zone->id,
+  $order                     = create_sales_order(
+    save                     => 1,
+    customer                 => $customer,
     transaction_description  => '<%period_start_date%>',
     transdate                => DateTime->from_kivitendo('01.03.2014'),
-    orderitems               => [
-      { parts_id             => $part->id,
-        description          => $part->description,
-        lastcost             => $part->lastcost,
-        sellprice            => $part->sellprice,
-        qty                  => 1,
-        unit                 => $unit->name,
-        %{ $params{orderitem} },
-      },
-    ],
+    orderitems => [ create_order_item(part => $part, qty =>  1, %{ $params{orderitem} }) ],
     periodic_invoices_config => $params{periodic_invoices_config} ? {
       active                 => 1,
       ar_chart_id            => $ar_chart->id,
@@ -101,10 +84,6 @@ sub create_sales_order {
     %{ $params{order} },
   );
 
-  $order->calculate_prices_and_taxes;
-
-  ok($order->save(cascade => 1));
-
   $::form         = Support::TestSetup->create_new_form;
   $::form->{year} = 2014;
   $ctrl           = SL::Controller::FinancialOverview->new;
@@ -118,7 +97,7 @@ init_common_state();
 
 # ----------------------------------------------------------------------
 # An order without periodic invoices:
-create_sales_order();
+make_sales_order();
 
 is_deeply($ctrl->data->{$_}, { months => [ (0) x 12 ], quarters => [ 0, 0, 0, 0 ], year => 0 }, "no periodic invoices, data for $_")
   for qw(purchase_invoices purchase_orders requests_for_quotation sales_invoices sales_quotations);
@@ -128,7 +107,7 @@ is_deeply($ctrl->data->{$_}, { months => [ 0, 0, 333.33, 0, 0, 0, 0, 0, 0, 0, 0,
 
 # ----------------------------------------------------------------------
 # order_value_periodicity=y, periodicity=q
-create_sales_order(
+make_sales_order(
   periodic_invoices_config  => {
     periodicity             => 'm',
     order_value_periodicity => 'y',
@@ -147,7 +126,7 @@ is_deeply($ctrl->data->{sales_orders_per_inv},
 
 # ----------------------------------------------------------------------
 # order_value_periodicity=y, periodicity=q, starting in previous year
-create_sales_order(
+make_sales_order(
   order                     => {
     transdate               => DateTime->from_kivitendo('01.03.2013'),
   },
@@ -169,7 +148,7 @@ is_deeply($ctrl->data->{sales_orders_per_inv},
 
 # ----------------------------------------------------------------------
 # order_value_periodicity=y, periodicity=q, starting in previous year, ending middle of year
-create_sales_order(
+make_sales_order(
   order                     => {
     transdate               => DateTime->from_kivitendo('01.03.2013'),
   },
@@ -193,7 +172,7 @@ is_deeply($ctrl->data->{sales_orders_per_inv},
 
 # ----------------------------------------------------------------------
 # order_value_periodicity=y, periodicity=q, starting and ending before current
-create_sales_order(
+make_sales_order(
   order                     => {
     transdate               => DateTime->from_kivitendo('01.03.2012'),
   },
index 7f7a081..5ca1224 100644 (file)
@@ -5,7 +5,7 @@ use lib 't';
 use Support::TestSetup;
 use Carp;
 use Test::Exception;
-use SL::Dev::ALL;
+use SL::Dev::ALL qw(:ALL);
 use SL::DB::Part;
 use SL::DB::Order;
 use SL::DB::Customer;
@@ -20,71 +20,71 @@ Support::TestSetup::login();
 
 clear_up();
 
-my $vendor   = SL::Dev::CustomerVendor::create_vendor->save;
-my $customer = SL::Dev::CustomerVendor::create_customer->save;
-my $project  = SL::Dev::Record::create_project(projectnumber => 'p1', description => 'Project 1')->save;
+my $vendor   = new_vendor()->save;
+my $customer = new_customer()->save;
+my $project  = create_project(projectnumber => 'p1', description => 'Project 1');
 
-my $part1 = SL::Dev::Part::create_part(   partnumber => 'T4254')->save;
-my $part2 = SL::Dev::Part::create_service(partnumber => 'Serv1')->save;
+my $part1 = new_part(   partnumber => 'T4254')->save;
+my $part2 = new_service(partnumber => 'Serv1')->save;
 
 # sales order with globalproject_id and item project_ids
-my $sales_order = SL::Dev::Record::create_sales_order(
+my $sales_order = create_sales_order(
   save             => 1,
   customer         => $customer,
   globalproject_id => $project->id,
   taxincluded      => 0,
-  orderitems       => [ SL::Dev::Record::create_order_item(part => $part1, qty =>  3, sellprice => 70, project_id => $project->id),
-                        SL::Dev::Record::create_order_item(part => $part2, qty => 10, sellprice => 50, project_id => $project->id),
+  orderitems       => [ create_order_item(part => $part1, qty =>  3, sellprice => 70, project_id => $project->id),
+                        create_order_item(part => $part2, qty => 10, sellprice => 50, project_id => $project->id),
                       ]
 );
 
 # sales order with no globalproject_id but item project_ids
-my $sales_order2 = SL::Dev::Record::create_sales_order(
+my $sales_order2 = create_sales_order(
   save             => 1,
   customer         => $customer,
   taxincluded      => 0,
-  orderitems       => [ SL::Dev::Record::create_order_item(part => $part1, qty =>  3, sellprice => 70, project_id => $project->id),
-                        SL::Dev::Record::create_order_item(part => $part2, qty => 10, sellprice => 50),
+  orderitems       => [ create_order_item(part => $part1, qty =>  3, sellprice => 70, project_id => $project->id),
+                        create_order_item(part => $part2, qty => 10, sellprice => 50),
                       ]
 );
 
 # purchase order with globalproject_id and item project_ids
-my $purchase_order = SL::Dev::Record::create_purchase_order(
+my $purchase_order = create_purchase_order(
   save             => 1,
   vendor           => $vendor,
   globalproject_id => $project->id,
   taxincluded      => 0,
-  orderitems       => [ SL::Dev::Record::create_order_item(part => $part1, qty =>  3, sellprice => 70, project_id => $project->id),
-                        SL::Dev::Record::create_order_item(part => $part2, qty => 10, sellprice => 50, project_id => $project->id),
+  orderitems       => [ create_order_item(part => $part1, qty =>  3, sellprice => 70, project_id => $project->id),
+                        create_order_item(part => $part2, qty => 10, sellprice => 50, project_id => $project->id),
                       ]
 );
 
 # sales_invoice with globalproject_id, and all items with project_id
-my $sales_invoice = SL::Dev::Record::create_sales_invoice(
+my $sales_invoice = create_sales_invoice(
   customer         => $customer,
   globalproject_id => $project->id,
   taxincluded      => 0,
-  invoiceitems     => [ SL::Dev::Record::create_invoice_item(part => $part1, qty =>  3, sellprice => 70, project_id => $project->id),
-                        SL::Dev::Record::create_invoice_item(part => $part2, qty => 10, sellprice => 50, project_id => $project->id),
+  invoiceitems     => [ create_invoice_item(part => $part1, qty =>  3, sellprice => 70, project_id => $project->id),
+                        create_invoice_item(part => $part2, qty => 10, sellprice => 50, project_id => $project->id),
                       ]
 );
 
 # sales_invoice with globalproject_id, but none of the items has a project_id
-my $sales_invoice2 = SL::Dev::Record::create_sales_invoice(
+my $sales_invoice2 = create_sales_invoice(
   customer         => $customer,
   globalproject_id => $project->id,
   taxincluded      => 0,
-  invoiceitems     => [ SL::Dev::Record::create_invoice_item(part => $part1, qty =>  3, sellprice => 70),
-                        SL::Dev::Record::create_invoice_item(part => $part2, qty => 10, sellprice => 50),
+  invoiceitems     => [ create_invoice_item(part => $part1, qty =>  3, sellprice => 70),
+                        create_invoice_item(part => $part2, qty => 10, sellprice => 50),
                       ]
 );
 
 # one of the invoice items has the project id, but there is no globalproject_id
-my $sales_invoice4 = SL::Dev::Record::create_sales_invoice(
+my $sales_invoice4 = create_sales_invoice(
   customer         => $customer,
   taxincluded      => 0,
-  invoiceitems     => [ SL::Dev::Record::create_invoice_item(part => $part1, qty =>  3, sellprice => 70),
-                        SL::Dev::Record::create_invoice_item(part => $part2, qty => 10, sellprice => 50, project_id => $project->id),
+  invoiceitems     => [ create_invoice_item(part => $part1, qty =>  3, sellprice => 70),
+                        create_invoice_item(part => $part2, qty => 10, sellprice => 50, project_id => $project->id),
                       ]
 );
 
index 5a33d5f..05e9aa4 100644 (file)
@@ -6,7 +6,7 @@ use lib 't';
 
 use_ok 'Support::TestSetup';
 use SL::DATEV qw(:CONSTANTS);
-use SL::Dev::ALL;
+use SL::Dev::ALL qw(:ALL);
 use List::Util qw(sum);
 use SL::DB::Buchungsgruppe;
 use SL::DB::Chart;
@@ -16,24 +16,30 @@ Support::TestSetup::login();
 
 clear_up();
 
+my $dbh = SL::DB->client->dbh;
+
 my $buchungsgruppe7 = SL::DB::Manager::Buchungsgruppe->find_by(description => 'Standard 7%') || die "No accounting group for 7\%";
 my $bank            = SL::DB::Manager::Chart->find_by(description => 'Bank')                 || die 'Can\'t find chart "Bank"';
 my $date            = DateTime->new(year => 2017, month =>  1, day => 1);
 my $payment_date    = DateTime->new(year => 2017, month =>  1, day => 5);
+my $gldate          = DateTime->new(year => 2017, month =>  2, day => 9); # simulate bookings for Jan being made in Feb
 
-my $part1 = SL::Dev::Part::create_part(partnumber => '19', description => 'Part 19%')->save;
-my $part2 = SL::Dev::Part::create_part(
+my $part1 = new_part(partnumber => '19', description => 'Part 19%')->save;
+my $part2 = new_part(
   partnumber         => '7',
   description        => 'Part 7%',
   buchungsgruppen_id => $buchungsgruppe7->id,
 )->save;
 
-my $invoice = SL::Dev::Record::create_sales_invoice(
+my $invoice = create_sales_invoice(
   invnumber    => "1 sales invoice",
+  itime        => $gldate,
+  gldate       => $gldate,
+  intnotes     => 'booked in February',
   taxincluded  => 0,
   transdate    => $date,
-  invoiceitems => [ SL::Dev::Record::create_invoice_item(part => $part1, qty =>  3, sellprice => 70),
-                    SL::Dev::Record::create_invoice_item(part => $part2, qty => 10, sellprice => 50),
+  invoiceitems => [ create_invoice_item(part => $part1, qty =>  3, sellprice => 70),
+                    create_invoice_item(part => $part2, qty => 10, sellprice => 50),
                   ]
 );
 $invoice->pay_invoice(chart_id      => $bank->id,
@@ -78,36 +84,62 @@ cmp_bag $datev1->generate_datev_lines, [
                                          },
                                        ], "trans_id datev check ok";
 
-my $invoice2 = SL::Dev::Record::create_sales_invoice(
+my $march_9 = DateTime->new(year => 2017, month =>  3, day => 9);
+my $invoice2 = create_sales_invoice(
   invnumber    => "2 sales invoice",
+  itime        => $march_9,
+  gldate       => $march_9,
+  intnotes     => 'booked in March',
   taxincluded  => 0,
   transdate    => $date,
-  invoiceitems => [ SL::Dev::Record::create_invoice_item(part => $part1, qty =>  6, sellprice => 70),
-                    SL::Dev::Record::create_invoice_item(part => $part2, qty => 20, sellprice => 50),
+  invoiceitems => [ create_invoice_item(part => $part1, qty =>  6, sellprice => 70),
+                    create_invoice_item(part => $part2, qty => 20, sellprice => 50),
                   ]
 );
 
-my $credit_note = SL::Dev::Record::create_credit_note(
+my $credit_note = create_credit_note(
   invnumber    => 'Gutschrift 34',
+  itime        => $gldate,
+  gldate       => $gldate,
+  intnotes     => 'booked in February',
   taxincluded  => 0,
   transdate    => $date,
-  invoiceitems => [ SL::Dev::Record::create_invoice_item(part => $part1, qty =>  3, sellprice => 70),
-                    SL::Dev::Record::create_invoice_item(part => $part2, qty => 10, sellprice => 50),
+  invoiceitems => [ create_invoice_item(part => $part1, qty =>  3, sellprice => 70),
+                    create_invoice_item(part => $part2, qty => 10, sellprice => 50),
                   ]
 );
 
-my $startdate = DateTime->new(year => 2017, month =>  1, day => 1);
+my $startdate = DateTime->new(year => 2017, month =>  1, day =>  1);
 my $enddate   = DateTime->new(year => 2017, month => 12, day => 31);
 
 my $datev = SL::DATEV->new(
-  dbh        => $credit_note->db->dbh,
+  dbh        => $dbh,
   from       => $startdate,
-  to         => $enddate
+  to         => $enddate,
 );
 $datev->generate_datev_data(from_to => $datev->fromto);
 my $datev_lines = $datev->generate_datev_lines;
 my $umsatzsumme = sum map { $_->{umsatz} } @{ $datev_lines };
-is($umsatzsumme, 3924.50, "umsatzsumme ok");
+cmp_ok($::form->round_amount($umsatzsumme,2), '==', 3924.5, "Sum of all bookings ok");
+
+note('testing gldatefrom');
+$datev = SL::DATEV->new(
+  dbh        => $dbh,
+  from       => $startdate,
+  to         => DateTime->new(year => 2017, month => 01, day => 31),
+);
+
+$::form               = Support::TestSetup->create_new_form;
+$::form->{gldatefrom} = DateTime->new(year => 2017, month => 3, day => 1)->to_kivitendo;
+
+$datev->generate_datev_data(from_to => $datev->fromto);
+$datev_lines = $datev->generate_datev_lines;
+$umsatzsumme = sum map { $_->{umsatz} } @{ $datev_lines };
+cmp_ok($umsatzsumme, '==', 1569.8, "Sum of bookings made after March 1st (only invoice2) ok");
+
+$::form->{gldatefrom} = DateTime->new(year => 2017, month => 5, day => 1)->to_kivitendo;
+$datev->generate_datev_data(from_to => $datev->fromto);
+cmp_bag $datev->generate_datev_lines, [], "no bookings for January made after May 1st: ok";
 
 done_testing();
 clear_up();
@@ -120,6 +152,4 @@ sub clear_up {
   SL::DB::Manager::Part->delete_all(          all => 1);
 };
 
-
 1;
-
index ead4024..b1ac4c2 100644 (file)
@@ -1,4 +1,4 @@
-use Test::More tests => 42;
+use Test::More tests => 41;
 
 use strict;
 
@@ -11,7 +11,6 @@ use Carp;
 use Data::Dumper;
 use Support::TestSetup;
 use Test::Exception;
-use List::Util qw(max);
 
 use SL::DB::Buchungsgruppe;
 use SL::DB::Currency;
@@ -22,18 +21,18 @@ use SL::DB::Order;
 use SL::DB::DeliveryOrder;
 use SL::DB::Part;
 use SL::DB::Unit;
-use SL::DB::TaxZone;
 
-my ($customer, $currency_id, $buchungsgruppe, $employee, $vendor, $taxzone, $buchungsgruppe7, $tax, $tax7,
-    $unit, @parts);
+use SL::Dev::ALL qw(:ALL);
+
+my ($customer, $employee, $payment_do, $unit, @parts, $department);
 
 my $VISUAL_TEST = 0;  # just a sleep to click around
 
 sub clear_up {
-  foreach (qw(DeliveryOrderItem DeliveryOrder InvoiceItem PurchaseInvoice Invoice Part Customer Vendor Department PaymentTerm)) {
+  foreach (qw(DeliveryOrderItem DeliveryOrder InvoiceItem Invoice Part Customer Department PaymentTerm)) {
     "SL::DB::Manager::${_}"->delete_all(all => 1);
   }
-  SL::DB::Manager::Employee->delete_all(where => [ id => 31915 ]);
+  SL::DB::Manager::Employee->delete_all(where => [ login => 'testuser' ]);
 };
 
 sub reset_state {
@@ -41,99 +40,48 @@ sub reset_state {
 
   clear_up();
 
-  $buchungsgruppe   = SL::DB::Manager::Buchungsgruppe->find_by(description => 'Standard 19%', %{ $params{buchungsgruppe} }) || croak "No accounting group 19\%";
-  $buchungsgruppe7  = SL::DB::Manager::Buchungsgruppe->find_by(description => 'Standard 7%', %{ $params{buchungsgruppe} })  || croak "No accounting group 7\%";
-  $taxzone          = SL::DB::Manager::TaxZone->find_by( description => 'Inland')                                           || croak "No taxzone";
-  $tax              = SL::DB::Manager::Tax->find_by(taxkey => 3, rate => 0.19, %{ $params{tax} })                           || croak "No tax for 19\%";
-  $tax7             = SL::DB::Manager::Tax->find_by(taxkey => 2, rate => 0.07)                                              || croak "No tax for 7\%";
-  $unit             = SL::DB::Manager::Unit->find_by(name => 'kg', %{ $params{unit} })                                      || croak "No unit";
-  $currency_id     = $::instance_conf->get_currency_id;
-
-  $customer     = SL::DB::Customer->new(
-    name        => '520484567dfaedc9e60fc',
-    currency_id => $currency_id,
-    taxzone_id  => $taxzone->id,
-    %{ $params{customer} }
-  )->save;
+  $unit     = SL::DB::Manager::Unit->find_by(name => 'kg') || die "Can't find unit 'kg'";
+  $customer = new_customer()->save;
 
-  # some od.rnr real anonym data
-  my $employee_bk = SL::DB::Employee->new(
-                'id' => 31915,
-                'login' => 'barbuschka.kappes',
-                'name' => 'Barbuschka Kappes',
+  $employee = SL::DB::Employee->new(
+    'login' => 'testuser',
+    'name'  => 'Test User',
   )->save;
 
-  my $department_do = SL::DB::Department->new(
-                 'description' => 'Maisenhaus-Versand',
-                 'id' => 32149,
-                 'itime' => undef,
-                 'mtime' => undef
+  $department = SL::DB::Department->new(
+    'description' => 'Test Department',
   )->save;
 
-  my $payment_do = SL::DB::PaymentTerm->new(
-                 'description' => '14Tage 2%Skonto, 30Tage netto',
-                 'description_long' => "Innerhalb von 14 Tagen abzüglich 2 % Skonto, innerhalb von 30 Tagen rein netto.|Bei einer Zahlung bis zum <%skonto_date%> gewähren wir 2 % Skonto (EUR <%skonto_amount%>) entspricht EUR <%total_wo_skonto%>.Bei einer Zahlung bis zum <%netto_date%> ist der fällige Betrag in Höhe von <%total%> <%currency%> zu überweisen.",
-                 'id' => 11276,
-                 'itime' => undef,
-                 'mtime' => undef,
-                 'percent_skonto' => '0.02',
-                 'ranking' => undef,
-                 'sortkey' => 4,
-                 'terms_netto' => 30,
-                 'auto_calculation' => undef,
-                 'terms_skonto' => 14
-  )->save;
+  $payment_do = create_payment_terms(
+     'description'      => '14Tage 2%Skonto, 30Tage netto',
+     'description_long' => "Innerhalb von 14 Tagen abzüglich 2 % Skonto, innerhalb von 30 Tagen rein netto.|Bei einer Zahlung bis zum <%skonto_date%> gewähren wir 2 % Skonto (EUR <%skonto_amount%>) entspricht EUR <%total_wo_skonto%>.Bei einer Zahlung bis zum <%netto_date%> ist der fällige Betrag in Höhe von <%total%> <%currency%> zu überweisen.",
+     'percent_skonto'   => '0.02',
+     'terms_netto'      => 30,
+     'terms_skonto'     => 14
+  );
 
   # two real parts
   @parts = ();
-  push @parts, SL::DB::Part->new(
-                 'id' => 26321,
-                 'image' => '',
-                 'lastcost' => '49.95000',
-                 'listprice' => '0.00000',
-                 'onhand' => '5.00000',
-                 'partnumber' => 'v-519160549',
-                 part_type    => 'part',
-                 #'partsgroup_id' => 111645,
-                 'rop' => '0',
-                 'sellprice' => '242.20000',
-                 #'warehouse_id' => 64702,
-                 'weight' => '0.79',
-                 description        => "Pflaumenbaum, Gr.5, Unterfilz weinrot, genietet[[Aufschnittbreite: 11,0, Kernform: US]]\"" ,
-                 buchungsgruppen_id => $buchungsgruppe->id,
-                 unit               => $unit->name,
-                 id                 => 26321,
+  push @parts, new_part(
+    description => "description 1",
+    lastcost    => '49.95000',
+    listprice   => '0.00000',
+    partnumber  => 'v-519160549',
+    sellprice   => '242.20000',
+    unit        => $unit->name,
+    weight      => '0.79',
   )->save;
 
-  push @parts, SL::DB::Part->new(
-                 'description' => "[[0640]]Flügel Hammerstiele bestehend aus:
-70 Stielen Standard in Weißbuche und
-20 Stielen Diskant abgekehlt in Weißbuche
-mit Röllchen aus Synthetikleder,
-Kapseln mit Yamaha Profil, Kerbenabstand 3,6 mm mit eingedrehten Abnickschrauben",
-                 'id' => 25505,
-                 'lastcost' => '153.00000',
-                 'listprice' => '0.00000',
-                 'part_type' => 'part',
-                 'onhand' => '9.00000',
-                 'partnumber' => 'v-120160086',
-                 # 'partsgroup_id' => 111639,
-                 'rop' => '0',
-                 'sellprice' => '344.30000',
-                 'weight' => '0.9',
-                  buchungsgruppen_id => $buchungsgruppe->id,
-                  unit               => $unit->name,
+  push @parts, new_part(
+    description => "description 2",
+    lastcost    => '153.00000',
+    listprice   => '0.00000',
+    partnumber  => 'v-120160086',
+    sellprice   => '344.30000',
+    unit        => $unit->name,
+    weight      => '0.9',
   )->save;
-}
-
-sub new_delivery_order {
-  my %params  = @_;
 
-  return SL::DB::DeliveryOrder->new(
-   currency_id => $currency_id,
-   taxzone_id  => $taxzone->id,
-    %params,
-  )->save;
 }
 
 Support::TestSetup::login();
@@ -141,87 +89,58 @@ Support::TestSetup::login();
 reset_state();
 
 # we create L20199 with two items
-my $do1 = new_delivery_order('department_id'    => 32149,
-                             'donumber'         => 'L20199',
-                             'employee_id'      => 31915,
-                             'intnotes'         => 'Achtung: Neue Lieferadresse ab 16.02.2015 in der Otto-Merck-Str. 7a!   13.02.2015/MH
-
-                                            Yamaha-Produkte (201...) immer plus 25% dazu rechnen / BK 13.02.2014',
-                              'ordnumber'       => 'A16399',
-                              'payment_id'      => 11276,
-                              'salesman_id'     => 31915,
-                              'shippingpoint'   => 'Maisenhaus',
-                              # 'shipto_id'     => 451463,
-                              'is_sales'        => 'true',
-                              'shipvia'         => 'DHL, Versand am 06.03.2015, 1 Paket  17,00 kg',
-                              'taxzone_id'      => 4,
-                              'closed'          => undef,
-                              # 'currency_id'   => 1,
-                              'cusordnumber'    => 'b84da',
-                              'customer_id'     => $customer->id,
-                              'id'              => 464003,
-                              'notes'           => '<ul><li><strong>fett</strong></li><li><strong>und</strong></li><li><strong>mit</strong></li><li><strong>bullets</strong></li><li>&nbsp;</li></ul>',
+my $do1 = create_sales_delivery_order(
+  'department_id' => $department->id,
+  'donumber'      => 'L20199',
+  'employee_id'   => $employee->id,
+  'intnotes'      => 'some intnotes',
+  'ordnumber'     => 'A16399',
+  'payment_id'    => $payment_do->id,
+  'salesman_id'   => $employee->id,
+  'shippingpoint' => 'sendtome',
+  'shipvia'       => 'DHL, Versand am 06.03.2015, 1 Paket  17,00 kg',
+  'cusordnumber'  => 'b84da',
+  'customer_id'   => $customer->id,
+  'notes'         => '<ul><li><strong>fett</strong></li><li><strong>und</strong></li><li><strong>mit</strong></li><li><strong>bullets</strong></li><li>&nbsp;</li></ul>',
+  orderitems => [
+                  create_delivery_order_item(
+                    part               => $parts[0],
+                    discount           => '0.25',
+                    lastcost           => '49.95000',
+                    longdescription    => "<ol><li>27</li><li>28</li><li>29</li><li><sub>asdf</sub></li><li><sub>asdf</sub></li><li><sup>oben</sup></li></ol><p><s>kommt nicht mehr vor</s></p>",
+                    marge_price_factor => 1,
+                    qty                => '2.00000',
+                    sellprice          => '242.20000',
+                    unit               => $unit->name,
+                  ),
+                  create_delivery_order_item(
+                    part            => $parts[1],
+                    discount        => '0.25',
+                    lastcost        => '153.00000',
+                    qty             => '3.00000',
+                    sellprice       => '344.30000',
+                    transdate       => '06.03.2015',
+                    unit            => $unit->name,
+                  )
+                ]
 );
 
-my $do1_item1 = SL::DB::DeliveryOrderItem->new('delivery_order_id' => 464003,
-                                               'description' => "Flügel Hammerkopf bestehend aus:
-                                                                 Bass/Diskant 26/65 Stück, Gesamtlänge 80/72, Bohrlänge 56/48
-                                                                 Pflaumenbaum, Gr.5, Unterfilz weinrot, genietet[[Aufschnittbreite: 11,0, Kernform: US]]",
-                                               'discount' => '0.25',
-                                               'id' => 144736,
-                                               'lastcost' => '49.95000',
-                                               'longdescription'    => "<ol><li>27</li><li>28</li><li>29</li><li><sub>asdf</sub></li><li><sub>asdf</sub></li><li><sup>oben</sup></li></ol><p><s>kommt nicht mehr vor</s></p>",
-                                               'marge_price_factor' => 1,
-                                               'mtime' => undef,
-                                               'ordnumber' => 'A16399',
-                                               'parts_id' => 26321,
-                                               'position' => 1,
-                                               'price_factor' => 1,
-                                               'qty' => '2.00000',
-                                               'sellprice' => '242.20000',
-                                               'transdate' => '06.03.2015',
-                                               'unit' => 'kg')->save;
-
-my $do1_item2 = SL::DB::DeliveryOrderItem->new('delivery_order_id' => 464003,
-                 'description' => "[[0640]]Flügel Hammerstiele bestehend aus:
-70 Stielen Standard in Weißbuche und
-20 Stielen Diskant abgekehlt in Weißbuche
-mit Röllchen aus Synthetikleder,
-Kapseln mit Yamaha Profil, Kerbenabstand 3,6 mm mit eingedrehten Abnickschrauben",
-                 'discount' => '0.25',
-                 'id' => 144737,
-                 'itime' => undef,
-                 'lastcost' => '153.00000',
-                 'longdescription' => '',
-                 'marge_price_factor' => 1,
-                 'mtime' => undef,
-                 'ordnumber' => 'A16399',
-                 'parts_id' => 25505,
-                 'position' => 2,
-                 'price_factor' => 1,
-                 'price_factor_id' => undef,
-                 'pricegroup_id' => undef,
-                 'project_id' => undef,
-                 'qty' => '3.00000',
-                 'reqdate' => undef,
-                 'sellprice' => '344.30000',
-                 'serialnumber' => '',
-                 'transdate' => '06.03.2015',
-                 'unit' => 'kg')->save;
 
 # TESTS
 
+my $do1_item1 = $do1->orderitems->[0];
+my $do1_item2 = $do1->orderitems->[1];
 
 # test delivery order before any conversion
 ok($do1->donumber eq "L20199", 'Delivery Order Number created');
 ok($do1->notes eq '<ul><li><strong>fett</strong></li><li><strong>und</strong></li><li><strong>mit</strong></li><li><strong>bullets</strong></li><li>&nbsp;</li></ul>', "do RichText notes saved");
 ok((not $do1->closed) , 'Delivery Order is not closed');
-ok($do1_item1->parts_id eq '26321', 'doi linked with part');
+is($do1_item1->parts_id, $parts[0]->id, 'doi linked with part');
 ok($do1_item1->qty == 2, 'qty check doi');
 ok($do1_item1->longdescription eq  "<ol><li>27</li><li>28</li><li>29</li><li><sub>asdf</sub></li><li><sub>asdf</sub></li><li><sup>oben</sup></li></ol><p><s>kommt nicht mehr vor</s></p>",
      "do item1 rich text longdescripition");
 ok ($do1_item2->position == 2, 'doi2 position check');
-ok (2 ==  scalar@{ SL::DB::Manager::DeliveryOrderItem->get_all(where => [ delivery_order_id => $do1->id ]) }, 'two doi linked');
+is (SL::DB::Manager::DeliveryOrderItem->get_all_count(where => [ delivery_order_id => $do1->id ]), 2 , 'two doi linked');
 
 
 # convert this do to invoice
@@ -231,12 +150,12 @@ sleep (300) if $VISUAL_TEST; # we can do a real visual test via gui login
 # test invoice afterwards
 
 ok ($invoice->shipvia eq "DHL, Versand am 06.03.2015, 1 Paket  17,00 kg", "ship via check");
-ok ($invoice->shippingpoint eq "Maisenhaus", "shipping point check");
+ok ($invoice->shippingpoint eq "sendtome", "shipping point check");
 ok ($invoice->ordnumber eq "A16399", "ordnumber check");
 ok ($invoice->donumber eq "L20199", "donumber check");
 ok ($invoice->notes eq '<ul><li><strong>fett</strong></li><li><strong>und</strong></li><li><strong>mit</strong></li><li><strong>bullets</strong></li><li>&nbsp;</li></ul>', "do RichText notes saved");
 ok(($do1->closed) , 'Delivery Order is closed after conversion');
-ok (SL::DB::PaymentTerm->new(id => $invoice->{payment_id})->load->description eq "14Tage 2%Skonto, 30Tage netto", 'payment term description check');
+is($invoice->payment_terms->description, "14Tage 2%Skonto, 30Tage netto", 'payment term description check');
 
 # some test data from original client invoice console (!)
 # my $invoice3 = SL::DB::Manager::Invoice->find_by( ordnumber => 'A16399' );
@@ -274,28 +193,26 @@ ok (SL::DB::PaymentTerm->new(id => $invoice->{payment_id})->load->description eq
 
 $invoice->load;
 
-ok($invoice->currency_id eq '1', 'currency_id');
-ok($invoice->cusordnumber eq 'b84da', 'cusordnumber check');
-ok(SL::DB::Department->new(id => $invoice->{department_id})->load->description eq "Maisenhaus-Versand", 'department description');
-is($invoice->amount, '1354.17000', 'amount check');
-is($invoice->marge_percent, '50.88580', 'marge percent check');
-is($invoice->marge_total, '579.06000', 'marge total check');
-is($invoice->netamount, '1137.96000', 'netamount check');
+is($invoice->cusordnumber            , 'b84da'           , 'cusordnumber check');
+is($invoice->department->description , "Test Department" , 'department description ok');
+is($invoice->amount                  , '1354.17000'      , 'amount check');
+is($invoice->marge_percent           , '50.88580'        , 'marge percent check');
+is($invoice->marge_total             , '579.06000'       , 'marge total check');
+is($invoice->netamount               , '1137.96000'      , 'netamount check');
 
 # some item checks
-ok(@ {$invoice->items_sorted}[0]->parts_id eq '26321', 'invoiceitem 1 linked with part');
-ok(2 ==  scalar@{ $invoice->invoiceitems }, 'two invoice items linked with invoice');
-is(@ {$invoice->items_sorted}[0]->position, 1, "position 1 order correct");
-is(@ {$invoice->items_sorted}[1]->position, 2, "position 2 order correct");
-is(@ {$invoice->items_sorted}[0]->longdescription, "<ol><li>27</li><li>28</li><li>29</li><li><sub>asdf</sub></li><li><sub>asdf</sub></li><li><sup>oben</sup></li></ol><p><s>kommt nicht mehr vor</s></p>",
+is($invoice->items_sorted->[0]->parts_id         , $parts[0]->id , 'invoiceitem 1 linked with part');
+is(scalar @{ $invoice->invoiceitems }            , 2             , 'two invoice items linked with invoice');
+is($invoice->items_sorted->[0]->position         , 1             , "position 1 order correct");
+is($invoice->items_sorted->[1]->position         , 2             , "position 2 order correct");
+is($invoice->items_sorted->[0]->part->partnumber , 'v-519160549' , "partnumber 1 correct");
+is($invoice->items_sorted->[1]->part->partnumber , 'v-120160086' , "partnumber 2 correct");
+is($invoice->items_sorted->[0]->qty              , '2.00000'     , "pos 1 qty");
+is($invoice->items_sorted->[1]->qty              , '3.00000'     , "pos 2 qty");
+is($invoice->items_sorted->[0]->discount         , 0.25          , "pos 1 discount");
+is($invoice->items_sorted->[1]->discount         , 0.25          , "pos 2 discount");
+is($invoice->items_sorted->[0]->longdescription  , "<ol><li>27</li><li>28</li><li>29</li><li><sub>asdf</sub></li><li><sub>asdf</sub></li><li><sup>oben</sup></li></ol><p><s>kommt nicht mehr vor</s></p>",
      "invoice item1 rich text longdescripition");
-is(@ {$invoice->items_sorted}[0]->part->partnumber, 'v-519160549', "partnumber 1 correct");
-is(@ {$invoice->items_sorted}[1]->part->partnumber, 'v-120160086', "partnumber 2 correct");
-is(@ {$invoice->items_sorted}[0]->qty, '2.00000', "pos 1 qty");
-is(@ {$invoice->items_sorted}[1]->qty, '3.00000', "pos 2 qty");
-is(@ {$invoice->items_sorted}[0]->discount, 0.25, "pos 1 discount");
-is(@ {$invoice->items_sorted}[1]->discount, 0.25, "pos 2 discount");
-
 # more ideas: check onhand, lastcost (parsed lastcost)
 
 
@@ -327,23 +244,25 @@ is(@ {$invoice->items_sorted}[1]->discount, 0.25, "pos 2 discount");
 #         };
 
 
-
 my @links_record    = RecordLinks->get_links('from_table' => 'delivery_orders',
                                              'to_table'   => 'ar',
-                                             'from_id'      => 464003);
-is($links_record[0]->{from_id}, '464003', "record from id check");
-is($links_record[0]->{from_table}, 'delivery_orders', "record from table check");
-is($links_record[0]->{to_table}, 'ar', "record to table check");
+                                             'from_id'    => $do1->id,
+                                            );
+
+is($links_record[0]->{from_id}    , $do1->id          , "record from id check");
+is($links_record[0]->{from_table} , 'delivery_orders' , "record from table check");
+is($links_record[0]->{to_table}   , 'ar'              , "record to table check");
 
-foreach (qw(144736 144737)) {
+foreach ( $do1_item1->id, $do1_item2->id ) {
   my @links_record_item1 = RecordLinks->get_links('from_table' => 'delivery_order_items',
-                                                 'to_table'   => 'invoice',
-                                                 'from_id'      => $_);
-  is($links_record_item1[0]->{from_id}, $_, "record from id check $_");
-  is($links_record_item1[0]->{from_table}, 'delivery_order_items', "record from table check $_");
-  is($links_record_item1[0]->{to_table}, 'invoice', "record to table check $_");
-}
+                                                  'to_table'   => 'invoice',
+                                                  'from_id'    => $_,
+                                                 );
 
+  is($links_record_item1[0]->{from_id}    , $_                     , "record from id check $_");
+  is($links_record_item1[0]->{from_table} , 'delivery_order_items' , "record from table check $_");
+  is($links_record_item1[0]->{to_table}   , 'invoice'              , "record to table check $_");
+}
 
 clear_up();
 
index ac2e449..b40cf74 100644 (file)
@@ -10,9 +10,9 @@ use Support::TestSetup;
 use Test::Exception;
 use List::Util qw(sum);
 
-use SL::Dev::Record;
-use SL::Dev::CustomerVendor;
-use SL::Dev::Part;
+use SL::Dev::Record qw(create_invoice_item create_sales_invoice create_credit_note);
+use SL::Dev::CustomerVendor qw(new_customer new_vendor);
+use SL::Dev::Part qw(new_part);
 use SL::DB::Buchungsgruppe;
 use SL::DB::Currency;
 use SL::DB::Exchangerate;
@@ -109,7 +109,7 @@ sub reset_state {
                                              currency_id => $currency->id,
                                             )->save;
 
-  $customer     = SL::Dev::CustomerVendor::create_customer(
+  $customer     = new_customer(
     name        => 'Test Customer',
     currency_id => $currency_id,
     taxzone_id  => $taxzone->id,
@@ -134,7 +134,7 @@ sub reset_state {
     auto_calculation => 1,
   )->save;
 
-  $vendor       = SL::Dev::CustomerVendor::create_vendor(
+  $vendor       = new_vendor(
     name        => 'Test Vendor',
     currency_id => $currency_id,
     taxzone_id  => $taxzone->id,
@@ -143,7 +143,7 @@ sub reset_state {
 
 
   @parts = ();
-  push @parts, SL::Dev::Part::create_part(
+  push @parts, new_part(
     partnumber         => 'T4254',
     description        => 'Fourty-two fifty-four',
     lastcost           => 1.93,
@@ -153,7 +153,7 @@ sub reset_state {
     %{ $params{part1} }
   )->save;
 
-  push @parts, SL::Dev::Part::create_part(
+  push @parts, new_part(
     partnumber         => 'T0815',
     description        => 'Zero EIGHT fifteeN @ 7%',
     lastcost           => 5.473,
@@ -162,7 +162,7 @@ sub reset_state {
     unit               => $unit->name,
     %{ $params{part2} }
   )->save;
-  push @parts, SL::Dev::Part::create_part(
+  push @parts, new_part(
     partnumber         => '19%',
     description        => 'Testware 19%',
     lastcost           => 0,
@@ -171,7 +171,7 @@ sub reset_state {
     unit               => $unit->name,
     %{ $params{part3} }
   )->save;
-  push @parts, SL::Dev::Part::create_part(
+  push @parts, new_part(
     partnumber         => '7%',
     description        => 'Testware 7%',
     lastcost           => 0,
@@ -302,8 +302,8 @@ sub total_amount {
 sub test_default_invoice_one_item_19_without_skonto() {
   reset_state() if $ALWAYS_RESET;
 
-  my $item    = SL::Dev::Record::create_invoice_item(part => $parts[0], qty => 2.5);
-  my $invoice = SL::Dev::Record::create_sales_invoice(
+  my $item    = create_invoice_item(part => $parts[0], qty => 2.5);
+  my $invoice = create_sales_invoice(
     taxincluded  => 0,
     invoiceitems => [ $item ],
     payment_id   => $payment_terms->id,
@@ -338,8 +338,8 @@ sub test_default_invoice_one_item_19_without_skonto() {
 sub test_default_invoice_one_item_19_without_skonto_overpaid() {
   reset_state() if $ALWAYS_RESET;
 
-  my $item    = SL::Dev::Record::create_invoice_item(part => $parts[0], qty => 2.5);
-  my $invoice = SL::Dev::Record::create_sales_invoice(
+  my $item    = create_invoice_item(part => $parts[0], qty => 2.5);
+  my $invoice = create_sales_invoice(
     taxincluded  => 0,
     invoiceitems => [ $item ],
     payment_id   => $payment_terms->id,
@@ -379,9 +379,9 @@ sub test_default_invoice_one_item_19_without_skonto_overpaid() {
 sub test_default_invoice_two_items_19_7_tax_with_skonto() {
   reset_state() if $ALWAYS_RESET;
 
-  my $item1   = SL::Dev::Record::create_invoice_item(part => $parts[0], qty => 2.5);
-  my $item2   = SL::Dev::Record::create_invoice_item(part => $parts[1], qty => 1.2);
-  my $invoice = SL::Dev::Record::create_sales_invoice(
+  my $item1   = create_invoice_item(part => $parts[0], qty => 2.5);
+  my $item2   = create_invoice_item(part => $parts[1], qty => 1.2);
+  my $invoice = create_sales_invoice(
     taxincluded  => 0,
     invoiceitems => [ $item1, $item2 ],
     payment_id   => $payment_terms->id,
@@ -413,9 +413,9 @@ sub test_default_invoice_two_items_19_7_tax_with_skonto() {
 sub test_default_invoice_two_items_19_7_tax_with_skonto_tax_included() {
   reset_state() if $ALWAYS_RESET;
 
-  my $item1   = SL::Dev::Record::create_invoice_item(part => $parts[0], qty => 2.5);
-  my $item2   = SL::Dev::Record::create_invoice_item(part => $parts[1], qty => 1.2);
-  my $invoice = SL::Dev::Record::create_sales_invoice(
+  my $item1   = create_invoice_item(part => $parts[0], qty => 2.5);
+  my $item2   = create_invoice_item(part => $parts[1], qty => 1.2);
+  my $invoice = create_sales_invoice(
     taxincluded  => 1,
     invoiceitems => [ $item1, $item2 ],
     payment_id   => $payment_terms->id,
@@ -450,9 +450,9 @@ sub test_default_invoice_two_items_19_7_tax_with_skonto_tax_included() {
 sub test_default_invoice_two_items_19_7_without_skonto() {
   reset_state() if $ALWAYS_RESET;
 
-  my $item1   = SL::Dev::Record::create_invoice_item(part => $parts[0], qty => 2.5);
-  my $item2   = SL::Dev::Record::create_invoice_item(part => $parts[1], qty => 1.2);
-  my $invoice = SL::Dev::Record::create_sales_invoice(
+  my $item1   = create_invoice_item(part => $parts[0], qty => 2.5);
+  my $item2   = create_invoice_item(part => $parts[1], qty => 1.2);
+  my $invoice = create_sales_invoice(
     taxincluded  => 0,
     invoiceitems => [ $item1, $item2 ],
     payment_id   => $payment_terms->id,
@@ -485,9 +485,9 @@ sub test_default_invoice_two_items_19_7_without_skonto() {
 sub test_default_invoice_two_items_19_7_without_skonto_incomplete_payment() {
   reset_state() if $ALWAYS_RESET;
 
-  my $item1   = SL::Dev::Record::create_invoice_item(part => $parts[0], qty => 2.5);
-  my $item2   = SL::Dev::Record::create_invoice_item(part => $parts[1], qty => 1.2);
-  my $invoice = SL::Dev::Record::create_sales_invoice(
+  my $item1   = create_invoice_item(part => $parts[0], qty => 2.5);
+  my $item2   = create_invoice_item(part => $parts[1], qty => 1.2);
+  my $invoice = create_sales_invoice(
     taxincluded  => 0,
     invoiceitems => [ $item1, $item2 ],
     payment_id   => $payment_terms->id,
@@ -516,9 +516,9 @@ sub test_default_invoice_two_items_19_7_without_skonto_incomplete_payment() {
 sub test_default_invoice_two_items_19_7_tax_without_skonto_multiple_payments() {
   reset_state() if $ALWAYS_RESET;
 
-  my $item1   = SL::Dev::Record::create_invoice_item(part => $parts[0], qty => 2.5);
-  my $item2   = SL::Dev::Record::create_invoice_item(part => $parts[1], qty => 1.2);
-  my $invoice = SL::Dev::Record::create_sales_invoice(
+  my $item1   = create_invoice_item(part => $parts[0], qty => 2.5);
+  my $item2   = create_invoice_item(part => $parts[1], qty => 1.2);
+  my $invoice = create_sales_invoice(
     taxincluded  => 0,
     invoiceitems => [ $item1, $item2 ],
     payment_id   => $payment_terms->id,
@@ -552,9 +552,9 @@ sub test_default_invoice_two_items_19_7_tax_without_skonto_multiple_payments() {
 sub test_default_invoice_two_items_19_7_tax_without_skonto_multiple_payments_final_difference_as_skonto() {
   reset_state() if $ALWAYS_RESET;
 
-  my $item1   = SL::Dev::Record::create_invoice_item(part => $parts[0], qty => 2.5);
-  my $item2   = SL::Dev::Record::create_invoice_item(part => $parts[1], qty => 1.2);
-  my $invoice = SL::Dev::Record::create_sales_invoice(
+  my $item1   = create_invoice_item(part => $parts[0], qty => 2.5);
+  my $item2   = create_invoice_item(part => $parts[1], qty => 1.2);
+  my $invoice = create_sales_invoice(
     taxincluded  => 0,
     invoiceitems => [ $item1, $item2 ],
     payment_id   => $payment_terms->id,
@@ -598,9 +598,9 @@ sub  test_default_invoice_two_items_19_7_tax_without_skonto_multiple_payments_fi
   # (11.66) rather than the 19% account (5.85).  The actual tax amount is
   # higher for the 19% case, though (1.11 compared to 0.82)
 
-  my $item1   = SL::Dev::Record::create_invoice_item(part => $parts[0], qty => 2.5);
-  my $item2   = SL::Dev::Record::create_invoice_item(part => $parts[1], qty => 1.2);
-  my $invoice = SL::Dev::Record::create_sales_invoice(
+  my $item1   = create_invoice_item(part => $parts[0], qty => 2.5);
+  my $item2   = create_invoice_item(part => $parts[1], qty => 1.2);
+  my $invoice = create_sales_invoice(
     taxincluded  => 0,
     invoiceitems => [ $item1, $item2 ],
     payment_id   => $payment_terms->id,
@@ -635,9 +635,9 @@ sub test_default_invoice_two_items_19_7_tax_without_skonto_multiple_payments_fin
   reset_state() if $ALWAYS_RESET;
 
   # if there are two cents left there will be two skonto bookings, 1 cent each
-  my $item1   = SL::Dev::Record::create_invoice_item(part => $parts[0], qty => 2.5);
-  my $item2   = SL::Dev::Record::create_invoice_item(part => $parts[1], qty => 1.2);
-  my $invoice = SL::Dev::Record::create_sales_invoice(
+  my $item1   = create_invoice_item(part => $parts[0], qty => 2.5);
+  my $item2   = create_invoice_item(part => $parts[1], qty => 1.2);
+  my $invoice = create_sales_invoice(
     taxincluded  => 0,
     invoiceitems => [ $item1, $item2 ],
     payment_id   => $payment_terms->id,
@@ -671,8 +671,8 @@ sub test_default_invoice_two_items_19_7_tax_without_skonto_multiple_payments_fin
 sub test_default_invoice_one_item_19_multiple_payment_final_difference_as_skonto() {
   reset_state() if $ALWAYS_RESET;
 
-  my $item    = SL::Dev::Record::create_invoice_item(part => $parts[0], qty => 2.5);
-  my $invoice = SL::Dev::Record::create_sales_invoice(
+  my $item    = create_invoice_item(part => $parts[0], qty => 2.5);
+  my $invoice = create_sales_invoice(
     taxincluded  => 0,
     invoiceitems => [ $item ],
     payment_id   => $payment_terms->id,
@@ -712,8 +712,8 @@ sub test_default_invoice_one_item_19_multiple_payment_final_difference_as_skonto
 sub test_default_invoice_one_item_19_multiple_payment_final_difference_as_skonto_1cent() {
   reset_state() if $ALWAYS_RESET;
 
-  my $item    = SL::Dev::Record::create_invoice_item(part => $parts[0], qty => 2.5);
-  my $invoice = SL::Dev::Record::create_sales_invoice(
+  my $item    = create_invoice_item(part => $parts[0], qty => 2.5);
+  my $invoice = create_sales_invoice(
     taxincluded  => 0,
     invoiceitems => [ $item ],
     payment_id   => $payment_terms->id,
@@ -857,9 +857,9 @@ sub test_default_purchase_invoice_two_charts_19_7_tax_without_skonto_multiple_pa
 sub test_default_invoice_two_items_19_7_tax_with_skonto_50_50() {
   reset_state() if $ALWAYS_RESET;
 
-  my $item1   = SL::Dev::Record::create_invoice_item(part => $parts[2], qty => 1);
-  my $item2   = SL::Dev::Record::create_invoice_item(part => $parts[3], qty => 1);
-  my $invoice = SL::Dev::Record::create_sales_invoice(
+  my $item1   = create_invoice_item(part => $parts[2], qty => 1);
+  my $item2   = create_invoice_item(part => $parts[3], qty => 1);
+  my $invoice = create_sales_invoice(
     taxincluded  => 0,
     invoiceitems => [ $item1, $item2 ],
     payment_id   => $payment_terms->id,
@@ -892,11 +892,11 @@ sub test_default_invoice_two_items_19_7_tax_with_skonto_50_50() {
 sub test_default_invoice_four_items_19_7_tax_with_skonto_4x_25() {
   reset_state() if $ALWAYS_RESET;
 
-  my $item1   = SL::Dev::Record::create_invoice_item(part => $parts[2], qty => 0.5);
-  my $item2   = SL::Dev::Record::create_invoice_item(part => $parts[3], qty => 0.5);
-  my $item3   = SL::Dev::Record::create_invoice_item(part => $parts[2], qty => 0.5);
-  my $item4   = SL::Dev::Record::create_invoice_item(part => $parts[3], qty => 0.5);
-  my $invoice = SL::Dev::Record::create_sales_invoice(
+  my $item1   = create_invoice_item(part => $parts[2], qty => 0.5);
+  my $item2   = create_invoice_item(part => $parts[3], qty => 0.5);
+  my $item3   = create_invoice_item(part => $parts[2], qty => 0.5);
+  my $item4   = create_invoice_item(part => $parts[3], qty => 0.5);
+  my $invoice = create_sales_invoice(
     taxincluded  => 0,
     invoiceitems => [ $item1, $item2, $item3, $item4 ],
     payment_id   => $payment_terms->id,
@@ -928,11 +928,11 @@ sub test_default_invoice_four_items_19_7_tax_with_skonto_4x_25() {
 sub test_default_invoice_four_items_19_7_tax_with_skonto_4x_25_tax_included() {
   reset_state() if $ALWAYS_RESET;
 
-  my $item1   = SL::Dev::Record::create_invoice_item(part => $parts[2], qty => 0.5);
-  my $item2   = SL::Dev::Record::create_invoice_item(part => $parts[3], qty => 0.5);
-  my $item3   = SL::Dev::Record::create_invoice_item(part => $parts[2], qty => 0.5);
-  my $item4   = SL::Dev::Record::create_invoice_item(part => $parts[3], qty => 0.5);
-  my $invoice = SL::Dev::Record::create_sales_invoice(
+  my $item1   = create_invoice_item(part => $parts[2], qty => 0.5);
+  my $item2   = create_invoice_item(part => $parts[3], qty => 0.5);
+  my $item3   = create_invoice_item(part => $parts[2], qty => 0.5);
+  my $item4   = create_invoice_item(part => $parts[3], qty => 0.5);
+  my $invoice = create_sales_invoice(
     taxincluded  => 1,
     invoiceitems => [ $item1, $item2, $item3, $item4 ],
     payment_id   => $payment_terms->id,
@@ -966,11 +966,11 @@ sub test_default_invoice_four_items_19_7_tax_with_skonto_4x_25_tax_included() {
 sub test_default_invoice_four_items_19_7_tax_with_skonto_4x_25_multiple() {
   reset_state() if $ALWAYS_RESET;
 
-  my $item1   = SL::Dev::Record::create_invoice_item(part => $parts[2], qty => 0.5);
-  my $item2   = SL::Dev::Record::create_invoice_item(part => $parts[3], qty => 0.5);
-  my $item3   = SL::Dev::Record::create_invoice_item(part => $parts[2], qty => 0.5);
-  my $item4   = SL::Dev::Record::create_invoice_item(part => $parts[3], qty => 0.5);
-  my $invoice = SL::Dev::Record::create_sales_invoice(
+  my $item1   = create_invoice_item(part => $parts[2], qty => 0.5);
+  my $item2   = create_invoice_item(part => $parts[3], qty => 0.5);
+  my $item3   = create_invoice_item(part => $parts[2], qty => 0.5);
+  my $item4   = create_invoice_item(part => $parts[3], qty => 0.5);
+  my $invoice = create_sales_invoice(
     taxincluded  => 0,
     invoiceitems => [ $item1, $item2, $item3, $item4 ],
     payment_id   => $payment_terms->id,
@@ -1413,9 +1413,9 @@ sub test_ap_currency_tax_not_included_and_payment_2_credit_note {
 sub test_credit_note_two_items_19_7_tax_tax_not_included() {
   reset_state() if $ALWAYS_RESET;
 
-  my $item1   = SL::Dev::Record::create_invoice_item(part => $parts[0], qty => 5);
-  my $item2   = SL::Dev::Record::create_invoice_item(part => $parts[1], qty => 3);
-  my $invoice = SL::Dev::Record::create_credit_note(
+  my $item1   = create_invoice_item(part => $parts[0], qty => 5);
+  my $item2   = create_invoice_item(part => $parts[1], qty => 3);
+  my $invoice = create_credit_note(
     invnumber    => 'cn1',
     taxincluded  => 0,
     invoiceitems => [ $item1, $item2 ],
index 22f4165..c888401 100644 (file)
@@ -10,6 +10,7 @@ use Data::Dumper;
 use List::MoreUtils qw(uniq);
 use Support::TestSetup;
 use Test::Exception;
+use SL::Dev::ALL qw(:ALL);
 
 use SL::DB::Buchungsgruppe;
 use SL::DB::Currency;
@@ -22,11 +23,12 @@ use SL::DB::Part;
 use SL::DB::Unit;
 use SL::DB::TaxZone;
 
-my ($customer, $currency_id, @parts, $buchungsgruppe, $buchungsgruppe7, $unit, $employee, $tax, $tax7, $taxzone);
+my ($customer, @parts, $buchungsgruppe, $buchungsgruppe7, $unit, $employee, $tax, $tax7, $taxzone);
 
 sub clear_up {
   SL::DB::Manager::Order->delete_all(all => 1);
   SL::DB::Manager::DeliveryOrder->delete_all(all => 1);
+  SL::DB::Manager::InvoiceItem->delete_all(all => 1);
   SL::DB::Manager::Invoice->delete_all(all => 1);
   SL::DB::Manager::Part->delete_all(all => 1);
   SL::DB::Manager::Customer->delete_all(all => 1);
@@ -47,44 +49,38 @@ sub reset_state {
   $tax7            = SL::DB::Manager::Tax->find_by(taxkey => 2, rate => 0.07)                                              || croak "No tax for 7\%";
   $taxzone         = SL::DB::Manager::TaxZone->find_by( description => 'Inland')                                           || croak "No taxzone";
 
-  $currency_id     = $::instance_conf->get_currency_id;
-
-  $customer     = SL::DB::Customer->new(
+  $customer     = new_customer(
     name        => 'Test Customer',
-    currency_id => $currency_id,
     taxzone_id  => $taxzone->id,
     %{ $params{customer} }
   )->save;
 
   @parts = ();
-  push @parts, SL::DB::Part->new(
+  push @parts, new_part(
     partnumber         => 'T4254',
     description        => 'Fourty-two fifty-four',
     lastcost           => 1.93,
     sellprice          => 2.34,
-    part_type          => 'part',
     buchungsgruppen_id => $buchungsgruppe->id,
     unit               => $unit->name,
     %{ $params{part1} }
   )->save;
 
-  push @parts, SL::DB::Part->new(
+  push @parts, new_part(
     partnumber         => 'T0815',
     description        => 'Zero EIGHT fifteeN @ 7%',
     lastcost           => 5.473,
     sellprice          => 9.714,
-    part_type          => 'part',
     buchungsgruppen_id => $buchungsgruppe7->id,
     unit               => $unit->name,
     %{ $params{part2} }
   )->save;
 
-  push @parts, SL::DB::Part->new(
+  push @parts, new_part(
     partnumber         => 'T888',
     description        => 'Triple 8',
     lastcost           => 0,
     sellprice          => 0.6,
-    part_type          => 'part',
     buchungsgruppen_id => $buchungsgruppe->id,
     unit               => $unit->name,
     %{ $params{part3} }
@@ -95,16 +91,8 @@ sub reset_state {
 sub new_invoice {
   my %params  = @_;
 
-  return SL::DB::Invoice->new(
-    customer_id => $customer->id,
-    currency_id => $currency_id,
-    employee_id => $employee->id,
-    salesman_id => $employee->id,
-    gldate      => DateTime->today_local->to_kivitendo,
+  return create_sales_invoice(
     taxzone_id  => $taxzone->id,
-    transdate   => DateTime->today_local->to_kivitendo,
-    invoice     => 1,
-    type        => 'invoice',
     %params,
   );
 }
@@ -114,12 +102,8 @@ sub new_item {
 
   my $part = delete($params{part}) || $parts[0];
 
-  return SL::DB::InvoiceItem->new(
-    parts_id    => $part->id,
-    lastcost    => $part->lastcost,
-    sellprice   => $part->sellprice,
-    description => $part->description,
-    unit        => $part->unit,
+  return create_invoice_item(
+    part => $part,
     %params,
   );
 }
@@ -127,7 +111,7 @@ sub new_item {
 sub test_default_invoice_one_item_19_tax_not_included() {
   reset_state();
 
-  my $item    = new_item(qty => 2.5);
+  my $item = new_item(qty => 2.5);
   my $invoice = new_invoice(
     taxincluded  => 0,
     invoiceitems => [ $item ],
index 3071cc3..cc3fc0e 100644 (file)
@@ -10,7 +10,7 @@ use Carp;
 use Data::Dumper;
 use Support::TestSetup;
 use SL::DB::Part;
-use SL::Dev::Part;
+use SL::Dev::Part qw(new_part);
 
 Support::TestSetup::login();
 
@@ -21,7 +21,7 @@ local $SIG{__WARN__} = sub {};
 
 # test simple transaction
 
-my $part = create_part();
+my $part = new_part();
 SL::DB->client->with_transaction(sub {
   $part->save;
   ok 1, 'part saved';
@@ -31,7 +31,7 @@ SL::DB->client->with_transaction(sub {
 };
 
 # test failing transaction
-my $part2 = create_part(partnumber => $part->partnumber); # woops, duplicate partnumber
+my $part2 = new_part(partnumber => $part->partnumber); # woops, duplicate partnumber
 SL::DB->client->with_transaction(sub {
   $part2->save;
   ok 0, 'part saved';
@@ -55,7 +55,7 @@ dies_ok {
 # TODO - not possible to test without locally adding hooks in run time
 
 # test if error gets correctly stored in db->error
-$part2 = create_part(partnumber => $part->partnumber); # woops, duplicate partnumber
+$part2 = new_part(partnumber => $part->partnumber); # woops, duplicate partnumber
 SL::DB->client->with_transaction(sub {
   $part2->save;
   ok 0, 'part saved';
index d35a95f..8797a1c 100644 (file)
@@ -7,7 +7,8 @@ use File::Temp;
 use Support::TestSetup;
 use Test::Exception;
 use SL::File;
-use SL::Dev::File;
+use SL::Dev::File qw(create_uploaded create_scanned create_created);
+
 
 Support::TestSetup::login();
 
@@ -25,12 +26,12 @@ my $scannerfile = "${temp_dir}/f2";
 clear_up();
 reset_state();
 
-my $file1 = SL::Dev::File::create_uploaded( file_name => 'file1', file_contents => 'inhalt1 uploaded' );
-my $file2 = SL::Dev::File::create_scanned(  file_name => 'file2', file_contents => 'inhalt2 scanned', file_path => $scannerfile );
-my $file3 = SL::Dev::File::create_created(  file_name => 'file3', file_contents => 'inhalt3 created'    );
-my $file4 = SL::Dev::File::create_created(  file_name => 'file3', file_contents => 'inhalt3 new version');
+my $file1 = create_uploaded( file_name => 'file1', file_contents => 'inhalt1 uploaded' );
+my $file2 = create_scanned(  file_name => 'file2', file_contents => 'inhalt2 scanned', file_path => $scannerfile );
+my $file3 = create_created(  file_name => 'file3', file_contents => 'inhalt3 created'    );
+my $file4 = create_created(  file_name => 'file3', file_contents => 'inhalt3 new version');
 
-is( SL::Dev::File->get_all_count(),                    3,"total number of files created is 3");
+is( SL::Dev::File::get_all_count(),                    3,"total number of files created is 3");
 ok( $file1->file_name                        eq 'file1' ,"file has right name");
 my $content1 = $file1->get_content;
 ok( $$content1 eq 'inhalt1 uploaded'                    ,"file has right content");
@@ -42,12 +43,12 @@ is( -f $scannerfile ? 1 : 0,                           1,"scanned document is mo
 my $content2 = File::Slurp::read_file($scannerfile);
 ok( $content2 eq 'inhalt2 scanned'                      ,"scanned file has right content");
 
-my @file5 = SL::Dev::File->get_all(file_name => 'file3');
+my @file5 = SL::Dev::File::get_all(file_name => 'file3');
 is(   scalar( @file5),                                 1, "one actual file found");
 my $content5 = $file5[0]->get_content();
 ok( $$content5 eq 'inhalt3 new version'                 ,"file has right actual content");
 
-my @file6 = SL::Dev::File->get_all_versions(file_name => 'file3');
+my @file6 = SL::Dev::File::get_all_versions(file_name => 'file3');
 is(   scalar( @file6),                                 2,"two file versions found");
 $content5 = $file6[0]->get_content;
 ok( $$content5 eq 'inhalt3 new version'                 ,"file has right actual content");
@@ -108,7 +109,7 @@ done_testing;
 sub clear_up {
   # Cleaning up may fail.
   eval {
-    SL::Dev::File->delete_all();
+    SL::Dev::File::delete_all();
     unlink($scannerfile);
   };
 }
index 75a5236..dc2f9dd 100644 (file)
@@ -18,7 +18,7 @@ use SL::DB::DeliveryOrderItemsStock;
 use SL::DB::Bin;
 use SL::WH;
 use SL::AM;
-use SL::Dev::ALL;
+use SL::Dev::ALL qw(:ALL);
 use SL::Helper::ShippedQty;
 use DateTime;
 
@@ -28,13 +28,13 @@ clear_up();
 
 my ($customer, $vendor, @parts, $unit);
 
-$customer = SL::Dev::CustomerVendor::create_customer(name => 'Testkunde'    )->save;
-$vendor   = SL::Dev::CustomerVendor::create_vendor(  name => 'Testlieferant')->save;
+$customer = new_customer(name => 'Testkunde'    )->save;
+$vendor   = new_vendor(  name => 'Testlieferant')->save;
 
 my $default_sellprice = 10;
 my $default_lastcost  =  4;
 
-my ($wh) = SL::Dev::Inventory::create_warehouse_and_bins();
+my ($wh) = create_warehouse_and_bins();
 my $bin1 = SL::DB::Manager::Bin->find_by(description => "Bin 1");
 my $bin2 = SL::DB::Manager::Bin->find_by(description => "Bin 2");
 
@@ -46,7 +46,7 @@ my %part_defaults = (
 
 # create 3 parts to be used in test
 for my $i ( 1 .. 4 ) {
-  SL::Dev::Part::create_part( %part_defaults, partnumber => $i, description => "part $i test" )->save;
+  new_part( %part_defaults, partnumber => $i, description => "part $i test" )->save;
 };
 
 my $part1 = SL::DB::Manager::Part->find_by( partnumber => '1' );
@@ -63,42 +63,45 @@ my %default_transfer_params = ( wh => $wh, bin => $bin1, unit => 'Stck');
 
 note("testing purchases, no fill_up");
 
-my $purchase_order = SL::Dev::Record::create_purchase_order(
+my $purchase_order = create_purchase_order(
   save       => 1,
-  orderitems => [ SL::Dev::Record::create_order_item(part => $part1, qty => 11),
-                  SL::Dev::Record::create_order_item(part => $part2, qty => 12),
-                  SL::Dev::Record::create_order_item(part => $part3, qty => 13),
+  orderitems => [ create_order_item(part => $part1, qty => 11),
+                  create_order_item(part => $part2, qty => 12),
+                  create_order_item(part => $part3, qty => 13),
                 ]
 );
 
+Rose::DB::Object::Helpers::forget_related($purchase_order, 'orderitems');
+$purchase_order->orderitems;
+
 SL::Helper::ShippedQty
   ->new(require_stock_out => 1)  # should make no difference while there is no delivery order
   ->calculate($purchase_order)
   ->write_to_objects;
 
-is($purchase_order->orderitems->[0]->{shipped_qty}, 0, "first purchase orderitem has no shipped_qty");
-is($purchase_order->orderitems->[0]->{delivered},   '', "first purchase orderitem is not delivered");
+is($purchase_order->items_sorted->[0]->{shipped_qty}, 0, "first purchase orderitem has no shipped_qty");
+ok(!$purchase_order->items_sorted->[0]->{delivered},     "first purchase orderitem is not delivered");
 
 my $purchase_orderitem_part1 = SL::DB::Manager::OrderItem->find_by( parts_id => $part1->id, trans_id => $purchase_order->id);
 
 is($purchase_orderitem_part1->shipped_qty, 0, "OrderItem shipped_qty method ok");
 
 is($purchase_order->closed,     0, 'purchase order is open');
-is($purchase_order->delivered, '', 'purchase order is not delivered');
+ok(!$purchase_order->delivered,    'purchase order is not delivered');
 
 note('converting purchase order to delivery order');
 # create purchase delivery order from purchase order
 my $purchase_delivery_order = $purchase_order->convert_to_delivery_order;
 is($purchase_order->closed,    0, 'purchase order is open');
-is($purchase_order->delivered, 1, 'purchase order is now delivered');
+ok($purchase_order->delivered,    'purchase order is now delivered');
 
 SL::Helper::ShippedQty
   ->new(require_stock_out => 0)
   ->calculate($purchase_order)
   ->write_to_objects;
 
-is($purchase_order->orderitems->[0]->{shipped_qty}, 11, "require_stock_out => 0: first purchase orderitem has shipped_qty");
-is($purchase_order->orderitems->[0]->{delivered},    1, "require_stock_out => 0: first purchase orderitem is delivered");
+is($purchase_order->items_sorted->[0]->{shipped_qty}, 11, "require_stock_out => 0: first purchase orderitem has shipped_qty");
+ok($purchase_order->items_sorted->[0]->{delivered},       "require_stock_out => 0: first purchase orderitem is delivered");
 
 Rose::DB::Object::Helpers::forget_related($purchase_order, 'orderitems');
 $purchase_order->orderitems;
@@ -108,22 +111,22 @@ SL::Helper::ShippedQty
   ->calculate($purchase_order)
   ->write_to_objects;
 
-is($purchase_order->orderitems->[0]->{shipped_qty}, 0,  "require_stock_out => 1: first purchase orderitem has no shipped_qty");
-is($purchase_order->orderitems->[0]->{delivered},   '', "require_stock_out => 1: first purchase orderitem is not delivered");
+is($purchase_order->items_sorted->[0]->{shipped_qty}, 0,  "require_stock_out => 1: first purchase orderitem has no shipped_qty");
+ok(!$purchase_order->items_sorted->[0]->{delivered},      "require_stock_out => 1: first purchase orderitem is not delivered");
 
 # ship items from delivery order
-SL::Dev::Inventory::transfer_purchase_delivery_order($purchase_delivery_order);
+transfer_purchase_delivery_order($purchase_delivery_order);
 
 Rose::DB::Object::Helpers::forget_related($purchase_order, 'orderitems');
 $purchase_order->orderitems;
 
 SL::Helper::ShippedQty
-  ->new(require_stock_out => 1)  # shouldn't make a difference now after shipping
+  ->new(require_stock_out => 1, keep_matches => 1)  # shouldn't make a difference now after shipping
   ->calculate($purchase_order)
   ->write_to_objects;
 
-is($purchase_order->orderitems->[0]->{shipped_qty}, 11, "require_stock_out => 1: first purchase orderitem has shipped_qty");
-is($purchase_order->orderitems->[0]->{delivered},    1, "require_stock_out => 1: first purchase orderitem is delivered");
+is($purchase_order->items_sorted->[0]->{shipped_qty}, 11, "require_stock_out => 1: first purchase orderitem has shipped_qty");
+ok($purchase_order->items_sorted->[0]->{delivered},       "require_stock_out => 1: first purchase orderitem is delivered");
 
 my $purchase_orderitem_part2 = SL::DB::Manager::OrderItem->find_by(parts_id => $part1->id, trans_id => $purchase_order->id);
 
@@ -132,21 +135,24 @@ is($purchase_orderitem_part2->shipped_qty(require_stock_out => 1), 11, "OrderIte
 
 note('testing sales, no fill_up');
 
-my $sales_order = SL::Dev::Record::create_sales_order(
+my $sales_order = create_sales_order(
   save       => 1,
-  orderitems => [ SL::Dev::Record::create_order_item(part => $part1, qty => 5),
-                  SL::Dev::Record::create_order_item(part => $part2, qty => 6),
-                  SL::Dev::Record::create_order_item(part => $part3, qty => 7),
+  orderitems => [ create_order_item(part => $part1, qty => 5),
+                  create_order_item(part => $part2, qty => 6),
+                  create_order_item(part => $part3, qty => 7),
                 ]
 );
 
+Rose::DB::Object::Helpers::forget_related($sales_order, 'orderitems');
+$sales_order->orderitems;
+
 SL::Helper::ShippedQty
   ->new(require_stock_out => 1)  # should make no difference while there is no delivery order
   ->calculate($sales_order)
   ->write_to_objects;
 
-is($sales_order->orderitems->[0]->{shipped_qty}, 0,  "first sales orderitem has no shipped_qty");
-is($sales_order->orderitems->[0]->{delivered},   '', "first sales orderitem is not delivered");
+is($sales_order->items_sorted->[0]->{shipped_qty}, 0,  "first sales orderitem has no shipped_qty");
+ok(!$sales_order->items_sorted->[0]->{delivered},      "first sales orderitem is not delivered");
 
 my $orderitem_part1 = SL::DB::Manager::OrderItem->find_by(parts_id => $part1->id, trans_id => $sales_order->id);
 my $orderitem_part2 = SL::DB::Manager::OrderItem->find_by(parts_id => $part2->id, trans_id => $sales_order->id);
@@ -161,8 +167,8 @@ SL::Helper::ShippedQty
   ->calculate($sales_order)
   ->write_to_objects;
 
-is($sales_order->orderitems->[0]->{shipped_qty}, 5, "require_stock_out => 0: first sales orderitem has shipped_qty");
-is($sales_order->orderitems->[0]->{delivered},   1, "require_stock_out => 0: first sales orderitem is delivered");
+is($sales_order->items_sorted->[0]->{shipped_qty}, 5, "require_stock_out => 0: first sales orderitem has shipped_qty");
+ok($sales_order->items_sorted->[0]->{delivered},      "require_stock_out => 0: first sales orderitem is delivered");
 
 Rose::DB::Object::Helpers::forget_related($sales_order, 'orderitems');
 $sales_order->orderitems;
@@ -172,11 +178,11 @@ SL::Helper::ShippedQty
   ->calculate($sales_order)
   ->write_to_objects;
 
-is($sales_order->orderitems->[0]->{shipped_qty}, 0,  "require_stock_out => 1: first sales orderitem has no shipped_qty");
-is($sales_order->orderitems->[0]->{delivered},   '', "require_stock_out => 1: first sales orderitem is not delivered");
+is($sales_order->items_sorted->[0]->{shipped_qty}, 0,  "require_stock_out => 1: first sales orderitem has no shipped_qty");
+ok(!$sales_order->items_sorted->[0]->{delivered},      "require_stock_out => 1: first sales orderitem is not delivered");
 
 # ship items from delivery order
-SL::Dev::Inventory::transfer_sales_delivery_order($sales_delivery_order);
+transfer_sales_delivery_order($sales_delivery_order);
 
 Rose::DB::Object::Helpers::forget_related($sales_order, 'orderitems');
 $sales_order->orderitems;
@@ -186,8 +192,8 @@ SL::Helper::ShippedQty
   ->calculate($sales_order)
   ->write_to_objects;
 
-is($sales_order->orderitems->[0]->{shipped_qty}, 5, "require_stock_out => 1: first sales orderitem has no shipped_qty");
-is($sales_order->orderitems->[0]->{delivered},   1, "require_stock_out => 1: first sales orderitem is not delivered");
+is($sales_order->items_sorted->[0]->{shipped_qty}, 5, "require_stock_out => 1: first sales orderitem has no shipped_qty");
+ok($sales_order->items_sorted->[0]->{delivered},      "require_stock_out => 1: first sales orderitem is not delivered");
 
 $orderitem_part1 = SL::DB::Manager::OrderItem->find_by(parts_id => $part1->id, trans_id => $sales_order->id);
 
@@ -200,6 +206,57 @@ is ($number_of_linked_items , 6, "6 record_links for items, 3 from sales order,
 
 clear_up();
 
+{
+#  legacy unlinked scenario:
+#
+#  order with two positions of the same part, qtys: 5, 3.
+#  3 linked delivery orders, with positions:
+#    1:  3 unlinked
+#    2:  1 linked to 1, 3 linked to 2
+#    3:  1 linked to 1
+#
+#  should be resolved under fill_up as 5/3, but gets resolved as 4/4
+  my $part = new_part()->save;
+  my $order = create_sales_order(
+    orderitems => [
+      create_order_item(part => $part, qty => 5),
+      create_order_item(part => $part, qty => 3),
+    ],
+  )->save;
+  my $do1 = create_sales_delivery_order(
+    orderitems => [
+      create_delivery_order_item(part => $part, qty => 3),
+    ],
+  );
+  my $do2 = create_sales_delivery_order(
+    orderitems => [
+      create_delivery_order_item(part => $part, qty => 1),
+      create_delivery_order_item(part => $part, qty => 3),
+    ],
+  );
+  my $do3 = create_sales_delivery_order(
+    orderitems => [
+      create_delivery_order_item(part => $part, qty => 1),
+    ],
+  );
+  $order->link_to_record($do1);
+  $order->link_to_record($do2);
+  $order->items_sorted->[0]->link_to_record($do2->items_sorted->[0]);
+  $order->items_sorted->[1]->link_to_record($do2->items_sorted->[1]);
+  $order->link_to_record($do3);
+  $order->items_sorted->[0]->link_to_record($do3->items->[0]);
+
+  SL::Helper::ShippedQty
+    ->new(fill_up => 1, require_stock_out => 0)
+    ->calculate($order)
+    ->write_to_objects;
+
+  is $order->items_sorted->[0]->{shipped_qty}, 5, 'unlinked legacy position test 1';
+  is $order->items_sorted->[1]->{shipped_qty}, 3, 'unlinked legacy position test 2';
+}
+
+clear_up();
+
 done_testing;
 
 sub clear_up {
index 197dfb5..6500dfa 100644 (file)
@@ -7,7 +7,7 @@ use Test::Exception;
 use SL::DB::Unit;
 use SL::DB::Part;
 use SL::DB::Assembly;
-use SL::Dev::Part;
+use SL::Dev::Part qw(new_assembly);
 use SL::DB::Helper::ValidateAssembly;
 
 Support::TestSetup::login();
@@ -32,13 +32,13 @@ is($assembly_item->assembly_part->partnumber, '19000', 'assembly part assembly p
 
 
 
-my $assembly2_part = SL::Dev::Part::create_assembly( partnumber => '20000', assnumber => 'as2' )->save;
+my $assembly2_part = new_assembly( partnumber => '20000', assnumber => 'as2' )->save;
 my $retval = validate_assembly($assembly_part,$assembly2_part);
 ok(!defined $retval, 'assembly 19000 can be child of assembly 20000' );
 $assembly2_part->add_assemblies(SL::DB::Assembly->new(parts_id => $assembly_part->id, qty => 3, bom => 1));
 $assembly2_part->save;
 
-my $assembly3_part = SL::Dev::Part::create_assembly( partnumber => '30000', assnumber => 'as3' )->save;
+my $assembly3_part = new_assembly( partnumber => '30000', assnumber => 'as3' )->save;
 $retval = validate_assembly($assembly3_part,$assembly_part);
 ok(!defined $retval, 'assembly 30000 can be child of assembly 19000' );
 
@@ -92,7 +92,7 @@ sub clear_up {
 sub reset_state {
   my %params = @_;
 
-  my $assembly = SL::Dev::Part::create_assembly( assnumber => '19000', partnumber => '19000' )->save;
+  my $assembly = new_assembly( assnumber => '19000', partnumber => '19000' )->save;
 };
 
 1;
index 3d6705e..46e35de 100644 (file)
@@ -6,15 +6,15 @@ use Support::TestSetup;
 use Carp;
 use Test::Exception;
 use SL::DB::Part;
-use SL::Dev::Part;
+use SL::Dev::Part qw(new_assortment);
 
 Support::TestSetup::login();
 
 clear_up();
 
-my $assortment = SL::Dev::Part::create_assortment( assnumber       => 'aso1',
-                                                   description     => "Assortment 1",
-                                                   number_of_parts => 10,
+my $assortment = new_assortment( assnumber       => 'aso1',
+                                 description     => "Assortment 1",
+                                 number_of_parts => 10,
                                                  )->save;
 
 is( SL::DB::Manager::Part->get_all_count(), 11,  "total number of parts created is 11");
index 583aefe..39e5756 100644 (file)
@@ -6,20 +6,20 @@ use Support::TestSetup;
 use Carp;
 use Test::Exception;
 use SL::DB::Part;
-use SL::Dev::Part;
-use SL::Dev::Inventory;
+use SL::Dev::Part qw(new_part);
+use SL::Dev::Inventory qw(create_warehouse_and_bins set_stock transfer_stock);
 
 Support::TestSetup::login();
 
 clear_up();
 
-my ($wh1, $bin1_1) = SL::Dev::Inventory::create_warehouse_and_bins(
+my ($wh1, $bin1_1) = create_warehouse_and_bins(
   warehouse_description => 'Testlager',
   bin_description       => 'Testlagerplatz',
   number_of_bins        => 2,
 );
 my $bin1_2 = $wh1->bins->[1];
-my ($wh2, $bin2_1) = SL::Dev::Inventory::create_warehouse_and_bins(
+my ($wh2, $bin2_1) = create_warehouse_and_bins(
   warehouse_description => 'Testlager 2',
   bin_description       => 'Testlagerplatz 2',
   number_of_bins        => 2,
@@ -28,18 +28,18 @@ my ($wh2, $bin2_1) = SL::Dev::Inventory::create_warehouse_and_bins(
 my $today     = DateTime->today;
 my $yesterday = $today->clone->add(days => -1);
 
-my $part = SL::Dev::Part::create_part->save;
-SL::Dev::Inventory::set_stock(part => $part, bin_id => $bin1_1->id, qty => 7, shippingdate => $yesterday);
-SL::Dev::Inventory::set_stock(part => $part, bin_id => $bin1_1->id, qty => 5);
-SL::Dev::Inventory::set_stock(part => $part, bin_id => $bin1_1->id, abs_qty => 8); # apply -4 to get qty 8 in bin1_1
-SL::Dev::Inventory::set_stock(part => $part, bin_id => $bin1_2->id, qty => 9);
-
-SL::Dev::Inventory::set_stock(part => $part, bin_id => $bin2_1->id, abs_qty => 10);
-SL::Dev::Inventory::transfer_stock(part     => $part,
-                                   from_bin => $wh2->bins->[0],
-                                   to_bin   => $wh2->bins->[1],
-                                   qty      => 2,
-                                  );
+my $part = new_part()->save;
+set_stock(part => $part, bin_id => $bin1_1->id, qty => 7, shippingdate => $yesterday);
+set_stock(part => $part, bin_id => $bin1_1->id, qty => 5);
+set_stock(part => $part, bin_id => $bin1_1->id, abs_qty => 8); # apply -4 to get qty 8 in bin1_1
+set_stock(part => $part, bin_id => $bin1_2->id, qty => 9);
+
+set_stock(part => $part, bin_id => $bin2_1->id, abs_qty => 10);
+transfer_stock(part     => $part,
+               from_bin => $wh2->bins->[0],
+               to_bin   => $wh2->bins->[1],
+               qty      => 2,
+              );
 
 is( SL::DB::Manager::Part->get_all_count(), 1,  "total number of parts created is 1");
 is( $part->get_stock == 27                                     , 1 , "total stock of part is 27");
index 2a05c55..b85cd68 100644 (file)
@@ -3,6 +3,8 @@ use Test::More;
 
 use lib 't';
 
+use SL::Dev::Part qw(new_part);
+
 use_ok 'Support::TestSetup';
 use_ok 'SL::DB::Bin';
 use_ok 'SL::DB::Part';
@@ -26,8 +28,7 @@ SL::DB::Manager::Bin      ->delete_all(where => [ or => [ description => NAME()
 SL::DB::Manager::Warehouse->delete_all(where => [ description => NAME() ]);
 
 # Create test data
-$part = SL::DB::Part->new(unit => 'mg', description => NAME(), partnumber => NAME(), part_type => 'part');
-$part->save();
+$part = new_part(unit => 'mg', description => NAME(), partnumber => NAME())->save();
 
 is(ref($part), 'SL::DB::Part', 'loading a part to test with id ' . $part->id);
 
index 9bd54e3..4e35451 100644 (file)
@@ -1,5 +1,7 @@
-[%- USE HTML -%][%- USE LxERP -%][%- USE P -%][% IF TEMPLATES.size %]
- [% LxERP.t8('Template suggestions') %]:
+[%- USE HTML -%][%- USE LxERP -%][%- USE P -%]
+
+[% IF TEMPLATES_AP.size %]
+ [% LxERP.t8('AP template suggestions') %]:
  <table>
   <thead>
    <tr>
@@ -11,7 +13,7 @@
   </thead>
 
   <tbody>
-   [% FOREACH template = TEMPLATES %]
+   [% FOREACH template = TEMPLATES_AP %]
     <tr class="listrow">
      <td>[% P.link(SELF.load_ap_record_template_url(template), template.template_name) %]</td>
      <td>[% HTML.escape(template.vendor.name) %]</td>
   </tbody>
  </table>
 [% ELSE %]
- <p class="message_hint">[% LxERP.t8('No template was found.') %]</p>
+ <p class="message_hint">[% LxERP.t8('No AP template was found.') %]</p>
+[% END %]
+
+[% IF TEMPLATES_GL.size %]
+ [% LxERP.t8('GL template suggestions') %]:
+ <table>
+  <thead>
+   <tr>
+    <th class="listheading">[% LxERP.t8('Description') %]</th>
+    <th class="listheading">[% LxERP.t8('Reference') %]</th>
+    <th class="listheading">[% LxERP.t8('Employee') %]</th>
+    <th class="listheading">[% LxERP.t8('Template date') %]</th>
+   </tr>
+  </thead>
+
+  <tbody>
+   [% FOREACH template = TEMPLATES_GL %]
+    <tr class="listrow">
+     <td>[% P.link(SELF.load_gl_record_template_url(template), template.template_name) %]</td>
+     <td>[% HTML.escape(template.reference) %]</td>
+     <td>[% HTML.escape(template.employee.name || template.employee.login) %]</td>
+     <td>[% HTML.escape(template.itime_as_date) %]</td>
+    </tr>
+   [% END %]
+  </tbody>
+ </table>
+[% ELSE %]
+ <p class="message_hint">[% LxERP.t8('No GL template was found.') %]</p>
 [% END %]
index 25b32b3..45b1818 100644 (file)
 
 
 <br>
-[% LxERP.t8('Vendor filter for AP transaction templates') %]:
 
-<form method="post" action="javascript:kivi.BankTransaction.filter_templates()">
+<form method="post" action="javascript:kivi.BankTransaction.filter_templates()" id="create_invoice_window_form">
  [% L.hidden_tag("bt_id",               SELF.transaction.id) %]
  [% L.hidden_tag("filter.bank_account", FORM.filter.bank_account) %]
  [% L.hidden_tag("filter.fromdate",     FORM.filter.fromdate) %]
  [% L.hidden_tag("filter.todate",      FORM.filter.todate) %]
  <table>
+  <tr>
+   <th align="right">[%- LxERP.t8("Template Description") %]</th>
+   <td>[% P.input_tag("template", template_name, style="width: 250px") %]</td>
+  </tr>
   <tr>
    <th align="right">[%- LxERP.t8("Vendor") %]</th>
-   <td>[% P.input_tag("vendor", vendor_name, class="initial_focus", style="width: 250px") %]</td>
+   <td>[% P.input_tag("vendor", vendor_name,  style="width: 250px") %]</td>
+  </tr>
+  <tr>
+   <th align="right">[%- LxERP.t8("Reference") %]</th>
+   <td>[% P.input_tag("reference", reference_name, style="width: 250px") %]</td>
   </tr>
  </table>
-</form>
-
   <p>
-   [% P.button_tag("kivi.BankTransaction.filter_templates()", LxERP.t8("Filter vendors")) %]
+   [% P.submit_tag('', LxERP.t8("Filter")) %]
+   [% P.button_tag('$("#create_invoice_window_form").resetForm()', LxERP.t8('Reset')) %]
    <a href="#" onclick="$('#create_invoice_window').dialog('close');">[% LxERP.t8("Cancel") %]</a>
   </p>
-
+</form>
   <hr>
 <div id="templates">
  [% PROCESS "bank_transactions/_template_list.html" %]
 </div>
+
+<script type="text/javascript">
+<!--
+$(function() {
+  $('#template').focus();
+});
+
+//-->
+</script>
index 51565a6..e875af6 100644 (file)
@@ -50,7 +50,9 @@
   </tr>
 
   <tr valign="top">
-   <th align="right" nowrap>[% LxERP.t8("Message") %]</th>
+   <th align="right" nowrap>[% LxERP.t8("Message") %]
+    <sup> [% L.link("generictranslations.pl?action=edit_email_strings", "1)", title=LxERP.t8('Tired of copying always nice phrases for this message? Click here to use the new preset message option!'), target="_blank") %]</sup>
+  </th>
    <td>[% L.textarea_tag("email_form.message", email_form.message, rows="15" cols="80" wrap="soft") %]</td>
   </tr>
 
index 1f537ac..9ff22ad 100644 (file)
@@ -1,9 +1,9 @@
 [%- USE HTML -%][%- USE LxERP %][%- USE T8 %]
 [%- BLOCK output %]
  <div id="flash_[% type %]" class="flash_message_[% type %]"[% IF !messages || !messages.size %] style="display: none"[% END %]>
-  <a href='#' style='float:right' 
-     onclick='$("#flash_[% type %]_content").empty();$("#flash_[% type %]_detail").empty();$("#flash_[% type %]").hide()'> 
-     <img src='image/close.png' border='0' alt='[% 'Close Flash' | $T8 %]'></a> 
+  <a href='#' style='float:right'
+     onclick='$("#flash_[% type %]_content").empty();$("#flash_[% type %]_detail").empty();$("#flash_[% type %]").hide()'>
+     <img src='image/close.png' border='0' alt='[% 'Close Flash' | $T8 %]'></a>
   <span class="flash_title">[%- title %]:</span>
   <span id="flash_[% type %]_content">
    [% FOREACH message = messages %]
@@ -17,8 +17,8 @@
   <div id="flash_detail_[% type %]" style="display: none">
     <br>
     <span id="flash_[% type %]_detail"></span><br>
-    <a href='#' style='float:left' 
-      onclick='$("#flash_detail_[% type %]").hide()'> 
+    <a href='#' style='float:left'
+      onclick='$("#flash_detail_[% type %]").hide()'>
       <img src='image/close.png' border='0' alt='[% 'Close Details' | $T8 %]'></a><br>
   </div>
  </div>
index 441d83d..ae9c13e 100644 (file)
       </table>
     </td>
   </tr>
-  [% IF ALL_DEPARTMENTS.as_list.size %]
-  <tr>
-    <th align=left>[% 'Department' | $T8 %]</th>
-  </tr>
   <tr>
-    <td>
-       <table>
-        <tr>
-          <td align=left>[% 'Department' | $T8 %]</td>
-          <td align=left></td>
-          <td>[% L.select_tag('department_id', ALL_DEPARTMENTS, title_key = 'description', with_empty = 1) %]</td>
-        </tr>
-       </table>
-    </td>
+   <td><hr size=1 noshade></td>
+   </tr>
+   <tr>
+     <td>
+        <table>
+         <tr>
+           <td align=left>[% 'Gldate' | $T8 %] [% 'From' | $T8 %]</td>
+           <td align=left></td>
+           <td>[% L.date_tag('gldatefrom') %]</td>
+         </tr>
+        </table>
+     </td>
+   </tr>
+   <tr>
+   <tr>
+    <td><hr size=1 noshade></td>
+   </tr>
+  [% IF ALL_DEPARTMENTS.as_list.size %]
+   <tr>
+     <td>
+        <table>
+         <tr>
+           <td align=left>[% 'Department' | $T8 %]</td>
+           <td align=left></td>
+           <td>[% L.select_tag('department_id', ALL_DEPARTMENTS, title_key = 'description', with_empty = 1) %]</td>
+         </tr>
+        </table>
+     </td>
+   </tr>
+   <tr>
+  <td><hr size=3 noshade></td>
   </tr>
   [% END %]
-  <tr>
-    <td><hr size=3 noshade></td>
-  </tr>
 </table>
 
 <input type=hidden name=beraternr value="[% beraternr %]">
index f3c0c95..2f19588 100644 (file)
   <tr>
    <th align="right">[% 'Type' | $T8 %]</th>
    <td>
-     [% L.checkbox_tag('filter.part.type[]', checked=filter.part.type_.part,     value='part',     label=LxERP.t8('Part')) %]
-     [% L.checkbox_tag('filter.part.type[]', checked=filter.part.type_.service,  value='service',  label=LxERP.t8('Service')) %]
-     [% L.checkbox_tag('filter.part.type[]', checked=filter.part.type_.assembly, value='assembly', label=LxERP.t8('Assembly')) %]
+     [% L.checkbox_tag('filter.part.part_type[]', checked=filter.part.part_type_.part,     value='part',     label=LxERP.t8('Part')) %]
+     [% L.checkbox_tag('filter.part.part_type[]', checked=filter.part.part_type_.service,  value='service',  label=LxERP.t8('Service')) %]
+     [% L.checkbox_tag('filter.part.part_type[]', checked=filter.part.part_type_.assembly, value='assembly', label=LxERP.t8('Assembly')) %]
+     [%- IF INSTANCE_CONF.get_feature_experimental %]
+       [% L.checkbox_tag('filter.part.part_type[]', checked=filter.part.part_type_.assortment, value='assortment', label=LxERP.t8('Assortment')) %]
+     [% END %]
    </td>
   </tr>
   <tr>
index 307ca32..3661464 100644 (file)
   <tr>
    <th align="right">[% 'Type' | $T8 %]</th>
    <td>
-     [% L.checkbox_tag('filter.part.type[]', checked=filter.part.type_.part,     value='part',     label=LxERP.t8('Part')) %]
-     [% L.checkbox_tag('filter.part.type[]', checked=filter.part.type_.service,  value='service',  label=LxERP.t8('Service')) %]
-     [% L.checkbox_tag('filter.part.type[]', checked=filter.part.type_.assembly, value='assembly', label=LxERP.t8('Assembly')) %]
+     [% L.checkbox_tag('filter.part.part_type[]', checked=filter.part.part_type_.part,     value='part',     label=LxERP.t8('Part')) %]
+     [% L.checkbox_tag('filter.part.part_type[]', checked=filter.part.part_type_.service,  value='service',  label=LxERP.t8('Service')) %]
+     [% L.checkbox_tag('filter.part.part_type[]', checked=filter.part.part_type_.assembly, value='assembly', label=LxERP.t8('Assembly')) %]
+     [%- IF INSTANCE_CONF.get_feature_experimental %]
+       [% L.checkbox_tag('filter.part.part_type[]', checked=filter.part.part_type_.assortment, value='assortment', label=LxERP.t8('Assortment')) %]
+     [% END %]
    </td>
   </tr>
  </table>
index 06ef6da..62ba79f 100644 (file)
          <th align="right">[% 'Contact Person' | $T8 %]</th>
          <td>
           [%- IF delivered %]
-          <input type="hidden" name="cp_id" value="[% HTML.escape(cp_id) %]">
-          [%- IF cp_id == row.cp_id %]
-          [%- HTML.escape(row.cp_name) %][%- IF row.cp_abteilung %] ([% HTML.escape(row.cp_abteilung) %])[% END -%]
-          [%- END %]
+            [% L.hidden_tag("cp_id", cp_id) %]
+            [% HTML.escape(CONTACT_OBJ.full_name) %][% IF CONTACT_OBJ.cp_abteilung %] ([% HTML.escape(CONTACT_OBJ.cp_abteilung) %])[% END %]
           [%- ELSE %]
             [% L.select_tag('cp_id', ALL_CONTACTS, default = cp_id, value_key = 'cp_id', title_key = 'full_name_dep', with_empty = 1, style='width: 250px') %]
           [%- END %]
diff --git a/templates/webpages/generictranslations/edit_email_strings.html b/templates/webpages/generictranslations/edit_email_strings.html
new file mode 100644 (file)
index 0000000..c24a8c8
--- /dev/null
@@ -0,0 +1,37 @@
+[%- USE T8 %]
+[%- USE HTML %]
+<h1>[% HTML.escape(title) %]</h1>
+[%- IF message %]
+ <p>
+ [% HTML.escape(message) %]
+ </p>
+[%- END %]
+ <form method="post" action="generictranslations.pl" id="form">
+  <table>
+   [%- FOREACH mail_string IN MAIL_STRINGS.keys.sort %]
+    <tr>
+      <th class="listheading">&nbsp;</th>
+      <th class="listheading">[% MAIL_STRINGS.$mail_string %]</th>
+     </tr>
+
+     [%- FOREACH language = LANGUAGES %]
+     <tr>
+      <td>
+       [%- IF language.id == 'default' %]
+       [% 'Default (no language selected)' | $T8 %]
+       [%- ELSE %]
+       [%- HTML.escape(language.description) %]
+       [%- END %]
+      </td>
+      <td>
+       [%- IF mail_string.search('preset') %]
+        <textarea name="translation__[% language.id %]__[% mail_string %]" rows="4" cols="60">[% HTML.escape(language.$mail_string) %]</textarea>
+       [%- ELSE %]
+        <input name="translation__[% language.id %]__[% mail_string %]" size="40" value="[% HTML.escape(language.$mail_string) %]">
+       [%- END %]
+      </td>
+     </tr>
+     [%- END %]
+   [%- END %]
+  </table>
+ </form>
index 51d4bc1..c775680 100644 (file)
           <td>[%- L.checkbox_tag('l_name', label=LxERP.t8('Name in Selected Records'), value='Y') %]</td>
           <td>[%- L.checkbox_tag('l_soldtotal', label=LxERP.t8('Qty in Selected Records'), value='Y') %]</td>
          </tr>
+         <tr>
+          <td>[%- L.checkbox_tag('l_warehouse', label=LxERP.t8('Default Warehouse'), value='Y') %]</td>
+          <td>[%- L.checkbox_tag('l_bin', label=LxERP.t8('Default Bin'), value='Y') %]</td>
+         </tr>
 
          [% CUSTOM_VARIABLES_INCLUSION_CODE %]
         </table>