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