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