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