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 if ($form->{ordnumber}) {
845 require SL::DB::Order;
846 if (my $order = SL::DB::Manager::Order->find_by(ordnumber => $form->{ordnumber})) {
848 $form->{orddate} = $order->transdate_as_date;
849 $form->{$_} = $order->$_ for qw(payment_id salesman_id taxzone_id quonumber);
853 $form->{currency} = $currency;
854 $form->{exchangerate} = "";
855 $form->{forex} = $form->check_exchangerate(\%myconfig, $form->{currency}, $form->{invdate}, $buysell);
856 $form->{exchangerate} = $form->{forex} if ($form->{forex});
861 for my $i (1 .. $form->{rowcount}) {
862 $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"});
864 my ($dec) = ($form->{"sellprice_$i"} =~ /\.(\d+)/);
866 my $decimalplaces = ($dec > 2) ? $dec : 2;
868 # copy delivery date from reqdate for order -> invoice conversion
869 $form->{"deliverydate_$i"} = $form->{"reqdate_$i"}
870 unless $form->{"deliverydate_$i"};
873 $form->{"sellprice_$i"} =
874 $form->format_amount(\%myconfig, $form->{"sellprice_$i"},
877 $form->{"lastcost_$i"} =
878 $form->format_amount(\%myconfig, $form->{"lastcost_$i"},
881 (my $dec_qty) = ($form->{"qty_$i"} =~ /\.(\d+)/);
882 $dec_qty = length $dec_qty;
884 $form->format_amount(\%myconfig, $form->{"qty_$i"}, $dec_qty);
890 $main::lxdebug->leave_sub();
894 $main::lxdebug->enter_sub();
896 my $form = $main::form;
897 my %myconfig = %main::myconfig;
898 my $locale = $main::locale;
901 $main::auth->assert($form->{type} eq 'sales_delivery_order' ? 'invoice_edit' : 'vendor_invoice_edit');
903 my @do_ids = map { $form->{"trans_id_$_"} } grep { $form->{"multi_id_$_"} } (1..$form->{rowcount});
905 if (!scalar @do_ids) {
906 $form->show_generic_error($locale->text('You have not selected any delivery order.'), 'back_button' => 1);
909 map { delete $form->{$_} } grep { m/^(?:trans|multi)_id_\d+/ } keys %{ $form };
911 if (!DO->retrieve('vc' => $form->{vc}, 'ids' => \@do_ids)) {
912 $form->show_generic_error($form->{vc} eq 'customer' ?
913 $locale->text('You cannot create an invoice for delivery orders for different customers.') :
914 $locale->text('You cannot create an invoice for delivery orders from different vendors.'),
918 my $source_type = $form->{type};
919 $form->{convert_from_do_ids} = join ' ', @do_ids;
920 # bei der auswahl von mehreren Lieferscheinen fuer eine Rechnung, die einfach in donumber_array
921 # zwischenspeichern (DO.pm) und als ' '-separierte Liste wieder zurueckschreiben
922 # Hinweis: delete gibt den wert zurueck und loescht danach das element (nett und einfach)
923 # $shell: perldoc perlunc; /delete EXPR
924 $form->{donumber} = delete $form->{donumber_array};
925 $form->{ordnumber} = delete $form->{ordnumber_array};
926 $form->{cusordnumber} = delete $form->{cusordnumber_array};
927 $form->{deliverydate} = $form->{transdate};
928 $form->{transdate} = $form->current_date(\%myconfig);
929 $form->{duedate} = $form->current_date(\%myconfig, $form->{invdate}, $form->{terms} * 1);
930 $form->{type} = "invoice";
932 $form->{defaultcurrency} = $form->get_default_currency(\%myconfig);
934 my ($script, $buysell);
935 if ($source_type eq 'purchase_delivery_order') {
936 $form->{title} = $locale->text('Add Vendor Invoice');
937 $form->{script} = 'ir.pl';
942 $form->{title} = $locale->text('Add Sales Invoice');
943 $form->{script} = 'is.pl';
948 map { delete $form->{$_} } qw(id subject message cc bcc printed emailed queued);
950 # get vendor or customer discount
952 my $saved_form = save_form();
953 if ($form->{vc} eq 'vendor') {
954 IR->get_vendor(\%myconfig, \%$form);
955 $vc_discount = $form->{vendor_discount};
957 IS->get_customer(\%myconfig, \%$form);
958 $vc_discount = $form->{customer_discount};
960 restore_form($saved_form);
962 $form->{rowcount} = 0;
963 foreach my $ref (@{ $form->{form_details} }) {
965 $ref->{reqdate} ||= $ref->{dord_transdate}; # copy transdates into each invoice row
966 map { $form->{"${_}_$form->{rowcount}"} = $ref->{$_} } keys %{ $ref };
967 map { $form->{"${_}_$form->{rowcount}"} = $form->format_amount(\%myconfig, $ref->{$_}) } qw(qty sellprice lastcost);
968 $form->{"converted_from_delivery_order_items_id_$form->{rowcount}"} = delete $form->{"delivery_order_items_id_$form->{rowcount}"};
970 if ($vc_discount){ # falls wir einen Lieferanten/Kundenrabatt haben
971 # und keinen anderen discount wert an $i ...
972 $form->{"discount_$form->{rowcount}"} ||= $vc_discount; # ... nehmen wir diesen Rabatt
975 $form->{"discount_$form->{rowcount}"} = $form->{"discount_$form->{rowcount}"} * 100; #s.a. Bug 1151
976 # Anm.: Eine Änderung des discounts in der SL/DO.pm->retrieve (select (doi.discount * 100) as discount) ergibt in psql einen
977 # Wert von 10.0000001490116. Ferner ist der Rabatt in der Rechnung dann bei 1.0 (?). Deswegen lasse ich das hier. jb 10.10.09
979 $form->{"discount_$form->{rowcount}"} = $form->format_amount(\%myconfig, $form->{"discount_$form->{rowcount}"});
981 delete $form->{form_details};
983 $locale = Locale->new("$myconfig{countrycode}", "$script");
985 require "bin/mozilla/$form->{script}";
992 $main::lxdebug->leave_sub();
996 $main::lxdebug->enter_sub();
1000 my $form = $main::form;
1002 $form->{saveasnew} = 1;
1003 $form->{closed} = 0;
1004 $form->{delivered} = 0;
1005 map { delete $form->{$_} } qw(printed emailed queued);
1006 delete @{ $form }{ grep { m/^stock_(?:in|out)_\d+/ } keys %{ $form } };
1007 $form->{"converted_from_delivery_order_items_id_$_"} = delete $form->{"delivery_order_items_id_$_"} for 1 .. $form->{"rowcount"};
1008 # Let kivitendo assign a new order number if the user hasn't changed the
1009 # previous one. If it has been changed manually then use it as-is.
1010 $form->{donumber} =~ s/^\s*//g;
1011 $form->{donumber} =~ s/\s*$//g;
1012 if ($form->{saved_donumber} && ($form->{saved_donumber} eq $form->{donumber})) {
1013 delete($form->{donumber});
1018 $main::lxdebug->leave_sub();
1022 $main::lxdebug->enter_sub();
1026 $::form->mtime_ischanged('delivery_orders','mail');
1028 $::form->{print_and_save} = 1;
1030 my $saved_form = save_form();
1034 restore_form($saved_form, 0, qw(id ordnumber quonumber));
1038 $main::lxdebug->leave_sub();
1041 sub calculate_stock_in_out {
1042 $main::lxdebug->enter_sub();
1044 my $form = $main::form;
1048 if (!$form->{"id_${i}"}) {
1049 $main::lxdebug->leave_sub();
1053 my $all_units = AM->retrieve_all_units();
1055 my $in_out = $form->{type} =~ /^sales/ ? 'out' : 'in';
1056 my $sinfo = DO->unpack_stock_information('packed' => $form->{"stock_${in_out}_${i}"});
1058 my $do_qty = AM->sum_with_unit($::form->{"qty_$i"}, $::form->{"unit_$i"});
1059 my $sum = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $sinfo });
1060 my $matches = $do_qty == $sum;
1062 my $content = $form->format_amount_units('amount' => $sum * 1,
1063 'part_unit' => $form->{"partunit_$i"},
1064 'amount_unit' => $all_units->{$form->{"partunit_$i"}}->{base_unit},
1065 'conv_units' => 'convertible_not_smaller',
1067 $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="?">|;
1069 $main::lxdebug->leave_sub();
1074 sub get_basic_bin_wh_info {
1075 $main::lxdebug->enter_sub();
1077 my $stock_info = shift;
1079 my $form = $main::form;
1081 foreach my $sinfo (@{ $stock_info }) {
1082 next unless ($sinfo->{bin_id});
1084 my $bin_info = WH->get_basic_bin_info('id' => $sinfo->{bin_id});
1085 map { $sinfo->{"${_}_description"} = $sinfo->{"${_}description"} = $bin_info->{"${_}_description"} } qw(bin warehouse);
1088 $main::lxdebug->leave_sub();
1091 sub stock_in_out_form {
1092 $main::lxdebug->enter_sub();
1094 my $form = $main::form;
1096 if ($form->{in_out} eq 'out') {
1102 $main::lxdebug->leave_sub();
1105 sub redo_stock_info {
1106 $main::lxdebug->enter_sub();
1110 my $form = $main::form;
1112 my @non_empty = grep { $_->{qty} } @{ $params{stock_info} };
1114 if ($params{add_empty_row}) {
1116 'warehouse_id' => scalar(@non_empty) ? $non_empty[-1]->{warehouse_id} : undef,
1117 'bin_id' => scalar(@non_empty) ? $non_empty[-1]->{bin_id} : undef,
1121 @{ $params{stock_info} } = @non_empty;
1123 $main::lxdebug->leave_sub();
1126 sub update_stock_in {
1127 $main::lxdebug->enter_sub();
1129 my $form = $main::form;
1130 my %myconfig = %main::myconfig;
1132 my $stock_info = [];
1134 foreach my $i (1..$form->{rowcount}) {
1135 $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
1136 push @{ $stock_info }, { map { $_ => $form->{"${_}_${i}"} } qw(warehouse_id bin_id chargenumber
1137 bestbefore qty unit delivery_order_items_stock_id) };
1140 display_stock_in_form($stock_info);
1142 $main::lxdebug->leave_sub();
1146 $main::lxdebug->enter_sub();
1148 my $form = $main::form;
1150 my $stock_info = DO->unpack_stock_information('packed' => $form->{stock});
1152 display_stock_in_form($stock_info);
1154 $main::lxdebug->leave_sub();
1157 sub display_stock_in_form {
1158 $main::lxdebug->enter_sub();
1160 my $stock_info = shift;
1162 my $form = $main::form;
1163 my %myconfig = %main::myconfig;
1164 my $locale = $main::locale;
1166 $form->{title} = $locale->text('Stock');
1168 my $part_info = IC->get_basic_part_info('id' => $form->{parts_id});
1170 # Standardlagerplatz für Standard-Auslagern verwenden, falls keiner für die Ware explizit definiert wurde
1171 if ($::instance_conf->get_transfer_default_use_master_default_bin) {
1172 $part_info->{warehouse_id} ||= $::instance_conf->get_warehouse_id;
1173 $part_info->{bin_id} ||= $::instance_conf->get_bin_id;
1176 my $units = AM->retrieve_units(\%myconfig, $form);
1177 # der zweite Parameter von unit_select_data gibt den default-Namen (selected) vor
1178 my $units_data = AM->unit_select_data($units, $form->{do_unit}, undef, $part_info->{unit});
1180 $form->get_lists('warehouses' => { 'key' => 'WAREHOUSES',
1181 'bins' => 'BINS' });
1183 redo_stock_info('stock_info' => $stock_info, 'add_empty_row' => !$form->{delivered});
1185 get_basic_bin_wh_info($stock_info);
1187 $form->header(no_layout => 1);
1188 print $form->parse_html_template('do/stock_in_form', { 'UNITS' => $units_data,
1189 'STOCK_INFO' => $stock_info,
1190 'PART_INFO' => $part_info, });
1192 $main::lxdebug->leave_sub();
1195 sub _stock_in_out_set_qty_display {
1196 my $stock_info = shift;
1198 my $all_units = AM->retrieve_all_units();
1199 my $sum = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
1200 $form->{qty_display} = $form->format_amount_units(amount => $sum * 1,
1201 part_unit => $form->{partunit},
1202 amount_unit => $all_units->{ $form->{partunit} }->{base_unit},
1203 conv_units => 'convertible_not_smaller',
1208 $main::lxdebug->enter_sub();
1210 my $form = $main::form;
1211 my %myconfig = %main::myconfig;
1213 my $stock_info = [];
1215 foreach my $i (1..$form->{rowcount}) {
1216 $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
1218 next if ($form->{"qty_$i"} <= 0);
1220 push @{ $stock_info }, { map { $_ => $form->{"${_}_${i}"} } qw(delivery_order_items_stock_id warehouse_id bin_id chargenumber bestbefore qty unit) };
1223 $form->{stock} = YAML::Dump($stock_info);
1225 _stock_in_out_set_qty_display($stock_info);
1227 my $do_qty = AM->sum_with_unit($::form->parse_amount(\%::myconfig, $::form->{do_qty}), $::form->{do_unit});
1228 my $transfer_qty = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
1231 print $form->parse_html_template('do/set_stock_in_out', {
1232 qty_matches => $do_qty == $transfer_qty,
1235 $main::lxdebug->leave_sub();
1238 sub stock_out_form {
1239 $main::lxdebug->enter_sub();
1241 my $form = $main::form;
1242 my %myconfig = %main::myconfig;
1243 my $locale = $main::locale;
1245 $form->{title} = $locale->text('Release From Stock');
1247 my $part_info = IC->get_basic_part_info('id' => $form->{parts_id});
1249 my $units = AM->retrieve_units(\%myconfig, $form);
1250 my $units_data = AM->unit_select_data($units, undef, undef, $part_info->{unit});
1252 my @contents = DO->get_item_availability('parts_id' => $form->{parts_id});
1254 my $stock_info = DO->unpack_stock_information('packed' => $form->{stock});
1256 if (!$form->{delivered}) {
1257 foreach my $row (@contents) {
1258 $row->{available_qty} = $form->format_amount_units('amount' => $row->{qty} * 1,
1259 'part_unit' => $part_info->{unit},
1260 'conv_units' => 'convertible_not_smaller',
1263 foreach my $sinfo (@{ $stock_info }) {
1264 next if (($row->{bin_id} != $sinfo->{bin_id}) ||
1265 ($row->{warehouse_id} != $sinfo->{warehouse_id}) ||
1266 ($row->{chargenumber} ne $sinfo->{chargenumber}) ||
1267 ($row->{bestbefore} ne $sinfo->{bestbefore}));
1269 map { $row->{"stock_$_"} = $sinfo->{$_} } qw(qty unit error delivery_order_items_stock_id);
1274 get_basic_bin_wh_info($stock_info);
1276 foreach my $sinfo (@{ $stock_info }) {
1277 map { $sinfo->{"stock_$_"} = $sinfo->{$_} } qw(qty unit);
1281 $form->header(no_layout => 1);
1282 print $form->parse_html_template('do/stock_out_form', { 'UNITS' => $units_data,
1283 'WHCONTENTS' => $form->{delivered} ? $stock_info : \@contents,
1284 'PART_INFO' => $part_info, });
1286 $main::lxdebug->leave_sub();
1290 $main::lxdebug->enter_sub();
1292 my $form = $main::form;
1293 my %myconfig = %main::myconfig;
1294 my $locale = $main::locale;
1296 my $stock_info = [];
1298 foreach my $i (1 .. $form->{rowcount}) {
1299 $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
1301 next if ($form->{"qty_$i"} <= 0);
1303 push @{ $stock_info }, {
1304 'warehouse_id' => $form->{"warehouse_id_$i"},
1305 'bin_id' => $form->{"bin_id_$i"},
1306 'chargenumber' => $form->{"chargenumber_$i"},
1307 'bestbefore' => $form->{"bestbefore_$i"},
1308 'qty' => $form->{"qty_$i"},
1309 'unit' => $form->{"unit_$i"},
1311 'delivery_order_items_stock_id' => $form->{"delivery_order_items_stock_id_$i"},
1315 my @errors = DO->check_stock_availability('requests' => $stock_info,
1316 'parts_id' => $form->{parts_id});
1318 $form->{stock} = YAML::Dump($stock_info);
1321 $form->{ERRORS} = [];
1322 map { push @{ $form->{ERRORS} }, $locale->text('Error in row #1: The quantity you entered is bigger than the stocked quantity.', $_->{row}); } @errors;
1323 stock_in_out_form();
1326 _stock_in_out_set_qty_display($stock_info);
1328 my $do_qty = AM->sum_with_unit($::form->parse_amount(\%::myconfig, $::form->{do_qty}), $::form->{do_unit});
1329 my $transfer_qty = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
1332 print $form->parse_html_template('do/set_stock_in_out', {
1333 qty_matches => $do_qty == $transfer_qty,
1337 $main::lxdebug->leave_sub();
1341 $main::lxdebug->enter_sub();
1343 my $form = $main::form;
1344 my %myconfig = %main::myconfig;
1345 my $locale = $main::locale;
1347 if ($form->{id} && DO->is_marked_as_delivered(id => $form->{id})) {
1348 $form->show_generic_error($locale->text('The parts for this delivery order have already been transferred in.'), 'back_button' => 1);
1351 save(no_redirect => 1);
1353 my @part_ids = map { $form->{"id_${_}"} } grep { $form->{"id_${_}"} && $form->{"stock_in_${_}"} } (1 .. $form->{rowcount});
1357 my $units = AM->retrieve_units(\%myconfig, $form);
1358 my %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
1361 $form->{ERRORS} = [];
1363 foreach my $i (1 .. $form->{rowcount}) {
1364 next unless ($form->{"id_$i"} && $form->{"stock_in_$i"});
1366 my $row_sum_base_qty = 0;
1367 my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
1369 foreach my $request (@{ DO->unpack_stock_information('packed' => $form->{"stock_in_$i"}) }) {
1370 $request->{parts_id} = $form->{"id_$i"};
1371 $row_sum_base_qty += $request->{qty} * $units->{$request->{unit}}->{factor} / $base_unit_factor;
1373 $request->{project_id} = $form->{"project_id_$i"} || $form->{globalproject_id};
1375 push @all_requests, $request;
1378 next if (0 == $row_sum_base_qty);
1380 my $do_base_qty = $form->parse_amount(\%myconfig, $form->{"qty_$i"}) * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
1382 # if ($do_base_qty != $row_sum_base_qty) {
1383 # push @{ $form->{ERRORS} }, $locale->text('Error in position #1: You must either assign no stock at all or the full quantity of #2 #3.',
1384 # $i, $form->{"qty_$i"}, $form->{"unit_$i"});
1388 if (@{ $form->{ERRORS} }) {
1389 push @{ $form->{ERRORS} }, $locale->text('The delivery order has not been marked as delivered. The warehouse contents have not changed.');
1391 set_headings('edit');
1393 $main::lxdebug->leave_sub();
1399 DO->transfer_in_out('direction' => 'in',
1400 'requests' => \@all_requests);
1402 SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
1404 $form->{callback} = 'do.pl?action=edit&type=purchase_delivery_order&id=' . $form->escape($form->{id});
1407 $main::lxdebug->leave_sub();
1411 $main::lxdebug->enter_sub();
1413 my $form = $main::form;
1414 my %myconfig = %main::myconfig;
1415 my $locale = $main::locale;
1417 if ($form->{id} && DO->is_marked_as_delivered(id => $form->{id})) {
1418 $form->show_generic_error($locale->text('The parts for this delivery order have already been transferred out.'), 'back_button' => 1);
1421 save(no_redirect => 1);
1423 my @part_ids = map { $form->{"id_${_}"} } grep { $form->{"id_${_}"} && $form->{"stock_out_${_}"} } (1 .. $form->{rowcount});
1427 my $units = AM->retrieve_units(\%myconfig, $form);
1428 my %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
1431 $form->{ERRORS} = [];
1433 foreach my $i (1 .. $form->{rowcount}) {
1434 next unless ($form->{"id_$i"} && $form->{"stock_out_$i"});
1436 my $row_sum_base_qty = 0;
1437 my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
1439 foreach my $request (@{ DO->unpack_stock_information('packed' => $form->{"stock_out_$i"}) }) {
1440 $request->{parts_id} = $form->{"id_$i"};
1441 $request->{base_qty} = $request->{qty} * $units->{$request->{unit}}->{factor} / $base_unit_factor;
1442 $request->{project_id} = $form->{"project_id_$i"} ? $form->{"project_id_$i"} : $form->{globalproject_id};
1444 my $map_key = join '--', ($form->{"id_$i"}, @{$request}{qw(warehouse_id bin_id chargenumber bestbefore)});
1446 $request_map{$map_key} ||= $request;
1447 $request_map{$map_key}->{sum_base_qty} ||= 0;
1448 $request_map{$map_key}->{sum_base_qty} += $request->{base_qty};
1449 $row_sum_base_qty += $request->{base_qty};
1451 push @all_requests, $request;
1454 next if (0 == $row_sum_base_qty);
1456 my $do_base_qty = $form->{"qty_$i"} * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
1458 # if ($do_base_qty != $row_sum_base_qty) {
1459 # push @{ $form->{ERRORS} }, $locale->text('Error in position #1: You must either assign no transfer at all or the full quantity of #2 #3.',
1460 # $i, $form->{"qty_$i"}, $form->{"unit_$i"});
1465 my @bin_ids = map { $_->{bin_id} } values %request_map;
1466 my %bin_info_map = WH->get_basic_bin_info('id' => \@bin_ids);
1467 my @contents = DO->get_item_availability('parts_id' => \@part_ids);
1469 foreach my $inv (@contents) {
1470 my $map_key = join '--', @{$inv}{qw(parts_id warehouse_id bin_id chargenumber bestbefore)};
1472 next unless ($request_map{$map_key});
1474 my $request = $request_map{$map_key};
1475 $request->{ok} = $request->{sum_base_qty} <= $inv->{qty};
1478 foreach my $request (values %request_map) {
1479 next if ($request->{ok});
1481 my $pinfo = $part_info_map{$request->{parts_id}};
1482 my $binfo = $bin_info_map{$request->{bin_id}};
1484 if ($::instance_conf->get_show_bestbefore) {
1485 push @{ $form->{ERRORS} }, $locale->text("There is not enough available of '#1' at warehouse '#2', bin '#3', #4, #5, for the transfer of #6.",
1486 $pinfo->{description},
1487 $binfo->{warehouse_description},
1488 $binfo->{bin_description},
1489 $request->{chargenumber} ? $locale->text('chargenumber #1', $request->{chargenumber}) : $locale->text('no chargenumber'),
1490 $request->{bestbefore} ? $locale->text('bestbefore #1', $request->{bestbefore}) : $locale->text('no bestbefore'),
1491 $form->format_amount_units('amount' => $request->{sum_base_qty},
1492 'part_unit' => $pinfo->{unit},
1493 'conv_units' => 'convertible_not_smaller'));
1495 push @{ $form->{ERRORS} }, $locale->text("There is not enough available of '#1' at warehouse '#2', bin '#3', #4, for the transfer of #5.",
1496 $pinfo->{description},
1497 $binfo->{warehouse_description},
1498 $binfo->{bin_description},
1499 $request->{chargenumber} ? $locale->text('chargenumber #1', $request->{chargenumber}) : $locale->text('no chargenumber'),
1500 $form->format_amount_units('amount' => $request->{sum_base_qty},
1501 'part_unit' => $pinfo->{unit},
1502 'conv_units' => 'convertible_not_smaller'));
1507 if (@{ $form->{ERRORS} }) {
1508 push @{ $form->{ERRORS} }, $locale->text('The delivery order has not been marked as delivered. The warehouse contents have not changed.');
1510 set_headings('edit');
1512 $main::lxdebug->leave_sub();
1517 DO->transfer_in_out('direction' => 'out',
1518 'requests' => \@all_requests);
1520 SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
1522 $form->{callback} = 'do.pl?action=edit&type=sales_delivery_order&id=' . $form->escape($form->{id});
1525 $main::lxdebug->leave_sub();
1529 $main::lxdebug->enter_sub();
1531 my $form = $main::form;
1533 DO->close_orders('ids' => [ $form->{id} ]);
1535 $form->{closed} = 1;
1539 $main::lxdebug->leave_sub();
1544 call_sub($main::form->{yes_nextsub});
1548 call_sub($main::form->{no_nextsub});
1552 call_sub($main::form->{update_nextsub} || $main::form->{nextsub} || 'update_delivery_order');
1556 my $form = $main::form;
1557 my $locale = $main::locale;
1559 foreach my $action (qw(update ship_to print e_mail save transfer_out transfer_out_default sort
1560 transfer_in transfer_in_default mark_closed save_as_new invoice delete)) {
1561 if ($form->{"action_${action}"}) {
1567 $form->error($locale->text('No action defined.'));
1570 sub transfer_out_default {
1571 $main::lxdebug->enter_sub();
1573 my $form = $main::form;
1575 transfer_in_out_default('direction' => 'out');
1577 $main::lxdebug->leave_sub();
1580 sub transfer_in_default {
1581 $main::lxdebug->enter_sub();
1583 my $form = $main::form;
1585 transfer_in_out_default('direction' => 'in');
1587 $main::lxdebug->leave_sub();
1590 # Falls das Standardlagerverfahren aktiv ist, wird
1591 # geprüft, ob alle Standardlagerplätze für die Auslager-
1592 # artikel vorhanden sind UND ob die Warenmenge ausreicht zum
1593 # Auslagern. Falls nicht wird entsprechend eine Fehlermeldung
1594 # generiert. Offen Chargennummer / bestbefore wird nicht berücksichtigt
1595 sub transfer_in_out_default {
1596 $main::lxdebug->enter_sub();
1598 my $form = $main::form;
1599 my %myconfig = %main::myconfig;
1600 my $locale = $main::locale;
1603 my (%missing_default_bins, %qty_parts, @all_requests, %part_info_map, $default_warehouse_id, $default_bin_id);
1605 Common::check_params(\%params, qw(direction));
1607 # entsprechende defaults holen, falls standardlagerplatz verwendet werden soll
1608 if ($::instance_conf->get_transfer_default_use_master_default_bin) {
1609 $default_warehouse_id = $::instance_conf->get_warehouse_id;
1610 $default_bin_id = $::instance_conf->get_bin_id;
1614 my @part_ids = map { $form->{"id_${_}"} } (1 .. $form->{rowcount});
1616 my $units = AM->retrieve_units(\%myconfig, $form);
1617 %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
1618 foreach my $i (1 .. $form->{rowcount}) {
1619 next unless ($form->{"id_$i"});
1620 my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
1621 my $qty = $form->parse_amount(\%myconfig, $form->{"qty_$i"}) * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
1623 $form->show_generic_error($locale->text("Cannot transfer negative entries." ), 'back_button' => 1) if ($qty < 0);
1624 # if we do not want to transfer services and this part is a service, set qty to zero
1625 # ... and do not create a hash entry in %qty_parts below (will skip check for bins for the transfer == out case)
1626 # ... 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)
1628 $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});
1629 $qty_parts{$form->{"id_$i"}} += $qty;
1631 delete $qty_parts{$form->{"id_$i"}} unless $qty_parts{$form->{"id_$i"}};
1632 undef $form->{"stock_in_$i"};
1635 $part_info_map{$form->{"id_$i"}}{bin_id} ||= $default_bin_id;
1636 $part_info_map{$form->{"id_$i"}}{warehouse_id} ||= $default_warehouse_id;
1638 push @all_requests, ($qty == 0) ? { } : {
1639 'chargenumber' => '', #?? die müsste entsprechend geholt werden
1640 #'bestbefore' => undef, # TODO wird nicht berücksichtigt
1641 'bin_id' => $part_info_map{$form->{"id_$i"}}{bin_id},
1643 'parts_id' => $form->{"id_$i"},
1644 'comment' => $locale->text("Default transfer delivery order"),
1645 'unit' => $part_info_map{$form->{"id_$i"}}{unit},
1646 'warehouse_id' => $part_info_map{$form->{"id_$i"}}{warehouse_id},
1647 'oe_id' => $form->{id},
1648 'project_id' => $form->{"project_id_$i"} ? $form->{"project_id_$i"} : $form->{globalproject_id}
1652 # jetzt wird erst überprüft, ob die Stückzahl entsprechend stimmt.
1653 # check if bin (transfer in and transfer out and qty (transfer out) is correct
1654 foreach my $key (keys %qty_parts) {
1656 $missing_default_bins{$key}{missing_bin} = 1 unless ($part_info_map{$key}{bin_id});
1657 next unless ($part_info_map{$key}{bin_id}); # abbruch
1659 if ($params{direction} eq 'out') { # wird nur für ausgehende Mengen benötigt
1660 my ($max_qty, $error) = WH->get_max_qty_parts_bin(parts_id => $key, bin_id => $part_info_map{$key}{bin_id});
1662 # wir können nicht entscheiden, welche charge oder mhd (bestbefore) ausgewählt sein soll
1663 # deshalb rückmeldung nach oben geben, manuell auszulagern
1664 # TODO Bei nur einem Treffer mit Charge oder bestbefore wäre das noch möglich
1665 $missing_default_bins{$key}{chargenumber} = 1;
1667 if ($max_qty < $qty_parts{$key}){
1668 $missing_default_bins{$key}{missing_qty} = $max_qty - $qty_parts{$key};
1674 # Abfrage für Fehlerbehandlung (nur bei direction == out)
1675 if (scalar (keys %missing_default_bins)) {
1677 foreach my $fehler (keys %missing_default_bins) {
1679 my $ware = WH->get_part_description(parts_id => $fehler);
1680 if ($missing_default_bins{$fehler}{missing_bin}){
1681 $fehlertext .= "Kein Standardlagerplatz definiert bei $ware <br>";
1683 if ($missing_default_bins{$fehler}{missing_qty}) { # missing_qty
1684 $fehlertext .= "Es fehlen " . $missing_default_bins{$fehler}{missing_qty}*-1 .
1685 " von $ware auf dem Standard-Lagerplatz " . $part_info_map{$fehler}{bin} . " zum Auslagern<br>";
1687 if ($missing_default_bins{$fehler}{chargenumber}){
1688 $fehlertext .= "Die Ware hat eine Chargennummer oder eine Mindesthaltbarkeit definiert.
1689 Hier kann man nicht automatisch entscheiden.
1690 Bitte diesen Lieferschein manuell auslagern.
1693 # auslagern soll immer gehen, auch wenn nicht genügend auf lager ist.
1694 # der lagerplatz ist hier extra konfigurierbar, bspw. Lager-Korrektur mit
1695 # Lagerplatz Lagerplatz-Korrektur
1696 my $default_warehouse_id_ignore_onhand = $::instance_conf->get_warehouse_id_ignore_onhand;
1697 my $default_bin_id_ignore_onhand = $::instance_conf->get_bin_id_ignore_onhand;
1698 if ($::instance_conf->get_transfer_default_ignore_onhand && $default_bin_id_ignore_onhand) {
1699 # entsprechende defaults holen
1700 # falls chargenumber, bestbefore oder anzahl nicht stimmt, auf automatischen
1701 # lagerplatz wegbuchen!
1702 foreach (@all_requests) {
1703 if ($_->{parts_id} eq $fehler){
1704 $_->{bin_id} = $default_bin_id_ignore_onhand;
1705 $_->{warehouse_id} = $default_warehouse_id_ignore_onhand;
1709 #$main::lxdebug->message(0, 'Fehlertext: ' . $fehlertext);
1710 $form->show_generic_error($locale->text("Cannot transfer. <br> Reason:<br>#1", $fehlertext ), 'back_button' => 1);
1716 # hier der eigentliche fallunterschied für in oder out
1717 my $prefix = $params{direction} eq 'in' ? 'in' : 'out';
1719 # dieser array_ref ist für DO->save da:
1720 # einmal die all_requests in YAML verwandeln, damit delivery_order_items_stock
1721 # gefüllt werden kann.
1723 foreach (@all_requests){
1725 next unless scalar(%{ $_ });
1726 $form->{"stock_${prefix}_$i"} = YAML::Dump([$_]);
1729 save(no_redirect => 1); # Wir können auslagern, deshalb beleg speichern
1730 # und in delivery_order_items_stock speichern
1731 DO->transfer_in_out('direction' => $prefix,
1732 'requests' => \@all_requests);
1734 SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
1736 $form->{callback} = 'do.pl?action=edit&type=sales_delivery_order&id=' . $form->escape($form->{id}) if $params{direction} eq 'out';
1737 $form->{callback} = 'do.pl?action=edit&type=purchase_delivery_order&id=' . $form->escape($form->{id}) if $params{direction} eq 'in';
1743 $main::lxdebug->enter_sub();
1747 my $form = $main::form;
1750 croak ("Delivery Order needs to be saved") unless $form->{id};
1752 # hashify partnumbers, positions. key is delivery_order_items_id
1753 for my $i (1 .. ($form->{rowcount}) ) {
1754 $temp_hash{$form->{"delivery_order_items_id_$i"}} = { runningnumber => $form->{"runningnumber_$i"}, partnumber => $form->{"partnumber_$i"} };
1756 # naturally sort partnumbers and get a sorted array of doi_ids
1757 my @sorted_doi_ids = sort { Sort::Naturally::ncmp($temp_hash{$a}->{"partnumber"}, $temp_hash{$b}->{"partnumber"}) } keys %temp_hash;
1762 for (@sorted_doi_ids) {
1763 $form->{"runningnumber_$temp_hash{$_}->{runningnumber}"} = $new_number;
1766 $main::lxdebug->leave_sub();
1778 do.pl - Script for all calls to delivery order
1787 Sorts all position with Natural Sort. Can be activated in form_footer.html like this
1788 C<E<lt>input class="submit" type="submit" name="action_sort" id="sort_button" value="[% 'Sort and Save' | $T8 %]"E<gt>>
1794 Sort and Save can be implemented as an optional button if configuration ca be set by client config.
1795 Example coding for database scripts and templates in (git show af2f24b8), check also
1796 autogeneration for rose (scripts/rose_auto_create_model.pl --h)