# ## jQuery UI dialog plugin ## pattern: $(<TARGET>).dialog('<FUNCTION>')
- # Opening and closing and closing a popup
+ # Opening and closing a popup
'dialog:open' => 1, # kivi.popup_dialog(<TARGET>)
'dialog:close' => 1,
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.
=head1 ADDING SUPPORT FOR ADDITIONAL FUNCTIONS
-In order not having to maintain two files (this one and
+In order to not have to maintain two files (this one and
C<js/client_js.js>) there's a script that can parse this file's
C<%supported_methods> definition and generate the file
C<js/client_js.js> accordingly. The steps are:
@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' },
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
## 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
# 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);
: 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',
$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',
$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,
);
}
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} //= {};
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 });
}
-sub is_collective_transaction {
- my ($self, $bt) = @_;
- return $bt->transaction_code eq "191";
-}
-
sub save_single_bank_transaction {
my ($self, %params) = @_;
);
}
+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) = @_;
$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,
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,
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;
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'),
);
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);
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 },
}
}
+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) = @_;
$_->{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];
+ }
}
}
# 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() } }
: ();
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 {
}
}
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',
$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
WHERE (ar.id IS NOT NULL)
AND $fromto
$trans_id_filter
+ $ar_itime_filter
$ar_department_id_filter
$filter
WHERE (ap.id IS NOT NULL)
AND $fromto
$trans_id_filter
+ $ap_itime_filter
$ap_department_id_filter
$filter
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);
return [ @linked_invoices ];
}
+sub is_batch_transaction {
+ ($_[0]->transaction_code // '') eq "191";
+}
+
+
sub get_agreement_with_invoice {
my ($self, $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;
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;
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;
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;
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;
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
$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 }) ) {
# 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'} . ') ';
=item C<cvars_by_config>
-Thi will return a list of CVars with the following changes over the standard accessor:
+This will return a list of CVars with the following changes over the standard accessor:
=over 4
Prolonged use has shown that users expect all methods to be present or none.
Future versions of this will likely remove the optional aliasing.
-=item * Sematics need to be updated
+=item * Semantics need to be updated
There are a few transitions that are currently neither supported nor well
-defined, most of the happening when the config of a cvar gets changed which
-instances are already saved. This needs to be cleaned up.
+defined, most of them happening when the config of a cvar gets changed, but
+whose instances have already been saved. This needs to be cleaned up.
=back
use strict;
+use Exporter;
use SL::Dev::Part;
use SL::Dev::CustomerVendor;
use SL::Dev::Inventory;
use SL::Dev::Record;
use SL::Dev::Payment;
+sub import {
+ no strict "refs";
+ for (qw(Part CustomerVendor Inventory Record Payment)) {
+ Exporter::export_to_level("SL::Dev::$_", 1, @_);
+ }
+}
+
+
1;
__END__
use strict;
use base qw(Exporter);
-our @EXPORT = qw(create_customer create_vendor);
+our @EXPORT_OK = qw(new_customer new_vendor);
+our %EXPORT_TAGS = (ALL => \@EXPORT_OK);
use SL::DB::TaxZone;
use SL::DB::Currency;
use SL::DB::Customer;
-sub create_customer {
+sub new_customer {
my (%params) = @_;
my $taxzone = _check_taxzone(delete $params{taxzone_id});
return $customer;
}
-sub create_vendor {
+sub new_vendor {
my (%params) = @_;
my $taxzone = _check_taxzone(delete $params{taxzone_id});
=head1 FUNCTIONS
-=head2 C<create_customer %PARAMS>
+=head2 C<new_customer %PARAMS>
Creates a new customer.
If neither taxzone_id or currency_id (both are NOT NULL) are passed as params
then default values are used.
-=head2 C<create_vendor %PARAMS>
+=head2 C<new_vendor %PARAMS>
Creates a new vendor.
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';
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},
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__
=head2 C<create_created %PARAMS>
-=head2 C<delete_all>
-
=head1 AUTHOR
Martin Helmling E<lt>martin.helmling@opendynamic.deE<gt>
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;
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(
return $part;
}
-sub create_service {
+sub new_service {
my (%params) = @_;
my $part = SL::DB::Part->new_service(
return $part;
}
-sub create_assembly {
+sub new_assembly {
my (%params) = @_;
my $assnumber = delete $params{assnumber};
$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,
return $assembly;
}
-sub create_assortment {
+sub new_assortment {
my (%params) = @_;
my $assnumber = delete $params{assnumber};
$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,
SL::Dev::Part - create part objects for testing, with minimal defaults
+=head1 SYNOPSIS
+
+ use SL::DEV::Part qw(new_part new_assembly new_service new_assortment);
+
+ # simple default objects
+ my $part = new_part()->save;
+ my $assembly = new_assembly()->save;
+ my $service = new_service()->save;
+ my $assortment = new_assortment()->save;
+
+ # pass additional params to the generated object
+ # see individual functions for special parameters
+ my $part = new_part(
+ partnumber => 'Test 001',
+ warehouse_id => $bin->warehouse->id,
+ bin_id => $bin->id,
+ );
+
=head1 FUNCTIONS
-=head2 C<create_part %PARAMS>
+=head2 C<new_part %PARAMS>
Creates a new part (part_type = part).
-Minimal usage, default values, without saving to database:
+=head2 C<new_service %PARAMS>
+
+Creates a new service (part_type = service).
- my $part = SL::Dev::Part::create_part();
+=head2 C<new_assembly %PARAMS>
-Create a test part with a default warehouse and bin and save it:
+Create a new assembly (part_type = assembly).
- my $wh = SL::Dev::Inventory::create_warehouse_and_bins()->save;
- my $part1 = SL::Dev::Part::create_part(partnumber => 'a123',
- description => 'Testpart 1',
- warehouse_id => $wh->id,
- bin_id => $wh->bins->[0]->id,
- )->save;
+Special params:
-=head2 C<create_service %PARAMS>
+=over 2
-Creates a new service (part_type = service).
+=item * C<number_of_parts>
-Minimal usage, default values, without saving to database:
+The number of automatically created assembly parts.
- my $part = SL::Dev::Part::create_service();
+=item * C<assnumber>
-=head2 C<create_assembly %PARAMS>
+the partnumber of the assembly
-Create a new assembly (part_type = assembly).
+=item * C<partnumber>
-Params: assnumber: the partnumber of the assembly
- partnumber: the partnumber of the first assembly part to be created
+the partnumber of the first assembly part to be created
-By default 3 parts (p1, p2, p3) are created and saved as an assembly (as1).
+=back
- my $assembly = SL::Dev::Part::create_assembly->save;
+By default 3 parts (p1, p2, p3) are created and saved as an assembly (as1).
Create a new assembly with 10 parts, the assembly gets partnumber 'Ass1' and the
parts get partnumbers 'Testpart 1' to 'Testpart 10':
- my $assembly = SL::Dev::Part::create_assembly(number_of_parts => 10,
- partnumber => 'Testpart',
- assnumber => 'Ass1'
- )->save;
+ my $assembly = SL::Dev::Part::new_assembly(
+ number_of_parts => 10,
+ partnumber => 'Testpart',
+ assnumber => 'Ass1'
+ )->save;
Create an assembly with specific parts:
+
my $assembly_item_1 = SL::DB::Assembly->new( parts_id => $part1->id, qty => 3, position => 1);
my $assembly_item_2 = SL::DB::Assembly->new( parts_id => $part2->id, qty => 3, position => 2);
- my $assembly_part = SL::Dev::Part::create_assembly( assnumber => 'Assembly 1',
- description => 'Assembly test',
- sellprice => $part1->sellprice + $part2->sellprice,
- assembly_items => [ $assembly_item_1, $assembly_item_2 ],
- )->save;
+ my $assembly_part = new_assembly(
+ assnumber => 'Assembly 1',
+ description => 'Assembly test',
+ sellprice => $part1->sellprice + $part2->sellprice,
+ assembly_items => [ $assembly_item_1, $assembly_item_2 ],
+ );
-=head2 C<create_assortment %PARAMS>
+=head2 C<new_assortment %PARAMS>
Create a new assortment (part_type = assortment).
-By default 3 parts (p1, p2, p3) are created and saved as an assortment.
+Special params:
+
+=over 2
+
+=item * C<number_of_parts>
+
+The number of automatically created assembly parts.
- my $assortment = SL::Dev::Part::create_assortment->save;
+=item * C<assnumber>
+
+the partnumber of the assortment
+
+=item * C<partnumber>
+
+the partnumber of the first assembly part to be created
+
+=back
+
+By default 3 parts (p1, p2, p3) are created and saved as an assortment.
Create a new assortment with 10 automatically created parts using the
number_of_parts param:
- my $assortment = SL::Dev::Part::create_assortment(number_of_parts => 10)->save;
+ my $assortment = new_assortment(number_of_parts => 10)->save;
Create an assortment with a certain name and pass some assortment_item Objects
from newly created parts:
- my $part1 = SL::Dev::Part::create_part( sellprice => '7.77')->save;
- my $part2 = SL::Dev::Part::create_part( sellprice => '6.66')->save;
+ my $part1 = new_part(sellprice => 7.77)->save;
+ my $part2 = new_part(sellprice => 6.66)->save;
my $assortment_item_1 = SL::DB::AssortmentItem->new( parts_id => $part1->id, qty => 3, unit => $part1->unit, position => 1);
my $assortment_item_2 = SL::DB::AssortmentItem->new( parts_id => $part2->id, qty => 3, unit => $part2->unit, position => 2);
- my $assortment_part = SL::Dev::Part::create_assortment( assnumber => 'Assortment 1',
- description => 'assortment test',
- sellprice => (3*$part1->sellprice + 3*$part2->sellprice),
- lastcost => (3*$part1->lastcost + 3*$part2->lastcost),
- assortment_items => [ $assortment_item_1, $assortment_item_2 ],
- )->save;
+ my $assortment_part = SL::Dev::Part::new_assortment(
+ assnumber => 'Assortment 1',
+ description => 'assortment test',
+ sellprice => (3*$part1->sellprice + 3*$part2->sellprice),
+ lastcost => (3*$part1->lastcost + 3*$part2->lastcost),
+ assortment_items => [ $assortment_item_1, $assortment_item_2 ],
+ )->save;
=head1 TODO
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;
$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) = @_;
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;
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(
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
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(
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(
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(
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(
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 ];
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 ],
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();
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'
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'
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;
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;
}
}
$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;
}
}
}
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;
$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 {
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);
}
$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);
}
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 } ]
}
sub init_oi2oe { {} }
sub init_oi_qty { {} }
+sub init_matches { [] }
sub init_delivered {
my ($self) = @_;
my $d = { };
=back
+=item * C<keep_matches>
+
+Boolean. If set to true the internal matchings of OrderItems and
+DeliveryOrderItems will be kept for later postprocessing, in case you need more
+than this modules provides.
+
+See C<matches> for the returned format.
+
=back
=item C<calculate OBJECTS>
C<delivered> is guaranteed only to be the correct boolean value, but not
any specific value.
+Note: C<write_to> will avoid loading unnecessary objects. This means if it is
+called with an Order object that has not loaded its orderitems yet, only
+C<delivered> will be set in the Order object. A subsequent C<<
+$order->orderitems->[0]->{delivered} >> will return C<undef>, and C<<
+$order->orderitems->[0]->shipped_qty >> will invoke another implicit
+calculation.
+
=item C<shipped_qty>
Valid after L</calculate>. Returns a hasref with shipped qtys by orderitems id.
Valid after L</calculate>. Returns a hashref with a delivered flag by order id.
+=item C<matches>
+
+Valid after L</calculate> with C<with_matches> set. Returns an arrayref of
+individual matches. Each match is an arrayref with these fields:
+
+=over 4
+
+=item *
+
+The id of the OrderItem.
+
+=item *
+
+The id of the DeliveryOrderItem.
+
+=item *
+
+The qty that was matched between the two converted to the unit of the OrderItem.
+
+=item *
+
+A boolean flag indicating if this match was found with record_item links. If
+false, the match was made in the fill up stage.
+
+=back
+
=back
=head1 REPLACED FUNCTIONALITY
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,
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};
}
# gibt die Fehlermeldung zurück. A.) Keine Teile definiert
# B.) Artikel und Anzahl der fehlenden Teile/Dienstleistungen
- if ($kannNichtFertigen) {
- return 0;
- }
+ die "<br><br>" . $kannNichtFertigen if ($kannNichtFertigen);
# soweit alles gut. Jetzt noch die wirkliche Lagerbewegung für das Erzeugnis ausführen ...
my $transferAssemblySQL = qq|INSERT INTO inventory (parts_id, warehouse_id, bin_id, chargenumber, bestbefore,
# 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!'));
}
}
# /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();
}
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,
],
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,
],
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};
$::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));
$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);
# ... 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"}};
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();
$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 = @_;
);
}
}
+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;
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";
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};
$::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);
}
$form->error($err[$errno]);
}
- undef($form->{callback});
# saving the history
if(!exists $form->{addition} && $form->{id} ne "") {
$form->{snumbers} = qq|gltransaction_| . $form->{id};
}
# /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();
}
}
sub _update_ship {
+ return unless $::form->{id};
my $helper = SL::Helper::ShippedQty->new->calculate($::form->{id});
for my $i (1..$::form->{rowcount}) {
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,
};
- 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
'...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',
'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',
'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)',
'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',
'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',
'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',
'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',
'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.',
'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',
'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',
'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',
'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',
'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',
'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',
'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',
'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',
\n=<br>
[Template/LaTeX]
-order=\\ <pagebreak> & \n \r " $ <bullet> % _ # ^ { } < > £ ± ² ³ ° § ® © \xad \xa0 ➔ → ← | − ≤ ≥ ‐ Ω μ Δ
+order=\\ <pagebreak> & \n \r " $ <bullet> % _ # ^ { } < > £ ± ² ³ ° § ® © \xad \xa0 ➔ → ← | − ≤ ≥ ‐ Ω μ Δ ~
\\=\\textbackslash\s
<pagebreak>=
"=''
μ={\\textmu}
Δ=$\\Delta$
Ω=$\\Omega$
+~={\\textasciitilde}
[Template/OpenDocument]
order=& < > " ' \x80 \n \r
\n=<br>
[Template/LaTeX]
-order=\\ <pagebreak> & \n \r " $ <bullet> % _ # ^ { } < > £ ± ² ³ ° § ® © ‐ Ω ~ μ Δ
+order=\\ <pagebreak> & \n \r " $ <bullet> % _ # ^ { } < > £ ± ² ³ ° § ® © \xad \xa0 ➔ → ← | − ≤ ≥ ‐ Ω μ Δ ~
\\=\\textbackslash\s
<pagebreak>=
"=''
<bullet>=$\\bullet$
%=\\%
_=\\_
-#=\\#
+# A hash mark starts a comment; therefore the line is ignored. So use
+# its hex code instead.
+\x23=\\#
{=\\{
}=\\}
<=$<$
²=$^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
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
Log in as C<username>. The default is to use the value from the
configuration file and C<demo> if none is set there.
+=item B<-c>, B<--client>=C<client>
+
+Use the database for client C<client>. C<client> can be a client's
+database ID or its name. The default is to use the value from the
+configuration file.
+
=item B<-o>, B<--log-file>=C<filename>
Use C<filename> as the log file. The default is to use the value from
package main;
-use Test::More tests => 80;
+use Test::More tests => 56;
use lib 't';
use strict;
use Carp;
use Support::TestSetup;
+use SL::Dev::ALL qw(:ALL);
use_ok 'SL::BackgroundJob::CreatePeriodicInvoices';
use_ok 'SL::DB::Chart';
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 {
# 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,
%{ $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) ]) };
-use Test::More tests => 105;
+use Test::More tests => 130;
use strict;
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);
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' ]);
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();
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',
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',
$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 ]
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 ]
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");
$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 ]
$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 ]
$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 ]
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),
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");
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 ]
};
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 ]
};
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 ]
};
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,
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;
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',
);
# $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}};
#####
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,
);
#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}};
use Carp;
use Support::TestSetup;
+use SL::Dev::ALL qw(:ALL);
use_ok 'SL::BackgroundJob::CreatePeriodicInvoices';
use_ok 'SL::Controller::FinancialControllingReport';
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,
%{ $params{order} },
);
- $order->calculate_prices_and_taxes;
-
- ok($order->save(cascade => 1));
-
$::form = Support::TestSetup->create_new_form;
$ctrl = SL::Controller::FinancialControllingReport->new;
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 ],
package main;
-use Test::More tests => 49;
+use Test::More tests => 43;
use lib 't';
use strict;
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';
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,
%{ $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;
# ----------------------------------------------------------------------
# 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);
# ----------------------------------------------------------------------
# order_value_periodicity=y, periodicity=q
-create_sales_order(
+make_sales_order(
periodic_invoices_config => {
periodicity => 'm',
order_value_periodicity => 'y',
# ----------------------------------------------------------------------
# order_value_periodicity=y, periodicity=q, starting in previous year
-create_sales_order(
+make_sales_order(
order => {
transdate => DateTime->from_kivitendo('01.03.2013'),
},
# ----------------------------------------------------------------------
# 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'),
},
# ----------------------------------------------------------------------
# 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'),
},
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;
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),
]
);
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;
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,
},
], "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();
SL::DB::Manager::Part->delete_all( all => 1);
};
-
1;
-
-use Test::More tests => 42;
+use Test::More tests => 41;
use strict;
use Data::Dumper;
use Support::TestSetup;
use Test::Exception;
-use List::Util qw(max);
use SL::DB::Buchungsgruppe;
use SL::DB::Currency;
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 {
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();
reset_state();
# we create L20199 with two items
-my $do1 = new_delivery_order('department_id' => 32149,
- 'donumber' => 'L20199',
- 'employee_id' => 31915,
- 'intnotes' => 'Achtung: Neue Lieferadresse ab 16.02.2015 in der Otto-Merck-Str. 7a! 13.02.2015/MH
-
- Yamaha-Produkte (201...) immer plus 25% dazu rechnen / BK 13.02.2014',
- 'ordnumber' => 'A16399',
- 'payment_id' => 11276,
- 'salesman_id' => 31915,
- 'shippingpoint' => 'Maisenhaus',
- # 'shipto_id' => 451463,
- 'is_sales' => 'true',
- 'shipvia' => 'DHL, Versand am 06.03.2015, 1 Paket 17,00 kg',
- 'taxzone_id' => 4,
- 'closed' => undef,
- # 'currency_id' => 1,
- 'cusordnumber' => 'b84da',
- 'customer_id' => $customer->id,
- 'id' => 464003,
- 'notes' => '<ul><li><strong>fett</strong></li><li><strong>und</strong></li><li><strong>mit</strong></li><li><strong>bullets</strong></li><li> </li></ul>',
+my $do1 = create_sales_delivery_order(
+ 'department_id' => $department->id,
+ 'donumber' => 'L20199',
+ 'employee_id' => $employee->id,
+ 'intnotes' => 'some intnotes',
+ 'ordnumber' => 'A16399',
+ 'payment_id' => $payment_do->id,
+ 'salesman_id' => $employee->id,
+ 'shippingpoint' => 'sendtome',
+ 'shipvia' => 'DHL, Versand am 06.03.2015, 1 Paket 17,00 kg',
+ 'cusordnumber' => 'b84da',
+ 'customer_id' => $customer->id,
+ 'notes' => '<ul><li><strong>fett</strong></li><li><strong>und</strong></li><li><strong>mit</strong></li><li><strong>bullets</strong></li><li> </li></ul>',
+ orderitems => [
+ create_delivery_order_item(
+ part => $parts[0],
+ discount => '0.25',
+ lastcost => '49.95000',
+ longdescription => "<ol><li>27</li><li>28</li><li>29</li><li><sub>asdf</sub></li><li><sub>asdf</sub></li><li><sup>oben</sup></li></ol><p><s>kommt nicht mehr vor</s></p>",
+ marge_price_factor => 1,
+ qty => '2.00000',
+ sellprice => '242.20000',
+ unit => $unit->name,
+ ),
+ create_delivery_order_item(
+ part => $parts[1],
+ discount => '0.25',
+ lastcost => '153.00000',
+ qty => '3.00000',
+ sellprice => '344.30000',
+ transdate => '06.03.2015',
+ unit => $unit->name,
+ )
+ ]
);
-my $do1_item1 = SL::DB::DeliveryOrderItem->new('delivery_order_id' => 464003,
- 'description' => "Flügel Hammerkopf bestehend aus:
- Bass/Diskant 26/65 Stück, Gesamtlänge 80/72, Bohrlänge 56/48
- Pflaumenbaum, Gr.5, Unterfilz weinrot, genietet[[Aufschnittbreite: 11,0, Kernform: US]]",
- 'discount' => '0.25',
- 'id' => 144736,
- 'lastcost' => '49.95000',
- 'longdescription' => "<ol><li>27</li><li>28</li><li>29</li><li><sub>asdf</sub></li><li><sub>asdf</sub></li><li><sup>oben</sup></li></ol><p><s>kommt nicht mehr vor</s></p>",
- 'marge_price_factor' => 1,
- 'mtime' => undef,
- 'ordnumber' => 'A16399',
- 'parts_id' => 26321,
- 'position' => 1,
- 'price_factor' => 1,
- 'qty' => '2.00000',
- 'sellprice' => '242.20000',
- 'transdate' => '06.03.2015',
- 'unit' => 'kg')->save;
-
-my $do1_item2 = SL::DB::DeliveryOrderItem->new('delivery_order_id' => 464003,
- 'description' => "[[0640]]Flügel Hammerstiele bestehend aus:
-70 Stielen Standard in Weißbuche und
-20 Stielen Diskant abgekehlt in Weißbuche
-mit Röllchen aus Synthetikleder,
-Kapseln mit Yamaha Profil, Kerbenabstand 3,6 mm mit eingedrehten Abnickschrauben",
- 'discount' => '0.25',
- 'id' => 144737,
- 'itime' => undef,
- 'lastcost' => '153.00000',
- 'longdescription' => '',
- 'marge_price_factor' => 1,
- 'mtime' => undef,
- 'ordnumber' => 'A16399',
- 'parts_id' => 25505,
- 'position' => 2,
- 'price_factor' => 1,
- 'price_factor_id' => undef,
- 'pricegroup_id' => undef,
- 'project_id' => undef,
- 'qty' => '3.00000',
- 'reqdate' => undef,
- 'sellprice' => '344.30000',
- 'serialnumber' => '',
- 'transdate' => '06.03.2015',
- 'unit' => 'kg')->save;
# TESTS
+my $do1_item1 = $do1->orderitems->[0];
+my $do1_item2 = $do1->orderitems->[1];
# test delivery order before any conversion
ok($do1->donumber eq "L20199", 'Delivery Order Number created');
ok($do1->notes eq '<ul><li><strong>fett</strong></li><li><strong>und</strong></li><li><strong>mit</strong></li><li><strong>bullets</strong></li><li> </li></ul>', "do RichText notes saved");
ok((not $do1->closed) , 'Delivery Order is not closed');
-ok($do1_item1->parts_id eq '26321', 'doi linked with part');
+is($do1_item1->parts_id, $parts[0]->id, 'doi linked with part');
ok($do1_item1->qty == 2, 'qty check doi');
ok($do1_item1->longdescription eq "<ol><li>27</li><li>28</li><li>29</li><li><sub>asdf</sub></li><li><sub>asdf</sub></li><li><sup>oben</sup></li></ol><p><s>kommt nicht mehr vor</s></p>",
"do item1 rich text longdescripition");
ok ($do1_item2->position == 2, 'doi2 position check');
-ok (2 == scalar@{ SL::DB::Manager::DeliveryOrderItem->get_all(where => [ delivery_order_id => $do1->id ]) }, 'two doi linked');
+is (SL::DB::Manager::DeliveryOrderItem->get_all_count(where => [ delivery_order_id => $do1->id ]), 2 , 'two doi linked');
# convert this do to invoice
# test invoice afterwards
ok ($invoice->shipvia eq "DHL, Versand am 06.03.2015, 1 Paket 17,00 kg", "ship via check");
-ok ($invoice->shippingpoint eq "Maisenhaus", "shipping point check");
+ok ($invoice->shippingpoint eq "sendtome", "shipping point check");
ok ($invoice->ordnumber eq "A16399", "ordnumber check");
ok ($invoice->donumber eq "L20199", "donumber check");
ok ($invoice->notes eq '<ul><li><strong>fett</strong></li><li><strong>und</strong></li><li><strong>mit</strong></li><li><strong>bullets</strong></li><li> </li></ul>', "do RichText notes saved");
ok(($do1->closed) , 'Delivery Order is closed after conversion');
-ok (SL::DB::PaymentTerm->new(id => $invoice->{payment_id})->load->description eq "14Tage 2%Skonto, 30Tage netto", 'payment term description check');
+is($invoice->payment_terms->description, "14Tage 2%Skonto, 30Tage netto", 'payment term description check');
# some test data from original client invoice console (!)
# my $invoice3 = SL::DB::Manager::Invoice->find_by( ordnumber => 'A16399' );
$invoice->load;
-ok($invoice->currency_id eq '1', 'currency_id');
-ok($invoice->cusordnumber eq 'b84da', 'cusordnumber check');
-ok(SL::DB::Department->new(id => $invoice->{department_id})->load->description eq "Maisenhaus-Versand", 'department description');
-is($invoice->amount, '1354.17000', 'amount check');
-is($invoice->marge_percent, '50.88580', 'marge percent check');
-is($invoice->marge_total, '579.06000', 'marge total check');
-is($invoice->netamount, '1137.96000', 'netamount check');
+is($invoice->cusordnumber , 'b84da' , 'cusordnumber check');
+is($invoice->department->description , "Test Department" , 'department description ok');
+is($invoice->amount , '1354.17000' , 'amount check');
+is($invoice->marge_percent , '50.88580' , 'marge percent check');
+is($invoice->marge_total , '579.06000' , 'marge total check');
+is($invoice->netamount , '1137.96000' , 'netamount check');
# some item checks
-ok(@ {$invoice->items_sorted}[0]->parts_id eq '26321', 'invoiceitem 1 linked with part');
-ok(2 == scalar@{ $invoice->invoiceitems }, 'two invoice items linked with invoice');
-is(@ {$invoice->items_sorted}[0]->position, 1, "position 1 order correct");
-is(@ {$invoice->items_sorted}[1]->position, 2, "position 2 order correct");
-is(@ {$invoice->items_sorted}[0]->longdescription, "<ol><li>27</li><li>28</li><li>29</li><li><sub>asdf</sub></li><li><sub>asdf</sub></li><li><sup>oben</sup></li></ol><p><s>kommt nicht mehr vor</s></p>",
+is($invoice->items_sorted->[0]->parts_id , $parts[0]->id , 'invoiceitem 1 linked with part');
+is(scalar @{ $invoice->invoiceitems } , 2 , 'two invoice items linked with invoice');
+is($invoice->items_sorted->[0]->position , 1 , "position 1 order correct");
+is($invoice->items_sorted->[1]->position , 2 , "position 2 order correct");
+is($invoice->items_sorted->[0]->part->partnumber , 'v-519160549' , "partnumber 1 correct");
+is($invoice->items_sorted->[1]->part->partnumber , 'v-120160086' , "partnumber 2 correct");
+is($invoice->items_sorted->[0]->qty , '2.00000' , "pos 1 qty");
+is($invoice->items_sorted->[1]->qty , '3.00000' , "pos 2 qty");
+is($invoice->items_sorted->[0]->discount , 0.25 , "pos 1 discount");
+is($invoice->items_sorted->[1]->discount , 0.25 , "pos 2 discount");
+is($invoice->items_sorted->[0]->longdescription , "<ol><li>27</li><li>28</li><li>29</li><li><sub>asdf</sub></li><li><sub>asdf</sub></li><li><sup>oben</sup></li></ol><p><s>kommt nicht mehr vor</s></p>",
"invoice item1 rich text longdescripition");
-is(@ {$invoice->items_sorted}[0]->part->partnumber, 'v-519160549', "partnumber 1 correct");
-is(@ {$invoice->items_sorted}[1]->part->partnumber, 'v-120160086', "partnumber 2 correct");
-is(@ {$invoice->items_sorted}[0]->qty, '2.00000', "pos 1 qty");
-is(@ {$invoice->items_sorted}[1]->qty, '3.00000', "pos 2 qty");
-is(@ {$invoice->items_sorted}[0]->discount, 0.25, "pos 1 discount");
-is(@ {$invoice->items_sorted}[1]->discount, 0.25, "pos 2 discount");
-
# more ideas: check onhand, lastcost (parsed lastcost)
# };
-
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();
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;
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,
auto_calculation => 1,
)->save;
- $vendor = SL::Dev::CustomerVendor::create_vendor(
+ $vendor = new_vendor(
name => 'Test Vendor',
currency_id => $currency_id,
taxzone_id => $taxzone->id,
@parts = ();
- push @parts, SL::Dev::Part::create_part(
+ push @parts, new_part(
partnumber => 'T4254',
description => 'Fourty-two fifty-four',
lastcost => 1.93,
%{ $params{part1} }
)->save;
- push @parts, SL::Dev::Part::create_part(
+ push @parts, new_part(
partnumber => 'T0815',
description => 'Zero EIGHT fifteeN @ 7%',
lastcost => 5.473,
unit => $unit->name,
%{ $params{part2} }
)->save;
- push @parts, SL::Dev::Part::create_part(
+ push @parts, new_part(
partnumber => '19%',
description => 'Testware 19%',
lastcost => 0,
unit => $unit->name,
%{ $params{part3} }
)->save;
- push @parts, SL::Dev::Part::create_part(
+ push @parts, new_part(
partnumber => '7%',
description => 'Testware 7%',
lastcost => 0,
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,
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,
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,
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,
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,
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,
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,
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,
# (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,
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,
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,
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,
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,
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,
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,
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,
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 ],
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;
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);
$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} }
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,
);
}
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,
);
}
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 ],
use Data::Dumper;
use Support::TestSetup;
use SL::DB::Part;
-use SL::Dev::Part;
+use SL::Dev::Part qw(new_part);
Support::TestSetup::login();
# test simple transaction
-my $part = create_part();
+my $part = new_part();
SL::DB->client->with_transaction(sub {
$part->save;
ok 1, 'part saved';
};
# 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';
# 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';
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();
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");
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");
sub clear_up {
# Cleaning up may fail.
eval {
- SL::Dev::File->delete_all();
+ SL::Dev::File::delete_all();
unlink($scannerfile);
};
}
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;
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");
# 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' );
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;
->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);
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);
->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;
->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;
->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);
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 {
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();
-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' );
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;
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");
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,
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");
use lib 't';
+use SL::Dev::Part qw(new_part);
+
use_ok 'Support::TestSetup';
use_ok 'SL::DB::Bin';
use_ok 'SL::DB::Part';
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);
-[%- USE HTML -%][%- USE LxERP -%][%- USE P -%][% IF TEMPLATES.size %]
- [% LxERP.t8('Template suggestions') %]:
+[%- USE HTML -%][%- USE LxERP -%][%- USE P -%]
+
+[% IF TEMPLATES_AP.size %]
+ [% LxERP.t8('AP template suggestions') %]:
<table>
<thead>
<tr>
</thead>
<tbody>
- [% FOREACH template = TEMPLATES %]
+ [% FOREACH template = TEMPLATES_AP %]
<tr class="listrow">
<td>[% P.link(SELF.load_ap_record_template_url(template), template.template_name) %]</td>
<td>[% HTML.escape(template.vendor.name) %]</td>
</tbody>
</table>
[% ELSE %]
- <p class="message_hint">[% LxERP.t8('No template was found.') %]</p>
+ <p class="message_hint">[% LxERP.t8('No AP template was found.') %]</p>
+[% END %]
+
+[% IF TEMPLATES_GL.size %]
+ [% LxERP.t8('GL template suggestions') %]:
+ <table>
+ <thead>
+ <tr>
+ <th class="listheading">[% LxERP.t8('Description') %]</th>
+ <th class="listheading">[% LxERP.t8('Reference') %]</th>
+ <th class="listheading">[% LxERP.t8('Employee') %]</th>
+ <th class="listheading">[% LxERP.t8('Template date') %]</th>
+ </tr>
+ </thead>
+
+ <tbody>
+ [% FOREACH template = TEMPLATES_GL %]
+ <tr class="listrow">
+ <td>[% P.link(SELF.load_gl_record_template_url(template), template.template_name) %]</td>
+ <td>[% HTML.escape(template.reference) %]</td>
+ <td>[% HTML.escape(template.employee.name || template.employee.login) %]</td>
+ <td>[% HTML.escape(template.itime_as_date) %]</td>
+ </tr>
+ [% END %]
+ </tbody>
+ </table>
+[% ELSE %]
+ <p class="message_hint">[% LxERP.t8('No GL template was found.') %]</p>
[% END %]
<br>
-[% LxERP.t8('Vendor filter for AP transaction templates') %]:
-<form method="post" action="javascript:kivi.BankTransaction.filter_templates()">
+<form method="post" action="javascript:kivi.BankTransaction.filter_templates()" id="create_invoice_window_form">
[% L.hidden_tag("bt_id", SELF.transaction.id) %]
[% L.hidden_tag("filter.bank_account", FORM.filter.bank_account) %]
[% L.hidden_tag("filter.fromdate", FORM.filter.fromdate) %]
[% L.hidden_tag("filter.todate", FORM.filter.todate) %]
<table>
+ <tr>
+ <th align="right">[%- LxERP.t8("Template Description") %]</th>
+ <td>[% P.input_tag("template", template_name, style="width: 250px") %]</td>
+ </tr>
<tr>
<th align="right">[%- LxERP.t8("Vendor") %]</th>
- <td>[% P.input_tag("vendor", vendor_name, class="initial_focus", style="width: 250px") %]</td>
+ <td>[% P.input_tag("vendor", vendor_name, style="width: 250px") %]</td>
+ </tr>
+ <tr>
+ <th align="right">[%- LxERP.t8("Reference") %]</th>
+ <td>[% P.input_tag("reference", reference_name, style="width: 250px") %]</td>
</tr>
</table>
-</form>
-
<p>
- [% P.button_tag("kivi.BankTransaction.filter_templates()", LxERP.t8("Filter vendors")) %]
+ [% P.submit_tag('', LxERP.t8("Filter")) %]
+ [% P.button_tag('$("#create_invoice_window_form").resetForm()', LxERP.t8('Reset')) %]
<a href="#" onclick="$('#create_invoice_window').dialog('close');">[% LxERP.t8("Cancel") %]</a>
</p>
-
+</form>
<hr>
<div id="templates">
[% PROCESS "bank_transactions/_template_list.html" %]
</div>
+
+<script type="text/javascript">
+<!--
+$(function() {
+ $('#template').focus();
+});
+
+//-->
+</script>
</tr>
<tr valign="top">
- <th align="right" nowrap>[% LxERP.t8("Message") %]</th>
+ <th align="right" nowrap>[% LxERP.t8("Message") %]
+ <sup> [% L.link("generictranslations.pl?action=edit_email_strings", "1)", title=LxERP.t8('Tired of copying always nice phrases for this message? Click here to use the new preset message option!'), target="_blank") %]</sup>
+ </th>
<td>[% L.textarea_tag("email_form.message", email_form.message, rows="15" cols="80" wrap="soft") %]</td>
</tr>
[%- USE HTML -%][%- USE LxERP %][%- USE T8 %]
[%- BLOCK output %]
<div id="flash_[% type %]" class="flash_message_[% type %]"[% IF !messages || !messages.size %] style="display: none"[% END %]>
- <a href='#' style='float:right'
- onclick='$("#flash_[% type %]_content").empty();$("#flash_[% type %]_detail").empty();$("#flash_[% type %]").hide()'>
- <img src='image/close.png' border='0' alt='[% 'Close Flash' | $T8 %]'></a>
+ <a href='#' style='float:right'
+ onclick='$("#flash_[% type %]_content").empty();$("#flash_[% type %]_detail").empty();$("#flash_[% type %]").hide()'>
+ <img src='image/close.png' border='0' alt='[% 'Close Flash' | $T8 %]'></a>
<span class="flash_title">[%- title %]:</span>
<span id="flash_[% type %]_content">
[% FOREACH message = messages %]
<div id="flash_detail_[% type %]" style="display: none">
<br>
<span id="flash_[% type %]_detail"></span><br>
- <a href='#' style='float:left'
- onclick='$("#flash_detail_[% type %]").hide()'>
+ <a href='#' style='float:left'
+ onclick='$("#flash_detail_[% type %]").hide()'>
<img src='image/close.png' border='0' alt='[% 'Close Details' | $T8 %]'></a><br>
</div>
</div>
</table>
</td>
</tr>
- [% IF ALL_DEPARTMENTS.as_list.size %]
- <tr>
- <th align=left>[% 'Department' | $T8 %]</th>
- </tr>
<tr>
- <td>
- <table>
- <tr>
- <td align=left>[% 'Department' | $T8 %]</td>
- <td align=left></td>
- <td>[% L.select_tag('department_id', ALL_DEPARTMENTS, title_key = 'description', with_empty = 1) %]</td>
- </tr>
- </table>
- </td>
+ <td><hr size=1 noshade></td>
+ </tr>
+ <tr>
+ <td>
+ <table>
+ <tr>
+ <td align=left>[% 'Gldate' | $T8 %] [% 'From' | $T8 %]</td>
+ <td align=left></td>
+ <td>[% L.date_tag('gldatefrom') %]</td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ <tr>
+ <tr>
+ <td><hr size=1 noshade></td>
+ </tr>
+ [% IF ALL_DEPARTMENTS.as_list.size %]
+ <tr>
+ <td>
+ <table>
+ <tr>
+ <td align=left>[% 'Department' | $T8 %]</td>
+ <td align=left></td>
+ <td>[% L.select_tag('department_id', ALL_DEPARTMENTS, title_key = 'description', with_empty = 1) %]</td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ <tr>
+ <td><hr size=3 noshade></td>
</tr>
[% END %]
- <tr>
- <td><hr size=3 noshade></td>
- </tr>
</table>
<input type=hidden name=beraternr value="[% beraternr %]">
<tr>
<th align="right">[% 'Type' | $T8 %]</th>
<td>
- [% L.checkbox_tag('filter.part.type[]', checked=filter.part.type_.part, value='part', label=LxERP.t8('Part')) %]
- [% L.checkbox_tag('filter.part.type[]', checked=filter.part.type_.service, value='service', label=LxERP.t8('Service')) %]
- [% L.checkbox_tag('filter.part.type[]', checked=filter.part.type_.assembly, value='assembly', label=LxERP.t8('Assembly')) %]
+ [% L.checkbox_tag('filter.part.part_type[]', checked=filter.part.part_type_.part, value='part', label=LxERP.t8('Part')) %]
+ [% L.checkbox_tag('filter.part.part_type[]', checked=filter.part.part_type_.service, value='service', label=LxERP.t8('Service')) %]
+ [% L.checkbox_tag('filter.part.part_type[]', checked=filter.part.part_type_.assembly, value='assembly', label=LxERP.t8('Assembly')) %]
+ [%- IF INSTANCE_CONF.get_feature_experimental %]
+ [% L.checkbox_tag('filter.part.part_type[]', checked=filter.part.part_type_.assortment, value='assortment', label=LxERP.t8('Assortment')) %]
+ [% END %]
</td>
</tr>
<tr>
<tr>
<th align="right">[% 'Type' | $T8 %]</th>
<td>
- [% L.checkbox_tag('filter.part.type[]', checked=filter.part.type_.part, value='part', label=LxERP.t8('Part')) %]
- [% L.checkbox_tag('filter.part.type[]', checked=filter.part.type_.service, value='service', label=LxERP.t8('Service')) %]
- [% L.checkbox_tag('filter.part.type[]', checked=filter.part.type_.assembly, value='assembly', label=LxERP.t8('Assembly')) %]
+ [% L.checkbox_tag('filter.part.part_type[]', checked=filter.part.part_type_.part, value='part', label=LxERP.t8('Part')) %]
+ [% L.checkbox_tag('filter.part.part_type[]', checked=filter.part.part_type_.service, value='service', label=LxERP.t8('Service')) %]
+ [% L.checkbox_tag('filter.part.part_type[]', checked=filter.part.part_type_.assembly, value='assembly', label=LxERP.t8('Assembly')) %]
+ [%- IF INSTANCE_CONF.get_feature_experimental %]
+ [% L.checkbox_tag('filter.part.part_type[]', checked=filter.part.part_type_.assortment, value='assortment', label=LxERP.t8('Assortment')) %]
+ [% END %]
</td>
</tr>
</table>
<th align="right">[% 'Contact Person' | $T8 %]</th>
<td>
[%- IF delivered %]
- <input type="hidden" name="cp_id" value="[% HTML.escape(cp_id) %]">
- [%- IF cp_id == row.cp_id %]
- [%- HTML.escape(row.cp_name) %][%- IF row.cp_abteilung %] ([% HTML.escape(row.cp_abteilung) %])[% END -%]
- [%- END %]
+ [% L.hidden_tag("cp_id", cp_id) %]
+ [% HTML.escape(CONTACT_OBJ.full_name) %][% IF CONTACT_OBJ.cp_abteilung %] ([% HTML.escape(CONTACT_OBJ.cp_abteilung) %])[% END %]
[%- ELSE %]
[% L.select_tag('cp_id', ALL_CONTACTS, default = cp_id, value_key = 'cp_id', title_key = 'full_name_dep', with_empty = 1, style='width: 250px') %]
[%- END %]
--- /dev/null
+[%- USE T8 %]
+[%- USE HTML %]
+<h1>[% HTML.escape(title) %]</h1>
+[%- IF message %]
+ <p>
+ [% HTML.escape(message) %]
+ </p>
+[%- END %]
+ <form method="post" action="generictranslations.pl" id="form">
+ <table>
+ [%- FOREACH mail_string IN MAIL_STRINGS.keys.sort %]
+ <tr>
+ <th class="listheading"> </th>
+ <th class="listheading">[% MAIL_STRINGS.$mail_string %]</th>
+ </tr>
+
+ [%- FOREACH language = LANGUAGES %]
+ <tr>
+ <td>
+ [%- IF language.id == 'default' %]
+ [% 'Default (no language selected)' | $T8 %]
+ [%- ELSE %]
+ [%- HTML.escape(language.description) %]
+ [%- END %]
+ </td>
+ <td>
+ [%- IF mail_string.search('preset') %]
+ <textarea name="translation__[% language.id %]__[% mail_string %]" rows="4" cols="60">[% HTML.escape(language.$mail_string) %]</textarea>
+ [%- ELSE %]
+ <input name="translation__[% language.id %]__[% mail_string %]" size="40" value="[% HTML.escape(language.$mail_string) %]">
+ [%- END %]
+ </td>
+ </tr>
+ [%- END %]
+ [%- END %]
+ </table>
+ </form>
<td>[%- L.checkbox_tag('l_name', label=LxERP.t8('Name in Selected Records'), value='Y') %]</td>
<td>[%- L.checkbox_tag('l_soldtotal', label=LxERP.t8('Qty in Selected Records'), value='Y') %]</td>
</tr>
+ <tr>
+ <td>[%- L.checkbox_tag('l_warehouse', label=LxERP.t8('Default Warehouse'), value='Y') %]</td>
+ <td>[%- L.checkbox_tag('l_bin', label=LxERP.t8('Default Bin'), value='Y') %]</td>
+ </tr>
[% CUSTOM_VARIABLES_INCLUSION_CODE %]
</table>