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/arap.pl";
49 require "bin/mozilla/common.pl";
50 require "bin/mozilla/io.pl";
51 require "bin/mozilla/reportgenerator.pl";
60 $main::auth->assert($main::form->{type} . '_edit');
64 $main::lxdebug->enter_sub();
70 my $form = $main::form;
71 my $locale = $main::locale;
73 if ($form->{type} eq 'purchase_delivery_order') {
74 $form->{vc} = 'vendor';
75 $form->{title} = $action eq "edit" ? $locale->text('Edit Purchase Delivery Order') : $locale->text('Add Purchase Delivery Order');
77 $form->{vc} = 'customer';
78 $form->{title} = $action eq "edit" ? $locale->text('Edit Sales Delivery Order') : $locale->text('Add Sales Delivery Order');
81 $form->{heading} = $locale->text('Delivery Order');
83 $main::lxdebug->leave_sub();
87 $main::lxdebug->enter_sub();
91 if (($::form->{type} =~ /purchase/) && !$::instance_conf->get_allow_new_purchase_invoice) {
92 $::form->show_generic_error($::locale->text("You do not have the permissions to access this function."));
95 my $form = $main::form;
99 $form->{show_details} = $::myconfig{show_form_details};
100 $form->{callback} = build_std_url('action=add', 'type', 'vc') unless ($form->{callback});
106 $main::lxdebug->leave_sub();
110 $main::lxdebug->enter_sub();
114 my $form = $main::form;
116 $form->{show_details} = $::myconfig{show_form_details};
118 # show history button
119 $form->{javascript} = qq|<script type="text/javascript" src="js/show_history.js"></script>|;
120 #/show hhistory button
122 $form->{simple_save} = 0;
124 set_headings("edit");
126 # editing without stuff to edit? try adding it first
127 if ($form->{rowcount} && !$form->{print_and_save}) {
128 # map { $id++ if $form->{"multi_id_$_"} } (1 .. $form->{rowcount});
132 undef $form->{rowcount};
134 $main::lxdebug->leave_sub();
137 } elsif (!$form->{id}) {
139 $main::lxdebug->leave_sub();
143 my ($language_id, $printer_id);
144 if ($form->{print_and_save}) {
145 $form->{action} = "dispatcher";
146 $form->{action_print} = "1";
147 $form->{resubmit} = 1;
148 $language_id = $form->{language_id};
149 $printer_id = $form->{printer_id};
152 set_headings("edit");
157 if ($form->{print_and_save}) {
158 $form->{language_id} = $language_id;
159 $form->{printer_id} = $printer_id;
164 $main::lxdebug->leave_sub();
168 $main::lxdebug->enter_sub();
172 my $form = $main::form;
173 my %myconfig = %main::myconfig;
175 # get customer/vendor
176 $form->all_vc(\%myconfig, $form->{vc}, ($form->{vc} eq 'customer') ? "AR" : "AP");
178 # retrieve order/quotation
179 my $editing = $form->{id};
181 DO->retrieve('vc' => $form->{vc},
182 'ids' => $form->{id});
184 $form->backup_vars(qw(payment_id language_id taxzone_id salesman_id taxincluded cp_id intnotes delivery_term_id currency));
186 # get customer / vendor
187 if ($form->{vc} eq 'vendor') {
188 IR->get_vendor(\%myconfig, \%$form);
189 $form->{discount} = $form->{vendor_discount};
191 IS->get_customer(\%myconfig, \%$form);
192 $form->{discount} = $form->{customer_discount};
195 $form->restore_vars(qw(payment_id language_id taxzone_id intnotes cp_id delivery_term_id));
196 $form->restore_vars(qw(currency)) if ($form->{id} || $form->{convert_from_oe_ids});
197 $form->restore_vars(qw(taxincluded)) if $form->{id};
198 $form->restore_vars(qw(salesman_id)) if $editing;
200 if ($form->{"all_$form->{vc}"}) {
201 unless ($form->{"$form->{vc}_id"}) {
202 $form->{"$form->{vc}_id"} = $form->{"all_$form->{vc}"}->[0]->{id};
206 ($form->{ $form->{vc} }) = split /--/, $form->{ $form->{vc} };
207 $form->{"old$form->{vc}"} = qq|$form->{$form->{vc}}--$form->{"$form->{vc}_id"}|;
209 $form->{employee} = "$form->{employee}--$form->{employee_id}";
211 $main::lxdebug->leave_sub();
215 $main::lxdebug->enter_sub();
219 my $form = $main::form;
220 my %myconfig = %main::myconfig;
222 $form->{formname} = $form->{type} unless $form->{formname};
225 foreach my $ref (@{ $form->{form_details} }) {
226 $form->{rowcount} = ++$i;
228 map { $form->{"${_}_$i"} = $ref->{$_} } keys %{$ref};
230 for my $i (1 .. $form->{rowcount}) {
232 $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"} * 100);
234 $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"});
236 my ($dec) = ($form->{"sellprice_$i"} =~ /\.(\d+)/);
238 my $decimalplaces = ($dec > 2) ? $dec : 2;
240 # copy reqdate from deliverydate for invoice -> order conversion
241 $form->{"reqdate_$i"} = $form->{"deliverydate_$i"} unless $form->{"reqdate_$i"};
243 $form->{"sellprice_$i"} = $form->format_amount(\%myconfig, $form->{"sellprice_$i"}, $decimalplaces);
244 $form->{"lastcost_$i"} = $form->format_amount(\%myconfig, $form->{"lastcost_$i"}, $decimalplaces);
246 (my $dec_qty) = ($form->{"qty_$i"} =~ /\.(\d+)/);
247 $dec_qty = length $dec_qty;
248 $form->{"qty_$i"} = $form->format_amount(\%myconfig, $form->{"qty_$i"}, $dec_qty);
251 $main::lxdebug->leave_sub();
255 $main::lxdebug->enter_sub();
259 my $form = $main::form;
260 my %myconfig = %main::myconfig;
262 $form->{employee_id} = $form->{old_employee_id} if $form->{old_employee_id};
263 $form->{salesman_id} = $form->{old_salesman_id} if $form->{old_salesman_id};
265 my $vc = $form->{vc} eq "customer" ? "customers" : "vendors";
266 $form->get_lists($vc => "ALL_VC",
267 "price_factors" => "ALL_PRICE_FACTORS",
268 "departments" => "ALL_DEPARTMENTS",
269 "business_types" => "ALL_BUSINESS_TYPES",
273 my @old_project_ids = uniq grep { $_ } map { $_ * 1 } ($form->{"globalproject_id"}, map { $form->{"project_id_$_"} } 1..$form->{"rowcount"});
274 my @old_ids_cond = @old_project_ids ? (id => \@old_project_ids) : ();
276 if (($vc eq 'customers') && $::instance_conf->get_customer_projects_only_in_sales) {
279 customer_id => $::form->{customer_id},
280 billable_customer_id => $::form->{customer_id},
285 and => [ active => 1, @customer_cond ],
289 $::form->{ALL_PROJECTS} = SL::DB::Manager::Project->get_all_sorted(query => \@conditions);
290 $::form->{ALL_EMPLOYEES} = SL::DB::Manager::Employee->get_all_sorted(query => [ or => [ id => $::form->{employee_id}, deleted => 0 ] ]);
291 $::form->{ALL_SALESMEN} = SL::DB::Manager::Employee->get_all_sorted(query => [ or => [ id => $::form->{salesman_id}, deleted => 0 ] ]);
292 $::form->{ALL_SHIPTO} = SL::DB::Manager::Shipto->get_all_sorted(query => [
293 or => [ trans_id => $::form->{"$::form->{vc}_id"} * 1, and => [ shipto_id => $::form->{shipto_id} * 1, trans_id => undef ] ]
295 $::form->{ALL_CONTACTS} = SL::DB::Manager::Contact->get_all_sorted(query => [
297 cp_cv_id => $::form->{"$::form->{vc}_id"} * 1,
300 cp_id => $::form->{cp_id} * 1
305 map { $_->{value} = "$_->{description}--$_->{id}" } @{ $form->{ALL_DEPARTMENTS} };
306 map { $_->{value} = "$_->{name}--$_->{id}" } @{ $form->{ALL_VC} };
308 $form->{SHOW_VC_DROP_DOWN} = $myconfig{vclimit} > scalar @{ $form->{ALL_VC} };
310 $form->{oldvcname} = $form->{"old$form->{vc}"};
311 $form->{oldvcname} =~ s/--.*//;
313 my $dispatch_to_popup = '';
314 if ($form->{resubmit} && ($form->{format} eq "html")) {
315 $dispatch_to_popup = "window.open('about:blank','Beleg'); document.do.target = 'Beleg';";
316 $dispatch_to_popup .= "document.do.submit();";
317 } elsif ($form->{resubmit}) {
318 # emulate click for resubmitting actions
319 $dispatch_to_popup = "document.do.${_}.click(); " for grep { /^action_/ } keys %$form;
321 $::request->{layout}->add_javascripts_inline("\$(function(){$dispatch_to_popup});");
324 my $follow_up_vc = $form->{ $form->{vc} eq 'customer' ? 'customer' : 'vendor' };
325 $follow_up_vc =~ s/--\d*\s*$//;
327 $form->{follow_up_trans_info} = $form->{donumber} .'('. $follow_up_vc .')';
329 $::request->{layout}->use_javascript(map { "${_}.js" } qw(kivi.SalesPurchase ckeditor/ckeditor ckeditor/adapters/jquery kivi.io autocomplete_customer autocomplete_part));
332 push @custom_hidden, map { "shiptocvar_" . $_->name } @{ SL::DB::Manager::CustomVariableConfig->get_all(where => [ module => 'ShipTo' ]) };
334 $::form->{HIDDENS} = [ map { +{ name => $_, value => $::form->{$_} } } (@custom_hidden) ];
337 # Fix für Bug 1082 Erwartet wird: 'abteilungsNAME--abteilungsID'
338 # und Erweiterung für Bug 1760:
339 # Das war leider nur ein Teil-Fix, da das Verhalten den 'Erneuern'-Knopf
340 # nicht überlebt. Konsequent jetzt auf L umgestellt
341 # $ perldoc SL::Template::Plugin::L
342 # Daher entsprechend nur die Anpassung in form_header
343 # und in DO.pm gemacht. 4 Testfälle:
344 # department_id speichern | i.O.
345 # department_id lesen | i.O.
346 # department leer überlebt erneuern | i.O.
347 # department nicht leer überlebt erneuern | i.O.
348 # $main::lxdebug->message(0, 'ABTEILUNGS ID in form?' . $form->{department_id});
349 print $form->parse_html_template('do/form_header');
351 $main::lxdebug->leave_sub();
355 $main::lxdebug->enter_sub();
359 my $form = $main::form;
361 $form->{PRINT_OPTIONS} = print_options('inline' => 1);
362 $form->{ALL_DELIVERY_TERMS} = SL::DB::Manager::DeliveryTerm->get_all_sorted();
364 print $form->parse_html_template('do/form_footer',
365 {transfer_default => ($::instance_conf->get_transfer_default)});
367 $main::lxdebug->leave_sub();
370 sub update_delivery_order {
371 $main::lxdebug->enter_sub();
375 my $form = $main::form;
376 my %myconfig = %main::myconfig;
378 set_headings($form->{"id"} ? "edit" : "add");
380 $form->{insertdate} = SL::DB::DeliveryOrder->new(id => $form->{id})->load->itime_as_date if $form->{id};
385 $payment_id = $form->{payment_id} if $form->{payment_id};
387 check_name($form->{vc});
388 $form->{discount} = $form->{"$form->{vc}_discount"} if defined $form->{"$form->{vc}_discount"};
389 # Problem: Wenn man ohne Erneuern einen Kunden/Lieferanten
390 # wechselt, wird der entsprechende Kunden/ Lieferantenrabatt
391 # nicht übernommen. Grundproblem: In Commit 82574e78
392 # hab ich aus discount customer_discount und vendor_discount
393 # gemacht und entsprechend an den Oberflächen richtig hin-
394 # geschoben. Die damals bessere Lösung wäre gewesen:
395 # In den Templates nur die hidden für form-discount wieder ein-
396 # setzen dann wäre die Verrenkung jetzt nicht notwendig.
397 # TODO: Ggf. Bugfix 1284, 1575 und 817 wieder zusammenführen
398 # Testfälle: Kunden mit Rabatt 0 -> Rabatt 20 i.O.
399 # Kunde mit Rabatt 20 -> Rabatt 0 i.O.
400 # Kunde mit Rabatt 20 -> Rabatt 5,5 i.O.
401 $form->{payment_id} = $payment_id if $form->{payment_id} eq "";
403 my $i = $form->{rowcount};
405 if ( ($form->{"partnumber_$i"} eq "")
406 && ($form->{"description_$i"} eq "")
407 && ($form->{"partsgroup_$i"} eq "")) {
414 if ($form->{type} eq 'purchase_delivery_order') {
415 IR->retrieve_item(\%myconfig, $form);
418 IS->retrieve_item(\%myconfig, $form);
422 my $rows = scalar @{ $form->{item_list} };
425 $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
426 if( !$form->{"qty_$i"} ) {
427 $form->{"qty_$i"} = 1;
432 select_item(mode => $mode, pre_entered_qty => $form->{"qty_$i"});
433 $::dispatcher->end_request;
437 my $sellprice = $form->parse_amount(\%myconfig, $form->{"sellprice_$i"});
439 map { $form->{"${_}_$i"} = $form->{item_list}[0]{$_} } keys %{ $form->{item_list}[0] };
441 $form->{"marge_price_factor_$i"} = $form->{item_list}->[0]->{price_factor};
444 $form->{"sellprice_$i"} = $sellprice;
446 my $record = _make_record();
447 my $price_source = SL::PriceSource->new(record_item => $record->items->[$i-1], record => $record);
448 my $best_price = $price_source->best_price;
449 my $best_discount = $price_source->best_discount;
452 $::form->{"sellprice_$i"} = $best_price->price;
453 $::form->{"active_price_source_$i"} = $best_price->source;
455 if ($best_discount) {
456 $::form->{"discount_$i"} = $best_discount->discount;
457 $::form->{"active_discount_source_$i"} = $best_discount->source;
461 $form->{"sellprice_$i"} = $form->format_amount(\%myconfig, $form->{"sellprice_$i"});
462 $form->{"lastcost_$i"} = $form->format_amount(\%myconfig, $form->{"lastcost_$i"});
463 $form->{"qty_$i"} = $form->format_amount(\%myconfig, $form->{"qty_$i"});
464 $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"} * 100.0);
471 # ok, so this is a new part
472 # ask if it is a part or service item
474 if ( $form->{"partsgroup_$i"}
475 && ($form->{"partsnumber_$i"} eq "")
476 && ($form->{"description_$i"} eq "")) {
478 $form->{"discount_$i"} = "";
479 $form->{"not_discountable_$i"} = "";
483 $form->{"id_$i"} = 0;
489 $main::lxdebug->leave_sub();
493 $main::lxdebug->enter_sub();
497 my $form = $main::form;
498 my %myconfig = %main::myconfig;
499 my $locale = $main::locale;
501 $form->{vc} = $form->{type} eq 'purchase_delivery_order' ? 'vendor' : 'customer';
503 $form->get_lists("projects" => { "key" => "ALL_PROJECTS",
505 "departments" => "ALL_DEPARTMENTS",
506 "$form->{vc}s" => "ALL_VC",
507 "business_types" => "ALL_BUSINESS_TYPES");
508 $form->{ALL_EMPLOYEES} = SL::DB::Manager::Employee->get_all_sorted(query => [ deleted => 0 ]);
510 $form->{SHOW_VC_DROP_DOWN} = $myconfig{vclimit} > scalar @{ $form->{ALL_VC} };
511 $form->{title} = $locale->text('Delivery Orders');
515 print $form->parse_html_template('do/search');
517 $main::lxdebug->leave_sub();
521 $main::lxdebug->enter_sub();
525 my $form = $main::form;
526 my %myconfig = %main::myconfig;
527 my $locale = $main::locale;
528 my $cgi = $::request->{cgi};
530 $form->{department_id} = (split /--/, $form->{department})[-1];
531 ($form->{ $form->{vc} }, $form->{"$form->{vc}_id"}) = split(/--/, $form->{ $form->{vc} });
533 report_generator_set_default_sort('transdate', 1);
537 $form->{rowcount} = scalar @{ $form->{DO} };
540 ids transdate reqdate
542 ordnumber customernumber cusordnumber
543 name employee salesman
544 shipvia globalprojectnumber
545 transaction_description department
550 $form->{l_open} = $form->{l_closed} = "Y" if ($form->{open} && $form->{closed});
551 $form->{l_delivered} = "Y" if ($form->{delivered} && $form->{notdelivered});
553 $form->{title} = $locale->text('Delivery Orders');
555 my $attachment_basename = $form->{vc} eq 'vendor' ? $locale->text('purchase_delivery_order_list') : $locale->text('sales_delivery_order_list');
557 my $report = SL::ReportGenerator->new(\%myconfig, $form);
559 my @hidden_variables = map { "l_${_}" } @columns;
560 push @hidden_variables, $form->{vc}, qw(l_closed l_notdelivered open closed delivered notdelivered donumber ordnumber serialnumber cusordnumber
561 transaction_description transdatefrom transdateto reqdatefrom reqdateto
562 type vc employee_id salesman_id project_id parts_partnumber parts_description
563 insertdatefrom insertdateto business_id);
565 my $href = build_std_url('action=orders', grep { $form->{$_} } @hidden_variables);
568 'ids' => { 'text' => '', },
569 'transdate' => { 'text' => $locale->text('Delivery Order Date'), },
570 'reqdate' => { 'text' => $locale->text('Reqdate'), },
571 'id' => { 'text' => $locale->text('ID'), },
572 'donumber' => { 'text' => $locale->text('Delivery Order'), },
573 'ordnumber' => { 'text' => $locale->text('Order'), },
574 'customernumber' => { 'text' => $locale->text('Customer Number'), },
575 'cusordnumber' => { 'text' => $locale->text('Customer Order Number'), },
576 'name' => { 'text' => $form->{vc} eq 'customer' ? $locale->text('Customer') : $locale->text('Vendor'), },
577 'employee' => { 'text' => $locale->text('Employee'), },
578 'salesman' => { 'text' => $locale->text('Salesman'), },
579 'shipvia' => { 'text' => $locale->text('Ship via'), },
580 'globalprojectnumber' => { 'text' => $locale->text('Project Number'), },
581 'transaction_description' => { 'text' => $locale->text('Transaction description'), },
582 'open' => { 'text' => $locale->text('Open'), },
583 'delivered' => { 'text' => $locale->text('Delivered'), },
584 'department' => { 'text' => $locale->text('Department'), },
585 'insertdate' => { 'text' => $locale->text('Insert Date'), },
588 foreach my $name (qw(id transdate reqdate donumber ordnumber name employee salesman shipvia transaction_description department insertdate)) {
589 my $sortdir = $form->{sort} eq $name ? 1 - $form->{sortdir} : $form->{sortdir};
590 $column_defs{$name}->{link} = $href . "&sort=$name&sortdir=$sortdir";
593 $form->{"l_type"} = "Y";
594 map { $column_defs{$_}->{visible} = $form->{"l_${_}"} ? 1 : 0 } @columns;
596 $column_defs{ids}->{visible} = 'HTML';
598 $report->set_columns(%column_defs);
599 $report->set_column_order(@columns);
601 $report->set_export_options('orders', @hidden_variables, qw(sort sortdir));
603 $report->set_sort_indicator($form->{sort}, $form->{sortdir});
606 if ($form->{customer}) {
607 push @options, $locale->text('Customer') . " : $form->{customer}";
609 if ($form->{vendor}) {
610 push @options, $locale->text('Vendor') . " : $form->{vendor}";
612 if ($form->{cp_name}) {
613 push @options, $locale->text('Contact Person') . " : $form->{cp_name}";
615 if ($form->{department}) {
616 my ($department) = split /--/, $form->{department};
617 push @options, $locale->text('Department') . " : $department";
619 if ($form->{donumber}) {
620 push @options, $locale->text('Delivery Order Number') . " : $form->{donumber}";
622 if ($form->{ordnumber}) {
623 push @options, $locale->text('Order Number') . " : $form->{ordnumber}";
625 push @options, $locale->text('Serial Number') . " : $form->{serialnumber}" if $form->{serialnumber};
626 if ($form->{business_id}) {
627 my $vc_type_label = $form->{vc} eq 'customer' ? $locale->text('Customer type') : $locale->text('Vendor type');
628 push @options, $vc_type_label . " : " . SL::DB::Business->new(id => $form->{business_id})->load->description;
630 if ($form->{transaction_description}) {
631 push @options, $locale->text('Transaction description') . " : $form->{transaction_description}";
633 if ($form->{parts_description}) {
634 push @options, $locale->text('Part Description') . " : $form->{parts_description}";
636 if ($form->{parts_partnumber}) {
637 push @options, $locale->text('Part Number') . " : $form->{parts_partnumber}";
639 if ( $form->{transdatefrom} or $form->{transdateto} ) {
640 push @options, $locale->text('Delivery Order Date');
641 push @options, $locale->text('From') . " " . $locale->date(\%myconfig, $form->{transdatefrom}, 1) if $form->{transdatefrom};
642 push @options, $locale->text('Bis') . " " . $locale->date(\%myconfig, $form->{transdateto}, 1) if $form->{transdateto};
644 if ( $form->{reqdatefrom} or $form->{reqdateto} ) {
645 push @options, $locale->text('Reqdate');
646 push @options, $locale->text('From') . " " . $locale->date(\%myconfig, $form->{reqdatefrom}, 1) if $form->{reqdatefrom};
647 push @options, $locale->text('Bis') . " " . $locale->date(\%myconfig, $form->{reqdateto}, 1) if $form->{reqdateto};
649 if ( $form->{insertdatefrom} or $form->{insertdateto} ) {
650 push @options, $locale->text('Insert Date');
651 push @options, $locale->text('From') . " " . $locale->date(\%myconfig, $form->{insertdatefrom}, 1) if $form->{insertdatefrom};
652 push @options, $locale->text('Bis') . " " . $locale->date(\%myconfig, $form->{insertdateto}, 1) if $form->{insertdateto};
655 push @options, $locale->text('Open');
657 if ($form->{closed}) {
658 push @options, $locale->text('Closed');
660 if ($form->{delivered}) {
661 push @options, $locale->text('Delivered');
663 if ($form->{notdelivered}) {
664 push @options, $locale->text('Not delivered');
667 $report->set_options('top_info_text' => join("\n", @options),
668 'raw_top_info_text' => $form->parse_html_template('do/orders_top'),
669 'raw_bottom_info_text' => $form->parse_html_template('do/orders_bottom'),
670 'output_format' => 'HTML',
671 'title' => $form->{title},
672 'attachment_basename' => $attachment_basename . strftime('_%Y%m%d', localtime time),
674 $report->set_options_from_form();
675 $locale->set_numberformat_wo_thousands_separator(\%myconfig) if lc($report->{options}->{output_format}) eq 'csv';
677 # add sort and escape callback, this one we use for the add sub
678 $form->{callback} = $href .= "&sort=$form->{sort}";
680 # escape callback for href
681 my $callback = $form->escape($href);
683 my $edit_url = build_std_url('action=edit', 'type', 'vc');
684 my $edit_order_url = build_std_url('script=oe.pl', 'type=' . ($form->{type} eq 'sales_delivery_order' ? 'sales_order' : 'purchase_order'), 'action=edit');
688 foreach my $dord (@{ $form->{DO} }) {
689 $dord->{open} = $dord->{closed} ? $locale->text('No') : $locale->text('Yes');
690 $dord->{delivered} = $dord->{delivered} ? $locale->text('Yes') : $locale->text('No');
692 my $row = { map { $_ => { 'data' => $dord->{$_} } } @columns };
695 'raw_data' => $cgi->hidden('-name' => "trans_id_${idx}", '-value' => $dord->{id})
696 . $cgi->checkbox('-name' => "multi_id_${idx}", '-value' => 1, '-label' => ''),
697 'valign' => 'center',
701 $row->{donumber}->{link} = $edit_url . "&id=" . E($dord->{id}) . "&callback=${callback}";
702 $row->{ordnumber}->{link} = $edit_order_url . "&id=" . E($dord->{oe_id}) . "&callback=${callback}" if $dord->{oe_id};
703 $report->add_data($row);
708 $report->generate_with_headers();
710 $main::lxdebug->leave_sub();
714 $main::lxdebug->enter_sub();
720 my $form = $main::form;
721 my %myconfig = %main::myconfig;
722 my $locale = $main::locale;
724 $form->mtime_ischanged('delivery_orders');
726 $form->{defaultcurrency} = $form->get_default_currency(\%myconfig);
728 $form->isblank("transdate", $locale->text('Delivery Order Date missing!'));
730 $form->{donumber} =~ s/^\s*//g;
731 $form->{donumber} =~ s/\s*$//g;
733 my $msg = ucfirst $form->{vc};
734 $form->isblank($form->{vc}, $locale->text($msg . " missing!"));
736 # $locale->text('Customer missing!');
737 # $locale->text('Vendor missing!');
739 remove_emptied_rows();
742 # if the name changed get new values
743 if (check_name($form->{vc})) {
745 $::dispatcher->end_request;
748 $form->{id} = 0 if $form->{saveasnew};
752 if(!exists $form->{addition}) {
753 $form->{snumbers} = qq|donumber_| . $form->{donumber};
754 $form->{addition} = "SAVED";
757 # /saving the history
759 $form->{simple_save} = 1;
760 if (!$params{no_redirect} && !$form->{print_and_save}) {
761 delete @{$form}{ary_diff([keys %{ $form }], [qw(login id script type cursor_fokus)])};
763 $::dispatcher->end_request;
765 $main::lxdebug->leave_sub();
769 $main::lxdebug->enter_sub();
773 my $form = $main::form;
774 my %myconfig = %main::myconfig;
775 my $locale = $main::locale;
779 if(!exists $form->{addition}) {
780 $form->{snumbers} = qq|donumber_| . $form->{donumber};
781 $form->{addition} = "DELETED";
784 # /saving the history
786 $form->info($locale->text('Delivery Order deleted!'));
787 $::dispatcher->end_request;
790 $form->error($locale->text('Cannot delete delivery order!'));
792 $main::lxdebug->leave_sub();
796 $main::lxdebug->enter_sub();
798 my $form = $main::form;
799 my %myconfig = %main::myconfig;
800 my $locale = $main::locale;
803 $form->mtime_ischanged('delivery_orders');
805 $main::auth->assert($form->{type} eq 'purchase_delivery_order' ? 'vendor_invoice_edit' : 'invoice_edit');
807 $form->{convert_from_do_ids} = $form->{id};
808 $form->{deliverydate} = $form->{transdate};
809 $form->{transdate} = $form->{invdate} = $form->current_date(\%myconfig);
810 $form->{duedate} = $form->current_date(\%myconfig, $form->{invdate}, $form->{terms} * 1);
811 $form->{defaultcurrency} = $form->get_default_currency(\%myconfig);
815 delete @{$form}{qw(id closed delivered)};
817 my ($script, $buysell);
818 if ($form->{type} eq 'purchase_delivery_order') {
819 $form->{title} = $locale->text('Add Vendor Invoice');
820 $form->{script} = 'ir.pl';
825 $form->{title} = $locale->text('Add Sales Invoice');
826 $form->{script} = 'is.pl';
831 for my $i (1 .. $form->{rowcount}) {
832 map { $form->{"${_}_${i}"} = $form->parse_amount(\%myconfig, $form->{"${_}_${i}"}) if $form->{"${_}_${i}"} } qw(ship qty sellprice lastcost basefactor discount);
834 # adds a customer/vendor discount, unless we have a workflow case
835 # CAVEAT: has to be done, after the above parse_amount
836 unless ($form->{"ordnumber"}) {
837 if ($form->{discount}) { # Falls wir einen Lieferanten-/Kundenrabatt haben
838 # und rabattfähig sind, dann
839 unless ($form->{"not_discountable_$i"}) {
840 $form->{"discount_$i"} = $form->{discount}*100; # ... nehmen wir diesen Rabatt
844 $form->{"donumber_$i"} = $form->{donumber};
845 $form->{"converted_from_delivery_order_items_id_$i"} = delete $form->{"delivery_order_items_id_$i"};
848 $form->{type} = "invoice";
851 $main::locale = Locale->new("$myconfig{countrycode}", "$script");
852 $locale = $main::locale;
854 require "bin/mozilla/$form->{script}";
856 my $currency = $form->{currency};
859 if ($form->{ordnumber}) {
860 require SL::DB::Order;
861 if (my $order = SL::DB::Manager::Order->find_by(ordnumber => $form->{ordnumber})) {
863 $form->{orddate} = $order->transdate_as_date;
864 $form->{$_} = $order->$_ for qw(payment_id salesman_id taxzone_id quonumber);
868 $form->{currency} = $currency;
869 $form->{exchangerate} = "";
870 $form->{forex} = $form->check_exchangerate(\%myconfig, $form->{currency}, $form->{invdate}, $buysell);
871 $form->{exchangerate} = $form->{forex} if ($form->{forex});
876 for my $i (1 .. $form->{rowcount}) {
877 $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"});
879 my ($dec) = ($form->{"sellprice_$i"} =~ /\.(\d+)/);
881 my $decimalplaces = ($dec > 2) ? $dec : 2;
883 # copy delivery date from reqdate for order -> invoice conversion
884 $form->{"deliverydate_$i"} = $form->{"reqdate_$i"}
885 unless $form->{"deliverydate_$i"};
888 $form->{"sellprice_$i"} =
889 $form->format_amount(\%myconfig, $form->{"sellprice_$i"},
892 $form->{"lastcost_$i"} =
893 $form->format_amount(\%myconfig, $form->{"lastcost_$i"},
896 (my $dec_qty) = ($form->{"qty_$i"} =~ /\.(\d+)/);
897 $dec_qty = length $dec_qty;
899 $form->format_amount(\%myconfig, $form->{"qty_$i"}, $dec_qty);
905 $main::lxdebug->leave_sub();
909 $main::lxdebug->enter_sub();
911 my $form = $main::form;
912 my %myconfig = %main::myconfig;
913 my $locale = $main::locale;
916 $main::auth->assert($form->{type} eq 'sales_delivery_order' ? 'invoice_edit' : 'vendor_invoice_edit');
918 my @do_ids = map { $form->{"trans_id_$_"} } grep { $form->{"multi_id_$_"} } (1..$form->{rowcount});
920 if (!scalar @do_ids) {
921 $form->show_generic_error($locale->text('You have not selected any delivery order.'), 'back_button' => 1);
924 map { delete $form->{$_} } grep { m/^(?:trans|multi)_id_\d+/ } keys %{ $form };
926 if (!DO->retrieve('vc' => $form->{vc}, 'ids' => \@do_ids)) {
927 $form->show_generic_error($form->{vc} eq 'customer' ?
928 $locale->text('You cannot create an invoice for delivery orders for different customers.') :
929 $locale->text('You cannot create an invoice for delivery orders from different vendors.'),
933 my $source_type = $form->{type};
934 $form->{convert_from_do_ids} = join ' ', @do_ids;
935 # bei der auswahl von mehreren Lieferscheinen fuer eine Rechnung, die einfach in donumber_array
936 # zwischenspeichern (DO.pm) und als ' '-separierte Liste wieder zurueckschreiben
937 # Hinweis: delete gibt den wert zurueck und loescht danach das element (nett und einfach)
938 # $shell: perldoc perlunc; /delete EXPR
939 $form->{donumber} = delete $form->{donumber_array};
940 $form->{ordnumber} = delete $form->{ordnumber_array};
941 $form->{cusordnumber} = delete $form->{cusordnumber_array};
942 $form->{deliverydate} = $form->{transdate};
943 $form->{transdate} = $form->current_date(\%myconfig);
944 $form->{duedate} = $form->current_date(\%myconfig, $form->{invdate}, $form->{terms} * 1);
945 $form->{type} = "invoice";
947 $form->{defaultcurrency} = $form->get_default_currency(\%myconfig);
949 my ($script, $buysell);
950 if ($source_type eq 'purchase_delivery_order') {
951 $form->{title} = $locale->text('Add Vendor Invoice');
952 $form->{script} = 'ir.pl';
957 $form->{title} = $locale->text('Add Sales Invoice');
958 $form->{script} = 'is.pl';
963 map { delete $form->{$_} } qw(id subject message cc bcc printed emailed queued);
965 # get vendor or customer discount
967 my $saved_form = save_form();
968 if ($form->{vc} eq 'vendor') {
969 IR->get_vendor(\%myconfig, \%$form);
970 $vc_discount = $form->{vendor_discount};
972 IS->get_customer(\%myconfig, \%$form);
973 $vc_discount = $form->{customer_discount};
975 # use payment terms from customer or vendor
976 restore_form($saved_form,0,qw(payment_id));
978 $form->{rowcount} = 0;
979 foreach my $ref (@{ $form->{form_details} }) {
981 $ref->{reqdate} ||= $ref->{dord_transdate}; # copy transdates into each invoice row
982 map { $form->{"${_}_$form->{rowcount}"} = $ref->{$_} } keys %{ $ref };
983 map { $form->{"${_}_$form->{rowcount}"} = $form->format_amount(\%myconfig, $ref->{$_}) } qw(qty sellprice lastcost);
984 $form->{"converted_from_delivery_order_items_id_$form->{rowcount}"} = delete $form->{"delivery_order_items_id_$form->{rowcount}"};
986 if ($vc_discount){ # falls wir einen Lieferanten/Kundenrabatt haben
987 # und keinen anderen discount wert an $i ...
988 $form->{"discount_$form->{rowcount}"} ||= $vc_discount; # ... nehmen wir diesen Rabatt
991 $form->{"discount_$form->{rowcount}"} = $form->{"discount_$form->{rowcount}"} * 100; #s.a. Bug 1151
992 # Anm.: Eine Änderung des discounts in der SL/DO.pm->retrieve (select (doi.discount * 100) as discount) ergibt in psql einen
993 # Wert von 10.0000001490116. Ferner ist der Rabatt in der Rechnung dann bei 1.0 (?). Deswegen lasse ich das hier. jb 10.10.09
995 $form->{"discount_$form->{rowcount}"} = $form->format_amount(\%myconfig, $form->{"discount_$form->{rowcount}"});
997 delete $form->{form_details};
999 $locale = Locale->new("$myconfig{countrycode}", "$script");
1001 require "bin/mozilla/$form->{script}";
1008 $main::lxdebug->leave_sub();
1012 $main::lxdebug->enter_sub();
1016 my $form = $main::form;
1018 $form->{saveasnew} = 1;
1019 $form->{closed} = 0;
1020 $form->{delivered} = 0;
1021 map { delete $form->{$_} } qw(printed emailed queued);
1022 delete @{ $form }{ grep { m/^stock_(?:in|out)_\d+/ } keys %{ $form } };
1023 $form->{"converted_from_delivery_order_items_id_$_"} = delete $form->{"delivery_order_items_id_$_"} for 1 .. $form->{"rowcount"};
1024 # Let kivitendo assign a new order number if the user hasn't changed the
1025 # previous one. If it has been changed manually then use it as-is.
1026 $form->{donumber} =~ s/^\s*//g;
1027 $form->{donumber} =~ s/\s*$//g;
1028 if ($form->{saved_donumber} && ($form->{saved_donumber} eq $form->{donumber})) {
1029 delete($form->{donumber});
1034 $main::lxdebug->leave_sub();
1038 $main::lxdebug->enter_sub();
1042 $::form->mtime_ischanged('delivery_orders','mail');
1044 $::form->{print_and_save} = 1;
1046 my $saved_form = save_form();
1050 restore_form($saved_form, 0, qw(id ordnumber quonumber));
1054 $main::lxdebug->leave_sub();
1057 sub calculate_stock_in_out {
1058 $main::lxdebug->enter_sub();
1060 my $form = $main::form;
1064 if (!$form->{"id_${i}"}) {
1065 $main::lxdebug->leave_sub();
1069 my $all_units = AM->retrieve_all_units();
1071 my $in_out = $form->{type} =~ /^sales/ ? 'out' : 'in';
1072 my $sinfo = DO->unpack_stock_information('packed' => $form->{"stock_${in_out}_${i}"});
1074 my $do_qty = AM->sum_with_unit($::form->{"qty_$i"}, $::form->{"unit_$i"});
1075 my $sum = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $sinfo });
1076 my $matches = $do_qty == $sum;
1078 my $content = $form->format_amount_units('amount' => $sum * 1,
1079 'part_unit' => $form->{"partunit_$i"},
1080 'amount_unit' => $all_units->{$form->{"partunit_$i"}}->{base_unit},
1081 'conv_units' => 'convertible_not_smaller',
1083 $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="?">|;
1085 $main::lxdebug->leave_sub();
1090 sub get_basic_bin_wh_info {
1091 $main::lxdebug->enter_sub();
1093 my $stock_info = shift;
1095 my $form = $main::form;
1097 foreach my $sinfo (@{ $stock_info }) {
1098 next unless ($sinfo->{bin_id});
1100 my $bin_info = WH->get_basic_bin_info('id' => $sinfo->{bin_id});
1101 map { $sinfo->{"${_}_description"} = $sinfo->{"${_}description"} = $bin_info->{"${_}_description"} } qw(bin warehouse);
1104 $main::lxdebug->leave_sub();
1107 sub stock_in_out_form {
1108 $main::lxdebug->enter_sub();
1110 my $form = $main::form;
1112 if ($form->{in_out} eq 'out') {
1118 $main::lxdebug->leave_sub();
1121 sub redo_stock_info {
1122 $main::lxdebug->enter_sub();
1126 my $form = $main::form;
1128 my @non_empty = grep { $_->{qty} } @{ $params{stock_info} };
1130 if ($params{add_empty_row}) {
1132 'warehouse_id' => scalar(@non_empty) ? $non_empty[-1]->{warehouse_id} : undef,
1133 'bin_id' => scalar(@non_empty) ? $non_empty[-1]->{bin_id} : undef,
1137 @{ $params{stock_info} } = @non_empty;
1139 $main::lxdebug->leave_sub();
1142 sub update_stock_in {
1143 $main::lxdebug->enter_sub();
1145 my $form = $main::form;
1146 my %myconfig = %main::myconfig;
1148 my $stock_info = [];
1150 foreach my $i (1..$form->{rowcount}) {
1151 $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
1152 push @{ $stock_info }, { map { $_ => $form->{"${_}_${i}"} } qw(warehouse_id bin_id chargenumber
1153 bestbefore qty unit delivery_order_items_stock_id) };
1156 display_stock_in_form($stock_info);
1158 $main::lxdebug->leave_sub();
1162 $main::lxdebug->enter_sub();
1164 my $form = $main::form;
1166 my $stock_info = DO->unpack_stock_information('packed' => $form->{stock});
1168 display_stock_in_form($stock_info);
1170 $main::lxdebug->leave_sub();
1173 sub display_stock_in_form {
1174 $main::lxdebug->enter_sub();
1176 my $stock_info = shift;
1178 my $form = $main::form;
1179 my %myconfig = %main::myconfig;
1180 my $locale = $main::locale;
1182 $form->{title} = $locale->text('Stock');
1184 my $part_info = IC->get_basic_part_info('id' => $form->{parts_id});
1186 # Standardlagerplatz für Standard-Auslagern verwenden, falls keiner für die Ware explizit definiert wurde
1187 if ($::instance_conf->get_transfer_default_use_master_default_bin) {
1188 $part_info->{warehouse_id} ||= $::instance_conf->get_warehouse_id;
1189 $part_info->{bin_id} ||= $::instance_conf->get_bin_id;
1192 my $units = AM->retrieve_units(\%myconfig, $form);
1193 # der zweite Parameter von unit_select_data gibt den default-Namen (selected) vor
1194 my $units_data = AM->unit_select_data($units, $form->{do_unit}, undef, $part_info->{unit});
1196 $form->get_lists('warehouses' => { 'key' => 'WAREHOUSES',
1197 'bins' => 'BINS' });
1199 redo_stock_info('stock_info' => $stock_info, 'add_empty_row' => !$form->{delivered});
1201 get_basic_bin_wh_info($stock_info);
1203 $form->header(no_layout => 1);
1204 print $form->parse_html_template('do/stock_in_form', { 'UNITS' => $units_data,
1205 'STOCK_INFO' => $stock_info,
1206 'PART_INFO' => $part_info, });
1208 $main::lxdebug->leave_sub();
1211 sub _stock_in_out_set_qty_display {
1212 my $stock_info = shift;
1214 my $all_units = AM->retrieve_all_units();
1215 my $sum = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
1216 $form->{qty_display} = $form->format_amount_units(amount => $sum * 1,
1217 part_unit => $form->{partunit},
1218 amount_unit => $all_units->{ $form->{partunit} }->{base_unit},
1219 conv_units => 'convertible_not_smaller',
1224 $main::lxdebug->enter_sub();
1226 my $form = $main::form;
1227 my %myconfig = %main::myconfig;
1229 my $stock_info = [];
1231 foreach my $i (1..$form->{rowcount}) {
1232 $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
1234 next if ($form->{"qty_$i"} <= 0);
1236 push @{ $stock_info }, { map { $_ => $form->{"${_}_${i}"} } qw(delivery_order_items_stock_id warehouse_id bin_id chargenumber bestbefore qty unit) };
1239 $form->{stock} = YAML::Dump($stock_info);
1241 _stock_in_out_set_qty_display($stock_info);
1243 my $do_qty = AM->sum_with_unit($::form->parse_amount(\%::myconfig, $::form->{do_qty}), $::form->{do_unit});
1244 my $transfer_qty = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
1247 print $form->parse_html_template('do/set_stock_in_out', {
1248 qty_matches => $do_qty == $transfer_qty,
1251 $main::lxdebug->leave_sub();
1254 sub stock_out_form {
1255 $main::lxdebug->enter_sub();
1257 my $form = $main::form;
1258 my %myconfig = %main::myconfig;
1259 my $locale = $main::locale;
1261 $form->{title} = $locale->text('Release From Stock');
1263 my $part_info = IC->get_basic_part_info('id' => $form->{parts_id});
1265 my $units = AM->retrieve_units(\%myconfig, $form);
1266 my $units_data = AM->unit_select_data($units, undef, undef, $part_info->{unit});
1268 my @contents = DO->get_item_availability('parts_id' => $form->{parts_id});
1270 my $stock_info = DO->unpack_stock_information('packed' => $form->{stock});
1272 if (!$form->{delivered}) {
1273 foreach my $row (@contents) {
1274 $row->{available_qty} = $form->format_amount_units('amount' => $row->{qty} * 1,
1275 'part_unit' => $part_info->{unit},
1276 'conv_units' => 'convertible_not_smaller',
1279 foreach my $sinfo (@{ $stock_info }) {
1280 next if (($row->{bin_id} != $sinfo->{bin_id}) ||
1281 ($row->{warehouse_id} != $sinfo->{warehouse_id}) ||
1282 ($row->{chargenumber} ne $sinfo->{chargenumber}) ||
1283 ($row->{bestbefore} ne $sinfo->{bestbefore}));
1285 map { $row->{"stock_$_"} = $sinfo->{$_} } qw(qty unit error delivery_order_items_stock_id);
1290 get_basic_bin_wh_info($stock_info);
1292 foreach my $sinfo (@{ $stock_info }) {
1293 map { $sinfo->{"stock_$_"} = $sinfo->{$_} } qw(qty unit);
1297 $form->header(no_layout => 1);
1298 print $form->parse_html_template('do/stock_out_form', { 'UNITS' => $units_data,
1299 'WHCONTENTS' => $form->{delivered} ? $stock_info : \@contents,
1300 'PART_INFO' => $part_info, });
1302 $main::lxdebug->leave_sub();
1306 $main::lxdebug->enter_sub();
1308 my $form = $main::form;
1309 my %myconfig = %main::myconfig;
1310 my $locale = $main::locale;
1312 my $stock_info = [];
1314 foreach my $i (1 .. $form->{rowcount}) {
1315 $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
1317 next if ($form->{"qty_$i"} <= 0);
1319 push @{ $stock_info }, {
1320 'warehouse_id' => $form->{"warehouse_id_$i"},
1321 'bin_id' => $form->{"bin_id_$i"},
1322 'chargenumber' => $form->{"chargenumber_$i"},
1323 'bestbefore' => $form->{"bestbefore_$i"},
1324 'qty' => $form->{"qty_$i"},
1325 'unit' => $form->{"unit_$i"},
1327 'delivery_order_items_stock_id' => $form->{"delivery_order_items_stock_id_$i"},
1331 my @errors = DO->check_stock_availability('requests' => $stock_info,
1332 'parts_id' => $form->{parts_id});
1334 $form->{stock} = YAML::Dump($stock_info);
1337 $form->{ERRORS} = [];
1338 map { push @{ $form->{ERRORS} }, $locale->text('Error in row #1: The quantity you entered is bigger than the stocked quantity.', $_->{row}); } @errors;
1339 stock_in_out_form();
1342 _stock_in_out_set_qty_display($stock_info);
1344 my $do_qty = AM->sum_with_unit($::form->parse_amount(\%::myconfig, $::form->{do_qty}), $::form->{do_unit});
1345 my $transfer_qty = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
1348 print $form->parse_html_template('do/set_stock_in_out', {
1349 qty_matches => $do_qty == $transfer_qty,
1353 $main::lxdebug->leave_sub();
1357 $main::lxdebug->enter_sub();
1359 my $form = $main::form;
1360 my %myconfig = %main::myconfig;
1361 my $locale = $main::locale;
1363 if ($form->{id} && DO->is_marked_as_delivered(id => $form->{id})) {
1364 $form->show_generic_error($locale->text('The parts for this delivery order have already been transferred in.'), 'back_button' => 1);
1367 save(no_redirect => 1);
1369 my @part_ids = map { $form->{"id_${_}"} } grep { $form->{"id_${_}"} && $form->{"stock_in_${_}"} } (1 .. $form->{rowcount});
1373 my $units = AM->retrieve_units(\%myconfig, $form);
1374 my %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
1377 $form->{ERRORS} = [];
1379 foreach my $i (1 .. $form->{rowcount}) {
1380 next unless ($form->{"id_$i"} && $form->{"stock_in_$i"});
1382 my $row_sum_base_qty = 0;
1383 my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
1385 foreach my $request (@{ DO->unpack_stock_information('packed' => $form->{"stock_in_$i"}) }) {
1386 $request->{parts_id} = $form->{"id_$i"};
1387 $row_sum_base_qty += $request->{qty} * $units->{$request->{unit}}->{factor} / $base_unit_factor;
1389 $request->{project_id} = $form->{"project_id_$i"} || $form->{globalproject_id};
1391 push @all_requests, $request;
1394 next if (0 == $row_sum_base_qty);
1396 my $do_base_qty = $form->parse_amount(\%myconfig, $form->{"qty_$i"}) * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
1398 # if ($do_base_qty != $row_sum_base_qty) {
1399 # push @{ $form->{ERRORS} }, $locale->text('Error in position #1: You must either assign no stock at all or the full quantity of #2 #3.',
1400 # $i, $form->{"qty_$i"}, $form->{"unit_$i"});
1404 if (@{ $form->{ERRORS} }) {
1405 push @{ $form->{ERRORS} }, $locale->text('The delivery order has not been marked as delivered. The warehouse contents have not changed.');
1407 set_headings('edit');
1409 $main::lxdebug->leave_sub();
1411 $::dispatcher->end_request;
1415 DO->transfer_in_out('direction' => 'in',
1416 'requests' => \@all_requests);
1418 SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
1420 $form->{callback} = 'do.pl?action=edit&type=purchase_delivery_order&id=' . $form->escape($form->{id});
1423 $main::lxdebug->leave_sub();
1427 $main::lxdebug->enter_sub();
1429 my $form = $main::form;
1430 my %myconfig = %main::myconfig;
1431 my $locale = $main::locale;
1433 if ($form->{id} && DO->is_marked_as_delivered(id => $form->{id})) {
1434 $form->show_generic_error($locale->text('The parts for this delivery order have already been transferred out.'), 'back_button' => 1);
1437 save(no_redirect => 1);
1439 my @part_ids = map { $form->{"id_${_}"} } grep { $form->{"id_${_}"} && $form->{"stock_out_${_}"} } (1 .. $form->{rowcount});
1443 my $units = AM->retrieve_units(\%myconfig, $form);
1444 my %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
1447 $form->{ERRORS} = [];
1449 foreach my $i (1 .. $form->{rowcount}) {
1450 next unless ($form->{"id_$i"} && $form->{"stock_out_$i"});
1452 my $row_sum_base_qty = 0;
1453 my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
1455 foreach my $request (@{ DO->unpack_stock_information('packed' => $form->{"stock_out_$i"}) }) {
1456 $request->{parts_id} = $form->{"id_$i"};
1457 $request->{base_qty} = $request->{qty} * $units->{$request->{unit}}->{factor} / $base_unit_factor;
1458 $request->{project_id} = $form->{"project_id_$i"} ? $form->{"project_id_$i"} : $form->{globalproject_id};
1460 my $map_key = join '--', ($form->{"id_$i"}, @{$request}{qw(warehouse_id bin_id chargenumber bestbefore)});
1462 $request_map{$map_key} ||= $request;
1463 $request_map{$map_key}->{sum_base_qty} ||= 0;
1464 $request_map{$map_key}->{sum_base_qty} += $request->{base_qty};
1465 $row_sum_base_qty += $request->{base_qty};
1467 push @all_requests, $request;
1470 next if (0 == $row_sum_base_qty);
1472 my $do_base_qty = $form->{"qty_$i"} * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
1474 # if ($do_base_qty != $row_sum_base_qty) {
1475 # push @{ $form->{ERRORS} }, $locale->text('Error in position #1: You must either assign no transfer at all or the full quantity of #2 #3.',
1476 # $i, $form->{"qty_$i"}, $form->{"unit_$i"});
1481 my @bin_ids = map { $_->{bin_id} } values %request_map;
1482 my %bin_info_map = WH->get_basic_bin_info('id' => \@bin_ids);
1483 my @contents = DO->get_item_availability('parts_id' => \@part_ids);
1485 foreach my $inv (@contents) {
1486 my $map_key = join '--', @{$inv}{qw(parts_id warehouse_id bin_id chargenumber bestbefore)};
1488 next unless ($request_map{$map_key});
1490 my $request = $request_map{$map_key};
1491 $request->{ok} = $request->{sum_base_qty} <= $inv->{qty};
1494 foreach my $request (values %request_map) {
1495 next if ($request->{ok});
1497 my $pinfo = $part_info_map{$request->{parts_id}};
1498 my $binfo = $bin_info_map{$request->{bin_id}};
1500 if ($::instance_conf->get_show_bestbefore) {
1501 push @{ $form->{ERRORS} }, $locale->text("There is not enough available of '#1' at warehouse '#2', bin '#3', #4, #5, for the transfer of #6.",
1502 $pinfo->{description},
1503 $binfo->{warehouse_description},
1504 $binfo->{bin_description},
1505 $request->{chargenumber} ? $locale->text('chargenumber #1', $request->{chargenumber}) : $locale->text('no chargenumber'),
1506 $request->{bestbefore} ? $locale->text('bestbefore #1', $request->{bestbefore}) : $locale->text('no bestbefore'),
1507 $form->format_amount_units('amount' => $request->{sum_base_qty},
1508 'part_unit' => $pinfo->{unit},
1509 'conv_units' => 'convertible_not_smaller'));
1511 push @{ $form->{ERRORS} }, $locale->text("There is not enough available of '#1' at warehouse '#2', bin '#3', #4, for the transfer of #5.",
1512 $pinfo->{description},
1513 $binfo->{warehouse_description},
1514 $binfo->{bin_description},
1515 $request->{chargenumber} ? $locale->text('chargenumber #1', $request->{chargenumber}) : $locale->text('no chargenumber'),
1516 $form->format_amount_units('amount' => $request->{sum_base_qty},
1517 'part_unit' => $pinfo->{unit},
1518 'conv_units' => 'convertible_not_smaller'));
1523 if (@{ $form->{ERRORS} }) {
1524 push @{ $form->{ERRORS} }, $locale->text('The delivery order has not been marked as delivered. The warehouse contents have not changed.');
1526 set_headings('edit');
1528 $main::lxdebug->leave_sub();
1530 $::dispatcher->end_request;
1533 DO->transfer_in_out('direction' => 'out',
1534 'requests' => \@all_requests);
1536 SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
1538 $form->{callback} = 'do.pl?action=edit&type=sales_delivery_order&id=' . $form->escape($form->{id});
1541 $main::lxdebug->leave_sub();
1545 $main::lxdebug->enter_sub();
1547 my $form = $main::form;
1549 DO->close_orders('ids' => [ $form->{id} ]);
1551 $form->{closed} = 1;
1555 $main::lxdebug->leave_sub();
1559 $::lxdebug->enter_sub;
1561 $::auth->assert('purchase_delivery_order_edit | sales_delivery_order_edit');
1564 retrieve_partunits();
1566 my $new_rowcount = $::form->{"rowcount"} * 1 + 1;
1567 $::form->{"project_id_${new_rowcount}"} = $::form->{"globalproject_id"};
1569 $::form->language_payment(\%::myconfig);
1571 Common::webdav_folder($::form);
1574 display_row(++$::form->{rowcount});
1577 $::lxdebug->leave_sub;
1581 call_sub($main::form->{yes_nextsub});
1585 call_sub($main::form->{no_nextsub});
1589 call_sub($main::form->{update_nextsub} || $main::form->{nextsub} || 'update_delivery_order');
1593 my $form = $main::form;
1594 my $locale = $main::locale;
1596 foreach my $action (qw(update ship_to print e_mail save transfer_out transfer_out_default sort
1597 transfer_in transfer_in_default mark_closed save_as_new invoice delete)) {
1598 if ($form->{"action_${action}"}) {
1604 $form->error($locale->text('No action defined.'));
1607 sub transfer_out_default {
1608 $main::lxdebug->enter_sub();
1610 my $form = $main::form;
1612 transfer_in_out_default('direction' => 'out');
1614 $main::lxdebug->leave_sub();
1617 sub transfer_in_default {
1618 $main::lxdebug->enter_sub();
1620 my $form = $main::form;
1622 transfer_in_out_default('direction' => 'in');
1624 $main::lxdebug->leave_sub();
1627 # Falls das Standardlagerverfahren aktiv ist, wird
1628 # geprüft, ob alle Standardlagerplätze für die Auslager-
1629 # artikel vorhanden sind UND ob die Warenmenge ausreicht zum
1630 # Auslagern. Falls nicht wird entsprechend eine Fehlermeldung
1631 # generiert. Offen Chargennummer / bestbefore wird nicht berücksichtigt
1632 sub transfer_in_out_default {
1633 $main::lxdebug->enter_sub();
1635 my $form = $main::form;
1636 my %myconfig = %main::myconfig;
1637 my $locale = $main::locale;
1640 my (%missing_default_bins, %qty_parts, @all_requests, %part_info_map, $default_warehouse_id, $default_bin_id);
1642 Common::check_params(\%params, qw(direction));
1644 # entsprechende defaults holen, falls standardlagerplatz verwendet werden soll
1645 if ($::instance_conf->get_transfer_default_use_master_default_bin) {
1646 $default_warehouse_id = $::instance_conf->get_warehouse_id;
1647 $default_bin_id = $::instance_conf->get_bin_id;
1651 my @part_ids = map { $form->{"id_${_}"} } (1 .. $form->{rowcount});
1653 my $units = AM->retrieve_units(\%myconfig, $form);
1654 %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
1655 foreach my $i (1 .. $form->{rowcount}) {
1656 next unless ($form->{"id_$i"});
1657 my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
1658 my $qty = $form->parse_amount(\%myconfig, $form->{"qty_$i"}) * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
1660 $form->show_generic_error($locale->text("Cannot transfer negative entries." ), 'back_button' => 1) if ($qty < 0);
1661 # if we do not want to transfer services and this part is a service, set qty to zero
1662 # ... and do not create a hash entry in %qty_parts below (will skip check for bins for the transfer == out case)
1663 # ... 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)
1665 $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});
1666 $qty_parts{$form->{"id_$i"}} += $qty;
1668 delete $qty_parts{$form->{"id_$i"}} unless $qty_parts{$form->{"id_$i"}};
1669 undef $form->{"stock_in_$i"};
1672 $part_info_map{$form->{"id_$i"}}{bin_id} ||= $default_bin_id;
1673 $part_info_map{$form->{"id_$i"}}{warehouse_id} ||= $default_warehouse_id;
1675 push @all_requests, ($qty == 0) ? { } : {
1676 'chargenumber' => '', #?? die müsste entsprechend geholt werden
1677 #'bestbefore' => undef, # TODO wird nicht berücksichtigt
1678 'bin_id' => $part_info_map{$form->{"id_$i"}}{bin_id},
1680 'parts_id' => $form->{"id_$i"},
1681 'comment' => $locale->text("Default transfer delivery order"),
1682 'unit' => $part_info_map{$form->{"id_$i"}}{unit},
1683 'warehouse_id' => $part_info_map{$form->{"id_$i"}}{warehouse_id},
1684 'oe_id' => $form->{id},
1685 'project_id' => $form->{"project_id_$i"} ? $form->{"project_id_$i"} : $form->{globalproject_id}
1689 # jetzt wird erst überprüft, ob die Stückzahl entsprechend stimmt.
1690 # check if bin (transfer in and transfer out and qty (transfer out) is correct
1691 foreach my $key (keys %qty_parts) {
1693 $missing_default_bins{$key}{missing_bin} = 1 unless ($part_info_map{$key}{bin_id});
1694 next unless ($part_info_map{$key}{bin_id}); # abbruch
1696 if ($params{direction} eq 'out') { # wird nur für ausgehende Mengen benötigt
1697 my ($max_qty, $error) = WH->get_max_qty_parts_bin(parts_id => $key, bin_id => $part_info_map{$key}{bin_id});
1699 # wir können nicht entscheiden, welche charge oder mhd (bestbefore) ausgewählt sein soll
1700 # deshalb rückmeldung nach oben geben, manuell auszulagern
1701 # TODO Bei nur einem Treffer mit Charge oder bestbefore wäre das noch möglich
1702 $missing_default_bins{$key}{chargenumber} = 1;
1704 if ($max_qty < $qty_parts{$key}){
1705 $missing_default_bins{$key}{missing_qty} = $max_qty - $qty_parts{$key};
1711 # Abfrage für Fehlerbehandlung (nur bei direction == out)
1712 if (scalar (keys %missing_default_bins)) {
1714 foreach my $fehler (keys %missing_default_bins) {
1716 my $ware = WH->get_part_description(parts_id => $fehler);
1717 if ($missing_default_bins{$fehler}{missing_bin}){
1718 $fehlertext .= "Kein Standardlagerplatz definiert bei $ware <br>";
1720 if ($missing_default_bins{$fehler}{missing_qty}) { # missing_qty
1721 $fehlertext .= "Es fehlen " . $missing_default_bins{$fehler}{missing_qty}*-1 .
1722 " von $ware auf dem Standard-Lagerplatz " . $part_info_map{$fehler}{bin} . " zum Auslagern<br>";
1724 if ($missing_default_bins{$fehler}{chargenumber}){
1725 $fehlertext .= "Die Ware hat eine Chargennummer oder eine Mindesthaltbarkeit definiert.
1726 Hier kann man nicht automatisch entscheiden.
1727 Bitte diesen Lieferschein manuell auslagern.
1730 # auslagern soll immer gehen, auch wenn nicht genügend auf lager ist.
1731 # der lagerplatz ist hier extra konfigurierbar, bspw. Lager-Korrektur mit
1732 # Lagerplatz Lagerplatz-Korrektur
1733 my $default_warehouse_id_ignore_onhand = $::instance_conf->get_warehouse_id_ignore_onhand;
1734 my $default_bin_id_ignore_onhand = $::instance_conf->get_bin_id_ignore_onhand;
1735 if ($::instance_conf->get_transfer_default_ignore_onhand && $default_bin_id_ignore_onhand) {
1736 # entsprechende defaults holen
1737 # falls chargenumber, bestbefore oder anzahl nicht stimmt, auf automatischen
1738 # lagerplatz wegbuchen!
1739 foreach (@all_requests) {
1740 if ($_->{parts_id} eq $fehler){
1741 $_->{bin_id} = $default_bin_id_ignore_onhand;
1742 $_->{warehouse_id} = $default_warehouse_id_ignore_onhand;
1746 #$main::lxdebug->message(0, 'Fehlertext: ' . $fehlertext);
1747 $form->show_generic_error($locale->text("Cannot transfer. <br> Reason:<br>#1", $fehlertext ), 'back_button' => 1);
1753 # hier der eigentliche fallunterschied für in oder out
1754 my $prefix = $params{direction} eq 'in' ? 'in' : 'out';
1756 # dieser array_ref ist für DO->save da:
1757 # einmal die all_requests in YAML verwandeln, damit delivery_order_items_stock
1758 # gefüllt werden kann.
1759 # could be dumped to the form in the first loop,
1760 # but maybe bin_id and warehouse_id has changed to the "korrekturlager" with
1761 # allowed negative qty ($::instance_conf->get_warehouse_id_ignore_onhand) ...
1763 foreach (@all_requests){
1765 next unless scalar(%{ $_ });
1766 $form->{"stock_${prefix}_$i"} = YAML::Dump([$_]);
1769 save(no_redirect => 1); # Wir können auslagern, deshalb beleg speichern
1770 # und in delivery_order_items_stock speichern
1772 # ... and fill back the persistent dois_id for inventory fk
1773 undef (@all_requests);
1774 foreach my $i (1 .. $form->{rowcount}) {
1775 next unless ($form->{"id_$i"} && $form->{"stock_${prefix}_$i"});
1776 push @all_requests, @{ DO->unpack_stock_information('packed' => $form->{"stock_${prefix}_$i"}) };
1778 DO->transfer_in_out('direction' => $prefix,
1779 'requests' => \@all_requests);
1781 SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
1783 $form->{callback} = 'do.pl?action=edit&type=sales_delivery_order&id=' . $form->escape($form->{id}) if $params{direction} eq 'out';
1784 $form->{callback} = 'do.pl?action=edit&type=purchase_delivery_order&id=' . $form->escape($form->{id}) if $params{direction} eq 'in';
1790 $main::lxdebug->enter_sub();
1794 my $form = $main::form;
1797 save(no_redirect => 1); # has to be done, at least for newly added positions
1799 # hashify partnumbers, positions. key is delivery_order_items_id
1800 for my $i (1 .. ($form->{rowcount}) ) {
1801 $temp_hash{$form->{"delivery_order_items_id_$i"}} = { runningnumber => $form->{"runningnumber_$i"}, partnumber => $form->{"partnumber_$i"} };
1802 if ($form->{id} && $form->{"discount_$i"}) {
1803 # prepare_order assumes a db value if there is a form->id and multiplies *100
1804 # We hope for new controller code (no more format_amount/parse_amount distinction)
1805 $form->{"discount_$i"} /=100;
1808 # naturally sort partnumbers and get a sorted array of doi_ids
1809 my @sorted_doi_ids = sort { Sort::Naturally::ncmp($temp_hash{$a}->{"partnumber"}, $temp_hash{$b}->{"partnumber"}) } keys %temp_hash;
1814 for (@sorted_doi_ids) {
1815 $form->{"runningnumber_$temp_hash{$_}->{runningnumber}"} = $new_number;
1818 # all parse_amounts changes are in form (i.e. , to .) therefore we need
1819 # another format_amount to change it back, for the next save ;-(
1820 # works great except for row discounts (see above comment)
1824 $main::lxdebug->leave_sub();
1836 do.pl - Script for all calls to delivery order
1845 Sorts all position with Natural Sort. Can be activated in form_footer.html like this
1846 C<E<lt>input class="submit" type="submit" name="action_sort" id="sort_button" value="[% 'Sort and Save' | $T8 %]"E<gt>>
1852 Sort and Save can be implemented as an optional button if configuration ca be set by client config.
1853 Example coding for database scripts and templates in (git show af2f24b8), check also
1854 autogeneration for rose (scripts/rose_auto_create_model.pl --h)