use List::MoreUtils qw(uniq);
use SL::DBUtils;
+use SL::DB::PeriodicInvoicesConfig;
sub new {
my $package = shift;
# Monat ihre Saldierungsperiode haben, außer Wartungsverträgen, die
# für den jeweiligen Monat bereits abgerechnet wurden.
#
-# Diese Werte sollen zusätzlich optional nach Verkäufer(in) und nach
-# Buchungsgruppe aufgeschlüsselt werden.
+# Diese Werte sollen zusätzlich optional nach Verkäufer(in),
+# Buchungsgruppe und Warengruppe aufgeschlüsselt werden.
#
# Diese Lösung geht deshalb immer über die Positionen der Belege
-# (wegen der Buchungsgruppe) und berechnet die Summen daraus manuell.
+# (wegen der Buchungsgruppen & Warengruppen) und berechnet die Summen
+# daraus manuell.
#
# Alle Aufträge, deren Lieferdatum leer ist, oder deren Lieferdatum
# vor dem aktuellen Monat liegt, werden in einer Kategorie 'alt'
# Monat vorgemerkt.
#
# 4. Es werden für alle offenen Kundenaufträge die Positionen
-# ausgelesen und mit Verkäufer(in), Buchungsgruppe verknüpft. Aus
-# Menge, Einzelpreis und Zeilenrabatt wird die Zeilensumme berechnet.
+# ausgelesen und mit Verkäufer(in), Buchungsgruppe, Warengruppe
+# verknüpft. Aus Menge, Einzelpreis und Zeilenrabatt wird die
+# Zeilensumme berechnet.
#
# 5. Mit den Informationen aus 3. und 4. werden Datenstrukturen
# initialisiert, die für die Gesamtsummen, für alle Verkäufer(innen),
-# für alle Buchungsgruppen, für alle Monate Werte enthalten.
+# für alle Buchungsgruppen, für alle Warengruppen, für alle Monate
+# Werte enthalten.
#
# 6. Es wird über alle Einträge aus 4. iteriert. Die Zeilensummen
# werden in den entsprechenden Datenstrukturen aus 5. addiert.
# den Aufträgen nie vorgekommen ist (sprich Rechnung enthält
# Positionen, die im Auftrag nicht enthalten sind, und die komplett
# andere Buchungsgruppen verwenden), so wird schlicht die allererste
-# in 4. gefundene Buchungsgruppe damit belastet.
+# in 4. gefundene Buchungsgruppe damit belastet. Analog passiert dies
+# auch für Warengruppen.
sub create {
my ($self) = @_;
FROM periodic_invoices pi
LEFT JOIN periodic_invoices_configs pcfg ON (pi.config_id = pcfg.id)
WHERE pcfg.active
+ AND NOT pcfg.periodicity = 'o'
AND (pi.period_start_date >= to_date($q_min_date, 'YYYY-MM-DD'))
SQL
# 2. Auslesen aktiver Wartungsvertragskonfigurationen
$query = <<SQL;
- SELECT (oi.qty * (1 - oi.discount) * oi.sellprice) AS linetotal,
+ SELECT (oi.qty * (1 - oi.discount) * oi.sellprice) AS linetotal, oi.recurring_billing_mode,
bg.description AS buchungsgruppe,
+ pg.partsgroup AS parts_group,
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)
LEFT JOIN periodic_invoices_configs pcfg ON (oi.trans_id = pcfg.oe_id)
LEFT JOIN parts p ON (oi.parts_id = p.id)
LEFT JOIN buchungsgruppen bg ON (p.buchungsgruppen_id = bg.id)
+ LEFT JOIN partsgroup pg ON (p.partsgroup_id = pg.id)
LEFT JOIN employee e ON (COALESCE(oe.salesman_id, oe.employee_id) = e.id)
WHERE pcfg.active
+ AND (pcfg.periodicity <> 'o')
+ AND ( (oi.recurring_billing_mode = 'always')
+ OR ( (oi.recurring_billing_mode = 'once')
+ AND (oi.recurring_billing_invoice_id IS NULL)))
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})) {
+ if ($ref->{recurring_billing_mode} eq 'once') {
+ push @scentries, { buchungsgruppe => $ref->{buchungsgruppe},
+ salesman => $ref->{salesman},
+ linetotal => $ref->{linetotal},
+ date => $date,
+ };
+ last;
+ }
+
+ 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,
+ parts_group => $ref->{parts_group},
};
}
- ($year, $month) = _fix_date($year, $month + ($periodicities{ $ref->{periodicity} } || 1));
+ ($year, $month) = _fix_date($year, $month + $billing_len);
}
}
$sth->finish;
$query = <<SQL;
SELECT (oi.qty * (1 - oi.discount) * oi.sellprice) AS linetotal,
bg.description AS buchungsgruppe,
+ pg.partsgroup AS parts_group,
CASE WHEN COALESCE(e.name, '') = '' THEN e.login ELSE e.name END AS salesman,
oe.ordnumber, EXTRACT(month FROM oe.reqdate) AS month, EXTRACT(year FROM oe.reqdate) AS year
FROM orderitems oi
LEFT JOIN oe ON (oi.trans_id = oe.id)
LEFT JOIN parts p ON (oi.parts_id = p.id)
LEFT JOIN buchungsgruppen bg ON (p.buchungsgruppen_id = bg.id)
+ LEFT JOIN partsgroup pg ON (p.partsgroup_id = pg.id)
LEFT JOIN employee e ON (COALESCE(oe.salesman_id, oe.employee_id) = e.id)
- WHERE (oe.customer_id IS NOT NULL)
- AND NOT COALESCE(oe.quotation, FALSE)
+ WHERE oe.record_type = 'sales_order'
AND NOT COALESCE(oe.closed, FALSE)
- AND (oe.id NOT IN (SELECT oe_id FROM periodic_invoices_configs))
+ AND (oe.id NOT IN (SELECT oe_id FROM periodic_invoices_configs WHERE periodicity <> 'o'))
SQL
# 5. Initialisierung der Datenstrukturen zum Speichern der
my @entries = selectall_hashref_query($::form, $dbh, $query);
my @salesmen = uniq map { $_->{salesman} } (@entries, @scentries);
my @buchungsgruppen = uniq map { $_->{buchungsgruppe} } (@entries, @scentries);
+ my @parts_groups = uniq map { $_->{parts_group} } (@entries, @scentries);
my @now = localtime;
my @dates = map { $self->_date_for($now[5] + 1900, $now[4] + $_) } (0..$self->{params}->{months} + 1);
my %dates_by_ordnumber = map { $_->{ordnumber} => $self->_date_for($_) } @entries;
support => { map { $_ => 0 } @dates },
salesman => { map { $_ => { map { $_ => 0 } @dates } } @salesmen },
buchungsgruppe => { map { $_ => { map { $_ => 0 } @dates } } @buchungsgruppen },
+ parts_group => { map { $_ => { map { $_ => 0 } @dates } } @parts_groups },
sorted => { month => [ sort { ($date_sorter{$a} || $a) cmp ($date_sorter{$b} || $b) } @dates ],
salesman => [ sort { $a cmp $b } @salesmen ],
buchungsgruppe => [ sort { $a cmp $b } @buchungsgruppen ],
+ parts_group => [ sort { $a cmp $b } @parts_groups ],
type => [ qw(order partial support) ],
},
};
$projection->{order}->{$date} += $ref->{linetotal};
$projection->{salesman}->{ $ref->{salesman} }->{$date} += $ref->{linetotal};
$projection->{buchungsgruppe}->{ $ref->{buchungsgruppe} }->{$date} += $ref->{linetotal};
+ $projection->{parts_group}->{ $ref->{parts_group} }->{$date} += $ref->{linetotal};
}
# 7. Aufsummieren der Wartungsvertragspositionen
$projection->{support}->{$date} += $ref->{linetotal};
$projection->{salesman}->{ $ref->{salesman} }->{$date} += $ref->{linetotal};
$projection->{buchungsgruppe}->{ $ref->{buchungsgruppe} }->{$date} += $ref->{linetotal};
+ $projection->{parts_group}->{ $ref->{parts_group} }->{$date} += $ref->{linetotal};
}
if (%dates_by_ordnumber) {
$query = <<SQL;
SELECT (i.qty * (1 - i.discount) * i.sellprice) AS linetotal,
bg.description AS buchungsgruppe,
+ pg.partsgroup AS parts_group,
ar.ordnumber
FROM invoice i
LEFT JOIN ar ON (i.trans_id = ar.id)
LEFT JOIN parts p ON (i.parts_id = p.id)
LEFT JOIN buchungsgruppen bg ON (p.buchungsgruppen_id = bg.id)
+ LEFT JOIN partsgroup pg ON (p.partsgroup_id = pg.id)
WHERE (ar.ordnumber IN ($ordnumbers))
SQL
my $date = $dates_by_ordnumber{ $ref->{ordnumber} } || die;
my $salesman = $salesman_by_ordnumber{ $ref->{ordnumber} } || die;
my $buchungsgruppe = $projection->{buchungsgruppe}->{ $ref->{buchungsgruppe} } ? $ref->{buchungsgruppe} : $buchungsgruppen[0];
+ my $parts_group = $projection->{parts_group}->{ $ref->{parts_group} } ? $ref->{parts_group} : $parts_groups[0];
$projection->{partial}->{$date} -= $ref->{linetotal};
$projection->{total}->{$date} -= $ref->{linetotal};
$projection->{salesman}->{$salesman}->{$date} -= $ref->{linetotal};
$projection->{buchungsgruppe}->{$buchungsgruppe}->{$date} -= $ref->{linetotal};
+ $projection->{parts_group}->{$parts_group}->{$date} -= $ref->{linetotal};
}
}
: $date;
}
+sub orders_for_time_period {
+ my ($class, %params) = @_;
+
+ my $dbh = SL::DB::Order->new->db->dbh;
+
+ my @recurring_orders;
+
+ # 1. Alle aktiven Konfigurationen für wiederkehrende Rechnungen auslesen.
+
+ my $configs = SL::DB::Manager::PeriodicInvoicesConfig->get_all(where => [ active => 1 ]);
+
+ my %calc_params;
+ $calc_params{start_date} = $params{after}->clone if $params{after};
+ $calc_params{end_date} = $params{before}->clone->add(days => -1) if $params{before};
+ $calc_params{end_date} //= $calc_params{start_date}->clone->add(years => 1);
+
+ foreach my $config (@{ $configs }) {
+ my @dates = $config->calculate_invoice_dates(%calc_params);
+ next unless @dates;
+
+ my $order = SL::DB::Order->new(id => $config->oe_id)->load(with_objects => [ qw(customer employee) ]);
+ $order->{is_recurring} = 1;
+
+ push @recurring_orders, $order;
+ }
+
+ my @where = (
+ record_type => 'sales_order',
+ or => [ closed => undef, closed => 0, ],
+ );
+ push @where, (reqdate => { ge => $params{after}->clone }) if $params{after};
+ push @where, (reqdate => { lt => $params{before}->clone }) if $params{before};
+ push @where, '!id' => [ map { $_->id } @recurring_orders ] if @recurring_orders;
+
+ # 1. Auslesen aller offenen Aufträge, deren Lieferdatum im
+ # gewünschten Bereich liegt
+ my $regular_orders = SL::DB::Manager::Order->get_all(
+ where => \@where,
+ with_objects => [ qw(customer employee) ],
+ );
+
+ return sort {
+ ($a->transdate <=> $b->transdate)
+ || ($a->reqdate <=> $b->reqdate)
+ || (lc($a->customer->name) cmp lc($b->customer->name))
+ } (@recurring_orders, @{ $regular_orders });
+}
+
1;