Revision a7ca8ba2
Von Jan Büren vor mehr als 9 Jahren hinzugefügt
SL/DB/DeliveryOrder.pm | ||
---|---|---|
$_[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__
|
||
|
||
... | ... | |
Returns a string describing this record's type: either
|
||
C<sales_delivery_order> or C<purchase_delivery_order>.
|
||
|
||
=item C<convert_to_invoice %params>
|
||
|
||
Creates a new invoice with C<$self> as the basis by calling
|
||
L<SL::DB::Invoice::new_from>. That invoice is posted, and C<$self> is
|
||
linked to the new invoice via L<SL::DB::RecordLink>. C<$self>'s
|
||
C<closed> attribute is set to C<true>, and C<$self> is saved.
|
||
|
||
The arguments in C<%params> are passed to L<SL::DB::Invoice::post>.
|
||
|
||
Returns the new invoice instance on success and C<undef> 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
|
t/db_helper/convert_invoice.t | ||
---|---|---|
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;
|
Auch abrufbar als: Unified diff
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.