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