From: Moritz Bunkus Date: Thu, 24 Aug 2017 06:20:39 +0000 (+0200) Subject: Merge pull request #11 from freiphone/patch-3 X-Git-Tag: release-3.5.4~866 X-Git-Url: http://wagnertech.de/gitweb/gitweb.cgi/mfinanz.git/commitdiff_plain/d51b3011755b4a7e006f433d9fe5836a22a69638?hp=2392ce9ec754eac5c0ed8494b8d836152d37adab Merge pull request #11 from freiphone/patch-3 Einkaufsrechnungen: Bearbeiter*in & Verkäufer*in mit aktueller Benutz… --- diff --git a/SL/ClientJS.pm b/SL/ClientJS.pm index 6ef639d74..a134cad9b 100644 --- a/SL/ClientJS.pm +++ b/SL/ClientJS.pm @@ -75,7 +75,7 @@ my %supported_methods = ( # ## jQuery UI dialog plugin ## pattern: $().dialog('') - # Opening and closing and closing a popup + # Opening and closing a popup 'dialog:open' => 1, # kivi.popup_dialog() '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, C, C =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) there's a script that can parse this file's C<%supported_methods> definition and generate the file C accordingly. The steps are: diff --git a/SL/Controller/BankTransaction.pm b/SL/Controller/BankTransaction.pm index 038267391..e77240621 100644 --- a/SL/Controller/BankTransaction.pm +++ b/SL/Controller/BankTransaction.pm @@ -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) = @_; diff --git a/SL/Controller/CsvImport/Base.pm b/SL/Controller/CsvImport/Base.pm index 43906bf80..85eb6fa67 100644 --- a/SL/Controller/CsvImport/Base.pm +++ b/SL/Controller/CsvImport/Base.pm @@ -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, diff --git a/SL/Controller/CsvImport/BaseMulti.pm b/SL/Controller/CsvImport/BaseMulti.pm index d4bb7455a..b97539489 100644 --- a/SL/Controller/CsvImport/BaseMulti.pm +++ b/SL/Controller/CsvImport/BaseMulti.pm @@ -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, diff --git a/SL/Controller/DeliveryValueReport.pm b/SL/Controller/DeliveryValueReport.pm index 20e02869e..0a714d2a2 100644 --- a/SL/Controller/DeliveryValueReport.pm +++ b/SL/Controller/DeliveryValueReport.pm @@ -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]; + } } } diff --git a/SL/Controller/Helper/GetModels/Paginated.pm b/SL/Controller/Helper/GetModels/Paginated.pm index d900c18be..86b248c20 100644 --- a/SL/Controller/Helper/GetModels/Paginated.pm +++ b/SL/Controller/Helper/GetModels/Paginated.pm @@ -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() } } : (); diff --git a/SL/Controller/Helper/ReportGenerator.pm b/SL/Controller/Helper/ReportGenerator.pm index 5a83be4b0..41f0c6c36 100644 --- a/SL/Controller/Helper/ReportGenerator.pm +++ b/SL/Controller/Helper/ReportGenerator.pm @@ -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 { diff --git a/SL/Controller/Order.pm b/SL/Controller/Order.pm index bc7fc5fc6..ab4401ef8 100644 --- a/SL/Controller/Order.pm +++ b/SL/Controller/Order.pm @@ -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', diff --git a/SL/DATEV.pm b/SL/DATEV.pm index c39ba3407..c5e081a80 100644 --- a/SL/DATEV.pm +++ b/SL/DATEV.pm @@ -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); diff --git a/SL/DB/BankTransaction.pm b/SL/DB/BankTransaction.pm index 6ad0348c7..e4cf67cd3 100644 --- a/SL/DB/BankTransaction.pm +++ b/SL/DB/BankTransaction.pm @@ -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'} . ') '; diff --git a/SL/DB/Helper/CustomVariables.pm b/SL/DB/Helper/CustomVariables.pm index d23e46e97..e80d6fda8 100644 --- a/SL/DB/Helper/CustomVariables.pm +++ b/SL/DB/Helper/CustomVariables.pm @@ -422,7 +422,7 @@ passed to import. =item C -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 has included the helper L \@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 +=head2 C 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 +=head2 C Creates a new vendor. diff --git a/SL/Dev/File.pm b/SL/Dev/File.pm index ff1ea116d..62228ea9e 100644 --- a/SL/Dev/File.pm +++ b/SL/Dev/File.pm @@ -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 -=head2 C - =head1 AUTHOR Martin Helmling Emartin.helmling@opendynamic.deE diff --git a/SL/Dev/Inventory.pm b/SL/Dev/Inventory.pm index e7aaee5e8..4432d86c8 100644 --- a/SL/Dev/Inventory.pm +++ b/SL/Dev/Inventory.pm @@ -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; diff --git a/SL/Dev/Part.pm b/SL/Dev/Part.pm index 45976b6f2..aa69c0d7b 100644 --- a/SL/Dev/Part.pm +++ b/SL/Dev/Part.pm @@ -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 +=head2 C Creates a new part (part_type = part). -Minimal usage, default values, without saving to database: +=head2 C + +Creates a new service (part_type = service). - my $part = SL::Dev::Part::create_part(); +=head2 C -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 +=over 2 -Creates a new service (part_type = service). +=item * C -Minimal usage, default values, without saving to database: +The number of automatically created assembly parts. - my $part = SL::Dev::Part::create_service(); +=item * C -=head2 C +the partnumber of the assembly -Create a new assembly (part_type = assembly). +=item * C -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 +=head2 C 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 + +The number of automatically created assembly parts. - my $assortment = SL::Dev::Part::create_assortment->save; +=item * C + +the partnumber of the assortment + +=item * C + +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 diff --git a/SL/Dev/Payment.pm b/SL/Dev/Payment.pm index e1acac681..0fdb3a4aa 100644 --- a/SL/Dev/Payment.pm +++ b/SL/Dev/Payment.pm @@ -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) = @_; diff --git a/SL/Dev/Record.pm b/SL/Dev/Record.pm index 649c6b2de..5fbbd8738 100644 --- a/SL/Dev/Record.pm +++ b/SL/Dev/Record.pm @@ -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 ], diff --git a/SL/Form.pm b/SL/Form.pm index 470809f77..f29c60128 100644 --- a/SL/Form.pm +++ b/SL/Form.pm @@ -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(); diff --git a/SL/Helper/ShippedQty.pm b/SL/Helper/ShippedQty.pm index e9c8e4e8b..1b3fe81d7 100644 --- a/SL/Helper/ShippedQty.pm +++ b/SL/Helper/ShippedQty.pm @@ -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 + +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 for the returned format. + =back =item C @@ -472,6 +487,13 @@ was found it will be set to zero. C is guaranteed only to be the correct boolean value, but not any specific value. +Note: C will avoid loading unnecessary objects. This means if it is +called with an Order object that has not loaded its orderitems yet, only +C will be set in the Order object. A subsequent C<< +$order->orderitems->[0]->{delivered} >> will return C, and C<< +$order->orderitems->[0]->shipped_qty >> will invoke another implicit +calculation. + =item C Valid after L. Returns a hasref with shipped qtys by orderitems id. @@ -483,6 +505,32 @@ linked elements were found. Valid after L. Returns a hashref with a delivered flag by order id. +=item C + +Valid after L with C 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 diff --git a/SL/IS.pm b/SL/IS.pm index bbf53e31a..1df0eb2f9 100644 --- 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}; diff --git a/SL/WH.pm b/SL/WH.pm index 895db95e5..bcdffa511 100644 --- 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 "

