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));
470 push @custom_hidden, map { "shiptocvar_" . $_->name } @{ SL::DB::Manager::CustomVariableConfig->get_all(where => [ module => 'ShipTo' ]) };
472 $::form->{HIDDENS} = [ map { +{ name => $_, value => $::form->{$_} } } (@custom_hidden) ];
474 setup_do_action_bar();
477 # Fix für Bug 1082 Erwartet wird: 'abteilungsNAME--abteilungsID'
478 # und Erweiterung für Bug 1760:
479 # Das war leider nur ein Teil-Fix, da das Verhalten den 'Erneuern'-Knopf
480 # nicht überlebt. Konsequent jetzt auf L umgestellt
481 # $ perldoc SL::Template::Plugin::L
482 # Daher entsprechend nur die Anpassung in form_header
483 # und in DO.pm gemacht. 4 Testfälle:
484 # department_id speichern | i.O.
485 # department_id lesen | i.O.
486 # department leer überlebt erneuern | i.O.
487 # department nicht leer überlebt erneuern | i.O.
488 # $main::lxdebug->message(0, 'ABTEILUNGS ID in form?' . $form->{department_id});
489 print $form->parse_html_template('do/form_header');
491 $main::lxdebug->leave_sub();
495 $main::lxdebug->enter_sub();
499 my $form = $main::form;
501 $form->{PRINT_OPTIONS} = setup_sales_purchase_print_options();
502 $form->{ALL_DELIVERY_TERMS} = SL::DB::Manager::DeliveryTerm->get_all_sorted();
504 print $form->parse_html_template('do/form_footer',
505 {transfer_default => ($::instance_conf->get_transfer_default)});
507 $main::lxdebug->leave_sub();
510 sub update_delivery_order {
511 $main::lxdebug->enter_sub();
515 my $form = $main::form;
516 my %myconfig = %main::myconfig;
518 set_headings($form->{"id"} ? "edit" : "add");
520 $form->{insertdate} = SL::DB::DeliveryOrder->new(id => $form->{id})->load->itime_as_date if $form->{id};
525 $payment_id = $form->{payment_id} if $form->{payment_id};
527 my $vc = $form->{vc};
528 if (($form->{"previous_${vc}_id"} || $form->{"${vc}_id"}) != $form->{"${vc}_id"}) {
529 $::form->{salesman_id} = SL::DB::Manager::Employee->current->id if exists $::form->{salesman_id};
531 IS->get_customer(\%myconfig, $form) if $vc eq 'customer';
532 IR->get_vendor(\%myconfig, $form) if $vc eq 'vendor';
535 $form->{discount} = $form->{"$form->{vc}_discount"} if defined $form->{"$form->{vc}_discount"};
536 # Problem: Wenn man ohne Erneuern einen Kunden/Lieferanten
537 # wechselt, wird der entsprechende Kunden/ Lieferantenrabatt
538 # nicht übernommen. Grundproblem: In Commit 82574e78
539 # hab ich aus discount customer_discount und vendor_discount
540 # gemacht und entsprechend an den Oberflächen richtig hin-
541 # geschoben. Die damals bessere Lösung wäre gewesen:
542 # In den Templates nur die hidden für form-discount wieder ein-
543 # setzen dann wäre die Verrenkung jetzt nicht notwendig.
544 # TODO: Ggf. Bugfix 1284, 1575 und 817 wieder zusammenführen
545 # Testfälle: Kunden mit Rabatt 0 -> Rabatt 20 i.O.
546 # Kunde mit Rabatt 20 -> Rabatt 0 i.O.
547 # Kunde mit Rabatt 20 -> Rabatt 5,5 i.O.
548 $form->{payment_id} = $payment_id if $form->{payment_id} eq "";
550 my $i = $form->{rowcount};
552 if ( ($form->{"partnumber_$i"} eq "")
553 && ($form->{"description_$i"} eq "")
554 && ($form->{"partsgroup_$i"} eq "")) {
561 if ($form->{type} eq 'purchase_delivery_order') {
562 IR->retrieve_item(\%myconfig, $form);
565 IS->retrieve_item(\%myconfig, $form);
569 my $rows = scalar @{ $form->{item_list} };
572 $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
573 if( !$form->{"qty_$i"} ) {
574 $form->{"qty_$i"} = 1;
579 select_item(mode => $mode, pre_entered_qty => $form->{"qty_$i"});
580 $::dispatcher->end_request;
584 my $sellprice = $form->parse_amount(\%myconfig, $form->{"sellprice_$i"});
586 map { $form->{"${_}_$i"} = $form->{item_list}[0]{$_} } keys %{ $form->{item_list}[0] };
588 $form->{"marge_price_factor_$i"} = $form->{item_list}->[0]->{price_factor};
591 $form->{"sellprice_$i"} = $sellprice;
593 my $record = _make_record();
594 my $price_source = SL::PriceSource->new(record_item => $record->items->[$i-1], record => $record);
595 my $best_price = $price_source->best_price;
596 my $best_discount = $price_source->best_discount;
599 $::form->{"sellprice_$i"} = $best_price->price;
600 $::form->{"active_price_source_$i"} = $best_price->source;
602 if ($best_discount) {
603 $::form->{"discount_$i"} = $best_discount->discount;
604 $::form->{"active_discount_source_$i"} = $best_discount->source;
608 $form->{"sellprice_$i"} = $form->format_amount(\%myconfig, $form->{"sellprice_$i"});
609 $form->{"lastcost_$i"} = $form->format_amount(\%myconfig, $form->{"lastcost_$i"});
610 $form->{"qty_$i"} = $form->format_amount(\%myconfig, $form->{"qty_$i"});
611 $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"} * 100.0);
618 # ok, so this is a new part
619 # ask if it is a part or service item
621 if ( $form->{"partsgroup_$i"}
622 && ($form->{"partsnumber_$i"} eq "")
623 && ($form->{"description_$i"} eq "")) {
625 $form->{"discount_$i"} = "";
626 $form->{"not_discountable_$i"} = "";
630 $form->{"id_$i"} = 0;
636 $main::lxdebug->leave_sub();
640 $main::lxdebug->enter_sub();
644 my $form = $main::form;
645 my %myconfig = %main::myconfig;
646 my $locale = $main::locale;
648 $form->{vc} = $form->{type} eq 'purchase_delivery_order' ? 'vendor' : 'customer';
650 $form->get_lists("projects" => { "key" => "ALL_PROJECTS",
652 "business_types" => "ALL_BUSINESS_TYPES");
653 $form->{ALL_EMPLOYEES} = SL::DB::Manager::Employee->get_all_sorted(query => [ deleted => 0 ]);
654 $form->{ALL_DEPARTMENTS} = SL::DB::Manager::Department->get_all_sorted;
655 $form->{title} = $locale->text('Delivery Orders');
657 setup_do_search_action_bar();
661 print $form->parse_html_template('do/search');
663 $main::lxdebug->leave_sub();
667 $main::lxdebug->enter_sub();
671 my $form = $main::form;
672 my %myconfig = %main::myconfig;
673 my $locale = $main::locale;
674 my $cgi = $::request->{cgi};
676 $::request->{layout}->use_javascript(map { "${_}.js" } qw(kivi.MassDeliveryOrderPrint kivi.SalesPurchase));
677 ($form->{ $form->{vc} }, $form->{"$form->{vc}_id"}) = split(/--/, $form->{ $form->{vc} });
679 report_generator_set_default_sort('transdate', 1);
683 $form->{rowcount} = scalar @{ $form->{DO} };
686 ids transdate reqdate
688 ordnumber customernumber cusordnumber
689 name employee salesman
690 shipvia globalprojectnumber
691 transaction_description department
696 $form->{l_open} = $form->{l_closed} = "Y" if ($form->{open} && $form->{closed});
697 $form->{l_delivered} = "Y" if ($form->{delivered} && $form->{notdelivered});
699 $form->{title} = $locale->text('Delivery Orders');
701 my $attachment_basename = $form->{vc} eq 'vendor' ? $locale->text('purchase_delivery_order_list') : $locale->text('sales_delivery_order_list');
703 my $report = SL::ReportGenerator->new(\%myconfig, $form);
705 my @hidden_variables = map { "l_${_}" } @columns;
706 push @hidden_variables, $form->{vc}, qw(l_closed l_notdelivered open closed delivered notdelivered donumber ordnumber serialnumber cusordnumber
707 transaction_description transdatefrom transdateto reqdatefrom reqdateto
708 type vc employee_id salesman_id project_id parts_partnumber parts_description
709 insertdatefrom insertdateto business_id all department_id);
711 my $href = build_std_url('action=orders', grep { $form->{$_} } @hidden_variables);
714 'ids' => { raw_header_data => SL::Presenter::Tag::checkbox_tag("", id => "multi_all", checkall => "[data-checkall=1]"), align => 'center' },
715 'transdate' => { 'text' => $locale->text('Delivery Order Date'), },
716 'reqdate' => { 'text' => $locale->text('Reqdate'), },
717 'id' => { 'text' => $locale->text('ID'), },
718 'donumber' => { 'text' => $locale->text('Delivery Order'), },
719 'ordnumber' => { 'text' => $locale->text('Order'), },
720 'customernumber' => { 'text' => $locale->text('Customer Number'), },
721 'cusordnumber' => { 'text' => $locale->text('Customer Order Number'), },
722 'name' => { 'text' => $form->{vc} eq 'customer' ? $locale->text('Customer') : $locale->text('Vendor'), },
723 'employee' => { 'text' => $locale->text('Employee'), },
724 'salesman' => { 'text' => $locale->text('Salesman'), },
725 'shipvia' => { 'text' => $locale->text('Ship via'), },
726 'globalprojectnumber' => { 'text' => $locale->text('Project Number'), },
727 'transaction_description' => { 'text' => $locale->text('Transaction description'), },
728 'open' => { 'text' => $locale->text('Open'), },
729 'delivered' => { 'text' => $locale->text('Delivered'), },
730 'department' => { 'text' => $locale->text('Department'), },
731 'insertdate' => { 'text' => $locale->text('Insert Date'), },
734 foreach my $name (qw(id transdate reqdate donumber ordnumber name employee salesman shipvia transaction_description department insertdate)) {
735 my $sortdir = $form->{sort} eq $name ? 1 - $form->{sortdir} : $form->{sortdir};
736 $column_defs{$name}->{link} = $href . "&sort=$name&sortdir=$sortdir";
739 $form->{"l_type"} = "Y";
740 map { $column_defs{$_}->{visible} = $form->{"l_${_}"} ? 1 : 0 } @columns;
742 $column_defs{ids}->{visible} = 'HTML';
744 $report->set_columns(%column_defs);
745 $report->set_column_order(@columns);
747 $report->set_export_options('orders', @hidden_variables, qw(sort sortdir));
749 $report->set_sort_indicator($form->{sort}, $form->{sortdir});
752 if ($form->{customer}) {
753 push @options, $locale->text('Customer') . " : $form->{customer}";
755 if ($form->{vendor}) {
756 push @options, $locale->text('Vendor') . " : $form->{vendor}";
758 if ($form->{cp_name}) {
759 push @options, $locale->text('Contact Person') . " : $form->{cp_name}";
761 if ($form->{department_id}) {
762 push @options, $locale->text('Department') . " : " . SL::DB::Department->new(id => $form->{department_id})->load->description;
764 if ($form->{donumber}) {
765 push @options, $locale->text('Delivery Order Number') . " : $form->{donumber}";
767 if ($form->{ordnumber}) {
768 push @options, $locale->text('Order Number') . " : $form->{ordnumber}";
770 push @options, $locale->text('Serial Number') . " : $form->{serialnumber}" if $form->{serialnumber};
771 if ($form->{business_id}) {
772 my $vc_type_label = $form->{vc} eq 'customer' ? $locale->text('Customer type') : $locale->text('Vendor type');
773 push @options, $vc_type_label . " : " . SL::DB::Business->new(id => $form->{business_id})->load->description;
775 if ($form->{transaction_description}) {
776 push @options, $locale->text('Transaction description') . " : $form->{transaction_description}";
778 if ($form->{parts_description}) {
779 push @options, $locale->text('Part Description') . " : $form->{parts_description}";
781 if ($form->{parts_partnumber}) {
782 push @options, $locale->text('Part Number') . " : $form->{parts_partnumber}";
784 if ( $form->{transdatefrom} or $form->{transdateto} ) {
785 push @options, $locale->text('Delivery Order Date');
786 push @options, $locale->text('From') . " " . $locale->date(\%myconfig, $form->{transdatefrom}, 1) if $form->{transdatefrom};
787 push @options, $locale->text('Bis') . " " . $locale->date(\%myconfig, $form->{transdateto}, 1) if $form->{transdateto};
789 if ( $form->{reqdatefrom} or $form->{reqdateto} ) {
790 push @options, $locale->text('Reqdate');
791 push @options, $locale->text('From') . " " . $locale->date(\%myconfig, $form->{reqdatefrom}, 1) if $form->{reqdatefrom};
792 push @options, $locale->text('Bis') . " " . $locale->date(\%myconfig, $form->{reqdateto}, 1) if $form->{reqdateto};
794 if ( $form->{insertdatefrom} or $form->{insertdateto} ) {
795 push @options, $locale->text('Insert Date');
796 push @options, $locale->text('From') . " " . $locale->date(\%myconfig, $form->{insertdatefrom}, 1) if $form->{insertdatefrom};
797 push @options, $locale->text('Bis') . " " . $locale->date(\%myconfig, $form->{insertdateto}, 1) if $form->{insertdateto};
800 push @options, $locale->text('Open');
802 if ($form->{closed}) {
803 push @options, $locale->text('Closed');
805 if ($form->{delivered}) {
806 push @options, $locale->text('Delivered');
808 if ($form->{notdelivered}) {
809 push @options, $locale->text('Not delivered');
811 push @options, $locale->text('Quick Search') . " : $form->{all}" if $form->{all};
813 my $pr = SL::DB::Manager::Printer->find_by(
814 printer_description => $::locale->text("sales_delivery_order_printer"));
816 $form->{printer_id} = $pr->id;
819 my $print_options = SL::Helper::PrintOptions->get_print_options(
821 hide_language_id => 1,
827 $report->set_options('top_info_text' => join("\n", @options),
828 'raw_top_info_text' => $form->parse_html_template('do/orders_top'),
829 'raw_bottom_info_text' => $form->parse_html_template('do/orders_bottom', { print_options => $print_options }),
830 'output_format' => 'HTML',
831 'title' => $form->{title},
832 'attachment_basename' => $attachment_basename . strftime('_%Y%m%d', localtime time),
834 $report->set_options_from_form();
835 $locale->set_numberformat_wo_thousands_separator(\%myconfig) if lc($report->{options}->{output_format}) eq 'csv';
837 # add sort and escape callback, this one we use for the add sub
838 $form->{callback} = $href .= "&sort=$form->{sort}";
840 # escape callback for href
841 my $callback = $form->escape($href);
843 my $edit_url = build_std_url('action=edit', 'type', 'vc');
844 my $edit_order_url = ($::instance_conf->get_feature_experimental_order)
845 ? build_std_url('script=controller.pl', 'action=Order/edit', 'type=' . ($form->{type} eq 'sales_delivery_order' ? 'sales_order' : 'purchase_order'))
846 : build_std_url('script=oe.pl', 'action=edit', 'type=' . ($form->{type} eq 'sales_delivery_order' ? 'sales_order' : 'purchase_order'));
850 foreach my $dord (@{ $form->{DO} }) {
851 $dord->{open} = $dord->{closed} ? $locale->text('No') : $locale->text('Yes');
852 $dord->{delivered} = $dord->{delivered} ? $locale->text('Yes') : $locale->text('No');
854 my $row = { map { $_ => { 'data' => $dord->{$_} } } @columns };
856 my $ord_id = $dord->{id};
858 'raw_data' => $cgi->hidden('-name' => "trans_id_${idx}", '-value' => $ord_id)
859 . $cgi->checkbox('-name' => "multi_id_${idx}",' id' => "multi_id_id_".$ord_id, '-value' => 1, 'data-checkall' => 1, '-label' => ''),
860 'valign' => 'center',
864 $row->{donumber}->{link} = $edit_url . "&id=" . E($dord->{id}) . "&callback=${callback}";
865 $row->{ordnumber}->{link} = $edit_order_url . "&id=" . E($dord->{oe_id}) . "&callback=${callback}" if $dord->{oe_id};
866 $report->add_data($row);
871 setup_do_orders_action_bar();
873 $report->generate_with_headers();
875 $main::lxdebug->leave_sub();
879 $main::lxdebug->enter_sub();
885 my $form = $main::form;
886 my %myconfig = %main::myconfig;
887 my $locale = $main::locale;
889 $form->mtime_ischanged('delivery_orders');
891 $form->{defaultcurrency} = $form->get_default_currency(\%myconfig);
893 $form->isblank("transdate", $locale->text('Delivery Order Date missing!'));
895 $form->{donumber} =~ s/^\s*//g;
896 $form->{donumber} =~ s/\s*$//g;
898 my $msg = ucfirst $form->{vc};
899 $form->isblank($form->{vc} . "_id", $locale->text($msg . " missing!"));
901 # $locale->text('Customer missing!');
902 # $locale->text('Vendor missing!');
904 remove_emptied_rows();
907 # if the name changed get new values
908 my $vc = $form->{vc};
909 if (($form->{"previous_${vc}_id"} || $form->{"${vc}_id"}) != $form->{"${vc}_id"}) {
910 $::form->{salesman_id} = SL::DB::Manager::Employee->current->id if exists $::form->{salesman_id};
912 IS->get_customer(\%myconfig, $form) if $vc eq 'customer';
913 IR->get_vendor(\%myconfig, $form) if $vc eq 'vendor';
916 $::dispatcher->end_request;
919 $form->{id} = 0 if $form->{saveasnew};
923 if(!exists $form->{addition}) {
924 $form->{snumbers} = qq|donumber_| . $form->{donumber};
925 $form->{addition} = "SAVED";
928 # /saving the history
930 $form->{simple_save} = 1;
931 if (!$params{no_redirect} && !$form->{print_and_save}) {
932 delete @{$form}{ary_diff([keys %{ $form }], [qw(login id script type cursor_fokus)])};
934 $::dispatcher->end_request;
936 $main::lxdebug->leave_sub();
940 $main::lxdebug->enter_sub();
944 my $form = $main::form;
945 my %myconfig = %main::myconfig;
946 my $locale = $main::locale;
948 if ($ret = DO->delete()) {
950 if(!exists $form->{addition}) {
951 $form->{snumbers} = qq|donumber_| . $form->{donumber};
952 $form->{addition} = "DELETED";
955 # /saving the history
957 $form->info($locale->text('Delivery Order deleted!'));
958 $::dispatcher->end_request;
961 $form->error($locale->text('Cannot delete delivery order!') . $ret);
963 $main::lxdebug->leave_sub();
967 $main::lxdebug->enter_sub();
969 my $form = $main::form;
970 my %myconfig = %main::myconfig;
971 my $locale = $main::locale;
974 $form->mtime_ischanged('delivery_orders');
976 $main::auth->assert($form->{type} eq 'purchase_delivery_order' ? 'vendor_invoice_edit' : 'invoice_edit');
978 $form->{convert_from_do_ids} = $form->{id};
979 # if we have a reqdate (Liefertermin), this is definetely the preferred
980 # deliverydate for invoices
981 $form->{deliverydate} = $form->{reqdate} || $form->{transdate};
982 $form->{transdate} = $form->{invdate} = $form->current_date(\%myconfig);
983 $form->{duedate} = $form->current_date(\%myconfig, $form->{invdate}, $form->{terms} * 1);
984 $form->{defaultcurrency} = $form->get_default_currency(\%myconfig);
988 delete @{$form}{qw(id closed delivered)};
990 my ($script, $buysell);
991 if ($form->{type} eq 'purchase_delivery_order') {
992 $form->{title} = $locale->text('Add Vendor Invoice');
993 $form->{script} = 'ir.pl';
998 $form->{title} = $locale->text('Add Sales Invoice');
999 $form->{script} = 'is.pl';
1004 for my $i (1 .. $form->{rowcount}) {
1005 map { $form->{"${_}_${i}"} = $form->parse_amount(\%myconfig, $form->{"${_}_${i}"}) if $form->{"${_}_${i}"} } qw(ship qty sellprice lastcost basefactor discount);
1007 # adds a customer/vendor discount, unless we have a workflow case
1008 # CAVEAT: has to be done, after the above parse_amount
1009 unless ($form->{"ordnumber"}) {
1010 if ($form->{discount}) { # Falls wir einen Lieferanten-/Kundenrabatt haben
1011 # und rabattfähig sind, dann
1012 unless ($form->{"not_discountable_$i"}) {
1013 $form->{"discount_$i"} = $form->{discount}*100; # ... nehmen wir diesen Rabatt
1017 $form->{"donumber_$i"} = $form->{donumber};
1018 $form->{"converted_from_delivery_order_items_id_$i"} = delete $form->{"delivery_order_items_id_$i"};
1021 $form->{type} = "invoice";
1024 $main::locale = Locale->new("$myconfig{countrycode}", "$script");
1025 $locale = $main::locale;
1027 require "bin/mozilla/$form->{script}";
1029 my $currency = $form->{currency};
1032 if ($form->{ordnumber}) {
1033 require SL::DB::Order;
1034 my $vc_id = $form->{type} =~ /^sales/ ? 'customer_id' : 'vendor_id';
1035 if (my $order = SL::DB::Manager::Order->find_by(ordnumber => $form->{ordnumber}, $vc_id => $form->{"$vc_id"})) {
1037 $form->{orddate} = $order->transdate_as_date;
1038 $form->{$_} = $order->$_ for qw(payment_id salesman_id taxzone_id quonumber);
1042 $form->{currency} = $currency;
1043 $form->{exchangerate} = "";
1044 $form->{forex} = $form->check_exchangerate(\%myconfig, $form->{currency}, $form->{invdate}, $buysell);
1045 $form->{exchangerate} = $form->{forex} if ($form->{forex});
1050 for my $i (1 .. $form->{rowcount}) {
1051 $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"});
1053 my ($dec) = ($form->{"sellprice_$i"} =~ /\.(\d+)/);
1055 my $decimalplaces = ($dec > 2) ? $dec : 2;
1057 # copy delivery date from reqdate for order -> invoice conversion
1058 $form->{"deliverydate_$i"} = $form->{"reqdate_$i"}
1059 unless $form->{"deliverydate_$i"};
1062 $form->{"sellprice_$i"} =
1063 $form->format_amount(\%myconfig, $form->{"sellprice_$i"},
1066 $form->{"lastcost_$i"} =
1067 $form->format_amount(\%myconfig, $form->{"lastcost_$i"},
1070 (my $dec_qty) = ($form->{"qty_$i"} =~ /\.(\d+)/);
1071 $dec_qty = length $dec_qty;
1073 $form->format_amount(\%myconfig, $form->{"qty_$i"}, $dec_qty);
1079 $main::lxdebug->leave_sub();
1083 $main::lxdebug->enter_sub();
1085 my $form = $main::form;
1086 my %myconfig = %main::myconfig;
1087 my $locale = $main::locale;
1090 $main::auth->assert($form->{type} eq 'sales_delivery_order' ? 'invoice_edit' : 'vendor_invoice_edit');
1092 my @do_ids = map { $form->{"trans_id_$_"} } grep { $form->{"multi_id_$_"} } (1..$form->{rowcount});
1094 if (!scalar @do_ids) {
1095 $form->show_generic_error($locale->text('You have not selected any delivery order.'));
1098 map { delete $form->{$_} } grep { m/^(?:trans|multi)_id_\d+/ } keys %{ $form };
1100 if (!DO->retrieve('vc' => $form->{vc}, 'ids' => \@do_ids)) {
1101 $form->show_generic_error($form->{vc} eq 'customer' ?
1102 $locale->text('You cannot create an invoice for delivery orders for different customers.') :
1103 $locale->text('You cannot create an invoice for delivery orders from different vendors.'),
1104 'back_button' => 1);
1107 my $source_type = $form->{type};
1108 $form->{convert_from_do_ids} = join ' ', @do_ids;
1109 # bei der auswahl von mehreren Lieferscheinen fuer eine Rechnung, die einfach in donumber_array
1110 # zwischenspeichern (DO.pm) und als ' '-separierte Liste wieder zurueckschreiben
1111 # Hinweis: delete gibt den wert zurueck und loescht danach das element (nett und einfach)
1112 # $shell: perldoc perlunc; /delete EXPR
1113 $form->{donumber} = delete $form->{donumber_array};
1114 $form->{ordnumber} = delete $form->{ordnumber_array};
1115 $form->{cusordnumber} = delete $form->{cusordnumber_array};
1116 $form->{deliverydate} = $form->{transdate};
1117 $form->{transdate} = $form->current_date(\%myconfig);
1118 $form->{duedate} = $form->current_date(\%myconfig, $form->{invdate}, $form->{terms} * 1);
1119 $form->{type} = "invoice";
1120 $form->{closed} = 0;
1121 $form->{defaultcurrency} = $form->get_default_currency(\%myconfig);
1123 my ($script, $buysell);
1124 if ($source_type eq 'purchase_delivery_order') {
1125 $form->{title} = $locale->text('Add Vendor Invoice');
1126 $form->{script} = 'ir.pl';
1131 $form->{title} = $locale->text('Add Sales Invoice');
1132 $form->{script} = 'is.pl';
1137 map { delete $form->{$_} } qw(id subject message cc bcc printed emailed queued);
1139 # get vendor or customer discount
1141 my $saved_form = save_form();
1142 if ($form->{vc} eq 'vendor') {
1143 IR->get_vendor(\%myconfig, \%$form);
1144 $vc_discount = $form->{vendor_discount};
1146 IS->get_customer(\%myconfig, \%$form);
1147 $vc_discount = $form->{customer_discount};
1149 # use payment terms from customer or vendor
1150 restore_form($saved_form,0,qw(payment_id));
1152 $form->{rowcount} = 0;
1153 foreach my $ref (@{ $form->{form_details} }) {
1154 $form->{rowcount}++;
1155 $ref->{reqdate} ||= $ref->{dord_transdate}; # copy transdates into each invoice row
1156 map { $form->{"${_}_$form->{rowcount}"} = $ref->{$_} } keys %{ $ref };
1157 map { $form->{"${_}_$form->{rowcount}"} = $form->format_amount(\%myconfig, $ref->{$_}) } qw(qty sellprice lastcost);
1158 $form->{"converted_from_delivery_order_items_id_$form->{rowcount}"} = delete $form->{"delivery_order_items_id_$form->{rowcount}"};
1160 if ($vc_discount){ # falls wir einen Lieferanten/Kundenrabatt haben
1161 # und keinen anderen discount wert an $i ...
1162 $form->{"discount_$form->{rowcount}"} ||= $vc_discount; # ... nehmen wir diesen Rabatt
1165 $form->{"discount_$form->{rowcount}"} = $form->{"discount_$form->{rowcount}"} * 100; #s.a. Bug 1151
1166 # Anm.: Eine Änderung des discounts in der SL/DO.pm->retrieve (select (doi.discount * 100) as discount) ergibt in psql einen
1167 # Wert von 10.0000001490116. Ferner ist der Rabatt in der Rechnung dann bei 1.0 (?). Deswegen lasse ich das hier. jb 10.10.09
1169 $form->{"discount_$form->{rowcount}"} = $form->format_amount(\%myconfig, $form->{"discount_$form->{rowcount}"});
1171 delete $form->{form_details};
1173 $locale = Locale->new("$myconfig{countrycode}", "$script");
1175 require "bin/mozilla/$form->{script}";
1182 $main::lxdebug->leave_sub();
1186 $main::lxdebug->enter_sub();
1190 my $form = $main::form;
1192 $form->{saveasnew} = 1;
1193 $form->{closed} = 0;
1194 $form->{delivered} = 0;
1195 map { delete $form->{$_} } qw(printed emailed queued);
1196 delete @{ $form }{ grep { m/^stock_(?:in|out)_\d+/ } keys %{ $form } };
1197 $form->{"converted_from_delivery_order_items_id_$_"} = delete $form->{"delivery_order_items_id_$_"} for 1 .. $form->{"rowcount"};
1198 # Let kivitendo assign a new order number if the user hasn't changed the
1199 # previous one. If it has been changed manually then use it as-is.
1200 $form->{donumber} =~ s/^\s*//g;
1201 $form->{donumber} =~ s/\s*$//g;
1202 if ($form->{saved_donumber} && ($form->{saved_donumber} eq $form->{donumber})) {
1203 delete($form->{donumber});
1208 $main::lxdebug->leave_sub();
1211 sub calculate_stock_in_out {
1212 $main::lxdebug->enter_sub();
1214 my $form = $main::form;
1218 if (!$form->{"id_${i}"}) {
1219 $main::lxdebug->leave_sub();
1223 my $all_units = AM->retrieve_all_units();
1225 my $in_out = $form->{type} =~ /^sales/ ? 'out' : 'in';
1226 my $sinfo = DO->unpack_stock_information('packed' => $form->{"stock_${in_out}_${i}"});
1228 my $do_qty = AM->sum_with_unit($::form->{"qty_$i"}, $::form->{"unit_$i"});
1229 my $sum = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $sinfo });
1230 my $matches = $do_qty == $sum;
1232 my $content = $form->format_amount_units('amount' => $sum * 1,
1233 'part_unit' => $form->{"partunit_$i"},
1234 'amount_unit' => $all_units->{$form->{"partunit_$i"}}->{base_unit},
1235 'conv_units' => 'convertible_not_smaller',
1237 $content = qq|<span id="stock_in_out_qty_display_${i}">${content}</span><input type=hidden id='stock_in_out_qty_matches_$i' value='$matches'> <input type="button" onclick="open_stock_in_out_window('${in_out}', $i);" value="?">|;
1239 $main::lxdebug->leave_sub();
1244 sub get_basic_bin_wh_info {
1245 $main::lxdebug->enter_sub();
1247 my $stock_info = shift;
1249 my $form = $main::form;
1251 foreach my $sinfo (@{ $stock_info }) {
1252 next unless ($sinfo->{bin_id});
1254 my $bin_info = WH->get_basic_bin_info('id' => $sinfo->{bin_id});
1255 map { $sinfo->{"${_}_description"} = $sinfo->{"${_}description"} = $bin_info->{"${_}_description"} } qw(bin warehouse);
1258 $main::lxdebug->leave_sub();
1261 sub stock_in_out_form {
1262 $main::lxdebug->enter_sub();
1264 my $form = $main::form;
1266 if ($form->{in_out} eq 'out') {
1272 $main::lxdebug->leave_sub();
1275 sub redo_stock_info {
1276 $main::lxdebug->enter_sub();
1280 my $form = $main::form;
1282 my @non_empty = grep { $_->{qty} } @{ $params{stock_info} };
1284 if ($params{add_empty_row}) {
1286 'warehouse_id' => scalar(@non_empty) ? $non_empty[-1]->{warehouse_id} : undef,
1287 'bin_id' => scalar(@non_empty) ? $non_empty[-1]->{bin_id} : undef,
1291 @{ $params{stock_info} } = @non_empty;
1293 $main::lxdebug->leave_sub();
1296 sub update_stock_in {
1297 $main::lxdebug->enter_sub();
1299 my $form = $main::form;
1300 my %myconfig = %main::myconfig;
1302 my $stock_info = [];
1304 foreach my $i (1..$form->{rowcount}) {
1305 $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
1306 push @{ $stock_info }, { map { $_ => $form->{"${_}_${i}"} } qw(warehouse_id bin_id chargenumber
1307 bestbefore qty unit delivery_order_items_stock_id) };
1310 display_stock_in_form($stock_info);
1312 $main::lxdebug->leave_sub();
1316 $main::lxdebug->enter_sub();
1318 my $form = $main::form;
1320 my $stock_info = DO->unpack_stock_information('packed' => $form->{stock});
1322 display_stock_in_form($stock_info);
1324 $main::lxdebug->leave_sub();
1327 sub display_stock_in_form {
1328 $main::lxdebug->enter_sub();
1330 my $stock_info = shift;
1332 my $form = $main::form;
1333 my %myconfig = %main::myconfig;
1334 my $locale = $main::locale;
1336 $form->{title} = $locale->text('Stock');
1338 my $part_info = IC->get_basic_part_info('id' => $form->{parts_id});
1340 # Standardlagerplatz für Standard-Auslagern verwenden, falls keiner für die Ware explizit definiert wurde
1341 if ($::instance_conf->get_transfer_default_use_master_default_bin) {
1342 $part_info->{warehouse_id} ||= $::instance_conf->get_warehouse_id;
1343 $part_info->{bin_id} ||= $::instance_conf->get_bin_id;
1346 my $units = AM->retrieve_units(\%myconfig, $form);
1347 # der zweite Parameter von unit_select_data gibt den default-Namen (selected) vor
1348 my $units_data = AM->unit_select_data($units, $form->{do_unit}, undef, $part_info->{unit});
1350 $form->get_lists('warehouses' => { 'key' => 'WAREHOUSES',
1351 'bins' => 'BINS' });
1353 redo_stock_info('stock_info' => $stock_info, 'add_empty_row' => !$form->{delivered});
1355 get_basic_bin_wh_info($stock_info);
1357 $form->header(no_layout => 1);
1358 print $form->parse_html_template('do/stock_in_form', { 'UNITS' => $units_data,
1359 'STOCK_INFO' => $stock_info,
1360 'PART_INFO' => $part_info, });
1362 $main::lxdebug->leave_sub();
1365 sub _stock_in_out_set_qty_display {
1366 my $stock_info = shift;
1368 my $all_units = AM->retrieve_all_units();
1369 my $sum = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
1370 $form->{qty_display} = $form->format_amount_units(amount => $sum * 1,
1371 part_unit => $form->{partunit},
1372 amount_unit => $all_units->{ $form->{partunit} }->{base_unit},
1373 conv_units => 'convertible_not_smaller',
1378 $main::lxdebug->enter_sub();
1380 my $form = $main::form;
1381 my %myconfig = %main::myconfig;
1383 my $stock_info = [];
1385 foreach my $i (1..$form->{rowcount}) {
1386 $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
1388 next if ($form->{"qty_$i"} <= 0);
1390 push @{ $stock_info }, { map { $_ => $form->{"${_}_${i}"} } qw(delivery_order_items_stock_id warehouse_id bin_id chargenumber bestbefore qty unit) };
1393 $form->{stock} = SL::YAML::Dump($stock_info);
1395 _stock_in_out_set_qty_display($stock_info);
1397 my $do_qty = AM->sum_with_unit($::form->parse_amount(\%::myconfig, $::form->{do_qty}), $::form->{do_unit});
1398 my $transfer_qty = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
1401 print $form->parse_html_template('do/set_stock_in_out', {
1402 qty_matches => $do_qty == $transfer_qty,
1405 $main::lxdebug->leave_sub();
1408 sub stock_out_form {
1409 $main::lxdebug->enter_sub();
1411 my $form = $main::form;
1412 my %myconfig = %main::myconfig;
1413 my $locale = $main::locale;
1415 $form->{title} = $locale->text('Release From Stock');
1417 my $part_info = IC->get_basic_part_info('id' => $form->{parts_id});
1419 my $units = AM->retrieve_units(\%myconfig, $form);
1420 my $units_data = AM->unit_select_data($units, undef, undef, $part_info->{unit});
1422 my @contents = DO->get_item_availability('parts_id' => $form->{parts_id});
1424 my $stock_info = DO->unpack_stock_information('packed' => $form->{stock});
1426 if (!$form->{delivered}) {
1427 foreach my $row (@contents) {
1428 $row->{available_qty} = $form->format_amount_units('amount' => $row->{qty} * 1,
1429 'part_unit' => $part_info->{unit},
1430 'conv_units' => 'convertible_not_smaller',
1433 foreach my $sinfo (@{ $stock_info }) {
1434 next if (($row->{bin_id} != $sinfo->{bin_id}) ||
1435 ($row->{warehouse_id} != $sinfo->{warehouse_id}) ||
1436 ($row->{chargenumber} ne $sinfo->{chargenumber}) ||
1437 ($row->{bestbefore} ne $sinfo->{bestbefore}));
1439 map { $row->{"stock_$_"} = $sinfo->{$_} } qw(qty unit error delivery_order_items_stock_id);
1444 get_basic_bin_wh_info($stock_info);
1446 foreach my $sinfo (@{ $stock_info }) {
1447 map { $sinfo->{"stock_$_"} = $sinfo->{$_} } qw(qty unit);
1451 $form->header(no_layout => 1);
1452 print $form->parse_html_template('do/stock_out_form', { 'UNITS' => $units_data,
1453 'WHCONTENTS' => $form->{delivered} ? $stock_info : \@contents,
1454 'PART_INFO' => $part_info, });
1456 $main::lxdebug->leave_sub();
1460 $main::lxdebug->enter_sub();
1462 my $form = $main::form;
1463 my %myconfig = %main::myconfig;
1464 my $locale = $main::locale;
1466 my $stock_info = [];
1468 foreach my $i (1 .. $form->{rowcount}) {
1469 $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
1471 next if ($form->{"qty_$i"} <= 0);
1473 push @{ $stock_info }, {
1474 'warehouse_id' => $form->{"warehouse_id_$i"},
1475 'bin_id' => $form->{"bin_id_$i"},
1476 'chargenumber' => $form->{"chargenumber_$i"},
1477 'bestbefore' => $form->{"bestbefore_$i"},
1478 'qty' => $form->{"qty_$i"},
1479 'unit' => $form->{"unit_$i"},
1481 'delivery_order_items_stock_id' => $form->{"delivery_order_items_stock_id_$i"},
1485 my @errors = DO->check_stock_availability('requests' => $stock_info,
1486 'parts_id' => $form->{parts_id});
1488 $form->{stock} = SL::YAML::Dump($stock_info);
1491 $form->{ERRORS} = [];
1492 map { push @{ $form->{ERRORS} }, $locale->text('Error in row #1: The quantity you entered is bigger than the stocked quantity.', $_->{row}); } @errors;
1493 stock_in_out_form();
1496 _stock_in_out_set_qty_display($stock_info);
1498 my $do_qty = AM->sum_with_unit($::form->parse_amount(\%::myconfig, $::form->{do_qty}), $::form->{do_unit});
1499 my $transfer_qty = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
1502 print $form->parse_html_template('do/set_stock_in_out', {
1503 qty_matches => $do_qty == $transfer_qty,
1507 $main::lxdebug->leave_sub();
1511 $main::lxdebug->enter_sub();
1513 my $form = $main::form;
1514 my %myconfig = %main::myconfig;
1515 my $locale = $main::locale;
1517 if ($form->{id} && DO->is_marked_as_delivered(id => $form->{id})) {
1518 $form->show_generic_error($locale->text('The parts for this delivery order have already been transferred in.'));
1521 save(no_redirect => 1);
1523 my @part_ids = map { $form->{"id_${_}"} } grep { $form->{"id_${_}"} && $form->{"stock_in_${_}"} } (1 .. $form->{rowcount});
1527 my $units = AM->retrieve_units(\%myconfig, $form);
1528 my %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
1531 $form->{ERRORS} = [];
1533 foreach my $i (1 .. $form->{rowcount}) {
1534 next unless ($form->{"id_$i"} && $form->{"stock_in_$i"});
1536 my $row_sum_base_qty = 0;
1537 my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
1539 foreach my $request (@{ DO->unpack_stock_information('packed' => $form->{"stock_in_$i"}) }) {
1540 $request->{parts_id} = $form->{"id_$i"};
1541 $row_sum_base_qty += $request->{qty} * $units->{$request->{unit}}->{factor} / $base_unit_factor;
1543 $request->{project_id} = $form->{"project_id_$i"} || $form->{globalproject_id};
1545 push @all_requests, $request;
1548 next if (0 == $row_sum_base_qty);
1550 my $do_base_qty = $form->parse_amount(\%myconfig, $form->{"qty_$i"}) * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
1552 # if ($do_base_qty != $row_sum_base_qty) {
1553 # push @{ $form->{ERRORS} }, $locale->text('Error in position #1: You must either assign no stock at all or the full quantity of #2 #3.',
1554 # $i, $form->{"qty_$i"}, $form->{"unit_$i"});
1558 if (@{ $form->{ERRORS} }) {
1559 push @{ $form->{ERRORS} }, $locale->text('The delivery order has not been marked as delivered. The warehouse contents have not changed.');
1561 set_headings('edit');
1563 $main::lxdebug->leave_sub();
1565 $::dispatcher->end_request;
1569 DO->transfer_in_out('direction' => 'in',
1570 'requests' => \@all_requests);
1572 SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
1574 $form->{callback} = 'do.pl?action=edit&type=purchase_delivery_order&id=' . $form->escape($form->{id});
1577 $main::lxdebug->leave_sub();
1581 $main::lxdebug->enter_sub();
1583 my $form = $main::form;
1584 my %myconfig = %main::myconfig;
1585 my $locale = $main::locale;
1587 if ($form->{id} && DO->is_marked_as_delivered(id => $form->{id})) {
1588 $form->show_generic_error($locale->text('The parts for this delivery order have already been transferred out.'));
1591 save(no_redirect => 1);
1593 my @part_ids = map { $form->{"id_${_}"} } grep { $form->{"id_${_}"} && $form->{"stock_out_${_}"} } (1 .. $form->{rowcount});
1597 my $units = AM->retrieve_units(\%myconfig, $form);
1598 my %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
1601 $form->{ERRORS} = [];
1603 foreach my $i (1 .. $form->{rowcount}) {
1604 next unless ($form->{"id_$i"} && $form->{"stock_out_$i"});
1606 my $row_sum_base_qty = 0;
1607 my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
1609 foreach my $request (@{ DO->unpack_stock_information('packed' => $form->{"stock_out_$i"}) }) {
1610 $request->{parts_id} = $form->{"id_$i"};
1611 $request->{base_qty} = $request->{qty} * $units->{$request->{unit}}->{factor} / $base_unit_factor;
1612 $request->{project_id} = $form->{"project_id_$i"} ? $form->{"project_id_$i"} : $form->{globalproject_id};
1614 my $map_key = join '--', ($form->{"id_$i"}, @{$request}{qw(warehouse_id bin_id chargenumber bestbefore)});
1616 $request_map{$map_key} ||= $request;
1617 $request_map{$map_key}->{sum_base_qty} ||= 0;
1618 $request_map{$map_key}->{sum_base_qty} += $request->{base_qty};
1619 $row_sum_base_qty += $request->{base_qty};
1621 push @all_requests, $request;
1624 next if (0 == $row_sum_base_qty);
1626 my $do_base_qty = $form->{"qty_$i"} * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
1628 # if ($do_base_qty != $row_sum_base_qty) {
1629 # push @{ $form->{ERRORS} }, $locale->text('Error in position #1: You must either assign no transfer at all or the full quantity of #2 #3.',
1630 # $i, $form->{"qty_$i"}, $form->{"unit_$i"});
1635 my @bin_ids = map { $_->{bin_id} } values %request_map;
1636 my %bin_info_map = WH->get_basic_bin_info('id' => \@bin_ids);
1637 my @contents = DO->get_item_availability('parts_id' => \@part_ids);
1639 foreach my $inv (@contents) {
1640 my $map_key = join '--', @{$inv}{qw(parts_id warehouse_id bin_id chargenumber bestbefore)};
1642 next unless ($request_map{$map_key});
1644 my $request = $request_map{$map_key};
1645 $request->{ok} = $request->{sum_base_qty} <= $inv->{qty};
1648 foreach my $request (values %request_map) {
1649 next if ($request->{ok});
1651 my $pinfo = $part_info_map{$request->{parts_id}};
1652 my $binfo = $bin_info_map{$request->{bin_id}};
1654 if ($::instance_conf->get_show_bestbefore) {
1655 push @{ $form->{ERRORS} }, $locale->text("There is not enough available of '#1' at warehouse '#2', bin '#3', #4, #5, for the transfer of #6.",
1656 $pinfo->{description},
1657 $binfo->{warehouse_description},
1658 $binfo->{bin_description},
1659 $request->{chargenumber} ? $locale->text('chargenumber #1', $request->{chargenumber}) : $locale->text('no chargenumber'),
1660 $request->{bestbefore} ? $locale->text('bestbefore #1', $request->{bestbefore}) : $locale->text('no bestbefore'),
1661 $form->format_amount_units('amount' => $request->{sum_base_qty},
1662 'part_unit' => $pinfo->{unit},
1663 'conv_units' => 'convertible_not_smaller'));
1665 push @{ $form->{ERRORS} }, $locale->text("There is not enough available of '#1' at warehouse '#2', bin '#3', #4, for the transfer of #5.",
1666 $pinfo->{description},
1667 $binfo->{warehouse_description},
1668 $binfo->{bin_description},
1669 $request->{chargenumber} ? $locale->text('chargenumber #1', $request->{chargenumber}) : $locale->text('no chargenumber'),
1670 $form->format_amount_units('amount' => $request->{sum_base_qty},
1671 'part_unit' => $pinfo->{unit},
1672 'conv_units' => 'convertible_not_smaller'));
1677 if (@{ $form->{ERRORS} }) {
1678 push @{ $form->{ERRORS} }, $locale->text('The delivery order has not been marked as delivered. The warehouse contents have not changed.');
1680 set_headings('edit');
1682 $main::lxdebug->leave_sub();
1684 $::dispatcher->end_request;
1687 DO->transfer_in_out('direction' => 'out',
1688 'requests' => \@all_requests);
1690 SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
1692 $form->{callback} = 'do.pl?action=edit&type=sales_delivery_order&id=' . $form->escape($form->{id});
1695 $main::lxdebug->leave_sub();
1699 $main::lxdebug->enter_sub();
1701 my $form = $main::form;
1703 DO->close_orders('ids' => [ $form->{id} ]);
1705 $form->{closed} = 1;
1709 $main::lxdebug->leave_sub();
1713 $::lxdebug->enter_sub;
1715 $::auth->assert('purchase_delivery_order_edit | sales_delivery_order_edit');
1718 retrieve_partunits();
1720 my $new_rowcount = $::form->{"rowcount"} * 1 + 1;
1721 $::form->{"project_id_${new_rowcount}"} = $::form->{"globalproject_id"};
1723 $::form->language_payment(\%::myconfig);
1725 Common::webdav_folder($::form);
1728 display_row(++$::form->{rowcount});
1731 $::lxdebug->leave_sub;
1735 call_sub($main::form->{yes_nextsub});
1739 call_sub($main::form->{no_nextsub});
1743 call_sub($main::form->{update_nextsub} || $main::form->{nextsub} || 'update_delivery_order');
1747 my $form = $main::form;
1748 my $locale = $main::locale;
1750 foreach my $action (qw(update print save transfer_out transfer_out_default sort
1751 transfer_in transfer_in_default mark_closed save_as_new invoice delete)) {
1752 if ($form->{"action_${action}"}) {
1758 $form->error($locale->text('No action defined.'));
1761 sub transfer_out_default {
1762 $main::lxdebug->enter_sub();
1764 my $form = $main::form;
1766 transfer_in_out_default('direction' => 'out');
1768 $main::lxdebug->leave_sub();
1771 sub transfer_in_default {
1772 $main::lxdebug->enter_sub();
1774 my $form = $main::form;
1776 transfer_in_out_default('direction' => 'in');
1778 $main::lxdebug->leave_sub();
1781 # Falls das Standardlagerverfahren aktiv ist, wird
1782 # geprüft, ob alle Standardlagerplätze für die Auslager-
1783 # artikel vorhanden sind UND ob die Warenmenge ausreicht zum
1784 # Auslagern. Falls nicht wird entsprechend eine Fehlermeldung
1785 # generiert. Offen Chargennummer / bestbefore wird nicht berücksichtigt
1786 sub transfer_in_out_default {
1787 $main::lxdebug->enter_sub();
1789 my $form = $main::form;
1790 my %myconfig = %main::myconfig;
1791 my $locale = $main::locale;
1794 my (%missing_default_bins, %qty_parts, @all_requests, %part_info_map, $default_warehouse_id, $default_bin_id);
1796 Common::check_params(\%params, qw(direction));
1798 # entsprechende defaults holen, falls standardlagerplatz verwendet werden soll
1799 if ($::instance_conf->get_transfer_default_use_master_default_bin) {
1800 $default_warehouse_id = $::instance_conf->get_warehouse_id;
1801 $default_bin_id = $::instance_conf->get_bin_id;
1805 my @part_ids = map { $form->{"id_${_}"} } (1 .. $form->{rowcount});
1807 my $units = AM->retrieve_units(\%myconfig, $form);
1808 %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
1809 foreach my $i (1 .. $form->{rowcount}) {
1810 next unless ($form->{"id_$i"});
1811 my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
1812 my $qty = $form->parse_amount(\%myconfig, $form->{"qty_$i"}) * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
1814 $form->show_generic_error($locale->text("Cannot transfer negative entries." )) if ($qty < 0);
1815 # if we do not want to transfer services and this part is a service, set qty to zero
1816 # ... and do not create a hash entry in %qty_parts below (will skip check for bins for the transfer == out case)
1817 # ... and push only a empty (undef) element to @all_requests (will skip check for bin_id and warehouse_id and will not alter the row)
1819 $qty = 0 if (!$::instance_conf->get_transfer_default_services && $part_info_map{$form->{"id_$i"}}->{part_type} eq 'service');
1820 $qty_parts{$form->{"id_$i"}} += $qty;
1822 delete $qty_parts{$form->{"id_$i"}} unless $qty_parts{$form->{"id_$i"}};
1823 undef $form->{"stock_in_$i"};
1826 $part_info_map{$form->{"id_$i"}}{bin_id} ||= $default_bin_id;
1827 $part_info_map{$form->{"id_$i"}}{warehouse_id} ||= $default_warehouse_id;
1829 push @all_requests, ($qty == 0) ? { } : {
1830 'chargenumber' => '', #?? die müsste entsprechend geholt werden
1831 #'bestbefore' => undef, # TODO wird nicht berücksichtigt
1832 'bin_id' => $part_info_map{$form->{"id_$i"}}{bin_id},
1834 'parts_id' => $form->{"id_$i"},
1835 'comment' => $locale->text("Default transfer delivery order"),
1836 'unit' => $part_info_map{$form->{"id_$i"}}{unit},
1837 'warehouse_id' => $part_info_map{$form->{"id_$i"}}{warehouse_id},
1838 'oe_id' => $form->{id},
1839 'project_id' => $form->{"project_id_$i"} ? $form->{"project_id_$i"} : $form->{globalproject_id}
1843 # jetzt wird erst überprüft, ob die Stückzahl entsprechend stimmt.
1844 # check if bin (transfer in and transfer out and qty (transfer out) is correct
1845 foreach my $key (keys %qty_parts) {
1847 $missing_default_bins{$key}{missing_bin} = 1 unless ($part_info_map{$key}{bin_id});
1848 next unless ($part_info_map{$key}{bin_id}); # abbruch
1850 if ($params{direction} eq 'out') { # wird nur für ausgehende Mengen benötigt
1851 my ($max_qty, $error) = WH->get_max_qty_parts_bin(parts_id => $key, bin_id => $part_info_map{$key}{bin_id});
1853 # wir können nicht entscheiden, welche charge oder mhd (bestbefore) ausgewählt sein soll
1854 # deshalb rückmeldung nach oben geben, manuell auszulagern
1855 # TODO Bei nur einem Treffer mit Charge oder bestbefore wäre das noch möglich
1856 $missing_default_bins{$key}{chargenumber} = 1;
1858 if ($max_qty < $qty_parts{$key}){
1859 $missing_default_bins{$key}{missing_qty} = $max_qty - $qty_parts{$key};
1865 # Abfrage für Fehlerbehandlung (nur bei direction == out)
1866 if (scalar (keys %missing_default_bins)) {
1868 foreach my $fehler (keys %missing_default_bins) {
1870 my $ware = WH->get_part_description(parts_id => $fehler);
1871 if ($missing_default_bins{$fehler}{missing_bin}){
1872 $fehlertext .= "Kein Standardlagerplatz definiert bei $ware <br>";
1874 if ($missing_default_bins{$fehler}{missing_qty}) { # missing_qty
1875 $fehlertext .= "Es fehlen " . $missing_default_bins{$fehler}{missing_qty}*-1 .
1876 " von $ware auf dem Standard-Lagerplatz " . $part_info_map{$fehler}{bin} . " zum Auslagern<br>";
1878 if ($missing_default_bins{$fehler}{chargenumber}){
1879 $fehlertext .= "Die Ware hat eine Chargennummer oder eine Mindesthaltbarkeit definiert.
1880 Hier kann man nicht automatisch entscheiden.
1881 Bitte diesen Lieferschein manuell auslagern.
1884 # auslagern soll immer gehen, auch wenn nicht genügend auf lager ist.
1885 # der lagerplatz ist hier extra konfigurierbar, bspw. Lager-Korrektur mit
1886 # Lagerplatz Lagerplatz-Korrektur
1887 my $default_warehouse_id_ignore_onhand = $::instance_conf->get_warehouse_id_ignore_onhand;
1888 my $default_bin_id_ignore_onhand = $::instance_conf->get_bin_id_ignore_onhand;
1889 if ($::instance_conf->get_transfer_default_ignore_onhand && $default_bin_id_ignore_onhand) {
1890 # entsprechende defaults holen
1891 # falls chargenumber, bestbefore oder anzahl nicht stimmt, auf automatischen
1892 # lagerplatz wegbuchen!
1893 foreach (@all_requests) {
1894 if ($_->{parts_id} eq $fehler){
1895 $_->{bin_id} = $default_bin_id_ignore_onhand;
1896 $_->{warehouse_id} = $default_warehouse_id_ignore_onhand;
1900 #$main::lxdebug->message(0, 'Fehlertext: ' . $fehlertext);
1901 $form->show_generic_error($locale->text("Cannot transfer. <br> Reason:<br>#1", $fehlertext ));
1907 # hier der eigentliche fallunterschied für in oder out
1908 my $prefix = $params{direction} eq 'in' ? 'in' : 'out';
1910 # dieser array_ref ist für DO->save da:
1911 # einmal die all_requests in YAML verwandeln, damit delivery_order_items_stock
1912 # gefüllt werden kann.
1913 # could be dumped to the form in the first loop,
1914 # but maybe bin_id and warehouse_id has changed to the "korrekturlager" with
1915 # allowed negative qty ($::instance_conf->get_warehouse_id_ignore_onhand) ...
1917 foreach (@all_requests){
1919 next unless scalar(%{ $_ });
1920 $form->{"stock_${prefix}_$i"} = SL::YAML::Dump([$_]);
1923 save(no_redirect => 1); # Wir können auslagern, deshalb beleg speichern
1924 # und in delivery_order_items_stock speichern
1926 # ... and fill back the persistent dois_id for inventory fk
1927 undef (@all_requests);
1928 foreach my $i (1 .. $form->{rowcount}) {
1929 next unless ($form->{"id_$i"} && $form->{"stock_${prefix}_$i"});
1930 push @all_requests, @{ DO->unpack_stock_information('packed' => $form->{"stock_${prefix}_$i"}) };
1932 DO->transfer_in_out('direction' => $prefix,
1933 'requests' => \@all_requests);
1935 SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
1937 $form->{callback} = 'do.pl?action=edit&type=sales_delivery_order&id=' . $form->escape($form->{id}) if $params{direction} eq 'out';
1938 $form->{callback} = 'do.pl?action=edit&type=purchase_delivery_order&id=' . $form->escape($form->{id}) if $params{direction} eq 'in';
1944 $main::lxdebug->enter_sub();
1948 my $form = $main::form;
1951 save(no_redirect => 1); # has to be done, at least for newly added positions
1953 # hashify partnumbers, positions. key is delivery_order_items_id
1954 for my $i (1 .. ($form->{rowcount}) ) {
1955 $temp_hash{$form->{"delivery_order_items_id_$i"}} = { runningnumber => $form->{"runningnumber_$i"}, partnumber => $form->{"partnumber_$i"} };
1956 if ($form->{id} && $form->{"discount_$i"}) {
1957 # prepare_order assumes a db value if there is a form->id and multiplies *100
1958 # We hope for new controller code (no more format_amount/parse_amount distinction)
1959 $form->{"discount_$i"} /=100;
1962 # naturally sort partnumbers and get a sorted array of doi_ids
1963 my @sorted_doi_ids = sort { Sort::Naturally::ncmp($temp_hash{$a}->{"partnumber"}, $temp_hash{$b}->{"partnumber"}) } keys %temp_hash;
1968 for (@sorted_doi_ids) {
1969 $form->{"runningnumber_$temp_hash{$_}->{runningnumber}"} = $new_number;
1972 # all parse_amounts changes are in form (i.e. , to .) therefore we need
1973 # another format_amount to change it back, for the next save ;-(
1974 # works great except for row discounts (see above comment)
1978 $main::lxdebug->leave_sub();
1990 do.pl - Script for all calls to delivery order
1998 Sorts all position with Natural Sort. Can be activated in form_footer.html like this
1999 C<E<lt>input class="submit" type="submit" name="action_sort" id="sort_button" value="[% 'Sort and Save' | $T8 %]"E<gt>>
2005 Sort and Save can be implemented as an optional button if configuration ca be set by client config.
2006 Example coding for database scripts and templates in (git show af2f24b8), check also
2007 autogeneration for rose (scripts/rose_auto_create_model.pl --h)