Bugfix: taxinclusive mit aus dem Auftrag übernehmen wenn aus Lieferschein eine Rechnu...
[kivitendo-erp.git] / bin / mozilla / do.pl
1 #=====================================================================
2 # LX-Office ERP
3 # Copyright (C) 2004
4 # Based on SQL-Ledger Version 2.1.9
5 # Web http://www.lx-office.org
6 #
7 #=====================================================================
8 # SQL-Ledger, Accounting
9 # Copyright (c) 1998-2003
10 #
11 #  Author: Dieter Simader
12 #   Email: dsimader@sql-ledger.org
13 #     Web: http://www.sql-ledger.org
14 #
15 #
16 # This program is free software; you can redistribute it and/or modify
17 # it under the terms of the GNU General Public License as published by
18 # the Free Software Foundation; either version 2 of the License, or
19 # (at your option) any later version.
20 #
21 # This program is distributed in the hope that it will be useful,
22 # but WITHOUT ANY WARRANTY; without even the implied warranty of
23 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24 # GNU General Public License for more details.
25 # You should have received a copy of the GNU General Public License
26 # along with this program; if not, write to the Free Software
27 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
28 # MA 02110-1335, USA.
29 #======================================================================
30 #
31 # Delivery orders
32 #======================================================================
33
34 use Carp;
35 use List::MoreUtils qw(uniq);
36 use List::Util qw(max sum);
37 use POSIX qw(strftime);
38
39 use SL::DB::DeliveryOrder;
40 use SL::DO;
41 use SL::IR;
42 use SL::IS;
43 use SL::MoreCommon qw(ary_diff restore_form save_form);
44 use SL::ReportGenerator;
45 use SL::WH;
46 use SL::YAML;
47 use Sort::Naturally ();
48 require "bin/mozilla/common.pl";
49 require "bin/mozilla/io.pl";
50 require "bin/mozilla/reportgenerator.pl";
51
52 use strict;
53
54 1;
55
56 # end of main
57
58 sub check_do_access {
59   $main::auth->assert($main::form->{type} . '_edit');
60 }
61
62 sub set_headings {
63   $main::lxdebug->enter_sub();
64
65   check_do_access();
66
67   my ($action) = @_;
68
69   my $form     = $main::form;
70   my $locale   = $main::locale;
71
72   if ($form->{type} eq 'purchase_delivery_order') {
73     $form->{vc}    = 'vendor';
74     $form->{title} = $action eq "edit" ? $locale->text('Edit Purchase Delivery Order') : $locale->text('Add Purchase Delivery Order');
75   } else {
76     $form->{vc}    = 'customer';
77     $form->{title} = $action eq "edit" ? $locale->text('Edit Sales Delivery Order') : $locale->text('Add Sales Delivery Order');
78   }
79
80   $form->{heading} = $locale->text('Delivery Order');
81
82   $main::lxdebug->leave_sub();
83 }
84
85 sub add {
86   $main::lxdebug->enter_sub();
87
88   check_do_access();
89
90   if (($::form->{type} =~ /purchase/) && !$::instance_conf->get_allow_new_purchase_invoice) {
91     $::form->show_generic_error($::locale->text("You do not have the permissions to access this function."));
92   }
93
94   my $form     = $main::form;
95
96   set_headings("add");
97
98   $form->{show_details} = $::myconfig{show_form_details};
99   $form->{callback} = build_std_url('action=add', 'type', 'vc') unless ($form->{callback});
100
101   order_links();
102   prepare_order();
103   display_form();
104
105   $main::lxdebug->leave_sub();
106 }
107
108 sub edit {
109   $main::lxdebug->enter_sub();
110
111   check_do_access();
112
113   my $form     = $main::form;
114
115   $form->{show_details} = $::myconfig{show_form_details};
116
117   # show history button
118   $form->{javascript} = qq|<script type="text/javascript" src="js/show_history.js"></script>|;
119   #/show hhistory button
120
121   $form->{simple_save} = 0;
122
123   set_headings("edit");
124
125   # editing without stuff to edit? try adding it first
126   if ($form->{rowcount} && !$form->{print_and_save}) {
127 #     map { $id++ if $form->{"multi_id_$_"} } (1 .. $form->{rowcount});
128 #     if (!$id) {
129
130       # reset rowcount
131       undef $form->{rowcount};
132       add();
133       $main::lxdebug->leave_sub();
134       return;
135 #     }
136   } elsif (!$form->{id}) {
137     add();
138     $main::lxdebug->leave_sub();
139     return;
140   }
141
142   my ($language_id, $printer_id);
143   if ($form->{print_and_save}) {
144     $form->{action}   = "dispatcher";
145     $form->{action_print} = "1";
146     $form->{resubmit} = 1;
147     $language_id      = $form->{language_id};
148     $printer_id       = $form->{printer_id};
149   }
150
151   set_headings("edit");
152
153   order_links();
154   prepare_order();
155
156   if ($form->{print_and_save}) {
157     $form->{language_id} = $language_id;
158     $form->{printer_id}  = $printer_id;
159   }
160
161   display_form();
162
163   $main::lxdebug->leave_sub();
164 }
165
166 sub order_links {
167   $main::lxdebug->enter_sub();
168
169   check_do_access();
170
171   my $form     = $main::form;
172   my %myconfig = %main::myconfig;
173
174   # retrieve order/quotation
175   my $editing = $form->{id};
176
177   DO->retrieve('vc'  => $form->{vc},
178                'ids' => $form->{id});
179
180   $form->backup_vars(qw(payment_id language_id taxzone_id salesman_id taxincluded cp_id intnotes delivery_term_id currency));
181
182   # get customer / vendor
183   if ($form->{vc} eq 'vendor') {
184     IR->get_vendor(\%myconfig, \%$form);
185     $form->{discount} = $form->{vendor_discount};
186   } else {
187     IS->get_customer(\%myconfig, \%$form);
188     $form->{discount} = $form->{customer_discount};
189   }
190
191   $form->restore_vars(qw(payment_id language_id taxzone_id intnotes cp_id delivery_term_id));
192   $form->restore_vars(qw(currency)) if ($form->{id} || $form->{convert_from_oe_ids});
193   $form->restore_vars(qw(taxincluded)) if $form->{id};
194   $form->restore_vars(qw(salesman_id)) if $editing;
195
196   $main::lxdebug->leave_sub();
197 }
198
199 sub prepare_order {
200   $main::lxdebug->enter_sub();
201
202   check_do_access();
203
204   my $form     = $main::form;
205   my %myconfig = %main::myconfig;
206
207   $form->{formname} = $form->{type} unless $form->{formname};
208
209   my $i = 0;
210   foreach my $ref (@{ $form->{form_details} }) {
211     $form->{rowcount} = ++$i;
212
213     map { $form->{"${_}_$i"} = $ref->{$_} } keys %{$ref};
214   }
215   for my $i (1 .. $form->{rowcount}) {
216     if ($form->{id}) {
217       $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"} * 100);
218     } else {
219       $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"});
220     }
221     my ($dec) = ($form->{"sellprice_$i"} =~ /\.(\d+)/);
222     $dec           = length $dec;
223     my $decimalplaces = ($dec > 2) ? $dec : 2;
224
225     # copy reqdate from deliverydate for invoice -> order conversion
226     $form->{"reqdate_$i"} = $form->{"deliverydate_$i"} unless $form->{"reqdate_$i"};
227
228     $form->{"sellprice_$i"} = $form->format_amount(\%myconfig, $form->{"sellprice_$i"}, $decimalplaces);
229     $form->{"lastcost_$i"} = $form->format_amount(\%myconfig, $form->{"lastcost_$i"}, $decimalplaces);
230
231     (my $dec_qty) = ($form->{"qty_$i"} =~ /\.(\d+)/);
232     $dec_qty = length $dec_qty;
233     $form->{"qty_$i"} = $form->format_amount(\%myconfig, $form->{"qty_$i"}, $dec_qty);
234   }
235
236   $main::lxdebug->leave_sub();
237 }
238
239 sub setup_do_action_bar {
240   my @transfer_qty   = qw(kivi.SalesPurchase.delivery_order_check_transfer_qty);
241   my @req_trans_desc = qw(kivi.SalesPurchase.check_transaction_description) x!!$::instance_conf->get_require_transaction_description_ps;
242   my $is_customer    = $::form->{vc} eq 'customer';
243
244   for my $bar ($::request->layout->get('actionbar')) {
245     $bar->add(
246       action =>
247         [ t8('Update'),
248           submit    => [ '#form', { action => "update" } ],
249           id        => 'update_button',
250           accesskey => 'enter',
251         ],
252
253       combobox => [
254         action => [
255           t8('Save'),
256           submit   => [ '#form', { action => "save" } ],
257           checks   => [ 'kivi.validate_form' ],
258           disabled => $::form->{delivered} ? t8('This record has already been delivered.') : undef,
259         ],
260         action => [
261           t8('Save as new'),
262           submit   => [ '#form', { action => "save_as_new" } ],
263           checks   => [ 'kivi.validate_form' ],
264           disabled => !$::form->{id},
265         ],
266         action => [
267           t8('Mark as closed'),
268           submit   => [ '#form', { action => "mark_closed" } ],
269           checks   => [ 'kivi.validate_form' ],
270           confirm  => t8('This will remove the delivery order from showing as open even if contents are not delivered. Proceed?'),
271           disabled => !$::form->{id}    ? t8('This record has not been saved yet.')
272                     : $::form->{closed} ? t8('This record has already been closed.')
273                     :                     undef,
274         ],
275       ], # end of combobox "Save"
276
277       action => [
278         t8('Delete'),
279         submit   => [ '#form', { action => "delete" } ],
280         confirm  => t8('Do you really want to delete this object?'),
281         disabled => !$::form->{id}                                                                              ? t8('This record has not been saved yet.')
282                   : $::form->{delivered}                                                                        ? t8('This record has already been delivered.')
283                   : ($::form->{vc} eq 'customer' && !$::instance_conf->get_sales_delivery_order_show_delete)    ? t8('Deleting this type of record has been disabled in the configuration.')
284                   : ($::form->{vc} eq 'vendor'   && !$::instance_conf->get_purchase_delivery_order_show_delete) ? t8('Deleting this type of record has been disabled in the configuration.')
285                   :                                                                                               undef,
286       ],
287
288       combobox => [
289         action => [
290           t8('Transfer out'),
291           submit   => [ '#form', { action => "transfer_out" } ],
292           checks   => [ 'kivi.validate_form', @transfer_qty ],
293           disabled => $::form->{delivered} ? t8('This record has already been delivered.') : undef,
294           only_if  => $is_customer,
295         ],
296         action => [
297           t8('Transfer out via default'),
298           submit   => [ '#form', { action => "transfer_out_default" } ],
299           checks   => [ 'kivi.validate_form' ],
300           disabled => $::form->{delivered} ? t8('This record has already been delivered.') : undef,
301           only_if  => $is_customer && $::instance_conf->get_transfer_default,
302         ],
303         action => [
304           t8('Transfer in'),
305           submit   => [ '#form', { action => "transfer_in" } ],
306           checks   => [ 'kivi.validate_form', @transfer_qty ],
307           disabled => $::form->{delivered} ? t8('This record has already been delivered.') : undef,
308           only_if  => !$is_customer,
309         ],
310         action => [
311           t8('Transfer in via default'),
312           submit   => [ '#form', { action => "transfer_in_default" } ],
313           checks   => [ 'kivi.validate_form' ],
314           disabled => $::form->{delivered} ? t8('This record has already been delivered.') : undef,
315           only_if  => !$is_customer && $::instance_conf->get_transfer_default,
316         ],
317       ], # end of combobox "Transfer out"
318
319
320       'separator',
321
322       action => [
323         t8('Invoice'),
324         submit => [ '#form', { action => "invoice" } ],
325         disabled => !$::form->{id} ? t8('This record has not been saved yet.') : undef,
326         confirm  => $::form->{delivered}                                                                         ? undef
327                   : ($::form->{vc} eq 'customer' && $::instance_conf->get_sales_delivery_order_check_stocked)    ? t8('This record has not been stocked out. Proceed?')
328                   : ($::form->{vc} eq 'vendor'   && $::instance_conf->get_purchase_delivery_order_check_stocked) ? t8('This record has not been stocked in. Proceed?')
329                   :                                                                                                undef,
330       ],
331
332       combobox => [
333         action => [ t8('Export') ],
334         action => [
335           t8('Print'),
336           call   => [ 'kivi.SalesPurchase.show_print_dialog' ],
337           checks => [ 'kivi.validate_form' ],
338         ],
339         action => [
340           t8('E Mail'),
341           call   => [ 'kivi.SalesPurchase.show_email_dialog' ],
342           checks => [ 'kivi.validate_form' ],
343           disabled => !$::form->{id} ? t8('This record has not been saved yet.') : undef,
344         ],
345       ], # end of combobox "Export"
346
347       combobox =>  [
348         action => [ t8('more') ],
349         action => [
350           t8('History'),
351           call     => [ 'set_history_window', $::form->{id} * 1, 'id' ],
352           disabled => !$::form->{id} ? t8('This record has not been saved yet.') : undef,
353         ],
354         action => [
355           t8('Follow-Up'),
356           call     => [ 'follow_up_window' ],
357           disabled => !$::form->{id} ? t8('This record has not been saved yet.') : undef,
358         ],
359       ], # end if combobox "more"
360     );
361   }
362   $::request->layout->add_javascripts('kivi.Validator.js');
363 }
364
365 sub setup_do_search_action_bar {
366   my %params = @_;
367
368   for my $bar ($::request->layout->get('actionbar')) {
369     $bar->add(
370       action => [
371         t8('Search'),
372         submit    => [ '#form' ],
373         accesskey => 'enter',
374         checks    => [ 'kivi.validate_form' ],
375       ],
376     );
377   }
378   $::request->layout->add_javascripts('kivi.Validator.js');
379 }
380
381 sub setup_do_orders_action_bar {
382   my %params = @_;
383
384   for my $bar ($::request->layout->get('actionbar')) {
385     $bar->add(
386       action => [
387         t8('New invoice'),
388         submit    => [ '#form', { action => 'invoice_multi' } ],
389         checks    => [ [ 'kivi.check_if_entries_selected', '#form tbody input[type=checkbox]' ] ],
390         accesskey => 'enter',
391       ],
392       action => [
393         t8('Print'),
394         call   => [ 'kivi.SalesPurchase.show_print_dialog', 'js:kivi.MassDeliveryOrderPrint.submitMultiOrders' ],
395         checks => [ [ 'kivi.check_if_entries_selected', '#form tbody input[type=checkbox]' ] ],
396       ],
397     );
398   }
399 }
400
401 sub form_header {
402   $main::lxdebug->enter_sub();
403
404   check_do_access();
405
406   my $form     = $main::form;
407   my %myconfig = %main::myconfig;
408
409   my $class       = "SL::DB::" . ($form->{vc} eq 'customer' ? 'Customer' : 'Vendor');
410   $form->{VC_OBJ} = $class->load_cached($form->{ $form->{vc} . '_id' });
411
412   $form->{CONTACT_OBJ}   = $form->{cp_id} ? SL::DB::Contact->load_cached($form->{cp_id}) : undef;
413   my $current_employee   = SL::DB::Manager::Employee->current;
414   $form->{employee_id}   = $form->{old_employee_id} if $form->{old_employee_id};
415   $form->{salesman_id}   = $form->{old_salesman_id} if $form->{old_salesman_id};
416   $form->{employee_id} ||= $current_employee->id;
417   $form->{salesman_id} ||= $current_employee->id;
418
419   my $vc = $form->{vc} eq "customer" ? "customers" : "vendors";
420   $form->get_lists("price_factors"  => "ALL_PRICE_FACTORS",
421                    "business_types" => "ALL_BUSINESS_TYPES",
422     );
423   $form->{ALL_DEPARTMENTS} = SL::DB::Manager::Department->get_all_sorted;
424   $form->{ALL_LANGUAGES}   = SL::DB::Manager::Language->get_all_sorted;
425
426   # Projects
427   my @old_project_ids = uniq grep { $_ } map { $_ * 1 } ($form->{"globalproject_id"}, map { $form->{"project_id_$_"} } 1..$form->{"rowcount"});
428   my @old_ids_cond    = @old_project_ids ? (id => \@old_project_ids) : ();
429   my @customer_cond;
430   if (($vc eq 'customers') && $::instance_conf->get_customer_projects_only_in_sales) {
431     @customer_cond = (
432       or => [
433         customer_id          => $::form->{customer_id},
434         billable_customer_id => $::form->{customer_id},
435       ]);
436   }
437   my @conditions = (
438     or => [
439       and => [ active => 1, @customer_cond ],
440       @old_ids_cond,
441     ]);
442
443   $::form->{ALL_PROJECTS}          = SL::DB::Manager::Project->get_all_sorted(query => \@conditions);
444   $::form->{ALL_EMPLOYEES}         = SL::DB::Manager::Employee->get_all_sorted(query => [ or => [ id => $::form->{employee_id},  deleted => 0 ] ]);
445   $::form->{ALL_SALESMEN}          = SL::DB::Manager::Employee->get_all_sorted(query => [ or => [ id => $::form->{salesman_id},  deleted => 0 ] ]);
446   $::form->{ALL_SHIPTO}            = SL::DB::Manager::Shipto->get_all_sorted(query => [
447     or => [ trans_id  => $::form->{"$::form->{vc}_id"} * 1, and => [ shipto_id => $::form->{shipto_id} * 1, trans_id => undef ] ]
448   ]);
449   $::form->{ALL_CONTACTS}          = SL::DB::Manager::Contact->get_all_sorted(query => [
450     or => [
451       cp_cv_id => $::form->{"$::form->{vc}_id"} * 1,
452       and      => [
453         cp_cv_id => undef,
454         cp_id    => $::form->{cp_id} * 1
455       ]
456     ]
457   ]);
458
459   my $dispatch_to_popup = '';
460   if ($form->{resubmit} && ($form->{format} eq "html")) {
461     $dispatch_to_popup  = "window.open('about:blank','Beleg'); document.do.target = 'Beleg';";
462     $dispatch_to_popup .= "document.do.submit();";
463   } elsif ($form->{resubmit} && $form->{action_print}) {
464     # emulate click for resubmitting actions
465     $dispatch_to_popup  = "kivi.SalesPurchase.show_print_dialog(); kivi.SalesPurchase.print_record();";
466   }
467   $::request->{layout}->add_javascripts_inline("\$(function(){$dispatch_to_popup});");
468
469
470   $form->{follow_up_trans_info} = $form->{donumber} .'('. $form->{VC_OBJ}->name .')' if $form->{VC_OBJ};
471
472   $::request->{layout}->use_javascript(map { "${_}.js" } qw(kivi.File kivi.MassDeliveryOrderPrint kivi.SalesPurchase kivi.Part kivi.CustomerVendor kivi.Validator ckeditor/ckeditor ckeditor/adapters/jquery kivi.io));
473
474   setup_do_action_bar();
475
476   $form->header();
477   # Fix für Bug 1082 Erwartet wird: 'abteilungsNAME--abteilungsID'
478   # und Erweiterung für Bug 1760:
479   # Das war leider nur ein Teil-Fix, da das Verhalten den 'Erneuern'-Knopf
480   # nicht überlebt. Konsequent jetzt auf L umgestellt
481   #   $ perldoc SL::Template::Plugin::L
482   # Daher entsprechend nur die Anpassung in form_header
483   # und in DO.pm gemacht. 4 Testfälle:
484   # department_id speichern                 | i.O.
485   # department_id lesen                     | i.O.
486   # department leer überlebt erneuern       | i.O.
487   # department nicht leer überlebt erneuern | i.O.
488   # $main::lxdebug->message(0, 'ABTEILUNGS ID in form?' . $form->{department_id});
489   print $form->parse_html_template('do/form_header');
490
491   $main::lxdebug->leave_sub();
492 }
493
494 sub form_footer {
495   $main::lxdebug->enter_sub();
496
497   check_do_access();
498
499   my $form     = $main::form;
500
501   $form->{PRINT_OPTIONS}      = setup_sales_purchase_print_options();
502   $form->{ALL_DELIVERY_TERMS} = SL::DB::Manager::DeliveryTerm->get_all_sorted();
503
504   my $shipto_cvars       = SL::DB::Shipto->new->cvars_by_config;
505   foreach my $var (@{ $shipto_cvars }) {
506     my $name = "shiptocvar_" . $var->config->name;
507     $var->value($form->{$name}) if exists $form->{$name};
508   }
509
510   print $form->parse_html_template('do/form_footer',
511     {transfer_default => ($::instance_conf->get_transfer_default),
512      shipto_cvars     => $shipto_cvars});
513
514   $main::lxdebug->leave_sub();
515 }
516
517 sub update_delivery_order {
518   $main::lxdebug->enter_sub();
519
520   check_do_access();
521
522   my $form     = $main::form;
523   my %myconfig = %main::myconfig;
524
525   set_headings($form->{"id"} ? "edit" : "add");
526
527   $form->{insertdate} = SL::DB::DeliveryOrder->new(id => $form->{id})->load->itime_as_date if $form->{id};
528
529   $form->{update} = 1;
530
531   my $payment_id;
532   $payment_id = $form->{payment_id} if $form->{payment_id};
533
534   my $vc = $form->{vc};
535   if (($form->{"previous_${vc}_id"} || $form->{"${vc}_id"}) != $form->{"${vc}_id"}) {
536     $::form->{salesman_id} = SL::DB::Manager::Employee->current->id if exists $::form->{salesman_id};
537
538     IS->get_customer(\%myconfig, $form) if $vc eq 'customer';
539     IR->get_vendor(\%myconfig, $form)   if $vc eq 'vendor';
540   }
541
542   $form->{discount} =  $form->{"$form->{vc}_discount"} if defined $form->{"$form->{vc}_discount"};
543   # Problem: Wenn man ohne Erneuern einen Kunden/Lieferanten
544   # wechselt, wird der entsprechende Kunden/ Lieferantenrabatt
545   # nicht übernommen. Grundproblem: In Commit 82574e78
546   # hab ich aus discount customer_discount und vendor_discount
547   # gemacht und entsprechend an den Oberflächen richtig hin-
548   # geschoben. Die damals bessere Lösung wäre gewesen:
549   # In den Templates nur die hidden für form-discount wieder ein-
550   # setzen dann wäre die Verrenkung jetzt nicht notwendig.
551   # TODO: Ggf. Bugfix 1284, 1575 und 817 wieder zusammenführen
552   # Testfälle: Kunden mit Rabatt 0 -> Rabatt 20 i.O.
553   #            Kunde mit Rabatt 20 -> Rabatt 0  i.O.
554   #            Kunde mit Rabatt 20 -> Rabatt 5,5 i.O.
555   $form->{payment_id} = $payment_id if $form->{payment_id} eq "";
556
557   my $i = $form->{rowcount};
558
559   if (   ($form->{"partnumber_$i"} eq "")
560       && ($form->{"description_$i"} eq "")
561       && ($form->{"partsgroup_$i"}  eq "")) {
562
563     check_form();
564
565   } else {
566
567     my $mode;
568     if ($form->{type} eq 'purchase_delivery_order') {
569       IR->retrieve_item(\%myconfig, $form);
570       $mode = 'IR';
571     } else {
572       IS->retrieve_item(\%myconfig, $form);
573       $mode = 'IS';
574     }
575
576     my $rows = scalar @{ $form->{item_list} };
577
578     if ($rows) {
579       $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
580       if( !$form->{"qty_$i"} ) {
581         $form->{"qty_$i"} = 1;
582       }
583
584       if ($rows > 1) {
585
586         select_item(mode => $mode, pre_entered_qty => $form->{"qty_$i"});
587         $::dispatcher->end_request;
588
589       } else {
590
591         my $sellprice = $form->parse_amount(\%myconfig, $form->{"sellprice_$i"});
592
593         map { $form->{"${_}_$i"} = $form->{item_list}[0]{$_} } keys %{ $form->{item_list}[0] };
594
595         $form->{"marge_price_factor_$i"} = $form->{item_list}->[0]->{price_factor};
596
597         if ($sellprice) {
598           $form->{"sellprice_$i"} = $sellprice;
599         } else {
600           my $record        = _make_record();
601           my $price_source  = SL::PriceSource->new(record_item => $record->items->[$i-1], record => $record);
602           my $best_price    = $price_source->best_price;
603           my $best_discount = $price_source->best_discount;
604
605           if ($best_price) {
606             $::form->{"sellprice_$i"}           = $best_price->price;
607             $::form->{"active_price_source_$i"} = $best_price->source;
608           }
609           if ($best_discount) {
610             $::form->{"discount_$i"}               = $best_discount->discount;
611             $::form->{"active_discount_source_$i"} = $best_discount->source;
612           }
613         }
614
615         $form->{"sellprice_$i"}          = $form->format_amount(\%myconfig, $form->{"sellprice_$i"});
616         $form->{"lastcost_$i"}           = $form->format_amount(\%myconfig, $form->{"lastcost_$i"});
617         $form->{"qty_$i"}                = $form->format_amount(\%myconfig, $form->{"qty_$i"});
618         $form->{"discount_$i"}           = $form->format_amount(\%myconfig, $form->{"discount_$i"} * 100.0);
619       }
620
621       display_form();
622
623     } else {
624
625       # ok, so this is a new part
626       # ask if it is a part or service item
627
628       if (   $form->{"partsgroup_$i"}
629           && ($form->{"partsnumber_$i"} eq "")
630           && ($form->{"description_$i"} eq "")) {
631         $form->{rowcount}--;
632         $form->{"discount_$i"} = "";
633         $form->{"not_discountable_$i"} = "";
634         display_form();
635
636       } else {
637         $form->{"id_$i"}   = 0;
638         new_item();
639       }
640     }
641   }
642
643   $main::lxdebug->leave_sub();
644 }
645
646 sub search {
647   $main::lxdebug->enter_sub();
648
649   check_do_access();
650
651   my $form     = $main::form;
652   my %myconfig = %main::myconfig;
653   my $locale   = $main::locale;
654
655   $form->{vc} = $form->{type} eq 'purchase_delivery_order' ? 'vendor' : 'customer';
656
657   $form->get_lists("projects"       => { "key" => "ALL_PROJECTS",
658                                          "all" => 1 },
659                    "business_types" => "ALL_BUSINESS_TYPES");
660   $form->{ALL_EMPLOYEES} = SL::DB::Manager::Employee->get_all_sorted(query => [ deleted => 0 ]);
661   $form->{ALL_DEPARTMENTS} = SL::DB::Manager::Department->get_all_sorted;
662   $form->{title}             = $locale->text('Delivery Orders');
663
664   setup_do_search_action_bar();
665
666   $form->header();
667
668   print $form->parse_html_template('do/search');
669
670   $main::lxdebug->leave_sub();
671 }
672
673 sub orders {
674   $main::lxdebug->enter_sub();
675
676   check_do_access();
677
678   my $form     = $main::form;
679   my %myconfig = %main::myconfig;
680   my $locale   = $main::locale;
681   my $cgi      = $::request->{cgi};
682
683   $::request->{layout}->use_javascript(map { "${_}.js" } qw(kivi.MassDeliveryOrderPrint kivi.SalesPurchase));
684   ($form->{ $form->{vc} }, $form->{"$form->{vc}_id"}) = split(/--/, $form->{ $form->{vc} });
685
686   report_generator_set_default_sort('transdate', 1);
687
688   DO->transactions();
689
690   $form->{rowcount} = scalar @{ $form->{DO} };
691
692   my @columns = qw(
693     ids                     transdate               reqdate
694     id                      donumber
695     ordnumber               customernumber          cusordnumber
696     name                    employee  salesman
697     shipvia                 globalprojectnumber
698     transaction_description department
699     open                    delivered
700     insertdate
701   );
702
703   $form->{l_open}      = $form->{l_closed} = "Y" if ($form->{open}      && $form->{closed});
704   $form->{l_delivered} = "Y"                     if ($form->{delivered} && $form->{notdelivered});
705
706   $form->{title}       = $locale->text('Delivery Orders');
707
708   my $attachment_basename = $form->{vc} eq 'vendor' ? $locale->text('purchase_delivery_order_list') : $locale->text('sales_delivery_order_list');
709
710   my $report = SL::ReportGenerator->new(\%myconfig, $form);
711
712   my @hidden_variables = map { "l_${_}" } @columns;
713   push @hidden_variables, $form->{vc}, qw(l_closed l_notdelivered open closed delivered notdelivered donumber ordnumber serialnumber cusordnumber
714                                           transaction_description transdatefrom transdateto reqdatefrom reqdateto
715                                           type vc employee_id salesman_id project_id parts_partnumber parts_description
716                                           insertdatefrom insertdateto business_id all department_id);
717
718   my $href = build_std_url('action=orders', grep { $form->{$_} } @hidden_variables);
719
720   my %column_defs = (
721     'ids'                     => { raw_header_data => SL::Presenter::Tag::checkbox_tag("", id => "multi_all", checkall => "[data-checkall=1]"), align => 'center' },
722     'transdate'               => { 'text' => $locale->text('Delivery Order Date'), },
723     'reqdate'                 => { 'text' => $locale->text('Reqdate'), },
724     'id'                      => { 'text' => $locale->text('ID'), },
725     'donumber'                => { 'text' => $locale->text('Delivery Order'), },
726     'ordnumber'               => { 'text' => $locale->text('Order'), },
727     'customernumber'          => { 'text' => $locale->text('Customer Number'), },
728     'cusordnumber'            => { 'text' => $locale->text('Customer Order Number'), },
729     'name'                    => { 'text' => $form->{vc} eq 'customer' ? $locale->text('Customer') : $locale->text('Vendor'), },
730     'employee'                => { 'text' => $locale->text('Employee'), },
731     'salesman'                => { 'text' => $locale->text('Salesman'), },
732     'shipvia'                 => { 'text' => $locale->text('Ship via'), },
733     'globalprojectnumber'     => { 'text' => $locale->text('Project Number'), },
734     'transaction_description' => { 'text' => $locale->text('Transaction description'), },
735     'open'                    => { 'text' => $locale->text('Open'), },
736     'delivered'               => { 'text' => $locale->text('Delivered'), },
737     'department'              => { 'text' => $locale->text('Department'), },
738     'insertdate'              => { 'text' => $locale->text('Insert Date'), },
739   );
740
741   foreach my $name (qw(id transdate reqdate donumber ordnumber name employee salesman shipvia transaction_description department insertdate)) {
742     my $sortdir                 = $form->{sort} eq $name ? 1 - $form->{sortdir} : $form->{sortdir};
743     $column_defs{$name}->{link} = $href . "&sort=$name&sortdir=$sortdir";
744   }
745
746   $form->{"l_type"} = "Y";
747   map { $column_defs{$_}->{visible} = $form->{"l_${_}"} ? 1 : 0 } @columns;
748
749   $column_defs{ids}->{visible} = 'HTML';
750
751   $report->set_columns(%column_defs);
752   $report->set_column_order(@columns);
753
754   $report->set_export_options('orders', @hidden_variables, qw(sort sortdir));
755
756   $report->set_sort_indicator($form->{sort}, $form->{sortdir});
757
758   my @options;
759   if ($form->{customer}) {
760     push @options, $locale->text('Customer') . " : $form->{customer}";
761   }
762   if ($form->{vendor}) {
763     push @options, $locale->text('Vendor') . " : $form->{vendor}";
764   }
765   if ($form->{cp_name}) {
766     push @options, $locale->text('Contact Person') . " : $form->{cp_name}";
767   }
768   if ($form->{department_id}) {
769     push @options, $locale->text('Department') . " : " . SL::DB::Department->new(id => $form->{department_id})->load->description;
770   }
771   if ($form->{donumber}) {
772     push @options, $locale->text('Delivery Order Number') . " : $form->{donumber}";
773   }
774   if ($form->{ordnumber}) {
775     push @options, $locale->text('Order Number') . " : $form->{ordnumber}";
776   }
777   push @options, $locale->text('Serial Number') . " : $form->{serialnumber}" if $form->{serialnumber};
778   if ($form->{business_id}) {
779     my $vc_type_label = $form->{vc} eq 'customer' ? $locale->text('Customer type') : $locale->text('Vendor type');
780     push @options, $vc_type_label . " : " . SL::DB::Business->new(id => $form->{business_id})->load->description;
781   }
782   if ($form->{transaction_description}) {
783     push @options, $locale->text('Transaction description') . " : $form->{transaction_description}";
784   }
785   if ($form->{parts_description}) {
786     push @options, $locale->text('Part Description') . " : $form->{parts_description}";
787   }
788   if ($form->{parts_partnumber}) {
789     push @options, $locale->text('Part Number') . " : $form->{parts_partnumber}";
790   }
791   if ( $form->{transdatefrom} or $form->{transdateto} ) {
792     push @options, $locale->text('Delivery Order Date');
793     push @options, $locale->text('From') . " " . $locale->date(\%myconfig, $form->{transdatefrom}, 1)     if $form->{transdatefrom};
794     push @options, $locale->text('Bis')  . " " . $locale->date(\%myconfig, $form->{transdateto},   1)     if $form->{transdateto};
795   };
796   if ( $form->{reqdatefrom} or $form->{reqdateto} ) {
797     push @options, $locale->text('Reqdate');
798     push @options, $locale->text('From') . " " . $locale->date(\%myconfig, $form->{reqdatefrom}, 1)       if $form->{reqdatefrom};
799     push @options, $locale->text('Bis')  . " " . $locale->date(\%myconfig, $form->{reqdateto},   1)       if $form->{reqdateto};
800   };
801   if ( $form->{insertdatefrom} or $form->{insertdateto} ) {
802     push @options, $locale->text('Insert Date');
803     push @options, $locale->text('From') . " " . $locale->date(\%myconfig, $form->{insertdatefrom}, 1)    if $form->{insertdatefrom};
804     push @options, $locale->text('Bis')  . " " . $locale->date(\%myconfig, $form->{insertdateto},   1)    if $form->{insertdateto};
805   };
806   if ($form->{open}) {
807     push @options, $locale->text('Open');
808   }
809   if ($form->{closed}) {
810     push @options, $locale->text('Closed');
811   }
812   if ($form->{delivered}) {
813     push @options, $locale->text('Delivered');
814   }
815   if ($form->{notdelivered}) {
816     push @options, $locale->text('Not delivered');
817   }
818   push @options, $locale->text('Quick Search') . " : $form->{all}" if $form->{all};
819
820   my $pr = SL::DB::Manager::Printer->find_by(
821       printer_description => $::locale->text("sales_delivery_order_printer"));
822   if ($pr ) {
823       $form->{printer_id} = $pr->id;
824   }
825
826   my $print_options = SL::Helper::PrintOptions->get_print_options(
827     options => {
828       hide_language_id => 1,
829       show_bothsided   => 1,
830       show_headers     => 1,
831     },
832   );
833
834   $report->set_options('top_info_text'        => join("\n", @options),
835                        'raw_top_info_text'    => $form->parse_html_template('do/orders_top'),
836                        'raw_bottom_info_text' => $form->parse_html_template('do/orders_bottom', { print_options => $print_options }),
837                        'output_format'        => 'HTML',
838                        'title'                => $form->{title},
839                        'attachment_basename'  => $attachment_basename . strftime('_%Y%m%d', localtime time),
840     );
841   $report->set_options_from_form();
842   $locale->set_numberformat_wo_thousands_separator(\%myconfig) if lc($report->{options}->{output_format}) eq 'csv';
843
844   # add sort and escape callback, this one we use for the add sub
845   $form->{callback} = $href .= "&sort=$form->{sort}";
846
847   # escape callback for href
848   my $callback = $form->escape($href);
849
850   my $edit_url       = build_std_url('action=edit', 'type', 'vc');
851   my $edit_order_url = ($::instance_conf->get_feature_experimental_order)
852                      ? build_std_url('script=controller.pl', 'action=Order/edit', 'type=' . ($form->{type} eq 'sales_delivery_order' ? 'sales_order' : 'purchase_order'))
853                      : build_std_url('script=oe.pl',         'action=edit',       'type=' . ($form->{type} eq 'sales_delivery_order' ? 'sales_order' : 'purchase_order'));
854
855   my $idx            = 1;
856
857   foreach my $dord (@{ $form->{DO} }) {
858     $dord->{open}      = $dord->{closed}    ? $locale->text('No')  : $locale->text('Yes');
859     $dord->{delivered} = $dord->{delivered} ? $locale->text('Yes') : $locale->text('No');
860
861     my $row = { map { $_ => { 'data' => $dord->{$_} } } @columns };
862
863     my $ord_id = $dord->{id};
864     $row->{ids}  = {
865       'raw_data' =>   $cgi->hidden('-name' => "trans_id_${idx}", '-value' => $ord_id)
866                     . $cgi->checkbox('-name' => "multi_id_${idx}",' id' => "multi_id_id_".$ord_id, '-value' => 1, 'data-checkall' => 1, '-label' => ''),
867       'valign'   => 'center',
868       'align'    => 'center',
869     };
870
871     $row->{donumber}->{link}  = $edit_url       . "&id=" . E($dord->{id})      . "&callback=${callback}";
872     $row->{ordnumber}->{link} = $edit_order_url . "&id=" . E($dord->{oe_id})   . "&callback=${callback}" if $dord->{oe_id};
873     $report->add_data($row);
874
875     $idx++;
876   }
877
878   setup_do_orders_action_bar();
879
880   $report->generate_with_headers();
881
882   $main::lxdebug->leave_sub();
883 }
884
885 sub save {
886   $main::lxdebug->enter_sub();
887
888   my (%params) = @_;
889
890   check_do_access();
891
892   my $form     = $main::form;
893   my %myconfig = %main::myconfig;
894   my $locale   = $main::locale;
895
896   $form->mtime_ischanged('delivery_orders');
897
898   $form->{defaultcurrency} = $form->get_default_currency(\%myconfig);
899
900   $form->isblank("transdate", $locale->text('Delivery Order Date missing!'));
901
902   $form->{donumber} =~ s/^\s*//g;
903   $form->{donumber} =~ s/\s*$//g;
904
905   my $msg = ucfirst $form->{vc};
906   $form->isblank($form->{vc} . "_id", $locale->text($msg . " missing!"));
907
908   # $locale->text('Customer missing!');
909   # $locale->text('Vendor missing!');
910
911   remove_emptied_rows();
912   validate_items();
913
914   # if the name changed get new values
915   my $vc = $form->{vc};
916   if (($form->{"previous_${vc}_id"} || $form->{"${vc}_id"}) != $form->{"${vc}_id"}) {
917     $::form->{salesman_id} = SL::DB::Manager::Employee->current->id if exists $::form->{salesman_id};
918
919     IS->get_customer(\%myconfig, $form) if $vc eq 'customer';
920     IR->get_vendor(\%myconfig, $form)   if $vc eq 'vendor';
921
922     update();
923     $::dispatcher->end_request;
924   }
925
926   $form->{id} = 0 if $form->{saveasnew};
927
928   DO->save();
929   # saving the history
930   if(!exists $form->{addition}) {
931     $form->{snumbers} = qq|donumber_| . $form->{donumber};
932     $form->{addition} = "SAVED";
933     $form->save_history;
934   }
935   # /saving the history
936
937   $form->{simple_save} = 1;
938   if (!$params{no_redirect} && !$form->{print_and_save}) {
939     delete @{$form}{ary_diff([keys %{ $form }], [qw(login id script type cursor_fokus)])};
940     edit();
941     $::dispatcher->end_request;
942   }
943   $main::lxdebug->leave_sub();
944 }
945
946 sub delete {
947   $main::lxdebug->enter_sub();
948
949   check_do_access();
950
951   my $form     = $main::form;
952   my %myconfig = %main::myconfig;
953   my $locale   = $main::locale;
954   my $ret;
955   if ($ret = DO->delete()) {
956     # saving the history
957     if(!exists $form->{addition}) {
958       $form->{snumbers} = qq|donumber_| . $form->{donumber};
959       $form->{addition} = "DELETED";
960       $form->save_history;
961     }
962     # /saving the history
963
964     $form->info($locale->text('Delivery Order deleted!'));
965     $::dispatcher->end_request;
966   }
967
968   $form->error($locale->text('Cannot delete delivery order!') . $ret);
969
970   $main::lxdebug->leave_sub();
971 }
972
973 sub invoice {
974   $main::lxdebug->enter_sub();
975
976   my $form     = $main::form;
977   my %myconfig = %main::myconfig;
978   my $locale   = $main::locale;
979
980   check_do_access();
981   $form->mtime_ischanged('delivery_orders');
982
983   $main::auth->assert($form->{type} eq 'purchase_delivery_order' ? 'vendor_invoice_edit' : 'invoice_edit');
984
985   $form->{convert_from_do_ids} = $form->{id};
986   # if we have a reqdate (Liefertermin), this is definetely the preferred
987   # deliverydate for invoices
988   $form->{deliverydate}        = $form->{reqdate} || $form->{transdate};
989   $form->{transdate}           = $form->{invdate} = $form->current_date(\%myconfig);
990   $form->{duedate}             = $form->current_date(\%myconfig, $form->{invdate}, $form->{terms} * 1);
991   $form->{defaultcurrency}     = $form->get_default_currency(\%myconfig);
992
993   $form->{rowcount}--;
994
995   delete @{$form}{qw(id closed delivered)};
996
997   my ($script, $buysell);
998   if ($form->{type} eq 'purchase_delivery_order') {
999     $form->{title}  = $locale->text('Add Vendor Invoice');
1000     $form->{script} = 'ir.pl';
1001     $script         = "ir";
1002     $buysell        = 'sell';
1003
1004   } else {
1005     $form->{title}  = $locale->text('Add Sales Invoice');
1006     $form->{script} = 'is.pl';
1007     $script         = "is";
1008     $buysell        = 'buy';
1009   }
1010
1011   for my $i (1 .. $form->{rowcount}) {
1012     map { $form->{"${_}_${i}"} = $form->parse_amount(\%myconfig, $form->{"${_}_${i}"}) if $form->{"${_}_${i}"} } qw(ship qty sellprice lastcost basefactor discount);
1013     # für bug 1284
1014     # adds a customer/vendor discount, unless we have a workflow case
1015     # CAVEAT: has to be done, after the above parse_amount
1016     unless ($form->{"ordnumber"}) {
1017       if ($form->{discount}) { # Falls wir einen Lieferanten-/Kundenrabatt haben
1018         # und rabattfähig sind, dann
1019         unless ($form->{"not_discountable_$i"}) {
1020           $form->{"discount_$i"} = $form->{discount}*100; # ... nehmen wir diesen Rabatt
1021         }
1022       }
1023     }
1024     $form->{"donumber_$i"} = $form->{donumber};
1025     $form->{"converted_from_delivery_order_items_id_$i"} = delete $form->{"delivery_order_items_id_$i"};
1026   }
1027
1028   $form->{type} = "invoice";
1029
1030   # locale messages
1031   $main::locale = Locale->new("$myconfig{countrycode}", "$script");
1032   $locale = $main::locale;
1033
1034   require "bin/mozilla/$form->{script}";
1035
1036   my $currency = $form->{currency};
1037   invoice_links();
1038
1039   if ($form->{ordnumber}) {
1040     require SL::DB::Order;
1041     my $vc_id  = $form->{type} =~ /^sales/ ? 'customer_id' : 'vendor_id';
1042     if (my $order = SL::DB::Manager::Order->find_by(ordnumber => $form->{ordnumber}, $vc_id => $form->{"$vc_id"})) {
1043       $order->load;
1044       $form->{orddate} = $order->transdate_as_date;
1045       $form->{$_}      = $order->$_ for qw(payment_id salesman_id taxzone_id quonumber taxincluded);
1046       $form->{taxincluded_changed_by_user} = 1;
1047     }
1048   }
1049
1050   $form->{currency}     = $currency;
1051   $form->{exchangerate} = "";
1052   $form->{forex}        = $form->check_exchangerate(\%myconfig, $form->{currency}, $form->{invdate}, $buysell);
1053   $form->{exchangerate} = $form->{forex} if ($form->{forex});
1054
1055   prepare_invoice();
1056
1057   # format amounts
1058   for my $i (1 .. $form->{rowcount}) {
1059     $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"});
1060
1061     my ($dec) = ($form->{"sellprice_$i"} =~ /\.(\d+)/);
1062     $dec           = length $dec;
1063     my $decimalplaces = ($dec > 2) ? $dec : 2;
1064
1065     # copy delivery date from reqdate for order -> invoice conversion
1066     $form->{"deliverydate_$i"} = $form->{"reqdate_$i"}
1067       unless $form->{"deliverydate_$i"};
1068
1069
1070     $form->{"sellprice_$i"} =
1071       $form->format_amount(\%myconfig, $form->{"sellprice_$i"},
1072                            $decimalplaces);
1073
1074     $form->{"lastcost_$i"} =
1075       $form->format_amount(\%myconfig, $form->{"lastcost_$i"},
1076                            $decimalplaces);
1077
1078     (my $dec_qty) = ($form->{"qty_$i"} =~ /\.(\d+)/);
1079     $dec_qty = length $dec_qty;
1080     $form->{"qty_$i"} =
1081       $form->format_amount(\%myconfig, $form->{"qty_$i"}, $dec_qty);
1082
1083   }
1084
1085   display_form();
1086
1087   $main::lxdebug->leave_sub();
1088 }
1089
1090 sub invoice_multi {
1091   $main::lxdebug->enter_sub();
1092
1093   my $form     = $main::form;
1094   my %myconfig = %main::myconfig;
1095   my $locale   = $main::locale;
1096
1097   check_do_access();
1098   $main::auth->assert($form->{type} eq 'sales_delivery_order' ? 'invoice_edit' : 'vendor_invoice_edit');
1099
1100   my @do_ids = map { $form->{"trans_id_$_"} } grep { $form->{"multi_id_$_"} } (1..$form->{rowcount});
1101
1102   if (!scalar @do_ids) {
1103     $form->show_generic_error($locale->text('You have not selected any delivery order.'));
1104   }
1105
1106   map { delete $form->{$_} } grep { m/^(?:trans|multi)_id_\d+/ } keys %{ $form };
1107
1108   if (!DO->retrieve('vc' => $form->{vc}, 'ids' => \@do_ids)) {
1109     $form->show_generic_error($form->{vc} eq 'customer' ?
1110                               $locale->text('You cannot create an invoice for delivery orders for different customers.') :
1111                               $locale->text('You cannot create an invoice for delivery orders from different vendors.'),
1112                               'back_button' => 1);
1113   }
1114
1115   my $source_type              = $form->{type};
1116   $form->{convert_from_do_ids} = join ' ', @do_ids;
1117   # bei der auswahl von mehreren Lieferscheinen fuer eine Rechnung, die einfach in donumber_array
1118   # zwischenspeichern (DO.pm) und als ' '-separierte Liste wieder zurueckschreiben
1119   # Hinweis: delete gibt den wert zurueck und loescht danach das element (nett und einfach)
1120   # $shell: perldoc perlunc; /delete EXPR
1121   $form->{donumber}            = delete $form->{donumber_array};
1122   $form->{ordnumber}           = delete $form->{ordnumber_array};
1123   $form->{cusordnumber}        = delete $form->{cusordnumber_array};
1124   $form->{deliverydate}        = $form->{transdate};
1125   $form->{transdate}           = $form->current_date(\%myconfig);
1126   $form->{duedate}             = $form->current_date(\%myconfig, $form->{invdate}, $form->{terms} * 1);
1127   $form->{type}                = "invoice";
1128   $form->{closed}              = 0;
1129   $form->{defaultcurrency}     = $form->get_default_currency(\%myconfig);
1130
1131   my ($script, $buysell);
1132   if ($source_type eq 'purchase_delivery_order') {
1133     $form->{title}  = $locale->text('Add Vendor Invoice');
1134     $form->{script} = 'ir.pl';
1135     $script         = "ir";
1136     $buysell        = 'sell';
1137
1138   } else {
1139     $form->{title}  = $locale->text('Add Sales Invoice');
1140     $form->{script} = 'is.pl';
1141     $script         = "is";
1142     $buysell        = 'buy';
1143   }
1144
1145   map { delete $form->{$_} } qw(id subject message cc bcc printed emailed queued);
1146
1147   # get vendor or customer discount
1148   my $vc_discount;
1149   my $saved_form = save_form();
1150   if ($form->{vc} eq 'vendor') {
1151     IR->get_vendor(\%myconfig, \%$form);
1152     $vc_discount = $form->{vendor_discount};
1153   } else {
1154     IS->get_customer(\%myconfig, \%$form);
1155     $vc_discount = $form->{customer_discount};
1156   }
1157   # use payment terms from customer or vendor
1158   restore_form($saved_form,0,qw(payment_id));
1159
1160   $form->{rowcount} = 0;
1161   foreach my $ref (@{ $form->{form_details} }) {
1162     $form->{rowcount}++;
1163     $ref->{reqdate} ||= $ref->{dord_transdate}; # copy transdates into each invoice row
1164     map { $form->{"${_}_$form->{rowcount}"} = $ref->{$_} } keys %{ $ref };
1165     map { $form->{"${_}_$form->{rowcount}"} = $form->format_amount(\%myconfig, $ref->{$_}) } qw(qty sellprice lastcost);
1166     $form->{"converted_from_delivery_order_items_id_$form->{rowcount}"} = delete $form->{"delivery_order_items_id_$form->{rowcount}"};
1167
1168     if ($vc_discount){ # falls wir einen Lieferanten/Kundenrabatt haben
1169       # und keinen anderen discount wert an $i ...
1170       $form->{"discount_$form->{rowcount}"} ||= $vc_discount; # ... nehmen wir diesen Rabatt
1171     }
1172
1173     $form->{"discount_$form->{rowcount}"}   = $form->{"discount_$form->{rowcount}"}  * 100; #s.a. Bug 1151
1174     # Anm.: Eine Änderung des discounts in der SL/DO.pm->retrieve (select (doi.discount * 100) as discount) ergibt in psql einen
1175     # Wert von 10.0000001490116. Ferner ist der Rabatt in der Rechnung dann bei 1.0 (?). Deswegen lasse ich das hier. jb 10.10.09
1176
1177     $form->{"discount_$form->{rowcount}"} = $form->format_amount(\%myconfig, $form->{"discount_$form->{rowcount}"});
1178   }
1179   delete $form->{form_details};
1180
1181   $locale = Locale->new("$myconfig{countrycode}", "$script");
1182
1183   require "bin/mozilla/$form->{script}";
1184
1185   invoice_links();
1186   prepare_invoice();
1187
1188   display_form();
1189
1190   $main::lxdebug->leave_sub();
1191 }
1192
1193 sub save_as_new {
1194   $main::lxdebug->enter_sub();
1195
1196   check_do_access();
1197
1198   my $form     = $main::form;
1199
1200   $form->{saveasnew} = 1;
1201   $form->{closed}    = 0;
1202   $form->{delivered} = 0;
1203   map { delete $form->{$_} } qw(printed emailed queued);
1204   delete @{ $form }{ grep { m/^stock_(?:in|out)_\d+/ } keys %{ $form } };
1205   $form->{"converted_from_delivery_order_items_id_$_"} = delete $form->{"delivery_order_items_id_$_"} for 1 .. $form->{"rowcount"};
1206   # Let kivitendo assign a new order number if the user hasn't changed the
1207   # previous one. If it has been changed manually then use it as-is.
1208   $form->{donumber} =~ s/^\s*//g;
1209   $form->{donumber} =~ s/\s*$//g;
1210   if ($form->{saved_donumber} && ($form->{saved_donumber} eq $form->{donumber})) {
1211     delete($form->{donumber});
1212   }
1213
1214   save();
1215
1216   $main::lxdebug->leave_sub();
1217 }
1218
1219 sub calculate_stock_in_out {
1220   $main::lxdebug->enter_sub();
1221
1222   my $form     = $main::form;
1223
1224   my $i = shift;
1225
1226   if (!$form->{"id_${i}"}) {
1227     $main::lxdebug->leave_sub();
1228     return '';
1229   }
1230
1231   my $all_units = AM->retrieve_all_units();
1232
1233   my $in_out   = $form->{type} =~ /^sales/ ? 'out' : 'in';
1234   my $sinfo    = DO->unpack_stock_information('packed' => $form->{"stock_${in_out}_${i}"});
1235
1236   my $do_qty   = AM->sum_with_unit($::form->{"qty_$i"}, $::form->{"unit_$i"});
1237   my $sum      = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $sinfo });
1238   my $matches  = $do_qty == $sum;
1239
1240   my $content  = $form->format_amount_units('amount'      => $sum * 1,
1241                                             'part_unit'   => $form->{"partunit_$i"},
1242                                             'amount_unit' => $all_units->{$form->{"partunit_$i"}}->{base_unit},
1243                                             'conv_units'  => 'convertible_not_smaller',
1244                                             'max_places'  => 2);
1245   $content     = qq|<span id="stock_in_out_qty_display_${i}">${content}</span><input type=hidden id='stock_in_out_qty_matches_$i' value='$matches'> <input type="button" onclick="open_stock_in_out_window('${in_out}', $i);" value="?">|;
1246
1247   $main::lxdebug->leave_sub();
1248
1249   return $content;
1250 }
1251
1252 sub get_basic_bin_wh_info {
1253   $main::lxdebug->enter_sub();
1254
1255   my $stock_info = shift;
1256
1257   my $form     = $main::form;
1258
1259   foreach my $sinfo (@{ $stock_info }) {
1260     next unless ($sinfo->{bin_id});
1261
1262     my $bin_info = WH->get_basic_bin_info('id' => $sinfo->{bin_id});
1263     map { $sinfo->{"${_}_description"} = $sinfo->{"${_}description"} = $bin_info->{"${_}_description"} } qw(bin warehouse);
1264   }
1265
1266   $main::lxdebug->leave_sub();
1267 }
1268
1269 sub stock_in_out_form {
1270   $main::lxdebug->enter_sub();
1271
1272   my $form     = $main::form;
1273
1274   if ($form->{in_out} eq 'out') {
1275     stock_out_form();
1276   } else {
1277     stock_in_form();
1278   }
1279
1280   $main::lxdebug->leave_sub();
1281 }
1282
1283 sub redo_stock_info {
1284   $main::lxdebug->enter_sub();
1285
1286   my %params    = @_;
1287
1288   my $form     = $main::form;
1289
1290   my @non_empty = grep { $_->{qty} } @{ $params{stock_info} };
1291
1292   if ($params{add_empty_row}) {
1293     push @non_empty, {
1294       'warehouse_id' => scalar(@non_empty) ? $non_empty[-1]->{warehouse_id} : undef,
1295       'bin_id'       => scalar(@non_empty) ? $non_empty[-1]->{bin_id}       : undef,
1296     };
1297   }
1298
1299   @{ $params{stock_info} } = @non_empty;
1300
1301   $main::lxdebug->leave_sub();
1302 }
1303
1304 sub update_stock_in {
1305   $main::lxdebug->enter_sub();
1306
1307   my $form     = $main::form;
1308   my %myconfig = %main::myconfig;
1309
1310   my $stock_info = [];
1311
1312   foreach my $i (1..$form->{rowcount}) {
1313     $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
1314     push @{ $stock_info }, { map { $_ => $form->{"${_}_${i}"} } qw(warehouse_id bin_id chargenumber
1315                                                                    bestbefore qty unit delivery_order_items_stock_id) };
1316   }
1317
1318   display_stock_in_form($stock_info);
1319
1320   $main::lxdebug->leave_sub();
1321 }
1322
1323 sub stock_in_form {
1324   $main::lxdebug->enter_sub();
1325
1326   my $form     = $main::form;
1327
1328   my $stock_info = DO->unpack_stock_information('packed' => $form->{stock});
1329
1330   display_stock_in_form($stock_info);
1331
1332   $main::lxdebug->leave_sub();
1333 }
1334
1335 sub display_stock_in_form {
1336   $main::lxdebug->enter_sub();
1337
1338   my $stock_info = shift;
1339
1340   my $form     = $main::form;
1341   my %myconfig = %main::myconfig;
1342   my $locale   = $main::locale;
1343
1344   $form->{title} = $locale->text('Stock');
1345
1346   my $part_info  = IC->get_basic_part_info('id' => $form->{parts_id});
1347
1348   # Standardlagerplatz für Standard-Auslagern verwenden, falls keiner für die Ware explizit definiert wurde
1349   if ($::instance_conf->get_transfer_default_use_master_default_bin) {
1350     $part_info->{warehouse_id} ||= $::instance_conf->get_warehouse_id;
1351     $part_info->{bin_id}       ||= $::instance_conf->get_bin_id;
1352   }
1353
1354   my $units      = AM->retrieve_units(\%myconfig, $form);
1355   # der zweite Parameter von unit_select_data gibt den default-Namen (selected) vor
1356   my $units_data = AM->unit_select_data($units, $form->{do_unit}, undef, $part_info->{unit});
1357
1358   $form->get_lists('warehouses' => { 'key'    => 'WAREHOUSES',
1359                                      'bins'   => 'BINS' });
1360
1361   redo_stock_info('stock_info' => $stock_info, 'add_empty_row' => !$form->{delivered});
1362
1363   get_basic_bin_wh_info($stock_info);
1364
1365   $form->header(no_layout => 1);
1366   print $form->parse_html_template('do/stock_in_form', { 'UNITS'      => $units_data,
1367                                                          'STOCK_INFO' => $stock_info,
1368                                                          'PART_INFO'  => $part_info, });
1369
1370   $main::lxdebug->leave_sub();
1371 }
1372
1373 sub _stock_in_out_set_qty_display {
1374   my $stock_info       = shift;
1375   my $form             = $::form;
1376   my $all_units        = AM->retrieve_all_units();
1377   my $sum              = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
1378   $form->{qty_display} = $form->format_amount_units(amount      => $sum * 1,
1379                                                     part_unit   => $form->{partunit},
1380                                                     amount_unit => $all_units->{ $form->{partunit} }->{base_unit},
1381                                                     conv_units  => 'convertible_not_smaller',
1382                                                     max_places  => 2);
1383 }
1384
1385 sub set_stock_in {
1386   $main::lxdebug->enter_sub();
1387
1388   my $form     = $main::form;
1389   my %myconfig = %main::myconfig;
1390
1391   my $stock_info = [];
1392
1393   foreach my $i (1..$form->{rowcount}) {
1394     $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
1395
1396     next if ($form->{"qty_$i"} <= 0);
1397
1398     push @{ $stock_info }, { map { $_ => $form->{"${_}_${i}"} } qw(delivery_order_items_stock_id warehouse_id bin_id chargenumber bestbefore qty unit) };
1399   }
1400
1401   $form->{stock} = SL::YAML::Dump($stock_info);
1402
1403   _stock_in_out_set_qty_display($stock_info);
1404
1405   my $do_qty       = AM->sum_with_unit($::form->parse_amount(\%::myconfig, $::form->{do_qty}), $::form->{do_unit});
1406   my $transfer_qty = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
1407
1408   $form->header();
1409   print $form->parse_html_template('do/set_stock_in_out', {
1410     qty_matches => $do_qty == $transfer_qty,
1411   });
1412
1413   $main::lxdebug->leave_sub();
1414 }
1415
1416 sub stock_out_form {
1417   $main::lxdebug->enter_sub();
1418
1419   my $form     = $main::form;
1420   my %myconfig = %main::myconfig;
1421   my $locale   = $main::locale;
1422
1423   $form->{title} = $locale->text('Release From Stock');
1424
1425   my $part_info  = IC->get_basic_part_info('id' => $form->{parts_id});
1426
1427   my $units      = AM->retrieve_units(\%myconfig, $form);
1428   my $units_data = AM->unit_select_data($units, undef, undef, $part_info->{unit});
1429
1430   my @contents   = DO->get_item_availability('parts_id' => $form->{parts_id});
1431
1432   my $stock_info = DO->unpack_stock_information('packed' => $form->{stock});
1433
1434   if (!$form->{delivered}) {
1435     foreach my $row (@contents) {
1436       $row->{available_qty} = $form->format_amount_units('amount'      => $row->{qty} * 1,
1437                                                          'part_unit'   => $part_info->{unit},
1438                                                          'conv_units'  => 'convertible_not_smaller',
1439                                                          'max_places'  => 2);
1440
1441       foreach my $sinfo (@{ $stock_info }) {
1442         next if (($row->{bin_id}       != $sinfo->{bin_id}) ||
1443                  ($row->{warehouse_id} != $sinfo->{warehouse_id}) ||
1444                  ($row->{chargenumber} ne $sinfo->{chargenumber}) ||
1445                  ($row->{bestbefore}   ne $sinfo->{bestbefore}));
1446
1447         map { $row->{"stock_$_"} = $sinfo->{$_} } qw(qty unit error delivery_order_items_stock_id);
1448       }
1449     }
1450
1451   } else {
1452     get_basic_bin_wh_info($stock_info);
1453
1454     foreach my $sinfo (@{ $stock_info }) {
1455       map { $sinfo->{"stock_$_"} = $sinfo->{$_} } qw(qty unit);
1456     }
1457   }
1458
1459   $form->header(no_layout => 1);
1460   print $form->parse_html_template('do/stock_out_form', { 'UNITS'      => $units_data,
1461                                                           'WHCONTENTS' => $form->{delivered} ? $stock_info : \@contents,
1462                                                           'PART_INFO'  => $part_info, });
1463
1464   $main::lxdebug->leave_sub();
1465 }
1466
1467 sub set_stock_out {
1468   $main::lxdebug->enter_sub();
1469
1470   my $form     = $main::form;
1471   my %myconfig = %main::myconfig;
1472   my $locale   = $main::locale;
1473
1474   my $stock_info = [];
1475
1476   foreach my $i (1 .. $form->{rowcount}) {
1477     $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
1478
1479     next if ($form->{"qty_$i"} <= 0);
1480
1481     push @{ $stock_info }, {
1482       'warehouse_id' => $form->{"warehouse_id_$i"},
1483       'bin_id'       => $form->{"bin_id_$i"},
1484       'chargenumber' => $form->{"chargenumber_$i"},
1485       'bestbefore'   => $form->{"bestbefore_$i"},
1486       'qty'          => $form->{"qty_$i"},
1487       'unit'         => $form->{"unit_$i"},
1488       'row'          => $i,
1489       'delivery_order_items_stock_id'  => $form->{"delivery_order_items_stock_id_$i"},
1490     };
1491   }
1492
1493   my @errors     = DO->check_stock_availability('requests' => $stock_info,
1494                                                 'parts_id' => $form->{parts_id});
1495
1496   $form->{stock} = SL::YAML::Dump($stock_info);
1497
1498   if (@errors) {
1499     $form->{ERRORS} = [];
1500     map { push @{ $form->{ERRORS} }, $locale->text('Error in row #1: The quantity you entered is bigger than the stocked quantity.', $_->{row}); } @errors;
1501     stock_in_out_form();
1502
1503   } else {
1504     _stock_in_out_set_qty_display($stock_info);
1505
1506     my $do_qty       = AM->sum_with_unit($::form->parse_amount(\%::myconfig, $::form->{do_qty}), $::form->{do_unit});
1507     my $transfer_qty = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
1508
1509     $form->header();
1510     print $form->parse_html_template('do/set_stock_in_out', {
1511       qty_matches => $do_qty == $transfer_qty,
1512     });
1513   }
1514
1515   $main::lxdebug->leave_sub();
1516 }
1517
1518 sub transfer_in {
1519   $main::lxdebug->enter_sub();
1520
1521   my $form     = $main::form;
1522   my %myconfig = %main::myconfig;
1523   my $locale   = $main::locale;
1524
1525   if ($form->{id} && DO->is_marked_as_delivered(id => $form->{id})) {
1526     $form->show_generic_error($locale->text('The parts for this delivery order have already been transferred in.'));
1527   }
1528
1529   save(no_redirect => 1);
1530
1531   my @part_ids = map { $form->{"id_${_}"} } grep { $form->{"id_${_}"} && $form->{"stock_in_${_}"} } (1 .. $form->{rowcount});
1532   my @all_requests;
1533
1534   if (@part_ids) {
1535     my $units         = AM->retrieve_units(\%myconfig, $form);
1536     my %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
1537     my %request_map;
1538
1539     $form->{ERRORS}   = [];
1540
1541     foreach my $i (1 .. $form->{rowcount}) {
1542       next unless ($form->{"id_$i"} && $form->{"stock_in_$i"});
1543
1544       my $row_sum_base_qty = 0;
1545       my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
1546
1547       foreach my $request (@{ DO->unpack_stock_information('packed' => $form->{"stock_in_$i"}) }) {
1548         $request->{parts_id}  = $form->{"id_$i"};
1549         $row_sum_base_qty    += $request->{qty} * $units->{$request->{unit}}->{factor} / $base_unit_factor;
1550
1551         $request->{project_id} = $form->{"project_id_$i"} || $form->{globalproject_id};
1552
1553         push @all_requests, $request;
1554       }
1555
1556       next if (0 == $row_sum_base_qty);
1557
1558       my $do_base_qty = $form->parse_amount(\%myconfig, $form->{"qty_$i"}) * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
1559
1560 #      if ($do_base_qty != $row_sum_base_qty) {
1561 #        push @{ $form->{ERRORS} }, $locale->text('Error in position #1: You must either assign no stock at all or the full quantity of #2 #3.',
1562 #                                                 $i, $form->{"qty_$i"}, $form->{"unit_$i"});
1563 #      }
1564     }
1565
1566     if (@{ $form->{ERRORS} }) {
1567       push @{ $form->{ERRORS} }, $locale->text('The delivery order has not been marked as delivered. The warehouse contents have not changed.');
1568
1569       set_headings('edit');
1570       update();
1571       $main::lxdebug->leave_sub();
1572
1573       $::dispatcher->end_request;
1574     }
1575   }
1576
1577   DO->transfer_in_out('direction' => 'in',
1578                       'requests'  => \@all_requests);
1579
1580   SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
1581
1582   $form->{callback} = 'do.pl?action=edit&type=purchase_delivery_order&id=' . $form->escape($form->{id});
1583   $form->redirect;
1584
1585   $main::lxdebug->leave_sub();
1586 }
1587
1588 sub transfer_out {
1589   $main::lxdebug->enter_sub();
1590
1591   my $form     = $main::form;
1592   my %myconfig = %main::myconfig;
1593   my $locale   = $main::locale;
1594
1595   if ($form->{id} && DO->is_marked_as_delivered(id => $form->{id})) {
1596     $form->show_generic_error($locale->text('The parts for this delivery order have already been transferred out.'));
1597   }
1598
1599   save(no_redirect => 1);
1600
1601   my @part_ids = map { $form->{"id_${_}"} } grep { $form->{"id_${_}"} && $form->{"stock_out_${_}"} } (1 .. $form->{rowcount});
1602   my @all_requests;
1603
1604   if (@part_ids) {
1605     my $units         = AM->retrieve_units(\%myconfig, $form);
1606     my %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
1607     my %request_map;
1608
1609     $form->{ERRORS}   = [];
1610
1611     foreach my $i (1 .. $form->{rowcount}) {
1612       next unless ($form->{"id_$i"} && $form->{"stock_out_$i"});
1613
1614       my $row_sum_base_qty = 0;
1615       my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
1616
1617       foreach my $request (@{ DO->unpack_stock_information('packed' => $form->{"stock_out_$i"}) }) {
1618         $request->{parts_id} = $form->{"id_$i"};
1619         $request->{base_qty} = $request->{qty} * $units->{$request->{unit}}->{factor} / $base_unit_factor;
1620         $request->{project_id} = $form->{"project_id_$i"} ? $form->{"project_id_$i"} : $form->{globalproject_id};
1621
1622         my $map_key          = join '--', ($form->{"id_$i"}, @{$request}{qw(warehouse_id bin_id chargenumber bestbefore)});
1623
1624         $request_map{$map_key}                 ||= $request;
1625         $request_map{$map_key}->{sum_base_qty} ||= 0;
1626         $request_map{$map_key}->{sum_base_qty}  += $request->{base_qty};
1627         $row_sum_base_qty                       += $request->{base_qty};
1628
1629         push @all_requests, $request;
1630       }
1631
1632       next if (0 == $row_sum_base_qty);
1633
1634       my $do_base_qty = $form->{"qty_$i"} * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
1635
1636 #      if ($do_base_qty != $row_sum_base_qty) {
1637 #        push @{ $form->{ERRORS} }, $locale->text('Error in position #1: You must either assign no transfer at all or the full quantity of #2 #3.',
1638 #                                                 $i, $form->{"qty_$i"}, $form->{"unit_$i"});
1639 #      }
1640     }
1641
1642     if (%request_map) {
1643       my @bin_ids      = map { $_->{bin_id} } values %request_map;
1644       my %bin_info_map = WH->get_basic_bin_info('id' => \@bin_ids);
1645       my @contents     = DO->get_item_availability('parts_id' => \@part_ids);
1646
1647       foreach my $inv (@contents) {
1648         my $map_key = join '--', @{$inv}{qw(parts_id warehouse_id bin_id chargenumber bestbefore)};
1649
1650         next unless ($request_map{$map_key});
1651
1652         my $request    = $request_map{$map_key};
1653         $request->{ok} = $request->{sum_base_qty} <= $inv->{qty};
1654       }
1655
1656       foreach my $request (values %request_map) {
1657         next if ($request->{ok});
1658
1659         my $pinfo = $part_info_map{$request->{parts_id}};
1660         my $binfo = $bin_info_map{$request->{bin_id}};
1661
1662         if ($::instance_conf->get_show_bestbefore) {
1663             push @{ $form->{ERRORS} }, $locale->text("There is not enough available of '#1' at warehouse '#2', bin '#3', #4, #5, for the transfer of #6.",
1664                                                      $pinfo->{description},
1665                                                      $binfo->{warehouse_description},
1666                                                      $binfo->{bin_description},
1667                                                      $request->{chargenumber} ? $locale->text('chargenumber #1', $request->{chargenumber}) : $locale->text('no chargenumber'),
1668                                                      $request->{bestbefore} ? $locale->text('bestbefore #1', $request->{bestbefore}) : $locale->text('no bestbefore'),
1669                                                      $form->format_amount_units('amount'      => $request->{sum_base_qty},
1670                                                                                 'part_unit'   => $pinfo->{unit},
1671                                                                                 'conv_units'  => 'convertible_not_smaller'));
1672         } else {
1673             push @{ $form->{ERRORS} }, $locale->text("There is not enough available of '#1' at warehouse '#2', bin '#3', #4, for the transfer of #5.",
1674                                                      $pinfo->{description},
1675                                                      $binfo->{warehouse_description},
1676                                                      $binfo->{bin_description},
1677                                                      $request->{chargenumber} ? $locale->text('chargenumber #1', $request->{chargenumber}) : $locale->text('no chargenumber'),
1678                                                      $form->format_amount_units('amount'      => $request->{sum_base_qty},
1679                                                                                 'part_unit'   => $pinfo->{unit},
1680                                                                                 'conv_units'  => 'convertible_not_smaller'));
1681         }
1682       }
1683     }
1684
1685     if (@{ $form->{ERRORS} }) {
1686       push @{ $form->{ERRORS} }, $locale->text('The delivery order has not been marked as delivered. The warehouse contents have not changed.');
1687
1688       set_headings('edit');
1689       update();
1690       $main::lxdebug->leave_sub();
1691
1692       $::dispatcher->end_request;
1693     }
1694   }
1695   DO->transfer_in_out('direction' => 'out',
1696                       'requests'  => \@all_requests);
1697
1698   SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
1699
1700   $form->{callback} = 'do.pl?action=edit&type=sales_delivery_order&id=' . $form->escape($form->{id});
1701   $form->redirect;
1702
1703   $main::lxdebug->leave_sub();
1704 }
1705
1706 sub mark_closed {
1707   $main::lxdebug->enter_sub();
1708
1709   my $form     = $main::form;
1710
1711   DO->close_orders('ids' => [ $form->{id} ]);
1712
1713   $form->{closed} = 1;
1714
1715   update();
1716
1717   $main::lxdebug->leave_sub();
1718 }
1719
1720 sub display_form {
1721   $::lxdebug->enter_sub;
1722
1723   $::auth->assert('purchase_delivery_order_edit | sales_delivery_order_edit');
1724
1725   relink_accounts();
1726   retrieve_partunits();
1727
1728   my $new_rowcount = $::form->{"rowcount"} * 1 + 1;
1729   $::form->{"project_id_${new_rowcount}"} = $::form->{"globalproject_id"};
1730
1731   $::form->language_payment(\%::myconfig);
1732
1733   Common::webdav_folder($::form);
1734
1735   form_header();
1736   display_row(++$::form->{rowcount});
1737   form_footer();
1738
1739   $::lxdebug->leave_sub;
1740 }
1741
1742 sub yes {
1743   call_sub($main::form->{yes_nextsub});
1744 }
1745
1746 sub no {
1747   call_sub($main::form->{no_nextsub});
1748 }
1749
1750 sub update {
1751   call_sub($main::form->{update_nextsub} || $main::form->{nextsub} || 'update_delivery_order');
1752 }
1753
1754 sub dispatcher {
1755   my $form     = $main::form;
1756   my $locale   = $main::locale;
1757
1758   foreach my $action (qw(update print save transfer_out transfer_out_default sort
1759                          transfer_in transfer_in_default mark_closed save_as_new invoice delete)) {
1760     if ($form->{"action_${action}"}) {
1761       call_sub($action);
1762       return;
1763     }
1764   }
1765
1766   $form->error($locale->text('No action defined.'));
1767 }
1768
1769 sub transfer_out_default {
1770   $main::lxdebug->enter_sub();
1771
1772   my $form     = $main::form;
1773
1774   transfer_in_out_default('direction' => 'out');
1775
1776   $main::lxdebug->leave_sub();
1777 }
1778
1779 sub transfer_in_default {
1780   $main::lxdebug->enter_sub();
1781
1782   my $form     = $main::form;
1783
1784   transfer_in_out_default('direction' => 'in');
1785
1786   $main::lxdebug->leave_sub();
1787 }
1788
1789 # Falls das Standardlagerverfahren aktiv ist, wird
1790 # geprüft, ob alle Standardlagerplätze für die Auslager-
1791 # artikel vorhanden sind UND ob die Warenmenge ausreicht zum
1792 # Auslagern. Falls nicht wird entsprechend eine Fehlermeldung
1793 # generiert. Offen Chargennummer / bestbefore wird nicht berücksichtigt
1794 sub transfer_in_out_default {
1795   $main::lxdebug->enter_sub();
1796
1797   my $form     = $main::form;
1798   my %myconfig = %main::myconfig;
1799   my $locale   = $main::locale;
1800   my %params   = @_;
1801
1802   my (%missing_default_bins, %qty_parts, @all_requests, %part_info_map, $default_warehouse_id, $default_bin_id);
1803
1804   Common::check_params(\%params, qw(direction));
1805
1806   # entsprechende defaults holen, falls standardlagerplatz verwendet werden soll
1807   if ($::instance_conf->get_transfer_default_use_master_default_bin) {
1808     $default_warehouse_id = $::instance_conf->get_warehouse_id;
1809     $default_bin_id       = $::instance_conf->get_bin_id;
1810   }
1811
1812
1813   my @part_ids = map { $form->{"id_${_}"} } (1 .. $form->{rowcount});
1814   if (@part_ids) {
1815     my $units         = AM->retrieve_units(\%myconfig, $form);
1816     %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
1817     foreach my $i (1 .. $form->{rowcount}) {
1818       next unless ($form->{"id_$i"});
1819       my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
1820       my $qty =   $form->parse_amount(\%myconfig, $form->{"qty_$i"}) * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
1821
1822       $form->show_generic_error($locale->text("Cannot transfer negative entries." )) if ($qty < 0);
1823       # if we do not want to transfer services and this part is a service, set qty to zero
1824       # ... and do not create a hash entry in %qty_parts below (will skip check for bins for the transfer == out case)
1825       # ... and push only a empty (undef) element to @all_requests (will skip check for bin_id and warehouse_id and will not alter the row)
1826
1827       $qty = 0 if (!$::instance_conf->get_transfer_default_services && $part_info_map{$form->{"id_$i"}}->{part_type} eq 'service');
1828       $qty_parts{$form->{"id_$i"}} += $qty;
1829       if ($qty == 0) {
1830         delete $qty_parts{$form->{"id_$i"}} unless $qty_parts{$form->{"id_$i"}};
1831         undef $form->{"stock_in_$i"};
1832       }
1833
1834       $part_info_map{$form->{"id_$i"}}{bin_id}       ||= $default_bin_id;
1835       $part_info_map{$form->{"id_$i"}}{warehouse_id} ||= $default_warehouse_id;
1836
1837       push @all_requests, ($qty == 0) ? { } : {
1838                         'chargenumber' => '',  #?? die müsste entsprechend geholt werden
1839                         #'bestbefore' => undef, # TODO wird nicht berücksichtigt
1840                         'bin_id' => $part_info_map{$form->{"id_$i"}}{bin_id},
1841                         'qty' => $qty,
1842                         'parts_id' => $form->{"id_$i"},
1843                         'comment' => $locale->text("Default transfer delivery order"),
1844                         'unit' => $part_info_map{$form->{"id_$i"}}{unit},
1845                         'warehouse_id' => $part_info_map{$form->{"id_$i"}}{warehouse_id},
1846                         'oe_id' => $form->{id},
1847                         'project_id' => $form->{"project_id_$i"} ? $form->{"project_id_$i"} : $form->{globalproject_id}
1848                       };
1849     }
1850
1851     # jetzt wird erst überprüft, ob die Stückzahl entsprechend stimmt.
1852     # check if bin (transfer in and transfer out and qty (transfer out) is correct
1853     foreach my $key (keys %qty_parts) {
1854
1855       $missing_default_bins{$key}{missing_bin} = 1 unless ($part_info_map{$key}{bin_id});
1856       next unless ($part_info_map{$key}{bin_id}); # abbruch
1857
1858       if ($params{direction} eq 'out') {  # wird nur für ausgehende Mengen benötigt
1859         my ($max_qty, $error) = WH->get_max_qty_parts_bin(parts_id => $key, bin_id => $part_info_map{$key}{bin_id});
1860         if ($error == 1) {
1861           # wir können nicht entscheiden, welche charge oder mhd (bestbefore) ausgewählt sein soll
1862           # deshalb rückmeldung nach oben geben, manuell auszulagern
1863           # TODO Bei nur einem Treffer mit Charge oder bestbefore wäre das noch möglich
1864           $missing_default_bins{$key}{chargenumber} = 1;
1865         }
1866         if ($max_qty < $qty_parts{$key}){
1867           $missing_default_bins{$key}{missing_qty} = $max_qty - $qty_parts{$key};
1868         }
1869       }
1870     }
1871   } # if @parts_id
1872
1873   # Abfrage für Fehlerbehandlung (nur bei direction == out)
1874   if (scalar (keys %missing_default_bins)) {
1875     my $fehlertext;
1876     foreach my $fehler (keys %missing_default_bins) {
1877
1878       my $ware = WH->get_part_description(parts_id => $fehler);
1879       if ($missing_default_bins{$fehler}{missing_bin}){
1880         $fehlertext .= "Kein Standardlagerplatz definiert bei $ware <br>";
1881       }
1882       if ($missing_default_bins{$fehler}{missing_qty}) {  # missing_qty
1883         $fehlertext .= "Es fehlen " . $missing_default_bins{$fehler}{missing_qty}*-1 .
1884                        " von $ware auf dem Standard-Lagerplatz " . $part_info_map{$fehler}{bin} .   " zum Auslagern<br>";
1885       }
1886       if ($missing_default_bins{$fehler}{chargenumber}){
1887         $fehlertext .= "Die Ware hat eine Chargennummer oder eine Mindesthaltbarkeit definiert.
1888                         Hier kann man nicht automatisch entscheiden.
1889                         Bitte diesen Lieferschein manuell auslagern.
1890                         Bei: $ware";
1891       }
1892       # auslagern soll immer gehen, auch wenn nicht genügend auf lager ist.
1893       # der lagerplatz ist hier extra konfigurierbar, bspw. Lager-Korrektur mit
1894       # Lagerplatz Lagerplatz-Korrektur
1895       my $default_warehouse_id_ignore_onhand = $::instance_conf->get_warehouse_id_ignore_onhand;
1896       my $default_bin_id_ignore_onhand       = $::instance_conf->get_bin_id_ignore_onhand;
1897       if ($::instance_conf->get_transfer_default_ignore_onhand && $default_bin_id_ignore_onhand) {
1898         # entsprechende defaults holen
1899         # falls chargenumber, bestbefore oder anzahl nicht stimmt, auf automatischen
1900         # lagerplatz wegbuchen!
1901         foreach (@all_requests) {
1902           if ($_->{parts_id} eq $fehler){
1903           $_->{bin_id}        = $default_bin_id_ignore_onhand;
1904           $_->{warehouse_id}  = $default_warehouse_id_ignore_onhand;
1905           }
1906         }
1907       } else {
1908         #$main::lxdebug->message(0, 'Fehlertext: ' . $fehlertext);
1909         $form->show_generic_error($locale->text("Cannot transfer. <br> Reason:<br>#1", $fehlertext ));
1910       }
1911     }
1912   }
1913
1914
1915   # hier der eigentliche fallunterschied für in oder out
1916   my $prefix   = $params{direction} eq 'in' ? 'in' : 'out';
1917
1918   # dieser array_ref ist für DO->save da:
1919   # einmal die all_requests in YAML verwandeln, damit delivery_order_items_stock
1920   # gefüllt werden kann.
1921   # could be dumped to the form in the first loop,
1922   # but maybe bin_id and warehouse_id has changed to the "korrekturlager" with
1923   # allowed negative qty ($::instance_conf->get_warehouse_id_ignore_onhand) ...
1924   my $i = 0;
1925   foreach (@all_requests){
1926     $i++;
1927     next unless scalar(%{ $_ });
1928     $form->{"stock_${prefix}_$i"} = SL::YAML::Dump([$_]);
1929   }
1930
1931   save(no_redirect => 1); # Wir können auslagern, deshalb beleg speichern
1932                           # und in delivery_order_items_stock speichern
1933
1934   # ... and fill back the persistent dois_id for inventory fk
1935   undef (@all_requests);
1936   foreach my $i (1 .. $form->{rowcount}) {
1937     next unless ($form->{"id_$i"} && $form->{"stock_${prefix}_$i"});
1938     push @all_requests, @{ DO->unpack_stock_information('packed' => $form->{"stock_${prefix}_$i"}) };
1939   }
1940   DO->transfer_in_out('direction' => $prefix,
1941                       'requests'  => \@all_requests);
1942
1943   SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
1944
1945   $form->{callback} = 'do.pl?action=edit&type=sales_delivery_order&id=' . $form->escape($form->{id}) if $params{direction} eq 'out';
1946   $form->{callback} = 'do.pl?action=edit&type=purchase_delivery_order&id=' . $form->escape($form->{id}) if $params{direction} eq 'in';
1947   $form->redirect;
1948
1949 }
1950
1951 sub sort {
1952   $main::lxdebug->enter_sub();
1953
1954   check_do_access();
1955
1956   my $form     = $main::form;
1957   my %temp_hash;
1958
1959   save(no_redirect => 1); # has to be done, at least for newly added positions
1960
1961   # hashify partnumbers, positions. key is delivery_order_items_id
1962   for my $i (1 .. ($form->{rowcount}) ) {
1963     $temp_hash{$form->{"delivery_order_items_id_$i"}} = { runningnumber => $form->{"runningnumber_$i"}, partnumber => $form->{"partnumber_$i"} };
1964     if ($form->{id} && $form->{"discount_$i"}) {
1965       # prepare_order assumes a db value if there is a form->id and multiplies *100
1966       # We hope for new controller code (no more format_amount/parse_amount distinction)
1967       $form->{"discount_$i"} /=100;
1968     }
1969   }
1970   # naturally sort partnumbers and get a sorted array of doi_ids
1971   my @sorted_doi_ids =  sort { Sort::Naturally::ncmp($temp_hash{$a}->{"partnumber"}, $temp_hash{$b}->{"partnumber"}) }  keys %temp_hash;
1972
1973
1974   my $new_number = 1;
1975
1976   for (@sorted_doi_ids) {
1977     $form->{"runningnumber_$temp_hash{$_}->{runningnumber}"} = $new_number;
1978     $new_number++;
1979   }
1980   # all parse_amounts changes are in form (i.e. , to .) therefore we need
1981   # another format_amount to change it back, for the next save ;-(
1982   # works great except for row discounts (see above comment)
1983   prepare_order();
1984
1985
1986     $main::lxdebug->leave_sub();
1987     save();
1988 }
1989
1990 __END__
1991
1992 =pod
1993
1994 =encoding utf8
1995
1996 =head1 NAME
1997
1998 do.pl - Script for all calls to delivery order
1999
2000 =head1 FUNCTIONS
2001
2002 =over 2
2003
2004 =item C<sort>
2005
2006 Sorts all position with Natural Sort. Can be activated in form_footer.html like this
2007 C<E<lt>input class="submit" type="submit" name="action_sort" id="sort_button" value="[% 'Sort and Save' | $T8 %]"E<gt>>
2008
2009 =back
2010
2011 =head1 TODO
2012
2013 Sort and Save can be implemented as an optional button if configuration ca be set by client config.
2014 Example coding for database scripts and templates in (git show af2f24b8), check also
2015 autogeneration for rose (scripts/rose_auto_create_model.pl --h)