From: Sven Schöling Date: Thu, 5 Mar 2015 12:23:05 +0000 (+0100) Subject: Merge branch 'periodic-invoices-order-value-basis' X-Git-Tag: release-3.2.1~53 X-Git-Url: http://wagnertech.de/gitweb/gitweb.cgi/mfinanz.git/commitdiff_plain/5534d9cb025cc4036f6fcce28a24c5e5b164641e?hp=326fa24ae5e32fa0196a6785accfc1b667165f56 Merge branch 'periodic-invoices-order-value-basis' --- diff --git a/SL/BackgroundJob/CreatePeriodicInvoices.pm b/SL/BackgroundJob/CreatePeriodicInvoices.pm index d80ba4eeb..bdf536346 100644 --- a/SL/BackgroundJob/CreatePeriodicInvoices.pm +++ b/SL/BackgroundJob/CreatePeriodicInvoices.pm @@ -66,7 +66,7 @@ sub run { } sub _log_msg { - my $message = join('', @_); + my $message = join('', 'SL::BackgroundJob::CreatePeriodicInvoices: ', @_); $message .= "\n" unless $message =~ m/\n$/; $::lxdebug->message(LXDebug::DEBUG1(), $message); } @@ -74,7 +74,7 @@ sub _log_msg { sub _generate_time_period_variables { my $config = shift; my $period_start_date = shift; - my $period_end_date = $period_start_date->clone->truncate(to => 'month')->add(months => $config->get_period_length)->subtract(days => 1); + my $period_end_date = $period_start_date->clone->truncate(to => 'month')->add(months => $config->get_billing_period_length)->subtract(days => 1); my @month_names = ('', $::locale->text('January'), $::locale->text('February'), $::locale->text('March'), $::locale->text('April'), $::locale->text('May'), $::locale->text('June'), @@ -107,7 +107,7 @@ sub _generate_time_period_variables { sub _replace_vars { my (%params) = @_; my $sub = $params{attribute}; - my $str = $params{object}->$sub; + my $str = $params{object}->$sub // ''; my $sub_fmt = lc($params{attribute_format} // 'text'); my ($start_tag, $end_tag) = $sub_fmt eq 'html' ? ('<%', '%>') : ('<%', '%>'); @@ -142,6 +142,40 @@ sub _replace_vars { $params{object}->$sub($str); } +sub _adjust_sellprices_for_period_lengths { + my (%params) = @_; + + my $billing_len = $params{config}->get_billing_period_length; + my $order_value_len = $params{config}->get_order_value_period_length; + + return if $billing_len == $order_value_len; + + my $is_last_invoice_in_cycle = $params{config}->is_last_bill_date_in_order_value_cycle(date => $params{period_start_date}); + + _log_msg("_adjust_sellprices_for_period_lengths: period_start_date $params{period_start_date} is_last_invoice_in_cycle $is_last_invoice_in_cycle billing_len $billing_len order_value_len $order_value_len"); + + if ($order_value_len < $billing_len) { + my $num_orders_per_invoice = $billing_len / $order_value_len; + + $_->sellprice($_->sellprice * $num_orders_per_invoice) for @{ $params{invoice}->items }; + + return; + } + + my $num_invoices_in_cycle = $order_value_len / $billing_len; + + foreach my $item (@{ $params{invoice}->items }) { + my $sellprice_one_invoice = $::form->round_amount($item->sellprice * $billing_len / $order_value_len, 2); + + if ($is_last_invoice_in_cycle) { + $item->sellprice($item->sellprice - ($num_invoices_in_cycle - 1) * $sellprice_one_invoice); + + } else { + $item->sellprice($sellprice_one_invoice); + } + } +} + sub _create_periodic_invoice { $main::lxdebug->enter_sub(); @@ -174,6 +208,8 @@ sub _create_periodic_invoice { _replace_vars(object => $item, vars => $time_period_vars, attribute => $_, attribute_format => ($_ eq 'longdescription' ? 'html' : 'text')) for qw(description longdescription); } + _adjust_sellprices_for_period_lengths(invoice => $invoice, config => $config, period_start_date => $period_start_date); + $invoice->post(ar_id => $config->ar_chart_id) || die; # like $form->add_shipto, but we don't need to check for a manual exception, @@ -210,6 +246,8 @@ sub _create_periodic_invoice { period_start_date => $period_start_date) ->save; + _log_msg("_create_invoice created for period start date $period_start_date id " . $invoice->id . " number " . $invoice->invnumber . " netamount " . $invoice->netamount . " amount " . $invoice->amount); + # die $invoice->transaction_description; })) { $::lxdebug->message(LXDebug->WARN(), "_create_invoice failed: " . join("\n", (split(/\n/, $self->{db_obj}->db->error))[0..2])); diff --git a/SL/Controller/FinancialControllingReport.pm b/SL/Controller/FinancialControllingReport.pm index c10c09dbe..125a8a855 100644 --- a/SL/Controller/FinancialControllingReport.pm +++ b/SL/Controller/FinancialControllingReport.pm @@ -3,7 +3,7 @@ package SL::Controller::FinancialControllingReport; use strict; use parent qw(SL::Controller::Base); -use List::Util qw(min sum); +use List::Util qw(max min sum); use SL::DB::Order; use SL::DB::ProjectType; @@ -12,8 +12,8 @@ use SL::Controller::Helper::ReportGenerator; use SL::Locale::String; use Rose::Object::MakeMethods::Generic ( - scalar => [ qw(project_types) ], - 'scalar --get_set_init' => [ qw(models) ], + scalar => [ qw(orders) ], + 'scalar --get_set_init' => [ qw(project_types models) ], ); __PACKAGE__->run_before(sub { $::auth->assert('sales_order_edit'); }); @@ -29,13 +29,11 @@ my %sort_columns = ( sub action_list { my ($self) = @_; - $self->project_types(SL::DB::Manager::ProjectType->get_all_sorted); - $self->make_filter_summary; $self->prepare_report; - $self->{orders} = $self->models->get; + $self->orders($self->models->get); $self->calculate_data; @@ -104,7 +102,7 @@ sub prepare_report { sub calculate_data { my ($self) = @_; - foreach my $order (@{ $self->{orders} }) { + foreach my $order (@{ $self->orders }) { my @delivery_orders = @{ $order->linked_records(direction => 'to', to => 'DeliveryOrder', via => 'Order', query => [ '!customer_id' => undef ]) }; my @invoices = @{ $order->linked_records(direction => 'to', to => 'Invoice', via => [ 'Order', 'DeliveryOrder' ]) }; @@ -113,10 +111,10 @@ sub calculate_data { map({ @{ $_->storno_invoices } } grep { $_->storno && !$_->storno_id } @invoices), ); - $order->{delivered_amount} = sum map { $self->sum_relevant_items(order => $order, other => $_, by_order => 1) } @delivery_orders; - $order->{billed_amount} = sum map { $self->sum_relevant_items(order => $order, other => $_) } @invoices; - $order->{paid_amount} = sum map { $_->paid * $_->netamount / (($_->amount * 1) || ($_->netamount * 1) || 1) } @invoices; - my $billed_amount = sum map { $_->netamount } @invoices; + $order->{delivered_amount} = sum(map { $self->sum_relevant_items(order => $order, other => $_, by_order => 1) } @delivery_orders) // 0; + $order->{billed_amount} = sum(map { $self->sum_relevant_items(order => $order, other => $_) } @invoices) // 0; + $order->{paid_amount} = sum(map { $_->paid * $_->netamount / (($_->amount * 1) || ($_->netamount * 1) || 1) } @invoices) // 0; + my $billed_amount = sum(map { $_->netamount } @invoices) // 0; $order->{other_amount} = $billed_amount - $order->{billed_amount}; $order->{billable_amount} = $order->{delivered_amount} - $order->{billed_amount}; @@ -136,19 +134,24 @@ sub calculate_data { sub calculate_periodic_invoices_order_netamount { my ($self, $order) = @_; + my $year = DateTime->today_local->year; + my $year_start = DateTime->new_local(day => 1, month => 1, year => $year); + my $year_end = DateTime->new_local(day => 31, month => 12, year => $year); + my $cfg = $order->periodic_invoices_config; - my $num_years = 0; + my $period_len = $cfg->get_billing_period_length; + my $num_months = 0; my $cur_date = $cfg->start_date->clone; - my $end_date = $cfg->terminated ? $self->end_date : undef; - $end_date //= DateTime->today_local; - $end_date = min($end_date, DateTime->today_local); + my $end_date = $cfg->terminated ? $cfg->end_date : undef; + $end_date //= $year_end; + $end_date = min $end_date, $year_end; while ($cur_date <= $end_date) { - $num_years++; - $cur_date->add(years => 1); + $num_months += $period_len if $cur_date >= $year_start; + $cur_date->add(months => $period_len); } - return $num_years * $order->netamount * (12 / $order->periodic_invoices_config->get_period_length); + return $num_months * $order->netamount / $order->periodic_invoices_config->get_order_value_period_length; } sub sum_items { @@ -202,7 +205,7 @@ sub list_objects { $data->{$_}->{data} = $::form->format_amount(\%::myconfig, $data->{$_}->{data}, 2) for grep { !m/_p$/ } @{ $self->{number_columns} }; }; - return $self->report_generator_list_objects(report => $self->{report}, objects => $self->{orders}, data_callback => $modify_data); + return $self->report_generator_list_objects(report => $self->{report}, objects => $self->orders, data_callback => $modify_data); } sub make_filter_summary { @@ -222,6 +225,8 @@ sub make_filter_summary { $self->{filter_summary} = join ', ', @filter_strings; } +sub init_project_types { SL::DB::Manager::ProjectType->get_all_sorted } + sub init_models { my ($self) = @_; diff --git a/SL/Controller/FinancialOverview.pm b/SL/Controller/FinancialOverview.pm index cd153d23f..19ae44103 100644 --- a/SL/Controller/FinancialOverview.pm +++ b/SL/Controller/FinancialOverview.pm @@ -4,6 +4,7 @@ use strict; use parent qw(SL::Controller::Base); use List::MoreUtils qw(none); +use List::Util qw(min); use SL::DB::Employee; use SL::DB::Invoice; @@ -39,7 +40,7 @@ sub prepare_report { $self->report(SL::ReportGenerator->new(\%::myconfig, $::form)); - my @columns = qw(year quarter month sales_quotations sales_orders sales_invoices requests_for_quotation purchase_orders purchase_invoices); + my @columns = (qw(year quarter month), @{ $self->types }); $self->number_columns([ grep { !m/^(?:month|year|quarter)$/ } @columns ]); @@ -48,14 +49,15 @@ sub prepare_report { year => { text => t8('Year') }, quarter => { text => t8('Quarter') }, sales_quotations => { text => t8('Sales Quotations') }, - sales_orders => { text => t8('Sales Orders') }, + sales_orders => { text => t8('Sales Orders Advance') }, + sales_orders_per_inv => { text => t8('Total Sales Orders Value') }, sales_invoices => { text => t8('Invoices') }, requests_for_quotation => { text => t8('Requests for Quotation') }, purchase_orders => { text => t8('Purchase Orders') }, purchase_invoices => { text => t8('Purchase Invoices') }, ); - map { $column_defs{$_}->{align} = 'right' } @columns; + $column_defs{$_}->{align} = 'right' for @columns; $self->report->set_options( std_column_visibility => 1, @@ -87,6 +89,7 @@ sub get_objects { $self->objects({ sales_quotations => SL::DB::Manager::Order->get_all( where => [ and => [ @f_date, @f_salesman, SL::DB::Manager::Order->type_filter('sales_quotation') ]]), sales_orders => SL::DB::Manager::Order->get_all( where => [ and => [ @f_date, @f_salesman, SL::DB::Manager::Order->type_filter('sales_order') ]], with_objects => [ qw(periodic_invoices_config) ]), + sales_orders_per_inv => [], requests_for_quotation => SL::DB::Manager::Order->get_all( where => [ and => [ @f_date, @f_salesman, SL::DB::Manager::Order->type_filter('request_quotation') ]]), purchase_orders => SL::DB::Manager::Order->get_all( where => [ and => [ @f_date, @f_salesman, SL::DB::Manager::Order->type_filter('purchase_order') ]]), sales_invoices => SL::DB::Manager::Invoice->get_all( where => [ and => [ @f_date, @f_salesman, ]]), @@ -97,7 +100,7 @@ sub get_objects { $self->objects->{sales_orders} = [ grep { !$_->periodic_invoices_config || !$_->periodic_invoices_config->active } @{ $self->objects->{sales_orders} } ]; } -sub init_types { [ qw(sales_quotations sales_orders sales_invoices requests_for_quotation purchase_orders purchase_invoices) ] } +sub init_types { [ qw(sales_quotations sales_orders sales_orders_per_inv sales_invoices requests_for_quotation purchase_orders purchase_invoices) ] } sub init_data { my ($self) = @_; @@ -122,7 +125,8 @@ sub calculate_one_time_data { my ($self) = @_; foreach my $type (@{ $self->types }) { - foreach my $object (@{ $self->objects->{ $type } }) { + my $src_object_type = $type eq 'sales_orders_per_inv' ? 'sales_orders' : $type; + foreach my $object (@{ $self->objects->{ $src_object_type } }) { my $month = $object->transdate->month - 1; my $tdata = $self->data->{$type}; @@ -145,15 +149,25 @@ sub calculate_periodic_invoices { sub calculate_one_periodic_invoice { my ($self, %params) = @_; - return if $params{config}->start_date > $params{end_date}; + # Calculate sales order advance + my $net = $params{config}->order->netamount * $params{config}->get_billing_period_length / $params{config}->get_order_value_period_length; + my $sord = $self->data->{sales_orders}; - my $first_date = $params{config}->start_date->clone->set_year($self->year); - my $net = $params{config}->order->netamount * (12 / $params{config}->get_period_length); - my $sord = $self->data->{sales_orders}; + foreach my $date ($params{config}->calculate_invoice_dates(start_date => $params{start_date}, end_date => $params{end_date}, past_dates => 1)) { + $sord->{months }->[ $date->month - 1 ] += $net; + $sord->{quarters}->[ $date->quarter - 1 ] += $net; + $sord->{year} += $net; + } + + # Calculate total sales order value + my $date = $params{config}->order->transdate; + return if $date->year != $params{start_date}->year; - $sord->{months }->[ $first_date->month - 1 ] += $net; - $sord->{quarters}->[ $first_date->quarter - 1 ] += $net; - $sord->{year} += $net; + $net = $params{config}->order->netamount; + $sord = $self->data->{sales_orders_per_inv}; + $sord->{months }->[ $date->month - 1 ] += $net; + $sord->{quarters}->[ $date->quarter - 1 ] += $net; + $sord->{year} += $net; } sub list_data { diff --git a/SL/DB/MetaSetup/PeriodicInvoicesConfig.pm b/SL/DB/MetaSetup/PeriodicInvoicesConfig.pm index eae8481d4..1aeb42fd7 100644 --- a/SL/DB/MetaSetup/PeriodicInvoicesConfig.pm +++ b/SL/DB/MetaSetup/PeriodicInvoicesConfig.pm @@ -17,7 +17,8 @@ __PACKAGE__->meta->columns( first_billing_date => { type => 'date' }, id => { type => 'integer', not_null => 1, sequence => 'id' }, oe_id => { type => 'integer', not_null => 1 }, - periodicity => { type => 'varchar', length => 10, not_null => 1 }, + order_value_periodicity => { type => 'varchar', length => 1, not_null => 1 }, + periodicity => { type => 'varchar', length => 1, not_null => 1 }, print => { type => 'boolean', default => 'false' }, printer_id => { type => 'integer' }, start_date => { type => 'date' }, diff --git a/SL/DB/PeriodicInvoicesConfig.pm b/SL/DB/PeriodicInvoicesConfig.pm index 8013cd940..0d6608565 100644 --- a/SL/DB/PeriodicInvoicesConfig.pm +++ b/SL/DB/PeriodicInvoicesConfig.pm @@ -11,16 +11,24 @@ __PACKAGE__->meta->initialize; # Creates get_all, get_all_count, get_all_iterator, delete_all and update_all. __PACKAGE__->meta->make_manager_class; -our @PERIODICITIES = qw(m q f b y); -our %PERIOD_LENGTHS = ( m => 1, q => 3, f => 4, b => 6, y => 12 ); +our %PERIOD_LENGTHS = ( m => 1, q => 3, b => 6, y => 12 ); +our %ORDER_VALUE_PERIOD_LENGTHS = ( %PERIOD_LENGTHS, 2 => 24, 3 => 36, 4 => 48, 5 => 60 ); +our @PERIODICITIES = keys %PERIOD_LENGTHS; +our @ORDER_VALUE_PERIODICITIES = keys %ORDER_VALUE_PERIOD_LENGTHS; -sub get_period_length { +sub get_billing_period_length { my $self = shift; return $PERIOD_LENGTHS{ $self->periodicity } || 1; } +sub get_order_value_period_length { + my $self = shift; + return $self->get_billing_period_length if $self->order_value_periodicity eq 'p'; + return $ORDER_VALUE_PERIOD_LENGTHS{ $self->order_value_periodicity } || 1; +} + sub _log_msg { - $::lxdebug->message(LXDebug->DEBUG1(), join('', @_)); + $::lxdebug->message(LXDebug->DEBUG1(), join('', 'SL::DB::PeriodicInvoicesConfig: ', @_)); } sub handle_automatic_extension { @@ -78,12 +86,12 @@ SQL sub calculate_invoice_dates { my ($self, %params) = @_; - my $period_len = $self->get_period_length; - my $cur_date = $self->first_billing_date || $self->start_date; + my $period_len = $self->get_billing_period_length; + my $cur_date = ($self->first_billing_date || $self->start_date)->clone; my $end_date = $self->terminated ? $self->end_date : undef; $end_date //= DateTime->today_local->add(years => 100); - my $start_date = $params{past_dates} ? undef : $self->get_previous_billed_period_start_date; - $start_date = $start_date ? $start_date->add(days => 1) : $cur_date->clone; + my $start_date = $params{past_dates} ? undef : $self->get_previous_billed_period_start_date; + $start_date = $start_date ? $start_date->clone->add(days => 1) : $cur_date->clone; $start_date = max($start_date, $params{start_date}) if $params{start_date}; $end_date = min($end_date, $params{end_date}) if $params{end_date}; @@ -99,4 +107,142 @@ sub calculate_invoice_dates { return @dates; } +sub is_last_bill_date_in_order_value_cycle { + my ($self, %params) = @_; + + my $months_billing = $self->get_billing_period_length; + my $months_order_value = $self->get_order_value_period_length; + + return 1 if $months_billing >= $months_order_value; + + my $next_billing_date = $params{date}->clone->add(months => $months_billing); + my $date_itr = max($self->start_date, $self->first_billing_date || $self->start_date)->clone; + + _log_msg("is_last_billing_date_in_order_value_cycle start: id " . $self->id . " date_itr $date_itr start " . $self->start_date); + + $date_itr->add(months => $months_order_value) while $date_itr < $next_billing_date; + + _log_msg("is_last_billing_date_in_order_value_cycle end: refdate $params{date} next_billing_date $next_billing_date date_itr $date_itr months_billing $months_billing months_order_value $months_order_value result " + . ($date_itr == $next_billing_date)); + + return $date_itr == $next_billing_date; +} + 1; +__END__ + +=pod + +=encoding utf8 + +=head1 NAME + +SL::DB::PeriodicInvoicesConfig - DB model for the configuration for periodic invoices + +=head1 FUNCTIONS + +=over 4 + +=item C + +Calculates dates for which invoices will have to be created. Returns a +list of L objects. + +This function looks at the configuration settings and at the list of +invoices that have already been created for this configuration. The +date range for which dates are created are controlled by several +values: + +=over 2 + +=item * The properties C and C +determine the start date. + +=item * The properties C and C determine the end +date. + +=item * The optional parameter C determines whether or not +dates for which invoices have already been created will be included in +the list. The default is not to include them. + +=item * The optional parameters C and C override +the start and end dates from the configuration. + +=item * If no end date is set or implied via the configuration and no +C parameter is given then the function will use 100 years +in the future as the end date. + +=back + +=item C + +Returns the number of months corresponding to the billing +periodicity. This means that a new invoice has to be created every x +months starting with the value in C (or +C if C is unset). + +=item C + +Returns the number of months the order's value refers to. This looks +at the C. + +Each invoice's value is calculated as C. + +=item C + +Returns the highest date (as an instance of L) for which an +invoice has been created from this configuration. + +=item C + +Configurations which haven't been terminated and which have an end +date set may be eligible for automatic extension by a certain number +of months. This what the function implements. + +If the configuration is not eligible or if the C hasn't been +reached yet then nothing is done and C is returned. Otherwise +its behavior is determined by the C property. + +If the property C is not 0 then the +C will be extended by C months, and +the configuration will be saved. In this case the new end date will be +returned. + +Otherwise (if C is 0) the property C +will be set to 1, and the configuration will be saved. In this case +C will be returned. + +=item C + +Determines whether or not the mandatory parameter C, an instance +of L, is the last billing date within the cycle given by the +order value periodicity. Returns a truish value if this is the case +and a falsish value otherwise. + +This check is always true if the billing periodicity is longer than or +equal to the order value periodicity. For example, if you have an +order whose value is given for three months and you bill every six +months and you have twice the order value on each invoice, meaning +each invoice is itself the last invoice for not only one but two order +value cycles. + +Otherwise (if the order value periodicity is longer than the billing +periodicity) this function iterates over all eligible dates starting +with C (or C if C +is unset) and adding the order value length with each step. If the +date given by the C parameter plus the billing period length +equals one of those dates then the given date is indeed the date of +the last invoice in that particular order value cycle. + +=back + +=head1 BUGS + +Nothing here yet. + +=head1 AUTHOR + +Moritz Bunkus Em.bunkus@linet-services.deE + +=cut diff --git a/SL/LiquidityProjection.pm b/SL/LiquidityProjection.pm index 03a1894c2..710c099d9 100644 --- a/SL/LiquidityProjection.pm +++ b/SL/LiquidityProjection.pm @@ -5,6 +5,7 @@ use strict; use List::MoreUtils qw(uniq); use SL::DBUtils; +use SL::DB::PeriodicInvoicesConfig; sub new { my $package = shift; @@ -128,7 +129,7 @@ SQL SELECT (oi.qty * (1 - oi.discount) * oi.sellprice) AS linetotal, bg.description AS buchungsgruppe, CASE WHEN COALESCE(e.name, '') = '' THEN e.login ELSE e.name END AS salesman, - pcfg.periodicity, pcfg.id AS config_id, + pcfg.periodicity, pcfg.order_value_periodicity, pcfg.id AS config_id, EXTRACT(year FROM pcfg.start_date) AS start_year, EXTRACT(month FROM pcfg.start_date) AS start_month FROM orderitems oi LEFT JOIN oe ON (oi.trans_id = oe.id) @@ -140,7 +141,6 @@ SQL SQL # 3. Iterieren über Saldierungsintervalle, vormerken - my %periodicities = ( 'm' => 1, 'q' => 3, 'y' => 12 ); my @scentries; $sth = prepare_execute_query($::form, $dbh, $query); while ($ref = $sth->fetchrow_hashref) { @@ -148,15 +148,20 @@ SQL my $date; while (($date = _the_date($year, $month)) le $self->{max_date}) { + my $billing_len = $SL::DB::PeriodicInvoicesConfig::PERIOD_LENGTHS{ $ref->{periodicity} } || 1; + if (($date ge $self->{min_date}) && (!$periodic_invoices{ $ref->{config_id} } || !$periodic_invoices{ $ref->{config_id} }->{$date})) { + my $order_value_periodicity = $ref->{order_value_periodicity} eq 'p' ? $ref->{periodicity} : $ref->{order_value_periodicity}; + my $order_value_len = $SL::DB::PeriodicInvoicesConfig::ORDER_VALUE_PERIOD_LENGTHS{$order_value_periodicity} || 1; + push @scentries, { buchungsgruppe => $ref->{buchungsgruppe}, salesman => $ref->{salesman}, - linetotal => $ref->{linetotal}, + linetotal => $ref->{linetotal} * $billing_len / $order_value_len, date => $date, }; } - ($year, $month) = _fix_date($year, $month + ($periodicities{ $ref->{periodicity} } || 1)); + ($year, $month) = _fix_date($year, $month + $billing_len); } } $sth->finish; diff --git a/SL/OE.pm b/SL/OE.pm index 061cd27a0..9944f71cd 100644 --- a/SL/OE.pm +++ b/SL/OE.pm @@ -738,7 +738,7 @@ sub load_periodic_invoice_config { my $config_obj = SL::DB::Manager::PeriodicInvoicesConfig->find_by(oe_id => $form->{id}); if ($config_obj) { - my $config = { map { $_ => $config_obj->$_ } qw(active terminated periodicity start_date_as_date end_date_as_date first_billing_date_as_date extend_automatically_by ar_chart_id + my $config = { map { $_ => $config_obj->$_ } qw(active terminated periodicity order_value_periodicity start_date_as_date end_date_as_date first_billing_date_as_date extend_automatically_by ar_chart_id print printer_id copies) }; $form->{periodic_invoices_config} = YAML::Dump($config); } diff --git a/bin/mozilla/oe.pl b/bin/mozilla/oe.pl index 4483deb1f..147e9d9de 100644 --- a/bin/mozilla/oe.pl +++ b/bin/mozilla/oe.pl @@ -2000,14 +2000,16 @@ sub edit_periodic_invoices_config { $config = YAML::Load($::form->{periodic_invoices_config}) if $::form->{periodic_invoices_config}; if ('HASH' ne ref $config) { - $config = { periodicity => 'y', + $config = { periodicity => 'm', + order_value_periodicity => 'p', # = same as periodicity start_date_as_date => $::form->{transdate} || $::form->current_date, extend_automatically_by => 12, active => 1, }; } - $config->{periodicity} = 'm' if none { $_ eq $config->{periodicity} } qw(m q b y); + $config->{periodicity} = 'm' if none { $_ eq $config->{periodicity} } @SL::DB::PeriodicInvoicesConfig::PERIODICITIES; + $config->{order_value_periodicity} = 'p' if none { $_ eq $config->{order_value_periodicity} } ('p', @SL::DB::PeriodicInvoicesConfig::ORDER_VALUE_PERIODICITIES); $::form->get_lists(printers => "ALL_PRINTERS", charts => { key => 'ALL_CHARTS', @@ -2033,7 +2035,8 @@ sub save_periodic_invoices_config { my $config = { active => $::form->{active} ? 1 : 0, terminated => $::form->{terminated} ? 1 : 0, - periodicity => (any { $_ eq $::form->{periodicity} } qw(m q b y)) ? $::form->{periodicity} : 'm', + periodicity => (any { $_ eq $::form->{periodicity} } @SL::DB::PeriodicInvoicesConfig::PERIODICITIES) ? $::form->{periodicity} : 'm', + order_value_periodicity => (any { $_ eq $::form->{order_value_periodicity} } ('p', @SL::DB::PeriodicInvoicesConfig::ORDER_VALUE_PERIODICITIES)) ? $::form->{order_value_periodicity} : 'p', start_date_as_date => $::form->{start_date_as_date}, end_date_as_date => $::form->{end_date_as_date}, first_billing_date_as_date => $::form->{first_billing_date_as_date}, diff --git a/locale/de/all b/locale/de/all index e0b5e0de0..ffd4375a4 100755 --- a/locale/de/all +++ b/locale/de/all @@ -35,9 +35,13 @@ $self->{texts} = { '...on the TODO list' => '...auf der Aufgabenliste', '0% tax with taxkey' => '0% Steuer mit Steuerschlüssel ', '1. Quarter' => '1. Quartal', + '2 years' => '2 Jahre', '2. Quarter' => '2. Quartal', + '3 years' => '3 Jahre', '3. Quarter' => '3. Quartal', + '4 years' => '4 Jahre', '4. Quarter' => '4. Quartal', + '5 years' => '5 Jahre', ' I DO CARE! Please check create warehouse and bins and define a name for the warehouse (Bins will be created automatically) and then continue' => 'ICH KÜMMER MICH Bitte haken Sie Lager und Lagerplätze erzeugen an (Automatisches Zuweisen der Lagerplätze) und vergeben einen Namen für dieses Lager (Lagerplätze werden automatisch übernommen). Danach auf weiter.', ' I DO CARE! Please click back and cancel the update and come back after there has been at least one warehouse defined with bin(s).:' => 'ICH KÜMMER MICH Brechen Sie das Update ab und legen selber mindestens ein Lager mit Lagerplätzen unter dem Menü System / Lager an.', ' I DO NOT CARE Please click continue and the following data (see list) will be deleted:' => 'IST MIR EGAL Mit einem Klick auf Weiter (rot) werden keine Daten übernommen, bzw. migriert und die folgende Information in der untenstehenden Liste wird gelöscht.', @@ -354,6 +358,7 @@ $self->{texts} = { 'Billed amount' => 'Abgerechneter Betrag', 'Billed extra expenses' => 'Abgerechnete Nebenkosten', 'Billing Address' => 'Rechnungsadresse', + 'Billing Periodicity' => 'Abrechnungsperiodizität', 'Billing/shipping address (city)' => 'Rechnungsadresse (Stadt)', 'Billing/shipping address (country)' => 'Rechnungsadresse (Land)', 'Billing/shipping address (street)' => 'Rechnungsadresse (Straße)', @@ -1731,6 +1736,7 @@ $self->{texts} = { 'Order deleted!' => 'Auftrag gelöscht!', 'Order probability' => 'Auftragswahrscheinlichkeit', 'Order probability & expected billing date' => 'Auftragswahrscheinlichkeit & vorrauss. Abrechnungsdatum', + 'Order value periodicity' => 'Auftragswert basiert auf Periodizität', 'Order/Item row name' => 'Name der Auftrag-/Positions-Zeilen', 'OrderItem' => 'Position', 'Ordered' => 'Von Kunden bestellt', @@ -1821,7 +1827,6 @@ $self->{texts} = { 'Periodic inventory' => 'Aufwandsmethode', 'Periodic invoices active' => 'Wiederkehrende Rechnungen aktiv', 'Periodic invoices inactive' => 'Wiederkehrende Rechnungen inaktiv', - 'Periodicity' => 'Periodizität', 'Perpetual inventory' => 'Bestandsmethode', 'Person' => 'Person', 'Personal settings' => 'Persönliche Einstellungen', @@ -2135,6 +2140,7 @@ $self->{texts} = { 'Sales Invoices' => 'Kundenrechnungen', 'Sales Order' => 'Kundenauftrag', 'Sales Orders' => 'Aufträge', + 'Sales Orders Advance' => 'Auftragsvorlauf', 'Sales Orders deleteable' => 'Kundenaufträge löschbar', 'Sales Price Rules' => 'Preisregeln Verkauf', 'Sales Price Rules ' => 'Preisregeln (Verkauf)', @@ -2813,6 +2819,7 @@ $self->{texts} = { 'Top Level Designation only' => 'Nur Hauptartikelbezeichnung', 'Total' => 'Summe', 'Total Fees' => 'Kumulierte Gebühren', + 'Total Sales Orders Value' => 'Auftragseingang', 'Total stock value' => 'Gesamter Bestandswert', 'Total sum' => 'Gesamtsumme', 'Total weight' => 'Gesamtgewicht', @@ -3240,6 +3247,7 @@ $self->{texts} = { 'sales_order' => 'Kundenauftrag', 'sales_order_list' => 'auftragsliste', 'sales_quotation' => 'Verkaufsangebot', + 'same as periodicity' => 'stimmt mit Abrechnungsperiodizität überein', 'saved' => 'gespeichert', 'saved!' => 'gespeichert', 'saving data' => 'Speichere Daten', diff --git a/sql/Pg-upgrade2/periodic_invoices_order_value_periodicity.sql b/sql/Pg-upgrade2/periodic_invoices_order_value_periodicity.sql new file mode 100644 index 000000000..d2db1d593 --- /dev/null +++ b/sql/Pg-upgrade2/periodic_invoices_order_value_periodicity.sql @@ -0,0 +1,26 @@ +-- @tag: periodic_invoices_order_value_periodicity +-- @description: Wiederkehrende Rechnungen: Einstellung für Periode, auf die sich der Auftragswert bezieht +-- @depends: release_3_1_0 + +-- Spalte »periodicity«: nur ein Zeichen, und Check auf gültige Werte +ALTER TABLE periodic_invoices_configs +ADD CONSTRAINT periodic_invoices_configs_valid_periodicity +CHECK (periodicity IN ('m', 'q', 'b', 'y')); + +ALTER TABLE periodic_invoices_configs +ALTER COLUMN periodicity TYPE varchar(1); + +-- Neue Spalte »order_value_periodicity« +ALTER TABLE periodic_invoices_configs +ADD COLUMN order_value_periodicity varchar(1); + +UPDATE periodic_invoices_configs +SET order_value_periodicity = 'p'; + +ALTER TABLE periodic_invoices_configs +ALTER COLUMN order_value_periodicity +SET NOT NULL; + +ALTER TABLE periodic_invoices_configs +ADD CONSTRAINT periodic_invoices_configs_valid_order_value_periodicity +CHECK (order_value_periodicity IN ('p', 'm', 'q', 'b', 'y', '2', '3', '4', '5')); diff --git a/t/background_job/create_periodic_invoices.t b/t/background_job/create_periodic_invoices.t new file mode 100644 index 000000000..8d1c17076 --- /dev/null +++ b/t/background_job/create_periodic_invoices.t @@ -0,0 +1,240 @@ +package DateTime; + +use SL::Helper::DateTime; + +no warnings 'redefine'; + +sub now_local { + return shift->new(time_zone => $::locale->get_local_time_zone, year => 2014, month => 3, day => 15, hour => 12, minute => 23, second => 34); +} + +sub today_local { + return shift->now_local->truncate(to => 'day'); +} + +package main; + +use Test::More tests => 80; + +use lib 't'; +use strict; +use utf8; + +use Carp; +use Support::TestSetup; + +use_ok 'SL::BackgroundJob::CreatePeriodicInvoices'; +use_ok 'SL::DB::Chart'; +use_ok 'SL::DB::Customer'; +use_ok 'SL::DB::Default'; +use_ok 'SL::DB::Invoice'; +use_ok 'SL::DB::Order'; +use_ok 'SL::DB::Part'; +use_ok 'SL::DB::TaxZone'; + +Support::TestSetup::login(); + +our ($ar_chart, $buchungsgruppe, $currency_id, $customer, $employee, $order, $part, $tax_zone, $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"; +} + +sub create_invoices { + my %params = @_; + + $params{$_} ||= {} for qw(customer part tax order orderitem periodic_invoices_config); + + # Clean up: remove invoices, orders, parts and customers + "SL::DB::Manager::${_}"->delete_all(all => 1) for qw(InvoiceItem Invoice OrderItem Order Customer Part); + + $customer = SL::DB::Customer->new( + name => 'Test Customer', + currency_id => $currency_id, + taxzone_id => $tax_zone->id, + %{ $params{customer} } + )->save; + + $part = SL::DB::Part->new( + partnumber => 'T4254', + description => 'Fourty-two fifty-four', + lastcost => 222.22, + sellprice => 333.33, + 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, + transaction_description => '<%period_start_date%>', + orderitems => [ + { parts_id => $part->id, + description => $part->description, + lastcost => $part->lastcost, + sellprice => $part->sellprice, + qty => 1, + unit => $unit->name, + %{ $params{orderitem} }, + }, + ], + periodic_invoices_config => { + active => 1, + ar_chart_id => $ar_chart->id, + %{ $params{periodic_invoices_config} }, + }, + %{ $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) ]) }; +} + +sub are_invoices { + my ($description, @exp_date_netamount_pairs) = @_; + + is scalar(@invoices), scalar(@exp_date_netamount_pairs), "${description} number of invoices " . scalar(@exp_date_netamount_pairs); + + my @actual_date_netamount_pairs = map { [ $_->transaction_description, $_->netamount * 1 ] } @invoices; + is_deeply \@actual_date_netamount_pairs, \@exp_date_netamount_pairs, "${description} date/netamount of created invoices"; +} + +init_common_state(); + +# order_value_periodicity=y +create_invoices(periodic_invoices_config => { periodicity => 'm', order_value_periodicity => 'y', start_date => DateTime->from_kivitendo('01.01.2013') }); +are_invoices 'p=m ovp=y',[ '01.01.2013', 27.78 ], [ '01.02.2013', 27.78 ], [ '01.03.2013', 27.78 ], [ '01.04.2013', 27.78 ], + [ '01.05.2013', 27.78 ], [ '01.06.2013', 27.78 ], [ '01.07.2013', 27.78 ], [ '01.08.2013', 27.78 ], + [ '01.09.2013', 27.78 ], [ '01.10.2013', 27.78 ], [ '01.11.2013', 27.78 ], [ '01.12.2013', 27.75 ], + [ '01.01.2014', 27.78 ], [ '01.02.2014', 27.78 ], [ '01.03.2014', 27.78 ]; + +create_invoices(periodic_invoices_config => { periodicity => 'q', order_value_periodicity => 'y', start_date => DateTime->from_kivitendo('01.01.2013') }); +are_invoices 'p=q ovp=y',[ '01.01.2013', 83.33 ], [ '01.04.2013', 83.33 ], [ '01.07.2013', 83.33 ], [ '01.10.2013', 83.34 ], [ '01.01.2014', 83.33 ]; + +create_invoices(periodic_invoices_config => { periodicity => 'b', order_value_periodicity => 'y', start_date => DateTime->from_kivitendo('01.01.2013') }); +are_invoices 'p=b ovp=y',[ '01.01.2013', 166.67 ], [ '01.07.2013', 166.66 ], [ '01.01.2014', 166.67 ]; + +create_invoices(periodic_invoices_config => { periodicity => 'y', order_value_periodicity => 'y', start_date => DateTime->from_kivitendo('01.01.2013') }); +are_invoices 'p=y ovp=y',[ '01.01.2013', 333.33 ], [ '01.01.2014', 333.33 ]; + +# order_value_periodicity=b +create_invoices(periodic_invoices_config => { periodicity => 'm', order_value_periodicity => 'b', start_date => DateTime->from_kivitendo('01.01.2013') }); +are_invoices 'p=m ovp=b',[ '01.01.2013', 55.56 ], [ '01.02.2013', 55.56 ], [ '01.03.2013', 55.56 ], [ '01.04.2013', 55.56 ], + [ '01.05.2013', 55.56 ], [ '01.06.2013', 55.53 ], [ '01.07.2013', 55.56 ], [ '01.08.2013', 55.56 ], + [ '01.09.2013', 55.56 ], [ '01.10.2013', 55.56 ], [ '01.11.2013', 55.56 ], [ '01.12.2013', 55.53 ], + [ '01.01.2014', 55.56 ], [ '01.02.2014', 55.56 ], [ '01.03.2014', 55.56 ]; + +create_invoices(periodic_invoices_config => { periodicity => 'q', order_value_periodicity => 'b', start_date => DateTime->from_kivitendo('01.01.2013') }); +are_invoices 'p=q ovp=b',[ '01.01.2013', 166.67 ], [ '01.04.2013', 166.66 ], [ '01.07.2013', 166.67 ], [ '01.10.2013', 166.66 ], [ '01.01.2014', 166.67 ]; + +create_invoices(periodic_invoices_config => { periodicity => 'b', order_value_periodicity => 'b', start_date => DateTime->from_kivitendo('01.01.2013') }); +are_invoices 'p=b ovp=b',[ '01.01.2013', 333.33 ], [ '01.07.2013', 333.33 ], [ '01.01.2014', 333.33 ]; + +create_invoices(periodic_invoices_config => { periodicity => 'y', order_value_periodicity => 'b', start_date => DateTime->from_kivitendo('01.01.2013') }); +are_invoices 'p=y ovp=b',[ '01.01.2013', 666.66 ], [ '01.01.2014', 666.66 ]; + +# order_value_periodicity=q +create_invoices(periodic_invoices_config => { periodicity => 'm', order_value_periodicity => 'q', start_date => DateTime->from_kivitendo('01.01.2013') }); +are_invoices 'p=m ovp=q',[ '01.01.2013', 111.11 ], [ '01.02.2013', 111.11 ], [ '01.03.2013', 111.11 ], [ '01.04.2013', 111.11 ], + [ '01.05.2013', 111.11 ], [ '01.06.2013', 111.11 ], [ '01.07.2013', 111.11 ], [ '01.08.2013', 111.11 ], + [ '01.09.2013', 111.11 ], [ '01.10.2013', 111.11 ], [ '01.11.2013', 111.11 ], [ '01.12.2013', 111.11 ], + [ '01.01.2014', 111.11 ], [ '01.02.2014', 111.11 ], [ '01.03.2014', 111.11 ]; + +create_invoices(periodic_invoices_config => { periodicity => 'q', order_value_periodicity => 'q', start_date => DateTime->from_kivitendo('01.01.2013') }); +are_invoices 'p=q ovp=q',[ '01.01.2013', 333.33 ], [ '01.04.2013', 333.33 ], [ '01.07.2013', 333.33 ], [ '01.10.2013', 333.33 ], [ '01.01.2014', 333.33 ]; + +create_invoices(periodic_invoices_config => { periodicity => 'b', order_value_periodicity => 'q', start_date => DateTime->from_kivitendo('01.01.2013') }); +are_invoices 'p=b ovp=q',[ '01.01.2013', 666.66 ], [ '01.07.2013', 666.66 ], [ '01.01.2014', 666.66 ]; + +create_invoices(periodic_invoices_config => { periodicity => 'y', order_value_periodicity => 'q', start_date => DateTime->from_kivitendo('01.01.2013') }); +are_invoices 'p=y ovp=q',[ '01.01.2013', 1333.32 ], [ '01.01.2014', 1333.32 ]; + +# order_value_periodicity=m +create_invoices(periodic_invoices_config => { periodicity => 'm', order_value_periodicity => 'm', start_date => DateTime->from_kivitendo('01.01.2013') }); +are_invoices 'p=m ovp=m',[ '01.01.2013', 333.33 ], [ '01.02.2013', 333.33 ], [ '01.03.2013', 333.33 ], [ '01.04.2013', 333.33 ], + [ '01.05.2013', 333.33 ], [ '01.06.2013', 333.33 ], [ '01.07.2013', 333.33 ], [ '01.08.2013', 333.33 ], + [ '01.09.2013', 333.33 ], [ '01.10.2013', 333.33 ], [ '01.11.2013', 333.33 ], [ '01.12.2013', 333.33 ], + [ '01.01.2014', 333.33 ], [ '01.02.2014', 333.33 ], [ '01.03.2014', 333.33 ]; + +create_invoices(periodic_invoices_config => { periodicity => 'q', order_value_periodicity => 'm', start_date => DateTime->from_kivitendo('01.01.2013') }); +are_invoices 'p=q ovp=m',[ '01.01.2013', 999.99 ], [ '01.04.2013', 999.99 ], [ '01.07.2013', 999.99 ], [ '01.10.2013', 999.99 ], [ '01.01.2014', 999.99 ]; + +create_invoices(periodic_invoices_config => { periodicity => 'b', order_value_periodicity => 'm', start_date => DateTime->from_kivitendo('01.01.2013') }); +are_invoices 'p=b ovp=m',[ '01.01.2013', 1999.98 ], [ '01.07.2013', 1999.98 ], [ '01.01.2014', 1999.98 ]; + +create_invoices(periodic_invoices_config => { periodicity => 'y', order_value_periodicity => 'm', start_date => DateTime->from_kivitendo('01.01.2013') }); +are_invoices 'p=y ovp=m',[ '01.01.2013', 3999.96 ], [ '01.01.2014', 3999.96 ]; + +# order_value_periodicity=2 +create_invoices(periodic_invoices_config => { periodicity => 'm', order_value_periodicity => '2', start_date => DateTime->from_kivitendo('01.01.2012') }); +are_invoices 'p=m ovp=2',[ '01.01.2012', 13.89 ], [ '01.02.2012', 13.89 ], [ '01.03.2012', 13.89 ], [ '01.04.2012', 13.89 ], + [ '01.05.2012', 13.89 ], [ '01.06.2012', 13.89 ], [ '01.07.2012', 13.89 ], [ '01.08.2012', 13.89 ], + [ '01.09.2012', 13.89 ], [ '01.10.2012', 13.89 ], [ '01.11.2012', 13.89 ], [ '01.12.2012', 13.89 ], + [ '01.01.2013', 13.89 ], [ '01.02.2013', 13.89 ], [ '01.03.2013', 13.89 ], [ '01.04.2013', 13.89 ], + [ '01.05.2013', 13.89 ], [ '01.06.2013', 13.89 ], [ '01.07.2013', 13.89 ], [ '01.08.2013', 13.89 ], + [ '01.09.2013', 13.89 ], [ '01.10.2013', 13.89 ], [ '01.11.2013', 13.89 ], [ '01.12.2013', 13.86 ], + [ '01.01.2014', 13.89 ], [ '01.02.2014', 13.89 ], [ '01.03.2014', 13.89 ]; + +create_invoices(periodic_invoices_config => { periodicity => 'q', order_value_periodicity => '2', start_date => DateTime->from_kivitendo('01.01.2012') }); +are_invoices 'p=q ovp=2',[ '01.01.2012', 41.67 ], [ '01.04.2012', 41.67 ], [ '01.07.2012', 41.67 ], [ '01.10.2012', 41.67 ], + [ '01.01.2013', 41.67 ], [ '01.04.2013', 41.67 ], [ '01.07.2013', 41.67 ], [ '01.10.2013', 41.64 ], + [ '01.01.2014', 41.67 ]; + +create_invoices(periodic_invoices_config => { periodicity => 'b', order_value_periodicity => '2', start_date => DateTime->from_kivitendo('01.01.2012') }); +are_invoices 'p=b ovp=2',[ '01.01.2012', 83.33 ], [ '01.07.2012', 83.33 ], [ '01.01.2013', 83.33 ], [ '01.07.2013', 83.34 ], [ '01.01.2014', 83.33 ]; + +create_invoices(periodic_invoices_config => { periodicity => 'y', order_value_periodicity => '2', start_date => DateTime->from_kivitendo('01.01.2012') }); +are_invoices 'p=y ovp=2',[ '01.01.2012', 166.67 ], [ '01.01.2013', 166.66 ], [ '01.01.2014', 166.67 ]; + +# order_value_periodicity=5 +create_invoices(periodic_invoices_config => { periodicity => 'm', order_value_periodicity => '5', start_date => DateTime->from_kivitendo('01.01.2009') }); +are_invoices 'p=m ovp=5',[ '01.01.2009', 5.56 ], [ '01.02.2009', 5.56 ], [ '01.03.2009', 5.56 ], [ '01.04.2009', 5.56 ], + [ '01.05.2009', 5.56 ], [ '01.06.2009', 5.56 ], [ '01.07.2009', 5.56 ], [ '01.08.2009', 5.56 ], + [ '01.09.2009', 5.56 ], [ '01.10.2009', 5.56 ], [ '01.11.2009', 5.56 ], [ '01.12.2009', 5.56 ], + [ '01.01.2010', 5.56 ], [ '01.02.2010', 5.56 ], [ '01.03.2010', 5.56 ], [ '01.04.2010', 5.56 ], + [ '01.05.2010', 5.56 ], [ '01.06.2010', 5.56 ], [ '01.07.2010', 5.56 ], [ '01.08.2010', 5.56 ], + [ '01.09.2010', 5.56 ], [ '01.10.2010', 5.56 ], [ '01.11.2010', 5.56 ], [ '01.12.2010', 5.56 ], + [ '01.01.2011', 5.56 ], [ '01.02.2011', 5.56 ], [ '01.03.2011', 5.56 ], [ '01.04.2011', 5.56 ], + [ '01.05.2011', 5.56 ], [ '01.06.2011', 5.56 ], [ '01.07.2011', 5.56 ], [ '01.08.2011', 5.56 ], + [ '01.09.2011', 5.56 ], [ '01.10.2011', 5.56 ], [ '01.11.2011', 5.56 ], [ '01.12.2011', 5.56 ], + [ '01.01.2012', 5.56 ], [ '01.02.2012', 5.56 ], [ '01.03.2012', 5.56 ], [ '01.04.2012', 5.56 ], + [ '01.05.2012', 5.56 ], [ '01.06.2012', 5.56 ], [ '01.07.2012', 5.56 ], [ '01.08.2012', 5.56 ], + [ '01.09.2012', 5.56 ], [ '01.10.2012', 5.56 ], [ '01.11.2012', 5.56 ], [ '01.12.2012', 5.56 ], + [ '01.01.2013', 5.56 ], [ '01.02.2013', 5.56 ], [ '01.03.2013', 5.56 ], [ '01.04.2013', 5.56 ], + [ '01.05.2013', 5.56 ], [ '01.06.2013', 5.56 ], [ '01.07.2013', 5.56 ], [ '01.08.2013', 5.56 ], + [ '01.09.2013', 5.56 ], [ '01.10.2013', 5.56 ], [ '01.11.2013', 5.56 ], [ '01.12.2013', 5.29 ], + [ '01.01.2014', 5.56 ], [ '01.02.2014', 5.56 ], [ '01.03.2014', 5.56 ]; + +create_invoices(periodic_invoices_config => { periodicity => 'q', order_value_periodicity => '5', start_date => DateTime->from_kivitendo('01.01.2009') }); +are_invoices 'p=q ovp=5',[ '01.01.2009', 16.67 ], [ '01.04.2009', 16.67 ], [ '01.07.2009', 16.67 ], [ '01.10.2009', 16.67 ], + [ '01.01.2010', 16.67 ], [ '01.04.2010', 16.67 ], [ '01.07.2010', 16.67 ], [ '01.10.2010', 16.67 ], + [ '01.01.2011', 16.67 ], [ '01.04.2011', 16.67 ], [ '01.07.2011', 16.67 ], [ '01.10.2011', 16.67 ], + [ '01.01.2012', 16.67 ], [ '01.04.2012', 16.67 ], [ '01.07.2012', 16.67 ], [ '01.10.2012', 16.67 ], + [ '01.01.2013', 16.67 ], [ '01.04.2013', 16.67 ], [ '01.07.2013', 16.67 ], [ '01.10.2013', 16.60 ], + [ '01.01.2014', 16.67 ]; + +create_invoices(periodic_invoices_config => { periodicity => 'b', order_value_periodicity => '5', start_date => DateTime->from_kivitendo('01.01.2009') }); +are_invoices 'p=b ovp=5',[ '01.01.2009', 33.33 ], [ '01.07.2009', 33.33 ], + [ '01.01.2010', 33.33 ], [ '01.07.2010', 33.33 ], + [ '01.01.2011', 33.33 ], [ '01.07.2011', 33.33 ], + [ '01.01.2012', 33.33 ], [ '01.07.2012', 33.33 ], + [ '01.01.2013', 33.33 ], [ '01.07.2013', 33.36 ], + [ '01.01.2014', 33.33 ]; + +create_invoices(periodic_invoices_config => { periodicity => 'y', order_value_periodicity => '5', start_date => DateTime->from_kivitendo('01.01.2009') }); +are_invoices 'p=y ovp=5',[ '01.01.2009', 66.67 ], [ '01.01.2010', 66.67 ], [ '01.01.2011', 66.67 ], [ '01.01.2012', 66.67 ], [ '01.01.2013', 66.65 ], [ '01.01.2014', 66.67 ]; + +done_testing(); diff --git a/t/controllers/financial_controlling/sales_order_with_periodic_invoices_config.t b/t/controllers/financial_controlling/sales_order_with_periodic_invoices_config.t new file mode 100644 index 000000000..1dab1e8f0 --- /dev/null +++ b/t/controllers/financial_controlling/sales_order_with_periodic_invoices_config.t @@ -0,0 +1,548 @@ +package DateTime; + +use SL::Helper::DateTime; + +no warnings 'redefine'; + +sub now_local { + return shift->new(time_zone => $::locale->get_local_time_zone, year => 2014, month => 3, day => 15, hour => 12, minute => 23, second => 34); +} + +sub today_local { + return shift->now_local->truncate(to => 'day'); +} + +package main; + +use Test::More; # tests => 49; + +use lib 't'; +use strict; +use utf8; + +use Carp; +use Support::TestSetup; + +use_ok 'SL::BackgroundJob::CreatePeriodicInvoices'; +use_ok 'SL::Controller::FinancialControllingReport'; +use_ok 'SL::DB::Chart'; +use_ok 'SL::DB::Customer'; +use_ok 'SL::DB::Default'; +use_ok 'SL::DB::Invoice'; +use_ok 'SL::DB::Order'; +use_ok 'SL::DB::Part'; +use_ok 'SL::DB::TaxZone'; + +Support::TestSetup::login(); + +our ($ar_chart, $buchungsgruppe, $ctrl, $currency_id, $customer, $employee, $order, $part, $tax_zone, $unit, @invoices); + +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"; +} + +sub create_sales_order { + my %params = @_; + + $params{$_} ||= {} for qw(customer part tax order orderitem); + + # Clean up: remove invoices, orders, parts and customers + "SL::DB::Manager::${_}"->delete_all(all => 1) for qw(InvoiceItem Invoice OrderItem Order Customer Part); + + $customer = SL::DB::Customer->new( + name => 'Test Customer', + currency_id => $currency_id, + taxzone_id => $tax_zone->id, + %{ $params{customer} } + )->save; + + $part = SL::DB::Part->new( + partnumber => 'T4254', + description => 'Fourty-two fifty-four', + lastcost => 222.22, + sellprice => 333.33, + 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, + 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} }, + }, + ], + periodic_invoices_config => $params{periodic_invoices_config} ? { + active => 1, + ar_chart_id => $ar_chart->id, + %{ $params{periodic_invoices_config} }, + } : undef, + %{ $params{order} }, + ); + + $order->calculate_prices_and_taxes; + + ok($order->save(cascade => 1)); + + $::form = Form->new(''); + $ctrl = SL::Controller::FinancialControllingReport->new; + + $ctrl->orders($ctrl->models->get); + $ctrl->calculate_data; +} + +my @columns = qw(net_amount other_amount + delivered_amount billed_amount paid_amount billable_amount + delivered_amount_p billed_amount_p paid_amount_p billable_amount_p); + +sub run_tests { + my ($msg, $num_orders, $values, %order_params) = @_; + + create_sales_order(%order_params); + + is($num_orders, scalar @{ $ctrl->orders }, "${msg}, #orders"); + is_deeply([ map { ($ctrl->orders->[0]->{$_} // 0) * 1 } @columns ], + $values, + "${msg}, values"); +} + +init_common_state(); + +# ---------------------------------------------------------------------- +# An order without periodic invoices: +run_tests("no periodic conf", 1, [ 333.33, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]); + +# ---------------------------------------------------------------------- +# order_value_periodicity=y, periodicity=q + +run_tests( + "periodic conf p=q ovp=y, starting in previous year", 1, + [ 333.33, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], + periodic_invoices_config => { + periodicity => 'q', + order_value_periodicity => 'y', + start_date => DateTime->from_kivitendo('01.05.2013'), + }); + +run_tests( + "periodic conf p=q ovp=y, starting and ending in previous year", 1, + [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], + periodic_invoices_config => { + periodicity => 'q', + order_value_periodicity => 'y', + terminated => 1, + start_date => DateTime->from_kivitendo('01.05.2013'), + end_date => DateTime->from_kivitendo('01.12.2013'), + }); + +run_tests( + "periodic conf p=q ovp=y, starting in next year", 1, + [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], + periodic_invoices_config => { + periodicity => 'q', + order_value_periodicity => 'y', + start_date => DateTime->from_kivitendo('01.01.2015'), + }); + +run_tests( + "periodic conf p=q ovp=y, starting January 1st of current year", 1, + [ 333.33, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], + periodic_invoices_config => { + periodicity => 'q', + order_value_periodicity => 'y', + start_date => DateTime->from_kivitendo('01.01.2014'), + }); + +run_tests( + "periodic conf p=q ovp=y, starting July 1st of current year", 1, + [ 166.665, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], + periodic_invoices_config => { + periodicity => 'q', + order_value_periodicity => 'y', + start_date => DateTime->from_kivitendo('01.07.2014'), + }); + +run_tests( + "periodic conf p=q ovp=y, starting May 1st of current year", 1, + [ 249.9975, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], + periodic_invoices_config => { + periodicity => 'q', + order_value_periodicity => 'y', + start_date => DateTime->from_kivitendo('01.05.2014'), + }); + +run_tests( + "periodic conf p=q ovp=y, starting January 1st of current year, ending June 30", 1, + [ 166.665, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], + periodic_invoices_config => { + periodicity => 'q', + order_value_periodicity => 'y', + start_date => DateTime->from_kivitendo('01.01.2014'), + end_date => DateTime->from_kivitendo('30.06.2014'), + terminated => 1, + }); + +run_tests( + "periodic conf p=q ovp=y, starting July 1st of current year, ending November 30", 1, + [ 166.665, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], + periodic_invoices_config => { + periodicity => 'q', + order_value_periodicity => 'y', + start_date => DateTime->from_kivitendo('01.07.2014'), + end_date => DateTime->from_kivitendo('30.11.2014'), + terminated => 1, + }); + +run_tests( + "periodic conf p=q ovp=y, starting May 1st of current year, ending next year", 1, + [ 249.9975, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], + periodic_invoices_config => { + periodicity => 'q', + order_value_periodicity => 'y', + start_date => DateTime->from_kivitendo('01.05.2014'), + end_date => DateTime->from_kivitendo('30.06.2015'), + terminated => 1, + }); + +run_tests( + "periodic conf p=q ovp=y, starting November 1 in previous year, ending April 30", 1, + [ 83.3325, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], + periodic_invoices_config => { + periodicity => 'q', + order_value_periodicity => 'y', + terminated => 1, + start_date => DateTime->from_kivitendo('01.11.2013'), + end_date => DateTime->from_kivitendo('30.04.2014'), + }); + + +# ---------------------------------------------------------------------- +# order_value_periodicity=y, periodicity=m + +run_tests( + "periodic conf p=m ovp=y, starting in previous year", 1, + [ 333.33, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], + periodic_invoices_config => { + periodicity => 'm', + order_value_periodicity => 'y', + start_date => DateTime->from_kivitendo('01.05.2013'), + }); + +run_tests( + "periodic conf p=m ovp=y, starting and ending in previous year", 1, + [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], + periodic_invoices_config => { + periodicity => 'm', + order_value_periodicity => 'y', + terminated => 1, + start_date => DateTime->from_kivitendo('01.05.2013'), + end_date => DateTime->from_kivitendo('01.12.2013'), + }); + +run_tests( + "periodic conf p=m ovp=y, starting in next year", 1, + [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], + periodic_invoices_config => { + periodicity => 'm', + order_value_periodicity => 'y', + start_date => DateTime->from_kivitendo('01.01.2015'), + }); + +run_tests( + "periodic conf p=m ovp=y, starting January 1st of current year", 1, + [ 333.33, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], + periodic_invoices_config => { + periodicity => 'm', + order_value_periodicity => 'y', + start_date => DateTime->from_kivitendo('01.01.2014'), + }); + +run_tests( + "periodic conf p=m ovp=y, starting July 1st of current year", 1, + [ 166.665, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], + periodic_invoices_config => { + periodicity => 'm', + order_value_periodicity => 'y', + start_date => DateTime->from_kivitendo('01.07.2014'), + }); + +run_tests( + "periodic conf p=m ovp=y, starting May 1st of current year", 1, + [ 222.22, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], + periodic_invoices_config => { + periodicity => 'm', + order_value_periodicity => 'y', + start_date => DateTime->from_kivitendo('01.05.2014'), + }); + +run_tests( + "periodic conf p=m ovp=y, starting January 1st of current year, ending June 30", 1, + [ 166.665, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], + periodic_invoices_config => { + periodicity => 'm', + order_value_periodicity => 'y', + start_date => DateTime->from_kivitendo('01.01.2014'), + end_date => DateTime->from_kivitendo('30.06.2014'), + terminated => 1, + }); + +run_tests( + "periodic conf p=m ovp=y, starting July 1st of current year, ending November 30", 1, + [ 138.8875, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], + periodic_invoices_config => { + periodicity => 'm', + order_value_periodicity => 'y', + start_date => DateTime->from_kivitendo('01.07.2014'), + end_date => DateTime->from_kivitendo('30.11.2014'), + terminated => 1, + }); + +run_tests( + "periodic conf p=m ovp=y, starting May 1st of current year, ending next year", 1, + [ 222.22, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], + periodic_invoices_config => { + periodicity => 'm', + order_value_periodicity => 'y', + start_date => DateTime->from_kivitendo('01.05.2014'), + end_date => DateTime->from_kivitendo('30.06.2015'), + terminated => 1, + }); + +run_tests( + "periodic conf p=m ovp=y, starting November 1 in previous year, ending April 30", 1, + [ 111.11, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], + periodic_invoices_config => { + periodicity => 'm', + order_value_periodicity => 'y', + terminated => 1, + start_date => DateTime->from_kivitendo('01.11.2013'), + end_date => DateTime->from_kivitendo('30.04.2014'), + }); + + +# ---------------------------------------------------------------------- +# order_value_periodicity=y, periodicity=q + +run_tests( + "periodic conf p=q ovp=2, starting in previous year", 1, + [ 166.665, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], + periodic_invoices_config => { + periodicity => 'q', + order_value_periodicity => '2', + start_date => DateTime->from_kivitendo('01.05.2013'), + }); + +run_tests( + "periodic conf p=q ovp=2, starting and ending in previous year", 1, + [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], + periodic_invoices_config => { + periodicity => 'q', + order_value_periodicity => '2', + terminated => 1, + start_date => DateTime->from_kivitendo('01.05.2013'), + end_date => DateTime->from_kivitendo('01.12.2013'), + }); + +run_tests( + "periodic conf p=q ovp=2, starting in next year", 1, + [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], + periodic_invoices_config => { + periodicity => 'q', + order_value_periodicity => '2', + start_date => DateTime->from_kivitendo('01.01.2015'), + }); + +run_tests( + "periodic conf p=q ovp=2, starting January 1st of current year", 1, + [ 166.665, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], + periodic_invoices_config => { + periodicity => 'q', + order_value_periodicity => '2', + start_date => DateTime->from_kivitendo('01.01.2014'), + }); + +run_tests( + "periodic conf p=q ovp=2, starting July 1st of current year", 1, + [ 83.3325, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], + periodic_invoices_config => { + periodicity => 'q', + order_value_periodicity => '2', + start_date => DateTime->from_kivitendo('01.07.2014'), + }); + +run_tests( + "periodic conf p=q ovp=2, starting May 1st of current year", 1, + [ 124.99875, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], + periodic_invoices_config => { + periodicity => 'q', + order_value_periodicity => '2', + start_date => DateTime->from_kivitendo('01.05.2014'), + }); + +run_tests( + "periodic conf p=q ovp=2, starting January 1st of current year, ending June 30", 1, + [ 83.3325, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], + periodic_invoices_config => { + periodicity => 'q', + order_value_periodicity => '2', + start_date => DateTime->from_kivitendo('01.01.2014'), + end_date => DateTime->from_kivitendo('30.06.2014'), + terminated => 1, + }); + +run_tests( + "periodic conf p=q ovp=2, starting July 1st of current year, ending November 30", 1, + [ 83.3325, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], + periodic_invoices_config => { + periodicity => 'q', + order_value_periodicity => '2', + start_date => DateTime->from_kivitendo('01.07.2014'), + end_date => DateTime->from_kivitendo('30.11.2014'), + terminated => 1, + }); + +run_tests( + "periodic conf p=q ovp=2, starting May 1st of current year, ending next year", 1, + [ 124.99875, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], + periodic_invoices_config => { + periodicity => 'q', + order_value_periodicity => '2', + start_date => DateTime->from_kivitendo('01.05.2014'), + end_date => DateTime->from_kivitendo('30.06.2015'), + terminated => 1, + }); + +run_tests( + "periodic conf p=q ovp=2, starting November 1 in previous year, ending April 30", 1, + [ 41.66625, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], + periodic_invoices_config => { + periodicity => 'q', + order_value_periodicity => '2', + terminated => 1, + start_date => DateTime->from_kivitendo('01.11.2013'), + end_date => DateTime->from_kivitendo('30.04.2014'), + }); + + + +# ---------------------------------------------------------------------- +# order_value_periodicity=m, periodicity=b + +run_tests( + "periodic conf p=b ovp=m, starting in previous year", 1, + [ 3999.96, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], + periodic_invoices_config => { + periodicity => 'b', + order_value_periodicity => 'm', + start_date => DateTime->from_kivitendo('01.05.2013'), + }); + +run_tests( + "periodic conf p=b ovp=m, starting and ending in previous year", 1, + [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], + periodic_invoices_config => { + periodicity => 'b', + order_value_periodicity => 'm', + terminated => 1, + start_date => DateTime->from_kivitendo('01.05.2013'), + end_date => DateTime->from_kivitendo('01.12.2013'), + }); + +run_tests( + "periodic conf p=b ovp=m, starting in next year", 1, + [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], + periodic_invoices_config => { + periodicity => 'b', + order_value_periodicity => 'm', + start_date => DateTime->from_kivitendo('01.01.2015'), + }); + +run_tests( + "periodic conf p=b ovp=m, starting January 1st of current year", 1, + [ 3999.96, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], + periodic_invoices_config => { + periodicity => 'b', + order_value_periodicity => 'm', + start_date => DateTime->from_kivitendo('01.01.2014'), + }); + +run_tests( + "periodic conf p=b ovp=m, starting July 1st of current year", 1, + [ 1999.98, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], + periodic_invoices_config => { + periodicity => 'b', + order_value_periodicity => 'm', + start_date => DateTime->from_kivitendo('01.07.2014'), + }); + +run_tests( + "periodic conf p=b ovp=m, starting May 1st of current year", 1, + [ 3999.96, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], + periodic_invoices_config => { + periodicity => 'b', + order_value_periodicity => 'm', + start_date => DateTime->from_kivitendo('01.05.2014'), + }); + +run_tests( + "periodic conf p=b ovp=m, starting January 1st of current year, ending June 30", 1, + [ 1999.98, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], + periodic_invoices_config => { + periodicity => 'b', + order_value_periodicity => 'm', + start_date => DateTime->from_kivitendo('01.01.2014'), + end_date => DateTime->from_kivitendo('30.06.2014'), + terminated => 1, + }); + +run_tests( + "periodic conf p=b ovp=m, starting July 1st of current year, ending November 30", 1, + [ 1999.98, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], + periodic_invoices_config => { + periodicity => 'b', + order_value_periodicity => 'm', + start_date => DateTime->from_kivitendo('01.07.2014'), + end_date => DateTime->from_kivitendo('30.11.2014'), + terminated => 1, + }); + +run_tests( + "periodic conf p=b ovp=m, starting May 1st of current year, ending next year", 1, + [ 3999.96, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], + periodic_invoices_config => { + periodicity => 'b', + order_value_periodicity => 'm', + start_date => DateTime->from_kivitendo('01.05.2014'), + end_date => DateTime->from_kivitendo('30.06.2015'), + terminated => 1, + }); + +run_tests( + "periodic conf p=b ovp=m, starting November 1 in previous year, ending April 30", 1, + [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], + periodic_invoices_config => { + periodicity => 'b', + order_value_periodicity => 'm', + terminated => 1, + start_date => DateTime->from_kivitendo('01.11.2013'), + end_date => DateTime->from_kivitendo('30.04.2014'), + }); + + +done_testing(); diff --git a/t/controllers/financial_overview/sales_orders.t b/t/controllers/financial_overview/sales_orders.t new file mode 100644 index 000000000..9b278be26 --- /dev/null +++ b/t/controllers/financial_overview/sales_orders.t @@ -0,0 +1,206 @@ +package DateTime; + +use SL::Helper::DateTime; + +no warnings 'redefine'; + +sub now_local { + return shift->new(time_zone => $::locale->get_local_time_zone, year => 2014, month => 3, day => 15, hour => 12, minute => 23, second => 34); +} + +sub today_local { + return shift->now_local->truncate(to => 'day'); +} + +package main; + +use Test::More tests => 49; + +use lib 't'; +use strict; +use utf8; + +use Carp; +use Support::TestSetup; + +use_ok 'SL::BackgroundJob::CreatePeriodicInvoices'; +use_ok 'SL::Controller::FinancialOverview'; +use_ok 'SL::DB::Chart'; +use_ok 'SL::DB::Customer'; +use_ok 'SL::DB::Default'; +use_ok 'SL::DB::Invoice'; +use_ok 'SL::DB::Order'; +use_ok 'SL::DB::Part'; +use_ok 'SL::DB::TaxZone'; + +Support::TestSetup::login(); + +our ($ar_chart, $buchungsgruppe, $ctrl, $currency_id, $customer, $employee, $order, $part, $tax_zone, $unit, @invoices); + +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"; +} + +sub create_sales_order { + my %params = @_; + + $params{$_} ||= {} for qw(customer part tax order orderitem); + + # Clean up: remove invoices, orders, parts and customers + "SL::DB::Manager::${_}"->delete_all(all => 1) for qw(InvoiceItem Invoice OrderItem Order Customer Part); + + $customer = SL::DB::Customer->new( + name => 'Test Customer', + currency_id => $currency_id, + taxzone_id => $tax_zone->id, + %{ $params{customer} } + )->save; + + $part = SL::DB::Part->new( + partnumber => 'T4254', + description => 'Fourty-two fifty-four', + lastcost => 222.22, + sellprice => 333.33, + 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, + 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} }, + }, + ], + periodic_invoices_config => $params{periodic_invoices_config} ? { + active => 1, + ar_chart_id => $ar_chart->id, + %{ $params{periodic_invoices_config} }, + } : undef, + %{ $params{order} }, + ); + + $order->calculate_prices_and_taxes; + + ok($order->save(cascade => 1)); + + $::form = Form->new(''); + $::form->{year} = 2014; + $ctrl = SL::Controller::FinancialOverview->new; + + $ctrl->get_objects; + $ctrl->calculate_one_time_data; + $ctrl->calculate_periodic_invoices; +} + +init_common_state(); + +# ---------------------------------------------------------------------- +# An order without periodic invoices: +create_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); + +is_deeply($ctrl->data->{$_}, { months => [ 0, 0, 333.33, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], quarters => [ 333.33, 0, 0, 0 ], year => 333.33 }, "no periodic invoices, data for $_") + for qw(sales_orders sales_orders_per_inv); + +# ---------------------------------------------------------------------- +# order_value_periodicity=y, periodicity=q +create_sales_order( + periodic_invoices_config => { + periodicity => 'm', + order_value_periodicity => 'y', + start_date => DateTime->from_kivitendo('01.05.2014'), + }); + +is_deeply($ctrl->data->{$_}, { months => [ (0) x 12 ], quarters => [ 0, 0, 0, 0 ], year => 0 }, "periodic conf p=m ovp=y, no invoices, data for $_") + for qw(purchase_invoices purchase_orders requests_for_quotation sales_invoices sales_quotations); + +is_deeply($ctrl->data->{sales_orders}, + { months => [ 0, 0, 0, 0, 27.7775, 27.7775, 27.7775, 27.7775, 27.7775, 27.7775, 27.7775, 27.7775 ], quarters => [ 0, 55.555, 83.3325, 83.3325 ], year => 222.22 }, + "periodic conf p=m ovp=y, no invoices, data for sales_orders"); +is_deeply($ctrl->data->{sales_orders_per_inv}, + { months => [ 0, 0, 333.33, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], quarters => [ 333.33, 0, 0, 0 ], year => 333.33 }, + "periodic conf p=m ovp=y, no invoices, data for sales_orders_per_inv"); + +# ---------------------------------------------------------------------- +# order_value_periodicity=y, periodicity=q, starting in previous year +create_sales_order( + order => { + transdate => DateTime->from_kivitendo('01.03.2013'), + }, + periodic_invoices_config => { + periodicity => 'q', + order_value_periodicity => 'y', + start_date => DateTime->from_kivitendo('01.05.2013'), + }); + +is_deeply($ctrl->data->{$_}, { months => [ (0) x 12 ], quarters => [ 0, 0, 0, 0 ], year => 0 }, "periodic conf p=q ovp=y, no invoices, starting previous year, data for $_") + for qw(purchase_invoices purchase_orders requests_for_quotation sales_invoices sales_quotations); + +is_deeply($ctrl->data->{sales_orders}, + { months => [ 0, 83.3325, 0, 0, 83.3325, 0, 0, 83.3325, 0, 0, 83.3325, 0 ], quarters => [ 83.3325, 83.3325, 83.3325, 83.3325 ], year => 333.33 }, + "periodic conf p=q ovp=y, no invoices, starting previous year, data for sales_orders"); +is_deeply($ctrl->data->{sales_orders_per_inv}, + { months => [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], quarters => [ 0, 0, 0, 0 ], year => 0 }, + "periodic conf p=q ovp=y, no invoices, starting previous year, data for sales_orders_per_inv"); + +# ---------------------------------------------------------------------- +# order_value_periodicity=y, periodicity=q, starting in previous year, ending middle of year +create_sales_order( + order => { + transdate => DateTime->from_kivitendo('01.03.2013'), + }, + periodic_invoices_config => { + periodicity => 'q', + order_value_periodicity => 'y', + start_date => DateTime->from_kivitendo('01.05.2013'), + end_date => DateTime->from_kivitendo('01.09.2014'), + terminated => 1, + }); + +is_deeply($ctrl->data->{$_}, { months => [ (0) x 12 ], quarters => [ 0, 0, 0, 0 ], year => 0 }, "periodic conf p=q ovp=y, no invoices, starting previous year, ending middle of year, data for $_") + for qw(purchase_invoices purchase_orders requests_for_quotation sales_invoices sales_quotations); + +is_deeply($ctrl->data->{sales_orders}, + { months => [ 0, 83.3325, 0, 0, 83.3325, 0, 0, 83.3325, 0, 0, 0, 0 ], quarters => [ 83.3325, 83.3325, 83.3325, 0 ], year => 249.9975 }, + "periodic conf p=q ovp=y, no invoices, starting previous year, ending middle of year, data for sales_orders"); +is_deeply($ctrl->data->{sales_orders_per_inv}, + { months => [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], quarters => [ 0, 0, 0, 0 ], year => 0 }, + "periodic conf p=q ovp=y, no invoices, starting previous year, ending middle of year, data for sales_orders_per_inv"); + +# ---------------------------------------------------------------------- +# order_value_periodicity=y, periodicity=q, starting and ending before current +create_sales_order( + order => { + transdate => DateTime->from_kivitendo('01.03.2012'), + }, + periodic_invoices_config => { + periodicity => 'q', + order_value_periodicity => 'y', + start_date => DateTime->from_kivitendo('01.05.2012'), + end_date => DateTime->from_kivitendo('01.09.2013'), + terminated => 1, + }); + +is_deeply($ctrl->data->{$_}, { months => [ (0) x 12 ], quarters => [ 0, 0, 0, 0 ], year => 0 }, "periodic conf p=q ovp=y, no invoices, starting and ending before current year, data for $_") + for qw(purchase_invoices purchase_orders requests_for_quotation sales_invoices sales_orders sales_orders_per_inv sales_quotations); + +done_testing(); diff --git a/templates/webpages/oe/edit_periodic_invoices_config.html b/templates/webpages/oe/edit_periodic_invoices_config.html index 093cf0574..656dc4326 100644 --- a/templates/webpages/oe/edit_periodic_invoices_config.html +++ b/templates/webpages/oe/edit_periodic_invoices_config.html @@ -1,6 +1,7 @@ [% USE HTML %] [% USE LxERP %] [% USE L %] +[% SET style="width: 400px" %]

