From: Jan Büren Date: Fri, 4 Sep 2015 09:31:22 +0000 (+0200) Subject: DeliveryOrder um convert_invoice erweitert X-Git-Tag: release-3.4.1~752^2~2 X-Git-Url: http://wagnertech.de/git?a=commitdiff_plain;h=a7ca8ba215c2a039e97c27bc3c637712fca837e9;p=kivitendo-erp.git DeliveryOrder um convert_invoice erweitert Diesselbe Idee wie bei SalesOrder->convert_invoice. Der ursprüngliche Lieferschein wird geschlossen und das neue Objekt mittels record_links verknüpft. Entsprechend Testfall mitgeliefert. --- diff --git a/SL/DB/DeliveryOrder.pm b/SL/DB/DeliveryOrder.pm index 7cfc2eac1..b6f1f9596 100644 --- a/SL/DB/DeliveryOrder.pm +++ b/SL/DB/DeliveryOrder.pm @@ -187,6 +187,39 @@ sub customervendor { $_[0]->is_sales ? $_[0]->customer : $_[0]->vendor; } +sub convert_to_invoice { + my ($self, %params) = @_; + + croak("Conversion to invoices is only supported for sales records") unless $self->customer_id; + + my $invoice; + if (!$self->db->with_transaction(sub { + require SL::DB::Invoice; + $invoice = SL::DB::Invoice->new_from($self)->post(%params) || die; + $self->link_to_record($invoice); + foreach my $item (@{ $invoice->items }) { + foreach (qw(delivery_order_items)) { # expand if needed (delivery_order_items) + if ($item->{"converted_from_${_}_id"}) { + die unless $item->{id}; + RecordLinks->create_links('mode' => 'ids', + 'from_table' => $_, + 'from_ids' => $item->{"converted_from_${_}_id"}, + 'to_table' => 'invoice', + 'to_id' => $item->{id}, + ) || die; + delete $item->{"converted_from_${_}_id"}; + } + } + } + $self->update_attributes(closed => 1); + 1; + })) { + return undef; + } + + return $invoice; +} + 1; __END__ @@ -288,6 +321,21 @@ TODO: Describe sales_order Returns a string describing this record's type: either C or C. +=item C + +Creates a new invoice with C<$self> as the basis by calling +L. That invoice is posted, and C<$self> is +linked to the new invoice via L. C<$self>'s +C attribute is set to C, and C<$self> is saved. + +The arguments in C<%params> are passed to L. + +Returns the new invoice instance on success and C on +failure. The whole process is run inside a transaction. On failure +nothing is created or changed in the database. + +At the moment only sales delivery orders can be converted. + =back =head1 BUGS diff --git a/t/db_helper/convert_invoice.t b/t/db_helper/convert_invoice.t new file mode 100644 index 000000000..5279d65eb --- /dev/null +++ b/t/db_helper/convert_invoice.t @@ -0,0 +1,290 @@ +use Test::More tests => 29; + +use strict; + +use lib 't'; +use utf8; + +use Support::TestSetup; + +use Carp; +use Data::Dumper; +use Support::TestSetup; +use Test::Exception; +use List::Util qw(max); + +use SL::DB::Buchungsgruppe; +use SL::DB::Currency; +use SL::DB::Customer; +use SL::DB::Employee; +use SL::DB::Invoice; +use SL::DB::Order; +use SL::DB::DeliveryOrder; +use SL::DB::Part; +use SL::DB::Unit; +use SL::DB::TaxZone; + +my ($customer, $currency_id, $buchungsgruppe, $employee, $vendor, $taxzone, $buchungsgruppe7, $tax, $tax7, + $unit, @parts); + +sub clear_up { + foreach (qw(DeliveryOrderItem DeliveryOrder InvoiceItem Invoice Part Customer Vendor Employee Department PaymentTerm)) { + "SL::DB::Manager::${_}"->delete_all(all => 1); + } +}; + +sub reset_state { + my %params = @_; + + clear_up(); + + $buchungsgruppe = SL::DB::Manager::Buchungsgruppe->find_by(description => 'Standard 19%', %{ $params{buchungsgruppe} }) || croak "No accounting group 19\%"; + $buchungsgruppe7 = SL::DB::Manager::Buchungsgruppe->find_by(description => 'Standard 7%', %{ $params{buchungsgruppe} }) || croak "No accounting group 7\%"; + $taxzone = SL::DB::Manager::TaxZone->find_by( description => 'Inland') || croak "No taxzone"; + $tax = SL::DB::Manager::Tax->find_by(taxkey => 3, rate => 0.19, %{ $params{tax} }) || croak "No tax for 19\%"; + $tax7 = SL::DB::Manager::Tax->find_by(taxkey => 2, rate => 0.07) || croak "No tax for 7\%"; + $unit = SL::DB::Manager::Unit->find_by(name => 'kg', %{ $params{unit} }) || croak "No unit"; + $currency_id = $::instance_conf->get_currency_id; + + $customer = SL::DB::Customer->new( + name => '520484567dfaedc9e60fc', + currency_id => $currency_id, + taxzone_id => $taxzone->id, + %{ $params{customer} } + )->save; + + # some od.rnr real anonym data + my $employee_bk = SL::DB::Employee->new( + 'id' => 31915, + 'login' => 'barbuschka.kappes', + 'name' => 'Barbuschka Kappes', + )->save; + + my $department_do = SL::DB::Department->new( + 'description' => 'Maisenhaus-Versand', + 'id' => 32149, + 'itime' => undef, + 'mtime' => undef + )->save; + + my $payment_do = SL::DB::PaymentTerm->new( + 'description' => '14Tage 2%Skonto, 30Tage netto', + 'description_long' => "Innerhalb von 14 Tagen abzüglich 2 % Skonto, innerhalb von 30 Tagen rein netto.|Bei einer Zahlung bis zum <%skonto_date%> gewähren wir 2 % Skonto (EUR <%skonto_amount%>) entspricht EUR <%total_wo_skonto%>.Bei einer Zahlung bis zum <%netto_date%> ist der fällige Betrag in Höhe von <%total%> <%currency%> zu überweisen.", + 'id' => 11276, + 'itime' => undef, + 'mtime' => undef, + 'percent_skonto' => '0.02', + 'ranking' => undef, + 'sortkey' => 4, + 'terms_netto' => 30, + 'auto_calculation' => undef, + 'terms_skonto' => 14 + )->save; + + # two real parts + @parts = (); + push @parts, SL::DB::Part->new( + 'id' => 26321, + 'image' => '', + 'lastcost' => '49.95000', + 'listprice' => '0.00000', + 'onhand' => '5.00000', + 'partnumber' => 'v-519160549', + #'partsgroup_id' => 111645, + 'rop' => '0', + 'sellprice' => '242.20000', + #'warehouse_id' => 64702, + 'weight' => '0.79', + description => "Nussbaum, Gr.5, Unterfilz weinrot, genietet[[Aufschnittbreite: 11,0, Kernform: US]]\"" , + buchungsgruppen_id => $buchungsgruppe->id, + unit => $unit->name, + id => 26321, + )->save; + + push @parts, SL::DB::Part->new( + 'description' => "[[0640]]Flügel Hammerstiele bestehend aus: +70 Stielen Standard in Weißbuche und +20 Stielen Diskant abgekehlt in Weißbuche +mit Röllchen aus Synthetikleder, +Kapseln mit Yamaha Profil, Kerbenabstand 3,6 mm mit eingedrehten Abnickschrauben", + 'id' => 25505, + 'lastcost' => '153.00000', + 'listprice' => '0.00000', + 'onhand' => '9.00000', + 'partnumber' => 'v-120160086', + # 'partsgroup_id' => 111639, + 'rop' => '0', + 'sellprice' => '344.30000', + 'weight' => '0.9', + buchungsgruppen_id => $buchungsgruppe->id, + unit => $unit->name, + )->save; +} + +sub new_delivery_order { + my %params = @_; + + return SL::DB::DeliveryOrder->new( + currency_id => $currency_id, + taxzone_id => $taxzone->id, + %params, + )->save; +} + +Support::TestSetup::login(); + +reset_state(); + +# we create L20199 with two items +my $do1 = new_delivery_order('department_id' => 32149, + 'donumber' => 'L20199', + 'employee_id' => 31915, + 'intnotes' => 'Achtung: Neue Lieferadresse ab 16.02.2015 in der Carl-von-Ossietzky-Str.32! 13.02.2015/MH + + Steinway-Produkte (201...) immer plus 25% dazu rechnen / BK 13.02.2014', + 'ordnumber' => 'A16399', + 'payment_id' => 11276, + 'salesman_id' => 31915, + 'shippingpoint' => 'Maisenhaus', + # 'shipto_id' => 451463, + 'is_sales' => 'true', + 'shipvia' => 'DHL, Versand am 06.03.2015, 1 Paket 17,00 kg', + 'taxzone_id' => 4, + 'closed' => undef, + # 'currency_id' => 1, + 'cusordnumber' => 'b84da', + 'customer_id' => $customer->id, + 'id' => 464003, +); + +my $do1_item1 = SL::DB::DeliveryOrderItem->new('delivery_order_id' => 464003, + 'description' => "Flügel Hammerkopf bestehend aus: + Bass/Diskant 26/65 Stück, Gesamtlänge 80/72, Bohrlänge 56/48 + Nussbaum, Gr.5, Unterfilz weinrot, genietet[[Aufschnittbreite: 11,0, Kernform: US]]", + 'discount' => '0.25', + 'id' => 144736, + 'lastcost' => '49.95000', + 'longdescription' => '', + 'marge_price_factor' => 1, + 'mtime' => undef, + 'ordnumber' => 'A16399', + 'parts_id' => 26321, + 'position' => 1, + 'price_factor' => 1, + 'qty' => '2.00000', + 'sellprice' => '242.20000', + 'transdate' => '06.03.2015', + 'unit' => 'kg')->save; + +my $do1_item2 = SL::DB::DeliveryOrderItem->new('delivery_order_id' => 464003, + 'description' => "[[0640]]Flügel Hammerstiele bestehend aus: +70 Stielen Standard in Weißbuche und +20 Stielen Diskant abgekehlt in Weißbuche +mit Röllchen aus Synthetikleder, +Kapseln mit Yamaha Profil, Kerbenabstand 3,6 mm mit eingedrehten Abnickschrauben", + 'discount' => '0.25', + 'id' => 144737, + 'itime' => undef, + 'lastcost' => '153.00000', + 'longdescription' => '', + 'marge_price_factor' => 1, + 'mtime' => undef, + 'ordnumber' => 'A16399', + 'parts_id' => 25505, + 'position' => 2, + 'price_factor' => 1, + 'price_factor_id' => undef, + 'pricegroup_id' => undef, + 'project_id' => undef, + 'qty' => '3.00000', + 'reqdate' => undef, + 'sellprice' => '344.30000', + 'serialnumber' => '', + 'transdate' => '06.03.2015', + 'unit' => 'kg')->save; + +# TESTS + + +# test delivery order before any conversion +ok($do1->donumber eq "L20199", 'Delivery Order Number created'); +ok((not $do1->closed) , 'Delivery Order is not closed'); +ok($do1_item1->parts_id eq '26321', 'doi linked with part'); +ok($do1_item1->qty == 2, 'qty check doi'); +ok($do1_item2->position == 2, 'doi2 position check'); +ok(2 == scalar@{ SL::DB::Manager::DeliveryOrderItem->get_all(where => [ delivery_order_id => $do1->id ]) }, 'two doi linked'); + + +# convert this do to invoice +my $invoice = $do1->convert_to_invoice(); + +# test invoice afterwards + +ok ($invoice->shipvia eq "DHL, Versand am 06.03.2015, 1 Paket 17,00 kg", "ship via check"); +ok ($invoice->shippingpoint eq "Maisenhaus", "shipping point check"); +ok ($invoice->ordnumber eq "A16399", "ordnumber check"); +ok ($invoice->donumber eq "L20199", "donumber check"); +ok(($do1->closed) , 'Delivery Order is closed after conversion'); +ok (SL::DB::PaymentTerm->new(id => $invoice->{payment_id})->load->description eq "14Tage 2%Skonto, 30Tage netto", 'payment term description check'); + +# some test data from original client invoice console (!) +# my $invoice3 = SL::DB::Manager::Invoice->find_by( ordnumber => 'A16399' ); +# which will fail due to PTC Calculation differs from GUI-Calculation, see issue: http://redmine.kivitendo-premium.de/issues/82 +# pp $invoice3 +# values from gui should be: +#ok($invoice->amount == 1354.20000, 'amount check'); +#ok($invoice->marge_percent == 50.88666, 'marge percent check'); +#ok($invoice->marge_total == 579.08000, 'marge total check'); +#ok($invoice->netamount == 1137.98000, 'netamount check'); + + +# the values change if one reloads the object +# without reloading we get this failures +#not ok 17 - amount check +# Failed test 'amount check' +# at t/db_helper/convert_invoice.t line 272. +# got: '1354.17' +# expected: '1354.17000' +#not ok 18 - marge percent check +# Failed test 'marge percent check' +# at t/db_helper/convert_invoice.t line 273. +# got: '50.8857956342929' +# expected: '50.88580' +#not ok 19 - marge total check +# Failed test 'marge total check' +# at t/db_helper/convert_invoice.t line 274. +# got: '579.06' +# expected: '579.06000' +#not ok 20 - netamount check +# Failed test 'netamount check' +# at t/db_helper/convert_invoice.t line 275. +# got: '1137.96' +# expected: '1137.96000' + +$invoice->load; + +ok($invoice->currency_id eq '1', 'currency_id'); +ok($invoice->cusordnumber eq 'b84da', 'cusordnumber check'); +ok(SL::DB::Department->new(id => $invoice->{department_id})->load->description eq "Maisenhaus-Versand", 'department description'); +is($invoice->amount, '1354.17000', 'amount check'); +is($invoice->marge_percent, '50.88580', 'marge percent check'); +is($invoice->marge_total, '579.06000', 'marge total check'); +is($invoice->netamount, '1137.96000', 'netamount check'); + +# some item checks +ok(@ {$invoice->items_sorted}[0]->parts_id eq '26321', 'invoiceitem 1 linked with part'); +ok(2 == scalar@{ $invoice->invoiceitems }, 'two invoice items linked with invoice'); +is(@ {$invoice->items_sorted}[0]->position, 1, "position 1 order correct"); +is(@ {$invoice->items_sorted}[1]->position, 2, "position 2 order correct"); +is(@ {$invoice->items_sorted}[0]->part->partnumber, 'v-519160549', "partnumber 1 correct"); +is(@ {$invoice->items_sorted}[1]->part->partnumber, 'v-120160086', "partnumber 2 correct"); +is(@ {$invoice->items_sorted}[0]->qty, '2.00000', "pos 1 qty"); +is(@ {$invoice->items_sorted}[1]->qty, '3.00000', "pos 2 qty"); +is(@ {$invoice->items_sorted}[0]->discount, 0.25, "pos 1 discount"); +is(@ {$invoice->items_sorted}[1]->discount, 0.25, "pos 2 discount"); + +# more ideas: check onhand, lastcost (parsed lastcost) + +clear_up(); + +1;