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