" . $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, diff --git a/bin/mozilla/ap.pl b/bin/mozilla/ap.pl index 790cb69c0..fe978cb2c 100644 --- a/bin/mozilla/ap.pl +++ b/bin/mozilla/ap.pl @@ -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!')); } diff --git a/bin/mozilla/ar.pl b/bin/mozilla/ar.pl index 9d1784f78..ec028e886 100644 --- a/bin/mozilla/ar.pl +++ b/bin/mozilla/ar.pl @@ -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(); } diff --git a/bin/mozilla/do.pl b/bin/mozilla/do.pl index 4652e7c00..1a304e797 100644 --- a/bin/mozilla/do.pl +++ b/bin/mozilla/do.pl @@ -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"}}; diff --git a/bin/mozilla/generictranslations.pl b/bin/mozilla/generictranslations.pl index 5d945566f..d3341f212 100644 --- a/bin/mozilla/generictranslations.pl +++ b/bin/mozilla/generictranslations.pl @@ -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; diff --git a/bin/mozilla/gl.pl b/bin/mozilla/gl.pl index 676611682..6dfa457cd 100644 --- a/bin/mozilla/gl.pl +++ b/bin/mozilla/gl.pl @@ -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(); } diff --git a/bin/mozilla/io.pl b/bin/mozilla/io.pl index 5bd5a25a6..778c718f4 100644 --- a/bin/mozilla/io.pl +++ b/bin/mozilla/io.pl @@ -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, }; diff --git a/doc/changelog b/doc/changelog index b36292e7e..2ee4ca83e 100644 --- a/doc/changelog +++ b/doc/changelog @@ -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 diff --git a/locale/de/all b/locale/de/all index f742c36ab..7b33f1d03 100755 --- a/locale/de/all +++ b/locale/de/all @@ -36,6 +36,7 @@ $self->{texts} = { '...done' => '...fertig', '...on the TODO list' => '...auf der Aufgabenliste', '0% tax with taxkey' => '0% Steuer mit Steuerschlü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ü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 überprü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', diff --git a/locale/de/special_chars b/locale/de/special_chars index 7747ed264..6041443dd 100644 --- a/locale/de/special_chars +++ b/locale/de/special_chars @@ -33,7 +33,7 @@ order=< > \n \n=
[Template/LaTeX] -order=\\ & \n \r " $ % _ # ^ { } < > £ ± ² ³ ° § ® © \xad \xa0 ➔ → ← | − ≤ ≥ ‐ ​ Ω μ Δ +order=\\ & \n \r " $ % _ # ^ { } < > £ ± ² ³ ° § ® © \xad \xa0 ➔ → ← | − ≤ ≥ ‐ ​ Ω μ Δ ~ \\=\\textbackslash\s = "='' @@ -74,6 +74,7 @@ _=\\_ μ={\\textmu} Δ=$\\Delta$ Ω=$\\Omega$ +~={\\textasciitilde} [Template/OpenDocument] order=& < > " ' \x80 \n \r diff --git a/locale/en/special_chars b/locale/en/special_chars index 20dbcdadd..b363c90dc 100644 --- a/locale/en/special_chars +++ b/locale/en/special_chars @@ -29,7 +29,7 @@ order=< > \n \n=
[Template/LaTeX] -order=\\ & \n \r " $ % _ # ^ { } < > £ ± ² ³ ° § ® © ‐ Ω ~ μ Δ +order=\\ & \n \r " $ % _ # ^ { } < > £ ± ² ³ ° § ® © \xad \xa0 ➔ → ← | − ≤ ≥ ‐ ​ Ω μ Δ ~ \\=\\textbackslash\s = "='' @@ -38,7 +38,9 @@ $=\\$ =$\\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 diff --git a/menus/user/00-erp.yaml b/menus/user/00-erp.yaml index c520c4b68..eae8faf77 100644 --- a/menus/user/00-erp.yaml +++ b/menus/user/00-erp.yaml @@ -1200,6 +1200,13 @@ 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 diff --git a/scripts/console b/scripts/console index 0c06dbf5a..cb08e2f03 100755 --- a/scripts/console +++ b/scripts/console @@ -288,6 +288,12 @@ Print the manual page and exit. Log in as C. The default is to use the value from the configuration file and C if none is set there. +=item B<-c>, B<--client>=C + +Use the database for client C. C 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 Use C as the log file. The default is to use the value from diff --git a/t/background_job/create_periodic_invoices.t b/t/background_job/create_periodic_invoices.t index 783f7c59c..1ce5fe600 100644 --- a/t/background_job/create_periodic_invoices.t +++ b/t/background_job/create_periodic_invoices.t @@ -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) ]) }; diff --git a/t/bank/bank_transactions.t b/t/bank/bank_transactions.t index fd9757518..69028b143 100644 --- a/t/bank/bank_transactions.t +++ b/t/bank/bank_transactions.t @@ -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; diff --git a/t/controllers/csvimport/artransactions.t b/t/controllers/csvimport/artransactions.t index dd0a8f1e3..670b5aba2 100644 --- a/t/controllers/csvimport/artransactions.t +++ b/t/controllers/csvimport/artransactions.t @@ -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}}; diff --git a/t/controllers/csvimport/parts.t b/t/controllers/csvimport/parts.t index b7f1a9c61..86a3c78ca 100644 --- a/t/controllers/csvimport/parts.t +++ b/t/controllers/csvimport/parts.t @@ -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}}; diff --git a/t/controllers/financial_controlling/sales_order_with_periodic_invoices_config.t b/t/controllers/financial_controlling/sales_order_with_periodic_invoices_config.t index 8541cb7be..5414e9031 100644 --- a/t/controllers/financial_controlling/sales_order_with_periodic_invoices_config.t +++ b/t/controllers/financial_controlling/sales_order_with_periodic_invoices_config.t @@ -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 ], diff --git a/t/controllers/financial_overview/sales_orders.t b/t/controllers/financial_overview/sales_orders.t index abfcf7e6c..4c613eeaf 100644 --- a/t/controllers/financial_overview/sales_orders.t +++ b/t/controllers/financial_overview/sales_orders.t @@ -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'), }, diff --git a/t/controllers/project/project_linked_records.t b/t/controllers/project/project_linked_records.t index 7f7a08118..5ca1224f1 100644 --- a/t/controllers/project/project_linked_records.t +++ b/t/controllers/project/project_linked_records.t @@ -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), ] ); diff --git a/t/datev/invoices.t b/t/datev/invoices.t index 5a33d5f0c..05e9aa4dd 100644 --- a/t/datev/invoices.t +++ b/t/datev/invoices.t @@ -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; - diff --git a/t/db_helper/convert_invoice.t b/t/db_helper/convert_invoice.t index ead4024ce..b1ac4c24f 100644 --- a/t/db_helper/convert_invoice.t +++ b/t/db_helper/convert_invoice.t @@ -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' => '
  • fett
  • und
  • mit
  • bullets
  •  
', +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' => '
  • fett
  • und
  • mit
  • bullets
  •  
', + orderitems => [ + create_delivery_order_item( + part => $parts[0], + discount => '0.25', + lastcost => '49.95000', + longdescription => "
  1. 27
  2. 28
  3. 29
  4. asdf
  5. asdf
  6. oben

kommt nicht mehr vor

", + 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' => "
  1. 27
  2. 28
  3. 29
  4. asdf
  5. asdf
  6. oben

kommt nicht mehr vor

", - '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 '
  • fett
  • und
  • mit
  • bullets
  •  
', "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 "
  1. 27
  2. 28
  3. 29
  4. asdf
  5. asdf
  6. oben

kommt nicht mehr vor

", "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 '
  • fett
  • und
  • mit
  • bullets
  •  
', "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, "
  1. 27
  2. 28
  3. 29
  4. asdf
  5. asdf
  6. oben

kommt nicht mehr vor

", +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 , "
  1. 27
  2. 28
  3. 29
  4. asdf
  5. asdf
  6. oben

kommt nicht mehr vor

", "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(); diff --git a/t/db_helper/payment.t b/t/db_helper/payment.t index ac2e44906..b40cf7432 100644 --- a/t/db_helper/payment.t +++ b/t/db_helper/payment.t @@ -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 ], diff --git a/t/db_helper/price_tax_calculator.t b/t/db_helper/price_tax_calculator.t index 22f4165dd..c8884018e 100644 --- a/t/db_helper/price_tax_calculator.t +++ b/t/db_helper/price_tax_calculator.t @@ -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 ], diff --git a/t/db_helper/with_transaction.t b/t/db_helper/with_transaction.t index 3071cc3d3..cc3fc0ece 100644 --- a/t/db_helper/with_transaction.t +++ b/t/db_helper/with_transaction.t @@ -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'; diff --git a/t/file/filesystem.t b/t/file/filesystem.t index d35a95faf..8797a1cda 100644 --- a/t/file/filesystem.t +++ b/t/file/filesystem.t @@ -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); }; } diff --git a/t/helper/shipped_qty.t b/t/helper/shipped_qty.t index 75a52362c..dc2f9dd3b 100644 --- a/t/helper/shipped_qty.t +++ b/t/helper/shipped_qty.t @@ -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 { diff --git a/t/part/assembly.t b/t/part/assembly.t index 197dfb580..6500dfa5c 100644 --- a/t/part/assembly.t +++ b/t/part/assembly.t @@ -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; diff --git a/t/part/assortment.t b/t/part/assortment.t index 3d6705e3a..46e35de3d 100644 --- a/t/part/assortment.t +++ b/t/part/assortment.t @@ -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"); diff --git a/t/part/stock.t b/t/part/stock.t index 583aefe4b..39e575665 100644 --- a/t/part/stock.t +++ b/t/part/stock.t @@ -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"); diff --git a/t/wh/transfer.t b/t/wh/transfer.t index 2a05c55d3..b85cd68ce 100644 --- a/t/wh/transfer.t +++ b/t/wh/transfer.t @@ -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); diff --git a/templates/webpages/bank_transactions/_template_list.html b/templates/webpages/bank_transactions/_template_list.html index 9bd54e333..4e354515f 100644 --- a/templates/webpages/bank_transactions/_template_list.html +++ b/templates/webpages/bank_transactions/_template_list.html @@ -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') %]: @@ -11,7 +13,7 @@ - [% FOREACH template = TEMPLATES %] + [% FOREACH template = TEMPLATES_AP %] @@ -22,5 +24,32 @@
[% P.link(SELF.load_ap_record_template_url(template), template.template_name) %] [% HTML.escape(template.vendor.name) %]
[% ELSE %] -

