Lieferschein: direktes, erstmaliges Drucken gefixt
[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);
1046     }
1047   }
1048
1049   $form->{currency}     = $currency;
1050   $form->{exchangerate} = "";
1051   $form->{forex}        = $form->check_exchangerate(\%myconfig, $form->{currency}, $form->{invdate}, $buysell);
1052   $form->{exchangerate} = $form->{forex} if ($form->{forex});
1053
1054   prepare_invoice();
1055
1056   # format amounts
1057   for my $i (1 .. $form->{rowcount}) {
1058     $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"});
1059
1060     my ($dec) = ($form->{"sellprice_$i"} =~ /\.(\d+)/);
1061     $dec           = length $dec;
1062     my $decimalplaces = ($dec > 2) ? $dec : 2;
1063
1064     # copy delivery date from reqdate for order -> invoice conversion
1065     $form->{"deliverydate_$i"} = $form->{"reqdate_$i"}
1066       unless $form->{"deliverydate_$i"};
1067
1068
1069     $form->{"sellprice_$i"} =
1070       $form->format_amount(\%myconfig, $form->{"sellprice_$i"},
1071                            $decimalplaces);
1072
1073     $form->{"lastcost_$i"} =
1074       $form->format_amount(\%myconfig, $form->{"lastcost_$i"},
1075                            $decimalplaces);
1076
1077     (my $dec_qty) = ($form->{"qty_$i"} =~ /\.(\d+)/);
1078     $dec_qty = length $dec_qty;
1079     $form->{"qty_$i"} =
1080       $form->format_amount(\%myconfig, $form->{"qty_$i"}, $dec_qty);
1081
1082   }
1083
1084   display_form();
1085
1086   $main::lxdebug->leave_sub();
1087 }
1088
1089 sub invoice_multi {
1090   $main::lxdebug->enter_sub();
1091
1092   my $form     = $main::form;
1093   my %myconfig = %main::myconfig;
1094   my $locale   = $main::locale;
1095
1096   check_do_access();
1097   $main::auth->assert($form->{type} eq 'sales_delivery_order' ? 'invoice_edit' : 'vendor_invoice_edit');
1098
1099   my @do_ids = map { $form->{"trans_id_$_"} } grep { $form->{"multi_id_$_"} } (1..$form->{rowcount});
1100
1101   if (!scalar @do_ids) {
1102     $form->show_generic_error($locale->text('You have not selected any delivery order.'));
1103   }
1104
1105   map { delete $form->{$_} } grep { m/^(?:trans|multi)_id_\d+/ } keys %{ $form };
1106
1107   if (!DO->retrieve('vc' => $form->{vc}, 'ids' => \@do_ids)) {
1108     $form->show_generic_error($form->{vc} eq 'customer' ?
1109                               $locale->text('You cannot create an invoice for delivery orders for different customers.') :
1110                               $locale->text('You cannot create an invoice for delivery orders from different vendors.'),
1111                               'back_button' => 1);
1112   }
1113
1114   my $source_type              = $form->{type};
1115   $form->{convert_from_do_ids} = join ' ', @do_ids;
1116   # bei der auswahl von mehreren Lieferscheinen fuer eine Rechnung, die einfach in donumber_array
1117   # zwischenspeichern (DO.pm) und als ' '-separierte Liste wieder zurueckschreiben
1118   # Hinweis: delete gibt den wert zurueck und loescht danach das element (nett und einfach)
1119   # $shell: perldoc perlunc; /delete EXPR
1120   $form->{donumber}            = delete $form->{donumber_array};
1121   $form->{ordnumber}           = delete $form->{ordnumber_array};
1122   $form->{cusordnumber}        = delete $form->{cusordnumber_array};
1123   $form->{deliverydate}        = $form->{transdate};
1124   $form->{transdate}           = $form->current_date(\%myconfig);
1125   $form->{duedate}             = $form->current_date(\%myconfig, $form->{invdate}, $form->{terms} * 1);
1126   $form->{type}                = "invoice";
1127   $form->{closed}              = 0;
1128   $form->{defaultcurrency}     = $form->get_default_currency(\%myconfig);
1129
1130   my ($script, $buysell);
1131   if ($source_type eq 'purchase_delivery_order') {
1132     $form->{title}  = $locale->text('Add Vendor Invoice');
1133     $form->{script} = 'ir.pl';
1134     $script         = "ir";
1135     $buysell        = 'sell';
1136
1137   } else {
1138     $form->{title}  = $locale->text('Add Sales Invoice');
1139     $form->{script} = 'is.pl';
1140     $script         = "is";
1141     $buysell        = 'buy';
1142   }
1143
1144   map { delete $form->{$_} } qw(id subject message cc bcc printed emailed queued);
1145
1146   # get vendor or customer discount
1147   my $vc_discount;
1148   my $saved_form = save_form();
1149   if ($form->{vc} eq 'vendor') {
1150     IR->get_vendor(\%myconfig, \%$form);
1151     $vc_discount = $form->{vendor_discount};
1152   } else {
1153     IS->get_customer(\%myconfig, \%$form);
1154     $vc_discount = $form->{customer_discount};
1155   }
1156   # use payment terms from customer or vendor
1157   restore_form($saved_form,0,qw(payment_id));
1158
1159   $form->{rowcount} = 0;
1160   foreach my $ref (@{ $form->{form_details} }) {
1161     $form->{rowcount}++;
1162     $ref->{reqdate} ||= $ref->{dord_transdate}; # copy transdates into each invoice row
1163     map { $form->{"${_}_$form->{rowcount}"} = $ref->{$_} } keys %{ $ref };
1164     map { $form->{"${_}_$form->{rowcount}"} = $form->format_amount(\%myconfig, $ref->{$_}) } qw(qty sellprice lastcost);
1165     $form->{"converted_from_delivery_order_items_id_$form->{rowcount}"} = delete $form->{"delivery_order_items_id_$form->{rowcount}"};
1166
1167     if ($vc_discount){ # falls wir einen Lieferanten/Kundenrabatt haben
1168       # und keinen anderen discount wert an $i ...
1169       $form->{"discount_$form->{rowcount}"} ||= $vc_discount; # ... nehmen wir diesen Rabatt
1170     }
1171
1172     $form->{"discount_$form->{rowcount}"}   = $form->{"discount_$form->{rowcount}"}  * 100; #s.a. Bug 1151
1173     # Anm.: Eine Änderung des discounts in der SL/DO.pm->retrieve (select (doi.discount * 100) as discount) ergibt in psql einen
1174     # Wert von 10.0000001490116. Ferner ist der Rabatt in der Rechnung dann bei 1.0 (?). Deswegen lasse ich das hier. jb 10.10.09
1175
1176     $form->{"discount_$form->{rowcount}"} = $form->format_amount(\%myconfig, $form->{"discount_$form->{rowcount}"});
1177   }
1178   delete $form->{form_details};
1179
1180   $locale = Locale->new("$myconfig{countrycode}", "$script");
1181
1182   require "bin/mozilla/$form->{script}";
1183
1184   invoice_links();
1185   prepare_invoice();
1186
1187   display_form();
1188
1189   $main::lxdebug->leave_sub();
1190 }
1191
1192 sub save_as_new {
1193   $main::lxdebug->enter_sub();
1194
1195   check_do_access();
1196
1197   my $form     = $main::form;
1198
1199   $form->{saveasnew} = 1;
1200   $form->{closed}    = 0;
1201   $form->{delivered} = 0;
1202   map { delete $form->{$_} } qw(printed emailed queued);
1203   delete @{ $form }{ grep { m/^stock_(?:in|out)_\d+/ } keys %{ $form } };
1204   $form->{"converted_from_delivery_order_items_id_$_"} = delete $form->{"delivery_order_items_id_$_"} for 1 .. $form->{"rowcount"};
1205   # Let kivitendo assign a new order number if the user hasn't changed the
1206   # previous one. If it has been changed manually then use it as-is.
1207   $form->{donumber} =~ s/^\s*//g;
1208   $form->{donumber} =~ s/\s*$//g;
1209   if ($form->{saved_donumber} && ($form->{saved_donumber} eq $form->{donumber})) {
1210     delete($form->{donumber});
1211   }
1212
1213   save();
1214
1215   $main::lxdebug->leave_sub();
1216 }
1217
1218 sub calculate_stock_in_out {
1219   $main::lxdebug->enter_sub();
1220
1221   my $form     = $main::form;
1222
1223   my $i = shift;
1224
1225   if (!$form->{"id_${i}"}) {
1226     $main::lxdebug->leave_sub();
1227     return '';
1228   }
1229
1230   my $all_units = AM->retrieve_all_units();
1231
1232   my $in_out   = $form->{type} =~ /^sales/ ? 'out' : 'in';
1233   my $sinfo    = DO->unpack_stock_information('packed' => $form->{"stock_${in_out}_${i}"});
1234
1235   my $do_qty   = AM->sum_with_unit($::form->{"qty_$i"}, $::form->{"unit_$i"});
1236   my $sum      = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $sinfo });
1237   my $matches  = $do_qty == $sum;
1238
1239   my $content  = $form->format_amount_units('amount'      => $sum * 1,
1240                                             'part_unit'   => $form->{"partunit_$i"},
1241                                             'amount_unit' => $all_units->{$form->{"partunit_$i"}}->{base_unit},
1242                                             'conv_units'  => 'convertible_not_smaller',
1243                                             'max_places'  => 2);
1244   $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="?">|;
1245
1246   $main::lxdebug->leave_sub();
1247
1248   return $content;
1249 }
1250
1251 sub get_basic_bin_wh_info {
1252   $main::lxdebug->enter_sub();
1253
1254   my $stock_info = shift;
1255
1256   my $form     = $main::form;
1257
1258   foreach my $sinfo (@{ $stock_info }) {
1259     next unless ($sinfo->{bin_id});
1260
1261     my $bin_info = WH->get_basic_bin_info('id' => $sinfo->{bin_id});
1262     map { $sinfo->{"${_}_description"} = $sinfo->{"${_}description"} = $bin_info->{"${_}_description"} } qw(bin warehouse);
1263   }
1264
1265   $main::lxdebug->leave_sub();
1266 }
1267
1268 sub stock_in_out_form {
1269   $main::lxdebug->enter_sub();
1270
1271   my $form     = $main::form;
1272
1273   if ($form->{in_out} eq 'out') {
1274     stock_out_form();
1275   } else {
1276     stock_in_form();
1277   }
1278
1279   $main::lxdebug->leave_sub();
1280 }
1281
1282 sub redo_stock_info {
1283   $main::lxdebug->enter_sub();
1284
1285   my %params    = @_;
1286
1287   my $form     = $main::form;
1288
1289   my @non_empty = grep { $_->{qty} } @{ $params{stock_info} };
1290
1291   if ($params{add_empty_row}) {
1292     push @non_empty, {
1293       'warehouse_id' => scalar(@non_empty) ? $non_empty[-1]->{warehouse_id} : undef,
1294       'bin_id'       => scalar(@non_empty) ? $non_empty[-1]->{bin_id}       : undef,
1295     };
1296   }
1297
1298   @{ $params{stock_info} } = @non_empty;
1299
1300   $main::lxdebug->leave_sub();
1301 }
1302
1303 sub update_stock_in {
1304   $main::lxdebug->enter_sub();
1305
1306   my $form     = $main::form;
1307   my %myconfig = %main::myconfig;
1308
1309   my $stock_info = [];
1310
1311   foreach my $i (1..$form->{rowcount}) {
1312     $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
1313     push @{ $stock_info }, { map { $_ => $form->{"${_}_${i}"} } qw(warehouse_id bin_id chargenumber
1314                                                                    bestbefore qty unit delivery_order_items_stock_id) };
1315   }
1316
1317   display_stock_in_form($stock_info);
1318
1319   $main::lxdebug->leave_sub();
1320 }
1321
1322 sub stock_in_form {
1323   $main::lxdebug->enter_sub();
1324
1325   my $form     = $main::form;
1326
1327   my $stock_info = DO->unpack_stock_information('packed' => $form->{stock});
1328
1329   display_stock_in_form($stock_info);
1330
1331   $main::lxdebug->leave_sub();
1332 }
1333
1334 sub display_stock_in_form {
1335   $main::lxdebug->enter_sub();
1336
1337   my $stock_info = shift;
1338
1339   my $form     = $main::form;
1340   my %myconfig = %main::myconfig;
1341   my $locale   = $main::locale;
1342
1343   $form->{title} = $locale->text('Stock');
1344
1345   my $part_info  = IC->get_basic_part_info('id' => $form->{parts_id});
1346
1347   # Standardlagerplatz für Standard-Auslagern verwenden, falls keiner für die Ware explizit definiert wurde
1348   if ($::instance_conf->get_transfer_default_use_master_default_bin) {
1349     $part_info->{warehouse_id} ||= $::instance_conf->get_warehouse_id;
1350     $part_info->{bin_id}       ||= $::instance_conf->get_bin_id;
1351   }
1352
1353   my $units      = AM->retrieve_units(\%myconfig, $form);
1354   # der zweite Parameter von unit_select_data gibt den default-Namen (selected) vor
1355   my $units_data = AM->unit_select_data($units, $form->{do_unit}, undef, $part_info->{unit});
1356
1357   $form->get_lists('warehouses' => { 'key'    => 'WAREHOUSES',
1358                                      'bins'   => 'BINS' });
1359
1360   redo_stock_info('stock_info' => $stock_info, 'add_empty_row' => !$form->{delivered});
1361
1362   get_basic_bin_wh_info($stock_info);
1363
1364   $form->header(no_layout => 1);
1365   print $form->parse_html_template('do/stock_in_form', { 'UNITS'      => $units_data,
1366                                                          'STOCK_INFO' => $stock_info,
1367                                                          'PART_INFO'  => $part_info, });
1368
1369   $main::lxdebug->leave_sub();
1370 }
1371
1372 sub _stock_in_out_set_qty_display {
1373   my $stock_info       = shift;
1374   my $form             = $::form;
1375   my $all_units        = AM->retrieve_all_units();
1376   my $sum              = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
1377   $form->{qty_display} = $form->format_amount_units(amount      => $sum * 1,
1378                                                     part_unit   => $form->{partunit},
1379                                                     amount_unit => $all_units->{ $form->{partunit} }->{base_unit},
1380                                                     conv_units  => 'convertible_not_smaller',
1381                                                     max_places  => 2);
1382 }
1383
1384 sub set_stock_in {
1385   $main::lxdebug->enter_sub();
1386
1387   my $form     = $main::form;
1388   my %myconfig = %main::myconfig;
1389
1390   my $stock_info = [];
1391
1392   foreach my $i (1..$form->{rowcount}) {
1393     $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
1394
1395     next if ($form->{"qty_$i"} <= 0);
1396
1397     push @{ $stock_info }, { map { $_ => $form->{"${_}_${i}"} } qw(delivery_order_items_stock_id warehouse_id bin_id chargenumber bestbefore qty unit) };
1398   }
1399
1400   $form->{stock} = SL::YAML::Dump($stock_info);
1401
1402   _stock_in_out_set_qty_display($stock_info);
1403
1404   my $do_qty       = AM->sum_with_unit($::form->parse_amount(\%::myconfig, $::form->{do_qty}), $::form->{do_unit});
1405   my $transfer_qty = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
1406
1407   $form->header();
1408   print $form->parse_html_template('do/set_stock_in_out', {
1409     qty_matches => $do_qty == $transfer_qty,
1410   });
1411
1412   $main::lxdebug->leave_sub();
1413 }
1414
1415 sub stock_out_form {
1416   $main::lxdebug->enter_sub();
1417
1418   my $form     = $main::form;
1419   my %myconfig = %main::myconfig;
1420   my $locale   = $main::locale;
1421
1422   $form->{title} = $locale->text('Release From Stock');
1423
1424   my $part_info  = IC->get_basic_part_info('id' => $form->{parts_id});
1425
1426   my $units      = AM->retrieve_units(\%myconfig, $form);
1427   my $units_data = AM->unit_select_data($units, undef, undef, $part_info->{unit});
1428
1429   my @contents   = DO->get_item_availability('parts_id' => $form->{parts_id});
1430
1431   my $stock_info = DO->unpack_stock_information('packed' => $form->{stock});
1432
1433   if (!$form->{delivered}) {
1434     foreach my $row (@contents) {
1435       $row->{available_qty} = $form->format_amount_units('amount'      => $row->{qty} * 1,
1436                                                          'part_unit'   => $part_info->{unit},
1437                                                          'conv_units'  => 'convertible_not_smaller',
1438                                                          'max_places'  => 2);
1439
1440       foreach my $sinfo (@{ $stock_info }) {
1441         next if (($row->{bin_id}       != $sinfo->{bin_id}) ||
1442                  ($row->{warehouse_id} != $sinfo->{warehouse_id}) ||
1443                  ($row->{chargenumber} ne $sinfo->{chargenumber}) ||
1444                  ($row->{bestbefore}   ne $sinfo->{bestbefore}));
1445
1446         map { $row->{"stock_$_"} = $sinfo->{$_} } qw(qty unit error delivery_order_items_stock_id);
1447       }
1448     }
1449
1450   } else {
1451     get_basic_bin_wh_info($stock_info);
1452
1453     foreach my $sinfo (@{ $stock_info }) {
1454       map { $sinfo->{"stock_$_"} = $sinfo->{$_} } qw(qty unit);
1455     }
1456   }
1457
1458   $form->header(no_layout => 1);
1459   print $form->parse_html_template('do/stock_out_form', { 'UNITS'      => $units_data,
1460                                                           'WHCONTENTS' => $form->{delivered} ? $stock_info : \@contents,
1461                                                           'PART_INFO'  => $part_info, });
1462
1463   $main::lxdebug->leave_sub();
1464 }
1465
1466 sub set_stock_out {
1467   $main::lxdebug->enter_sub();
1468
1469   my $form     = $main::form;
1470   my %myconfig = %main::myconfig;
1471   my $locale   = $main::locale;
1472
1473   my $stock_info = [];
1474
1475   foreach my $i (1 .. $form->{rowcount}) {
1476     $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
1477
1478     next if ($form->{"qty_$i"} <= 0);
1479
1480     push @{ $stock_info }, {
1481       'warehouse_id' => $form->{"warehouse_id_$i"},
1482       'bin_id'       => $form->{"bin_id_$i"},
1483       'chargenumber' => $form->{"chargenumber_$i"},
1484       'bestbefore'   => $form->{"bestbefore_$i"},
1485       'qty'          => $form->{"qty_$i"},
1486       'unit'         => $form->{"unit_$i"},
1487       'row'          => $i,
1488       'delivery_order_items_stock_id'  => $form->{"delivery_order_items_stock_id_$i"},
1489     };
1490   }
1491
1492   my @errors     = DO->check_stock_availability('requests' => $stock_info,
1493                                                 'parts_id' => $form->{parts_id});
1494
1495   $form->{stock} = SL::YAML::Dump($stock_info);
1496
1497   if (@errors) {
1498     $form->{ERRORS} = [];
1499     map { push @{ $form->{ERRORS} }, $locale->text('Error in row #1: The quantity you entered is bigger than the stocked quantity.', $_->{row}); } @errors;
1500     stock_in_out_form();
1501
1502   } else {
1503     _stock_in_out_set_qty_display($stock_info);
1504
1505     my $do_qty       = AM->sum_with_unit($::form->parse_amount(\%::myconfig, $::form->{do_qty}), $::form->{do_unit});
1506     my $transfer_qty = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
1507
1508     $form->header();
1509     print $form->parse_html_template('do/set_stock_in_out', {
1510       qty_matches => $do_qty == $transfer_qty,
1511     });
1512   }
1513
1514   $main::lxdebug->leave_sub();
1515 }
1516
1517 sub transfer_in {
1518   $main::lxdebug->enter_sub();
1519
1520   my $form     = $main::form;
1521   my %myconfig = %main::myconfig;
1522   my $locale   = $main::locale;
1523
1524   if ($form->{id} && DO->is_marked_as_delivered(id => $form->{id})) {
1525     $form->show_generic_error($locale->text('The parts for this delivery order have already been transferred in.'));
1526   }
1527
1528   save(no_redirect => 1);
1529
1530   my @part_ids = map { $form->{"id_${_}"} } grep { $form->{"id_${_}"} && $form->{"stock_in_${_}"} } (1 .. $form->{rowcount});
1531   my @all_requests;
1532
1533   if (@part_ids) {
1534     my $units         = AM->retrieve_units(\%myconfig, $form);
1535     my %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
1536     my %request_map;
1537
1538     $form->{ERRORS}   = [];
1539
1540     foreach my $i (1 .. $form->{rowcount}) {
1541       next unless ($form->{"id_$i"} && $form->{"stock_in_$i"});
1542
1543       my $row_sum_base_qty = 0;
1544       my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
1545
1546       foreach my $request (@{ DO->unpack_stock_information('packed' => $form->{"stock_in_$i"}) }) {
1547         $request->{parts_id}  = $form->{"id_$i"};
1548         $row_sum_base_qty    += $request->{qty} * $units->{$request->{unit}}->{factor} / $base_unit_factor;
1549
1550         $request->{project_id} = $form->{"project_id_$i"} || $form->{globalproject_id};
1551
1552         push @all_requests, $request;
1553       }
1554
1555       next if (0 == $row_sum_base_qty);
1556
1557       my $do_base_qty = $form->parse_amount(\%myconfig, $form->{"qty_$i"}) * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
1558
1559 #      if ($do_base_qty != $row_sum_base_qty) {
1560 #        push @{ $form->{ERRORS} }, $locale->text('Error in position #1: You must either assign no stock at all or the full quantity of #2 #3.',
1561 #                                                 $i, $form->{"qty_$i"}, $form->{"unit_$i"});
1562 #      }
1563     }
1564
1565     if (@{ $form->{ERRORS} }) {
1566       push @{ $form->{ERRORS} }, $locale->text('The delivery order has not been marked as delivered. The warehouse contents have not changed.');
1567
1568       set_headings('edit');
1569       update();
1570       $main::lxdebug->leave_sub();
1571
1572       $::dispatcher->end_request;
1573     }
1574   }
1575
1576   DO->transfer_in_out('direction' => 'in',
1577                       'requests'  => \@all_requests);
1578
1579   SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
1580
1581   $form->{callback} = 'do.pl?action=edit&type=purchase_delivery_order&id=' . $form->escape($form->{id});
1582   $form->redirect;
1583
1584   $main::lxdebug->leave_sub();
1585 }
1586
1587 sub transfer_out {
1588   $main::lxdebug->enter_sub();
1589
1590   my $form     = $main::form;
1591   my %myconfig = %main::myconfig;
1592   my $locale   = $main::locale;
1593
1594   if ($form->{id} && DO->is_marked_as_delivered(id => $form->{id})) {
1595     $form->show_generic_error($locale->text('The parts for this delivery order have already been transferred out.'));
1596   }
1597
1598   save(no_redirect => 1);
1599
1600   my @part_ids = map { $form->{"id_${_}"} } grep { $form->{"id_${_}"} && $form->{"stock_out_${_}"} } (1 .. $form->{rowcount});
1601   my @all_requests;
1602
1603   if (@part_ids) {
1604     my $units         = AM->retrieve_units(\%myconfig, $form);
1605     my %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
1606     my %request_map;
1607
1608     $form->{ERRORS}   = [];
1609
1610     foreach my $i (1 .. $form->{rowcount}) {
1611       next unless ($form->{"id_$i"} && $form->{"stock_out_$i"});
1612
1613       my $row_sum_base_qty = 0;
1614       my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
1615
1616       foreach my $request (@{ DO->unpack_stock_information('packed' => $form->{"stock_out_$i"}) }) {
1617         $request->{parts_id} = $form->{"id_$i"};
1618         $request->{base_qty} = $request->{qty} * $units->{$request->{unit}}->{factor} / $base_unit_factor;
1619         $request->{project_id} = $form->{"project_id_$i"} ? $form->{"project_id_$i"} : $form->{globalproject_id};
1620
1621         my $map_key          = join '--', ($form->{"id_$i"}, @{$request}{qw(warehouse_id bin_id chargenumber bestbefore)});
1622
1623         $request_map{$map_key}                 ||= $request;
1624         $request_map{$map_key}->{sum_base_qty} ||= 0;
1625         $request_map{$map_key}->{sum_base_qty}  += $request->{base_qty};
1626         $row_sum_base_qty                       += $request->{base_qty};
1627
1628         push @all_requests, $request;
1629       }
1630
1631       next if (0 == $row_sum_base_qty);
1632
1633       my $do_base_qty = $form->{"qty_$i"} * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
1634
1635 #      if ($do_base_qty != $row_sum_base_qty) {
1636 #        push @{ $form->{ERRORS} }, $locale->text('Error in position #1: You must either assign no transfer at all or the full quantity of #2 #3.',
1637 #                                                 $i, $form->{"qty_$i"}, $form->{"unit_$i"});
1638 #      }
1639     }
1640
1641     if (%request_map) {
1642       my @bin_ids      = map { $_->{bin_id} } values %request_map;
1643       my %bin_info_map = WH->get_basic_bin_info('id' => \@bin_ids);
1644       my @contents     = DO->get_item_availability('parts_id' => \@part_ids);
1645
1646       foreach my $inv (@contents) {
1647         my $map_key = join '--', @{$inv}{qw(parts_id warehouse_id bin_id chargenumber bestbefore)};
1648
1649         next unless ($request_map{$map_key});
1650
1651         my $request    = $request_map{$map_key};
1652         $request->{ok} = $request->{sum_base_qty} <= $inv->{qty};
1653       }
1654
1655       foreach my $request (values %request_map) {
1656         next if ($request->{ok});
1657
1658         my $pinfo = $part_info_map{$request->{parts_id}};
1659         my $binfo = $bin_info_map{$request->{bin_id}};
1660
1661         if ($::instance_conf->get_show_bestbefore) {
1662             push @{ $form->{ERRORS} }, $locale->text("There is not enough available of '#1' at warehouse '#2', bin '#3', #4, #5, for the transfer of #6.",
1663                                                      $pinfo->{description},
1664                                                      $binfo->{warehouse_description},
1665                                                      $binfo->{bin_description},
1666                                                      $request->{chargenumber} ? $locale->text('chargenumber #1', $request->{chargenumber}) : $locale->text('no chargenumber'),
1667                                                      $request->{bestbefore} ? $locale->text('bestbefore #1', $request->{bestbefore}) : $locale->text('no bestbefore'),
1668                                                      $form->format_amount_units('amount'      => $request->{sum_base_qty},
1669                                                                                 'part_unit'   => $pinfo->{unit},
1670                                                                                 'conv_units'  => 'convertible_not_smaller'));
1671         } else {
1672             push @{ $form->{ERRORS} }, $locale->text("There is not enough available of '#1' at warehouse '#2', bin '#3', #4, for the transfer of #5.",
1673                                                      $pinfo->{description},
1674                                                      $binfo->{warehouse_description},
1675                                                      $binfo->{bin_description},
1676                                                      $request->{chargenumber} ? $locale->text('chargenumber #1', $request->{chargenumber}) : $locale->text('no chargenumber'),
1677                                                      $form->format_amount_units('amount'      => $request->{sum_base_qty},
1678                                                                                 'part_unit'   => $pinfo->{unit},
1679                                                                                 'conv_units'  => 'convertible_not_smaller'));
1680         }
1681       }
1682     }
1683
1684     if (@{ $form->{ERRORS} }) {
1685       push @{ $form->{ERRORS} }, $locale->text('The delivery order has not been marked as delivered. The warehouse contents have not changed.');
1686
1687       set_headings('edit');
1688       update();
1689       $main::lxdebug->leave_sub();
1690
1691       $::dispatcher->end_request;
1692     }
1693   }
1694   DO->transfer_in_out('direction' => 'out',
1695                       'requests'  => \@all_requests);
1696
1697   SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
1698
1699   $form->{callback} = 'do.pl?action=edit&type=sales_delivery_order&id=' . $form->escape($form->{id});
1700   $form->redirect;
1701
1702   $main::lxdebug->leave_sub();
1703 }
1704
1705 sub mark_closed {
1706   $main::lxdebug->enter_sub();
1707
1708   my $form     = $main::form;
1709
1710   DO->close_orders('ids' => [ $form->{id} ]);
1711
1712   $form->{closed} = 1;
1713
1714   update();
1715
1716   $main::lxdebug->leave_sub();
1717 }
1718
1719 sub display_form {
1720   $::lxdebug->enter_sub;
1721
1722   $::auth->assert('purchase_delivery_order_edit | sales_delivery_order_edit');
1723
1724   relink_accounts();
1725   retrieve_partunits();
1726
1727   my $new_rowcount = $::form->{"rowcount"} * 1 + 1;
1728   $::form->{"project_id_${new_rowcount}"} = $::form->{"globalproject_id"};
1729
1730   $::form->language_payment(\%::myconfig);
1731
1732   Common::webdav_folder($::form);
1733
1734   form_header();
1735   display_row(++$::form->{rowcount});
1736   form_footer();
1737
1738   $::lxdebug->leave_sub;
1739 }
1740
1741 sub yes {
1742   call_sub($main::form->{yes_nextsub});
1743 }
1744
1745 sub no {
1746   call_sub($main::form->{no_nextsub});
1747 }
1748
1749 sub update {
1750   call_sub($main::form->{update_nextsub} || $main::form->{nextsub} || 'update_delivery_order');
1751 }
1752
1753 sub dispatcher {
1754   my $form     = $main::form;
1755   my $locale   = $main::locale;
1756
1757   foreach my $action (qw(update print save transfer_out transfer_out_default sort
1758                          transfer_in transfer_in_default mark_closed save_as_new invoice delete)) {
1759     if ($form->{"action_${action}"}) {
1760       call_sub($action);
1761       return;
1762     }
1763   }
1764
1765   $form->error($locale->text('No action defined.'));
1766 }
1767
1768 sub transfer_out_default {
1769   $main::lxdebug->enter_sub();
1770
1771   my $form     = $main::form;
1772
1773   transfer_in_out_default('direction' => 'out');
1774
1775   $main::lxdebug->leave_sub();
1776 }
1777
1778 sub transfer_in_default {
1779   $main::lxdebug->enter_sub();
1780
1781   my $form     = $main::form;
1782
1783   transfer_in_out_default('direction' => 'in');
1784
1785   $main::lxdebug->leave_sub();
1786 }
1787
1788 # Falls das Standardlagerverfahren aktiv ist, wird
1789 # geprüft, ob alle Standardlagerplätze für die Auslager-
1790 # artikel vorhanden sind UND ob die Warenmenge ausreicht zum
1791 # Auslagern. Falls nicht wird entsprechend eine Fehlermeldung
1792 # generiert. Offen Chargennummer / bestbefore wird nicht berücksichtigt
1793 sub transfer_in_out_default {
1794   $main::lxdebug->enter_sub();
1795
1796   my $form     = $main::form;
1797   my %myconfig = %main::myconfig;
1798   my $locale   = $main::locale;
1799   my %params   = @_;
1800
1801   my (%missing_default_bins, %qty_parts, @all_requests, %part_info_map, $default_warehouse_id, $default_bin_id);
1802
1803   Common::check_params(\%params, qw(direction));
1804
1805   # entsprechende defaults holen, falls standardlagerplatz verwendet werden soll
1806   if ($::instance_conf->get_transfer_default_use_master_default_bin) {
1807     $default_warehouse_id = $::instance_conf->get_warehouse_id;
1808     $default_bin_id       = $::instance_conf->get_bin_id;
1809   }
1810
1811
1812   my @part_ids = map { $form->{"id_${_}"} } (1 .. $form->{rowcount});
1813   if (@part_ids) {
1814     my $units         = AM->retrieve_units(\%myconfig, $form);
1815     %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
1816     foreach my $i (1 .. $form->{rowcount}) {
1817       next unless ($form->{"id_$i"});
1818       my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
1819       my $qty =   $form->parse_amount(\%myconfig, $form->{"qty_$i"}) * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
1820
1821       $form->show_generic_error($locale->text("Cannot transfer negative entries." )) if ($qty < 0);
1822       # if we do not want to transfer services and this part is a service, set qty to zero
1823       # ... and do not create a hash entry in %qty_parts below (will skip check for bins for the transfer == out case)
1824       # ... 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)
1825
1826       $qty = 0 if (!$::instance_conf->get_transfer_default_services && $part_info_map{$form->{"id_$i"}}->{part_type} eq 'service');
1827       $qty_parts{$form->{"id_$i"}} += $qty;
1828       if ($qty == 0) {
1829         delete $qty_parts{$form->{"id_$i"}} unless $qty_parts{$form->{"id_$i"}};
1830         undef $form->{"stock_in_$i"};
1831       }
1832
1833       $part_info_map{$form->{"id_$i"}}{bin_id}       ||= $default_bin_id;
1834       $part_info_map{$form->{"id_$i"}}{warehouse_id} ||= $default_warehouse_id;
1835
1836       push @all_requests, ($qty == 0) ? { } : {
1837                         'chargenumber' => '',  #?? die müsste entsprechend geholt werden
1838                         #'bestbefore' => undef, # TODO wird nicht berücksichtigt
1839                         'bin_id' => $part_info_map{$form->{"id_$i"}}{bin_id},
1840                         'qty' => $qty,
1841                         'parts_id' => $form->{"id_$i"},
1842                         'comment' => $locale->text("Default transfer delivery order"),
1843                         'unit' => $part_info_map{$form->{"id_$i"}}{unit},
1844                         'warehouse_id' => $part_info_map{$form->{"id_$i"}}{warehouse_id},
1845                         'oe_id' => $form->{id},
1846                         'project_id' => $form->{"project_id_$i"} ? $form->{"project_id_$i"} : $form->{globalproject_id}
1847                       };
1848     }
1849
1850     # jetzt wird erst überprüft, ob die Stückzahl entsprechend stimmt.
1851     # check if bin (transfer in and transfer out and qty (transfer out) is correct
1852     foreach my $key (keys %qty_parts) {
1853
1854       $missing_default_bins{$key}{missing_bin} = 1 unless ($part_info_map{$key}{bin_id});
1855       next unless ($part_info_map{$key}{bin_id}); # abbruch
1856
1857       if ($params{direction} eq 'out') {  # wird nur für ausgehende Mengen benötigt
1858         my ($max_qty, $error) = WH->get_max_qty_parts_bin(parts_id => $key, bin_id => $part_info_map{$key}{bin_id});
1859         if ($error == 1) {
1860           # wir können nicht entscheiden, welche charge oder mhd (bestbefore) ausgewählt sein soll
1861           # deshalb rückmeldung nach oben geben, manuell auszulagern
1862           # TODO Bei nur einem Treffer mit Charge oder bestbefore wäre das noch möglich
1863           $missing_default_bins{$key}{chargenumber} = 1;
1864         }
1865         if ($max_qty < $qty_parts{$key}){
1866           $missing_default_bins{$key}{missing_qty} = $max_qty - $qty_parts{$key};
1867         }
1868       }
1869     }
1870   } # if @parts_id
1871
1872   # Abfrage für Fehlerbehandlung (nur bei direction == out)
1873   if (scalar (keys %missing_default_bins)) {
1874     my $fehlertext;
1875     foreach my $fehler (keys %missing_default_bins) {
1876
1877       my $ware = WH->get_part_description(parts_id => $fehler);
1878       if ($missing_default_bins{$fehler}{missing_bin}){
1879         $fehlertext .= "Kein Standardlagerplatz definiert bei $ware <br>";
1880       }
1881       if ($missing_default_bins{$fehler}{missing_qty}) {  # missing_qty
1882         $fehlertext .= "Es fehlen " . $missing_default_bins{$fehler}{missing_qty}*-1 .
1883                        " von $ware auf dem Standard-Lagerplatz " . $part_info_map{$fehler}{bin} .   " zum Auslagern<br>";
1884       }
1885       if ($missing_default_bins{$fehler}{chargenumber}){
1886         $fehlertext .= "Die Ware hat eine Chargennummer oder eine Mindesthaltbarkeit definiert.
1887                         Hier kann man nicht automatisch entscheiden.
1888                         Bitte diesen Lieferschein manuell auslagern.
1889                         Bei: $ware";
1890       }
1891       # auslagern soll immer gehen, auch wenn nicht genügend auf lager ist.
1892       # der lagerplatz ist hier extra konfigurierbar, bspw. Lager-Korrektur mit
1893       # Lagerplatz Lagerplatz-Korrektur
1894       my $default_warehouse_id_ignore_onhand = $::instance_conf->get_warehouse_id_ignore_onhand;
1895       my $default_bin_id_ignore_onhand       = $::instance_conf->get_bin_id_ignore_onhand;
1896       if ($::instance_conf->get_transfer_default_ignore_onhand && $default_bin_id_ignore_onhand) {
1897         # entsprechende defaults holen
1898         # falls chargenumber, bestbefore oder anzahl nicht stimmt, auf automatischen
1899         # lagerplatz wegbuchen!
1900         foreach (@all_requests) {
1901           if ($_->{parts_id} eq $fehler){
1902           $_->{bin_id}        = $default_bin_id_ignore_onhand;
1903           $_->{warehouse_id}  = $default_warehouse_id_ignore_onhand;
1904           }
1905         }
1906       } else {
1907         #$main::lxdebug->message(0, 'Fehlertext: ' . $fehlertext);
1908         $form->show_generic_error($locale->text("Cannot transfer. <br> Reason:<br>#1", $fehlertext ));
1909       }
1910     }
1911   }
1912
1913
1914   # hier der eigentliche fallunterschied für in oder out
1915   my $prefix   = $params{direction} eq 'in' ? 'in' : 'out';
1916
1917   # dieser array_ref ist für DO->save da:
1918   # einmal die all_requests in YAML verwandeln, damit delivery_order_items_stock
1919   # gefüllt werden kann.
1920   # could be dumped to the form in the first loop,
1921   # but maybe bin_id and warehouse_id has changed to the "korrekturlager" with
1922   # allowed negative qty ($::instance_conf->get_warehouse_id_ignore_onhand) ...
1923   my $i = 0;
1924   foreach (@all_requests){
1925     $i++;
1926     next unless scalar(%{ $_ });
1927     $form->{"stock_${prefix}_$i"} = SL::YAML::Dump([$_]);
1928   }
1929
1930   save(no_redirect => 1); # Wir können auslagern, deshalb beleg speichern
1931                           # und in delivery_order_items_stock speichern
1932
1933   # ... and fill back the persistent dois_id for inventory fk
1934   undef (@all_requests);
1935   foreach my $i (1 .. $form->{rowcount}) {
1936     next unless ($form->{"id_$i"} && $form->{"stock_${prefix}_$i"});
1937     push @all_requests, @{ DO->unpack_stock_information('packed' => $form->{"stock_${prefix}_$i"}) };
1938   }
1939   DO->transfer_in_out('direction' => $prefix,
1940                       'requests'  => \@all_requests);
1941
1942   SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
1943
1944   $form->{callback} = 'do.pl?action=edit&type=sales_delivery_order&id=' . $form->escape($form->{id}) if $params{direction} eq 'out';
1945   $form->{callback} = 'do.pl?action=edit&type=purchase_delivery_order&id=' . $form->escape($form->{id}) if $params{direction} eq 'in';
1946   $form->redirect;
1947
1948 }
1949
1950 sub sort {
1951   $main::lxdebug->enter_sub();
1952
1953   check_do_access();
1954
1955   my $form     = $main::form;
1956   my %temp_hash;
1957
1958   save(no_redirect => 1); # has to be done, at least for newly added positions
1959
1960   # hashify partnumbers, positions. key is delivery_order_items_id
1961   for my $i (1 .. ($form->{rowcount}) ) {
1962     $temp_hash{$form->{"delivery_order_items_id_$i"}} = { runningnumber => $form->{"runningnumber_$i"}, partnumber => $form->{"partnumber_$i"} };
1963     if ($form->{id} && $form->{"discount_$i"}) {
1964       # prepare_order assumes a db value if there is a form->id and multiplies *100
1965       # We hope for new controller code (no more format_amount/parse_amount distinction)
1966       $form->{"discount_$i"} /=100;
1967     }
1968   }
1969   # naturally sort partnumbers and get a sorted array of doi_ids
1970   my @sorted_doi_ids =  sort { Sort::Naturally::ncmp($temp_hash{$a}->{"partnumber"}, $temp_hash{$b}->{"partnumber"}) }  keys %temp_hash;
1971
1972
1973   my $new_number = 1;
1974
1975   for (@sorted_doi_ids) {
1976     $form->{"runningnumber_$temp_hash{$_}->{runningnumber}"} = $new_number;
1977     $new_number++;
1978   }
1979   # all parse_amounts changes are in form (i.e. , to .) therefore we need
1980   # another format_amount to change it back, for the next save ;-(
1981   # works great except for row discounts (see above comment)
1982   prepare_order();
1983
1984
1985     $main::lxdebug->leave_sub();
1986     save();
1987 }
1988
1989 __END__
1990
1991 =pod
1992
1993 =encoding utf8
1994
1995 =head1 NAME
1996
1997 do.pl - Script for all calls to delivery order
1998
1999 =head1 FUNCTIONS
2000
2001 =over 2
2002
2003 =item C<sort>
2004
2005 Sorts all position with Natural Sort. Can be activated in form_footer.html like this
2006 C<E<lt>input class="submit" type="submit" name="action_sort" id="sort_button" value="[% 'Sort and Save' | $T8 %]"E<gt>>
2007
2008 =back
2009
2010 =head1 TODO
2011
2012 Sort and Save can be implemented as an optional button if configuration ca be set by client config.
2013 Example coding for database scripts and templates in (git show af2f24b8), check also
2014 autogeneration for rose (scripts/rose_auto_create_model.pl --h)