}
sub _log_msg {
- my $message = join('', @_);
+ my $message = join('', 'SL::BackgroundJob::CreatePeriodicInvoices: ', @_);
$message .= "\n" unless $message =~ m/\n$/;
$::lxdebug->message(LXDebug::DEBUG1(), $message);
}
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'),
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' ? ('<%', '%>') : ('<%', '%>');
$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();
_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,
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]));
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;
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'); });
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;
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' ]) };
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};
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 {
$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 {
$self->{filter_summary} = join ', ', @filter_strings;
}
+sub init_project_types { SL::DB::Manager::ProjectType->get_all_sorted }
+
sub init_models {
my ($self) = @_;
use parent qw(SL::Controller::Base);
use List::MoreUtils qw(none);
+use List::Util qw(min);
use SL::DB::Employee;
use SL::DB::Invoice;
$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 ]);
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,
$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, ]]),
$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) = @_;
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};
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 {
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' },
# 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 {
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};
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<calculate_invoice_dates %params>
+
+Calculates dates for which invoices will have to be created. Returns a
+list of L<DateTime> 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<first_billing_date> and C<start_date>
+determine the start date.
+
+=item * The properties C<end_date> and C<terminated> determine the end
+date.
+
+=item * The optional parameter C<past_dates> 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<start_date> and C<end_date> override
+the start and end dates from the configuration.
+
+=item * If no end date is set or implied via the configuration and no
+C<end_date> parameter is given then the function will use 100 years
+in the future as the end date.
+
+=back
+
+=item C<get_billing_period_length>
+
+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<first_billing_date> (or
+C<start_date> if C<first_billing_date> is unset).
+
+=item C<get_order_value_period_length>
+
+Returns the number of months the order's value refers to. This looks
+at the C<order_value_periodicity>.
+
+Each invoice's value is calculated as C<order value *
+billing_period_length / order_value_period_length>.
+
+=item C<get_previous_billed_period_start_date>
+
+Returns the highest date (as an instance of L<DateTime>) for which an
+invoice has been created from this configuration.
+
+=item C<handle_automatic_extension>
+
+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<end_date> hasn't been
+reached yet then nothing is done and C<undef> is returned. Otherwise
+its behavior is determined by the C<extend_automatically_by> property.
+
+If the property C<extend_automatically_by> is not 0 then the
+C<end_date> will be extended by C<extend_automatically_by> months, and
+the configuration will be saved. In this case the new end date will be
+returned.
+
+Otherwise (if C<extend_automatically_by> is 0) the property C<active>
+will be set to 1, and the configuration will be saved. In this case
+C<undef> will be returned.
+
+=item C<is_last_billing_date_in_order_value_cycle %params>
+
+Determines whether or not the mandatory parameter C<date>, an instance
+of L<DateTime>, 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<first_billing_date> (or C<start_date> if C<first_billing_date>
+is unset) and adding the order value length with each step. If the
+date given by the C<date> 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 E<lt>m.bunkus@linet-services.deE<gt>
+
+=cut
use List::MoreUtils qw(uniq);
use SL::DBUtils;
+use SL::DB::PeriodicInvoicesConfig;
sub new {
my $package = shift;
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)
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) {
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;
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);
}
$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',
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},
'...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',
'<b> I DO CARE!</b> Please check create warehouse and bins and define a name for the warehouse (Bins will be created automatically) and then continue' => '<b>ICH KÜMMER MICH</b> 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.',
'<b> I DO CARE!</b> Please click back and cancel the update and come back after there has been at least one warehouse defined with bin(s).:' => '<b>ICH KÜMMER MICH</b> Brechen Sie das Update ab und legen selber mindestens ein Lager mit Lagerplätzen unter dem Menü System / Lager an.',
'<b> I DO NOT CARE</b> Please click continue and the following data (see list) will be deleted:' => '<b>IST MIR EGAL</b> Mit einem Klick auf Weiter (rot) werden keine Daten übernommen, bzw. migriert und die folgende Information in der untenstehenden Liste wird gelöscht.',
'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)',
'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',
'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',
'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)',
'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',
'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',
--- /dev/null
+-- @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'));
--- /dev/null
+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();
--- /dev/null
+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();
--- /dev/null
+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();
[% USE HTML %]
[% USE LxERP %]
[% USE L %]
+[% SET style="width: 400px" %]
<h1>[% title %]</h1>
<form name="Form" action="oe.pl" method="post">
</tr>
<tr>
- <th align="right" valign="top">[%- LxERP.t8('Periodicity') %]</th>
+ <th align="right" valign="top">[%- LxERP.t8('Billing Periodicity') %]</th>
<td valign="top">
- [% L.radio_button_tag("periodicity", value => "m", label => LxERP.t8("monthly"), checked => periodicity == 'm') %]
- <br>
- [% L.radio_button_tag("periodicity", value => "q", label => LxERP.t8("every third month"), checked => periodicity == 'q') %]
- <br>
- [% L.radio_button_tag("periodicity", value => "b", label => LxERP.t8("semiannually"), checked => periodicity == 'b') %]
- <br>
- [% 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) %]
+ </td>
+ </tr>
+
+ <tr>
+ <th align="right" valign="top">[%- LxERP.t8('Order value periodicity') %]</th>
+ <td valign="top">
+ [% 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) %]
</td>
</tr>
<tr>
<th align="right">[%- LxERP.t8('Record in') %]</th>
<td valign="top">
- [% 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) %]
</td>
</tr>
<tr>
<th align="right">[%- LxERP.t8('Printer') %]</th>
<td valign="top">
- [% 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) %]
</td>
</tr>