1 #=====================================================================
4 # Based on SQL-Ledger Version 2.1.9
5 # Web http://www.lx-office.org
7 #=====================================================================
8 # SQL-Ledger, Accounting
9 # Copyright (c) 1998-2003
11 # Author: Dieter Simader
12 # Email: dsimader@sql-ledger.org
13 # Web: http://www.sql-ledger.org
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.
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,
29 #======================================================================
32 #======================================================================
35 use List::MoreUtils qw(uniq);
36 use List::Util qw(max sum);
37 use POSIX qw(strftime);
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;
48 use SL::MoreCommon qw(ary_diff restore_form save_form);
49 use SL::Presenter::ItemsList;
50 use SL::ReportGenerator;
53 use Sort::Naturally ();
54 require "bin/mozilla/common.pl";
55 require "bin/mozilla/io.pl";
56 require "bin/mozilla/reportgenerator.pl";
58 use SL::Helper::Flash qw(flash flash_later render_flash);
66 sub check_do_access_for_edit {
67 validate_type($::form->{type});
69 my $right = SL::DB::DeliveryOrder::TypeData::get3($::form->{type}, "rights", "edit");
70 $main::auth->assert($right);
74 validate_type($::form->{type});
76 my $right = SL::DB::DeliveryOrder::TypeData::get3($::form->{type}, "rights", "view");
77 $main::auth->assert($right);
81 $main::lxdebug->enter_sub();
87 my $form = $main::form;
88 my $locale = $main::locale;
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');
94 $form->{vc} = 'customer';
95 $form->{title} = $action eq "edit" ? $locale->text('Edit Sales Delivery Order') : $locale->text('Add Sales Delivery Order');
98 $form->{heading} = $locale->text('Delivery Order');
100 $main::lxdebug->leave_sub();
104 $main::lxdebug->enter_sub();
106 check_do_access_for_edit();
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."));
112 my $form = $main::form;
116 $form->{show_details} = $::myconfig{show_form_details};
117 $form->{callback} = build_std_url('action=add', 'type', 'vc') unless ($form->{callback});
119 if (!$form->{form_validity_token}) {
120 $form->{form_validity_token} = SL::DB::ValidityToken->create(scope => SL::DB::ValidityToken::SCOPE_DELIVERY_ORDER_SAVE())->token;
123 order_links(is_new => 1);
127 $main::lxdebug->leave_sub();
130 sub add_from_reclamation {
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();
136 croak("Error while converting: " . $error);
139 # edit new saved delivery order
140 $::form->{id} = $delivery_order->id;
145 $main::lxdebug->enter_sub();
149 my $form = $main::form;
151 $form->{show_details} = $::myconfig{show_form_details};
153 # show history button
154 $form->{javascript} = qq|<script type="text/javascript" src="js/show_history.js"></script>|;
155 #/show hhistory button
157 $form->{simple_save} = 0;
159 set_headings("edit");
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});
167 undef $form->{rowcount};
169 $main::lxdebug->leave_sub();
172 } elsif (!$form->{id}) {
174 $main::lxdebug->leave_sub();
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};
187 set_headings("edit");
192 if ($form->{print_and_save}) {
193 $form->{language_id} = $language_id;
194 $form->{printer_id} = $printer_id;
199 $main::lxdebug->leave_sub();
203 $main::lxdebug->enter_sub();
208 my $form = $main::form;
209 my %myconfig = %main::myconfig;
211 # retrieve order/quotation
212 my $editing = $form->{id};
214 DO->retrieve('vc' => $form->{vc},
215 'ids' => $form->{id});
217 $form->backup_vars(qw(payment_id language_id taxzone_id salesman_id taxincluded cp_id intnotes delivery_term_id currency));
219 # get customer / vendor
220 if ($form->{vc} eq 'vendor') {
221 IR->get_vendor(\%myconfig, \%$form);
222 $form->{discount} = $form->{vendor_discount};
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};
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;
234 $main::lxdebug->leave_sub();
238 $main::lxdebug->enter_sub();
242 my $form = $main::form;
243 my %myconfig = %main::myconfig;
245 $form->{formname} = $form->{type} unless $form->{formname};
248 foreach my $ref (@{ $form->{form_details} }) {
249 $form->{rowcount} = ++$i;
251 map { $form->{"${_}_$i"} = $ref->{$_} } keys %{$ref};
253 for my $i (1 .. $form->{rowcount}) {
255 $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"} * 100);
257 $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"});
259 my ($dec) = ($form->{"sellprice_$i"} =~ /\.(\d+)/);
261 my $decimalplaces = ($dec > 2) ? $dec : 2;
263 # copy reqdate from deliverydate for invoice -> order conversion
264 $form->{"reqdate_$i"} = $form->{"deliverydate_$i"} unless $form->{"reqdate_$i"};
266 $form->{"sellprice_$i"} = $form->format_amount(\%myconfig, $form->{"sellprice_$i"}, $decimalplaces);
267 $form->{"lastcost_$i"} = $form->format_amount(\%myconfig, $form->{"lastcost_$i"}, $decimalplaces);
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);
274 $main::lxdebug->leave_sub();
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';
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;
289 my $may_edit_create = $::auth->assert(SL::DB::DeliveryOrder::TypeData::get3($::form->{type}, "rights", "edit"), 1);
291 for my $bar ($::request->layout->get('actionbar')) {
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',
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.')
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.')
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.')
327 ], # end of combobox "Save"
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.')
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.')
349 only_if => $is_customer,
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.')
358 only_if => $is_customer && $::instance_conf->get_transfer_default,
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.')
367 only_if => !$is_customer,
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.')
376 only_if => !$is_customer && $::instance_conf->get_transfer_default,
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.')
387 ], # end of combobox "Transfer out"
393 action => [ t8('Workflow') ],
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?')
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,
411 action => [ t8('Export') ],
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,
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.')
426 ], # end of combobox "Export"
429 action => [ t8('more') ],
432 call => [ 'set_history_window', $::form->{id} * 1, 'id' ],
433 disabled => !$::form->{id} ? t8('This record has not been saved yet.') : undef,
437 call => [ 'follow_up_window' ],
438 disabled => !$::form->{id} ? t8('This record has not been saved yet.') : undef,
440 ], # end if combobox "more"
443 $::request->layout->add_javascripts('kivi.Validator.js');
446 sub setup_do_search_action_bar {
449 for my $bar ($::request->layout->get('actionbar')) {
453 submit => [ '#form' ],
454 accesskey => 'enter',
455 checks => [ 'kivi.validate_form' ],
459 $::request->layout->add_javascripts('kivi.Validator.js');
462 sub setup_do_orders_action_bar {
465 for my $bar ($::request->layout->get('actionbar')) {
469 submit => [ '#form', { action => 'invoice_multi' } ],
470 checks => [ [ 'kivi.check_if_entries_selected', '#form tbody input[type=checkbox]' ] ],
471 accesskey => 'enter',
475 call => [ 'kivi.SalesPurchase.show_print_dialog', 'js:kivi.MassDeliveryOrderPrint.submitMultiOrders' ],
476 checks => [ [ 'kivi.check_if_entries_selected', '#form tbody input[type=checkbox]' ] ],
483 $main::lxdebug->enter_sub();
487 my $form = $main::form;
488 my %myconfig = %main::myconfig;
490 my $class = "SL::DB::" . ($form->{vc} eq 'customer' ? 'Customer' : 'Vendor');
491 $form->{VC_OBJ} = $class->load_cached($form->{ $form->{vc} . '_id' });
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;
500 my $vc = $form->{vc} eq "customer" ? "customers" : "vendors";
501 $form->get_lists("price_factors" => "ALL_PRICE_FACTORS",
502 "business_types" => "ALL_BUSINESS_TYPES",
504 $form->{ALL_DEPARTMENTS} = SL::DB::Manager::Department->get_all_sorted;
505 $form->{ALL_LANGUAGES} = SL::DB::Manager::Language->get_all_sorted;
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) : ();
511 if (($vc eq 'customers') && $::instance_conf->get_customer_projects_only_in_sales) {
514 customer_id => $::form->{customer_id},
515 billable_customer_id => $::form->{customer_id},
520 and => [ active => 1, @customer_cond ],
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 ] ]
531 $::form->{ALL_CONTACTS} = SL::DB::Manager::Contact->get_all_sorted(query => [
533 cp_cv_id => $::form->{"$::form->{vc}_id"} * 1,
536 cp_id => $::form->{cp_id} * 1
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();";
549 $::request->{layout}->add_javascripts_inline("\$(function(){$dispatch_to_popup});");
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();
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));
557 setup_do_action_bar();
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');
574 $main::lxdebug->leave_sub();
578 $main::lxdebug->enter_sub();
582 my $form = $main::form;
584 $form->{PRINT_OPTIONS} = setup_sales_purchase_print_options();
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};
592 print $form->parse_html_template('do/form_footer',
593 {transfer_default => ($::instance_conf->get_transfer_default),
594 shipto_cvars => $shipto_cvars});
596 $main::lxdebug->leave_sub();
599 sub update_delivery_order {
600 $main::lxdebug->enter_sub();
604 my $form = $main::form;
605 my %myconfig = %main::myconfig;
607 set_headings($form->{"id"} ? "edit" : "add");
609 $form->{insertdate} = SL::DB::DeliveryOrder->new(id => $form->{id})->load->itime_as_date if $form->{id};
614 $payment_id = $form->{payment_id} if $form->{payment_id};
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};
620 if ($vc eq 'customer') {
621 IS->get_customer(\%myconfig, $form);
622 $::form->{billing_address_id} = $::form->{default_billing_address_id};
624 IR->get_vendor(\%myconfig, $form);
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 "";
643 my $i = $form->{rowcount};
645 if ( ($form->{"partnumber_$i"} eq "")
646 && ($form->{"description_$i"} eq "")
647 && ($form->{"partsgroup_$i"} eq "")) {
654 if ($form->{type} eq 'purchase_delivery_order') {
655 IR->retrieve_item(\%myconfig, $form);
658 IS->retrieve_item(\%myconfig, $form);
662 my $rows = scalar @{ $form->{item_list} };
665 $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
666 if( !$form->{"qty_$i"} ) {
667 $form->{"qty_$i"} = 1;
672 select_item(mode => $mode, pre_entered_qty => $form->{"qty_$i"});
673 $::dispatcher->end_request;
677 my $sellprice = $form->parse_amount(\%myconfig, $form->{"sellprice_$i"});
679 map { $form->{"${_}_$i"} = $form->{item_list}[0]{$_} } keys %{ $form->{item_list}[0] };
681 $form->{"marge_price_factor_$i"} = $form->{item_list}->[0]->{price_factor};
684 $form->{"sellprice_$i"} = $sellprice;
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;
692 $::form->{"sellprice_$i"} = $best_price->price;
693 $::form->{"active_price_source_$i"} = $best_price->source;
695 if ($best_discount) {
696 $::form->{"discount_$i"} = $best_discount->discount;
697 $::form->{"active_discount_source_$i"} = $best_discount->source;
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);
711 # ok, so this is a new part
712 # ask if it is a part or service item
714 if ( $form->{"partsgroup_$i"}
715 && ($form->{"partsnumber_$i"} eq "")
716 && ($form->{"description_$i"} eq "")) {
718 $form->{"discount_$i"} = "";
719 $form->{"not_discountable_$i"} = "";
723 $form->{"id_$i"} = 0;
729 $main::lxdebug->leave_sub();
733 $main::lxdebug->enter_sub();
737 my $form = $main::form;
738 my %myconfig = %main::myconfig;
739 my $locale = $main::locale;
741 $form->{vc} = $form->{type} eq 'purchase_delivery_order' ? 'vendor' : 'customer';
743 $form->get_lists("projects" => { "key" => "ALL_PROJECTS",
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');
750 setup_do_search_action_bar();
754 print $form->parse_html_template('do/search');
756 $main::lxdebug->leave_sub();
760 $main::lxdebug->enter_sub();
764 my $form = $main::form;
765 my %myconfig = %main::myconfig;
766 my $locale = $main::locale;
767 my $cgi = $::request->{cgi};
769 $::request->{layout}->use_javascript(map { "${_}.js" } qw(kivi.MassDeliveryOrderPrint kivi.SalesPurchase));
770 ($form->{ $form->{vc} }, $form->{"$form->{vc}_id"}) = split(/--/, $form->{ $form->{vc} });
772 report_generator_set_default_sort('transdate', 1);
776 $form->{rowcount} = scalar @{ $form->{DO} };
779 ids transdate reqdate
781 ordnumber order_confirmation_number
782 customernumber vendor_confirmation_number
784 name employee salesman
785 shipvia globalprojectnumber
786 transaction_description department
791 $form->{l_open} = $form->{l_closed} = "Y" if ($form->{open} && $form->{closed});
792 $form->{l_delivered} = "Y" if ($form->{delivered} && $form->{notdelivered});
794 $form->{title} = $locale->text('Delivery Orders');
796 my $attachment_basename = SL::DB::DeliveryOrder::TypeData::get3($form->{type}, "text", "attachment");
798 my $report = SL::ReportGenerator->new(\%myconfig, $form);
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);
807 my $href = build_std_url('action=orders', grep { $form->{$_} } @hidden_variables);
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'), },
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";
838 $form->{"l_type"} = "Y";
839 map { $column_defs{$_}->{visible} = $form->{"l_${_}"} ? 1 : 0 } @columns;
841 $column_defs{ids}->{visible} = 'HTML';
843 $report->set_columns(%column_defs);
844 $report->set_column_order(@columns);
846 $report->set_export_options('orders', @hidden_variables, qw(sort sortdir));
848 $report->set_sort_indicator($form->{sort}, $form->{sortdir});
851 if ($form->{customer}) {
852 push @options, $locale->text('Customer') . " : $form->{customer}";
854 if ($form->{vendor}) {
855 push @options, $locale->text('Vendor') . " : $form->{vendor}";
857 if ($form->{cp_name}) {
858 push @options, $locale->text('Contact Person') . " : $form->{cp_name}";
860 if ($form->{department_id}) {
861 push @options, $locale->text('Department') . " : " . SL::DB::Department->new(id => $form->{department_id})->load->description;
863 if ($form->{donumber}) {
864 push @options, $locale->text('Delivery Order Number') . " : $form->{donumber}";
866 if ($form->{ordnumber}) {
867 push @options, $locale->text('Order Number') . " : $form->{ordnumber}";
869 if ($form->{order_confirmation_number}) {
870 push @options, $locale->text('Order Confirmation Number') . " : $form->{order_confirmation_number}";
872 if ($form->{vendor_confirmation_number}) {
873 push @options, $locale->text('Vendor Confirmation Number') . " : $form->{vendor_confirmation_number}";
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;
880 if ($form->{transaction_description}) {
881 push @options, $locale->text('Transaction description') . " : $form->{transaction_description}";
883 if ($form->{fulltext}) {
884 push @options, $locale->text('Full Text') . " : $form->{fulltext}";
886 if ($form->{parts_description}) {
887 push @options, $locale->text('Part Description') . " : $form->{parts_description}";
889 if ($form->{parts_partnumber}) {
890 push @options, $locale->text('Part Number') . " : $form->{parts_partnumber}";
892 if ($form->{chargenumber}) {
893 push @options, $locale->text('Charge Number') . " : $form->{chargenumber}";
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};
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};
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};
911 push @options, $locale->text('Open');
913 if ($form->{closed}) {
914 push @options, $locale->text('Closed');
916 if ($form->{delivered}) {
917 push @options, $locale->text('Delivered');
919 if ($form->{notdelivered}) {
920 push @options, $locale->text('Not delivered');
922 push @options, $locale->text('Quick Search') . " : $form->{all}" if $form->{all};
924 my $pr = SL::DB::Manager::Printer->find_by(
925 printer_description => $::locale->text("sales_delivery_order_printer"));
927 $form->{printer_id} = $pr->id;
930 my $print_options = SL::Helper::PrintOptions->get_print_options(
932 hide_language_id => 1,
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),
945 $report->set_options_from_form();
946 $locale->set_numberformat_wo_thousands_separator(\%myconfig) if lc($report->{options}->{output_format}) eq 'csv';
948 # add sort and escape callback, this one we use for the add sub
949 $form->{callback} = $href .= "&sort=$form->{sort}";
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)) );
955 # escape callback for href
956 my $callback = $form->escape($href);
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'));
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');
967 my $row = { map { $_ => { 'data' => $dord->{$_} } } grep {$_ ne 'items' || $_ ne 'order_confirmation_numbers'} @columns };
969 my $ord_id = $dord->{id};
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',
976 $row->{donumber}->{link} = SL::Controller::DeliveryOrder->url_for(action => "edit", id => $dord->{id}, type => $dord->{record_type}, callback => $form->{callback});
978 if (!$form->{hide_oe_links}) {
979 $row->{ordnumber}->{link} = $edit_order_url . "&id=" . E($dord->{oe_id}) . "&callback=${callback}" if $dord->{oe_id};
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};
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';
997 $report->add_data($row);
1002 setup_do_orders_action_bar();
1004 $report->generate_with_headers();
1006 $main::lxdebug->leave_sub();
1010 $main::lxdebug->enter_sub();
1014 check_do_access_for_edit();
1016 my $form = $main::form;
1017 my %myconfig = %main::myconfig;
1018 my $locale = $main::locale;
1020 $form->mtime_ischanged('delivery_orders');
1022 $form->{defaultcurrency} = $form->get_default_currency(\%myconfig);
1024 $form->isblank("transdate", $locale->text('Delivery Order Date missing!'));
1026 $form->{donumber} =~ s/^\s*//g;
1027 $form->{donumber} =~ s/\s*$//g;
1029 my $msg = ucfirst $form->{vc};
1030 $form->isblank($form->{vc} . "_id", $locale->text($msg . " missing!"));
1032 # $locale->text('Customer missing!');
1033 # $locale->text('Vendor missing!');
1035 remove_emptied_rows();
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";
1044 if ($missing_serialnr ne '') {
1045 flash('error', $locale->text('Serial Number missing in Row') . $missing_serialnr);
1048 $::dispatcher->end_request;
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};
1057 if ($vc eq 'customer') {
1058 IS->get_customer(\%myconfig, $form);
1059 $::form->{billing_address_id} = $::form->{default_billing_address_id};
1061 IR->get_vendor(\%myconfig, $form);
1065 $::dispatcher->end_request;
1068 if ($form->{saveasnew}) {
1070 $form->{form_validity_token} = SL::DB::ValidityToken->create(scope => SL::DB::ValidityToken::SCOPE_DELIVERY_ORDER_SAVE())->token;
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}) {
1077 for my $i (1 .. $form->{rowcount}) {
1078 if ($form->{"converted_from_orderitems_id_$i"}) {
1079 $has_linked_pos = 1;
1083 if (!$has_linked_pos) {
1084 $form->error($locale->text('Need at least one original position for the workflow Order to Delivery Order!'));
1089 # saving the history
1090 if(!exists $form->{addition}) {
1091 $form->{snumbers} = qq|donumber_| . $form->{donumber};
1092 $form->{addition} = "SAVED";
1093 $form->save_history;
1095 # /saving the history
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)])};
1101 $::dispatcher->end_request;
1103 $main::lxdebug->leave_sub();
1107 $main::lxdebug->enter_sub();
1109 check_do_access_for_edit();
1111 my $form = $main::form;
1112 my %myconfig = %main::myconfig;
1113 my $locale = $main::locale;
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;
1122 # /saving the history
1124 $form->info($locale->text('Delivery Order deleted!'));
1125 $::dispatcher->end_request;
1128 $form->error($locale->text('Cannot delete delivery order!') . $ret);
1130 $main::lxdebug->leave_sub();
1132 sub delete_transfers {
1133 $main::lxdebug->enter_sub();
1135 check_do_access_for_edit();
1137 my $form = $main::form;
1138 my %myconfig = %main::myconfig;
1139 my $locale = $main::locale;
1142 die "Invalid form type" unless $form->{type} =~ m/^(sales|purchase)_delivery_order$/;
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;
1151 # /saving the history
1153 flash_later('info', $locale->text("Transfer undone."));
1155 $form->{callback} = 'do.pl?action=edit&type=' . $form->{type} . '&id=' . $form->escape($form->{id});
1159 $form->error($locale->text('Cannot undo delivery order transfer!') . $ret);
1161 $main::lxdebug->leave_sub();
1164 sub invoice_from_delivery_order_controller {
1165 $main::lxdebug->enter_sub();
1166 my $form = $main::form;
1168 my $from_id = delete $form->{from_id};
1169 my $delivery_order = SL::DB::DeliveryOrder->new(id => $from_id)->load;
1171 $delivery_order->flatten_to_form($form, format_amounts => 1);
1172 $form->{rowcount}++;
1175 $main::lxdebug->leave_sub();
1179 $main::lxdebug->enter_sub();
1181 my $form = $main::form;
1182 my %myconfig = %main::myconfig;
1183 my $locale = $main::locale;
1186 $form->mtime_ischanged('delivery_orders');
1188 $main::auth->assert($form->{type} eq 'purchase_delivery_order' ? 'vendor_invoice_edit' : 'invoice_edit');
1190 $form->get_employee();
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);
1200 $form->{rowcount}--;
1202 delete @{$form}{qw(id closed delivered)};
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';
1210 $form->{form_validity_token} = SL::DB::ValidityToken->create(scope => SL::DB::ValidityToken::SCOPE_PURCHASE_INVOICE_POST())->token;
1213 $form->{title} = $locale->text('Add Sales Invoice');
1214 $form->{script} = 'is.pl';
1217 $form->{form_validity_token} = SL::DB::ValidityToken->create(scope => SL::DB::ValidityToken::SCOPE_SALES_INVOICE_POST())->token;
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);
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
1233 $form->{"donumber_$i"} = $form->{donumber};
1234 $form->{"converted_from_delivery_order_items_id_$i"} = delete $form->{"delivery_order_items_id_$i"};
1237 $form->{type} = "invoice";
1240 $main::locale = Locale->new("$myconfig{countrycode}", "$script");
1241 $locale = $main::locale;
1243 require "bin/mozilla/$form->{script}";
1245 my $currency = $form->{currency};
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"})) {
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;
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});
1267 for my $i (1 .. $form->{rowcount}) {
1268 $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"});
1270 my ($dec) = ($form->{"sellprice_$i"} =~ /\.(\d+)/);
1272 my $decimalplaces = ($dec > 2) ? $dec : 2;
1274 # copy delivery date from reqdate for order -> invoice conversion
1275 $form->{"deliverydate_$i"} = $form->{"reqdate_$i"}
1276 unless $form->{"deliverydate_$i"};
1279 $form->{"sellprice_$i"} =
1280 $form->format_amount(\%myconfig, $form->{"sellprice_$i"},
1283 $form->{"lastcost_$i"} =
1284 $form->format_amount(\%myconfig, $form->{"lastcost_$i"},
1287 (my $dec_qty) = ($form->{"qty_$i"} =~ /\.(\d+)/);
1288 $dec_qty = length $dec_qty;
1290 $form->format_amount(\%myconfig, $form->{"qty_$i"}, $dec_qty);
1296 $main::lxdebug->leave_sub();
1300 $main::lxdebug->enter_sub();
1302 my $form = $main::form;
1303 my %myconfig = %main::myconfig;
1304 my $locale = $main::locale;
1307 $main::auth->assert($form->{type} eq 'sales_delivery_order' ? 'invoice_edit' : 'vendor_invoice_edit');
1309 my @do_ids = map { $form->{"trans_id_$_"} } grep { $form->{"multi_id_$_"} } (1..$form->{rowcount});
1311 if (!scalar @do_ids) {
1312 $form->show_generic_error($locale->text('You have not selected any delivery order.'));
1315 map { delete $form->{$_} } grep { m/^(?:trans|multi)_id_\d+/ } keys %{ $form };
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);
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);
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';
1346 $form->{form_validity_token} = SL::DB::ValidityToken->create(scope => SL::DB::ValidityToken::SCOPE_PURCHASE_INVOICE_POST())->token;
1349 $form->{title} = $locale->text('Add Sales Invoice');
1350 $form->{script} = 'is.pl';
1353 $form->{form_validity_token} = SL::DB::ValidityToken->create(scope => SL::DB::ValidityToken::SCOPE_SALES_INVOICE_POST())->token;
1356 map { delete $form->{$_} } qw(id subject message cc bcc printed emailed queued);
1358 # get vendor or customer 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};
1365 IS->get_customer(\%myconfig, \%$form);
1366 $vc_discount = $form->{customer_discount};
1368 # use payment terms from customer or vendor
1369 restore_form($saved_form,0,qw(payment_id));
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}"};
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
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
1388 $form->{"discount_$form->{rowcount}"} = $form->format_amount(\%myconfig, $form->{"discount_$form->{rowcount}"});
1390 delete $form->{form_details};
1392 $locale = Locale->new("$myconfig{countrycode}", "$script");
1394 require "bin/mozilla/$form->{script}";
1401 $main::lxdebug->leave_sub();
1404 sub save_and_reclamation {
1405 my $form = $main::form;
1406 my $id = $form->{id};
1407 my $type = $form->{type};
1409 # save the delivery order
1410 save(no_redirect => 1);
1412 my $to_reclamation_type =
1413 $type eq 'sales_delivery_order' ? 'sales_reclamation'
1414 : 'purchase_reclamation';
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)
1425 $main::lxdebug->enter_sub();
1427 check_do_access_for_edit();
1429 my $form = $main::form;
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});
1447 $main::lxdebug->leave_sub();
1450 sub calculate_stock_in_out {
1451 $main::lxdebug->enter_sub();
1453 my $form = $main::form;
1457 if (!$form->{"id_${i}"}) {
1458 $main::lxdebug->leave_sub();
1462 my $all_units = AM->retrieve_all_units();
1464 my $in_out = $form->{type} =~ /^sales/ ? 'out' : 'in';
1465 my $sinfo = DO->unpack_stock_information('packed' => $form->{"stock_${in_out}_${i}"});
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;
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"};
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="?">|;
1476 $main::lxdebug->leave_sub();
1481 sub get_basic_bin_wh_info {
1482 $main::lxdebug->enter_sub();
1484 my $stock_info = shift;
1486 my $form = $main::form;
1488 foreach my $sinfo (@{ $stock_info }) {
1489 next unless ($sinfo->{bin_id});
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);
1495 $main::lxdebug->leave_sub();
1498 sub stock_in_out_form {
1499 $main::lxdebug->enter_sub();
1501 my $form = $main::form;
1503 if ($form->{in_out} eq 'out') {
1509 $main::lxdebug->leave_sub();
1512 sub redo_stock_info {
1513 $main::lxdebug->enter_sub();
1517 my $form = $main::form;
1519 my @non_empty = grep { $_->{qty} } @{ $params{stock_info} };
1521 if ($params{add_empty_row}) {
1523 'warehouse_id' => scalar(@non_empty) ? $non_empty[-1]->{warehouse_id} : undef,
1524 'bin_id' => scalar(@non_empty) ? $non_empty[-1]->{bin_id} : undef,
1528 @{ $params{stock_info} } = @non_empty;
1530 $main::lxdebug->leave_sub();
1533 sub update_stock_in {
1534 $main::lxdebug->enter_sub();
1536 my $form = $main::form;
1537 my %myconfig = %main::myconfig;
1539 my $stock_info = [];
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) };
1547 display_stock_in_form($stock_info);
1549 $main::lxdebug->leave_sub();
1553 $main::lxdebug->enter_sub();
1555 my $form = $main::form;
1557 my $stock_info = DO->unpack_stock_information('packed' => $form->{stock});
1559 display_stock_in_form($stock_info);
1561 $main::lxdebug->leave_sub();
1564 sub display_stock_in_form {
1565 $main::lxdebug->enter_sub();
1567 my $stock_info = shift;
1569 my $form = $main::form;
1570 my %myconfig = %main::myconfig;
1571 my $locale = $main::locale;
1573 $form->{title} = $locale->text('Stock');
1575 my $part_info = IC->get_basic_part_info('id' => $form->{parts_id});
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;
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});
1587 $form->get_lists('warehouses' => { 'key' => 'WAREHOUSES',
1588 'bins' => 'BINS' });
1590 redo_stock_info('stock_info' => $stock_info, 'add_empty_row' => !$form->{delivered});
1592 get_basic_bin_wh_info($stock_info);
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, });
1599 $main::lxdebug->leave_sub();
1602 sub _stock_in_out_set_qty_display {
1603 my $stock_info = shift;
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"};
1612 $main::lxdebug->enter_sub();
1614 my $form = $main::form;
1615 my %myconfig = %main::myconfig;
1617 my $stock_info = [];
1619 foreach my $i (1..$form->{rowcount}) {
1620 $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
1622 next if ($form->{"qty_$i"} <= 0);
1624 push @{ $stock_info }, { map { $_ => $form->{"${_}_${i}"} } qw(delivery_order_items_stock_id warehouse_id bin_id chargenumber bestbefore qty unit) };
1627 $form->{stock} = SL::YAML::Dump($stock_info);
1629 _stock_in_out_set_qty_display($stock_info);
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 });
1635 print $form->parse_html_template('do/set_stock_in_out', {
1636 qty_matches => $do_qty == $transfer_qty,
1639 $main::lxdebug->leave_sub();
1642 sub stock_out_form {
1643 $main::lxdebug->enter_sub();
1645 my $form = $main::form;
1646 my %myconfig = %main::myconfig;
1647 my $locale = $main::locale;
1649 $form->{title} = $locale->text('Release From Stock');
1651 my $part_info = IC->get_basic_part_info('id' => $form->{parts_id});
1653 my $units = AM->retrieve_units(\%myconfig, $form);
1654 my $units_data = AM->unit_select_data($units, undef, undef, $part_info->{unit});
1656 my @contents = DO->get_item_availability('parts_id' => $form->{parts_id});
1658 my $stock_info = DO->unpack_stock_information('packed' => $form->{stock});
1660 if (!$form->{delivered}) {
1661 foreach my $row (@contents) {
1662 $row->{available_qty} = $form->format_amount(\%::myconfig, $row->{qty} * 1) . ' ' . $part_info->{unit};
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}));
1670 map { $row->{"stock_$_"} = $sinfo->{$_} } qw(qty unit error delivery_order_items_stock_id);
1675 get_basic_bin_wh_info($stock_info);
1677 foreach my $sinfo (@{ $stock_info }) {
1678 map { $sinfo->{"stock_$_"} = $sinfo->{$_} } qw(qty unit);
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, });
1687 $main::lxdebug->leave_sub();
1691 $main::lxdebug->enter_sub();
1693 my $form = $main::form;
1694 my %myconfig = %main::myconfig;
1695 my $locale = $main::locale;
1697 my $stock_info = [];
1699 foreach my $i (1 .. $form->{rowcount}) {
1700 $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
1702 next if ($form->{"qty_$i"} <= 0);
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"},
1712 'delivery_order_items_stock_id' => $form->{"delivery_order_items_stock_id_$i"},
1716 my @errors = DO->check_stock_availability('requests' => $stock_info,
1717 'parts_id' => $form->{parts_id});
1719 $form->{stock} = SL::YAML::Dump($stock_info);
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();
1727 _stock_in_out_set_qty_display($stock_info);
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 });
1733 print $form->parse_html_template('do/set_stock_in_out', {
1734 qty_matches => $do_qty == $transfer_qty,
1738 $main::lxdebug->leave_sub();
1742 $main::lxdebug->enter_sub();
1744 my $form = $main::form;
1745 my %myconfig = %main::myconfig;
1746 my $locale = $main::locale;
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.'));
1752 save(no_redirect => 1);
1754 my @part_ids = map { $form->{"id_${_}"} } grep { $form->{"id_${_}"} && $form->{"stock_in_${_}"} } (1 .. $form->{rowcount});
1758 my $units = AM->retrieve_units(\%myconfig, $form);
1759 my %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
1762 $form->{ERRORS} = [];
1764 foreach my $i (1 .. $form->{rowcount}) {
1765 next unless ($form->{"id_$i"} && $form->{"stock_in_$i"});
1767 my $row_sum_base_qty = 0;
1768 my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
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;
1774 $request->{project_id} = $form->{"project_id_$i"} || $form->{globalproject_id};
1776 push @all_requests, $request;
1779 next if (0 == $row_sum_base_qty);
1781 my $do_base_qty = $form->parse_amount(\%myconfig, $form->{"qty_$i"}) * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
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"});
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.');
1792 set_headings('edit');
1794 $main::lxdebug->leave_sub();
1796 $::dispatcher->end_request;
1800 DO->transfer_in_out('direction' => 'in',
1801 'requests' => \@all_requests);
1803 SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
1805 flash_later('info', $locale->text("Transfer successful"));
1806 $form->{callback} = 'do.pl?action=edit&type=purchase_delivery_order&id=' . $form->escape($form->{id});
1809 $main::lxdebug->leave_sub();
1813 $main::lxdebug->enter_sub();
1815 my $form = $main::form;
1816 my %myconfig = %main::myconfig;
1817 my $locale = $main::locale;
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.'));
1823 save(no_redirect => 1);
1825 my @part_ids = map { $form->{"id_${_}"} } grep { $form->{"id_${_}"} && $form->{"stock_out_${_}"} } (1 .. $form->{rowcount});
1829 my $units = AM->retrieve_units(\%myconfig, $form);
1830 my %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
1833 $form->{ERRORS} = [];
1835 foreach my $i (1 .. $form->{rowcount}) {
1836 next unless ($form->{"id_$i"} && $form->{"stock_out_$i"});
1838 my $row_sum_base_qty = 0;
1839 my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
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};
1846 my $map_key = join '--', ($form->{"id_$i"}, @{$request}{qw(warehouse_id bin_id chargenumber bestbefore)});
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};
1853 push @all_requests, $request;
1856 next if (0 == $row_sum_base_qty);
1858 my $do_base_qty = $form->{"qty_$i"} * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
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"});
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);
1871 foreach my $inv (@contents) {
1872 my $map_key = join '--', @{$inv}{qw(parts_id warehouse_id bin_id chargenumber bestbefore)};
1874 next unless ($request_map{$map_key});
1876 my $request = $request_map{$map_key};
1877 $request->{ok} = $request->{sum_base_qty} <= $inv->{qty};
1880 foreach my $request (values %request_map) {
1881 next if ($request->{ok});
1883 my $pinfo = $part_info_map{$request->{parts_id}};
1884 my $binfo = $bin_info_map{$request->{bin_id}};
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});
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});
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.');
1908 set_headings('edit');
1910 $main::lxdebug->leave_sub();
1912 $::dispatcher->end_request;
1915 DO->transfer_in_out('direction' => 'out',
1916 'requests' => \@all_requests);
1918 SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
1920 flash_later('info', $locale->text("Transfer successful"));
1921 $form->{callback} = 'do.pl?action=edit&type=sales_delivery_order&id=' . $form->escape($form->{id});
1924 $main::lxdebug->leave_sub();
1928 $main::lxdebug->enter_sub();
1930 my $form = $main::form;
1932 DO->close_orders('ids' => [ $form->{id} ]);
1934 $form->{closed} = 1;
1938 $main::lxdebug->leave_sub();
1942 $::lxdebug->enter_sub;
1947 retrieve_partunits();
1949 my $new_rowcount = $::form->{"rowcount"} * 1 + 1;
1950 $::form->{"project_id_${new_rowcount}"} = $::form->{"globalproject_id"};
1952 $::form->language_payment(\%::myconfig);
1954 Common::webdav_folder($::form);
1957 display_row(++$::form->{rowcount});
1960 $::lxdebug->leave_sub;
1964 call_sub($main::form->{yes_nextsub});
1968 call_sub($main::form->{no_nextsub});
1972 call_sub($main::form->{update_nextsub} || $main::form->{nextsub} || 'update_delivery_order');
1976 my $form = $main::form;
1977 my $locale = $main::locale;
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}"}) {
1987 $form->error($locale->text('No action defined.'));
1990 sub transfer_out_default {
1991 $main::lxdebug->enter_sub();
1993 my $form = $main::form;
1995 transfer_in_out_default('direction' => 'out');
1997 $main::lxdebug->leave_sub();
2000 sub transfer_in_default {
2001 $main::lxdebug->enter_sub();
2003 my $form = $main::form;
2005 transfer_in_out_default('direction' => 'in');
2007 $main::lxdebug->leave_sub();
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();
2018 my $form = $main::form;
2019 my %myconfig = %main::myconfig;
2020 my $locale = $main::locale;
2023 my (%missing_default_bins, %qty_parts, @all_requests, %part_info_map, $default_warehouse_id, $default_bin_id);
2025 Common::check_params(\%params, qw(direction));
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;
2034 my @part_ids = map { $form->{"id_${_}"} } (1 .. $form->{rowcount});
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;
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)
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;
2051 delete $qty_parts{$form->{"id_$i"}} unless $qty_parts{$form->{"id_$i"}};
2052 undef $form->{"stock_in_$i"};
2055 $part_info_map{$form->{"id_$i"}}{bin_id} ||= $default_bin_id;
2056 $part_info_map{$form->{"id_$i"}}{warehouse_id} ||= $default_warehouse_id;
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},
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}
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) {
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
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});
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;
2087 if ($max_qty < $qty_parts{$key}){
2088 $missing_default_bins{$key}{missing_qty} = $max_qty - $qty_parts{$key};
2094 # Abfrage für Fehlerbehandlung (nur bei direction == out)
2095 if (scalar (keys %missing_default_bins)) {
2097 foreach my $fehler (keys %missing_default_bins) {
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>";
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>";
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.
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;
2129 #$main::lxdebug->message(0, 'Fehlertext: ' . $fehlertext);
2130 $form->show_generic_error($locale->text("Cannot transfer. <br> Reason:<br>#1", $fehlertext ));
2136 # hier der eigentliche fallunterschied für in oder out
2137 my $prefix = $params{direction} eq 'in' ? 'in' : 'out';
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) ...
2146 foreach (@all_requests){
2148 next unless scalar(%{ $_ });
2149 $form->{"stock_${prefix}_$i"} = SL::YAML::Dump([$_]);
2152 save(no_redirect => 1); # Wir können auslagern, deshalb beleg speichern
2153 # und in delivery_order_items_stock speichern
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"}) };
2161 DO->transfer_in_out('direction' => $prefix,
2162 'requests' => \@all_requests);
2164 SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
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';
2173 $main::lxdebug->enter_sub();
2177 my $form = $main::form;
2180 save(no_redirect => 1); # has to be done, at least for newly added positions
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;
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;
2197 for (@sorted_doi_ids) {
2198 $form->{"runningnumber_$temp_hash{$_}->{runningnumber}"} = $new_number;
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)
2207 $main::lxdebug->leave_sub();
2219 do.pl - Script for all calls to delivery order
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>>
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)