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