[% LxERP.t8('No template was found.') %]

+

[% LxERP.t8('No AP template was found.') %]

+[% END %] + +[% IF TEMPLATES_GL.size %] + [% LxERP.t8('GL template suggestions') %]: + + + + + + + + + + + + [% FOREACH template = TEMPLATES_GL %] + + + + + + + [% END %] + +
[% LxERP.t8('Description') %][% LxERP.t8('Reference') %][% LxERP.t8('Employee') %][% LxERP.t8('Template date') %]
[% P.link(SELF.load_gl_record_template_url(template), template.template_name) %][% HTML.escape(template.reference) %][% HTML.escape(template.employee.name || template.employee.login) %][% HTML.escape(template.itime_as_date) %]
+[% ELSE %] +

[% LxERP.t8('No GL template was found.') %]

[% END %] diff --git a/templates/webpages/bank_transactions/create_invoice.html b/templates/webpages/bank_transactions/create_invoice.html index 25b32b395..45b1818ec 100644 --- a/templates/webpages/bank_transactions/create_invoice.html +++ b/templates/webpages/bank_transactions/create_invoice.html @@ -25,27 +25,42 @@
-[% LxERP.t8('Vendor filter for AP transaction templates') %]: -
+ [% 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) %] + + + + - + + + + +
[%- LxERP.t8("Template Description") %][% P.input_tag("template", template_name, style="width: 250px") %]
[%- LxERP.t8("Vendor") %][% P.input_tag("vendor", vendor_name, class="initial_focus", style="width: 250px") %][% P.input_tag("vendor", vendor_name, style="width: 250px") %]
[%- LxERP.t8("Reference") %][% P.input_tag("reference", reference_name, style="width: 250px") %]
-
-

