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