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::DB::DeliveryOrder;
43 use SL::MoreCommon qw(ary_diff restore_form save_form);
44 use SL::ReportGenerator;
47 use Sort::Naturally ();
48 require "bin/mozilla/common.pl";
49 require "bin/mozilla/io.pl";
50 require "bin/mozilla/reportgenerator.pl";
59 $main::auth->assert($main::form->{type} . '_edit');
63 $main::lxdebug->enter_sub();
69 my $form = $main::form;
70 my $locale = $main::locale;
72 if ($form->{type} eq 'purchase_delivery_order') {
73 $form->{vc} = 'vendor';
74 $form->{title} = $action eq "edit" ? $locale->text('Edit Purchase Delivery Order') : $locale->text('Add Purchase Delivery Order');
76 $form->{vc} = 'customer';
77 $form->{title} = $action eq "edit" ? $locale->text('Edit Sales Delivery Order') : $locale->text('Add Sales Delivery Order');
80 $form->{heading} = $locale->text('Delivery Order');
82 $main::lxdebug->leave_sub();
86 $main::lxdebug->enter_sub();
90 if (($::form->{type} =~ /purchase/) && !$::instance_conf->get_allow_new_purchase_invoice) {
91 $::form->show_generic_error($::locale->text("You do not have the permissions to access this function."));
94 my $form = $main::form;
98 $form->{show_details} = $::myconfig{show_form_details};
99 $form->{callback} = build_std_url('action=add', 'type', 'vc') unless ($form->{callback});
105 $main::lxdebug->leave_sub();
109 $main::lxdebug->enter_sub();
113 my $form = $main::form;
115 $form->{show_details} = $::myconfig{show_form_details};
117 # show history button
118 $form->{javascript} = qq|<script type="text/javascript" src="js/show_history.js"></script>|;
119 #/show hhistory button
121 $form->{simple_save} = 0;
123 set_headings("edit");
125 # editing without stuff to edit? try adding it first
126 if ($form->{rowcount} && !$form->{print_and_save}) {
127 # map { $id++ if $form->{"multi_id_$_"} } (1 .. $form->{rowcount});
131 undef $form->{rowcount};
133 $main::lxdebug->leave_sub();
136 } elsif (!$form->{id}) {
138 $main::lxdebug->leave_sub();
142 my ($language_id, $printer_id);
143 if ($form->{print_and_save}) {
144 $form->{action} = "dispatcher";
145 $form->{action_print} = "1";
146 $form->{resubmit} = 1;
147 $language_id = $form->{language_id};
148 $printer_id = $form->{printer_id};
151 set_headings("edit");
156 if ($form->{print_and_save}) {
157 $form->{language_id} = $language_id;
158 $form->{printer_id} = $printer_id;
163 $main::lxdebug->leave_sub();
167 $main::lxdebug->enter_sub();
171 my $form = $main::form;
172 my %myconfig = %main::myconfig;
174 # retrieve order/quotation
175 my $editing = $form->{id};
177 DO->retrieve('vc' => $form->{vc},
178 'ids' => $form->{id});
180 $form->backup_vars(qw(payment_id language_id taxzone_id salesman_id taxincluded cp_id intnotes delivery_term_id currency));
182 # get customer / vendor
183 if ($form->{vc} eq 'vendor') {
184 IR->get_vendor(\%myconfig, \%$form);
185 $form->{discount} = $form->{vendor_discount};
187 IS->get_customer(\%myconfig, \%$form);
188 $form->{discount} = $form->{customer_discount};
191 $form->restore_vars(qw(payment_id language_id taxzone_id intnotes cp_id delivery_term_id));
192 $form->restore_vars(qw(currency)) if ($form->{id} || $form->{convert_from_oe_ids});
193 $form->restore_vars(qw(taxincluded)) if $form->{id};
194 $form->restore_vars(qw(salesman_id)) if $editing;
196 $main::lxdebug->leave_sub();
200 $main::lxdebug->enter_sub();
204 my $form = $main::form;
205 my %myconfig = %main::myconfig;
207 $form->{formname} = $form->{type} unless $form->{formname};
210 foreach my $ref (@{ $form->{form_details} }) {
211 $form->{rowcount} = ++$i;
213 map { $form->{"${_}_$i"} = $ref->{$_} } keys %{$ref};
215 for my $i (1 .. $form->{rowcount}) {
217 $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"} * 100);
219 $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"});
221 my ($dec) = ($form->{"sellprice_$i"} =~ /\.(\d+)/);
223 my $decimalplaces = ($dec > 2) ? $dec : 2;
225 # copy reqdate from deliverydate for invoice -> order conversion
226 $form->{"reqdate_$i"} = $form->{"deliverydate_$i"} unless $form->{"reqdate_$i"};
228 $form->{"sellprice_$i"} = $form->format_amount(\%myconfig, $form->{"sellprice_$i"}, $decimalplaces);
229 $form->{"lastcost_$i"} = $form->format_amount(\%myconfig, $form->{"lastcost_$i"}, $decimalplaces);
231 (my $dec_qty) = ($form->{"qty_$i"} =~ /\.(\d+)/);
232 $dec_qty = length $dec_qty;
233 $form->{"qty_$i"} = $form->format_amount(\%myconfig, $form->{"qty_$i"}, $dec_qty);
236 $main::lxdebug->leave_sub();
239 sub setup_do_action_bar {
240 my @transfer_qty = qw(kivi.SalesPurchase.delivery_order_check_transfer_qty);
241 my @req_trans_desc = qw(kivi.SalesPurchase.check_transaction_description) x!!$::instance_conf->get_require_transaction_description_ps;
242 my $is_customer = $::form->{vc} eq 'customer';
244 for my $bar ($::request->layout->get('actionbar')) {
248 submit => [ '#form', { action => "update" } ],
249 id => 'update_button',
250 accesskey => 'enter',
256 submit => [ '#form', { action => "save" } ],
257 checks => [ 'kivi.validate_form' ],
258 disabled => $::form->{delivered} ? t8('This record has already been delivered.') : undef,
262 submit => [ '#form', { action => "save_as_new" } ],
263 checks => [ 'kivi.validate_form' ],
264 disabled => !$::form->{id},
267 t8('Mark as closed'),
268 submit => [ '#form', { action => "mark_closed" } ],
269 checks => [ 'kivi.validate_form' ],
270 confirm => t8('This will remove the delivery order from showing as open even if contents are not delivered. Proceed?'),
271 disabled => !$::form->{id} ? t8('This record has not been saved yet.')
272 : $::form->{closed} ? t8('This record has already been closed.')
275 ], # end of combobox "Save"
279 submit => [ '#form', { action => "delete" } ],
280 confirm => t8('Do you really want to delete this object?'),
281 disabled => !$::form->{id} ? t8('This record has not been saved yet.')
282 : $::form->{delivered} ? t8('This record has already been delivered.')
283 : ($::form->{vc} eq 'customer' && !$::instance_conf->get_sales_delivery_order_show_delete) ? t8('Deleting this type of record has been disabled in the configuration.')
284 : ($::form->{vc} eq 'vendor' && !$::instance_conf->get_purchase_delivery_order_show_delete) ? t8('Deleting this type of record has been disabled in the configuration.')
291 submit => [ '#form', { action => "transfer_out" } ],
292 checks => [ 'kivi.validate_form', @transfer_qty ],
293 disabled => $::form->{delivered} ? t8('This record has already been delivered.') : undef,
294 only_if => $is_customer,
297 t8('Transfer out via default'),
298 submit => [ '#form', { action => "transfer_out_default" } ],
299 checks => [ 'kivi.validate_form' ],
300 disabled => $::form->{delivered} ? t8('This record has already been delivered.') : undef,
301 only_if => $is_customer && $::instance_conf->get_transfer_default,
305 submit => [ '#form', { action => "transfer_in" } ],
306 checks => [ 'kivi.validate_form', @transfer_qty ],
307 disabled => $::form->{delivered} ? t8('This record has already been delivered.') : undef,
308 only_if => !$is_customer,
311 t8('Transfer in via default'),
312 submit => [ '#form', { action => "transfer_in_default" } ],
313 checks => [ 'kivi.validate_form' ],
314 disabled => $::form->{delivered} ? t8('This record has already been delivered.') : undef,
315 only_if => !$is_customer && $::instance_conf->get_transfer_default,
317 ], # end of combobox "Transfer out"
324 submit => [ '#form', { action => "invoice" } ],
325 disabled => !$::form->{id} ? t8('This record has not been saved yet.') : undef,
329 action => [ t8('Export') ],
332 call => [ 'kivi.SalesPurchase.show_print_dialog' ],
333 checks => [ 'kivi.validate_form' ],
337 call => [ 'kivi.SalesPurchase.show_email_dialog' ],
338 checks => [ 'kivi.validate_form' ],
339 disabled => !$::form->{id} ? t8('This record has not been saved yet.') : undef,
341 ], # end of combobox "Export"
344 action => [ t8('more') ],
347 call => [ 'set_history_window', $::form->{id} * 1, 'id' ],
348 disabled => !$::form->{id} ? t8('This record has not been saved yet.') : undef,
352 call => [ 'follow_up_window' ],
353 disabled => !$::form->{id} ? t8('This record has not been saved yet.') : undef,
355 ], # end if combobox "more"
358 $::request->layout->add_javascripts('kivi.Validator.js');
361 sub setup_do_search_action_bar {
364 for my $bar ($::request->layout->get('actionbar')) {
368 submit => [ '#form' ],
369 accesskey => 'enter',
370 checks => [ 'kivi.validate_form' ],
374 $::request->layout->add_javascripts('kivi.Validator.js');
377 sub setup_do_orders_action_bar {
380 for my $bar ($::request->layout->get('actionbar')) {
384 submit => [ '#form', { action => 'invoice_multi' } ],
385 checks => [ [ 'kivi.check_if_entries_selected', '#form tbody input[type=checkbox]' ] ],
386 accesskey => 'enter',
390 call => [ 'kivi.SalesPurchase.show_print_dialog', 'js:kivi.MassDeliveryOrderPrint.submitMultiOrders' ],
391 checks => [ [ 'kivi.check_if_entries_selected', '#form tbody input[type=checkbox]' ] ],
398 $main::lxdebug->enter_sub();
402 my $form = $main::form;
403 my %myconfig = %main::myconfig;
405 my $class = "SL::DB::" . ($form->{vc} eq 'customer' ? 'Customer' : 'Vendor');
406 $form->{VC_OBJ} = $class->load_cached($form->{ $form->{vc} . '_id' });
408 $form->{CONTACT_OBJ} = $form->{cp_id} ? SL::DB::Contact->load_cached($form->{cp_id}) : undef;
409 my $current_employee = SL::DB::Manager::Employee->current;
410 $form->{employee_id} = $form->{old_employee_id} if $form->{old_employee_id};
411 $form->{salesman_id} = $form->{old_salesman_id} if $form->{old_salesman_id};
412 $form->{employee_id} ||= $current_employee->id;
413 $form->{salesman_id} ||= $current_employee->id;
415 my $vc = $form->{vc} eq "customer" ? "customers" : "vendors";
416 $form->get_lists("price_factors" => "ALL_PRICE_FACTORS",
417 "business_types" => "ALL_BUSINESS_TYPES",
419 $form->{ALL_DEPARTMENTS} = SL::DB::Manager::Department->get_all_sorted;
422 my @old_project_ids = uniq grep { $_ } map { $_ * 1 } ($form->{"globalproject_id"}, map { $form->{"project_id_$_"} } 1..$form->{"rowcount"});
423 my @old_ids_cond = @old_project_ids ? (id => \@old_project_ids) : ();
425 if (($vc eq 'customers') && $::instance_conf->get_customer_projects_only_in_sales) {
428 customer_id => $::form->{customer_id},
429 billable_customer_id => $::form->{customer_id},
434 and => [ active => 1, @customer_cond ],
438 $::form->{ALL_PROJECTS} = SL::DB::Manager::Project->get_all_sorted(query => \@conditions);
439 $::form->{ALL_EMPLOYEES} = SL::DB::Manager::Employee->get_all_sorted(query => [ or => [ id => $::form->{employee_id}, deleted => 0 ] ]);
440 $::form->{ALL_SALESMEN} = SL::DB::Manager::Employee->get_all_sorted(query => [ or => [ id => $::form->{salesman_id}, deleted => 0 ] ]);
441 $::form->{ALL_SHIPTO} = SL::DB::Manager::Shipto->get_all_sorted(query => [
442 or => [ trans_id => $::form->{"$::form->{vc}_id"} * 1, and => [ shipto_id => $::form->{shipto_id} * 1, trans_id => undef ] ]
444 $::form->{ALL_CONTACTS} = SL::DB::Manager::Contact->get_all_sorted(query => [
446 cp_cv_id => $::form->{"$::form->{vc}_id"} * 1,
449 cp_id => $::form->{cp_id} * 1
454 my $dispatch_to_popup = '';
455 if ($form->{resubmit} && ($form->{format} eq "html")) {
456 $dispatch_to_popup = "window.open('about:blank','Beleg'); document.do.target = 'Beleg';";
457 $dispatch_to_popup .= "document.do.submit();";
458 } elsif ($form->{resubmit}) {
459 # emulate click for resubmitting actions
460 $dispatch_to_popup = "document.do.${_}.click(); " for grep { /^action_/ } keys %$form;
462 $::request->{layout}->add_javascripts_inline("\$(function(){$dispatch_to_popup});");
465 $form->{follow_up_trans_info} = $form->{donumber} .'('. $form->{VC_OBJ}->name .')' if $form->{VC_OBJ};
467 $::request->{layout}->use_javascript(map { "${_}.js" } qw(kivi.File kivi.MassDeliveryOrderPrint kivi.SalesPurchase kivi.Part kivi.CustomerVendor kivi.Validator ckeditor/ckeditor ckeditor/adapters/jquery kivi.io));
469 setup_do_action_bar();
472 # Fix für Bug 1082 Erwartet wird: 'abteilungsNAME--abteilungsID'
473 # und Erweiterung für Bug 1760:
474 # Das war leider nur ein Teil-Fix, da das Verhalten den 'Erneuern'-Knopf
475 # nicht überlebt. Konsequent jetzt auf L umgestellt
476 # $ perldoc SL::Template::Plugin::L
477 # Daher entsprechend nur die Anpassung in form_header
478 # und in DO.pm gemacht. 4 Testfälle:
479 # department_id speichern | i.O.
480 # department_id lesen | i.O.
481 # department leer überlebt erneuern | i.O.
482 # department nicht leer überlebt erneuern | i.O.
483 # $main::lxdebug->message(0, 'ABTEILUNGS ID in form?' . $form->{department_id});
484 print $form->parse_html_template('do/form_header');
486 $main::lxdebug->leave_sub();
490 $main::lxdebug->enter_sub();
494 my $form = $main::form;
496 $form->{PRINT_OPTIONS} = setup_sales_purchase_print_options();
497 $form->{ALL_DELIVERY_TERMS} = SL::DB::Manager::DeliveryTerm->get_all_sorted();
499 my $shipto_cvars = SL::DB::Shipto->new->cvars_by_config;
500 foreach my $var (@{ $shipto_cvars }) {
501 my $name = "shiptocvar_" . $var->config->name;
502 $var->value($form->{$name}) if exists $form->{$name};
505 print $form->parse_html_template('do/form_footer',
506 {transfer_default => ($::instance_conf->get_transfer_default),
507 shipto_cvars => $shipto_cvars});
509 $main::lxdebug->leave_sub();
512 sub update_delivery_order {
513 $main::lxdebug->enter_sub();
517 my $form = $main::form;
518 my %myconfig = %main::myconfig;
520 set_headings($form->{"id"} ? "edit" : "add");
522 $form->{insertdate} = SL::DB::DeliveryOrder->new(id => $form->{id})->load->itime_as_date if $form->{id};
527 $payment_id = $form->{payment_id} if $form->{payment_id};
529 my $vc = $form->{vc};
530 if (($form->{"previous_${vc}_id"} || $form->{"${vc}_id"}) != $form->{"${vc}_id"}) {
531 $::form->{salesman_id} = SL::DB::Manager::Employee->current->id if exists $::form->{salesman_id};
533 IS->get_customer(\%myconfig, $form) if $vc eq 'customer';
534 IR->get_vendor(\%myconfig, $form) if $vc eq 'vendor';
537 $form->{discount} = $form->{"$form->{vc}_discount"} if defined $form->{"$form->{vc}_discount"};
538 # Problem: Wenn man ohne Erneuern einen Kunden/Lieferanten
539 # wechselt, wird der entsprechende Kunden/ Lieferantenrabatt
540 # nicht übernommen. Grundproblem: In Commit 82574e78
541 # hab ich aus discount customer_discount und vendor_discount
542 # gemacht und entsprechend an den Oberflächen richtig hin-
543 # geschoben. Die damals bessere Lösung wäre gewesen:
544 # In den Templates nur die hidden für form-discount wieder ein-
545 # setzen dann wäre die Verrenkung jetzt nicht notwendig.
546 # TODO: Ggf. Bugfix 1284, 1575 und 817 wieder zusammenführen
547 # Testfälle: Kunden mit Rabatt 0 -> Rabatt 20 i.O.
548 # Kunde mit Rabatt 20 -> Rabatt 0 i.O.
549 # Kunde mit Rabatt 20 -> Rabatt 5,5 i.O.
550 $form->{payment_id} = $payment_id if $form->{payment_id} eq "";
552 my $i = $form->{rowcount};
554 if ( ($form->{"partnumber_$i"} eq "")
555 && ($form->{"description_$i"} eq "")
556 && ($form->{"partsgroup_$i"} eq "")) {
563 if ($form->{type} eq 'purchase_delivery_order') {
564 IR->retrieve_item(\%myconfig, $form);
567 IS->retrieve_item(\%myconfig, $form);
571 my $rows = scalar @{ $form->{item_list} };
574 $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
575 if( !$form->{"qty_$i"} ) {
576 $form->{"qty_$i"} = 1;
581 select_item(mode => $mode, pre_entered_qty => $form->{"qty_$i"});
582 $::dispatcher->end_request;
586 my $sellprice = $form->parse_amount(\%myconfig, $form->{"sellprice_$i"});
588 map { $form->{"${_}_$i"} = $form->{item_list}[0]{$_} } keys %{ $form->{item_list}[0] };
590 $form->{"marge_price_factor_$i"} = $form->{item_list}->[0]->{price_factor};
593 $form->{"sellprice_$i"} = $sellprice;
595 my $record = _make_record();
596 my $price_source = SL::PriceSource->new(record_item => $record->items->[$i-1], record => $record);
597 my $best_price = $price_source->best_price;
598 my $best_discount = $price_source->best_discount;
601 $::form->{"sellprice_$i"} = $best_price->price;
602 $::form->{"active_price_source_$i"} = $best_price->source;
604 if ($best_discount) {
605 $::form->{"discount_$i"} = $best_discount->discount;
606 $::form->{"active_discount_source_$i"} = $best_discount->source;
610 $form->{"sellprice_$i"} = $form->format_amount(\%myconfig, $form->{"sellprice_$i"});
611 $form->{"lastcost_$i"} = $form->format_amount(\%myconfig, $form->{"lastcost_$i"});
612 $form->{"qty_$i"} = $form->format_amount(\%myconfig, $form->{"qty_$i"});
613 $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"} * 100.0);
620 # ok, so this is a new part
621 # ask if it is a part or service item
623 if ( $form->{"partsgroup_$i"}
624 && ($form->{"partsnumber_$i"} eq "")
625 && ($form->{"description_$i"} eq "")) {
627 $form->{"discount_$i"} = "";
628 $form->{"not_discountable_$i"} = "";
632 $form->{"id_$i"} = 0;
638 $main::lxdebug->leave_sub();
642 $main::lxdebug->enter_sub();
646 my $form = $main::form;
647 my %myconfig = %main::myconfig;
648 my $locale = $main::locale;
650 $form->{vc} = $form->{type} eq 'purchase_delivery_order' ? 'vendor' : 'customer';
652 $form->get_lists("projects" => { "key" => "ALL_PROJECTS",
654 "business_types" => "ALL_BUSINESS_TYPES");
655 $form->{ALL_EMPLOYEES} = SL::DB::Manager::Employee->get_all_sorted(query => [ deleted => 0 ]);
656 $form->{ALL_DEPARTMENTS} = SL::DB::Manager::Department->get_all_sorted;
657 $form->{title} = $locale->text('Delivery Orders');
659 setup_do_search_action_bar();
663 print $form->parse_html_template('do/search');
665 $main::lxdebug->leave_sub();
669 $main::lxdebug->enter_sub();
673 my $form = $main::form;
674 my %myconfig = %main::myconfig;
675 my $locale = $main::locale;
676 my $cgi = $::request->{cgi};
678 $::request->{layout}->use_javascript(map { "${_}.js" } qw(kivi.MassDeliveryOrderPrint kivi.SalesPurchase));
679 ($form->{ $form->{vc} }, $form->{"$form->{vc}_id"}) = split(/--/, $form->{ $form->{vc} });
681 report_generator_set_default_sort('transdate', 1);
685 $form->{rowcount} = scalar @{ $form->{DO} };
688 ids transdate reqdate
690 ordnumber customernumber cusordnumber
691 name employee salesman
692 shipvia globalprojectnumber
693 transaction_description department
698 $form->{l_open} = $form->{l_closed} = "Y" if ($form->{open} && $form->{closed});
699 $form->{l_delivered} = "Y" if ($form->{delivered} && $form->{notdelivered});
701 $form->{title} = $locale->text('Delivery Orders');
703 my $attachment_basename = $form->{vc} eq 'vendor' ? $locale->text('purchase_delivery_order_list') : $locale->text('sales_delivery_order_list');
705 my $report = SL::ReportGenerator->new(\%myconfig, $form);
707 my @hidden_variables = map { "l_${_}" } @columns;
708 push @hidden_variables, $form->{vc}, qw(l_closed l_notdelivered open closed delivered notdelivered donumber ordnumber serialnumber cusordnumber
709 transaction_description transdatefrom transdateto reqdatefrom reqdateto
710 type vc employee_id salesman_id project_id parts_partnumber parts_description
711 insertdatefrom insertdateto business_id all department_id);
713 my $href = build_std_url('action=orders', grep { $form->{$_} } @hidden_variables);
716 'ids' => { raw_header_data => SL::Presenter::Tag::checkbox_tag("", id => "multi_all", checkall => "[data-checkall=1]"), align => 'center' },
717 'transdate' => { 'text' => $locale->text('Delivery Order Date'), },
718 'reqdate' => { 'text' => $locale->text('Reqdate'), },
719 'id' => { 'text' => $locale->text('ID'), },
720 'donumber' => { 'text' => $locale->text('Delivery Order'), },
721 'ordnumber' => { 'text' => $locale->text('Order'), },
722 'customernumber' => { 'text' => $locale->text('Customer Number'), },
723 'cusordnumber' => { 'text' => $locale->text('Customer Order Number'), },
724 'name' => { 'text' => $form->{vc} eq 'customer' ? $locale->text('Customer') : $locale->text('Vendor'), },
725 'employee' => { 'text' => $locale->text('Employee'), },
726 'salesman' => { 'text' => $locale->text('Salesman'), },
727 'shipvia' => { 'text' => $locale->text('Ship via'), },
728 'globalprojectnumber' => { 'text' => $locale->text('Project Number'), },
729 'transaction_description' => { 'text' => $locale->text('Transaction description'), },
730 'open' => { 'text' => $locale->text('Open'), },
731 'delivered' => { 'text' => $locale->text('Delivered'), },
732 'department' => { 'text' => $locale->text('Department'), },
733 'insertdate' => { 'text' => $locale->text('Insert Date'), },
736 foreach my $name (qw(id transdate reqdate donumber ordnumber name employee salesman shipvia transaction_description department insertdate)) {
737 my $sortdir = $form->{sort} eq $name ? 1 - $form->{sortdir} : $form->{sortdir};
738 $column_defs{$name}->{link} = $href . "&sort=$name&sortdir=$sortdir";
741 $form->{"l_type"} = "Y";
742 map { $column_defs{$_}->{visible} = $form->{"l_${_}"} ? 1 : 0 } @columns;
744 $column_defs{ids}->{visible} = 'HTML';
746 $report->set_columns(%column_defs);
747 $report->set_column_order(@columns);
749 $report->set_export_options('orders', @hidden_variables, qw(sort sortdir));
751 $report->set_sort_indicator($form->{sort}, $form->{sortdir});
754 if ($form->{customer}) {
755 push @options, $locale->text('Customer') . " : $form->{customer}";
757 if ($form->{vendor}) {
758 push @options, $locale->text('Vendor') . " : $form->{vendor}";
760 if ($form->{cp_name}) {
761 push @options, $locale->text('Contact Person') . " : $form->{cp_name}";
763 if ($form->{department_id}) {
764 push @options, $locale->text('Department') . " : " . SL::DB::Department->new(id => $form->{department_id})->load->description;
766 if ($form->{donumber}) {
767 push @options, $locale->text('Delivery Order Number') . " : $form->{donumber}";
769 if ($form->{ordnumber}) {
770 push @options, $locale->text('Order Number') . " : $form->{ordnumber}";
772 push @options, $locale->text('Serial Number') . " : $form->{serialnumber}" if $form->{serialnumber};
773 if ($form->{business_id}) {
774 my $vc_type_label = $form->{vc} eq 'customer' ? $locale->text('Customer type') : $locale->text('Vendor type');
775 push @options, $vc_type_label . " : " . SL::DB::Business->new(id => $form->{business_id})->load->description;
777 if ($form->{transaction_description}) {
778 push @options, $locale->text('Transaction description') . " : $form->{transaction_description}";
780 if ($form->{parts_description}) {
781 push @options, $locale->text('Part Description') . " : $form->{parts_description}";
783 if ($form->{parts_partnumber}) {
784 push @options, $locale->text('Part Number') . " : $form->{parts_partnumber}";
786 if ( $form->{transdatefrom} or $form->{transdateto} ) {
787 push @options, $locale->text('Delivery Order Date');
788 push @options, $locale->text('From') . " " . $locale->date(\%myconfig, $form->{transdatefrom}, 1) if $form->{transdatefrom};
789 push @options, $locale->text('Bis') . " " . $locale->date(\%myconfig, $form->{transdateto}, 1) if $form->{transdateto};
791 if ( $form->{reqdatefrom} or $form->{reqdateto} ) {
792 push @options, $locale->text('Reqdate');
793 push @options, $locale->text('From') . " " . $locale->date(\%myconfig, $form->{reqdatefrom}, 1) if $form->{reqdatefrom};
794 push @options, $locale->text('Bis') . " " . $locale->date(\%myconfig, $form->{reqdateto}, 1) if $form->{reqdateto};
796 if ( $form->{insertdatefrom} or $form->{insertdateto} ) {
797 push @options, $locale->text('Insert Date');
798 push @options, $locale->text('From') . " " . $locale->date(\%myconfig, $form->{insertdatefrom}, 1) if $form->{insertdatefrom};
799 push @options, $locale->text('Bis') . " " . $locale->date(\%myconfig, $form->{insertdateto}, 1) if $form->{insertdateto};
802 push @options, $locale->text('Open');
804 if ($form->{closed}) {
805 push @options, $locale->text('Closed');
807 if ($form->{delivered}) {
808 push @options, $locale->text('Delivered');
810 if ($form->{notdelivered}) {
811 push @options, $locale->text('Not delivered');
813 push @options, $locale->text('Quick Search') . " : $form->{all}" if $form->{all};
815 my $pr = SL::DB::Manager::Printer->find_by(
816 printer_description => $::locale->text("sales_delivery_order_printer"));
818 $form->{printer_id} = $pr->id;
821 my $print_options = SL::Helper::PrintOptions->get_print_options(
823 hide_language_id => 1,
829 $report->set_options('top_info_text' => join("\n", @options),
830 'raw_top_info_text' => $form->parse_html_template('do/orders_top'),
831 'raw_bottom_info_text' => $form->parse_html_template('do/orders_bottom', { print_options => $print_options }),
832 'output_format' => 'HTML',
833 'title' => $form->{title},
834 'attachment_basename' => $attachment_basename . strftime('_%Y%m%d', localtime time),
836 $report->set_options_from_form();
837 $locale->set_numberformat_wo_thousands_separator(\%myconfig) if lc($report->{options}->{output_format}) eq 'csv';
839 # add sort and escape callback, this one we use for the add sub
840 $form->{callback} = $href .= "&sort=$form->{sort}";
842 # escape callback for href
843 my $callback = $form->escape($href);
845 my $edit_url = build_std_url('action=edit', 'type', 'vc');
846 my $edit_order_url = ($::instance_conf->get_feature_experimental_order)
847 ? build_std_url('script=controller.pl', 'action=Order/edit', 'type=' . ($form->{type} eq 'sales_delivery_order' ? 'sales_order' : 'purchase_order'))
848 : build_std_url('script=oe.pl', 'action=edit', 'type=' . ($form->{type} eq 'sales_delivery_order' ? 'sales_order' : 'purchase_order'));
852 foreach my $dord (@{ $form->{DO} }) {
853 $dord->{open} = $dord->{closed} ? $locale->text('No') : $locale->text('Yes');
854 $dord->{delivered} = $dord->{delivered} ? $locale->text('Yes') : $locale->text('No');
856 my $row = { map { $_ => { 'data' => $dord->{$_} } } @columns };
858 my $ord_id = $dord->{id};
860 'raw_data' => $cgi->hidden('-name' => "trans_id_${idx}", '-value' => $ord_id)
861 . $cgi->checkbox('-name' => "multi_id_${idx}",' id' => "multi_id_id_".$ord_id, '-value' => 1, 'data-checkall' => 1, '-label' => ''),
862 'valign' => 'center',
866 $row->{donumber}->{link} = $edit_url . "&id=" . E($dord->{id}) . "&callback=${callback}";
867 $row->{ordnumber}->{link} = $edit_order_url . "&id=" . E($dord->{oe_id}) . "&callback=${callback}" if $dord->{oe_id};
868 $report->add_data($row);
873 setup_do_orders_action_bar();
875 $report->generate_with_headers();
877 $main::lxdebug->leave_sub();
881 $main::lxdebug->enter_sub();
887 my $form = $main::form;
888 my %myconfig = %main::myconfig;
889 my $locale = $main::locale;
891 $form->mtime_ischanged('delivery_orders');
893 $form->{defaultcurrency} = $form->get_default_currency(\%myconfig);
895 $form->isblank("transdate", $locale->text('Delivery Order Date missing!'));
897 $form->{donumber} =~ s/^\s*//g;
898 $form->{donumber} =~ s/\s*$//g;
900 my $msg = ucfirst $form->{vc};
901 $form->isblank($form->{vc} . "_id", $locale->text($msg . " missing!"));
903 # $locale->text('Customer missing!');
904 # $locale->text('Vendor missing!');
906 remove_emptied_rows();
909 # if the name changed get new values
910 my $vc = $form->{vc};
911 if (($form->{"previous_${vc}_id"} || $form->{"${vc}_id"}) != $form->{"${vc}_id"}) {
912 $::form->{salesman_id} = SL::DB::Manager::Employee->current->id if exists $::form->{salesman_id};
914 IS->get_customer(\%myconfig, $form) if $vc eq 'customer';
915 IR->get_vendor(\%myconfig, $form) if $vc eq 'vendor';
918 $::dispatcher->end_request;
921 $form->{id} = 0 if $form->{saveasnew};
925 if(!exists $form->{addition}) {
926 $form->{snumbers} = qq|donumber_| . $form->{donumber};
927 $form->{addition} = "SAVED";
930 # /saving the history
932 $form->{simple_save} = 1;
933 if (!$params{no_redirect} && !$form->{print_and_save}) {
934 delete @{$form}{ary_diff([keys %{ $form }], [qw(login id script type cursor_fokus)])};
936 $::dispatcher->end_request;
938 $main::lxdebug->leave_sub();
942 $main::lxdebug->enter_sub();
946 my $form = $main::form;
947 my %myconfig = %main::myconfig;
948 my $locale = $main::locale;
950 if ($ret = DO->delete()) {
952 if(!exists $form->{addition}) {
953 $form->{snumbers} = qq|donumber_| . $form->{donumber};
954 $form->{addition} = "DELETED";
957 # /saving the history
959 $form->info($locale->text('Delivery Order deleted!'));
960 $::dispatcher->end_request;
963 $form->error($locale->text('Cannot delete delivery order!') . $ret);
965 $main::lxdebug->leave_sub();
969 $main::lxdebug->enter_sub();
971 my $form = $main::form;
972 my %myconfig = %main::myconfig;
973 my $locale = $main::locale;
976 $form->mtime_ischanged('delivery_orders');
978 $main::auth->assert($form->{type} eq 'purchase_delivery_order' ? 'vendor_invoice_edit' : 'invoice_edit');
980 $form->{convert_from_do_ids} = $form->{id};
981 # if we have a reqdate (Liefertermin), this is definetely the preferred
982 # deliverydate for invoices
983 $form->{deliverydate} = $form->{reqdate} || $form->{transdate};
984 $form->{transdate} = $form->{invdate} = $form->current_date(\%myconfig);
985 $form->{duedate} = $form->current_date(\%myconfig, $form->{invdate}, $form->{terms} * 1);
986 $form->{defaultcurrency} = $form->get_default_currency(\%myconfig);
990 delete @{$form}{qw(id closed delivered)};
992 my ($script, $buysell);
993 if ($form->{type} eq 'purchase_delivery_order') {
994 $form->{title} = $locale->text('Add Vendor Invoice');
995 $form->{script} = 'ir.pl';
1000 $form->{title} = $locale->text('Add Sales Invoice');
1001 $form->{script} = 'is.pl';
1006 for my $i (1 .. $form->{rowcount}) {
1007 map { $form->{"${_}_${i}"} = $form->parse_amount(\%myconfig, $form->{"${_}_${i}"}) if $form->{"${_}_${i}"} } qw(ship qty sellprice lastcost basefactor discount);
1009 # adds a customer/vendor discount, unless we have a workflow case
1010 # CAVEAT: has to be done, after the above parse_amount
1011 unless ($form->{"ordnumber"}) {
1012 if ($form->{discount}) { # Falls wir einen Lieferanten-/Kundenrabatt haben
1013 # und rabattfähig sind, dann
1014 unless ($form->{"not_discountable_$i"}) {
1015 $form->{"discount_$i"} = $form->{discount}*100; # ... nehmen wir diesen Rabatt
1019 $form->{"donumber_$i"} = $form->{donumber};
1020 $form->{"converted_from_delivery_order_items_id_$i"} = delete $form->{"delivery_order_items_id_$i"};
1023 $form->{type} = "invoice";
1026 $main::locale = Locale->new("$myconfig{countrycode}", "$script");
1027 $locale = $main::locale;
1029 require "bin/mozilla/$form->{script}";
1031 my $currency = $form->{currency};
1034 if ($form->{ordnumber}) {
1035 require SL::DB::Order;
1036 my $vc_id = $form->{type} =~ /^sales/ ? 'customer_id' : 'vendor_id';
1037 if (my $order = SL::DB::Manager::Order->find_by(ordnumber => $form->{ordnumber}, $vc_id => $form->{"$vc_id"})) {
1039 $form->{orddate} = $order->transdate_as_date;
1040 $form->{$_} = $order->$_ for qw(payment_id salesman_id taxzone_id quonumber);
1044 $form->{currency} = $currency;
1045 $form->{exchangerate} = "";
1046 $form->{forex} = $form->check_exchangerate(\%myconfig, $form->{currency}, $form->{invdate}, $buysell);
1047 $form->{exchangerate} = $form->{forex} if ($form->{forex});
1052 for my $i (1 .. $form->{rowcount}) {
1053 $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"});
1055 my ($dec) = ($form->{"sellprice_$i"} =~ /\.(\d+)/);
1057 my $decimalplaces = ($dec > 2) ? $dec : 2;
1059 # copy delivery date from reqdate for order -> invoice conversion
1060 $form->{"deliverydate_$i"} = $form->{"reqdate_$i"}
1061 unless $form->{"deliverydate_$i"};
1064 $form->{"sellprice_$i"} =
1065 $form->format_amount(\%myconfig, $form->{"sellprice_$i"},
1068 $form->{"lastcost_$i"} =
1069 $form->format_amount(\%myconfig, $form->{"lastcost_$i"},
1072 (my $dec_qty) = ($form->{"qty_$i"} =~ /\.(\d+)/);
1073 $dec_qty = length $dec_qty;
1075 $form->format_amount(\%myconfig, $form->{"qty_$i"}, $dec_qty);
1081 $main::lxdebug->leave_sub();
1085 $main::lxdebug->enter_sub();
1087 my $form = $main::form;
1088 my %myconfig = %main::myconfig;
1089 my $locale = $main::locale;
1092 $main::auth->assert($form->{type} eq 'sales_delivery_order' ? 'invoice_edit' : 'vendor_invoice_edit');
1094 my @do_ids = map { $form->{"trans_id_$_"} } grep { $form->{"multi_id_$_"} } (1..$form->{rowcount});
1096 if (!scalar @do_ids) {
1097 $form->show_generic_error($locale->text('You have not selected any delivery order.'));
1100 map { delete $form->{$_} } grep { m/^(?:trans|multi)_id_\d+/ } keys %{ $form };
1102 if (!DO->retrieve('vc' => $form->{vc}, 'ids' => \@do_ids)) {
1103 $form->show_generic_error($form->{vc} eq 'customer' ?
1104 $locale->text('You cannot create an invoice for delivery orders for different customers.') :
1105 $locale->text('You cannot create an invoice for delivery orders from different vendors.'),
1106 'back_button' => 1);
1109 my $source_type = $form->{type};
1110 $form->{convert_from_do_ids} = join ' ', @do_ids;
1111 # bei der auswahl von mehreren Lieferscheinen fuer eine Rechnung, die einfach in donumber_array
1112 # zwischenspeichern (DO.pm) und als ' '-separierte Liste wieder zurueckschreiben
1113 # Hinweis: delete gibt den wert zurueck und loescht danach das element (nett und einfach)
1114 # $shell: perldoc perlunc; /delete EXPR
1115 $form->{donumber} = delete $form->{donumber_array};
1116 $form->{ordnumber} = delete $form->{ordnumber_array};
1117 $form->{cusordnumber} = delete $form->{cusordnumber_array};
1118 $form->{deliverydate} = $form->{transdate};
1119 $form->{transdate} = $form->current_date(\%myconfig);
1120 $form->{duedate} = $form->current_date(\%myconfig, $form->{invdate}, $form->{terms} * 1);
1121 $form->{type} = "invoice";
1122 $form->{closed} = 0;
1123 $form->{defaultcurrency} = $form->get_default_currency(\%myconfig);
1125 my ($script, $buysell);
1126 if ($source_type eq 'purchase_delivery_order') {
1127 $form->{title} = $locale->text('Add Vendor Invoice');
1128 $form->{script} = 'ir.pl';
1133 $form->{title} = $locale->text('Add Sales Invoice');
1134 $form->{script} = 'is.pl';
1139 map { delete $form->{$_} } qw(id subject message cc bcc printed emailed queued);
1141 # get vendor or customer discount
1143 my $saved_form = save_form();
1144 if ($form->{vc} eq 'vendor') {
1145 IR->get_vendor(\%myconfig, \%$form);
1146 $vc_discount = $form->{vendor_discount};
1148 IS->get_customer(\%myconfig, \%$form);
1149 $vc_discount = $form->{customer_discount};
1151 # use payment terms from customer or vendor
1152 restore_form($saved_form,0,qw(payment_id));
1154 $form->{rowcount} = 0;
1155 foreach my $ref (@{ $form->{form_details} }) {
1156 $form->{rowcount}++;
1157 $ref->{reqdate} ||= $ref->{dord_transdate}; # copy transdates into each invoice row
1158 map { $form->{"${_}_$form->{rowcount}"} = $ref->{$_} } keys %{ $ref };
1159 map { $form->{"${_}_$form->{rowcount}"} = $form->format_amount(\%myconfig, $ref->{$_}) } qw(qty sellprice lastcost);
1160 $form->{"converted_from_delivery_order_items_id_$form->{rowcount}"} = delete $form->{"delivery_order_items_id_$form->{rowcount}"};
1162 if ($vc_discount){ # falls wir einen Lieferanten/Kundenrabatt haben
1163 # und keinen anderen discount wert an $i ...
1164 $form->{"discount_$form->{rowcount}"} ||= $vc_discount; # ... nehmen wir diesen Rabatt
1167 $form->{"discount_$form->{rowcount}"} = $form->{"discount_$form->{rowcount}"} * 100; #s.a. Bug 1151
1168 # Anm.: Eine Änderung des discounts in der SL/DO.pm->retrieve (select (doi.discount * 100) as discount) ergibt in psql einen
1169 # Wert von 10.0000001490116. Ferner ist der Rabatt in der Rechnung dann bei 1.0 (?). Deswegen lasse ich das hier. jb 10.10.09
1171 $form->{"discount_$form->{rowcount}"} = $form->format_amount(\%myconfig, $form->{"discount_$form->{rowcount}"});
1173 delete $form->{form_details};
1175 $locale = Locale->new("$myconfig{countrycode}", "$script");
1177 require "bin/mozilla/$form->{script}";
1184 $main::lxdebug->leave_sub();
1188 $main::lxdebug->enter_sub();
1192 my $form = $main::form;
1194 $form->{saveasnew} = 1;
1195 $form->{closed} = 0;
1196 $form->{delivered} = 0;
1197 map { delete $form->{$_} } qw(printed emailed queued);
1198 delete @{ $form }{ grep { m/^stock_(?:in|out)_\d+/ } keys %{ $form } };
1199 $form->{"converted_from_delivery_order_items_id_$_"} = delete $form->{"delivery_order_items_id_$_"} for 1 .. $form->{"rowcount"};
1200 # Let kivitendo assign a new order number if the user hasn't changed the
1201 # previous one. If it has been changed manually then use it as-is.
1202 $form->{donumber} =~ s/^\s*//g;
1203 $form->{donumber} =~ s/\s*$//g;
1204 if ($form->{saved_donumber} && ($form->{saved_donumber} eq $form->{donumber})) {
1205 delete($form->{donumber});
1210 $main::lxdebug->leave_sub();
1213 sub calculate_stock_in_out {
1214 $main::lxdebug->enter_sub();
1216 my $form = $main::form;
1220 if (!$form->{"id_${i}"}) {
1221 $main::lxdebug->leave_sub();
1225 my $all_units = AM->retrieve_all_units();
1227 my $in_out = $form->{type} =~ /^sales/ ? 'out' : 'in';
1228 my $sinfo = DO->unpack_stock_information('packed' => $form->{"stock_${in_out}_${i}"});
1230 my $do_qty = AM->sum_with_unit($::form->{"qty_$i"}, $::form->{"unit_$i"});
1231 my $sum = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $sinfo });
1232 my $matches = $do_qty == $sum;
1234 my $content = $form->format_amount_units('amount' => $sum * 1,
1235 'part_unit' => $form->{"partunit_$i"},
1236 'amount_unit' => $all_units->{$form->{"partunit_$i"}}->{base_unit},
1237 'conv_units' => 'convertible_not_smaller',
1239 $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="?">|;
1241 $main::lxdebug->leave_sub();
1246 sub get_basic_bin_wh_info {
1247 $main::lxdebug->enter_sub();
1249 my $stock_info = shift;
1251 my $form = $main::form;
1253 foreach my $sinfo (@{ $stock_info }) {
1254 next unless ($sinfo->{bin_id});
1256 my $bin_info = WH->get_basic_bin_info('id' => $sinfo->{bin_id});
1257 map { $sinfo->{"${_}_description"} = $sinfo->{"${_}description"} = $bin_info->{"${_}_description"} } qw(bin warehouse);
1260 $main::lxdebug->leave_sub();
1263 sub stock_in_out_form {
1264 $main::lxdebug->enter_sub();
1266 my $form = $main::form;
1268 if ($form->{in_out} eq 'out') {
1274 $main::lxdebug->leave_sub();
1277 sub redo_stock_info {
1278 $main::lxdebug->enter_sub();
1282 my $form = $main::form;
1284 my @non_empty = grep { $_->{qty} } @{ $params{stock_info} };
1286 if ($params{add_empty_row}) {
1288 'warehouse_id' => scalar(@non_empty) ? $non_empty[-1]->{warehouse_id} : undef,
1289 'bin_id' => scalar(@non_empty) ? $non_empty[-1]->{bin_id} : undef,
1293 @{ $params{stock_info} } = @non_empty;
1295 $main::lxdebug->leave_sub();
1298 sub update_stock_in {
1299 $main::lxdebug->enter_sub();
1301 my $form = $main::form;
1302 my %myconfig = %main::myconfig;
1304 my $stock_info = [];
1306 foreach my $i (1..$form->{rowcount}) {
1307 $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
1308 push @{ $stock_info }, { map { $_ => $form->{"${_}_${i}"} } qw(warehouse_id bin_id chargenumber
1309 bestbefore qty unit delivery_order_items_stock_id) };
1312 display_stock_in_form($stock_info);
1314 $main::lxdebug->leave_sub();
1318 $main::lxdebug->enter_sub();
1320 my $form = $main::form;
1322 my $stock_info = DO->unpack_stock_information('packed' => $form->{stock});
1324 display_stock_in_form($stock_info);
1326 $main::lxdebug->leave_sub();
1329 sub display_stock_in_form {
1330 $main::lxdebug->enter_sub();
1332 my $stock_info = shift;
1334 my $form = $main::form;
1335 my %myconfig = %main::myconfig;
1336 my $locale = $main::locale;
1338 $form->{title} = $locale->text('Stock');
1340 my $part_info = IC->get_basic_part_info('id' => $form->{parts_id});
1342 # Standardlagerplatz für Standard-Auslagern verwenden, falls keiner für die Ware explizit definiert wurde
1343 if ($::instance_conf->get_transfer_default_use_master_default_bin) {
1344 $part_info->{warehouse_id} ||= $::instance_conf->get_warehouse_id;
1345 $part_info->{bin_id} ||= $::instance_conf->get_bin_id;
1348 my $units = AM->retrieve_units(\%myconfig, $form);
1349 # der zweite Parameter von unit_select_data gibt den default-Namen (selected) vor
1350 my $units_data = AM->unit_select_data($units, $form->{do_unit}, undef, $part_info->{unit});
1352 $form->get_lists('warehouses' => { 'key' => 'WAREHOUSES',
1353 'bins' => 'BINS' });
1355 redo_stock_info('stock_info' => $stock_info, 'add_empty_row' => !$form->{delivered});
1357 get_basic_bin_wh_info($stock_info);
1359 $form->header(no_layout => 1);
1360 print $form->parse_html_template('do/stock_in_form', { 'UNITS' => $units_data,
1361 'STOCK_INFO' => $stock_info,
1362 'PART_INFO' => $part_info, });
1364 $main::lxdebug->leave_sub();
1367 sub _stock_in_out_set_qty_display {
1368 my $stock_info = shift;
1370 my $all_units = AM->retrieve_all_units();
1371 my $sum = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
1372 $form->{qty_display} = $form->format_amount_units(amount => $sum * 1,
1373 part_unit => $form->{partunit},
1374 amount_unit => $all_units->{ $form->{partunit} }->{base_unit},
1375 conv_units => 'convertible_not_smaller',
1380 $main::lxdebug->enter_sub();
1382 my $form = $main::form;
1383 my %myconfig = %main::myconfig;
1385 my $stock_info = [];
1387 foreach my $i (1..$form->{rowcount}) {
1388 $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
1390 next if ($form->{"qty_$i"} <= 0);
1392 push @{ $stock_info }, { map { $_ => $form->{"${_}_${i}"} } qw(delivery_order_items_stock_id warehouse_id bin_id chargenumber bestbefore qty unit) };
1395 $form->{stock} = SL::YAML::Dump($stock_info);
1397 _stock_in_out_set_qty_display($stock_info);
1399 my $do_qty = AM->sum_with_unit($::form->parse_amount(\%::myconfig, $::form->{do_qty}), $::form->{do_unit});
1400 my $transfer_qty = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
1403 print $form->parse_html_template('do/set_stock_in_out', {
1404 qty_matches => $do_qty == $transfer_qty,
1407 $main::lxdebug->leave_sub();
1410 sub stock_out_form {
1411 $main::lxdebug->enter_sub();
1413 my $form = $main::form;
1414 my %myconfig = %main::myconfig;
1415 my $locale = $main::locale;
1417 $form->{title} = $locale->text('Release From Stock');
1419 my $part_info = IC->get_basic_part_info('id' => $form->{parts_id});
1421 my $units = AM->retrieve_units(\%myconfig, $form);
1422 my $units_data = AM->unit_select_data($units, undef, undef, $part_info->{unit});
1424 my @contents = DO->get_item_availability('parts_id' => $form->{parts_id});
1426 my $stock_info = DO->unpack_stock_information('packed' => $form->{stock});
1428 if (!$form->{delivered}) {
1429 foreach my $row (@contents) {
1430 $row->{available_qty} = $form->format_amount_units('amount' => $row->{qty} * 1,
1431 'part_unit' => $part_info->{unit},
1432 'conv_units' => 'convertible_not_smaller',
1435 foreach my $sinfo (@{ $stock_info }) {
1436 next if (($row->{bin_id} != $sinfo->{bin_id}) ||
1437 ($row->{warehouse_id} != $sinfo->{warehouse_id}) ||
1438 ($row->{chargenumber} ne $sinfo->{chargenumber}) ||
1439 ($row->{bestbefore} ne $sinfo->{bestbefore}));
1441 map { $row->{"stock_$_"} = $sinfo->{$_} } qw(qty unit error delivery_order_items_stock_id);
1446 get_basic_bin_wh_info($stock_info);
1448 foreach my $sinfo (@{ $stock_info }) {
1449 map { $sinfo->{"stock_$_"} = $sinfo->{$_} } qw(qty unit);
1453 $form->header(no_layout => 1);
1454 print $form->parse_html_template('do/stock_out_form', { 'UNITS' => $units_data,
1455 'WHCONTENTS' => $form->{delivered} ? $stock_info : \@contents,
1456 'PART_INFO' => $part_info, });
1458 $main::lxdebug->leave_sub();
1462 $main::lxdebug->enter_sub();
1464 my $form = $main::form;
1465 my %myconfig = %main::myconfig;
1466 my $locale = $main::locale;
1468 my $stock_info = [];
1470 foreach my $i (1 .. $form->{rowcount}) {
1471 $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
1473 next if ($form->{"qty_$i"} <= 0);
1475 push @{ $stock_info }, {
1476 'warehouse_id' => $form->{"warehouse_id_$i"},
1477 'bin_id' => $form->{"bin_id_$i"},
1478 'chargenumber' => $form->{"chargenumber_$i"},
1479 'bestbefore' => $form->{"bestbefore_$i"},
1480 'qty' => $form->{"qty_$i"},
1481 'unit' => $form->{"unit_$i"},
1483 'delivery_order_items_stock_id' => $form->{"delivery_order_items_stock_id_$i"},
1487 my @errors = DO->check_stock_availability('requests' => $stock_info,
1488 'parts_id' => $form->{parts_id});
1490 $form->{stock} = SL::YAML::Dump($stock_info);
1493 $form->{ERRORS} = [];
1494 map { push @{ $form->{ERRORS} }, $locale->text('Error in row #1: The quantity you entered is bigger than the stocked quantity.', $_->{row}); } @errors;
1495 stock_in_out_form();
1498 _stock_in_out_set_qty_display($stock_info);
1500 my $do_qty = AM->sum_with_unit($::form->parse_amount(\%::myconfig, $::form->{do_qty}), $::form->{do_unit});
1501 my $transfer_qty = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
1504 print $form->parse_html_template('do/set_stock_in_out', {
1505 qty_matches => $do_qty == $transfer_qty,
1509 $main::lxdebug->leave_sub();
1513 $main::lxdebug->enter_sub();
1515 my $form = $main::form;
1516 my %myconfig = %main::myconfig;
1517 my $locale = $main::locale;
1519 if ($form->{id} && DO->is_marked_as_delivered(id => $form->{id})) {
1520 $form->show_generic_error($locale->text('The parts for this delivery order have already been transferred in.'));
1523 save(no_redirect => 1);
1525 my @part_ids = map { $form->{"id_${_}"} } grep { $form->{"id_${_}"} && $form->{"stock_in_${_}"} } (1 .. $form->{rowcount});
1529 my $units = AM->retrieve_units(\%myconfig, $form);
1530 my %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
1533 $form->{ERRORS} = [];
1535 foreach my $i (1 .. $form->{rowcount}) {
1536 next unless ($form->{"id_$i"} && $form->{"stock_in_$i"});
1538 my $row_sum_base_qty = 0;
1539 my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
1541 foreach my $request (@{ DO->unpack_stock_information('packed' => $form->{"stock_in_$i"}) }) {
1542 $request->{parts_id} = $form->{"id_$i"};
1543 $row_sum_base_qty += $request->{qty} * $units->{$request->{unit}}->{factor} / $base_unit_factor;
1545 $request->{project_id} = $form->{"project_id_$i"} || $form->{globalproject_id};
1547 push @all_requests, $request;
1550 next if (0 == $row_sum_base_qty);
1552 my $do_base_qty = $form->parse_amount(\%myconfig, $form->{"qty_$i"}) * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
1554 # if ($do_base_qty != $row_sum_base_qty) {
1555 # push @{ $form->{ERRORS} }, $locale->text('Error in position #1: You must either assign no stock at all or the full quantity of #2 #3.',
1556 # $i, $form->{"qty_$i"}, $form->{"unit_$i"});
1560 if (@{ $form->{ERRORS} }) {
1561 push @{ $form->{ERRORS} }, $locale->text('The delivery order has not been marked as delivered. The warehouse contents have not changed.');
1563 set_headings('edit');
1565 $main::lxdebug->leave_sub();
1567 $::dispatcher->end_request;
1571 DO->transfer_in_out('direction' => 'in',
1572 'requests' => \@all_requests);
1574 SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
1576 $form->{callback} = 'do.pl?action=edit&type=purchase_delivery_order&id=' . $form->escape($form->{id});
1579 $main::lxdebug->leave_sub();
1583 $main::lxdebug->enter_sub();
1585 my $form = $main::form;
1586 my %myconfig = %main::myconfig;
1587 my $locale = $main::locale;
1589 if ($form->{id} && DO->is_marked_as_delivered(id => $form->{id})) {
1590 $form->show_generic_error($locale->text('The parts for this delivery order have already been transferred out.'));
1593 save(no_redirect => 1);
1595 my @part_ids = map { $form->{"id_${_}"} } grep { $form->{"id_${_}"} && $form->{"stock_out_${_}"} } (1 .. $form->{rowcount});
1599 my $units = AM->retrieve_units(\%myconfig, $form);
1600 my %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
1603 $form->{ERRORS} = [];
1605 foreach my $i (1 .. $form->{rowcount}) {
1606 next unless ($form->{"id_$i"} && $form->{"stock_out_$i"});
1608 my $row_sum_base_qty = 0;
1609 my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
1611 foreach my $request (@{ DO->unpack_stock_information('packed' => $form->{"stock_out_$i"}) }) {
1612 $request->{parts_id} = $form->{"id_$i"};
1613 $request->{base_qty} = $request->{qty} * $units->{$request->{unit}}->{factor} / $base_unit_factor;
1614 $request->{project_id} = $form->{"project_id_$i"} ? $form->{"project_id_$i"} : $form->{globalproject_id};
1616 my $map_key = join '--', ($form->{"id_$i"}, @{$request}{qw(warehouse_id bin_id chargenumber bestbefore)});
1618 $request_map{$map_key} ||= $request;
1619 $request_map{$map_key}->{sum_base_qty} ||= 0;
1620 $request_map{$map_key}->{sum_base_qty} += $request->{base_qty};
1621 $row_sum_base_qty += $request->{base_qty};
1623 push @all_requests, $request;
1626 next if (0 == $row_sum_base_qty);
1628 my $do_base_qty = $form->{"qty_$i"} * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
1630 # if ($do_base_qty != $row_sum_base_qty) {
1631 # push @{ $form->{ERRORS} }, $locale->text('Error in position #1: You must either assign no transfer at all or the full quantity of #2 #3.',
1632 # $i, $form->{"qty_$i"}, $form->{"unit_$i"});
1637 my @bin_ids = map { $_->{bin_id} } values %request_map;
1638 my %bin_info_map = WH->get_basic_bin_info('id' => \@bin_ids);
1639 my @contents = DO->get_item_availability('parts_id' => \@part_ids);
1641 foreach my $inv (@contents) {
1642 my $map_key = join '--', @{$inv}{qw(parts_id warehouse_id bin_id chargenumber bestbefore)};
1644 next unless ($request_map{$map_key});
1646 my $request = $request_map{$map_key};
1647 $request->{ok} = $request->{sum_base_qty} <= $inv->{qty};
1650 foreach my $request (values %request_map) {
1651 next if ($request->{ok});
1653 my $pinfo = $part_info_map{$request->{parts_id}};
1654 my $binfo = $bin_info_map{$request->{bin_id}};
1656 if ($::instance_conf->get_show_bestbefore) {
1657 push @{ $form->{ERRORS} }, $locale->text("There is not enough available of '#1' at warehouse '#2', bin '#3', #4, #5, for the transfer of #6.",
1658 $pinfo->{description},
1659 $binfo->{warehouse_description},
1660 $binfo->{bin_description},
1661 $request->{chargenumber} ? $locale->text('chargenumber #1', $request->{chargenumber}) : $locale->text('no chargenumber'),
1662 $request->{bestbefore} ? $locale->text('bestbefore #1', $request->{bestbefore}) : $locale->text('no bestbefore'),
1663 $form->format_amount_units('amount' => $request->{sum_base_qty},
1664 'part_unit' => $pinfo->{unit},
1665 'conv_units' => 'convertible_not_smaller'));
1667 push @{ $form->{ERRORS} }, $locale->text("There is not enough available of '#1' at warehouse '#2', bin '#3', #4, for the transfer of #5.",
1668 $pinfo->{description},
1669 $binfo->{warehouse_description},
1670 $binfo->{bin_description},
1671 $request->{chargenumber} ? $locale->text('chargenumber #1', $request->{chargenumber}) : $locale->text('no chargenumber'),
1672 $form->format_amount_units('amount' => $request->{sum_base_qty},
1673 'part_unit' => $pinfo->{unit},
1674 'conv_units' => 'convertible_not_smaller'));
1679 if (@{ $form->{ERRORS} }) {
1680 push @{ $form->{ERRORS} }, $locale->text('The delivery order has not been marked as delivered. The warehouse contents have not changed.');
1682 set_headings('edit');
1684 $main::lxdebug->leave_sub();
1686 $::dispatcher->end_request;
1689 DO->transfer_in_out('direction' => 'out',
1690 'requests' => \@all_requests);
1692 SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
1694 $form->{callback} = 'do.pl?action=edit&type=sales_delivery_order&id=' . $form->escape($form->{id});
1697 $main::lxdebug->leave_sub();
1701 $main::lxdebug->enter_sub();
1703 my $form = $main::form;
1705 DO->close_orders('ids' => [ $form->{id} ]);
1707 $form->{closed} = 1;
1711 $main::lxdebug->leave_sub();
1715 $::lxdebug->enter_sub;
1717 $::auth->assert('purchase_delivery_order_edit | sales_delivery_order_edit');
1720 retrieve_partunits();
1722 my $new_rowcount = $::form->{"rowcount"} * 1 + 1;
1723 $::form->{"project_id_${new_rowcount}"} = $::form->{"globalproject_id"};
1725 $::form->language_payment(\%::myconfig);
1727 Common::webdav_folder($::form);
1730 display_row(++$::form->{rowcount});
1733 $::lxdebug->leave_sub;
1737 call_sub($main::form->{yes_nextsub});
1741 call_sub($main::form->{no_nextsub});
1745 call_sub($main::form->{update_nextsub} || $main::form->{nextsub} || 'update_delivery_order');
1749 my $form = $main::form;
1750 my $locale = $main::locale;
1752 foreach my $action (qw(update print save transfer_out transfer_out_default sort
1753 transfer_in transfer_in_default mark_closed save_as_new invoice delete)) {
1754 if ($form->{"action_${action}"}) {
1760 $form->error($locale->text('No action defined.'));
1763 sub transfer_out_default {
1764 $main::lxdebug->enter_sub();
1766 my $form = $main::form;
1768 transfer_in_out_default('direction' => 'out');
1770 $main::lxdebug->leave_sub();
1773 sub transfer_in_default {
1774 $main::lxdebug->enter_sub();
1776 my $form = $main::form;
1778 transfer_in_out_default('direction' => 'in');
1780 $main::lxdebug->leave_sub();
1783 # Falls das Standardlagerverfahren aktiv ist, wird
1784 # geprüft, ob alle Standardlagerplätze für die Auslager-
1785 # artikel vorhanden sind UND ob die Warenmenge ausreicht zum
1786 # Auslagern. Falls nicht wird entsprechend eine Fehlermeldung
1787 # generiert. Offen Chargennummer / bestbefore wird nicht berücksichtigt
1788 sub transfer_in_out_default {
1789 $main::lxdebug->enter_sub();
1791 my $form = $main::form;
1792 my %myconfig = %main::myconfig;
1793 my $locale = $main::locale;
1796 my (%missing_default_bins, %qty_parts, @all_requests, %part_info_map, $default_warehouse_id, $default_bin_id);
1798 Common::check_params(\%params, qw(direction));
1800 # entsprechende defaults holen, falls standardlagerplatz verwendet werden soll
1801 if ($::instance_conf->get_transfer_default_use_master_default_bin) {
1802 $default_warehouse_id = $::instance_conf->get_warehouse_id;
1803 $default_bin_id = $::instance_conf->get_bin_id;
1807 my @part_ids = map { $form->{"id_${_}"} } (1 .. $form->{rowcount});
1809 my $units = AM->retrieve_units(\%myconfig, $form);
1810 %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
1811 foreach my $i (1 .. $form->{rowcount}) {
1812 next unless ($form->{"id_$i"});
1813 my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
1814 my $qty = $form->parse_amount(\%myconfig, $form->{"qty_$i"}) * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
1816 $form->show_generic_error($locale->text("Cannot transfer negative entries." )) if ($qty < 0);
1817 # if we do not want to transfer services and this part is a service, set qty to zero
1818 # ... and do not create a hash entry in %qty_parts below (will skip check for bins for the transfer == out case)
1819 # ... 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)
1821 $qty = 0 if (!$::instance_conf->get_transfer_default_services && $part_info_map{$form->{"id_$i"}}->{part_type} eq 'service');
1822 $qty_parts{$form->{"id_$i"}} += $qty;
1824 delete $qty_parts{$form->{"id_$i"}} unless $qty_parts{$form->{"id_$i"}};
1825 undef $form->{"stock_in_$i"};
1828 $part_info_map{$form->{"id_$i"}}{bin_id} ||= $default_bin_id;
1829 $part_info_map{$form->{"id_$i"}}{warehouse_id} ||= $default_warehouse_id;
1831 push @all_requests, ($qty == 0) ? { } : {
1832 'chargenumber' => '', #?? die müsste entsprechend geholt werden
1833 #'bestbefore' => undef, # TODO wird nicht berücksichtigt
1834 'bin_id' => $part_info_map{$form->{"id_$i"}}{bin_id},
1836 'parts_id' => $form->{"id_$i"},
1837 'comment' => $locale->text("Default transfer delivery order"),
1838 'unit' => $part_info_map{$form->{"id_$i"}}{unit},
1839 'warehouse_id' => $part_info_map{$form->{"id_$i"}}{warehouse_id},
1840 'oe_id' => $form->{id},
1841 'project_id' => $form->{"project_id_$i"} ? $form->{"project_id_$i"} : $form->{globalproject_id}
1845 # jetzt wird erst überprüft, ob die Stückzahl entsprechend stimmt.
1846 # check if bin (transfer in and transfer out and qty (transfer out) is correct
1847 foreach my $key (keys %qty_parts) {
1849 $missing_default_bins{$key}{missing_bin} = 1 unless ($part_info_map{$key}{bin_id});
1850 next unless ($part_info_map{$key}{bin_id}); # abbruch
1852 if ($params{direction} eq 'out') { # wird nur für ausgehende Mengen benötigt
1853 my ($max_qty, $error) = WH->get_max_qty_parts_bin(parts_id => $key, bin_id => $part_info_map{$key}{bin_id});
1855 # wir können nicht entscheiden, welche charge oder mhd (bestbefore) ausgewählt sein soll
1856 # deshalb rückmeldung nach oben geben, manuell auszulagern
1857 # TODO Bei nur einem Treffer mit Charge oder bestbefore wäre das noch möglich
1858 $missing_default_bins{$key}{chargenumber} = 1;
1860 if ($max_qty < $qty_parts{$key}){
1861 $missing_default_bins{$key}{missing_qty} = $max_qty - $qty_parts{$key};
1867 # Abfrage für Fehlerbehandlung (nur bei direction == out)
1868 if (scalar (keys %missing_default_bins)) {
1870 foreach my $fehler (keys %missing_default_bins) {
1872 my $ware = WH->get_part_description(parts_id => $fehler);
1873 if ($missing_default_bins{$fehler}{missing_bin}){
1874 $fehlertext .= "Kein Standardlagerplatz definiert bei $ware <br>";
1876 if ($missing_default_bins{$fehler}{missing_qty}) { # missing_qty
1877 $fehlertext .= "Es fehlen " . $missing_default_bins{$fehler}{missing_qty}*-1 .
1878 " von $ware auf dem Standard-Lagerplatz " . $part_info_map{$fehler}{bin} . " zum Auslagern<br>";
1880 if ($missing_default_bins{$fehler}{chargenumber}){
1881 $fehlertext .= "Die Ware hat eine Chargennummer oder eine Mindesthaltbarkeit definiert.
1882 Hier kann man nicht automatisch entscheiden.
1883 Bitte diesen Lieferschein manuell auslagern.
1886 # auslagern soll immer gehen, auch wenn nicht genügend auf lager ist.
1887 # der lagerplatz ist hier extra konfigurierbar, bspw. Lager-Korrektur mit
1888 # Lagerplatz Lagerplatz-Korrektur
1889 my $default_warehouse_id_ignore_onhand = $::instance_conf->get_warehouse_id_ignore_onhand;
1890 my $default_bin_id_ignore_onhand = $::instance_conf->get_bin_id_ignore_onhand;
1891 if ($::instance_conf->get_transfer_default_ignore_onhand && $default_bin_id_ignore_onhand) {
1892 # entsprechende defaults holen
1893 # falls chargenumber, bestbefore oder anzahl nicht stimmt, auf automatischen
1894 # lagerplatz wegbuchen!
1895 foreach (@all_requests) {
1896 if ($_->{parts_id} eq $fehler){
1897 $_->{bin_id} = $default_bin_id_ignore_onhand;
1898 $_->{warehouse_id} = $default_warehouse_id_ignore_onhand;
1902 #$main::lxdebug->message(0, 'Fehlertext: ' . $fehlertext);
1903 $form->show_generic_error($locale->text("Cannot transfer. <br> Reason:<br>#1", $fehlertext ));
1909 # hier der eigentliche fallunterschied für in oder out
1910 my $prefix = $params{direction} eq 'in' ? 'in' : 'out';
1912 # dieser array_ref ist für DO->save da:
1913 # einmal die all_requests in YAML verwandeln, damit delivery_order_items_stock
1914 # gefüllt werden kann.
1915 # could be dumped to the form in the first loop,
1916 # but maybe bin_id and warehouse_id has changed to the "korrekturlager" with
1917 # allowed negative qty ($::instance_conf->get_warehouse_id_ignore_onhand) ...
1919 foreach (@all_requests){
1921 next unless scalar(%{ $_ });
1922 $form->{"stock_${prefix}_$i"} = SL::YAML::Dump([$_]);
1925 save(no_redirect => 1); # Wir können auslagern, deshalb beleg speichern
1926 # und in delivery_order_items_stock speichern
1928 # ... and fill back the persistent dois_id for inventory fk
1929 undef (@all_requests);
1930 foreach my $i (1 .. $form->{rowcount}) {
1931 next unless ($form->{"id_$i"} && $form->{"stock_${prefix}_$i"});
1932 push @all_requests, @{ DO->unpack_stock_information('packed' => $form->{"stock_${prefix}_$i"}) };
1934 DO->transfer_in_out('direction' => $prefix,
1935 'requests' => \@all_requests);
1937 SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
1939 $form->{callback} = 'do.pl?action=edit&type=sales_delivery_order&id=' . $form->escape($form->{id}) if $params{direction} eq 'out';
1940 $form->{callback} = 'do.pl?action=edit&type=purchase_delivery_order&id=' . $form->escape($form->{id}) if $params{direction} eq 'in';
1946 $main::lxdebug->enter_sub();
1950 my $form = $main::form;
1953 save(no_redirect => 1); # has to be done, at least for newly added positions
1955 # hashify partnumbers, positions. key is delivery_order_items_id
1956 for my $i (1 .. ($form->{rowcount}) ) {
1957 $temp_hash{$form->{"delivery_order_items_id_$i"}} = { runningnumber => $form->{"runningnumber_$i"}, partnumber => $form->{"partnumber_$i"} };
1958 if ($form->{id} && $form->{"discount_$i"}) {
1959 # prepare_order assumes a db value if there is a form->id and multiplies *100
1960 # We hope for new controller code (no more format_amount/parse_amount distinction)
1961 $form->{"discount_$i"} /=100;
1964 # naturally sort partnumbers and get a sorted array of doi_ids
1965 my @sorted_doi_ids = sort { Sort::Naturally::ncmp($temp_hash{$a}->{"partnumber"}, $temp_hash{$b}->{"partnumber"}) } keys %temp_hash;
1970 for (@sorted_doi_ids) {
1971 $form->{"runningnumber_$temp_hash{$_}->{runningnumber}"} = $new_number;
1974 # all parse_amounts changes are in form (i.e. , to .) therefore we need
1975 # another format_amount to change it back, for the next save ;-(
1976 # works great except for row discounts (see above comment)
1980 $main::lxdebug->leave_sub();
1992 do.pl - Script for all calls to delivery order
2000 Sorts all position with Natural Sort. Can be activated in form_footer.html like this
2001 C<E<lt>input class="submit" type="submit" name="action_sort" id="sort_button" value="[% 'Sort and Save' | $T8 %]"E<gt>>
2007 Sort and Save can be implemented as an optional button if configuration ca be set by client config.
2008 Example coding for database scripts and templates in (git show af2f24b8), check also
2009 autogeneration for rose (scripts/rose_auto_create_model.pl --h)