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