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., 675 Mass Ave, Cambridge, MA 02139, USA.
28 #======================================================================
31 #======================================================================
33 use List::MoreUtils qw(uniq);
34 use List::Util qw(max sum);
35 use POSIX qw(strftime);
38 use SL::DB::DeliveryOrder;
42 use SL::MoreCommon qw(ary_diff);
43 use SL::ReportGenerator;
45 use Sort::Naturally ();
46 require "bin/mozilla/arap.pl";
47 require "bin/mozilla/common.pl";
48 require "bin/mozilla/invoice_io.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->{callback} = build_std_url('action=add', 'type', 'vc') unless ($form->{callback});
104 $main::lxdebug->leave_sub();
108 $main::lxdebug->enter_sub();
112 my $form = $main::form;
114 # show history button
115 $form->{javascript} = qq|<script type="text/javascript" src="js/show_history.js"></script>|;
116 #/show hhistory button
118 $form->{simple_save} = 0;
120 set_headings("edit");
122 # editing without stuff to edit? try adding it first
123 if ($form->{rowcount} && !$form->{print_and_save}) {
124 # map { $id++ if $form->{"multi_id_$_"} } (1 .. $form->{rowcount});
128 undef $form->{rowcount};
130 $main::lxdebug->leave_sub();
133 } elsif (!$form->{id}) {
135 $main::lxdebug->leave_sub();
139 my ($language_id, $printer_id);
140 if ($form->{print_and_save}) {
141 $form->{action} = "dispatcher";
142 $form->{action_print} = "1";
143 $form->{resubmit} = 1;
144 $language_id = $form->{language_id};
145 $printer_id = $form->{printer_id};
148 set_headings("edit");
153 if ($form->{print_and_save}) {
154 $form->{language_id} = $language_id;
155 $form->{printer_id} = $printer_id;
160 $main::lxdebug->leave_sub();
164 $main::lxdebug->enter_sub();
168 my $form = $main::form;
169 my %myconfig = %main::myconfig;
171 # get customer/vendor
172 $form->all_vc(\%myconfig, $form->{vc}, ($form->{vc} eq 'customer') ? "AR" : "AP");
174 # retrieve order/quotation
175 $form->{webdav} = $::instance_conf->get_webdav;
177 my $editing = $form->{id};
179 DO->retrieve('vc' => $form->{vc},
180 'ids' => $form->{id});
182 $form->backup_vars(qw(payment_id language_id taxzone_id salesman_id taxincluded cp_id intnotes delivery_term_id currency));
184 # get customer / vendor
185 if ($form->{vc} eq 'vendor') {
186 IR->get_vendor(\%myconfig, \%$form);
187 $form->{discount} = $form->{vendor_discount};
189 IS->get_customer(\%myconfig, \%$form);
190 $form->{discount} = $form->{customer_discount};
193 $form->restore_vars(qw(payment_id language_id taxzone_id intnotes cp_id delivery_term_id));
194 $form->restore_vars(qw(currency)) if ($form->{id} || $form->{convert_from_oe_ids});
195 $form->restore_vars(qw(taxincluded)) if $form->{id};
196 $form->restore_vars(qw(salesman_id)) if $editing;
198 if ($form->{"all_$form->{vc}"}) {
199 unless ($form->{"$form->{vc}_id"}) {
200 $form->{"$form->{vc}_id"} = $form->{"all_$form->{vc}"}->[0]->{id};
204 ($form->{ $form->{vc} }) = split /--/, $form->{ $form->{vc} };
205 $form->{"old$form->{vc}"} = qq|$form->{$form->{vc}}--$form->{"$form->{vc}_id"}|;
207 $form->{employee} = "$form->{employee}--$form->{employee_id}";
209 $main::lxdebug->leave_sub();
213 $main::lxdebug->enter_sub();
217 my $form = $main::form;
218 my %myconfig = %main::myconfig;
220 $form->{formname} = $form->{type} unless $form->{formname};
223 foreach my $ref (@{ $form->{form_details} }) {
224 $form->{rowcount} = ++$i;
226 map { $form->{"${_}_$i"} = $ref->{$_} } keys %{$ref};
228 for my $i (1 .. $form->{rowcount}) {
230 $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"} * 100);
232 $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"});
234 my ($dec) = ($form->{"sellprice_$i"} =~ /\.(\d+)/);
236 my $decimalplaces = ($dec > 2) ? $dec : 2;
238 # copy reqdate from deliverydate for invoice -> order conversion
239 $form->{"reqdate_$i"} = $form->{"deliverydate_$i"} unless $form->{"reqdate_$i"};
241 $form->{"sellprice_$i"} = $form->format_amount(\%myconfig, $form->{"sellprice_$i"}, $decimalplaces);
242 $form->{"lastcost_$i"} = $form->format_amount(\%myconfig, $form->{"lastcost_$i"}, $decimalplaces);
244 (my $dec_qty) = ($form->{"qty_$i"} =~ /\.(\d+)/);
245 $dec_qty = length $dec_qty;
246 $form->{"qty_$i"} = $form->format_amount(\%myconfig, $form->{"qty_$i"}, $dec_qty);
249 $main::lxdebug->leave_sub();
253 $main::lxdebug->enter_sub();
257 my $form = $main::form;
258 my %myconfig = %main::myconfig;
260 $form->{employee_id} = $form->{old_employee_id} if $form->{old_employee_id};
261 $form->{salesman_id} = $form->{old_salesman_id} if $form->{old_salesman_id};
263 my $vc = $form->{vc} eq "customer" ? "customers" : "vendors";
264 $form->get_lists($vc => "ALL_VC",
265 "price_factors" => "ALL_PRICE_FACTORS",
266 "departments" => "ALL_DEPARTMENTS",
267 "business_types" => "ALL_BUSINESS_TYPES",
271 my @old_project_ids = uniq grep { $_ } map { $_ * 1 } ($form->{"globalproject_id"}, map { $form->{"project_id_$_"} } 1..$form->{"rowcount"});
272 my @old_ids_cond = @old_project_ids ? (id => \@old_project_ids) : ();
274 if (($vc eq 'customers') && $::instance_conf->get_customer_projects_only_in_sales) {
277 customer_id => $::form->{customer_id},
278 billable_customer_id => $::form->{customer_id},
283 and => [ active => 1, @customer_cond ],
287 $::form->{ALL_PROJECTS} = SL::DB::Manager::Project->get_all_sorted(query => \@conditions);
288 $::form->{ALL_EMPLOYEES} = SL::DB::Manager::Employee->get_all_sorted(query => [ or => [ id => $::form->{employee_id}, deleted => 0 ] ]);
289 $::form->{ALL_SALESMEN} = SL::DB::Manager::Employee->get_all_sorted(query => [ or => [ id => $::form->{salesman_id}, deleted => 0 ] ]);
290 $::form->{ALL_SHIPTO} = SL::DB::Manager::Shipto->get_all_sorted(query => [
291 or => [ trans_id => $::form->{"$::form->{vc}_id"} * 1, and => [ shipto_id => $::form->{shipto_id} * 1, trans_id => undef ] ]
293 $::form->{ALL_CONTACTS} = SL::DB::Manager::Contact->get_all_sorted(query => [
295 cp_cv_id => $::form->{"$::form->{vc}_id"} * 1,
298 cp_id => $::form->{cp_id} * 1
303 map { $_->{value} = "$_->{description}--$_->{id}" } @{ $form->{ALL_DEPARTMENTS} };
304 map { $_->{value} = "$_->{name}--$_->{id}" } @{ $form->{ALL_VC} };
306 $form->{SHOW_VC_DROP_DOWN} = $myconfig{vclimit} > scalar @{ $form->{ALL_VC} };
308 $form->{oldvcname} = $form->{"old$form->{vc}"};
309 $form->{oldvcname} =~ s/--.*//;
311 my $dispatch_to_popup = '';
312 if ($form->{resubmit} && ($form->{format} eq "html")) {
313 $dispatch_to_popup = "window.open('about:blank','Beleg'); document.do.target = 'Beleg';";
314 $dispatch_to_popup .= "document.do.submit();";
315 } elsif ($form->{resubmit}) {
316 # emulate click for resubmitting actions
317 $dispatch_to_popup = "document.do.${_}.click(); " for grep { /^action_/ } keys %$form;
319 $::request->{layout}->add_javascripts_inline("\$(function(){$dispatch_to_popup});");
322 my $follow_up_vc = $form->{ $form->{vc} eq 'customer' ? 'customer' : 'vendor' };
323 $follow_up_vc =~ s/--\d*\s*$//;
325 $form->{follow_up_trans_info} = $form->{donumber} .'('. $follow_up_vc .')';
327 $::request->{layout}->use_javascript(map { "${_}.js" } qw(kivi.SalesPurchase ckeditor/ckeditor ckeditor/adapters/jquery kivi.io autocomplete_customer autocomplete_part));
330 # Fix für Bug 1082 Erwartet wird: 'abteilungsNAME--abteilungsID'
331 # und Erweiterung für Bug 1760:
332 # Das war leider nur ein Teil-Fix, da das Verhalten den 'Erneuern'-Knopf
333 # nicht überlebt. Konsequent jetzt auf L umgestellt
334 # $ perldoc SL::Template::Plugin::L
335 # Daher entsprechend nur die Anpassung in form_header
336 # und in DO.pm gemacht. 4 Testfälle:
337 # department_id speichern | i.O.
338 # department_id lesen | i.O.
339 # department leer überlebt erneuern | i.O.
340 # department nicht leer überlebt erneuern | i.O.
341 # $main::lxdebug->message(0, 'ABTEILUNGS ID in form?' . $form->{department_id});
342 print $form->parse_html_template('do/form_header');
344 $main::lxdebug->leave_sub();
348 $main::lxdebug->enter_sub();
352 my $form = $main::form;
354 $form->{PRINT_OPTIONS} = print_options('inline' => 1);
355 $form->{ALL_DELIVERY_TERMS} = SL::DB::Manager::DeliveryTerm->get_all_sorted();
357 print $form->parse_html_template('do/form_footer',
358 {transfer_default => ($::instance_conf->get_transfer_default)});
360 $main::lxdebug->leave_sub();
363 sub update_delivery_order {
364 $main::lxdebug->enter_sub();
368 my $form = $main::form;
369 my %myconfig = %main::myconfig;
371 set_headings($form->{"id"} ? "edit" : "add");
373 $form->{insertdate} = SL::DB::DeliveryOrder->new(id => $form->{id})->load->itime_as_date if $form->{id};
378 $payment_id = $form->{payment_id} if $form->{payment_id};
380 check_name($form->{vc});
381 $form->{discount} = $form->{"$form->{vc}_discount"} if defined $form->{"$form->{vc}_discount"};
382 # Problem: Wenn man ohne Erneuern einen Kunden/Lieferanten
383 # wechselt, wird der entsprechende Kunden/ Lieferantenrabatt
384 # nicht übernommen. Grundproblem: In Commit 82574e78
385 # hab ich aus discount customer_discount und vendor_discount
386 # gemacht und entsprechend an den Oberflächen richtig hin-
387 # geschoben. Die damals bessere Lösung wäre gewesen:
388 # In den Templates nur die hidden für form-discount wieder ein-
389 # setzen dann wäre die Verrenkung jetzt nicht notwendig.
390 # TODO: Ggf. Bugfix 1284, 1575 und 817 wieder zusammenführen
391 # Testfälle: Kunden mit Rabatt 0 -> Rabatt 20 i.O.
392 # Kunde mit Rabatt 20 -> Rabatt 0 i.O.
393 # Kunde mit Rabatt 20 -> Rabatt 5,5 i.O.
394 $form->{payment_id} = $payment_id if $form->{payment_id} eq "";
396 my $i = $form->{rowcount};
398 if ( ($form->{"partnumber_$i"} eq "")
399 && ($form->{"description_$i"} eq "")
400 && ($form->{"partsgroup_$i"} eq "")) {
407 if ($form->{type} eq 'purchase_delivery_order') {
408 IR->retrieve_item(\%myconfig, $form);
411 IS->retrieve_item(\%myconfig, $form);
415 my $rows = scalar @{ $form->{item_list} };
418 $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
419 if( !$form->{"qty_$i"} ) {
420 $form->{"qty_$i"} = 1;
425 select_item(mode => $mode, pre_entered_qty => $form->{"qty_$i"});
430 my $sellprice = $form->parse_amount(\%myconfig, $form->{"sellprice_$i"});
432 map { $form->{"${_}_$i"} = $form->{item_list}[0]{$_} } keys %{ $form->{item_list}[0] };
434 $form->{"marge_price_factor_$i"} = $form->{item_list}->[0]->{price_factor};
437 $form->{"sellprice_$i"} = $sellprice;
439 my $record = _make_record();
440 my $price_source = SL::PriceSource->new(record_item => $record->items->[$i-1], record => $record);
441 my $best_price = $price_source->best_price;
442 my $best_discount = $price_source->best_discount;
445 $::form->{"sellprice_$i"} = $best_price->price;
446 $::form->{"active_price_source_$i"} = $best_price->source;
448 if ($best_discount) {
449 $::form->{"discount_$i"} = $best_discount->discount;
450 $::form->{"active_discount_source_$i"} = $best_discount->source;
454 $form->{"sellprice_$i"} = $form->format_amount(\%myconfig, $form->{"sellprice_$i"});
455 $form->{"lastcost_$i"} = $form->format_amount(\%myconfig, $form->{"lastcost_$i"});
456 $form->{"qty_$i"} = $form->format_amount(\%myconfig, $form->{"qty_$i"});
457 $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"} * 100.0);
464 # ok, so this is a new part
465 # ask if it is a part or service item
467 if ( $form->{"partsgroup_$i"}
468 && ($form->{"partsnumber_$i"} eq "")
469 && ($form->{"description_$i"} eq "")) {
471 $form->{"discount_$i"} = "";
472 $form->{"not_discountable_$i"} = "";
476 $form->{"id_$i"} = 0;
482 $main::lxdebug->leave_sub();
486 $main::lxdebug->enter_sub();
490 my $form = $main::form;
491 my %myconfig = %main::myconfig;
492 my $locale = $main::locale;
494 $form->{vc} = $form->{type} eq 'purchase_delivery_order' ? 'vendor' : 'customer';
496 $form->get_lists("projects" => { "key" => "ALL_PROJECTS",
498 "departments" => "ALL_DEPARTMENTS",
499 "$form->{vc}s" => "ALL_VC",
500 "business_types" => "ALL_BUSINESS_TYPES");
501 $form->{ALL_EMPLOYEES} = SL::DB::Manager::Employee->get_all_sorted(query => [ deleted => 0 ]);
503 $form->{SHOW_VC_DROP_DOWN} = $myconfig{vclimit} > scalar @{ $form->{ALL_VC} };
504 $form->{title} = $locale->text('Delivery Orders');
508 print $form->parse_html_template('do/search');
510 $main::lxdebug->leave_sub();
514 $main::lxdebug->enter_sub();
518 my $form = $main::form;
519 my %myconfig = %main::myconfig;
520 my $locale = $main::locale;
521 my $cgi = $::request->{cgi};
523 $form->{department_id} = (split /--/, $form->{department})[-1];
524 ($form->{ $form->{vc} }, $form->{"$form->{vc}_id"}) = split(/--/, $form->{ $form->{vc} });
526 report_generator_set_default_sort('transdate', 1);
530 $form->{rowcount} = scalar @{ $form->{DO} };
533 ids transdate reqdate
535 ordnumber customernumber cusordnumber
536 name employee salesman
537 shipvia globalprojectnumber
538 transaction_description department
543 $form->{l_open} = $form->{l_closed} = "Y" if ($form->{open} && $form->{closed});
544 $form->{l_delivered} = "Y" if ($form->{delivered} && $form->{notdelivered});
546 $form->{title} = $locale->text('Delivery Orders');
548 my $attachment_basename = $form->{vc} eq 'vendor' ? $locale->text('purchase_delivery_order_list') : $locale->text('sales_delivery_order_list');
550 my $report = SL::ReportGenerator->new(\%myconfig, $form);
552 my @hidden_variables = map { "l_${_}" } @columns;
553 push @hidden_variables, $form->{vc}, qw(l_closed l_notdelivered open closed delivered notdelivered donumber ordnumber serialnumber cusordnumber
554 transaction_description transdatefrom transdateto reqdatefrom reqdateto
555 type vc employee_id salesman_id project_id
556 insertdatefrom insertdateto business_id);
558 my $href = build_std_url('action=orders', grep { $form->{$_} } @hidden_variables);
561 'ids' => { 'text' => '', },
562 'transdate' => { 'text' => $locale->text('Delivery Order Date'), },
563 'reqdate' => { 'text' => $locale->text('Reqdate'), },
564 'id' => { 'text' => $locale->text('ID'), },
565 'donumber' => { 'text' => $locale->text('Delivery Order'), },
566 'ordnumber' => { 'text' => $locale->text('Order'), },
567 'customernumber' => { 'text' => $locale->text('Customer Number'), },
568 'cusordnumber' => { 'text' => $locale->text('Customer Order Number'), },
569 'name' => { 'text' => $form->{vc} eq 'customer' ? $locale->text('Customer') : $locale->text('Vendor'), },
570 'employee' => { 'text' => $locale->text('Employee'), },
571 'salesman' => { 'text' => $locale->text('Salesman'), },
572 'shipvia' => { 'text' => $locale->text('Ship via'), },
573 'globalprojectnumber' => { 'text' => $locale->text('Project Number'), },
574 'transaction_description' => { 'text' => $locale->text('Transaction description'), },
575 'open' => { 'text' => $locale->text('Open'), },
576 'delivered' => { 'text' => $locale->text('Delivered'), },
577 'department' => { 'text' => $locale->text('Department'), },
578 'insertdate' => { 'text' => $locale->text('Insert Date'), },
581 foreach my $name (qw(id transdate reqdate donumber ordnumber name employee salesman shipvia transaction_description department insertdate)) {
582 my $sortdir = $form->{sort} eq $name ? 1 - $form->{sortdir} : $form->{sortdir};
583 $column_defs{$name}->{link} = $href . "&sort=$name&sortdir=$sortdir";
586 $form->{"l_type"} = "Y";
587 map { $column_defs{$_}->{visible} = $form->{"l_${_}"} ? 1 : 0 } @columns;
589 $column_defs{ids}->{visible} = 'HTML';
591 $report->set_columns(%column_defs);
592 $report->set_column_order(@columns);
594 $report->set_export_options('orders', @hidden_variables, qw(sort sortdir));
596 $report->set_sort_indicator($form->{sort}, $form->{sortdir});
599 if ($form->{customer}) {
600 push @options, $locale->text('Customer') . " : $form->{customer}";
602 if ($form->{vendor}) {
603 push @options, $locale->text('Vendor') . " : $form->{vendor}";
605 if ($form->{cp_name}) {
606 push @options, $locale->text('Contact Person') . " : $form->{cp_name}";
608 if ($form->{department}) {
609 my ($department) = split /--/, $form->{department};
610 push @options, $locale->text('Department') . " : $department";
612 if ($form->{donumber}) {
613 push @options, $locale->text('Delivery Order Number') . " : $form->{donumber}";
615 if ($form->{ordnumber}) {
616 push @options, $locale->text('Order Number') . " : $form->{ordnumber}";
618 push @options, $locale->text('Serial Number') . " : $form->{serialnumber}" if $form->{serialnumber};
619 if ($form->{business_id}) {
620 my $vc_type_label = $form->{vc} eq 'customer' ? $locale->text('Customer type') : $locale->text('Vendor type');
621 push @options, $vc_type_label . " : " . SL::DB::Business->new(id => $form->{business_id})->load->description;
623 if ($form->{transaction_description}) {
624 push @options, $locale->text('Transaction description') . " : $form->{transaction_description}";
626 if ( $form->{transdatefrom} or $form->{transdateto} ) {
627 push @options, $locale->text('Delivery Order Date');
628 push @options, $locale->text('From') . " " . $locale->date(\%myconfig, $form->{transdatefrom}, 1) if $form->{transdatefrom};
629 push @options, $locale->text('Bis') . " " . $locale->date(\%myconfig, $form->{transdateto}, 1) if $form->{transdateto};
631 if ( $form->{reqdatefrom} or $form->{reqdateto} ) {
632 push @options, $locale->text('Reqdate');
633 push @options, $locale->text('From') . " " . $locale->date(\%myconfig, $form->{reqdatefrom}, 1) if $form->{reqdatefrom};
634 push @options, $locale->text('Bis') . " " . $locale->date(\%myconfig, $form->{reqdateto}, 1) if $form->{reqdateto};
636 if ( $form->{insertdatefrom} or $form->{insertdateto} ) {
637 push @options, $locale->text('Insert Date');
638 push @options, $locale->text('From') . " " . $locale->date(\%myconfig, $form->{insertdatefrom}, 1) if $form->{insertdatefrom};
639 push @options, $locale->text('Bis') . " " . $locale->date(\%myconfig, $form->{insertdateto}, 1) if $form->{insertdateto};
642 push @options, $locale->text('Open');
644 if ($form->{closed}) {
645 push @options, $locale->text('Closed');
647 if ($form->{delivered}) {
648 push @options, $locale->text('Delivered');
650 if ($form->{notdelivered}) {
651 push @options, $locale->text('Not delivered');
654 $report->set_options('top_info_text' => join("\n", @options),
655 'raw_top_info_text' => $form->parse_html_template('do/orders_top'),
656 'raw_bottom_info_text' => $form->parse_html_template('do/orders_bottom'),
657 'output_format' => 'HTML',
658 'title' => $form->{title},
659 'attachment_basename' => $attachment_basename . strftime('_%Y%m%d', localtime time),
661 $report->set_options_from_form();
662 $locale->set_numberformat_wo_thousands_separator(\%myconfig) if lc($report->{options}->{output_format}) eq 'csv';
664 # add sort and escape callback, this one we use for the add sub
665 $form->{callback} = $href .= "&sort=$form->{sort}";
667 # escape callback for href
668 my $callback = $form->escape($href);
670 my $edit_url = build_std_url('action=edit', 'type', 'vc');
671 my $edit_order_url = build_std_url('script=oe.pl', 'type=' . ($form->{type} eq 'sales_delivery_order' ? 'sales_order' : 'purchase_order'), 'action=edit');
675 foreach my $dord (@{ $form->{DO} }) {
676 $dord->{open} = $dord->{closed} ? $locale->text('No') : $locale->text('Yes');
677 $dord->{delivered} = $dord->{delivered} ? $locale->text('Yes') : $locale->text('No');
679 my $row = { map { $_ => { 'data' => $dord->{$_} } } @columns };
682 'raw_data' => $cgi->hidden('-name' => "trans_id_${idx}", '-value' => $dord->{id})
683 . $cgi->checkbox('-name' => "multi_id_${idx}", '-value' => 1, '-label' => ''),
684 'valign' => 'center',
688 $row->{donumber}->{link} = $edit_url . "&id=" . E($dord->{id}) . "&callback=${callback}";
689 $row->{ordnumber}->{link} = $edit_order_url . "&id=" . E($dord->{oe_id}) . "&callback=${callback}" if $dord->{oe_id};
690 $report->add_data($row);
695 $report->generate_with_headers();
697 $main::lxdebug->leave_sub();
701 $main::lxdebug->enter_sub();
707 my $form = $main::form;
708 my %myconfig = %main::myconfig;
709 my $locale = $main::locale;
711 $form->mtime_ischanged('delivery_orders');
713 $form->{defaultcurrency} = $form->get_default_currency(\%myconfig);
715 $form->isblank("transdate", $locale->text('Delivery Order Date missing!'));
717 $form->{donumber} =~ s/^\s*//g;
718 $form->{donumber} =~ s/\s*$//g;
720 my $msg = ucfirst $form->{vc};
721 $form->isblank($form->{vc}, $locale->text($msg . " missing!"));
723 # $locale->text('Customer missing!');
724 # $locale->text('Vendor missing!');
726 remove_emptied_rows();
729 # if the name changed get new values
730 if (check_name($form->{vc})) {
735 $form->{id} = 0 if $form->{saveasnew};
739 if(!exists $form->{addition}) {
740 $form->{snumbers} = qq|donumber_| . $form->{donumber};
741 $form->{addition} = "SAVED";
744 # /saving the history
746 $form->{simple_save} = 1;
747 if (!$params{no_redirect} && !$form->{print_and_save}) {
748 delete @{$form}{ary_diff([keys %{ $form }], [qw(login id script type cursor_fokus)])};
752 $main::lxdebug->leave_sub();
756 $main::lxdebug->enter_sub();
760 my $form = $main::form;
761 my %myconfig = %main::myconfig;
762 my $locale = $main::locale;
766 if(!exists $form->{addition}) {
767 $form->{snumbers} = qq|donumber_| . $form->{donumber};
768 $form->{addition} = "DELETED";
771 # /saving the history
773 $form->info($locale->text('Delivery Order deleted!'));
777 $form->error($locale->text('Cannot delete delivery order!'));
779 $main::lxdebug->leave_sub();
783 $main::lxdebug->enter_sub();
785 my $form = $main::form;
786 my %myconfig = %main::myconfig;
787 my $locale = $main::locale;
790 $form->mtime_ischanged('delivery_orders');
792 $main::auth->assert($form->{type} eq 'purchase_delivery_order' ? 'vendor_invoice_edit' : 'invoice_edit');
794 $form->{convert_from_do_ids} = $form->{id};
795 $form->{deliverydate} = $form->{transdate};
796 $form->{transdate} = $form->{invdate} = $form->current_date(\%myconfig);
797 $form->{duedate} = $form->current_date(\%myconfig, $form->{invdate}, $form->{terms} * 1);
798 $form->{defaultcurrency} = $form->get_default_currency(\%myconfig);
802 delete @{$form}{qw(id closed delivered)};
804 my ($script, $buysell);
805 if ($form->{type} eq 'purchase_delivery_order') {
806 $form->{title} = $locale->text('Add Vendor Invoice');
807 $form->{script} = 'ir.pl';
812 $form->{title} = $locale->text('Add Sales Invoice');
813 $form->{script} = 'is.pl';
818 for my $i (1 .. $form->{rowcount}) {
820 unless ($form->{"ordnumber"}) {
821 if ($form->{discount}) { # Falls wir einen Lieferanten-/Kundenrabatt haben
822 # und rabattfähig sind, dann
823 unless ($form->{"not_discountable_$i"}) {
824 $form->{"discount_$i"} = $form->{discount}*100; # ... nehmen wir diesen Rabatt
828 map { $form->{"${_}_${i}"} = $form->parse_amount(\%myconfig, $form->{"${_}_${i}"}) if $form->{"${_}_${i}"} } qw(ship qty sellprice lastcost basefactor);
829 $form->{"donumber_$i"} = $form->{donumber};
830 $form->{"converted_from_delivery_order_items_id_$i"} = delete $form->{"delivery_order_items_id_$i"};
833 $form->{type} = "invoice";
836 $main::locale = Locale->new("$myconfig{countrycode}", "$script");
837 $locale = $main::locale;
839 require "bin/mozilla/$form->{script}";
841 my $currency = $form->{currency};
844 $form->{currency} = $currency;
845 $form->{exchangerate} = "";
846 $form->{forex} = $form->check_exchangerate(\%myconfig, $form->{currency}, $form->{invdate}, $buysell);
847 $form->{exchangerate} = $form->{forex} if ($form->{forex});
852 for my $i (1 .. $form->{rowcount}) {
853 $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"});
855 my ($dec) = ($form->{"sellprice_$i"} =~ /\.(\d+)/);
857 my $decimalplaces = ($dec > 2) ? $dec : 2;
859 # copy delivery date from reqdate for order -> invoice conversion
860 $form->{"deliverydate_$i"} = $form->{"reqdate_$i"}
861 unless $form->{"deliverydate_$i"};
864 $form->{"sellprice_$i"} =
865 $form->format_amount(\%myconfig, $form->{"sellprice_$i"},
868 $form->{"lastcost_$i"} =
869 $form->format_amount(\%myconfig, $form->{"lastcost_$i"},
872 (my $dec_qty) = ($form->{"qty_$i"} =~ /\.(\d+)/);
873 $dec_qty = length $dec_qty;
875 $form->format_amount(\%myconfig, $form->{"qty_$i"}, $dec_qty);
881 $main::lxdebug->leave_sub();
885 $main::lxdebug->enter_sub();
887 my $form = $main::form;
888 my %myconfig = %main::myconfig;
889 my $locale = $main::locale;
892 $main::auth->assert($form->{type} eq 'sales_delivery_order' ? 'invoice_edit' : 'vendor_invoice_edit');
894 my @do_ids = map { $form->{"trans_id_$_"} } grep { $form->{"multi_id_$_"} } (1..$form->{rowcount});
896 if (!scalar @do_ids) {
897 $form->show_generic_error($locale->text('You have not selected any delivery order.'), 'back_button' => 1);
900 map { delete $form->{$_} } grep { m/^(?:trans|multi)_id_\d+/ } keys %{ $form };
902 if (!DO->retrieve('vc' => $form->{vc}, 'ids' => \@do_ids)) {
903 $form->show_generic_error($form->{vc} eq 'customer' ?
904 $locale->text('You cannot create an invoice for delivery orders for different customers.') :
905 $locale->text('You cannot create an invoice for delivery orders from different vendors.'),
909 my $source_type = $form->{type};
910 $form->{convert_from_do_ids} = join ' ', @do_ids;
911 # bei der auswahl von mehreren Lieferscheinen fuer eine Rechnung, die einfach in donumber_array
912 # zwischenspeichern (DO.pm) und als ' '-separierte Liste wieder zurueckschreiben
913 # Hinweis: delete gibt den wert zurueck und loescht danach das element (nett und einfach)
914 # $shell: perldoc perlunc; /delete EXPR
915 $form->{donumber} = delete $form->{donumber_array};
916 $form->{ordnumber} = delete $form->{ordnumber_array};
917 $form->{cusordnumber} = delete $form->{cusordnumber_array};
918 $form->{deliverydate} = $form->{transdate};
919 $form->{transdate} = $form->current_date(\%myconfig);
920 $form->{duedate} = $form->current_date(\%myconfig, $form->{invdate}, $form->{terms} * 1);
921 $form->{type} = "invoice";
923 $form->{defaultcurrency} = $form->get_default_currency(\%myconfig);
925 my ($script, $buysell);
926 if ($source_type eq 'purchase_delivery_order') {
927 $form->{title} = $locale->text('Add Vendor Invoice');
928 $form->{script} = 'ir.pl';
933 $form->{title} = $locale->text('Add Sales Invoice');
934 $form->{script} = 'is.pl';
939 map { delete $form->{$_} } qw(id subject message cc bcc printed emailed queued);
941 # get vendor or customer discount
943 my $saved_form = save_form();
944 if ($form->{vc} eq 'vendor') {
945 IR->get_vendor(\%myconfig, \%$form);
946 $vc_discount = $form->{vendor_discount};
948 IS->get_customer(\%myconfig, \%$form);
949 $vc_discount = $form->{customer_discount};
951 restore_form($saved_form);
953 $form->{rowcount} = 0;
954 foreach my $ref (@{ $form->{form_details} }) {
956 $ref->{reqdate} ||= $ref->{dord_transdate}; # copy transdates into each invoice row
957 map { $form->{"${_}_$form->{rowcount}"} = $ref->{$_} } keys %{ $ref };
958 map { $form->{"${_}_$form->{rowcount}"} = $form->format_amount(\%myconfig, $ref->{$_}) } qw(qty sellprice lastcost);
959 $form->{"converted_from_delivery_order_items_id_$form->{rowcount}"} = delete $form->{"delivery_order_items_id_$form->{rowcount}"};
961 if ($vc_discount){ # falls wir einen Lieferanten/Kundenrabatt haben
962 # und keinen anderen discount wert an $i ...
963 $form->{"discount_$form->{rowcount}"} ||= $vc_discount; # ... nehmen wir diesen Rabatt
966 $form->{"discount_$form->{rowcount}"} = $form->{"discount_$form->{rowcount}"} * 100; #s.a. Bug 1151
967 # Anm.: Eine Änderung des discounts in der SL/DO.pm->retrieve (select (doi.discount * 100) as discount) ergibt in psql einen
968 # Wert von 10.0000001490116. Ferner ist der Rabatt in der Rechnung dann bei 1.0 (?). Deswegen lasse ich das hier. jb 10.10.09
970 $form->{"discount_$form->{rowcount}"} = $form->format_amount(\%myconfig, $form->{"discount_$form->{rowcount}"});
972 delete $form->{form_details};
974 $locale = Locale->new("$myconfig{countrycode}", "$script");
976 require "bin/mozilla/$form->{script}";
983 $main::lxdebug->leave_sub();
987 $main::lxdebug->enter_sub();
991 my $form = $main::form;
993 $form->{saveasnew} = 1;
995 $form->{delivered} = 0;
996 map { delete $form->{$_} } qw(printed emailed queued);
997 delete @{ $form }{ grep { m/^stock_(?:in|out)_\d+/ } keys %{ $form } };
998 $form->{"converted_from_delivery_order_items_id_$_"} = delete $form->{"delivery_order_items_id_$_"} for 1 .. $form->{"rowcount"};
999 # Let kivitendo assign a new order number if the user hasn't changed the
1000 # previous one. If it has been changed manually then use it as-is.
1001 $form->{donumber} =~ s/^\s*//g;
1002 $form->{donumber} =~ s/\s*$//g;
1003 if ($form->{saved_donumber} && ($form->{saved_donumber} eq $form->{donumber})) {
1004 delete($form->{donumber});
1009 $main::lxdebug->leave_sub();
1013 $main::lxdebug->enter_sub();
1017 $::form->mtime_ischanged('delivery_orders','mail');
1019 $::form->{print_and_save} = 1;
1021 my $saved_form = save_form();
1025 restore_form($saved_form, 0, qw(id ordnumber quonumber));
1029 $main::lxdebug->leave_sub();
1032 sub calculate_stock_in_out {
1033 $main::lxdebug->enter_sub();
1035 my $form = $main::form;
1039 if (!$form->{"id_${i}"}) {
1040 $main::lxdebug->leave_sub();
1044 my $all_units = AM->retrieve_all_units();
1046 my $in_out = $form->{type} =~ /^sales/ ? 'out' : 'in';
1047 my $sinfo = DO->unpack_stock_information('packed' => $form->{"stock_${in_out}_${i}"});
1049 my $do_qty = AM->sum_with_unit($::form->{"qty_$i"}, $::form->{"unit_$i"});
1050 my $sum = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $sinfo });
1051 my $matches = $do_qty == $sum;
1053 my $content = $form->format_amount_units('amount' => $sum * 1,
1054 'part_unit' => $form->{"partunit_$i"},
1055 'amount_unit' => $all_units->{$form->{"partunit_$i"}}->{base_unit},
1056 'conv_units' => 'convertible_not_smaller',
1058 $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="?">|;
1060 $main::lxdebug->leave_sub();
1065 sub get_basic_bin_wh_info {
1066 $main::lxdebug->enter_sub();
1068 my $stock_info = shift;
1070 my $form = $main::form;
1072 foreach my $sinfo (@{ $stock_info }) {
1073 next unless ($sinfo->{bin_id});
1075 my $bin_info = WH->get_basic_bin_info('id' => $sinfo->{bin_id});
1076 map { $sinfo->{"${_}_description"} = $sinfo->{"${_}description"} = $bin_info->{"${_}_description"} } qw(bin warehouse);
1079 $main::lxdebug->leave_sub();
1082 sub stock_in_out_form {
1083 $main::lxdebug->enter_sub();
1085 my $form = $main::form;
1087 if ($form->{in_out} eq 'out') {
1093 $main::lxdebug->leave_sub();
1096 sub redo_stock_info {
1097 $main::lxdebug->enter_sub();
1101 my $form = $main::form;
1103 my @non_empty = grep { $_->{qty} } @{ $params{stock_info} };
1105 if ($params{add_empty_row}) {
1107 'warehouse_id' => scalar(@non_empty) ? $non_empty[-1]->{warehouse_id} : undef,
1108 'bin_id' => scalar(@non_empty) ? $non_empty[-1]->{bin_id} : undef,
1112 @{ $params{stock_info} } = @non_empty;
1114 $main::lxdebug->leave_sub();
1117 sub update_stock_in {
1118 $main::lxdebug->enter_sub();
1120 my $form = $main::form;
1121 my %myconfig = %main::myconfig;
1123 my $stock_info = [];
1125 foreach my $i (1..$form->{rowcount}) {
1126 $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
1127 push @{ $stock_info }, { map { $_ => $form->{"${_}_${i}"} } qw(warehouse_id bin_id chargenumber
1128 bestbefore qty unit delivery_order_items_stock_id) };
1131 display_stock_in_form($stock_info);
1133 $main::lxdebug->leave_sub();
1137 $main::lxdebug->enter_sub();
1139 my $form = $main::form;
1141 my $stock_info = DO->unpack_stock_information('packed' => $form->{stock});
1143 display_stock_in_form($stock_info);
1145 $main::lxdebug->leave_sub();
1148 sub display_stock_in_form {
1149 $main::lxdebug->enter_sub();
1151 my $stock_info = shift;
1153 my $form = $main::form;
1154 my %myconfig = %main::myconfig;
1155 my $locale = $main::locale;
1157 $form->{title} = $locale->text('Stock');
1159 my $part_info = IC->get_basic_part_info('id' => $form->{parts_id});
1161 # Standardlagerplatz für Standard-Auslagern verwenden, falls keiner für die Ware explizit definiert wurde
1162 if ($::instance_conf->get_transfer_default_use_master_default_bin) {
1163 $part_info->{warehouse_id} ||= $::instance_conf->get_warehouse_id;
1164 $part_info->{bin_id} ||= $::instance_conf->get_bin_id;
1167 my $units = AM->retrieve_units(\%myconfig, $form);
1168 # der zweite Parameter von unit_select_data gibt den default-Namen (selected) vor
1169 my $units_data = AM->unit_select_data($units, $form->{do_unit}, undef, $part_info->{unit});
1171 $form->get_lists('warehouses' => { 'key' => 'WAREHOUSES',
1172 'bins' => 'BINS' });
1174 redo_stock_info('stock_info' => $stock_info, 'add_empty_row' => !$form->{delivered});
1176 get_basic_bin_wh_info($stock_info);
1178 $form->header(no_layout => 1);
1179 print $form->parse_html_template('do/stock_in_form', { 'UNITS' => $units_data,
1180 'STOCK_INFO' => $stock_info,
1181 'PART_INFO' => $part_info, });
1183 $main::lxdebug->leave_sub();
1186 sub _stock_in_out_set_qty_display {
1187 my $stock_info = shift;
1189 my $all_units = AM->retrieve_all_units();
1190 my $sum = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
1191 $form->{qty_display} = $form->format_amount_units(amount => $sum * 1,
1192 part_unit => $form->{partunit},
1193 amount_unit => $all_units->{ $form->{partunit} }->{base_unit},
1194 conv_units => 'convertible_not_smaller',
1199 $main::lxdebug->enter_sub();
1201 my $form = $main::form;
1202 my %myconfig = %main::myconfig;
1204 my $stock_info = [];
1206 foreach my $i (1..$form->{rowcount}) {
1207 $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
1209 next if ($form->{"qty_$i"} <= 0);
1211 push @{ $stock_info }, { map { $_ => $form->{"${_}_${i}"} } qw(delivery_order_items_stock_id warehouse_id bin_id chargenumber bestbefore qty unit) };
1214 $form->{stock} = YAML::Dump($stock_info);
1216 _stock_in_out_set_qty_display($stock_info);
1218 my $do_qty = AM->sum_with_unit($::form->parse_amount(\%::myconfig, $::form->{do_qty}), $::form->{do_unit});
1219 my $transfer_qty = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
1222 print $form->parse_html_template('do/set_stock_in_out', {
1223 qty_matches => $do_qty == $transfer_qty,
1226 $main::lxdebug->leave_sub();
1229 sub stock_out_form {
1230 $main::lxdebug->enter_sub();
1232 my $form = $main::form;
1233 my %myconfig = %main::myconfig;
1234 my $locale = $main::locale;
1236 $form->{title} = $locale->text('Release From Stock');
1238 my $part_info = IC->get_basic_part_info('id' => $form->{parts_id});
1240 my $units = AM->retrieve_units(\%myconfig, $form);
1241 my $units_data = AM->unit_select_data($units, undef, undef, $part_info->{unit});
1243 my @contents = DO->get_item_availability('parts_id' => $form->{parts_id});
1245 my $stock_info = DO->unpack_stock_information('packed' => $form->{stock});
1247 if (!$form->{delivered}) {
1248 foreach my $row (@contents) {
1249 $row->{available_qty} = $form->format_amount_units('amount' => $row->{qty} * 1,
1250 'part_unit' => $part_info->{unit},
1251 'conv_units' => 'convertible_not_smaller',
1254 foreach my $sinfo (@{ $stock_info }) {
1255 next if (($row->{bin_id} != $sinfo->{bin_id}) ||
1256 ($row->{warehouse_id} != $sinfo->{warehouse_id}) ||
1257 ($row->{chargenumber} ne $sinfo->{chargenumber}) ||
1258 ($row->{bestbefore} ne $sinfo->{bestbefore}));
1260 map { $row->{"stock_$_"} = $sinfo->{$_} } qw(qty unit error delivery_order_items_stock_id);
1265 get_basic_bin_wh_info($stock_info);
1267 foreach my $sinfo (@{ $stock_info }) {
1268 map { $sinfo->{"stock_$_"} = $sinfo->{$_} } qw(qty unit);
1272 $form->header(no_layout => 1);
1273 print $form->parse_html_template('do/stock_out_form', { 'UNITS' => $units_data,
1274 'WHCONTENTS' => $form->{delivered} ? $stock_info : \@contents,
1275 'PART_INFO' => $part_info, });
1277 $main::lxdebug->leave_sub();
1281 $main::lxdebug->enter_sub();
1283 my $form = $main::form;
1284 my %myconfig = %main::myconfig;
1285 my $locale = $main::locale;
1287 my $stock_info = [];
1289 foreach my $i (1 .. $form->{rowcount}) {
1290 $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
1292 next if ($form->{"qty_$i"} <= 0);
1294 push @{ $stock_info }, {
1295 'warehouse_id' => $form->{"warehouse_id_$i"},
1296 'bin_id' => $form->{"bin_id_$i"},
1297 'chargenumber' => $form->{"chargenumber_$i"},
1298 'bestbefore' => $form->{"bestbefore_$i"},
1299 'qty' => $form->{"qty_$i"},
1300 'unit' => $form->{"unit_$i"},
1302 'delivery_order_items_stock_id' => $form->{"delivery_order_items_stock_id_$i"},
1306 my @errors = DO->check_stock_availability('requests' => $stock_info,
1307 'parts_id' => $form->{parts_id});
1309 $form->{stock} = YAML::Dump($stock_info);
1312 $form->{ERRORS} = [];
1313 map { push @{ $form->{ERRORS} }, $locale->text('Error in row #1: The quantity you entered is bigger than the stocked quantity.', $_->{row}); } @errors;
1314 stock_in_out_form();
1317 _stock_in_out_set_qty_display($stock_info);
1319 my $do_qty = AM->sum_with_unit($::form->parse_amount(\%::myconfig, $::form->{do_qty}), $::form->{do_unit});
1320 my $transfer_qty = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
1323 print $form->parse_html_template('do/set_stock_in_out', {
1324 qty_matches => $do_qty == $transfer_qty,
1328 $main::lxdebug->leave_sub();
1332 $main::lxdebug->enter_sub();
1334 my $form = $main::form;
1335 my %myconfig = %main::myconfig;
1336 my $locale = $main::locale;
1338 if ($form->{id} && DO->is_marked_as_delivered(id => $form->{id})) {
1339 $form->show_generic_error($locale->text('The parts for this delivery order have already been transferred in.'), 'back_button' => 1);
1342 save(no_redirect => 1);
1344 my @part_ids = map { $form->{"id_${_}"} } grep { $form->{"id_${_}"} && $form->{"stock_in_${_}"} } (1 .. $form->{rowcount});
1348 my $units = AM->retrieve_units(\%myconfig, $form);
1349 my %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
1352 $form->{ERRORS} = [];
1354 foreach my $i (1 .. $form->{rowcount}) {
1355 next unless ($form->{"id_$i"} && $form->{"stock_in_$i"});
1357 my $row_sum_base_qty = 0;
1358 my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
1360 foreach my $request (@{ DO->unpack_stock_information('packed' => $form->{"stock_in_$i"}) }) {
1361 $request->{parts_id} = $form->{"id_$i"};
1362 $row_sum_base_qty += $request->{qty} * $units->{$request->{unit}}->{factor} / $base_unit_factor;
1364 $request->{project_id} = $form->{"project_id_$i"} || $form->{globalproject_id};
1366 push @all_requests, $request;
1369 next if (0 == $row_sum_base_qty);
1371 my $do_base_qty = $form->parse_amount(\%myconfig, $form->{"qty_$i"}) * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
1373 # if ($do_base_qty != $row_sum_base_qty) {
1374 # push @{ $form->{ERRORS} }, $locale->text('Error in position #1: You must either assign no stock at all or the full quantity of #2 #3.',
1375 # $i, $form->{"qty_$i"}, $form->{"unit_$i"});
1379 if (@{ $form->{ERRORS} }) {
1380 push @{ $form->{ERRORS} }, $locale->text('The delivery order has not been marked as delivered. The warehouse contents have not changed.');
1382 set_headings('edit');
1384 $main::lxdebug->leave_sub();
1390 DO->transfer_in_out('direction' => 'in',
1391 'requests' => \@all_requests);
1393 SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
1395 $form->{callback} = 'do.pl?action=edit&type=purchase_delivery_order&id=' . $form->escape($form->{id});
1398 $main::lxdebug->leave_sub();
1402 $main::lxdebug->enter_sub();
1404 my $form = $main::form;
1405 my %myconfig = %main::myconfig;
1406 my $locale = $main::locale;
1408 if ($form->{id} && DO->is_marked_as_delivered(id => $form->{id})) {
1409 $form->show_generic_error($locale->text('The parts for this delivery order have already been transferred out.'), 'back_button' => 1);
1412 save(no_redirect => 1);
1414 my @part_ids = map { $form->{"id_${_}"} } grep { $form->{"id_${_}"} && $form->{"stock_out_${_}"} } (1 .. $form->{rowcount});
1418 my $units = AM->retrieve_units(\%myconfig, $form);
1419 my %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
1422 $form->{ERRORS} = [];
1424 foreach my $i (1 .. $form->{rowcount}) {
1425 next unless ($form->{"id_$i"} && $form->{"stock_out_$i"});
1427 my $row_sum_base_qty = 0;
1428 my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
1430 foreach my $request (@{ DO->unpack_stock_information('packed' => $form->{"stock_out_$i"}) }) {
1431 $request->{parts_id} = $form->{"id_$i"};
1432 $request->{base_qty} = $request->{qty} * $units->{$request->{unit}}->{factor} / $base_unit_factor;
1433 $request->{project_id} = $form->{"project_id_$i"} ? $form->{"project_id_$i"} : $form->{globalproject_id};
1435 my $map_key = join '--', ($form->{"id_$i"}, @{$request}{qw(warehouse_id bin_id chargenumber bestbefore)});
1437 $request_map{$map_key} ||= $request;
1438 $request_map{$map_key}->{sum_base_qty} ||= 0;
1439 $request_map{$map_key}->{sum_base_qty} += $request->{base_qty};
1440 $row_sum_base_qty += $request->{base_qty};
1442 push @all_requests, $request;
1445 next if (0 == $row_sum_base_qty);
1447 my $do_base_qty = $form->{"qty_$i"} * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
1449 # if ($do_base_qty != $row_sum_base_qty) {
1450 # push @{ $form->{ERRORS} }, $locale->text('Error in position #1: You must either assign no transfer at all or the full quantity of #2 #3.',
1451 # $i, $form->{"qty_$i"}, $form->{"unit_$i"});
1456 my @bin_ids = map { $_->{bin_id} } values %request_map;
1457 my %bin_info_map = WH->get_basic_bin_info('id' => \@bin_ids);
1458 my @contents = DO->get_item_availability('parts_id' => \@part_ids);
1460 foreach my $inv (@contents) {
1461 my $map_key = join '--', @{$inv}{qw(parts_id warehouse_id bin_id chargenumber bestbefore)};
1463 next unless ($request_map{$map_key});
1465 my $request = $request_map{$map_key};
1466 $request->{ok} = $request->{sum_base_qty} <= $inv->{qty};
1469 foreach my $request (values %request_map) {
1470 next if ($request->{ok});
1472 my $pinfo = $part_info_map{$request->{parts_id}};
1473 my $binfo = $bin_info_map{$request->{bin_id}};
1475 if ($::instance_conf->get_show_bestbefore) {
1476 push @{ $form->{ERRORS} }, $locale->text("There is not enough available of '#1' at warehouse '#2', bin '#3', #4, #5, for the transfer of #6.",
1477 $pinfo->{description},
1478 $binfo->{warehouse_description},
1479 $binfo->{bin_description},
1480 $request->{chargenumber} ? $locale->text('chargenumber #1', $request->{chargenumber}) : $locale->text('no chargenumber'),
1481 $request->{bestbefore} ? $locale->text('bestbefore #1', $request->{bestbefore}) : $locale->text('no bestbefore'),
1482 $form->format_amount_units('amount' => $request->{sum_base_qty},
1483 'part_unit' => $pinfo->{unit},
1484 'conv_units' => 'convertible_not_smaller'));
1486 push @{ $form->{ERRORS} }, $locale->text("There is not enough available of '#1' at warehouse '#2', bin '#3', #4, for the transfer of #5.",
1487 $pinfo->{description},
1488 $binfo->{warehouse_description},
1489 $binfo->{bin_description},
1490 $request->{chargenumber} ? $locale->text('chargenumber #1', $request->{chargenumber}) : $locale->text('no chargenumber'),
1491 $form->format_amount_units('amount' => $request->{sum_base_qty},
1492 'part_unit' => $pinfo->{unit},
1493 'conv_units' => 'convertible_not_smaller'));
1498 if (@{ $form->{ERRORS} }) {
1499 push @{ $form->{ERRORS} }, $locale->text('The delivery order has not been marked as delivered. The warehouse contents have not changed.');
1501 set_headings('edit');
1503 $main::lxdebug->leave_sub();
1508 DO->transfer_in_out('direction' => 'out',
1509 'requests' => \@all_requests);
1511 SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
1513 $form->{callback} = 'do.pl?action=edit&type=sales_delivery_order&id=' . $form->escape($form->{id});
1516 $main::lxdebug->leave_sub();
1520 $main::lxdebug->enter_sub();
1522 my $form = $main::form;
1524 DO->close_orders('ids' => [ $form->{id} ]);
1526 $form->{closed} = 1;
1530 $main::lxdebug->leave_sub();
1535 call_sub($main::form->{yes_nextsub});
1539 call_sub($main::form->{no_nextsub});
1543 call_sub($main::form->{update_nextsub} || $main::form->{nextsub} || 'update_delivery_order');
1547 my $form = $main::form;
1548 my $locale = $main::locale;
1550 foreach my $action (qw(update ship_to print e_mail save transfer_out transfer_out_default sort
1551 transfer_in transfer_in_default mark_closed save_as_new invoice delete)) {
1552 if ($form->{"action_${action}"}) {
1558 $form->error($locale->text('No action defined.'));
1561 sub transfer_out_default {
1562 $main::lxdebug->enter_sub();
1564 my $form = $main::form;
1566 transfer_in_out_default('direction' => 'out');
1568 $main::lxdebug->leave_sub();
1571 sub transfer_in_default {
1572 $main::lxdebug->enter_sub();
1574 my $form = $main::form;
1576 transfer_in_out_default('direction' => 'in');
1578 $main::lxdebug->leave_sub();
1581 # Falls das Standardlagerverfahren aktiv ist, wird
1582 # geprüft, ob alle Standardlagerplätze für die Auslager-
1583 # artikel vorhanden sind UND ob die Warenmenge ausreicht zum
1584 # Auslagern. Falls nicht wird entsprechend eine Fehlermeldung
1585 # generiert. Offen Chargennummer / bestbefore wird nicht berücksichtigt
1586 sub transfer_in_out_default {
1587 $main::lxdebug->enter_sub();
1589 my $form = $main::form;
1590 my %myconfig = %main::myconfig;
1591 my $locale = $main::locale;
1594 my (%missing_default_bins, %qty_parts, @all_requests, %part_info_map, $default_warehouse_id, $default_bin_id);
1596 Common::check_params(\%params, qw(direction));
1598 # entsprechende defaults holen, falls standardlagerplatz verwendet werden soll
1599 if ($::instance_conf->get_transfer_default_use_master_default_bin) {
1600 $default_warehouse_id = $::instance_conf->get_warehouse_id;
1601 $default_bin_id = $::instance_conf->get_bin_id;
1605 my @part_ids = map { $form->{"id_${_}"} } (1 .. $form->{rowcount});
1607 my $units = AM->retrieve_units(\%myconfig, $form);
1608 %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
1609 foreach my $i (1 .. $form->{rowcount}) {
1610 next unless ($form->{"id_$i"});
1611 my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
1612 my $qty = $form->parse_amount(\%myconfig, $form->{"qty_$i"}) * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
1614 $form->show_generic_error($locale->text("Cannot transfer negative entries." ), 'back_button' => 1) if ($qty < 0);
1615 # if we do not want to transfer services and this part is a service, set qty to zero
1616 # ... and do not create a hash entry in %qty_parts below (will skip check for bins for the transfer == out case)
1617 # ... 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)
1619 $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});
1620 $qty_parts{$form->{"id_$i"}} += $qty;
1622 delete $qty_parts{$form->{"id_$i"}} unless $qty_parts{$form->{"id_$i"}};
1623 undef $form->{"stock_in_$i"};
1626 $part_info_map{$form->{"id_$i"}}{bin_id} ||= $default_bin_id;
1627 $part_info_map{$form->{"id_$i"}}{warehouse_id} ||= $default_warehouse_id;
1629 push @all_requests, ($qty == 0) ? { } : {
1630 'chargenumber' => '', #?? die müsste entsprechend geholt werden
1631 #'bestbefore' => undef, # TODO wird nicht berücksichtigt
1632 'bin_id' => $part_info_map{$form->{"id_$i"}}{bin_id},
1634 'parts_id' => $form->{"id_$i"},
1635 'comment' => $locale->text("Default transfer delivery order"),
1636 'unit' => $part_info_map{$form->{"id_$i"}}{unit},
1637 'warehouse_id' => $part_info_map{$form->{"id_$i"}}{warehouse_id},
1638 'oe_id' => $form->{id},
1639 'project_id' => $form->{"project_id_$i"} ? $form->{"project_id_$i"} : $form->{globalproject_id}
1643 # jetzt wird erst überprüft, ob die Stückzahl entsprechend stimmt.
1644 # check if bin (transfer in and transfer out and qty (transfer out) is correct
1645 foreach my $key (keys %qty_parts) {
1647 $missing_default_bins{$key}{missing_bin} = 1 unless ($part_info_map{$key}{bin_id});
1648 next unless ($part_info_map{$key}{bin_id}); # abbruch
1650 if ($params{direction} eq 'out') { # wird nur für ausgehende Mengen benötigt
1651 my ($max_qty, $error) = WH->get_max_qty_parts_bin(parts_id => $key, bin_id => $part_info_map{$key}{bin_id});
1653 # wir können nicht entscheiden, welche charge oder mhd (bestbefore) ausgewählt sein soll
1654 # deshalb rückmeldung nach oben geben, manuell auszulagern
1655 # TODO Bei nur einem Treffer mit Charge oder bestbefore wäre das noch möglich
1656 $missing_default_bins{$key}{chargenumber} = 1;
1658 if ($max_qty < $qty_parts{$key}){
1659 $missing_default_bins{$key}{missing_qty} = $max_qty - $qty_parts{$key};
1665 # Abfrage für Fehlerbehandlung (nur bei direction == out)
1666 if (scalar (keys %missing_default_bins)) {
1668 foreach my $fehler (keys %missing_default_bins) {
1670 my $ware = WH->get_part_description(parts_id => $fehler);
1671 if ($missing_default_bins{$fehler}{missing_bin}){
1672 $fehlertext .= "Kein Standardlagerplatz definiert bei $ware <br>";
1674 if ($missing_default_bins{$fehler}{missing_qty}) { # missing_qty
1675 $fehlertext .= "Es fehlen " . $missing_default_bins{$fehler}{missing_qty}*-1 .
1676 " von $ware auf dem Standard-Lagerplatz " . $part_info_map{$fehler}{bin} . " zum Auslagern<br>";
1678 if ($missing_default_bins{$fehler}{chargenumber}){
1679 $fehlertext .= "Die Ware hat eine Chargennummer oder eine Mindesthaltbarkeit definiert.
1680 Hier kann man nicht automatisch entscheiden.
1681 Bitte diesen Lieferschein manuell auslagern.
1684 # auslagern soll immer gehen, auch wenn nicht genügend auf lager ist.
1685 # der lagerplatz ist hier extra konfigurierbar, bspw. Lager-Korrektur mit
1686 # Lagerplatz Lagerplatz-Korrektur
1687 my $default_warehouse_id_ignore_onhand = $::instance_conf->get_warehouse_id_ignore_onhand;
1688 my $default_bin_id_ignore_onhand = $::instance_conf->get_bin_id_ignore_onhand;
1689 if ($::instance_conf->get_transfer_default_ignore_onhand && $default_bin_id_ignore_onhand) {
1690 # entsprechende defaults holen
1691 # falls chargenumber, bestbefore oder anzahl nicht stimmt, auf automatischen
1692 # lagerplatz wegbuchen!
1693 foreach (@all_requests) {
1694 if ($_->{parts_id} eq $fehler){
1695 $_->{bin_id} = $default_bin_id_ignore_onhand;
1696 $_->{warehouse_id} = $default_warehouse_id_ignore_onhand;
1700 #$main::lxdebug->message(0, 'Fehlertext: ' . $fehlertext);
1701 $form->show_generic_error($locale->text("Cannot transfer. <br> Reason:<br>#1", $fehlertext ), 'back_button' => 1);
1707 # hier der eigentliche fallunterschied für in oder out
1708 my $prefix = $params{direction} eq 'in' ? 'in' : 'out';
1710 # dieser array_ref ist für DO->save da:
1711 # einmal die all_requests in YAML verwandeln, damit delivery_order_items_stock
1712 # gefüllt werden kann.
1714 foreach (@all_requests){
1716 next unless scalar(%{ $_ });
1717 $form->{"stock_${prefix}_$i"} = YAML::Dump([$_]);
1720 save(no_redirect => 1); # Wir können auslagern, deshalb beleg speichern
1721 # und in delivery_order_items_stock speichern
1722 DO->transfer_in_out('direction' => $prefix,
1723 'requests' => \@all_requests);
1725 SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
1727 $form->{callback} = 'do.pl?action=edit&type=sales_delivery_order&id=' . $form->escape($form->{id}) if $params{direction} eq 'out';
1728 $form->{callback} = 'do.pl?action=edit&type=purchase_delivery_order&id=' . $form->escape($form->{id}) if $params{direction} eq 'in';
1734 $main::lxdebug->enter_sub();
1738 my $form = $main::form;
1741 croak ("Delivery Order needs to be saved") unless $form->{id};
1743 # hashify partnumbers, positions. key is delivery_order_items_id
1744 for my $i (1 .. ($form->{rowcount}) ) {
1745 $temp_hash{$form->{"delivery_order_items_id_$i"}} = { runningnumber => $form->{"runningnumber_$i"}, partnumber => $form->{"partnumber_$i"} };
1747 # naturally sort partnumbers and get a sorted array of doi_ids
1748 my @sorted_doi_ids = sort { Sort::Naturally::ncmp($temp_hash{$a}->{"partnumber"}, $temp_hash{$b}->{"partnumber"}) } keys %temp_hash;
1753 for (@sorted_doi_ids) {
1754 $form->{"runningnumber_$temp_hash{$_}->{runningnumber}"} = $new_number;
1757 $main::lxdebug->leave_sub();
1769 do.pl - Script for all calls to delivery order
1778 Sorts all position with Natural Sort. Can be activated in form_footer.html like this
1779 C<E<lt>input class="submit" type="submit" name="action_sort" id="sort_button" value="[% 'Sort and Save' | $T8 %]"E<gt>>
1785 Sort and Save can be implemented as an optional button if configuration ca be set by client config.
1786 Example coding for database scripts and templates in (git show af2f24b8), check also
1787 autogeneration for rose (scripts/rose_auto_create_model.pl --h)