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