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'),
$cur_date->add(years => 1);
}
- return $num_years * $order->netamount * (12 / $order->periodic_invoices_config->get_period_length);
+ return $num_years * $order->netamount * (12 / $order->periodic_invoices_config->get_billing_period_length);
}
sub sum_items {
return if $params{config}->start_date > $params{end_date};
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 $net = $params{config}->order->netamount * (12 / $params{config}->get_billing_period_length);
my $sord = $self->data->{sales_orders};
$sord->{months }->[ $first_date->month - 1 ] += $net;
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('', @_));
}
sub calculate_invoice_dates {
my ($self, %params) = @_;
- my $period_len = $self->get_period_length;
+ my $period_len = $self->get_billing_period_length;
my $cur_date = $self->first_billing_date || $self->start_date;
my $end_date = $self->terminated ? $self->end_date : undef;
$end_date //= DateTime->today_local->add(years => 100);
}
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.
+
+=back
+
+=head1 BUGS
+
+Nothing here yet.
+
+=head1 AUTHOR
+
+Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
+
+=cut
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_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'));
[% 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>