Bericht Lieferscheine (do.pl) check_all bzw. hier multi_all 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 use YAML;
39
40 use SL::DB::DeliveryOrder;
41 use SL::DO;
42 use SL::IR;
43 use SL::IS;
44 use SL::MoreCommon qw(ary_diff restore_form save_form);
45 use SL::ReportGenerator;
46 use SL::WH;
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           checks    => [ 'kivi.validate_form' ],
251           accesskey => 'enter',
252         ],
253
254       combobox => [
255         action => [
256           t8('Save'),
257           submit   => [ '#form', { action => "save" } ],
258           checks   => [ 'kivi.validate_form' ],
259           disabled => $::form->{delivered} ? t8('This record has already been delivered.') : undef,
260         ],
261         action => [
262           t8('Save as new'),
263           submit   => [ '#form', { action => "save_as_new" } ],
264           checks   => [ 'kivi.validate_form' ],
265           disabled => !$::form->{id},
266         ],
267         action => [
268           t8('Mark as closed'),
269           submit   => [ '#form', { action => "mark_closed" } ],
270           checks   => [ 'kivi.validate_form' ],
271           confirm  => t8('This will remove the delivery order from showing as open even if contents are not delivered. Proceed?'),
272           disabled => !$::form->{id}    ? t8('This record has not been saved yet.')
273                     : $::form->{closed} ? t8('This record has already been closed.')
274                     :                     undef,
275         ],
276       ], # end of combobox "Save"
277
278       action => [
279         t8('Delete'),
280         submit   => [ '#form', { action => "delete" } ],
281         confirm  => t8('Do you really want to delete this object?'),
282         disabled => !$::form->{id}                                                                              ? t8('This record has not been saved yet.')
283                   : $::form->{delivered}                                                                        ? t8('This record has already been delivered.')
284                   : ($::form->{vc} eq 'customer' && !$::instance_conf->get_sales_delivery_order_show_delete)    ? t8('Deleting this type of record has been disabled in the configuration.')
285                   : ($::form->{vc} eq 'vendor'   && !$::instance_conf->get_purchase_delivery_order_show_delete) ? t8('Deleting this type of record has been disabled in the configuration.')
286                   :                                                                                               undef,
287       ],
288
289       combobox => [
290         action => [
291           t8('Transfer out'),
292           submit   => [ '#form', { action => "transfer_out" } ],
293           checks   => [ 'kivi.validate_form', @transfer_qty ],
294           disabled => $::form->{delivered} ? t8('This record has already been delivered.') : undef,
295           only_if  => $is_customer,
296         ],
297         action => [
298           t8('Transfer out via default'),
299           submit   => [ '#form', { action => "transfer_out_default" } ],
300           checks   => [ 'kivi.validate_form' ],
301           disabled => $::form->{delivered} ? t8('This record has already been delivered.') : undef,
302           only_if  => $is_customer && $::instance_conf->get_transfer_default,
303         ],
304         action => [
305           t8('Transfer in'),
306           submit   => [ '#form', { action => "transfer_in" } ],
307           checks   => [ 'kivi.validate_form', @transfer_qty ],
308           disabled => $::form->{delivered} ? t8('This record has already been delivered.') : undef,
309           only_if  => !$is_customer,
310         ],
311         action => [
312           t8('Transfer in via default'),
313           submit   => [ '#form', { action => "transfer_in_default" } ],
314           checks   => [ 'kivi.validate_form' ],
315           disabled => $::form->{delivered} ? t8('This record has already been delivered.') : undef,
316           only_if  => !$is_customer && $::instance_conf->get_transfer_default,
317         ],
318       ], # end of combobox "Transfer out"
319
320
321       'separator',
322
323       action => [
324         t8('Invoice'),
325         submit => [ '#form', { action => "invoice" } ],
326         disabled => !$::form->{id} ? t8('This record has not been saved yet.') : undef,
327       ],
328
329       combobox => [
330         action => [ t8('Export') ],
331         action => [
332           t8('Print'),
333           call   => [ 'kivi.SalesPurchase.show_print_dialog' ],
334           checks => [ 'kivi.validate_form' ],
335         ],
336         action => [
337           t8('E Mail'),
338           call   => [ 'kivi.SalesPurchase.show_email_dialog' ],
339           checks => [ 'kivi.validate_form' ],
340         ],
341       ], # end of combobox "Export"
342
343       combobox =>  [
344         action => [ t8('more') ],
345         action => [
346           t8('History'),
347           call     => [ 'set_history_window', $::form->{id} * 1, 'id' ],
348           disabled => !$::form->{id} ? t8('This record has not been saved yet.') : undef,
349         ],
350         action => [
351           t8('Follow-Up'),
352           call     => [ 'follow_up_window' ],
353           disabled => !$::form->{id} ? t8('This record has not been saved yet.') : undef,
354         ],
355       ], # end if combobox "more"
356     );
357   }
358   $::request->layout->add_javascripts('kivi.Validator.js');
359 }
360
361 sub setup_do_search_action_bar {
362   my %params = @_;
363
364   for my $bar ($::request->layout->get('actionbar')) {
365     $bar->add(
366       action => [
367         t8('Search'),
368         submit    => [ '#form' ],
369         accesskey => 'enter',
370         checks    => [ 'kivi.validate_form' ],
371       ],
372     );
373   }
374   $::request->layout->add_javascripts('kivi.Validator.js');
375 }
376
377 sub setup_do_orders_action_bar {
378   my %params = @_;
379
380   for my $bar ($::request->layout->get('actionbar')) {
381     $bar->add(
382       action => [
383         t8('New invoice'),
384         submit    => [ '#form', { action => 'invoice_multi' } ],
385         checks    => [ [ 'kivi.check_if_entries_selected', '#form tbody input[type=checkbox]' ] ],
386         accesskey => 'enter',
387       ],
388       action => [
389         t8('Print'),
390         call   => [ 'kivi.SalesPurchase.show_print_dialog', 'js:kivi.MassDeliveryOrderPrint.submitMultiOrders' ],
391         checks => [ [ 'kivi.check_if_entries_selected', '#form tbody input[type=checkbox]' ] ],
392       ],
393     );
394   }
395 }
396
397 sub form_header {
398   $main::lxdebug->enter_sub();
399
400   check_do_access();
401
402   my $form     = $main::form;
403   my %myconfig = %main::myconfig;
404
405   my $class       = "SL::DB::" . ($form->{vc} eq 'customer' ? 'Customer' : 'Vendor');
406   $form->{VC_OBJ} = $class->load_cached($form->{ $form->{vc} . '_id' });
407
408   $form->{CONTACT_OBJ}   = $form->{cp_id} ? SL::DB::Contact->load_cached($form->{cp_id}) : undef;
409   my $current_employee   = SL::DB::Manager::Employee->current;
410   $form->{employee_id}   = $form->{old_employee_id} if $form->{old_employee_id};
411   $form->{salesman_id}   = $form->{old_salesman_id} if $form->{old_salesman_id};
412   $form->{employee_id} ||= $current_employee->id;
413   $form->{salesman_id} ||= $current_employee->id;
414
415   my $vc = $form->{vc} eq "customer" ? "customers" : "vendors";
416   $form->get_lists("price_factors"  => "ALL_PRICE_FACTORS",
417                    "business_types" => "ALL_BUSINESS_TYPES",
418     );
419   $form->{ALL_DEPARTMENTS} = SL::DB::Manager::Department->get_all_sorted;
420
421   # Projects
422   my @old_project_ids = uniq grep { $_ } map { $_ * 1 } ($form->{"globalproject_id"}, map { $form->{"project_id_$_"} } 1..$form->{"rowcount"});
423   my @old_ids_cond    = @old_project_ids ? (id => \@old_project_ids) : ();
424   my @customer_cond;
425   if (($vc eq 'customers') && $::instance_conf->get_customer_projects_only_in_sales) {
426     @customer_cond = (
427       or => [
428         customer_id          => $::form->{customer_id},
429         billable_customer_id => $::form->{customer_id},
430       ]);
431   }
432   my @conditions = (
433     or => [
434       and => [ active => 1, @customer_cond ],
435       @old_ids_cond,
436     ]);
437
438   $::form->{ALL_PROJECTS}          = SL::DB::Manager::Project->get_all_sorted(query => \@conditions);
439   $::form->{ALL_EMPLOYEES}         = SL::DB::Manager::Employee->get_all_sorted(query => [ or => [ id => $::form->{employee_id},  deleted => 0 ] ]);
440   $::form->{ALL_SALESMEN}          = SL::DB::Manager::Employee->get_all_sorted(query => [ or => [ id => $::form->{salesman_id},  deleted => 0 ] ]);
441   $::form->{ALL_SHIPTO}            = SL::DB::Manager::Shipto->get_all_sorted(query => [
442     or => [ trans_id  => $::form->{"$::form->{vc}_id"} * 1, and => [ shipto_id => $::form->{shipto_id} * 1, trans_id => undef ] ]
443   ]);
444   $::form->{ALL_CONTACTS}          = SL::DB::Manager::Contact->get_all_sorted(query => [
445     or => [
446       cp_cv_id => $::form->{"$::form->{vc}_id"} * 1,
447       and      => [
448         cp_cv_id => undef,
449         cp_id    => $::form->{cp_id} * 1
450       ]
451     ]
452   ]);
453
454   my $dispatch_to_popup = '';
455   if ($form->{resubmit} && ($form->{format} eq "html")) {
456     $dispatch_to_popup  = "window.open('about:blank','Beleg'); document.do.target = 'Beleg';";
457     $dispatch_to_popup .= "document.do.submit();";
458   } elsif ($form->{resubmit}) {
459     # emulate click for resubmitting actions
460     $dispatch_to_popup  = "document.do.${_}.click(); " for grep { /^action_/ } keys %$form;
461   }
462   $::request->{layout}->add_javascripts_inline("\$(function(){$dispatch_to_popup});");
463
464
465   $form->{follow_up_trans_info} = $form->{donumber} .'('. $form->{VC_OBJ}->name .')' if $form->{VC_OBJ};
466
467   $::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));
468
469   my @custom_hidden;
470   push @custom_hidden, map { "shiptocvar_" . $_->name } @{ SL::DB::Manager::CustomVariableConfig->get_all(where => [ module => 'ShipTo' ]) };
471
472   $::form->{HIDDENS} = [ map { +{ name => $_, value => $::form->{$_} } } (@custom_hidden) ];
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   print $form->parse_html_template('do/form_footer',
505     {transfer_default         => ($::instance_conf->get_transfer_default)});
506
507   $main::lxdebug->leave_sub();
508 }
509
510 sub update_delivery_order {
511   $main::lxdebug->enter_sub();
512
513   check_do_access();
514
515   my $form     = $main::form;
516   my %myconfig = %main::myconfig;
517
518   set_headings($form->{"id"} ? "edit" : "add");
519
520   $form->{insertdate} = SL::DB::DeliveryOrder->new(id => $form->{id})->load->itime_as_date if $form->{id};
521
522   $form->{update} = 1;
523
524   my $payment_id;
525   $payment_id = $form->{payment_id} if $form->{payment_id};
526
527   my $vc = $form->{vc};
528   if (($form->{"previous_${vc}_id"} || $form->{"${vc}_id"}) != $form->{"${vc}_id"}) {
529     $::form->{salesman_id} = SL::DB::Manager::Employee->current->id if exists $::form->{salesman_id};
530
531     IS->get_customer(\%myconfig, $form) if $vc eq 'customer';
532     IR->get_vendor(\%myconfig, $form)   if $vc eq 'vendor';
533   }
534
535   $form->{discount} =  $form->{"$form->{vc}_discount"} if defined $form->{"$form->{vc}_discount"};
536   # Problem: Wenn man ohne Erneuern einen Kunden/Lieferanten
537   # wechselt, wird der entsprechende Kunden/ Lieferantenrabatt
538   # nicht übernommen. Grundproblem: In Commit 82574e78
539   # hab ich aus discount customer_discount und vendor_discount
540   # gemacht und entsprechend an den Oberflächen richtig hin-
541   # geschoben. Die damals bessere Lösung wäre gewesen:
542   # In den Templates nur die hidden für form-discount wieder ein-
543   # setzen dann wäre die Verrenkung jetzt nicht notwendig.
544   # TODO: Ggf. Bugfix 1284, 1575 und 817 wieder zusammenführen
545   # Testfälle: Kunden mit Rabatt 0 -> Rabatt 20 i.O.
546   #            Kunde mit Rabatt 20 -> Rabatt 0  i.O.
547   #            Kunde mit Rabatt 20 -> Rabatt 5,5 i.O.
548   $form->{payment_id} = $payment_id if $form->{payment_id} eq "";
549
550   my $i = $form->{rowcount};
551
552   if (   ($form->{"partnumber_$i"} eq "")
553       && ($form->{"description_$i"} eq "")
554       && ($form->{"partsgroup_$i"}  eq "")) {
555
556     check_form();
557
558   } else {
559
560     my $mode;
561     if ($form->{type} eq 'purchase_delivery_order') {
562       IR->retrieve_item(\%myconfig, $form);
563       $mode = 'IR';
564     } else {
565       IS->retrieve_item(\%myconfig, $form);
566       $mode = 'IS';
567     }
568
569     my $rows = scalar @{ $form->{item_list} };
570
571     if ($rows) {
572       $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
573       if( !$form->{"qty_$i"} ) {
574         $form->{"qty_$i"} = 1;
575       }
576
577       if ($rows > 1) {
578
579         select_item(mode => $mode, pre_entered_qty => $form->{"qty_$i"});
580         $::dispatcher->end_request;
581
582       } else {
583
584         my $sellprice = $form->parse_amount(\%myconfig, $form->{"sellprice_$i"});
585
586         map { $form->{"${_}_$i"} = $form->{item_list}[0]{$_} } keys %{ $form->{item_list}[0] };
587
588         $form->{"marge_price_factor_$i"} = $form->{item_list}->[0]->{price_factor};
589
590         if ($sellprice) {
591           $form->{"sellprice_$i"} = $sellprice;
592         } else {
593           my $record        = _make_record();
594           my $price_source  = SL::PriceSource->new(record_item => $record->items->[$i-1], record => $record);
595           my $best_price    = $price_source->best_price;
596           my $best_discount = $price_source->best_discount;
597
598           if ($best_price) {
599             $::form->{"sellprice_$i"}           = $best_price->price;
600             $::form->{"active_price_source_$i"} = $best_price->source;
601           }
602           if ($best_discount) {
603             $::form->{"discount_$i"}               = $best_discount->discount;
604             $::form->{"active_discount_source_$i"} = $best_discount->source;
605           }
606         }
607
608         $form->{"sellprice_$i"}          = $form->format_amount(\%myconfig, $form->{"sellprice_$i"});
609         $form->{"lastcost_$i"}           = $form->format_amount(\%myconfig, $form->{"lastcost_$i"});
610         $form->{"qty_$i"}                = $form->format_amount(\%myconfig, $form->{"qty_$i"});
611         $form->{"discount_$i"}           = $form->format_amount(\%myconfig, $form->{"discount_$i"} * 100.0);
612       }
613
614       display_form();
615
616     } else {
617
618       # ok, so this is a new part
619       # ask if it is a part or service item
620
621       if (   $form->{"partsgroup_$i"}
622           && ($form->{"partsnumber_$i"} eq "")
623           && ($form->{"description_$i"} eq "")) {
624         $form->{rowcount}--;
625         $form->{"discount_$i"} = "";
626         $form->{"not_discountable_$i"} = "";
627         display_form();
628
629       } else {
630         $form->{"id_$i"}   = 0;
631         new_item();
632       }
633     }
634   }
635
636   $main::lxdebug->leave_sub();
637 }
638
639 sub search {
640   $main::lxdebug->enter_sub();
641
642   check_do_access();
643
644   my $form     = $main::form;
645   my %myconfig = %main::myconfig;
646   my $locale   = $main::locale;
647
648   $form->{vc} = $form->{type} eq 'purchase_delivery_order' ? 'vendor' : 'customer';
649
650   $form->get_lists("projects"       => { "key" => "ALL_PROJECTS",
651                                          "all" => 1 },
652                    "business_types" => "ALL_BUSINESS_TYPES");
653   $form->{ALL_EMPLOYEES} = SL::DB::Manager::Employee->get_all_sorted(query => [ deleted => 0 ]);
654   $form->{ALL_DEPARTMENTS} = SL::DB::Manager::Department->get_all_sorted;
655   $form->{title}             = $locale->text('Delivery Orders');
656
657   setup_do_search_action_bar();
658
659   $form->header();
660
661   print $form->parse_html_template('do/search');
662
663   $main::lxdebug->leave_sub();
664 }
665
666 sub orders {
667   $main::lxdebug->enter_sub();
668
669   check_do_access();
670
671   my $form     = $main::form;
672   my %myconfig = %main::myconfig;
673   my $locale   = $main::locale;
674   my $cgi      = $::request->{cgi};
675
676   $::request->{layout}->use_javascript(map { "${_}.js" } qw(kivi.MassDeliveryOrderPrint kivi.SalesPurchase));
677   ($form->{ $form->{vc} }, $form->{"$form->{vc}_id"}) = split(/--/, $form->{ $form->{vc} });
678
679   report_generator_set_default_sort('transdate', 1);
680
681   DO->transactions();
682
683   $form->{rowcount} = scalar @{ $form->{DO} };
684
685   my @columns = qw(
686     ids                     transdate               reqdate
687     id                      donumber
688     ordnumber               customernumber          cusordnumber
689     name                    employee  salesman
690     shipvia                 globalprojectnumber
691     transaction_description department
692     open                    delivered
693     insertdate
694   );
695
696   $form->{l_open}      = $form->{l_closed} = "Y" if ($form->{open}      && $form->{closed});
697   $form->{l_delivered} = "Y"                     if ($form->{delivered} && $form->{notdelivered});
698
699   $form->{title}       = $locale->text('Delivery Orders');
700
701   my $attachment_basename = $form->{vc} eq 'vendor' ? $locale->text('purchase_delivery_order_list') : $locale->text('sales_delivery_order_list');
702
703   my $report = SL::ReportGenerator->new(\%myconfig, $form);
704
705   my @hidden_variables = map { "l_${_}" } @columns;
706   push @hidden_variables, $form->{vc}, qw(l_closed l_notdelivered open closed delivered notdelivered donumber ordnumber serialnumber cusordnumber
707                                           transaction_description transdatefrom transdateto reqdatefrom reqdateto
708                                           type vc employee_id salesman_id project_id parts_partnumber parts_description
709                                           insertdatefrom insertdateto business_id);
710
711   my $href = build_std_url('action=orders', grep { $form->{$_} } @hidden_variables);
712
713   my %column_defs = (
714     'ids'                     => { raw_header_data => SL::Presenter::Tag::checkbox_tag("", id => "multi_all", checkall => "[data-checkall=1]"), align => 'center' },
715     'transdate'               => { 'text' => $locale->text('Delivery Order Date'), },
716     'reqdate'                 => { 'text' => $locale->text('Reqdate'), },
717     'id'                      => { 'text' => $locale->text('ID'), },
718     'donumber'                => { 'text' => $locale->text('Delivery Order'), },
719     'ordnumber'               => { 'text' => $locale->text('Order'), },
720     'customernumber'          => { 'text' => $locale->text('Customer Number'), },
721     'cusordnumber'            => { 'text' => $locale->text('Customer Order Number'), },
722     'name'                    => { 'text' => $form->{vc} eq 'customer' ? $locale->text('Customer') : $locale->text('Vendor'), },
723     'employee'                => { 'text' => $locale->text('Employee'), },
724     'salesman'                => { 'text' => $locale->text('Salesman'), },
725     'shipvia'                 => { 'text' => $locale->text('Ship via'), },
726     'globalprojectnumber'     => { 'text' => $locale->text('Project Number'), },
727     'transaction_description' => { 'text' => $locale->text('Transaction description'), },
728     'open'                    => { 'text' => $locale->text('Open'), },
729     'delivered'               => { 'text' => $locale->text('Delivered'), },
730     'department'              => { 'text' => $locale->text('Department'), },
731     'insertdate'              => { 'text' => $locale->text('Insert Date'), },
732   );
733
734   foreach my $name (qw(id transdate reqdate donumber ordnumber name employee salesman shipvia transaction_description department insertdate)) {
735     my $sortdir                 = $form->{sort} eq $name ? 1 - $form->{sortdir} : $form->{sortdir};
736     $column_defs{$name}->{link} = $href . "&sort=$name&sortdir=$sortdir";
737   }
738
739   $form->{"l_type"} = "Y";
740   map { $column_defs{$_}->{visible} = $form->{"l_${_}"} ? 1 : 0 } @columns;
741
742   $column_defs{ids}->{visible} = 'HTML';
743
744   $report->set_columns(%column_defs);
745   $report->set_column_order(@columns);
746
747   $report->set_export_options('orders', @hidden_variables, qw(sort sortdir));
748
749   $report->set_sort_indicator($form->{sort}, $form->{sortdir});
750
751   my @options;
752   if ($form->{customer}) {
753     push @options, $locale->text('Customer') . " : $form->{customer}";
754   }
755   if ($form->{vendor}) {
756     push @options, $locale->text('Vendor') . " : $form->{vendor}";
757   }
758   if ($form->{cp_name}) {
759     push @options, $locale->text('Contact Person') . " : $form->{cp_name}";
760   }
761   if ($form->{department_id}) {
762     push @options, $locale->text('Department') . " : " . SL::DB::Department->new(id => $form->{department_id})->load->description;
763   }
764   if ($form->{donumber}) {
765     push @options, $locale->text('Delivery Order Number') . " : $form->{donumber}";
766   }
767   if ($form->{ordnumber}) {
768     push @options, $locale->text('Order Number') . " : $form->{ordnumber}";
769   }
770   push @options, $locale->text('Serial Number') . " : $form->{serialnumber}" if $form->{serialnumber};
771   if ($form->{business_id}) {
772     my $vc_type_label = $form->{vc} eq 'customer' ? $locale->text('Customer type') : $locale->text('Vendor type');
773     push @options, $vc_type_label . " : " . SL::DB::Business->new(id => $form->{business_id})->load->description;
774   }
775   if ($form->{transaction_description}) {
776     push @options, $locale->text('Transaction description') . " : $form->{transaction_description}";
777   }
778   if ($form->{parts_description}) {
779     push @options, $locale->text('Part Description') . " : $form->{parts_description}";
780   }
781   if ($form->{parts_partnumber}) {
782     push @options, $locale->text('Part Number') . " : $form->{parts_partnumber}";
783   }
784   if ( $form->{transdatefrom} or $form->{transdateto} ) {
785     push @options, $locale->text('Delivery Order Date');
786     push @options, $locale->text('From') . " " . $locale->date(\%myconfig, $form->{transdatefrom}, 1)     if $form->{transdatefrom};
787     push @options, $locale->text('Bis')  . " " . $locale->date(\%myconfig, $form->{transdateto},   1)     if $form->{transdateto};
788   };
789   if ( $form->{reqdatefrom} or $form->{reqdateto} ) {
790     push @options, $locale->text('Reqdate');
791     push @options, $locale->text('From') . " " . $locale->date(\%myconfig, $form->{reqdatefrom}, 1)       if $form->{reqdatefrom};
792     push @options, $locale->text('Bis')  . " " . $locale->date(\%myconfig, $form->{reqdateto},   1)       if $form->{reqdateto};
793   };
794   if ( $form->{insertdatefrom} or $form->{insertdateto} ) {
795     push @options, $locale->text('Insert Date');
796     push @options, $locale->text('From') . " " . $locale->date(\%myconfig, $form->{insertdatefrom}, 1)    if $form->{insertdatefrom};
797     push @options, $locale->text('Bis')  . " " . $locale->date(\%myconfig, $form->{insertdateto},   1)    if $form->{insertdateto};
798   };
799   if ($form->{open}) {
800     push @options, $locale->text('Open');
801   }
802   if ($form->{closed}) {
803     push @options, $locale->text('Closed');
804   }
805   if ($form->{delivered}) {
806     push @options, $locale->text('Delivered');
807   }
808   if ($form->{notdelivered}) {
809     push @options, $locale->text('Not delivered');
810   }
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)
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} = 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} = 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"} = 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)