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