Lieferscheine: Formatierung der Mengen in verschiedenen Einheiten entfernt.
[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 $amount_unit = $all_units->{$form->{"partunit_$i"}}->{base_unit};
1306   my $content     = $form->format_amount(\%::myconfig, AM->convert_unit($amount_unit, $form->{"unit_$i"}) * $sum * 1) . ' ' . $form->{"unit_$i"};
1307
1308   $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="?">|;
1309
1310   $main::lxdebug->leave_sub();
1311
1312   return $content;
1313 }
1314
1315 sub get_basic_bin_wh_info {
1316   $main::lxdebug->enter_sub();
1317
1318   my $stock_info = shift;
1319
1320   my $form     = $main::form;
1321
1322   foreach my $sinfo (@{ $stock_info }) {
1323     next unless ($sinfo->{bin_id});
1324
1325     my $bin_info = WH->get_basic_bin_info('id' => $sinfo->{bin_id});
1326     map { $sinfo->{"${_}_description"} = $sinfo->{"${_}description"} = $bin_info->{"${_}_description"} } qw(bin warehouse);
1327   }
1328
1329   $main::lxdebug->leave_sub();
1330 }
1331
1332 sub stock_in_out_form {
1333   $main::lxdebug->enter_sub();
1334
1335   my $form     = $main::form;
1336
1337   if ($form->{in_out} eq 'out') {
1338     stock_out_form();
1339   } else {
1340     stock_in_form();
1341   }
1342
1343   $main::lxdebug->leave_sub();
1344 }
1345
1346 sub redo_stock_info {
1347   $main::lxdebug->enter_sub();
1348
1349   my %params    = @_;
1350
1351   my $form     = $main::form;
1352
1353   my @non_empty = grep { $_->{qty} } @{ $params{stock_info} };
1354
1355   if ($params{add_empty_row}) {
1356     push @non_empty, {
1357       'warehouse_id' => scalar(@non_empty) ? $non_empty[-1]->{warehouse_id} : undef,
1358       'bin_id'       => scalar(@non_empty) ? $non_empty[-1]->{bin_id}       : undef,
1359     };
1360   }
1361
1362   @{ $params{stock_info} } = @non_empty;
1363
1364   $main::lxdebug->leave_sub();
1365 }
1366
1367 sub update_stock_in {
1368   $main::lxdebug->enter_sub();
1369
1370   my $form     = $main::form;
1371   my %myconfig = %main::myconfig;
1372
1373   my $stock_info = [];
1374
1375   foreach my $i (1..$form->{rowcount}) {
1376     $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
1377     push @{ $stock_info }, { map { $_ => $form->{"${_}_${i}"} } qw(warehouse_id bin_id chargenumber
1378                                                                    bestbefore qty unit delivery_order_items_stock_id) };
1379   }
1380
1381   display_stock_in_form($stock_info);
1382
1383   $main::lxdebug->leave_sub();
1384 }
1385
1386 sub stock_in_form {
1387   $main::lxdebug->enter_sub();
1388
1389   my $form     = $main::form;
1390
1391   my $stock_info = DO->unpack_stock_information('packed' => $form->{stock});
1392
1393   display_stock_in_form($stock_info);
1394
1395   $main::lxdebug->leave_sub();
1396 }
1397
1398 sub display_stock_in_form {
1399   $main::lxdebug->enter_sub();
1400
1401   my $stock_info = shift;
1402
1403   my $form     = $main::form;
1404   my %myconfig = %main::myconfig;
1405   my $locale   = $main::locale;
1406
1407   $form->{title} = $locale->text('Stock');
1408
1409   my $part_info  = IC->get_basic_part_info('id' => $form->{parts_id});
1410
1411   # Standardlagerplatz für Standard-Auslagern verwenden, falls keiner für die Ware explizit definiert wurde
1412   if ($::instance_conf->get_transfer_default_use_master_default_bin) {
1413     $part_info->{warehouse_id} ||= $::instance_conf->get_warehouse_id;
1414     $part_info->{bin_id}       ||= $::instance_conf->get_bin_id;
1415   }
1416
1417   my $units      = AM->retrieve_units(\%myconfig, $form);
1418   # der zweite Parameter von unit_select_data gibt den default-Namen (selected) vor
1419   my $units_data = AM->unit_select_data($units, $form->{do_unit}, undef, $part_info->{unit});
1420
1421   $form->get_lists('warehouses' => { 'key'    => 'WAREHOUSES',
1422                                      'bins'   => 'BINS' });
1423
1424   redo_stock_info('stock_info' => $stock_info, 'add_empty_row' => !$form->{delivered});
1425
1426   get_basic_bin_wh_info($stock_info);
1427
1428   $form->header(no_layout => 1);
1429   print $form->parse_html_template('do/stock_in_form', { 'UNITS'      => $units_data,
1430                                                          'STOCK_INFO' => $stock_info,
1431                                                          'PART_INFO'  => $part_info, });
1432
1433   $main::lxdebug->leave_sub();
1434 }
1435
1436 sub _stock_in_out_set_qty_display {
1437   my $stock_info       = shift;
1438   my $form             = $::form;
1439   my $all_units        = AM->retrieve_all_units();
1440   my $sum              = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
1441   my $amount_unit      = $all_units->{$form->{"partunit"}}->{base_unit};
1442   $form->{qty_display} = $form->format_amount(\%::myconfig, AM->convert_unit($amount_unit, $form->{"do_unit"}) * $sum * 1) . ' ' . $form->{"do_unit"};
1443 }
1444
1445 sub set_stock_in {
1446   $main::lxdebug->enter_sub();
1447
1448   my $form     = $main::form;
1449   my %myconfig = %main::myconfig;
1450
1451   my $stock_info = [];
1452
1453   foreach my $i (1..$form->{rowcount}) {
1454     $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
1455
1456     next if ($form->{"qty_$i"} <= 0);
1457
1458     push @{ $stock_info }, { map { $_ => $form->{"${_}_${i}"} } qw(delivery_order_items_stock_id warehouse_id bin_id chargenumber bestbefore qty unit) };
1459   }
1460
1461   $form->{stock} = SL::YAML::Dump($stock_info);
1462
1463   _stock_in_out_set_qty_display($stock_info);
1464
1465   my $do_qty       = AM->sum_with_unit($::form->parse_amount(\%::myconfig, $::form->{do_qty}), $::form->{do_unit});
1466   my $transfer_qty = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
1467
1468   $form->header();
1469   print $form->parse_html_template('do/set_stock_in_out', {
1470     qty_matches => $do_qty == $transfer_qty,
1471   });
1472
1473   $main::lxdebug->leave_sub();
1474 }
1475
1476 sub stock_out_form {
1477   $main::lxdebug->enter_sub();
1478
1479   my $form     = $main::form;
1480   my %myconfig = %main::myconfig;
1481   my $locale   = $main::locale;
1482
1483   $form->{title} = $locale->text('Release From Stock');
1484
1485   my $part_info  = IC->get_basic_part_info('id' => $form->{parts_id});
1486
1487   my $units      = AM->retrieve_units(\%myconfig, $form);
1488   my $units_data = AM->unit_select_data($units, undef, undef, $part_info->{unit});
1489
1490   my @contents   = DO->get_item_availability('parts_id' => $form->{parts_id});
1491
1492   my $stock_info = DO->unpack_stock_information('packed' => $form->{stock});
1493
1494   if (!$form->{delivered}) {
1495     foreach my $row (@contents) {
1496       $row->{available_qty} = $form->format_amount(\%::myconfig, $row->{qty} * 1) . ' ' . $part_info->{unit};
1497
1498       foreach my $sinfo (@{ $stock_info }) {
1499         next if (($row->{bin_id}       != $sinfo->{bin_id}) ||
1500                  ($row->{warehouse_id} != $sinfo->{warehouse_id}) ||
1501                  ($row->{chargenumber} ne $sinfo->{chargenumber}) ||
1502                  ($row->{bestbefore}   ne $sinfo->{bestbefore}));
1503
1504         map { $row->{"stock_$_"} = $sinfo->{$_} } qw(qty unit error delivery_order_items_stock_id);
1505       }
1506     }
1507
1508   } else {
1509     get_basic_bin_wh_info($stock_info);
1510
1511     foreach my $sinfo (@{ $stock_info }) {
1512       map { $sinfo->{"stock_$_"} = $sinfo->{$_} } qw(qty unit);
1513     }
1514   }
1515
1516   $form->header(no_layout => 1);
1517   print $form->parse_html_template('do/stock_out_form', { 'UNITS'      => $units_data,
1518                                                           'WHCONTENTS' => $form->{delivered} ? $stock_info : \@contents,
1519                                                           'PART_INFO'  => $part_info, });
1520
1521   $main::lxdebug->leave_sub();
1522 }
1523
1524 sub set_stock_out {
1525   $main::lxdebug->enter_sub();
1526
1527   my $form     = $main::form;
1528   my %myconfig = %main::myconfig;
1529   my $locale   = $main::locale;
1530
1531   my $stock_info = [];
1532
1533   foreach my $i (1 .. $form->{rowcount}) {
1534     $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
1535
1536     next if ($form->{"qty_$i"} <= 0);
1537
1538     push @{ $stock_info }, {
1539       'warehouse_id' => $form->{"warehouse_id_$i"},
1540       'bin_id'       => $form->{"bin_id_$i"},
1541       'chargenumber' => $form->{"chargenumber_$i"},
1542       'bestbefore'   => $form->{"bestbefore_$i"},
1543       'qty'          => $form->{"qty_$i"},
1544       'unit'         => $form->{"unit_$i"},
1545       'row'          => $i,
1546       'delivery_order_items_stock_id'  => $form->{"delivery_order_items_stock_id_$i"},
1547     };
1548   }
1549
1550   my @errors     = DO->check_stock_availability('requests' => $stock_info,
1551                                                 'parts_id' => $form->{parts_id});
1552
1553   $form->{stock} = SL::YAML::Dump($stock_info);
1554
1555   if (@errors) {
1556     $form->{ERRORS} = [];
1557     map { push @{ $form->{ERRORS} }, $locale->text('Error in row #1: The quantity you entered is bigger than the stocked quantity.', $_->{row}); } @errors;
1558     stock_in_out_form();
1559
1560   } else {
1561     _stock_in_out_set_qty_display($stock_info);
1562
1563     my $do_qty       = AM->sum_with_unit($::form->parse_amount(\%::myconfig, $::form->{do_qty}), $::form->{do_unit});
1564     my $transfer_qty = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
1565
1566     $form->header();
1567     print $form->parse_html_template('do/set_stock_in_out', {
1568       qty_matches => $do_qty == $transfer_qty,
1569     });
1570   }
1571
1572   $main::lxdebug->leave_sub();
1573 }
1574
1575 sub transfer_in {
1576   $main::lxdebug->enter_sub();
1577
1578   my $form     = $main::form;
1579   my %myconfig = %main::myconfig;
1580   my $locale   = $main::locale;
1581
1582   if ($form->{id} && DO->is_marked_as_delivered(id => $form->{id})) {
1583     $form->show_generic_error($locale->text('The parts for this delivery order have already been transferred in.'));
1584   }
1585
1586   save(no_redirect => 1);
1587
1588   my @part_ids = map { $form->{"id_${_}"} } grep { $form->{"id_${_}"} && $form->{"stock_in_${_}"} } (1 .. $form->{rowcount});
1589   my @all_requests;
1590
1591   if (@part_ids) {
1592     my $units         = AM->retrieve_units(\%myconfig, $form);
1593     my %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
1594     my %request_map;
1595
1596     $form->{ERRORS}   = [];
1597
1598     foreach my $i (1 .. $form->{rowcount}) {
1599       next unless ($form->{"id_$i"} && $form->{"stock_in_$i"});
1600
1601       my $row_sum_base_qty = 0;
1602       my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
1603
1604       foreach my $request (@{ DO->unpack_stock_information('packed' => $form->{"stock_in_$i"}) }) {
1605         $request->{parts_id}  = $form->{"id_$i"};
1606         $row_sum_base_qty    += $request->{qty} * $units->{$request->{unit}}->{factor} / $base_unit_factor;
1607
1608         $request->{project_id} = $form->{"project_id_$i"} || $form->{globalproject_id};
1609
1610         push @all_requests, $request;
1611       }
1612
1613       next if (0 == $row_sum_base_qty);
1614
1615       my $do_base_qty = $form->parse_amount(\%myconfig, $form->{"qty_$i"}) * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
1616
1617 #      if ($do_base_qty != $row_sum_base_qty) {
1618 #        push @{ $form->{ERRORS} }, $locale->text('Error in position #1: You must either assign no stock at all or the full quantity of #2 #3.',
1619 #                                                 $i, $form->{"qty_$i"}, $form->{"unit_$i"});
1620 #      }
1621     }
1622
1623     if (@{ $form->{ERRORS} }) {
1624       push @{ $form->{ERRORS} }, $locale->text('The delivery order has not been marked as delivered. The warehouse contents have not changed.');
1625
1626       set_headings('edit');
1627       update();
1628       $main::lxdebug->leave_sub();
1629
1630       $::dispatcher->end_request;
1631     }
1632   }
1633
1634   DO->transfer_in_out('direction' => 'in',
1635                       'requests'  => \@all_requests);
1636
1637   SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
1638
1639   flash_later('info', $locale->text("Transfer successful"));
1640   $form->{callback} = 'do.pl?action=edit&type=purchase_delivery_order&id=' . $form->escape($form->{id});
1641   $form->redirect;
1642
1643   $main::lxdebug->leave_sub();
1644 }
1645
1646 sub transfer_out {
1647   $main::lxdebug->enter_sub();
1648
1649   my $form     = $main::form;
1650   my %myconfig = %main::myconfig;
1651   my $locale   = $main::locale;
1652
1653   if ($form->{id} && DO->is_marked_as_delivered(id => $form->{id})) {
1654     $form->show_generic_error($locale->text('The parts for this delivery order have already been transferred out.'));
1655   }
1656
1657   save(no_redirect => 1);
1658
1659   my @part_ids = map { $form->{"id_${_}"} } grep { $form->{"id_${_}"} && $form->{"stock_out_${_}"} } (1 .. $form->{rowcount});
1660   my @all_requests;
1661
1662   if (@part_ids) {
1663     my $units         = AM->retrieve_units(\%myconfig, $form);
1664     my %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
1665     my %request_map;
1666
1667     $form->{ERRORS}   = [];
1668
1669     foreach my $i (1 .. $form->{rowcount}) {
1670       next unless ($form->{"id_$i"} && $form->{"stock_out_$i"});
1671
1672       my $row_sum_base_qty = 0;
1673       my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
1674
1675       foreach my $request (@{ DO->unpack_stock_information('packed' => $form->{"stock_out_$i"}) }) {
1676         $request->{parts_id} = $form->{"id_$i"};
1677         $request->{base_qty} = $request->{qty} * $units->{$request->{unit}}->{factor} / $base_unit_factor;
1678         $request->{project_id} = $form->{"project_id_$i"} ? $form->{"project_id_$i"} : $form->{globalproject_id};
1679
1680         my $map_key          = join '--', ($form->{"id_$i"}, @{$request}{qw(warehouse_id bin_id chargenumber bestbefore)});
1681
1682         $request_map{$map_key}                 ||= $request;
1683         $request_map{$map_key}->{sum_base_qty} ||= 0;
1684         $request_map{$map_key}->{sum_base_qty}  += $request->{base_qty};
1685         $row_sum_base_qty                       += $request->{base_qty};
1686
1687         push @all_requests, $request;
1688       }
1689
1690       next if (0 == $row_sum_base_qty);
1691
1692       my $do_base_qty = $form->{"qty_$i"} * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
1693
1694 #      if ($do_base_qty != $row_sum_base_qty) {
1695 #        push @{ $form->{ERRORS} }, $locale->text('Error in position #1: You must either assign no transfer at all or the full quantity of #2 #3.',
1696 #                                                 $i, $form->{"qty_$i"}, $form->{"unit_$i"});
1697 #      }
1698     }
1699
1700     if (%request_map) {
1701       my @bin_ids      = map { $_->{bin_id} } values %request_map;
1702       my %bin_info_map = WH->get_basic_bin_info('id' => \@bin_ids);
1703       my @contents     = DO->get_item_availability('parts_id' => \@part_ids);
1704
1705       foreach my $inv (@contents) {
1706         my $map_key = join '--', @{$inv}{qw(parts_id warehouse_id bin_id chargenumber bestbefore)};
1707
1708         next unless ($request_map{$map_key});
1709
1710         my $request    = $request_map{$map_key};
1711         $request->{ok} = $request->{sum_base_qty} <= $inv->{qty};
1712       }
1713
1714       foreach my $request (values %request_map) {
1715         next if ($request->{ok});
1716
1717         my $pinfo = $part_info_map{$request->{parts_id}};
1718         my $binfo = $bin_info_map{$request->{bin_id}};
1719
1720         if ($::instance_conf->get_show_bestbefore) {
1721             push @{ $form->{ERRORS} }, $locale->text("There is not enough available of '#1' at warehouse '#2', bin '#3', #4, #5, for the transfer of #6.",
1722                                                      $pinfo->{description},
1723                                                      $binfo->{warehouse_description},
1724                                                      $binfo->{bin_description},
1725                                                      $request->{chargenumber} ? $locale->text('chargenumber #1', $request->{chargenumber}) : $locale->text('no chargenumber'),
1726                                                      $request->{bestbefore} ? $locale->text('bestbefore #1', $request->{bestbefore}) : $locale->text('no bestbefore'),
1727                                                      $form->format_amount(\%::myconfig, $request->{sum_base_qty}) . ' ' . $pinfo->{unit});
1728         } else {
1729             push @{ $form->{ERRORS} }, $locale->text("There is not enough available of '#1' at warehouse '#2', bin '#3', #4, for the transfer of #5.",
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                                                      $form->format_amount(\%::myconfig, $request->{sum_base_qty}) . ' ' . $pinfo->{unit});
1735         }
1736       }
1737     }
1738
1739     if (@{ $form->{ERRORS} }) {
1740       push @{ $form->{ERRORS} }, $locale->text('The delivery order has not been marked as delivered. The warehouse contents have not changed.');
1741
1742       set_headings('edit');
1743       update();
1744       $main::lxdebug->leave_sub();
1745
1746       $::dispatcher->end_request;
1747     }
1748   }
1749   DO->transfer_in_out('direction' => 'out',
1750                       'requests'  => \@all_requests);
1751
1752   SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
1753
1754   flash_later('info', $locale->text("Transfer successful"));
1755   $form->{callback} = 'do.pl?action=edit&type=sales_delivery_order&id=' . $form->escape($form->{id});
1756   $form->redirect;
1757
1758   $main::lxdebug->leave_sub();
1759 }
1760
1761 sub mark_closed {
1762   $main::lxdebug->enter_sub();
1763
1764   my $form     = $main::form;
1765
1766   DO->close_orders('ids' => [ $form->{id} ]);
1767
1768   $form->{closed} = 1;
1769
1770   update();
1771
1772   $main::lxdebug->leave_sub();
1773 }
1774
1775 sub display_form {
1776   $::lxdebug->enter_sub;
1777
1778   $::auth->assert('purchase_delivery_order_edit | sales_delivery_order_edit');
1779
1780   relink_accounts();
1781   retrieve_partunits();
1782
1783   my $new_rowcount = $::form->{"rowcount"} * 1 + 1;
1784   $::form->{"project_id_${new_rowcount}"} = $::form->{"globalproject_id"};
1785
1786   $::form->language_payment(\%::myconfig);
1787
1788   Common::webdav_folder($::form);
1789
1790   form_header();
1791   display_row(++$::form->{rowcount});
1792   form_footer();
1793
1794   $::lxdebug->leave_sub;
1795 }
1796
1797 sub yes {
1798   call_sub($main::form->{yes_nextsub});
1799 }
1800
1801 sub no {
1802   call_sub($main::form->{no_nextsub});
1803 }
1804
1805 sub update {
1806   call_sub($main::form->{update_nextsub} || $main::form->{nextsub} || 'update_delivery_order');
1807 }
1808
1809 sub dispatcher {
1810   my $form     = $main::form;
1811   my $locale   = $main::locale;
1812
1813   foreach my $action (qw(update print save transfer_out transfer_out_default sort
1814                          transfer_in transfer_in_default mark_closed save_as_new invoice delete)) {
1815     if ($form->{"action_${action}"}) {
1816       call_sub($action);
1817       return;
1818     }
1819   }
1820
1821   $form->error($locale->text('No action defined.'));
1822 }
1823
1824 sub transfer_out_default {
1825   $main::lxdebug->enter_sub();
1826
1827   my $form     = $main::form;
1828
1829   transfer_in_out_default('direction' => 'out');
1830
1831   $main::lxdebug->leave_sub();
1832 }
1833
1834 sub transfer_in_default {
1835   $main::lxdebug->enter_sub();
1836
1837   my $form     = $main::form;
1838
1839   transfer_in_out_default('direction' => 'in');
1840
1841   $main::lxdebug->leave_sub();
1842 }
1843
1844 # Falls das Standardlagerverfahren aktiv ist, wird
1845 # geprüft, ob alle Standardlagerplätze für die Auslager-
1846 # artikel vorhanden sind UND ob die Warenmenge ausreicht zum
1847 # Auslagern. Falls nicht wird entsprechend eine Fehlermeldung
1848 # generiert. Offen Chargennummer / bestbefore wird nicht berücksichtigt
1849 sub transfer_in_out_default {
1850   $main::lxdebug->enter_sub();
1851
1852   my $form     = $main::form;
1853   my %myconfig = %main::myconfig;
1854   my $locale   = $main::locale;
1855   my %params   = @_;
1856
1857   my (%missing_default_bins, %qty_parts, @all_requests, %part_info_map, $default_warehouse_id, $default_bin_id);
1858
1859   Common::check_params(\%params, qw(direction));
1860
1861   # entsprechende defaults holen, falls standardlagerplatz verwendet werden soll
1862   if ($::instance_conf->get_transfer_default_use_master_default_bin) {
1863     $default_warehouse_id = $::instance_conf->get_warehouse_id;
1864     $default_bin_id       = $::instance_conf->get_bin_id;
1865   }
1866
1867
1868   my @part_ids = map { $form->{"id_${_}"} } (1 .. $form->{rowcount});
1869   if (@part_ids) {
1870     my $units         = AM->retrieve_units(\%myconfig, $form);
1871     %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
1872     foreach my $i (1 .. $form->{rowcount}) {
1873       next unless ($form->{"id_$i"});
1874       my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
1875       my $qty =   $form->parse_amount(\%myconfig, $form->{"qty_$i"}) * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
1876
1877       $form->show_generic_error($locale->text("Cannot transfer negative entries." )) if ($qty < 0);
1878       # if we do not want to transfer services and this part is a service, set qty to zero
1879       # ... and do not create a hash entry in %qty_parts below (will skip check for bins for the transfer == out case)
1880       # ... 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)
1881
1882       $qty = 0 if (!$::instance_conf->get_transfer_default_services && $part_info_map{$form->{"id_$i"}}->{part_type} eq 'service');
1883       $qty_parts{$form->{"id_$i"}} += $qty;
1884       if ($qty == 0) {
1885         delete $qty_parts{$form->{"id_$i"}} unless $qty_parts{$form->{"id_$i"}};
1886         undef $form->{"stock_in_$i"};
1887       }
1888
1889       $part_info_map{$form->{"id_$i"}}{bin_id}       ||= $default_bin_id;
1890       $part_info_map{$form->{"id_$i"}}{warehouse_id} ||= $default_warehouse_id;
1891
1892       push @all_requests, ($qty == 0) ? { } : {
1893                         'chargenumber' => '',  #?? die müsste entsprechend geholt werden
1894                         #'bestbefore' => undef, # TODO wird nicht berücksichtigt
1895                         'bin_id' => $part_info_map{$form->{"id_$i"}}{bin_id},
1896                         'qty' => $qty,
1897                         'parts_id' => $form->{"id_$i"},
1898                         'comment' => $locale->text("Default transfer delivery order"),
1899                         'unit' => $part_info_map{$form->{"id_$i"}}{unit},
1900                         'warehouse_id' => $part_info_map{$form->{"id_$i"}}{warehouse_id},
1901                         'oe_id' => $form->{id},
1902                         'project_id' => $form->{"project_id_$i"} ? $form->{"project_id_$i"} : $form->{globalproject_id}
1903                       };
1904     }
1905
1906     # jetzt wird erst überprüft, ob die Stückzahl entsprechend stimmt.
1907     # check if bin (transfer in and transfer out and qty (transfer out) is correct
1908     foreach my $key (keys %qty_parts) {
1909
1910       $missing_default_bins{$key}{missing_bin} = 1 unless ($part_info_map{$key}{bin_id});
1911       next unless ($part_info_map{$key}{bin_id}); # abbruch
1912
1913       if ($params{direction} eq 'out') {  # wird nur für ausgehende Mengen benötigt
1914         my ($max_qty, $error) = WH->get_max_qty_parts_bin(parts_id => $key, bin_id => $part_info_map{$key}{bin_id});
1915         if ($error == 1) {
1916           # wir können nicht entscheiden, welche charge oder mhd (bestbefore) ausgewählt sein soll
1917           # deshalb rückmeldung nach oben geben, manuell auszulagern
1918           # TODO Bei nur einem Treffer mit Charge oder bestbefore wäre das noch möglich
1919           $missing_default_bins{$key}{chargenumber} = 1;
1920         }
1921         if ($max_qty < $qty_parts{$key}){
1922           $missing_default_bins{$key}{missing_qty} = $max_qty - $qty_parts{$key};
1923         }
1924       }
1925     }
1926   } # if @parts_id
1927
1928   # Abfrage für Fehlerbehandlung (nur bei direction == out)
1929   if (scalar (keys %missing_default_bins)) {
1930     my $fehlertext;
1931     foreach my $fehler (keys %missing_default_bins) {
1932
1933       my $ware = WH->get_part_description(parts_id => $fehler);
1934       if ($missing_default_bins{$fehler}{missing_bin}){
1935         $fehlertext .= "Kein Standardlagerplatz definiert bei $ware <br>";
1936       }
1937       if ($missing_default_bins{$fehler}{missing_qty}) {  # missing_qty
1938         $fehlertext .= "Es fehlen " . $missing_default_bins{$fehler}{missing_qty}*-1 .
1939                        " von $ware auf dem Standard-Lagerplatz " . $part_info_map{$fehler}{bin} .   " zum Auslagern<br>";
1940       }
1941       if ($missing_default_bins{$fehler}{chargenumber}){
1942         $fehlertext .= "Die Ware hat eine Chargennummer oder eine Mindesthaltbarkeit definiert.
1943                         Hier kann man nicht automatisch entscheiden.
1944                         Bitte diesen Lieferschein manuell auslagern.
1945                         Bei: $ware";
1946       }
1947       # auslagern soll immer gehen, auch wenn nicht genügend auf lager ist.
1948       # der lagerplatz ist hier extra konfigurierbar, bspw. Lager-Korrektur mit
1949       # Lagerplatz Lagerplatz-Korrektur
1950       my $default_warehouse_id_ignore_onhand = $::instance_conf->get_warehouse_id_ignore_onhand;
1951       my $default_bin_id_ignore_onhand       = $::instance_conf->get_bin_id_ignore_onhand;
1952       if ($::instance_conf->get_transfer_default_ignore_onhand && $default_bin_id_ignore_onhand) {
1953         # entsprechende defaults holen
1954         # falls chargenumber, bestbefore oder anzahl nicht stimmt, auf automatischen
1955         # lagerplatz wegbuchen!
1956         foreach (@all_requests) {
1957           if ($_->{parts_id} eq $fehler){
1958           $_->{bin_id}        = $default_bin_id_ignore_onhand;
1959           $_->{warehouse_id}  = $default_warehouse_id_ignore_onhand;
1960           }
1961         }
1962       } else {
1963         #$main::lxdebug->message(0, 'Fehlertext: ' . $fehlertext);
1964         $form->show_generic_error($locale->text("Cannot transfer. <br> Reason:<br>#1", $fehlertext ));
1965       }
1966     }
1967   }
1968
1969
1970   # hier der eigentliche fallunterschied für in oder out
1971   my $prefix   = $params{direction} eq 'in' ? 'in' : 'out';
1972
1973   # dieser array_ref ist für DO->save da:
1974   # einmal die all_requests in YAML verwandeln, damit delivery_order_items_stock
1975   # gefüllt werden kann.
1976   # could be dumped to the form in the first loop,
1977   # but maybe bin_id and warehouse_id has changed to the "korrekturlager" with
1978   # allowed negative qty ($::instance_conf->get_warehouse_id_ignore_onhand) ...
1979   my $i = 0;
1980   foreach (@all_requests){
1981     $i++;
1982     next unless scalar(%{ $_ });
1983     $form->{"stock_${prefix}_$i"} = SL::YAML::Dump([$_]);
1984   }
1985
1986   save(no_redirect => 1); # Wir können auslagern, deshalb beleg speichern
1987                           # und in delivery_order_items_stock speichern
1988
1989   # ... and fill back the persistent dois_id for inventory fk
1990   undef (@all_requests);
1991   foreach my $i (1 .. $form->{rowcount}) {
1992     next unless ($form->{"id_$i"} && $form->{"stock_${prefix}_$i"});
1993     push @all_requests, @{ DO->unpack_stock_information('packed' => $form->{"stock_${prefix}_$i"}) };
1994   }
1995   DO->transfer_in_out('direction' => $prefix,
1996                       'requests'  => \@all_requests);
1997
1998   SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
1999
2000   $form->{callback} = 'do.pl?action=edit&type=sales_delivery_order&id=' . $form->escape($form->{id}) if $params{direction} eq 'out';
2001   $form->{callback} = 'do.pl?action=edit&type=purchase_delivery_order&id=' . $form->escape($form->{id}) if $params{direction} eq 'in';
2002   $form->redirect;
2003
2004 }
2005
2006 sub sort {
2007   $main::lxdebug->enter_sub();
2008
2009   check_do_access();
2010
2011   my $form     = $main::form;
2012   my %temp_hash;
2013
2014   save(no_redirect => 1); # has to be done, at least for newly added positions
2015
2016   # hashify partnumbers, positions. key is delivery_order_items_id
2017   for my $i (1 .. ($form->{rowcount}) ) {
2018     $temp_hash{$form->{"delivery_order_items_id_$i"}} = { runningnumber => $form->{"runningnumber_$i"}, partnumber => $form->{"partnumber_$i"} };
2019     if ($form->{id} && $form->{"discount_$i"}) {
2020       # prepare_order assumes a db value if there is a form->id and multiplies *100
2021       # We hope for new controller code (no more format_amount/parse_amount distinction)
2022       $form->{"discount_$i"} /=100;
2023     }
2024   }
2025   # naturally sort partnumbers and get a sorted array of doi_ids
2026   my @sorted_doi_ids =  sort { Sort::Naturally::ncmp($temp_hash{$a}->{"partnumber"}, $temp_hash{$b}->{"partnumber"}) }  keys %temp_hash;
2027
2028
2029   my $new_number = 1;
2030
2031   for (@sorted_doi_ids) {
2032     $form->{"runningnumber_$temp_hash{$_}->{runningnumber}"} = $new_number;
2033     $new_number++;
2034   }
2035   # all parse_amounts changes are in form (i.e. , to .) therefore we need
2036   # another format_amount to change it back, for the next save ;-(
2037   # works great except for row discounts (see above comment)
2038   prepare_order();
2039
2040
2041     $main::lxdebug->leave_sub();
2042     save();
2043 }
2044
2045 __END__
2046
2047 =pod
2048
2049 =encoding utf8
2050
2051 =head1 NAME
2052
2053 do.pl - Script for all calls to delivery order
2054
2055 =head1 FUNCTIONS
2056
2057 =over 2
2058
2059 =item C<sort>
2060
2061 Sorts all position with Natural Sort. Can be activated in form_footer.html like this
2062 C<E<lt>input class="submit" type="submit" name="action_sort" id="sort_button" value="[% 'Sort and Save' | $T8 %]"E<gt>>
2063
2064 =back
2065
2066 =head1 TODO
2067
2068 Sort and Save can be implemented as an optional button if configuration ca be set by client config.
2069 Example coding for database scripts and templates in (git show af2f24b8), check also
2070 autogeneration for rose (scripts/rose_auto_create_model.pl --h)