[% title %]

@@ -20,15 +21,19 @@ - [%- LxERP.t8('Periodicity') %] + [%- LxERP.t8('Billing Periodicity') %] - [% L.radio_button_tag("periodicity", value => "m", label => LxERP.t8("monthly"), checked => periodicity == 'm') %] -
- [% L.radio_button_tag("periodicity", value => "q", label => LxERP.t8("every third month"), checked => periodicity == 'q') %] -
- [% L.radio_button_tag("periodicity", value => "b", label => LxERP.t8("semiannually"), checked => periodicity == 'b') %] -
- [% L.radio_button_tag("periodicity", value => "y", label => LxERP.t8("yearly"), checked => periodicity == 'y') %] + [% L.select_tag("periodicity", [ [ "m", LxERP.t8("monthly") ], [ "q", LxERP.t8("every third month") ], [ "b", LxERP.t8("semiannually") ], [ "y", LxERP.t8("yearly") ] ], default=periodicity, style=style) %] + + + + + [%- LxERP.t8('Order value periodicity') %] + + [% L.select_tag("order_value_periodicity", + [ [ "p", LxERP.t8("same as periodicity") ], [ "m", LxERP.t8("monthly") ], [ "q", LxERP.t8("every third month") ], [ "b", LxERP.t8("semiannually") ], [ "y", LxERP.t8("yearly") ], + [ "2", LxERP.t8("2 years") ], [ "3", LxERP.t8("3 years") ], [ "4", LxERP.t8("4 years") ], [ "5", LxERP.t8("5 years") ], ], + default=order_value_periodicity, style=style) %] @@ -63,7 +68,7 @@ [%- LxERP.t8('Record in') %] - [% L.select_tag("ar_chart_id", AR, title_key => 'description', default => ar_chart_id) %] + [% L.select_tag("ar_chart_id", AR, title_key => 'description', default => ar_chart_id, style=style) %] @@ -77,7 +82,7 @@ [%- LxERP.t8('Printer') %] - [% L.select_tag("printer_id", ALL_PRINTERS, title_key = 'printer_description', default = printer_id, disabled = !print) %] + [% L.select_tag("printer_id", ALL_PRINTERS, title_key = 'printer_description', default = printer_id, disabled = !print, style=style) %]