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