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