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