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);
40 use SL::DB::DeliveryOrder;
44 use SL::MoreCommon qw(ary_diff restore_form save_form);
45 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();
240 $main::lxdebug->enter_sub();
244 my $form = $main::form;
245 my %myconfig = %main::myconfig;
247 my $class = "SL::DB::" . ($form->{vc} eq 'customer' ? 'Customer' : 'Vendor');
248 $form->{VC_OBJ} = $class->load_cached($form->{ $form->{vc} . '_id' });
250 $form->{employee_id} = $form->{old_employee_id} if $form->{old_employee_id};
251 $form->{salesman_id} = $form->{old_salesman_id} if $form->{old_salesman_id};
253 my $vc = $form->{vc} eq "customer" ? "customers" : "vendors";
254 $form->get_lists("price_factors" => "ALL_PRICE_FACTORS",
255 "business_types" => "ALL_BUSINESS_TYPES",
257 $form->{ALL_DEPARTMENTS} = SL::DB::Manager::Department->get_all;
260 my @old_project_ids = uniq grep { $_ } map { $_ * 1 } ($form->{"globalproject_id"}, map { $form->{"project_id_$_"} } 1..$form->{"rowcount"});
261 my @old_ids_cond = @old_project_ids ? (id => \@old_project_ids) : ();
263 if (($vc eq 'customers') && $::instance_conf->get_customer_projects_only_in_sales) {
266 customer_id => $::form->{customer_id},
267 billable_customer_id => $::form->{customer_id},
272 and => [ active => 1, @customer_cond ],
276 $::form->{ALL_PROJECTS} = SL::DB::Manager::Project->get_all_sorted(query => \@conditions);
277 $::form->{ALL_EMPLOYEES} = SL::DB::Manager::Employee->get_all_sorted(query => [ or => [ id => $::form->{employee_id}, deleted => 0 ] ]);
278 $::form->{ALL_SALESMEN} = SL::DB::Manager::Employee->get_all_sorted(query => [ or => [ id => $::form->{salesman_id}, deleted => 0 ] ]);
279 $::form->{ALL_SHIPTO} = SL::DB::Manager::Shipto->get_all_sorted(query => [
280 or => [ trans_id => $::form->{"$::form->{vc}_id"} * 1, and => [ shipto_id => $::form->{shipto_id} * 1, trans_id => undef ] ]
282 $::form->{ALL_CONTACTS} = SL::DB::Manager::Contact->get_all_sorted(query => [
284 cp_cv_id => $::form->{"$::form->{vc}_id"} * 1,
287 cp_id => $::form->{cp_id} * 1
292 my $dispatch_to_popup = '';
293 if ($form->{resubmit} && ($form->{format} eq "html")) {
294 $dispatch_to_popup = "window.open('about:blank','Beleg'); document.do.target = 'Beleg';";
295 $dispatch_to_popup .= "document.do.submit();";
296 } elsif ($form->{resubmit}) {
297 # emulate click for resubmitting actions
298 $dispatch_to_popup = "document.do.${_}.click(); " for grep { /^action_/ } keys %$form;
300 $::request->{layout}->add_javascripts_inline("\$(function(){$dispatch_to_popup});");
303 $form->{follow_up_trans_info} = $form->{donumber} .'('. $form->{VC_OBJ}->name .')';
305 $::request->{layout}->use_javascript(map { "${_}.js" } qw(kivi.File kivi.MassDeliveryOrderPrint kivi.SalesPurchase ckeditor/ckeditor ckeditor/adapters/jquery kivi.io autocomplete_customer autocomplete_part));
308 push @custom_hidden, map { "shiptocvar_" . $_->name } @{ SL::DB::Manager::CustomVariableConfig->get_all(where => [ module => 'ShipTo' ]) };
310 $::form->{HIDDENS} = [ map { +{ name => $_, value => $::form->{$_} } } (@custom_hidden) ];
313 # Fix für Bug 1082 Erwartet wird: 'abteilungsNAME--abteilungsID'
314 # und Erweiterung für Bug 1760:
315 # Das war leider nur ein Teil-Fix, da das Verhalten den 'Erneuern'-Knopf
316 # nicht überlebt. Konsequent jetzt auf L umgestellt
317 # $ perldoc SL::Template::Plugin::L
318 # Daher entsprechend nur die Anpassung in form_header
319 # und in DO.pm gemacht. 4 Testfälle:
320 # department_id speichern | i.O.
321 # department_id lesen | i.O.
322 # department leer überlebt erneuern | i.O.
323 # department nicht leer überlebt erneuern | i.O.
324 # $main::lxdebug->message(0, 'ABTEILUNGS ID in form?' . $form->{department_id});
325 print $form->parse_html_template('do/form_header');
327 $main::lxdebug->leave_sub();
331 $main::lxdebug->enter_sub();
335 my $form = $main::form;
337 $form->{PRINT_OPTIONS} = print_options('inline' => 1);
338 $form->{ALL_DELIVERY_TERMS} = SL::DB::Manager::DeliveryTerm->get_all_sorted();
340 print $form->parse_html_template('do/form_footer',
341 {transfer_default => ($::instance_conf->get_transfer_default)});
343 $main::lxdebug->leave_sub();
346 sub update_delivery_order {
347 $main::lxdebug->enter_sub();
351 my $form = $main::form;
352 my %myconfig = %main::myconfig;
354 set_headings($form->{"id"} ? "edit" : "add");
356 $form->{insertdate} = SL::DB::DeliveryOrder->new(id => $form->{id})->load->itime_as_date if $form->{id};
361 $payment_id = $form->{payment_id} if $form->{payment_id};
363 my $vc = $form->{vc};
364 if (($form->{"previous_${vc}_id"} || $form->{"${vc}_id"}) != $form->{"${vc}_id"}) {
365 $::form->{salesman_id} = SL::DB::Manager::Employee->current->id if exists $::form->{salesman_id};
367 IS->get_customer(\%myconfig, $form) if $vc eq 'customer';
368 IR->get_vendor(\%myconfig, $form) if $vc eq 'vendor';
371 $form->{discount} = $form->{"$form->{vc}_discount"} if defined $form->{"$form->{vc}_discount"};
372 # Problem: Wenn man ohne Erneuern einen Kunden/Lieferanten
373 # wechselt, wird der entsprechende Kunden/ Lieferantenrabatt
374 # nicht übernommen. Grundproblem: In Commit 82574e78
375 # hab ich aus discount customer_discount und vendor_discount
376 # gemacht und entsprechend an den Oberflächen richtig hin-
377 # geschoben. Die damals bessere Lösung wäre gewesen:
378 # In den Templates nur die hidden für form-discount wieder ein-
379 # setzen dann wäre die Verrenkung jetzt nicht notwendig.
380 # TODO: Ggf. Bugfix 1284, 1575 und 817 wieder zusammenführen
381 # Testfälle: Kunden mit Rabatt 0 -> Rabatt 20 i.O.
382 # Kunde mit Rabatt 20 -> Rabatt 0 i.O.
383 # Kunde mit Rabatt 20 -> Rabatt 5,5 i.O.
384 $form->{payment_id} = $payment_id if $form->{payment_id} eq "";
386 my $i = $form->{rowcount};
388 if ( ($form->{"partnumber_$i"} eq "")
389 && ($form->{"description_$i"} eq "")
390 && ($form->{"partsgroup_$i"} eq "")) {
397 if ($form->{type} eq 'purchase_delivery_order') {
398 IR->retrieve_item(\%myconfig, $form);
401 IS->retrieve_item(\%myconfig, $form);
405 my $rows = scalar @{ $form->{item_list} };
408 $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
409 if( !$form->{"qty_$i"} ) {
410 $form->{"qty_$i"} = 1;
415 select_item(mode => $mode, pre_entered_qty => $form->{"qty_$i"});
416 $::dispatcher->end_request;
420 my $sellprice = $form->parse_amount(\%myconfig, $form->{"sellprice_$i"});
422 map { $form->{"${_}_$i"} = $form->{item_list}[0]{$_} } keys %{ $form->{item_list}[0] };
424 $form->{"marge_price_factor_$i"} = $form->{item_list}->[0]->{price_factor};
427 $form->{"sellprice_$i"} = $sellprice;
429 my $record = _make_record();
430 my $price_source = SL::PriceSource->new(record_item => $record->items->[$i-1], record => $record);
431 my $best_price = $price_source->best_price;
432 my $best_discount = $price_source->best_discount;
435 $::form->{"sellprice_$i"} = $best_price->price;
436 $::form->{"active_price_source_$i"} = $best_price->source;
438 if ($best_discount) {
439 $::form->{"discount_$i"} = $best_discount->discount;
440 $::form->{"active_discount_source_$i"} = $best_discount->source;
444 $form->{"sellprice_$i"} = $form->format_amount(\%myconfig, $form->{"sellprice_$i"});
445 $form->{"lastcost_$i"} = $form->format_amount(\%myconfig, $form->{"lastcost_$i"});
446 $form->{"qty_$i"} = $form->format_amount(\%myconfig, $form->{"qty_$i"});
447 $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"} * 100.0);
454 # ok, so this is a new part
455 # ask if it is a part or service item
457 if ( $form->{"partsgroup_$i"}
458 && ($form->{"partsnumber_$i"} eq "")
459 && ($form->{"description_$i"} eq "")) {
461 $form->{"discount_$i"} = "";
462 $form->{"not_discountable_$i"} = "";
466 $form->{"id_$i"} = 0;
472 $main::lxdebug->leave_sub();
476 $main::lxdebug->enter_sub();
480 my $form = $main::form;
481 my %myconfig = %main::myconfig;
482 my $locale = $main::locale;
484 $form->{vc} = $form->{type} eq 'purchase_delivery_order' ? 'vendor' : 'customer';
486 $form->get_lists("projects" => { "key" => "ALL_PROJECTS",
488 "business_types" => "ALL_BUSINESS_TYPES");
489 $form->{ALL_EMPLOYEES} = SL::DB::Manager::Employee->get_all_sorted(query => [ deleted => 0 ]);
490 $form->{ALL_DEPARTMENTS} = SL::DB::Manager::Department->get_all;
491 $form->{title} = $locale->text('Delivery Orders');
495 print $form->parse_html_template('do/search');
497 $main::lxdebug->leave_sub();
501 $main::lxdebug->enter_sub();
505 my $form = $main::form;
506 my %myconfig = %main::myconfig;
507 my $locale = $main::locale;
508 my $cgi = $::request->{cgi};
510 $::request->{layout}->use_javascript(map { "${_}.js" } qw(kivi.MassDeliveryOrderPrint));
511 ($form->{ $form->{vc} }, $form->{"$form->{vc}_id"}) = split(/--/, $form->{ $form->{vc} });
513 report_generator_set_default_sort('transdate', 1);
517 $form->{rowcount} = scalar @{ $form->{DO} };
520 ids transdate reqdate
522 ordnumber customernumber cusordnumber
523 name employee salesman
524 shipvia globalprojectnumber
525 transaction_description department
530 $form->{l_open} = $form->{l_closed} = "Y" if ($form->{open} && $form->{closed});
531 $form->{l_delivered} = "Y" if ($form->{delivered} && $form->{notdelivered});
533 $form->{title} = $locale->text('Delivery Orders');
535 my $attachment_basename = $form->{vc} eq 'vendor' ? $locale->text('purchase_delivery_order_list') : $locale->text('sales_delivery_order_list');
537 my $report = SL::ReportGenerator->new(\%myconfig, $form);
539 my @hidden_variables = map { "l_${_}" } @columns;
540 push @hidden_variables, $form->{vc}, qw(l_closed l_notdelivered open closed delivered notdelivered donumber ordnumber serialnumber cusordnumber
541 transaction_description transdatefrom transdateto reqdatefrom reqdateto
542 type vc employee_id salesman_id project_id parts_partnumber parts_description
543 insertdatefrom insertdateto business_id);
545 my $href = build_std_url('action=orders', grep { $form->{$_} } @hidden_variables);
548 'ids' => { 'text' => '<input type="checkbox" id="multi_all" value="1">', 'align' => 'center' },
549 'transdate' => { 'text' => $locale->text('Delivery Order Date'), },
550 'reqdate' => { 'text' => $locale->text('Reqdate'), },
551 'id' => { 'text' => $locale->text('ID'), },
552 'donumber' => { 'text' => $locale->text('Delivery Order'), },
553 'ordnumber' => { 'text' => $locale->text('Order'), },
554 'customernumber' => { 'text' => $locale->text('Customer Number'), },
555 'cusordnumber' => { 'text' => $locale->text('Customer Order Number'), },
556 'name' => { 'text' => $form->{vc} eq 'customer' ? $locale->text('Customer') : $locale->text('Vendor'), },
557 'employee' => { 'text' => $locale->text('Employee'), },
558 'salesman' => { 'text' => $locale->text('Salesman'), },
559 'shipvia' => { 'text' => $locale->text('Ship via'), },
560 'globalprojectnumber' => { 'text' => $locale->text('Project Number'), },
561 'transaction_description' => { 'text' => $locale->text('Transaction description'), },
562 'open' => { 'text' => $locale->text('Open'), },
563 'delivered' => { 'text' => $locale->text('Delivered'), },
564 'department' => { 'text' => $locale->text('Department'), },
565 'insertdate' => { 'text' => $locale->text('Insert Date'), },
568 foreach my $name (qw(id transdate reqdate donumber ordnumber name employee salesman shipvia transaction_description department insertdate)) {
569 my $sortdir = $form->{sort} eq $name ? 1 - $form->{sortdir} : $form->{sortdir};
570 $column_defs{$name}->{link} = $href . "&sort=$name&sortdir=$sortdir";
573 $form->{"l_type"} = "Y";
574 map { $column_defs{$_}->{visible} = $form->{"l_${_}"} ? 1 : 0 } @columns;
576 $column_defs{ids}->{visible} = 'HTML';
578 $report->set_columns(%column_defs);
579 $report->set_column_order(@columns);
581 $report->set_export_options('orders', @hidden_variables, qw(sort sortdir));
583 $report->set_sort_indicator($form->{sort}, $form->{sortdir});
586 if ($form->{customer}) {
587 push @options, $locale->text('Customer') . " : $form->{customer}";
589 if ($form->{vendor}) {
590 push @options, $locale->text('Vendor') . " : $form->{vendor}";
592 if ($form->{cp_name}) {
593 push @options, $locale->text('Contact Person') . " : $form->{cp_name}";
595 if ($form->{department_id}) {
596 push @options, $locale->text('Department') . " : " . SL::DB::Department->new(id => $form->{department_id})->load->description;
598 if ($form->{donumber}) {
599 push @options, $locale->text('Delivery Order Number') . " : $form->{donumber}";
601 if ($form->{ordnumber}) {
602 push @options, $locale->text('Order Number') . " : $form->{ordnumber}";
604 push @options, $locale->text('Serial Number') . " : $form->{serialnumber}" if $form->{serialnumber};
605 if ($form->{business_id}) {
606 my $vc_type_label = $form->{vc} eq 'customer' ? $locale->text('Customer type') : $locale->text('Vendor type');
607 push @options, $vc_type_label . " : " . SL::DB::Business->new(id => $form->{business_id})->load->description;
609 if ($form->{transaction_description}) {
610 push @options, $locale->text('Transaction description') . " : $form->{transaction_description}";
612 if ($form->{parts_description}) {
613 push @options, $locale->text('Part Description') . " : $form->{parts_description}";
615 if ($form->{parts_partnumber}) {
616 push @options, $locale->text('Part Number') . " : $form->{parts_partnumber}";
618 if ( $form->{transdatefrom} or $form->{transdateto} ) {
619 push @options, $locale->text('Delivery Order Date');
620 push @options, $locale->text('From') . " " . $locale->date(\%myconfig, $form->{transdatefrom}, 1) if $form->{transdatefrom};
621 push @options, $locale->text('Bis') . " " . $locale->date(\%myconfig, $form->{transdateto}, 1) if $form->{transdateto};
623 if ( $form->{reqdatefrom} or $form->{reqdateto} ) {
624 push @options, $locale->text('Reqdate');
625 push @options, $locale->text('From') . " " . $locale->date(\%myconfig, $form->{reqdatefrom}, 1) if $form->{reqdatefrom};
626 push @options, $locale->text('Bis') . " " . $locale->date(\%myconfig, $form->{reqdateto}, 1) if $form->{reqdateto};
628 if ( $form->{insertdatefrom} or $form->{insertdateto} ) {
629 push @options, $locale->text('Insert Date');
630 push @options, $locale->text('From') . " " . $locale->date(\%myconfig, $form->{insertdatefrom}, 1) if $form->{insertdatefrom};
631 push @options, $locale->text('Bis') . " " . $locale->date(\%myconfig, $form->{insertdateto}, 1) if $form->{insertdateto};
634 push @options, $locale->text('Open');
636 if ($form->{closed}) {
637 push @options, $locale->text('Closed');
639 if ($form->{delivered}) {
640 push @options, $locale->text('Delivered');
642 if ($form->{notdelivered}) {
643 push @options, $locale->text('Not delivered');
646 # all_vc ruft get_employee auf, dort wird emloyee überschrieben, deshalb retten:
647 my $save_employee_id = $form->{'employee_id'};
648 my $save_employee = $form->{'employee'};
649 $form->all_vc(\%myconfig, $form->{vc}, ($form->{vc} eq 'customer') ? "AR" : "AP");
650 $form->{'employee_id'} = $save_employee_id;
651 $form->{'employee'} = $save_employee;
653 my $pr = SL::DB::Manager::Printer->find_by(
654 printer_description => $::locale->text("sales_delivery_order_printer"));
656 $form->{printer_id} = $pr->id;
659 $report->set_options('top_info_text' => join("\n", @options),
660 'raw_top_info_text' => $form->parse_html_template('do/orders_top'),
661 'raw_bottom_info_text' => $form->parse_html_template('do/orders_bottom',
663 print_options => print_options(inline => 1,hide_language_id => 1),
665 'output_format' => 'HTML',
666 'title' => $form->{title},
667 'attachment_basename' => $attachment_basename . strftime('_%Y%m%d', localtime time),
669 $report->set_options_from_form();
670 $locale->set_numberformat_wo_thousands_separator(\%myconfig) if lc($report->{options}->{output_format}) eq 'csv';
672 # add sort and escape callback, this one we use for the add sub
673 $form->{callback} = $href .= "&sort=$form->{sort}";
675 # escape callback for href
676 my $callback = $form->escape($href);
678 my $edit_url = build_std_url('action=edit', 'type', 'vc');
679 my $edit_order_url = build_std_url('script=oe.pl', 'type=' . ($form->{type} eq 'sales_delivery_order' ? 'sales_order' : 'purchase_order'), 'action=edit');
683 foreach my $dord (@{ $form->{DO} }) {
684 $dord->{open} = $dord->{closed} ? $locale->text('No') : $locale->text('Yes');
685 $dord->{delivered} = $dord->{delivered} ? $locale->text('Yes') : $locale->text('No');
687 my $row = { map { $_ => { 'data' => $dord->{$_} } } @columns };
689 my $ord_id = $dord->{id};
691 'raw_data' => $cgi->hidden('-name' => "trans_id_${idx}", '-value' => $ord_id)
692 . $cgi->checkbox('-name' => "multi_id_${idx}",' id' => "multi_id_id_".$ord_id, '-value' => 1, '-label' => ''),
693 'valign' => 'center',
697 $row->{donumber}->{link} = $edit_url . "&id=" . E($dord->{id}) . "&callback=${callback}";
698 $row->{ordnumber}->{link} = $edit_order_url . "&id=" . E($dord->{oe_id}) . "&callback=${callback}" if $dord->{oe_id};
699 $report->add_data($row);
704 $report->generate_with_headers();
706 $main::lxdebug->leave_sub();
710 $main::lxdebug->enter_sub();
716 my $form = $main::form;
717 my %myconfig = %main::myconfig;
718 my $locale = $main::locale;
720 $form->mtime_ischanged('delivery_orders');
722 $form->{defaultcurrency} = $form->get_default_currency(\%myconfig);
724 $form->isblank("transdate", $locale->text('Delivery Order Date missing!'));
726 $form->{donumber} =~ s/^\s*//g;
727 $form->{donumber} =~ s/\s*$//g;
729 my $msg = ucfirst $form->{vc};
730 $form->isblank($form->{vc} . "_id", $locale->text($msg . " missing!"));
732 # $locale->text('Customer missing!');
733 # $locale->text('Vendor missing!');
735 remove_emptied_rows();
738 # if the name changed get new values
739 my $vc = $form->{vc};
740 if (($form->{"previous_${vc}_id"} || $form->{"${vc}_id"}) != $form->{"${vc}_id"}) {
741 $::form->{salesman_id} = SL::DB::Manager::Employee->current->id if exists $::form->{salesman_id};
743 IS->get_customer(\%myconfig, $form) if $vc eq 'customer';
744 IR->get_vendor(\%myconfig, $form) if $vc eq 'vendor';
747 $::dispatcher->end_request;
750 $form->{id} = 0 if $form->{saveasnew};
754 if(!exists $form->{addition}) {
755 $form->{snumbers} = qq|donumber_| . $form->{donumber};
756 $form->{addition} = "SAVED";
759 # /saving the history
761 $form->{simple_save} = 1;
762 if (!$params{no_redirect} && !$form->{print_and_save}) {
763 delete @{$form}{ary_diff([keys %{ $form }], [qw(login id script type cursor_fokus)])};
765 $::dispatcher->end_request;
767 $main::lxdebug->leave_sub();
771 $main::lxdebug->enter_sub();
775 my $form = $main::form;
776 my %myconfig = %main::myconfig;
777 my $locale = $main::locale;
781 if(!exists $form->{addition}) {
782 $form->{snumbers} = qq|donumber_| . $form->{donumber};
783 $form->{addition} = "DELETED";
786 # /saving the history
788 $form->info($locale->text('Delivery Order deleted!'));
789 $::dispatcher->end_request;
792 $form->error($locale->text('Cannot delete delivery order!'));
794 $main::lxdebug->leave_sub();
798 $main::lxdebug->enter_sub();
800 my $form = $main::form;
801 my %myconfig = %main::myconfig;
802 my $locale = $main::locale;
805 $form->mtime_ischanged('delivery_orders');
807 $main::auth->assert($form->{type} eq 'purchase_delivery_order' ? 'vendor_invoice_edit' : 'invoice_edit');
809 $form->{convert_from_do_ids} = $form->{id};
810 $form->{deliverydate} = $form->{transdate};
811 $form->{transdate} = $form->{invdate} = $form->current_date(\%myconfig);
812 $form->{duedate} = $form->current_date(\%myconfig, $form->{invdate}, $form->{terms} * 1);
813 $form->{defaultcurrency} = $form->get_default_currency(\%myconfig);
817 delete @{$form}{qw(id closed delivered)};
819 my ($script, $buysell);
820 if ($form->{type} eq 'purchase_delivery_order') {
821 $form->{title} = $locale->text('Add Vendor Invoice');
822 $form->{script} = 'ir.pl';
827 $form->{title} = $locale->text('Add Sales Invoice');
828 $form->{script} = 'is.pl';
833 for my $i (1 .. $form->{rowcount}) {
834 map { $form->{"${_}_${i}"} = $form->parse_amount(\%myconfig, $form->{"${_}_${i}"}) if $form->{"${_}_${i}"} } qw(ship qty sellprice lastcost basefactor discount);
836 # adds a customer/vendor discount, unless we have a workflow case
837 # CAVEAT: has to be done, after the above parse_amount
838 unless ($form->{"ordnumber"}) {
839 if ($form->{discount}) { # Falls wir einen Lieferanten-/Kundenrabatt haben
840 # und rabattfähig sind, dann
841 unless ($form->{"not_discountable_$i"}) {
842 $form->{"discount_$i"} = $form->{discount}*100; # ... nehmen wir diesen Rabatt
846 $form->{"donumber_$i"} = $form->{donumber};
847 $form->{"converted_from_delivery_order_items_id_$i"} = delete $form->{"delivery_order_items_id_$i"};
850 $form->{type} = "invoice";
853 $main::locale = Locale->new("$myconfig{countrycode}", "$script");
854 $locale = $main::locale;
856 require "bin/mozilla/$form->{script}";
858 my $currency = $form->{currency};
861 if ($form->{ordnumber}) {
862 require SL::DB::Order;
863 if (my $order = SL::DB::Manager::Order->find_by(ordnumber => $form->{ordnumber})) {
865 $form->{orddate} = $order->transdate_as_date;
866 $form->{$_} = $order->$_ for qw(payment_id salesman_id taxzone_id quonumber);
870 $form->{currency} = $currency;
871 $form->{exchangerate} = "";
872 $form->{forex} = $form->check_exchangerate(\%myconfig, $form->{currency}, $form->{invdate}, $buysell);
873 $form->{exchangerate} = $form->{forex} if ($form->{forex});
878 for my $i (1 .. $form->{rowcount}) {
879 $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"});
881 my ($dec) = ($form->{"sellprice_$i"} =~ /\.(\d+)/);
883 my $decimalplaces = ($dec > 2) ? $dec : 2;
885 # copy delivery date from reqdate for order -> invoice conversion
886 $form->{"deliverydate_$i"} = $form->{"reqdate_$i"}
887 unless $form->{"deliverydate_$i"};
890 $form->{"sellprice_$i"} =
891 $form->format_amount(\%myconfig, $form->{"sellprice_$i"},
894 $form->{"lastcost_$i"} =
895 $form->format_amount(\%myconfig, $form->{"lastcost_$i"},
898 (my $dec_qty) = ($form->{"qty_$i"} =~ /\.(\d+)/);
899 $dec_qty = length $dec_qty;
901 $form->format_amount(\%myconfig, $form->{"qty_$i"}, $dec_qty);
907 $main::lxdebug->leave_sub();
911 $main::lxdebug->enter_sub();
913 my $form = $main::form;
914 my %myconfig = %main::myconfig;
915 my $locale = $main::locale;
918 $main::auth->assert($form->{type} eq 'sales_delivery_order' ? 'invoice_edit' : 'vendor_invoice_edit');
920 my @do_ids = map { $form->{"trans_id_$_"} } grep { $form->{"multi_id_$_"} } (1..$form->{rowcount});
922 if (!scalar @do_ids) {
923 $form->show_generic_error($locale->text('You have not selected any delivery order.'));
926 map { delete $form->{$_} } grep { m/^(?:trans|multi)_id_\d+/ } keys %{ $form };
928 if (!DO->retrieve('vc' => $form->{vc}, 'ids' => \@do_ids)) {
929 $form->show_generic_error($form->{vc} eq 'customer' ?
930 $locale->text('You cannot create an invoice for delivery orders for different customers.') :
931 $locale->text('You cannot create an invoice for delivery orders from different vendors.'),
935 my $source_type = $form->{type};
936 $form->{convert_from_do_ids} = join ' ', @do_ids;
937 # bei der auswahl von mehreren Lieferscheinen fuer eine Rechnung, die einfach in donumber_array
938 # zwischenspeichern (DO.pm) und als ' '-separierte Liste wieder zurueckschreiben
939 # Hinweis: delete gibt den wert zurueck und loescht danach das element (nett und einfach)
940 # $shell: perldoc perlunc; /delete EXPR
941 $form->{donumber} = delete $form->{donumber_array};
942 $form->{ordnumber} = delete $form->{ordnumber_array};
943 $form->{cusordnumber} = delete $form->{cusordnumber_array};
944 $form->{deliverydate} = $form->{transdate};
945 $form->{transdate} = $form->current_date(\%myconfig);
946 $form->{duedate} = $form->current_date(\%myconfig, $form->{invdate}, $form->{terms} * 1);
947 $form->{type} = "invoice";
949 $form->{defaultcurrency} = $form->get_default_currency(\%myconfig);
951 my ($script, $buysell);
952 if ($source_type eq 'purchase_delivery_order') {
953 $form->{title} = $locale->text('Add Vendor Invoice');
954 $form->{script} = 'ir.pl';
959 $form->{title} = $locale->text('Add Sales Invoice');
960 $form->{script} = 'is.pl';
965 map { delete $form->{$_} } qw(id subject message cc bcc printed emailed queued);
967 # get vendor or customer discount
969 my $saved_form = save_form();
970 if ($form->{vc} eq 'vendor') {
971 IR->get_vendor(\%myconfig, \%$form);
972 $vc_discount = $form->{vendor_discount};
974 IS->get_customer(\%myconfig, \%$form);
975 $vc_discount = $form->{customer_discount};
977 # use payment terms from customer or vendor
978 restore_form($saved_form,0,qw(payment_id));
980 $form->{rowcount} = 0;
981 foreach my $ref (@{ $form->{form_details} }) {
983 $ref->{reqdate} ||= $ref->{dord_transdate}; # copy transdates into each invoice row
984 map { $form->{"${_}_$form->{rowcount}"} = $ref->{$_} } keys %{ $ref };
985 map { $form->{"${_}_$form->{rowcount}"} = $form->format_amount(\%myconfig, $ref->{$_}) } qw(qty sellprice lastcost);
986 $form->{"converted_from_delivery_order_items_id_$form->{rowcount}"} = delete $form->{"delivery_order_items_id_$form->{rowcount}"};
988 if ($vc_discount){ # falls wir einen Lieferanten/Kundenrabatt haben
989 # und keinen anderen discount wert an $i ...
990 $form->{"discount_$form->{rowcount}"} ||= $vc_discount; # ... nehmen wir diesen Rabatt
993 $form->{"discount_$form->{rowcount}"} = $form->{"discount_$form->{rowcount}"} * 100; #s.a. Bug 1151
994 # Anm.: Eine Änderung des discounts in der SL/DO.pm->retrieve (select (doi.discount * 100) as discount) ergibt in psql einen
995 # Wert von 10.0000001490116. Ferner ist der Rabatt in der Rechnung dann bei 1.0 (?). Deswegen lasse ich das hier. jb 10.10.09
997 $form->{"discount_$form->{rowcount}"} = $form->format_amount(\%myconfig, $form->{"discount_$form->{rowcount}"});
999 delete $form->{form_details};
1001 $locale = Locale->new("$myconfig{countrycode}", "$script");
1003 require "bin/mozilla/$form->{script}";
1010 $main::lxdebug->leave_sub();
1014 $main::lxdebug->enter_sub();
1018 my $form = $main::form;
1020 $form->{saveasnew} = 1;
1021 $form->{closed} = 0;
1022 $form->{delivered} = 0;
1023 map { delete $form->{$_} } qw(printed emailed queued);
1024 delete @{ $form }{ grep { m/^stock_(?:in|out)_\d+/ } keys %{ $form } };
1025 $form->{"converted_from_delivery_order_items_id_$_"} = delete $form->{"delivery_order_items_id_$_"} for 1 .. $form->{"rowcount"};
1026 # Let kivitendo assign a new order number if the user hasn't changed the
1027 # previous one. If it has been changed manually then use it as-is.
1028 $form->{donumber} =~ s/^\s*//g;
1029 $form->{donumber} =~ s/\s*$//g;
1030 if ($form->{saved_donumber} && ($form->{saved_donumber} eq $form->{donumber})) {
1031 delete($form->{donumber});
1036 $main::lxdebug->leave_sub();
1040 $main::lxdebug->enter_sub();
1044 $::form->mtime_ischanged('delivery_orders','mail');
1046 $::form->{print_and_save} = 1;
1048 my $saved_form = save_form();
1052 restore_form($saved_form, 0, qw(id ordnumber quonumber));
1056 $main::lxdebug->leave_sub();
1059 sub calculate_stock_in_out {
1060 $main::lxdebug->enter_sub();
1062 my $form = $main::form;
1066 if (!$form->{"id_${i}"}) {
1067 $main::lxdebug->leave_sub();
1071 my $all_units = AM->retrieve_all_units();
1073 my $in_out = $form->{type} =~ /^sales/ ? 'out' : 'in';
1074 my $sinfo = DO->unpack_stock_information('packed' => $form->{"stock_${in_out}_${i}"});
1076 my $do_qty = AM->sum_with_unit($::form->{"qty_$i"}, $::form->{"unit_$i"});
1077 my $sum = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $sinfo });
1078 my $matches = $do_qty == $sum;
1080 my $content = $form->format_amount_units('amount' => $sum * 1,
1081 'part_unit' => $form->{"partunit_$i"},
1082 'amount_unit' => $all_units->{$form->{"partunit_$i"}}->{base_unit},
1083 'conv_units' => 'convertible_not_smaller',
1085 $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="?">|;
1087 $main::lxdebug->leave_sub();
1092 sub get_basic_bin_wh_info {
1093 $main::lxdebug->enter_sub();
1095 my $stock_info = shift;
1097 my $form = $main::form;
1099 foreach my $sinfo (@{ $stock_info }) {
1100 next unless ($sinfo->{bin_id});
1102 my $bin_info = WH->get_basic_bin_info('id' => $sinfo->{bin_id});
1103 map { $sinfo->{"${_}_description"} = $sinfo->{"${_}description"} = $bin_info->{"${_}_description"} } qw(bin warehouse);
1106 $main::lxdebug->leave_sub();
1109 sub stock_in_out_form {
1110 $main::lxdebug->enter_sub();
1112 my $form = $main::form;
1114 if ($form->{in_out} eq 'out') {
1120 $main::lxdebug->leave_sub();
1123 sub redo_stock_info {
1124 $main::lxdebug->enter_sub();
1128 my $form = $main::form;
1130 my @non_empty = grep { $_->{qty} } @{ $params{stock_info} };
1132 if ($params{add_empty_row}) {
1134 'warehouse_id' => scalar(@non_empty) ? $non_empty[-1]->{warehouse_id} : undef,
1135 'bin_id' => scalar(@non_empty) ? $non_empty[-1]->{bin_id} : undef,
1139 @{ $params{stock_info} } = @non_empty;
1141 $main::lxdebug->leave_sub();
1144 sub update_stock_in {
1145 $main::lxdebug->enter_sub();
1147 my $form = $main::form;
1148 my %myconfig = %main::myconfig;
1150 my $stock_info = [];
1152 foreach my $i (1..$form->{rowcount}) {
1153 $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
1154 push @{ $stock_info }, { map { $_ => $form->{"${_}_${i}"} } qw(warehouse_id bin_id chargenumber
1155 bestbefore qty unit delivery_order_items_stock_id) };
1158 display_stock_in_form($stock_info);
1160 $main::lxdebug->leave_sub();
1164 $main::lxdebug->enter_sub();
1166 my $form = $main::form;
1168 my $stock_info = DO->unpack_stock_information('packed' => $form->{stock});
1170 display_stock_in_form($stock_info);
1172 $main::lxdebug->leave_sub();
1175 sub display_stock_in_form {
1176 $main::lxdebug->enter_sub();
1178 my $stock_info = shift;
1180 my $form = $main::form;
1181 my %myconfig = %main::myconfig;
1182 my $locale = $main::locale;
1184 $form->{title} = $locale->text('Stock');
1186 my $part_info = IC->get_basic_part_info('id' => $form->{parts_id});
1188 # Standardlagerplatz für Standard-Auslagern verwenden, falls keiner für die Ware explizit definiert wurde
1189 if ($::instance_conf->get_transfer_default_use_master_default_bin) {
1190 $part_info->{warehouse_id} ||= $::instance_conf->get_warehouse_id;
1191 $part_info->{bin_id} ||= $::instance_conf->get_bin_id;
1194 my $units = AM->retrieve_units(\%myconfig, $form);
1195 # der zweite Parameter von unit_select_data gibt den default-Namen (selected) vor
1196 my $units_data = AM->unit_select_data($units, $form->{do_unit}, undef, $part_info->{unit});
1198 $form->get_lists('warehouses' => { 'key' => 'WAREHOUSES',
1199 'bins' => 'BINS' });
1201 redo_stock_info('stock_info' => $stock_info, 'add_empty_row' => !$form->{delivered});
1203 get_basic_bin_wh_info($stock_info);
1205 $form->header(no_layout => 1);
1206 print $form->parse_html_template('do/stock_in_form', { 'UNITS' => $units_data,
1207 'STOCK_INFO' => $stock_info,
1208 'PART_INFO' => $part_info, });
1210 $main::lxdebug->leave_sub();
1213 sub _stock_in_out_set_qty_display {
1214 my $stock_info = shift;
1216 my $all_units = AM->retrieve_all_units();
1217 my $sum = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
1218 $form->{qty_display} = $form->format_amount_units(amount => $sum * 1,
1219 part_unit => $form->{partunit},
1220 amount_unit => $all_units->{ $form->{partunit} }->{base_unit},
1221 conv_units => 'convertible_not_smaller',
1226 $main::lxdebug->enter_sub();
1228 my $form = $main::form;
1229 my %myconfig = %main::myconfig;
1231 my $stock_info = [];
1233 foreach my $i (1..$form->{rowcount}) {
1234 $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
1236 next if ($form->{"qty_$i"} <= 0);
1238 push @{ $stock_info }, { map { $_ => $form->{"${_}_${i}"} } qw(delivery_order_items_stock_id warehouse_id bin_id chargenumber bestbefore qty unit) };
1241 $form->{stock} = YAML::Dump($stock_info);
1243 _stock_in_out_set_qty_display($stock_info);
1245 my $do_qty = AM->sum_with_unit($::form->parse_amount(\%::myconfig, $::form->{do_qty}), $::form->{do_unit});
1246 my $transfer_qty = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
1249 print $form->parse_html_template('do/set_stock_in_out', {
1250 qty_matches => $do_qty == $transfer_qty,
1253 $main::lxdebug->leave_sub();
1256 sub stock_out_form {
1257 $main::lxdebug->enter_sub();
1259 my $form = $main::form;
1260 my %myconfig = %main::myconfig;
1261 my $locale = $main::locale;
1263 $form->{title} = $locale->text('Release From Stock');
1265 my $part_info = IC->get_basic_part_info('id' => $form->{parts_id});
1267 my $units = AM->retrieve_units(\%myconfig, $form);
1268 my $units_data = AM->unit_select_data($units, undef, undef, $part_info->{unit});
1270 my @contents = DO->get_item_availability('parts_id' => $form->{parts_id});
1272 my $stock_info = DO->unpack_stock_information('packed' => $form->{stock});
1274 if (!$form->{delivered}) {
1275 foreach my $row (@contents) {
1276 $row->{available_qty} = $form->format_amount_units('amount' => $row->{qty} * 1,
1277 'part_unit' => $part_info->{unit},
1278 'conv_units' => 'convertible_not_smaller',
1281 foreach my $sinfo (@{ $stock_info }) {
1282 next if (($row->{bin_id} != $sinfo->{bin_id}) ||
1283 ($row->{warehouse_id} != $sinfo->{warehouse_id}) ||
1284 ($row->{chargenumber} ne $sinfo->{chargenumber}) ||
1285 ($row->{bestbefore} ne $sinfo->{bestbefore}));
1287 map { $row->{"stock_$_"} = $sinfo->{$_} } qw(qty unit error delivery_order_items_stock_id);
1292 get_basic_bin_wh_info($stock_info);
1294 foreach my $sinfo (@{ $stock_info }) {
1295 map { $sinfo->{"stock_$_"} = $sinfo->{$_} } qw(qty unit);
1299 $form->header(no_layout => 1);
1300 print $form->parse_html_template('do/stock_out_form', { 'UNITS' => $units_data,
1301 'WHCONTENTS' => $form->{delivered} ? $stock_info : \@contents,
1302 'PART_INFO' => $part_info, });
1304 $main::lxdebug->leave_sub();
1308 $main::lxdebug->enter_sub();
1310 my $form = $main::form;
1311 my %myconfig = %main::myconfig;
1312 my $locale = $main::locale;
1314 my $stock_info = [];
1316 foreach my $i (1 .. $form->{rowcount}) {
1317 $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
1319 next if ($form->{"qty_$i"} <= 0);
1321 push @{ $stock_info }, {
1322 'warehouse_id' => $form->{"warehouse_id_$i"},
1323 'bin_id' => $form->{"bin_id_$i"},
1324 'chargenumber' => $form->{"chargenumber_$i"},
1325 'bestbefore' => $form->{"bestbefore_$i"},
1326 'qty' => $form->{"qty_$i"},
1327 'unit' => $form->{"unit_$i"},
1329 'delivery_order_items_stock_id' => $form->{"delivery_order_items_stock_id_$i"},
1333 my @errors = DO->check_stock_availability('requests' => $stock_info,
1334 'parts_id' => $form->{parts_id});
1336 $form->{stock} = YAML::Dump($stock_info);
1339 $form->{ERRORS} = [];
1340 map { push @{ $form->{ERRORS} }, $locale->text('Error in row #1: The quantity you entered is bigger than the stocked quantity.', $_->{row}); } @errors;
1341 stock_in_out_form();
1344 _stock_in_out_set_qty_display($stock_info);
1346 my $do_qty = AM->sum_with_unit($::form->parse_amount(\%::myconfig, $::form->{do_qty}), $::form->{do_unit});
1347 my $transfer_qty = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
1350 print $form->parse_html_template('do/set_stock_in_out', {
1351 qty_matches => $do_qty == $transfer_qty,
1355 $main::lxdebug->leave_sub();
1359 $main::lxdebug->enter_sub();
1361 my $form = $main::form;
1362 my %myconfig = %main::myconfig;
1363 my $locale = $main::locale;
1365 if ($form->{id} && DO->is_marked_as_delivered(id => $form->{id})) {
1366 $form->show_generic_error($locale->text('The parts for this delivery order have already been transferred in.'));
1369 save(no_redirect => 1);
1371 my @part_ids = map { $form->{"id_${_}"} } grep { $form->{"id_${_}"} && $form->{"stock_in_${_}"} } (1 .. $form->{rowcount});
1375 my $units = AM->retrieve_units(\%myconfig, $form);
1376 my %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
1379 $form->{ERRORS} = [];
1381 foreach my $i (1 .. $form->{rowcount}) {
1382 next unless ($form->{"id_$i"} && $form->{"stock_in_$i"});
1384 my $row_sum_base_qty = 0;
1385 my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
1387 foreach my $request (@{ DO->unpack_stock_information('packed' => $form->{"stock_in_$i"}) }) {
1388 $request->{parts_id} = $form->{"id_$i"};
1389 $row_sum_base_qty += $request->{qty} * $units->{$request->{unit}}->{factor} / $base_unit_factor;
1391 $request->{project_id} = $form->{"project_id_$i"} || $form->{globalproject_id};
1393 push @all_requests, $request;
1396 next if (0 == $row_sum_base_qty);
1398 my $do_base_qty = $form->parse_amount(\%myconfig, $form->{"qty_$i"}) * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
1400 # if ($do_base_qty != $row_sum_base_qty) {
1401 # push @{ $form->{ERRORS} }, $locale->text('Error in position #1: You must either assign no stock at all or the full quantity of #2 #3.',
1402 # $i, $form->{"qty_$i"}, $form->{"unit_$i"});
1406 if (@{ $form->{ERRORS} }) {
1407 push @{ $form->{ERRORS} }, $locale->text('The delivery order has not been marked as delivered. The warehouse contents have not changed.');
1409 set_headings('edit');
1411 $main::lxdebug->leave_sub();
1413 $::dispatcher->end_request;
1417 DO->transfer_in_out('direction' => 'in',
1418 'requests' => \@all_requests);
1420 SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
1422 $form->{callback} = 'do.pl?action=edit&type=purchase_delivery_order&id=' . $form->escape($form->{id});
1425 $main::lxdebug->leave_sub();
1429 $main::lxdebug->enter_sub();
1431 my $form = $main::form;
1432 my %myconfig = %main::myconfig;
1433 my $locale = $main::locale;
1435 if ($form->{id} && DO->is_marked_as_delivered(id => $form->{id})) {
1436 $form->show_generic_error($locale->text('The parts for this delivery order have already been transferred out.'));
1439 save(no_redirect => 1);
1441 my @part_ids = map { $form->{"id_${_}"} } grep { $form->{"id_${_}"} && $form->{"stock_out_${_}"} } (1 .. $form->{rowcount});
1445 my $units = AM->retrieve_units(\%myconfig, $form);
1446 my %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
1449 $form->{ERRORS} = [];
1451 foreach my $i (1 .. $form->{rowcount}) {
1452 next unless ($form->{"id_$i"} && $form->{"stock_out_$i"});
1454 my $row_sum_base_qty = 0;
1455 my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
1457 foreach my $request (@{ DO->unpack_stock_information('packed' => $form->{"stock_out_$i"}) }) {
1458 $request->{parts_id} = $form->{"id_$i"};
1459 $request->{base_qty} = $request->{qty} * $units->{$request->{unit}}->{factor} / $base_unit_factor;
1460 $request->{project_id} = $form->{"project_id_$i"} ? $form->{"project_id_$i"} : $form->{globalproject_id};
1462 my $map_key = join '--', ($form->{"id_$i"}, @{$request}{qw(warehouse_id bin_id chargenumber bestbefore)});
1464 $request_map{$map_key} ||= $request;
1465 $request_map{$map_key}->{sum_base_qty} ||= 0;
1466 $request_map{$map_key}->{sum_base_qty} += $request->{base_qty};
1467 $row_sum_base_qty += $request->{base_qty};
1469 push @all_requests, $request;
1472 next if (0 == $row_sum_base_qty);
1474 my $do_base_qty = $form->{"qty_$i"} * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
1476 # if ($do_base_qty != $row_sum_base_qty) {
1477 # push @{ $form->{ERRORS} }, $locale->text('Error in position #1: You must either assign no transfer at all or the full quantity of #2 #3.',
1478 # $i, $form->{"qty_$i"}, $form->{"unit_$i"});
1483 my @bin_ids = map { $_->{bin_id} } values %request_map;
1484 my %bin_info_map = WH->get_basic_bin_info('id' => \@bin_ids);
1485 my @contents = DO->get_item_availability('parts_id' => \@part_ids);
1487 foreach my $inv (@contents) {
1488 my $map_key = join '--', @{$inv}{qw(parts_id warehouse_id bin_id chargenumber bestbefore)};
1490 next unless ($request_map{$map_key});
1492 my $request = $request_map{$map_key};
1493 $request->{ok} = $request->{sum_base_qty} <= $inv->{qty};
1496 foreach my $request (values %request_map) {
1497 next if ($request->{ok});
1499 my $pinfo = $part_info_map{$request->{parts_id}};
1500 my $binfo = $bin_info_map{$request->{bin_id}};
1502 if ($::instance_conf->get_show_bestbefore) {
1503 push @{ $form->{ERRORS} }, $locale->text("There is not enough available of '#1' at warehouse '#2', bin '#3', #4, #5, for the transfer of #6.",
1504 $pinfo->{description},
1505 $binfo->{warehouse_description},
1506 $binfo->{bin_description},
1507 $request->{chargenumber} ? $locale->text('chargenumber #1', $request->{chargenumber}) : $locale->text('no chargenumber'),
1508 $request->{bestbefore} ? $locale->text('bestbefore #1', $request->{bestbefore}) : $locale->text('no bestbefore'),
1509 $form->format_amount_units('amount' => $request->{sum_base_qty},
1510 'part_unit' => $pinfo->{unit},
1511 'conv_units' => 'convertible_not_smaller'));
1513 push @{ $form->{ERRORS} }, $locale->text("There is not enough available of '#1' at warehouse '#2', bin '#3', #4, for the transfer of #5.",
1514 $pinfo->{description},
1515 $binfo->{warehouse_description},
1516 $binfo->{bin_description},
1517 $request->{chargenumber} ? $locale->text('chargenumber #1', $request->{chargenumber}) : $locale->text('no chargenumber'),
1518 $form->format_amount_units('amount' => $request->{sum_base_qty},
1519 'part_unit' => $pinfo->{unit},
1520 'conv_units' => 'convertible_not_smaller'));
1525 if (@{ $form->{ERRORS} }) {
1526 push @{ $form->{ERRORS} }, $locale->text('The delivery order has not been marked as delivered. The warehouse contents have not changed.');
1528 set_headings('edit');
1530 $main::lxdebug->leave_sub();
1532 $::dispatcher->end_request;
1535 DO->transfer_in_out('direction' => 'out',
1536 'requests' => \@all_requests);
1538 SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
1540 $form->{callback} = 'do.pl?action=edit&type=sales_delivery_order&id=' . $form->escape($form->{id});
1543 $main::lxdebug->leave_sub();
1547 $main::lxdebug->enter_sub();
1549 my $form = $main::form;
1551 DO->close_orders('ids' => [ $form->{id} ]);
1553 $form->{closed} = 1;
1557 $main::lxdebug->leave_sub();
1561 $::lxdebug->enter_sub;
1563 $::auth->assert('purchase_delivery_order_edit | sales_delivery_order_edit');
1566 retrieve_partunits();
1568 my $new_rowcount = $::form->{"rowcount"} * 1 + 1;
1569 $::form->{"project_id_${new_rowcount}"} = $::form->{"globalproject_id"};
1571 $::form->language_payment(\%::myconfig);
1573 Common::webdav_folder($::form);
1576 display_row(++$::form->{rowcount});
1579 $::lxdebug->leave_sub;
1583 call_sub($main::form->{yes_nextsub});
1587 call_sub($main::form->{no_nextsub});
1591 call_sub($main::form->{update_nextsub} || $main::form->{nextsub} || 'update_delivery_order');
1595 my $form = $main::form;
1596 my $locale = $main::locale;
1598 foreach my $action (qw(update ship_to print e_mail save transfer_out transfer_out_default sort
1599 transfer_in transfer_in_default mark_closed save_as_new invoice delete)) {
1600 if ($form->{"action_${action}"}) {
1606 $form->error($locale->text('No action defined.'));
1609 sub transfer_out_default {
1610 $main::lxdebug->enter_sub();
1612 my $form = $main::form;
1614 transfer_in_out_default('direction' => 'out');
1616 $main::lxdebug->leave_sub();
1619 sub transfer_in_default {
1620 $main::lxdebug->enter_sub();
1622 my $form = $main::form;
1624 transfer_in_out_default('direction' => 'in');
1626 $main::lxdebug->leave_sub();
1629 # Falls das Standardlagerverfahren aktiv ist, wird
1630 # geprüft, ob alle Standardlagerplätze für die Auslager-
1631 # artikel vorhanden sind UND ob die Warenmenge ausreicht zum
1632 # Auslagern. Falls nicht wird entsprechend eine Fehlermeldung
1633 # generiert. Offen Chargennummer / bestbefore wird nicht berücksichtigt
1634 sub transfer_in_out_default {
1635 $main::lxdebug->enter_sub();
1637 my $form = $main::form;
1638 my %myconfig = %main::myconfig;
1639 my $locale = $main::locale;
1642 my (%missing_default_bins, %qty_parts, @all_requests, %part_info_map, $default_warehouse_id, $default_bin_id);
1644 Common::check_params(\%params, qw(direction));
1646 # entsprechende defaults holen, falls standardlagerplatz verwendet werden soll
1647 if ($::instance_conf->get_transfer_default_use_master_default_bin) {
1648 $default_warehouse_id = $::instance_conf->get_warehouse_id;
1649 $default_bin_id = $::instance_conf->get_bin_id;
1653 my @part_ids = map { $form->{"id_${_}"} } (1 .. $form->{rowcount});
1655 my $units = AM->retrieve_units(\%myconfig, $form);
1656 %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
1657 foreach my $i (1 .. $form->{rowcount}) {
1658 next unless ($form->{"id_$i"});
1659 my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
1660 my $qty = $form->parse_amount(\%myconfig, $form->{"qty_$i"}) * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
1662 $form->show_generic_error($locale->text("Cannot transfer negative entries." )) if ($qty < 0);
1663 # if we do not want to transfer services and this part is a service, set qty to zero
1664 # ... and do not create a hash entry in %qty_parts below (will skip check for bins for the transfer == out case)
1665 # ... 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)
1667 $qty = 0 if (!$::instance_conf->get_transfer_default_services && !defined($part_info_map{$form->{"id_$i"}}->{inventory_accno_id}) && !$part_info_map{$form->{"id_$i"}}->{assembly});
1668 $qty_parts{$form->{"id_$i"}} += $qty;
1670 delete $qty_parts{$form->{"id_$i"}} unless $qty_parts{$form->{"id_$i"}};
1671 undef $form->{"stock_in_$i"};
1674 $part_info_map{$form->{"id_$i"}}{bin_id} ||= $default_bin_id;
1675 $part_info_map{$form->{"id_$i"}}{warehouse_id} ||= $default_warehouse_id;
1677 push @all_requests, ($qty == 0) ? { } : {
1678 'chargenumber' => '', #?? die müsste entsprechend geholt werden
1679 #'bestbefore' => undef, # TODO wird nicht berücksichtigt
1680 'bin_id' => $part_info_map{$form->{"id_$i"}}{bin_id},
1682 'parts_id' => $form->{"id_$i"},
1683 'comment' => $locale->text("Default transfer delivery order"),
1684 'unit' => $part_info_map{$form->{"id_$i"}}{unit},
1685 'warehouse_id' => $part_info_map{$form->{"id_$i"}}{warehouse_id},
1686 'oe_id' => $form->{id},
1687 'project_id' => $form->{"project_id_$i"} ? $form->{"project_id_$i"} : $form->{globalproject_id}
1691 # jetzt wird erst überprüft, ob die Stückzahl entsprechend stimmt.
1692 # check if bin (transfer in and transfer out and qty (transfer out) is correct
1693 foreach my $key (keys %qty_parts) {
1695 $missing_default_bins{$key}{missing_bin} = 1 unless ($part_info_map{$key}{bin_id});
1696 next unless ($part_info_map{$key}{bin_id}); # abbruch
1698 if ($params{direction} eq 'out') { # wird nur für ausgehende Mengen benötigt
1699 my ($max_qty, $error) = WH->get_max_qty_parts_bin(parts_id => $key, bin_id => $part_info_map{$key}{bin_id});
1701 # wir können nicht entscheiden, welche charge oder mhd (bestbefore) ausgewählt sein soll
1702 # deshalb rückmeldung nach oben geben, manuell auszulagern
1703 # TODO Bei nur einem Treffer mit Charge oder bestbefore wäre das noch möglich
1704 $missing_default_bins{$key}{chargenumber} = 1;
1706 if ($max_qty < $qty_parts{$key}){
1707 $missing_default_bins{$key}{missing_qty} = $max_qty - $qty_parts{$key};
1713 # Abfrage für Fehlerbehandlung (nur bei direction == out)
1714 if (scalar (keys %missing_default_bins)) {
1716 foreach my $fehler (keys %missing_default_bins) {
1718 my $ware = WH->get_part_description(parts_id => $fehler);
1719 if ($missing_default_bins{$fehler}{missing_bin}){
1720 $fehlertext .= "Kein Standardlagerplatz definiert bei $ware <br>";
1722 if ($missing_default_bins{$fehler}{missing_qty}) { # missing_qty
1723 $fehlertext .= "Es fehlen " . $missing_default_bins{$fehler}{missing_qty}*-1 .
1724 " von $ware auf dem Standard-Lagerplatz " . $part_info_map{$fehler}{bin} . " zum Auslagern<br>";
1726 if ($missing_default_bins{$fehler}{chargenumber}){
1727 $fehlertext .= "Die Ware hat eine Chargennummer oder eine Mindesthaltbarkeit definiert.
1728 Hier kann man nicht automatisch entscheiden.
1729 Bitte diesen Lieferschein manuell auslagern.
1732 # auslagern soll immer gehen, auch wenn nicht genügend auf lager ist.
1733 # der lagerplatz ist hier extra konfigurierbar, bspw. Lager-Korrektur mit
1734 # Lagerplatz Lagerplatz-Korrektur
1735 my $default_warehouse_id_ignore_onhand = $::instance_conf->get_warehouse_id_ignore_onhand;
1736 my $default_bin_id_ignore_onhand = $::instance_conf->get_bin_id_ignore_onhand;
1737 if ($::instance_conf->get_transfer_default_ignore_onhand && $default_bin_id_ignore_onhand) {
1738 # entsprechende defaults holen
1739 # falls chargenumber, bestbefore oder anzahl nicht stimmt, auf automatischen
1740 # lagerplatz wegbuchen!
1741 foreach (@all_requests) {
1742 if ($_->{parts_id} eq $fehler){
1743 $_->{bin_id} = $default_bin_id_ignore_onhand;
1744 $_->{warehouse_id} = $default_warehouse_id_ignore_onhand;
1748 #$main::lxdebug->message(0, 'Fehlertext: ' . $fehlertext);
1749 $form->show_generic_error($locale->text("Cannot transfer. <br> Reason:<br>#1", $fehlertext ));
1755 # hier der eigentliche fallunterschied für in oder out
1756 my $prefix = $params{direction} eq 'in' ? 'in' : 'out';
1758 # dieser array_ref ist für DO->save da:
1759 # einmal die all_requests in YAML verwandeln, damit delivery_order_items_stock
1760 # gefüllt werden kann.
1761 # could be dumped to the form in the first loop,
1762 # but maybe bin_id and warehouse_id has changed to the "korrekturlager" with
1763 # allowed negative qty ($::instance_conf->get_warehouse_id_ignore_onhand) ...
1765 foreach (@all_requests){
1767 next unless scalar(%{ $_ });
1768 $form->{"stock_${prefix}_$i"} = YAML::Dump([$_]);
1771 save(no_redirect => 1); # Wir können auslagern, deshalb beleg speichern
1772 # und in delivery_order_items_stock speichern
1774 # ... and fill back the persistent dois_id for inventory fk
1775 undef (@all_requests);
1776 foreach my $i (1 .. $form->{rowcount}) {
1777 next unless ($form->{"id_$i"} && $form->{"stock_${prefix}_$i"});
1778 push @all_requests, @{ DO->unpack_stock_information('packed' => $form->{"stock_${prefix}_$i"}) };
1780 DO->transfer_in_out('direction' => $prefix,
1781 'requests' => \@all_requests);
1783 SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
1785 $form->{callback} = 'do.pl?action=edit&type=sales_delivery_order&id=' . $form->escape($form->{id}) if $params{direction} eq 'out';
1786 $form->{callback} = 'do.pl?action=edit&type=purchase_delivery_order&id=' . $form->escape($form->{id}) if $params{direction} eq 'in';
1792 $main::lxdebug->enter_sub();
1796 my $form = $main::form;
1799 save(no_redirect => 1); # has to be done, at least for newly added positions
1801 # hashify partnumbers, positions. key is delivery_order_items_id
1802 for my $i (1 .. ($form->{rowcount}) ) {
1803 $temp_hash{$form->{"delivery_order_items_id_$i"}} = { runningnumber => $form->{"runningnumber_$i"}, partnumber => $form->{"partnumber_$i"} };
1804 if ($form->{id} && $form->{"discount_$i"}) {
1805 # prepare_order assumes a db value if there is a form->id and multiplies *100
1806 # We hope for new controller code (no more format_amount/parse_amount distinction)
1807 $form->{"discount_$i"} /=100;
1810 # naturally sort partnumbers and get a sorted array of doi_ids
1811 my @sorted_doi_ids = sort { Sort::Naturally::ncmp($temp_hash{$a}->{"partnumber"}, $temp_hash{$b}->{"partnumber"}) } keys %temp_hash;
1816 for (@sorted_doi_ids) {
1817 $form->{"runningnumber_$temp_hash{$_}->{runningnumber}"} = $new_number;
1820 # all parse_amounts changes are in form (i.e. , to .) therefore we need
1821 # another format_amount to change it back, for the next save ;-(
1822 # works great except for row discounts (see above comment)
1826 $main::lxdebug->leave_sub();
1838 do.pl - Script for all calls to delivery order
1846 Sorts all position with Natural Sort. Can be activated in form_footer.html like this
1847 C<E<lt>input class="submit" type="submit" name="action_sort" id="sort_button" value="[% 'Sort and Save' | $T8 %]"E<gt>>
1853 Sort and Save can be implemented as an optional button if configuration ca be set by client config.
1854 Example coding for database scripts and templates in (git show af2f24b8), check also
1855 autogeneration for rose (scripts/rose_auto_create_model.pl --h)