Revision 5a618706
Von Jan Büren vor mehr als 3 Jahren hinzugefügt
SL/ARAP.pm | ||
---|---|---|
67 | 67 |
my $q_ordered = qq|SELECT oi.parts_id, oi.qty, oi.unit, p.unit AS partunit |
68 | 68 |
FROM orderitems oi |
69 | 69 |
LEFT JOIN parts p ON (oi.parts_id = p.id) |
70 |
WHERE oi.trans_id = ?|; |
|
70 |
WHERE oi.trans_id = ? |
|
71 |
AND not oi.optional|; |
|
71 | 72 |
my $h_ordered = prepare_query($form, $dbh, $q_ordered); |
72 | 73 |
|
73 | 74 |
my @close_oe_ids; |
SL/DB/Helper/FlattenToForm.pm | ||
---|---|---|
97 | 97 |
_copy($item->part, $form, '', "_${idx}", 0, qw(listprice)); |
98 | 98 |
_copy($item, $form, '', "_${idx}", 0, qw(description project_id ship serialnumber pricegroup_id ordnumber donumber cusordnumber unit |
99 | 99 |
subtotal longdescription price_factor_id marge_price_factor reqdate transdate |
100 |
active_price_source active_discount_source)); |
|
100 |
active_price_source active_discount_source optional));
|
|
101 | 101 |
_copy($item, $form, '', "_${idx}", $format_noround, qw(qty sellprice fxsellprice)); |
102 | 102 |
_copy($item, $form, '', "_${idx}", $format_amounts, qw(marge_total marge_percent lastcost)); |
103 | 103 |
_copy($item, $form, '', "_${idx}", $format_percent, qw(discount)); |
SL/DB/Helper/PriceTaxCalculator.pm | ||
---|---|---|
44 | 44 |
# set exchangerate in $data>{exchangerate} |
45 | 45 |
if ( ref($self) eq 'SL::DB::Order' ) { |
46 | 46 |
# orders store amount in the order currency |
47 |
$data{exchangerate} = 1; |
|
47 |
$data{exchangerate} = 1; |
|
48 |
$data{allow_optional_items} = 1; |
|
48 | 49 |
} else { |
49 | 50 |
# invoices store amount in the default currency |
50 | 51 |
_get_exchangerate($self, \%data, %params); |
... | ... | |
121 | 122 |
} else { |
122 | 123 |
$tax_amount = $linetotal * $tax_rate; |
123 | 124 |
} |
124 |
|
|
125 |
if ($taxkey->tax->chart_id) { |
|
126 |
$data->{taxes_by_chart_id}->{ $taxkey->tax->chart_id } ||= 0; |
|
127 |
$data->{taxes_by_chart_id}->{ $taxkey->tax->chart_id } += $tax_amount; |
|
128 |
$data->{taxes_by_tax_id}->{ $taxkey->tax_id } ||= 0; |
|
129 |
$data->{taxes_by_tax_id}->{ $taxkey->tax_id } += $tax_amount; |
|
130 |
} elsif ($tax_amount) { |
|
131 |
die "tax_amount != 0 but no chart_id for taxkey " . $taxkey->id . " tax " . $taxkey->tax->id; |
|
132 |
} |
|
133 |
|
|
134 | 125 |
my $chart = $part->get_chart(type => $data->{is_sales} ? 'income' : 'expense', taxzone => $self->taxzone_id); |
135 |
$data->{amounts}->{ $chart->id } ||= { taxkey => $taxkey->taxkey_id, tax_id => $taxkey->tax_id, amount => 0 }; |
|
136 |
$data->{amounts}->{ $chart->id }->{amount} += $linetotal; |
|
137 |
$data->{amounts}->{ $chart->id }->{amount} -= $tax_amount if $self->taxincluded; |
|
126 |
unless ($data->{allow_optional_items} && $item->optional) { |
|
127 |
if ($taxkey->tax->chart_id) { |
|
128 |
$data->{taxes_by_chart_id}->{ $taxkey->tax->chart_id } ||= 0; |
|
129 |
$data->{taxes_by_chart_id}->{ $taxkey->tax->chart_id } += $tax_amount; |
|
130 |
$data->{taxes_by_tax_id}->{ $taxkey->tax_id } ||= 0; |
|
131 |
$data->{taxes_by_tax_id}->{ $taxkey->tax_id } += $tax_amount; |
|
132 |
} elsif ($tax_amount) { |
|
133 |
die "tax_amount != 0 but no chart_id for taxkey " . $taxkey->id . " tax " . $taxkey->tax->id; |
|
134 |
} |
|
138 | 135 |
|
136 |
$data->{amounts}->{ $chart->id } ||= { taxkey => $taxkey->taxkey_id, tax_id => $taxkey->tax_id, amount => 0 }; |
|
137 |
$data->{amounts}->{ $chart->id }->{amount} += $linetotal; |
|
138 |
$data->{amounts}->{ $chart->id }->{amount} -= $tax_amount if $self->taxincluded; |
|
139 |
} |
|
139 | 140 |
my $linetotal_cost = 0; |
140 | 141 |
|
141 | 142 |
if (!$linetotal) { |
... | ... | |
150 | 151 |
$item->marge_total( $linetotal_net - $linetotal_cost); |
151 | 152 |
$item->marge_percent($item->marge_total * 100 / $linetotal_net); |
152 | 153 |
|
153 |
$self->marge_total( $self->marge_total + $item->marge_total); |
|
154 |
$data->{lastcost_total} += $linetotal_cost; |
|
154 |
unless ($data->{allow_optional_items} && $item->optional) { |
|
155 |
$self->marge_total( $self->marge_total + $item->marge_total); |
|
156 |
$data->{lastcost_total} += $linetotal_cost; |
|
157 |
} |
|
155 | 158 |
} |
156 | 159 |
|
157 | 160 |
push @{ $data->{assembly_items} }, []; |
SL/DB/MetaSetup/OrderItem.pm | ||
---|---|---|
23 | 23 |
marge_price_factor => { type => 'numeric', default => 1, precision => 15, scale => 5 }, |
24 | 24 |
marge_total => { type => 'numeric', precision => 15, scale => 5 }, |
25 | 25 |
mtime => { type => 'timestamp' }, |
26 |
optional => { type => 'boolean', default => 'false' }, |
|
26 | 27 |
ordnumber => { type => 'text' }, |
27 | 28 |
parts_id => { type => 'integer' }, |
28 | 29 |
position => { type => 'integer', not_null => 1 }, |
SL/DB/Order.pm | ||
---|---|---|
393 | 393 |
marge_percent marge_price_factor marge_total |
394 | 394 |
ordnumber parts_id price_factor price_factor_id pricegroup_id |
395 | 395 |
project_id qty reqdate sellprice serialnumber ship subtotal transdate unit |
396 |
optional |
|
396 | 397 |
)), |
397 | 398 |
custom_variables => \@custom_variables, |
398 | 399 |
); |
SL/OE.pm | ||
---|---|---|
1366 | 1366 |
partnotes serialnumber reqdate sellprice sellprice_nofmt listprice listprice_nofmt netprice netprice_nofmt |
1367 | 1367 |
discount discount_nofmt p_discount discount_sub discount_sub_nofmt nodiscount_sub nodiscount_sub_nofmt |
1368 | 1368 |
linetotal linetotal_nofmt nodiscount_linetotal nodiscount_linetotal_nofmt tax_rate projectnumber projectdescription |
1369 |
price_factor price_factor_name partsgroup weight weight_nofmt lineweight lineweight_nofmt); |
|
1369 |
price_factor price_factor_name partsgroup weight weight_nofmt lineweight lineweight_nofmt optional);
|
|
1370 | 1370 |
|
1371 | 1371 |
push @arrays, map { "ic_cvar_$_->{name}" } @{ $ic_cvar_configs }; |
1372 | 1372 |
push @arrays, map { "project_cvar_$_->{name}" } @{ $project_cvar_configs }; |
... | ... | |
1433 | 1433 |
push @{ $form->{TEMPLATE_ARRAYS}->{price_factor} }, $price_factor->{formatted_factor}; |
1434 | 1434 |
push @{ $form->{TEMPLATE_ARRAYS}->{price_factor_name} }, $price_factor->{description}; |
1435 | 1435 |
push @{ $form->{TEMPLATE_ARRAYS}->{partsgroup} }, $form->{"partsgroup_$i"}; |
1436 |
push @{ $form->{TEMPLATE_ARRAYS}->{optional} }, $form->{"optional_$i"}; |
|
1436 | 1437 |
|
1437 | 1438 |
my $sellprice = $form->parse_amount($myconfig, $form->{"sellprice_$i"}); |
1438 | 1439 |
my ($dec) = ($sellprice =~ /\.(\d+)/); |
... | ... | |
1472 | 1473 |
$form->{non_separate_subtotal} += $linetotal; |
1473 | 1474 |
} |
1474 | 1475 |
|
1475 |
$form->{ordtotal} += $linetotal; |
|
1476 |
$form->{ordtotal} += $linetotal unless $form->{"optional_$i"};
|
|
1476 | 1477 |
$form->{nodiscount_total} += $nodiscount_linetotal; |
1477 | 1478 |
$form->{discount_total} += $discount; |
1478 | 1479 |
|
... | ... | |
1520 | 1521 |
|
1521 | 1522 |
map { $taxrate += $form->{"${_}_rate"} } split(/ /, $form->{"taxaccounts_$i"}); |
1522 | 1523 |
|
1523 |
if ($form->{taxincluded}) { |
|
1524 |
unless ($form->{"optional_$i"}) { |
|
1525 |
if ($form->{taxincluded}) { |
|
1524 | 1526 |
|
1525 |
# calculate tax |
|
1526 |
$taxamount = $linetotal * $taxrate / (1 + $taxrate); |
|
1527 |
$taxbase = $linetotal / (1 + $taxrate); |
|
1528 |
} else { |
|
1529 |
$taxamount = $linetotal * $taxrate; |
|
1530 |
$taxbase = $linetotal; |
|
1527 |
# calculate tax |
|
1528 |
$taxamount = $linetotal * $taxrate / (1 + $taxrate); |
|
1529 |
$taxbase = $linetotal / (1 + $taxrate); |
|
1530 |
} else { |
|
1531 |
$taxamount = $linetotal * $taxrate; |
|
1532 |
$taxbase = $linetotal; |
|
1533 |
} |
|
1531 | 1534 |
} |
1532 | 1535 |
|
1533 | 1536 |
if ($taxamount != 0) { |
doc/changelog | ||
---|---|---|
37 | 37 |
|
38 | 38 |
Kleinere neue Features und Detailverbesserungen: |
39 | 39 |
|
40 |
- Angebote und Aufträge im Ein- und Verkauf können optionale Positionen enthalten. |
|
41 |
Optionale Positionen werden in der zweiten Zeile der Position aktiviert. |
|
42 |
Die einzelne Position wird dann berechnet und erscheint im Ausdruck mit dem |
|
43 |
berechnetem Preis, die Position wird aber nicht in der Gesamtsumme des Belegs |
|
44 |
aufgenommen. Dies gilt auch für die Gesamt-Marge und den Gesamt-Ertrag des Belegs. |
|
45 |
Innerhalb der Druckvorlagen steht das Attribut mit <%optional%> als Variable zu Verfügung. |
|
46 |
Beim Status setzen eines Auftrags (offen oder geschlossen) werden optionale Position |
|
47 |
ignoriert. D.h. ein Auftrag gilt als geschlossen, wenn alle nicht optionalen |
|
48 |
Positionen fakturiert worden sind. Das Attribut optional steht auch nur in |
|
49 |
den Angeboten/Aufträgen zu Verfügung. Sobald über den Workflow ein neuer Beleg |
|
50 |
erstellt wird, wird die vorher optionale Position zu einer normalen Position |
|
51 |
und wird dann auch entsprechend bei dem Rechnungsbeleg mit fakturiert und im |
|
52 |
Druckvorlagen-System entfällt das Attribut <%optional%>. |
|
53 |
Entsprechend exemplarisch im aktuellen Druckvorlagensatz RB ergänzt. |
|
54 |
|
|
40 | 55 |
- Lagerbestandsbericht: Die Resultate pro Seite können im Bericht eingestellt werden |
41 | 56 |
- Es gibt eine PDF-Druckvorschau für die Standard-Druckvorlage bei Angeboten und |
42 | 57 |
Aufträgen im Einkauf und Verkauf ohne ein vorheriges Dialogmenü (Druckvorlage |
sql/Pg-upgrade2/orderitems_optional.sql | ||
---|---|---|
1 |
-- @tag: orderitems_optional |
|
2 |
-- @description: Optionale Artikel im Angebot und Auftrag |
|
3 |
-- @depends: release_3_5_6_1 |
|
4 |
ALTER TABLE orderitems ADD COLUMN optional BOOLEAN default FALSE; |
|
5 |
|
t/db_helper/price_tax_calculator.t | ||
---|---|---|
98 | 98 |
%params, |
99 | 99 |
); |
100 | 100 |
} |
101 |
sub new_order { |
|
102 |
my %params = @_; |
|
103 |
|
|
104 |
return create_sales_order( |
|
105 |
transdate => $transdate, |
|
106 |
taxzone_id => $taxzone->id, |
|
107 |
%params, |
|
108 |
); |
|
109 |
} |
|
101 | 110 |
|
102 | 111 |
sub new_item { |
103 | 112 |
my (%params) = @_; |
... | ... | |
109 | 118 |
%params, |
110 | 119 |
); |
111 | 120 |
} |
121 |
sub new_order_item { |
|
122 |
my (%params) = @_; |
|
123 |
|
|
124 |
my $part = delete($params{part}) || $parts[0]; |
|
125 |
|
|
126 |
return create_order_item( |
|
127 |
part => $part, |
|
128 |
%params, |
|
129 |
); |
|
130 |
} |
|
112 | 131 |
|
113 | 132 |
sub test_default_invoice_one_item_19_tax_not_included() { |
114 | 133 |
reset_state(); |
... | ... | |
553 | 572 |
rounding => 0, |
554 | 573 |
}, "${title}: calculated data"); |
555 | 574 |
} |
575 |
sub test_default_order_two_items_19_one_optional() { |
|
576 |
reset_state(); |
|
577 |
|
|
578 |
my $item = new_order_item(qty => 2.5); |
|
579 |
my $item_optional = new_order_item(qty => 2.5, optional => 1); |
|
580 |
|
|
581 |
my $order = new_order( |
|
582 |
taxincluded => 0, |
|
583 |
orderitems => [ $item, $item_optional ], |
|
584 |
); |
|
585 |
|
|
586 |
my $taxkey = $item->part->get_taxkey(date => $transdate, is_sales => 1, taxzone => $order->taxzone_id); |
|
587 |
|
|
588 |
# sellprice 2.34 * qty 2.5 = 5.85 |
|
589 |
# 19%(5.85) = 1.1115; rounded = 1.11 |
|
590 |
# total rounded = 6.96 |
|
591 |
|
|
592 |
# lastcost 1.93 * qty 2.5 = 4.825; rounded 4.83 |
|
593 |
# line marge_total = 1.02 |
|
594 |
# line marge_percent = 17.4358974358974 |
|
595 |
|
|
596 |
my $title = 'default order, two item, one item optional, 19% tax not included'; |
|
597 |
my %data = $order->calculate_prices_and_taxes; |
|
598 |
|
|
599 |
is($item->marge_total, 1.02, "${title}: item marge_total"); |
|
600 |
is($item->marge_percent, 17.4358974358974, "${title}: item marge_percent"); |
|
601 |
is($item->marge_price_factor, 1, "${title}: item marge_price_factor"); |
|
602 |
|
|
603 |
# optional items have a linetotal and marge, but ... |
|
604 |
is($item_optional->marge_total, 1.02, "${title}: item optional marge_total"); |
|
605 |
is($item_optional->marge_percent, 17.4358974358974, "${title}: item optional marge_percent"); |
|
606 |
is($item_optional->marge_price_factor, 1, "${title}: item optional marge_price_factor"); |
|
607 |
|
|
608 |
# ... should not be calculated for the record sum |
|
609 |
is($order->netamount, 5.85, "${title}: netamount"); |
|
610 |
is($order->amount, 6.96, "${title}: amount"); |
|
611 |
is($order->marge_total, 1.02, "${title}: marge_total"); |
|
612 |
is($order->marge_percent, 17.4358974358974, "${title}: marge_percent"); |
|
613 |
is($order->orderitems->[1]->optional, 1, "${title}: second order item has attribute optional"); |
|
614 |
# diag explain $order->orderitems->[1]->optional; |
|
615 |
# diag explain \%data; |
|
616 |
is_deeply(\%data, { |
|
617 |
allocated => {}, |
|
618 |
amounts => { |
|
619 |
$buchungsgruppe->income_accno_id($taxzone) => { |
|
620 |
amount => 5.85, |
|
621 |
tax_id => $tax->id, |
|
622 |
taxkey => 3, |
|
623 |
}, |
|
624 |
}, |
|
625 |
amounts_cogs => {}, |
|
626 |
assembly_items => [ |
|
627 |
[], |
|
628 |
[], |
|
629 |
], |
|
630 |
exchangerate => 1, |
|
631 |
taxes_by_chart_id => { |
|
632 |
$tax->chart_id => 1.11, |
|
633 |
}, |
|
634 |
taxes_by_tax_id => { |
|
635 |
$tax->id => 1.1115, |
|
636 |
}, |
|
637 |
items => [ |
|
638 |
{ linetotal => 5.85, |
|
639 |
linetotal_cost => 4.83, |
|
640 |
sellprice => 2.34, |
|
641 |
tax_amount => 1.1115, |
|
642 |
taxkey_id => $taxkey->id, |
|
643 |
}, |
|
644 |
{ linetotal => 5.85, |
|
645 |
linetotal_cost => 4.83, |
|
646 |
sellprice => 2.34, |
|
647 |
tax_amount => 1.1115, |
|
648 |
taxkey_id => $taxkey->id, |
|
649 |
}, |
|
650 |
], |
|
651 |
rounding => 0, |
|
652 |
}, "${title}: calculated data"); |
|
653 |
} |
|
556 | 654 |
|
557 | 655 |
|
558 | 656 |
Support::TestSetup::login(); |
... | ... | |
566 | 664 |
test_default_invoice_one_item_19_tax_not_included_rounding_discount(); |
567 | 665 |
test_default_invoice_one_item_19_tax_not_included_rounding_discount_huge_qty(); |
568 | 666 |
test_default_invoice_one_item_19_tax_not_included_rounding_discount_big_qty_low_sellprice(); |
667 |
test_default_order_two_items_19_one_optional(); |
|
569 | 668 |
|
570 | 669 |
clear_up(); |
571 | 670 |
done_testing(); |
templates/print/RB/deutsch.tex | ||
---|---|---|
56 | 56 |
\newcommand{\auftragerteilt}{Auftrag erteilt:} |
57 | 57 |
\newcommand{\angebotortdatum}{Wir nehmen das vorstehende Angebot an.} |
58 | 58 |
\newcommand{\abweichendeLieferadresse}{abweichende Lieferadresse} |
59 |
\newcommand{\optional}{Optionale Position nach Absprache} |
|
59 | 60 |
|
60 | 61 |
% auftragbestätigung (sales_order) |
61 | 62 |
\newcommand{\auftragsbestaetigung} {Auftragsbestätigung} |
templates/print/RB/english.tex | ||
---|---|---|
68 | 68 |
\newcommand{\den} {Date} |
69 | 69 |
\newcommand{\unterschrift} {Signature} |
70 | 70 |
\newcommand{\stempel} {Company stamp} |
71 |
\newcommand{\optional}{Optional position by arrangement} |
|
71 | 72 |
|
72 | 73 |
% lieferschein (sales_delivery_order) |
73 | 74 |
\newcommand{\lieferschein} {Delivery order} |
templates/print/RB/sales_order.tex | ||
---|---|---|
151 | 151 |
<%if serialnumber%> && \scriptsize \seriennummer: <%serialnumber%>\\<%end serialnumber%> |
152 | 152 |
<%if ean%> && \scriptsize \ean: <%ean%>\\<%end ean%> |
153 | 153 |
<%if projectnumber%> && \scriptsize \projektnummer: <%projectnumber%>\\<%end projectnumber%> |
154 |
<%if optional%> && \scriptsize \optional \\<%end%> |
|
154 | 155 |
<%if customer_make%> |
155 | 156 |
<%foreach customer_make%> |
156 | 157 |
\ifthenelse{\equal{<%customer_make%>}{<%name%>}}{&& \kundenartnr: <%customer_model%>\\}{} |
templates/print/RB/sales_quotation.tex | ||
---|---|---|
147 | 147 |
<%if serialnumber%> && \scriptsize \seriennummer: <%serialnumber%>\\<%end serialnumber%> |
148 | 148 |
<%if ean%> && \scriptsize \ean: <%ean%>\\<%end ean%> |
149 | 149 |
<%if projectnumber%> && \scriptsize \projektnummer: <%projectnumber%>\\<%end projectnumber%> |
150 |
<%if optional%> && \scriptsize \optional \\<%end%> |
|
150 | 151 |
<%if customer_make%> |
151 | 152 |
<%foreach customer_make%> |
152 | 153 |
\ifthenelse{\equal{<%customer_make%>}{<%name%>}}{&& \kundenartnr: <%customer_model%>\\}{} |
templates/webpages/order/tabs/_second_row.html | ||
---|---|---|
38 | 38 |
<span[%- IF ITEM.part.onhand < ITEM.part.rop -%] class="numeric plus0"[%- END -%]> |
39 | 39 |
[%- ITEM.part.onhand_as_number -%] [%- ITEM.part.unit -%] |
40 | 40 |
</span> |
41 |
<b>[%- 'Optional' | $T8 %]</b> |
|
42 |
[%- L.yes_no_tag("order.orderitems[].optional", ITEM.optional |
|
43 |
class="recalc") %] |
|
41 | 44 |
</td></tr> |
42 | 45 |
|
43 | 46 |
<tr> |
Auch abrufbar als: Unified diff
orderitems um Attribut optional erweitert
Optionale orderitems werden nicht in den Belegsumme aufaddiert
Anpassung für Order-Controller und Druckvorlagen-System
Weitere Anwender-Details s.a. Changelog