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