- [% 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')) %] [% LxERP.t8("Cancel") %]

- +
[% PROCESS "bank_transactions/_template_list.html" %]
+ + diff --git a/templates/webpages/common/_send_email_dialog.html b/templates/webpages/common/_send_email_dialog.html index 51565a6f3..e875af6b4 100644 --- a/templates/webpages/common/_send_email_dialog.html +++ b/templates/webpages/common/_send_email_dialog.html @@ -50,7 +50,9 @@ - [% LxERP.t8("Message") %] + [% LxERP.t8("Message") %] + [% 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") %] + [% L.textarea_tag("email_form.message", email_form.message, rows="15" cols="80" wrap="soft") %] diff --git a/templates/webpages/common/flash.html b/templates/webpages/common/flash.html index 1f537ac4a..9ff22adeb 100644 --- a/templates/webpages/common/flash.html +++ b/templates/webpages/common/flash.html @@ -1,9 +1,9 @@ [%- USE HTML -%][%- USE LxERP %][%- USE T8 %] [%- BLOCK output %] diff --git a/templates/webpages/datev/export_bewegungsdaten.html b/templates/webpages/datev/export_bewegungsdaten.html index 441d83df9..ae9c13edd 100644 --- a/templates/webpages/datev/export_bewegungsdaten.html +++ b/templates/webpages/datev/export_bewegungsdaten.html @@ -54,25 +54,40 @@ - [% IF ALL_DEPARTMENTS.as_list.size %] - - [% 'Department' | $T8 %] - - - - - - - - -
[% 'Department' | $T8 %][% L.select_tag('department_id', ALL_DEPARTMENTS, title_key = 'description', with_empty = 1) %]
- +
+ + + + + + + + + +
[% 'Gldate' | $T8 %] [% 'From' | $T8 %][% L.date_tag('gldatefrom') %]
+ + + + +
+ + [% IF ALL_DEPARTMENTS.as_list.size %] + + + + + + + + +
[% 'Department' | $T8 %][% L.select_tag('department_id', ALL_DEPARTMENTS, title_key = 'description', with_empty = 1) %]
+ + + +
[% END %] - -
- diff --git a/templates/webpages/delivery_plan/_filter.html b/templates/webpages/delivery_plan/_filter.html index f3c0c9569..2f195885e 100644 --- a/templates/webpages/delivery_plan/_filter.html +++ b/templates/webpages/delivery_plan/_filter.html @@ -81,9 +81,12 @@ [% 'Type' | $T8 %] - [% 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 %] diff --git a/templates/webpages/delivery_value_report/_filter.html b/templates/webpages/delivery_value_report/_filter.html index 307ca32ac..3661464f3 100644 --- a/templates/webpages/delivery_value_report/_filter.html +++ b/templates/webpages/delivery_value_report/_filter.html @@ -81,9 +81,12 @@ [% 'Type' | $T8 %] - [% 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 %] diff --git a/templates/webpages/do/form_header.html b/templates/webpages/do/form_header.html index 06ef6da05..62ba79f4a 100644 --- a/templates/webpages/do/form_header.html +++ b/templates/webpages/do/form_header.html @@ -147,10 +147,8 @@ [% 'Contact Person' | $T8 %] [%- IF delivered %] - - [%- 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 index 000000000..c24a8c831 --- /dev/null +++ b/templates/webpages/generictranslations/edit_email_strings.html @@ -0,0 +1,37 @@ +[%- USE T8 %] +[%- USE HTML %] +

[% HTML.escape(title) %]

+[%- IF message %] +

+ [% HTML.escape(message) %] +

+[%- END %] +
+ + [%- FOREACH mail_string IN MAIL_STRINGS.keys.sort %] + + + + + + [%- FOREACH language = LANGUAGES %] + + + + + [%- END %] + [%- END %] +
 [% MAIL_STRINGS.$mail_string %]
+ [%- IF language.id == 'default' %] + [% 'Default (no language selected)' | $T8 %] + [%- ELSE %] + [%- HTML.escape(language.description) %] + [%- END %] + + [%- IF mail_string.search('preset') %] + + [%- ELSE %] + + [%- END %] +
+
diff --git a/templates/webpages/ic/search.html b/templates/webpages/ic/search.html index 51d4bc10f..c77568072 100644 --- a/templates/webpages/ic/search.html +++ b/templates/webpages/ic/search.html @@ -224,6 +224,10 @@ [%- L.checkbox_tag('l_name', label=LxERP.t8('Name in Selected Records'), value='Y') %] [%- L.checkbox_tag('l_soldtotal', label=LxERP.t8('Qty in Selected Records'), value='Y') %] + + [%- L.checkbox_tag('l_warehouse', label=LxERP.t8('Default Warehouse'), value='Y') %] + [%- L.checkbox_tag('l_bin', label=LxERP.t8('Default Bin'), value='Y') %] + [% CUSTOM_VARIABLES_INCLUSION_CODE %]