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