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