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->{defaultcurrency} = $form->get_default_currency(\%myconfig);
713 $form->isblank("transdate", $locale->text('Delivery Order Date missing!'));
715 $form->{donumber} =~ s/^\s*//g;
716 $form->{donumber} =~ s/\s*$//g;
718 my $msg = ucfirst $form->{vc};
719 $form->isblank($form->{vc}, $locale->text($msg . " missing!"));
721 # $locale->text('Customer missing!');
722 # $locale->text('Vendor missing!');
724 remove_emptied_rows();
727 # if the name changed get new values
728 if (check_name($form->{vc})) {
733 $form->{id} = 0 if $form->{saveasnew};
737 if(!exists $form->{addition}) {
738 $form->{snumbers} = qq|donumber_| . $form->{donumber};
739 $form->{addition} = "SAVED";
742 # /saving the history
744 $form->{simple_save} = 1;
745 if (!$params{no_redirect} && !$form->{print_and_save}) {
746 delete @{$form}{ary_diff([keys %{ $form }], [qw(login id script type cursor_fokus)])};
750 $main::lxdebug->leave_sub();
754 $main::lxdebug->enter_sub();
758 my $form = $main::form;
759 my %myconfig = %main::myconfig;
760 my $locale = $main::locale;
764 if(!exists $form->{addition}) {
765 $form->{snumbers} = qq|donumber_| . $form->{donumber};
766 $form->{addition} = "DELETED";
769 # /saving the history
771 $form->info($locale->text('Delivery Order deleted!'));
775 $form->error($locale->text('Cannot delete delivery order!'));
777 $main::lxdebug->leave_sub();
781 $main::lxdebug->enter_sub();
783 my $form = $main::form;
784 my %myconfig = %main::myconfig;
785 my $locale = $main::locale;
788 $main::auth->assert($form->{type} eq 'purchase_delivery_order' ? 'vendor_invoice_edit' : 'invoice_edit');
790 $form->{convert_from_do_ids} = $form->{id};
791 $form->{deliverydate} = $form->{transdate};
792 $form->{transdate} = $form->{invdate} = $form->current_date(\%myconfig);
793 $form->{duedate} = $form->current_date(\%myconfig, $form->{invdate}, $form->{terms} * 1);
794 $form->{defaultcurrency} = $form->get_default_currency(\%myconfig);
798 delete @{$form}{qw(id closed delivered)};
800 my ($script, $buysell);
801 if ($form->{type} eq 'purchase_delivery_order') {
802 $form->{title} = $locale->text('Add Vendor Invoice');
803 $form->{script} = 'ir.pl';
808 $form->{title} = $locale->text('Add Sales Invoice');
809 $form->{script} = 'is.pl';
814 for my $i (1 .. $form->{rowcount}) {
816 unless ($form->{"ordnumber"}) {
817 if ($form->{discount}) { # Falls wir einen Lieferanten-/Kundenrabatt haben
818 # und rabattfähig sind, dann
819 unless ($form->{"not_discountable_$i"}) {
820 $form->{"discount_$i"} = $form->{discount}*100; # ... nehmen wir diesen Rabatt
824 map { $form->{"${_}_${i}"} = $form->parse_amount(\%myconfig, $form->{"${_}_${i}"}) if $form->{"${_}_${i}"} } qw(ship qty sellprice lastcost basefactor);
825 $form->{"donumber_$i"} = $form->{donumber};
826 $form->{"converted_from_delivery_order_items_id_$i"} = delete $form->{"delivery_order_items_id_$i"};
829 $form->{type} = "invoice";
832 $main::locale = Locale->new("$myconfig{countrycode}", "$script");
833 $locale = $main::locale;
835 require "bin/mozilla/$form->{script}";
837 my $currency = $form->{currency};
840 $form->{currency} = $currency;
841 $form->{exchangerate} = "";
842 $form->{forex} = $form->check_exchangerate(\%myconfig, $form->{currency}, $form->{invdate}, $buysell);
843 $form->{exchangerate} = $form->{forex} if ($form->{forex});
848 for my $i (1 .. $form->{rowcount}) {
849 $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"});
851 my ($dec) = ($form->{"sellprice_$i"} =~ /\.(\d+)/);
853 my $decimalplaces = ($dec > 2) ? $dec : 2;
855 # copy delivery date from reqdate for order -> invoice conversion
856 $form->{"deliverydate_$i"} = $form->{"reqdate_$i"}
857 unless $form->{"deliverydate_$i"};
860 $form->{"sellprice_$i"} =
861 $form->format_amount(\%myconfig, $form->{"sellprice_$i"},
864 $form->{"lastcost_$i"} =
865 $form->format_amount(\%myconfig, $form->{"lastcost_$i"},
868 (my $dec_qty) = ($form->{"qty_$i"} =~ /\.(\d+)/);
869 $dec_qty = length $dec_qty;
871 $form->format_amount(\%myconfig, $form->{"qty_$i"}, $dec_qty);
877 $main::lxdebug->leave_sub();
881 $main::lxdebug->enter_sub();
883 my $form = $main::form;
884 my %myconfig = %main::myconfig;
885 my $locale = $main::locale;
888 $main::auth->assert($form->{type} eq 'sales_delivery_order' ? 'invoice_edit' : 'vendor_invoice_edit');
890 my @do_ids = map { $form->{"trans_id_$_"} } grep { $form->{"multi_id_$_"} } (1..$form->{rowcount});
892 if (!scalar @do_ids) {
893 $form->show_generic_error($locale->text('You have not selected any delivery order.'), 'back_button' => 1);
896 map { delete $form->{$_} } grep { m/^(?:trans|multi)_id_\d+/ } keys %{ $form };
898 if (!DO->retrieve('vc' => $form->{vc}, 'ids' => \@do_ids)) {
899 $form->show_generic_error($form->{vc} eq 'customer' ?
900 $locale->text('You cannot create an invoice for delivery orders for different customers.') :
901 $locale->text('You cannot create an invoice for delivery orders from different vendors.'),
905 my $source_type = $form->{type};
906 $form->{convert_from_do_ids} = join ' ', @do_ids;
907 # bei der auswahl von mehreren Lieferscheinen fuer eine Rechnung, die einfach in donumber_array
908 # zwischenspeichern (DO.pm) und als ' '-separierte Liste wieder zurueckschreiben
909 # Hinweis: delete gibt den wert zurueck und loescht danach das element (nett und einfach)
910 # $shell: perldoc perlunc; /delete EXPR
911 $form->{donumber} = delete $form->{donumber_array};
912 $form->{ordnumber} = delete $form->{ordnumber_array};
913 $form->{cusordnumber} = delete $form->{cusordnumber_array};
914 $form->{deliverydate} = $form->{transdate};
915 $form->{transdate} = $form->current_date(\%myconfig);
916 $form->{duedate} = $form->current_date(\%myconfig, $form->{invdate}, $form->{terms} * 1);
917 $form->{type} = "invoice";
919 $form->{defaultcurrency} = $form->get_default_currency(\%myconfig);
921 my ($script, $buysell);
922 if ($source_type eq 'purchase_delivery_order') {
923 $form->{title} = $locale->text('Add Vendor Invoice');
924 $form->{script} = 'ir.pl';
929 $form->{title} = $locale->text('Add Sales Invoice');
930 $form->{script} = 'is.pl';
935 map { delete $form->{$_} } qw(id subject message cc bcc printed emailed queued);
937 # get vendor or customer discount
939 my $saved_form = save_form();
940 if ($form->{vc} eq 'vendor') {
941 IR->get_vendor(\%myconfig, \%$form);
942 $vc_discount = $form->{vendor_discount};
944 IS->get_customer(\%myconfig, \%$form);
945 $vc_discount = $form->{customer_discount};
947 restore_form($saved_form);
949 $form->{rowcount} = 0;
950 foreach my $ref (@{ $form->{form_details} }) {
952 $ref->{reqdate} ||= $ref->{dord_transdate}; # copy transdates into each invoice row
953 map { $form->{"${_}_$form->{rowcount}"} = $ref->{$_} } keys %{ $ref };
954 map { $form->{"${_}_$form->{rowcount}"} = $form->format_amount(\%myconfig, $ref->{$_}) } qw(qty sellprice lastcost);
955 $form->{"converted_from_delivery_order_items_id_$form->{rowcount}"} = delete $form->{"delivery_order_items_id_$form->{rowcount}"};
957 if ($vc_discount){ # falls wir einen Lieferanten/Kundenrabatt haben
958 # und keinen anderen discount wert an $i ...
959 $form->{"discount_$form->{rowcount}"} ||= $vc_discount; # ... nehmen wir diesen Rabatt
962 $form->{"discount_$form->{rowcount}"} = $form->{"discount_$form->{rowcount}"} * 100; #s.a. Bug 1151
963 # Anm.: Eine Änderung des discounts in der SL/DO.pm->retrieve (select (doi.discount * 100) as discount) ergibt in psql einen
964 # Wert von 10.0000001490116. Ferner ist der Rabatt in der Rechnung dann bei 1.0 (?). Deswegen lasse ich das hier. jb 10.10.09
966 $form->{"discount_$form->{rowcount}"} = $form->format_amount(\%myconfig, $form->{"discount_$form->{rowcount}"});
968 delete $form->{form_details};
970 $locale = Locale->new("$myconfig{countrycode}", "$script");
972 require "bin/mozilla/$form->{script}";
979 $main::lxdebug->leave_sub();
983 $main::lxdebug->enter_sub();
987 my $form = $main::form;
989 $form->{saveasnew} = 1;
991 $form->{delivered} = 0;
992 map { delete $form->{$_} } qw(printed emailed queued);
993 delete @{ $form }{ grep { m/^stock_(?:in|out)_\d+/ } keys %{ $form } };
994 $form->{"converted_from_delivery_order_items_id_$_"} = delete $form->{"delivery_order_items_id_$_"} for 1 .. $form->{"rowcount"};
995 # Let kivitendo assign a new order number if the user hasn't changed the
996 # previous one. If it has been changed manually then use it as-is.
997 $form->{donumber} =~ s/^\s*//g;
998 $form->{donumber} =~ s/\s*$//g;
999 if ($form->{saved_donumber} && ($form->{saved_donumber} eq $form->{donumber})) {
1000 delete($form->{donumber});
1005 $main::lxdebug->leave_sub();
1009 $main::lxdebug->enter_sub();
1013 my $form = $main::form;
1015 $form->{print_and_save} = 1;
1017 my $saved_form = save_form();
1021 restore_form($saved_form, 0, qw(id ordnumber quonumber));
1025 $main::lxdebug->leave_sub();
1028 sub calculate_stock_in_out {
1029 $main::lxdebug->enter_sub();
1031 my $form = $main::form;
1035 if (!$form->{"id_${i}"}) {
1036 $main::lxdebug->leave_sub();
1040 my $all_units = AM->retrieve_all_units();
1042 my $in_out = $form->{type} =~ /^sales/ ? 'out' : 'in';
1043 my $sinfo = DO->unpack_stock_information('packed' => $form->{"stock_${in_out}_${i}"});
1045 my $do_qty = AM->sum_with_unit($::form->{"qty_$i"}, $::form->{"unit_$i"});
1046 my $sum = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $sinfo });
1047 my $matches = $do_qty == $sum;
1049 my $content = $form->format_amount_units('amount' => $sum * 1,
1050 'part_unit' => $form->{"partunit_$i"},
1051 'amount_unit' => $all_units->{$form->{"partunit_$i"}}->{base_unit},
1052 'conv_units' => 'convertible_not_smaller',
1054 $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="?">|;
1056 $main::lxdebug->leave_sub();
1061 sub get_basic_bin_wh_info {
1062 $main::lxdebug->enter_sub();
1064 my $stock_info = shift;
1066 my $form = $main::form;
1068 foreach my $sinfo (@{ $stock_info }) {
1069 next unless ($sinfo->{bin_id});
1071 my $bin_info = WH->get_basic_bin_info('id' => $sinfo->{bin_id});
1072 map { $sinfo->{"${_}_description"} = $sinfo->{"${_}description"} = $bin_info->{"${_}_description"} } qw(bin warehouse);
1075 $main::lxdebug->leave_sub();
1078 sub stock_in_out_form {
1079 $main::lxdebug->enter_sub();
1081 my $form = $main::form;
1083 if ($form->{in_out} eq 'out') {
1089 $main::lxdebug->leave_sub();
1092 sub redo_stock_info {
1093 $main::lxdebug->enter_sub();
1097 my $form = $main::form;
1099 my @non_empty = grep { $_->{qty} } @{ $params{stock_info} };
1101 if ($params{add_empty_row}) {
1103 'warehouse_id' => scalar(@non_empty) ? $non_empty[-1]->{warehouse_id} : undef,
1104 'bin_id' => scalar(@non_empty) ? $non_empty[-1]->{bin_id} : undef,
1108 @{ $params{stock_info} } = @non_empty;
1110 $main::lxdebug->leave_sub();
1113 sub update_stock_in {
1114 $main::lxdebug->enter_sub();
1116 my $form = $main::form;
1117 my %myconfig = %main::myconfig;
1119 my $stock_info = [];
1121 foreach my $i (1..$form->{rowcount}) {
1122 $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
1123 push @{ $stock_info }, { map { $_ => $form->{"${_}_${i}"} } qw(warehouse_id bin_id chargenumber
1124 bestbefore qty unit delivery_order_items_stock_id) };
1127 display_stock_in_form($stock_info);
1129 $main::lxdebug->leave_sub();
1133 $main::lxdebug->enter_sub();
1135 my $form = $main::form;
1137 my $stock_info = DO->unpack_stock_information('packed' => $form->{stock});
1139 display_stock_in_form($stock_info);
1141 $main::lxdebug->leave_sub();
1144 sub display_stock_in_form {
1145 $main::lxdebug->enter_sub();
1147 my $stock_info = shift;
1149 my $form = $main::form;
1150 my %myconfig = %main::myconfig;
1151 my $locale = $main::locale;
1153 $form->{title} = $locale->text('Stock');
1155 my $part_info = IC->get_basic_part_info('id' => $form->{parts_id});
1157 # Standardlagerplatz für Standard-Auslagern verwenden, falls keiner für die Ware explizit definiert wurde
1158 if ($::instance_conf->get_transfer_default_use_master_default_bin) {
1159 $part_info->{warehouse_id} ||= $::instance_conf->get_warehouse_id;
1160 $part_info->{bin_id} ||= $::instance_conf->get_bin_id;
1163 my $units = AM->retrieve_units(\%myconfig, $form);
1164 # der zweite Parameter von unit_select_data gibt den default-Namen (selected) vor
1165 my $units_data = AM->unit_select_data($units, $form->{do_unit}, undef, $part_info->{unit});
1167 $form->get_lists('warehouses' => { 'key' => 'WAREHOUSES',
1168 'bins' => 'BINS' });
1170 redo_stock_info('stock_info' => $stock_info, 'add_empty_row' => !$form->{delivered});
1172 get_basic_bin_wh_info($stock_info);
1174 $form->header(no_layout => 1);
1175 print $form->parse_html_template('do/stock_in_form', { 'UNITS' => $units_data,
1176 'STOCK_INFO' => $stock_info,
1177 'PART_INFO' => $part_info, });
1179 $main::lxdebug->leave_sub();
1182 sub _stock_in_out_set_qty_display {
1183 my $stock_info = shift;
1185 my $all_units = AM->retrieve_all_units();
1186 my $sum = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
1187 $form->{qty_display} = $form->format_amount_units(amount => $sum * 1,
1188 part_unit => $form->{partunit},
1189 amount_unit => $all_units->{ $form->{partunit} }->{base_unit},
1190 conv_units => 'convertible_not_smaller',
1195 $main::lxdebug->enter_sub();
1197 my $form = $main::form;
1198 my %myconfig = %main::myconfig;
1200 my $stock_info = [];
1202 foreach my $i (1..$form->{rowcount}) {
1203 $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
1205 next if ($form->{"qty_$i"} <= 0);
1207 push @{ $stock_info }, { map { $_ => $form->{"${_}_${i}"} } qw(delivery_order_items_stock_id warehouse_id bin_id chargenumber bestbefore qty unit) };
1210 $form->{stock} = YAML::Dump($stock_info);
1212 _stock_in_out_set_qty_display($stock_info);
1214 my $do_qty = AM->sum_with_unit($::form->parse_amount(\%::myconfig, $::form->{do_qty}), $::form->{do_unit});
1215 my $transfer_qty = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
1218 print $form->parse_html_template('do/set_stock_in_out', {
1219 qty_matches => $do_qty == $transfer_qty,
1222 $main::lxdebug->leave_sub();
1225 sub stock_out_form {
1226 $main::lxdebug->enter_sub();
1228 my $form = $main::form;
1229 my %myconfig = %main::myconfig;
1230 my $locale = $main::locale;
1232 $form->{title} = $locale->text('Release From Stock');
1234 my $part_info = IC->get_basic_part_info('id' => $form->{parts_id});
1236 my $units = AM->retrieve_units(\%myconfig, $form);
1237 my $units_data = AM->unit_select_data($units, undef, undef, $part_info->{unit});
1239 my @contents = DO->get_item_availability('parts_id' => $form->{parts_id});
1241 my $stock_info = DO->unpack_stock_information('packed' => $form->{stock});
1243 if (!$form->{delivered}) {
1244 foreach my $row (@contents) {
1245 $row->{available_qty} = $form->format_amount_units('amount' => $row->{qty} * 1,
1246 'part_unit' => $part_info->{unit},
1247 'conv_units' => 'convertible_not_smaller',
1250 foreach my $sinfo (@{ $stock_info }) {
1251 next if (($row->{bin_id} != $sinfo->{bin_id}) ||
1252 ($row->{warehouse_id} != $sinfo->{warehouse_id}) ||
1253 ($row->{chargenumber} ne $sinfo->{chargenumber}) ||
1254 ($row->{bestbefore} ne $sinfo->{bestbefore}));
1256 map { $row->{"stock_$_"} = $sinfo->{$_} } qw(qty unit error delivery_order_items_stock_id);
1261 get_basic_bin_wh_info($stock_info);
1263 foreach my $sinfo (@{ $stock_info }) {
1264 map { $sinfo->{"stock_$_"} = $sinfo->{$_} } qw(qty unit);
1268 $form->header(no_layout => 1);
1269 print $form->parse_html_template('do/stock_out_form', { 'UNITS' => $units_data,
1270 'WHCONTENTS' => $form->{delivered} ? $stock_info : \@contents,
1271 'PART_INFO' => $part_info, });
1273 $main::lxdebug->leave_sub();
1277 $main::lxdebug->enter_sub();
1279 my $form = $main::form;
1280 my %myconfig = %main::myconfig;
1281 my $locale = $main::locale;
1283 my $stock_info = [];
1285 foreach my $i (1 .. $form->{rowcount}) {
1286 $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
1288 next if ($form->{"qty_$i"} <= 0);
1290 push @{ $stock_info }, {
1291 'warehouse_id' => $form->{"warehouse_id_$i"},
1292 'bin_id' => $form->{"bin_id_$i"},
1293 'chargenumber' => $form->{"chargenumber_$i"},
1294 'bestbefore' => $form->{"bestbefore_$i"},
1295 'qty' => $form->{"qty_$i"},
1296 'unit' => $form->{"unit_$i"},
1298 'delivery_order_items_stock_id' => $form->{"delivery_order_items_stock_id_$i"},
1302 my @errors = DO->check_stock_availability('requests' => $stock_info,
1303 'parts_id' => $form->{parts_id});
1305 $form->{stock} = YAML::Dump($stock_info);
1308 $form->{ERRORS} = [];
1309 map { push @{ $form->{ERRORS} }, $locale->text('Error in row #1: The quantity you entered is bigger than the stocked quantity.', $_->{row}); } @errors;
1310 stock_in_out_form();
1313 _stock_in_out_set_qty_display($stock_info);
1315 my $do_qty = AM->sum_with_unit($::form->parse_amount(\%::myconfig, $::form->{do_qty}), $::form->{do_unit});
1316 my $transfer_qty = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
1319 print $form->parse_html_template('do/set_stock_in_out', {
1320 qty_matches => $do_qty == $transfer_qty,
1324 $main::lxdebug->leave_sub();
1328 $main::lxdebug->enter_sub();
1330 my $form = $main::form;
1331 my %myconfig = %main::myconfig;
1332 my $locale = $main::locale;
1334 if ($form->{id} && DO->is_marked_as_delivered(id => $form->{id})) {
1335 $form->show_generic_error($locale->text('The parts for this delivery order have already been transferred in.'), 'back_button' => 1);
1338 save(no_redirect => 1);
1340 my @part_ids = map { $form->{"id_${_}"} } grep { $form->{"id_${_}"} && $form->{"stock_in_${_}"} } (1 .. $form->{rowcount});
1344 my $units = AM->retrieve_units(\%myconfig, $form);
1345 my %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
1348 $form->{ERRORS} = [];
1350 foreach my $i (1 .. $form->{rowcount}) {
1351 next unless ($form->{"id_$i"} && $form->{"stock_in_$i"});
1353 my $row_sum_base_qty = 0;
1354 my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
1356 foreach my $request (@{ DO->unpack_stock_information('packed' => $form->{"stock_in_$i"}) }) {
1357 $request->{parts_id} = $form->{"id_$i"};
1358 $row_sum_base_qty += $request->{qty} * $units->{$request->{unit}}->{factor} / $base_unit_factor;
1360 $request->{project_id} = $form->{"project_id_$i"} || $form->{globalproject_id};
1362 push @all_requests, $request;
1365 next if (0 == $row_sum_base_qty);
1367 my $do_base_qty = $form->parse_amount(\%myconfig, $form->{"qty_$i"}) * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
1369 # if ($do_base_qty != $row_sum_base_qty) {
1370 # push @{ $form->{ERRORS} }, $locale->text('Error in position #1: You must either assign no stock at all or the full quantity of #2 #3.',
1371 # $i, $form->{"qty_$i"}, $form->{"unit_$i"});
1375 if (@{ $form->{ERRORS} }) {
1376 push @{ $form->{ERRORS} }, $locale->text('The delivery order has not been marked as delivered. The warehouse contents have not changed.');
1378 set_headings('edit');
1380 $main::lxdebug->leave_sub();
1386 DO->transfer_in_out('direction' => 'in',
1387 'requests' => \@all_requests);
1389 SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
1391 $form->{callback} = 'do.pl?action=edit&type=purchase_delivery_order&id=' . $form->escape($form->{id});
1394 $main::lxdebug->leave_sub();
1398 $main::lxdebug->enter_sub();
1400 my $form = $main::form;
1401 my %myconfig = %main::myconfig;
1402 my $locale = $main::locale;
1404 if ($form->{id} && DO->is_marked_as_delivered(id => $form->{id})) {
1405 $form->show_generic_error($locale->text('The parts for this delivery order have already been transferred out.'), 'back_button' => 1);
1408 save(no_redirect => 1);
1410 my @part_ids = map { $form->{"id_${_}"} } grep { $form->{"id_${_}"} && $form->{"stock_out_${_}"} } (1 .. $form->{rowcount});
1414 my $units = AM->retrieve_units(\%myconfig, $form);
1415 my %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
1418 $form->{ERRORS} = [];
1420 foreach my $i (1 .. $form->{rowcount}) {
1421 next unless ($form->{"id_$i"} && $form->{"stock_out_$i"});
1423 my $row_sum_base_qty = 0;
1424 my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
1426 foreach my $request (@{ DO->unpack_stock_information('packed' => $form->{"stock_out_$i"}) }) {
1427 $request->{parts_id} = $form->{"id_$i"};
1428 $request->{base_qty} = $request->{qty} * $units->{$request->{unit}}->{factor} / $base_unit_factor;
1429 $request->{project_id} = $form->{"project_id_$i"} ? $form->{"project_id_$i"} : $form->{globalproject_id};
1431 my $map_key = join '--', ($form->{"id_$i"}, @{$request}{qw(warehouse_id bin_id chargenumber bestbefore)});
1433 $request_map{$map_key} ||= $request;
1434 $request_map{$map_key}->{sum_base_qty} ||= 0;
1435 $request_map{$map_key}->{sum_base_qty} += $request->{base_qty};
1436 $row_sum_base_qty += $request->{base_qty};
1438 push @all_requests, $request;
1441 next if (0 == $row_sum_base_qty);
1443 my $do_base_qty = $form->{"qty_$i"} * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
1445 # if ($do_base_qty != $row_sum_base_qty) {
1446 # push @{ $form->{ERRORS} }, $locale->text('Error in position #1: You must either assign no transfer at all or the full quantity of #2 #3.',
1447 # $i, $form->{"qty_$i"}, $form->{"unit_$i"});
1452 my @bin_ids = map { $_->{bin_id} } values %request_map;
1453 my %bin_info_map = WH->get_basic_bin_info('id' => \@bin_ids);
1454 my @contents = DO->get_item_availability('parts_id' => \@part_ids);
1456 foreach my $inv (@contents) {
1457 my $map_key = join '--', @{$inv}{qw(parts_id warehouse_id bin_id chargenumber bestbefore)};
1459 next unless ($request_map{$map_key});
1461 my $request = $request_map{$map_key};
1462 $request->{ok} = $request->{sum_base_qty} <= $inv->{qty};
1465 foreach my $request (values %request_map) {
1466 next if ($request->{ok});
1468 my $pinfo = $part_info_map{$request->{parts_id}};
1469 my $binfo = $bin_info_map{$request->{bin_id}};
1471 if ($::instance_conf->get_show_bestbefore) {
1472 push @{ $form->{ERRORS} }, $locale->text("There is not enough available of '#1' at warehouse '#2', bin '#3', #4, #5, for the transfer of #6.",
1473 $pinfo->{description},
1474 $binfo->{warehouse_description},
1475 $binfo->{bin_description},
1476 $request->{chargenumber} ? $locale->text('chargenumber #1', $request->{chargenumber}) : $locale->text('no chargenumber'),
1477 $request->{bestbefore} ? $locale->text('bestbefore #1', $request->{bestbefore}) : $locale->text('no bestbefore'),
1478 $form->format_amount_units('amount' => $request->{sum_base_qty},
1479 'part_unit' => $pinfo->{unit},
1480 'conv_units' => 'convertible_not_smaller'));
1482 push @{ $form->{ERRORS} }, $locale->text("There is not enough available of '#1' at warehouse '#2', bin '#3', #4, for the transfer of #5.",
1483 $pinfo->{description},
1484 $binfo->{warehouse_description},
1485 $binfo->{bin_description},
1486 $request->{chargenumber} ? $locale->text('chargenumber #1', $request->{chargenumber}) : $locale->text('no chargenumber'),
1487 $form->format_amount_units('amount' => $request->{sum_base_qty},
1488 'part_unit' => $pinfo->{unit},
1489 'conv_units' => 'convertible_not_smaller'));
1494 if (@{ $form->{ERRORS} }) {
1495 push @{ $form->{ERRORS} }, $locale->text('The delivery order has not been marked as delivered. The warehouse contents have not changed.');
1497 set_headings('edit');
1499 $main::lxdebug->leave_sub();
1504 DO->transfer_in_out('direction' => 'out',
1505 'requests' => \@all_requests);
1507 SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
1509 $form->{callback} = 'do.pl?action=edit&type=sales_delivery_order&id=' . $form->escape($form->{id});
1512 $main::lxdebug->leave_sub();
1516 $main::lxdebug->enter_sub();
1518 my $form = $main::form;
1520 DO->close_orders('ids' => [ $form->{id} ]);
1522 $form->{closed} = 1;
1526 $main::lxdebug->leave_sub();
1531 call_sub($main::form->{yes_nextsub});
1535 call_sub($main::form->{no_nextsub});
1539 call_sub($main::form->{update_nextsub} || $main::form->{nextsub} || 'update_delivery_order');
1543 my $form = $main::form;
1544 my $locale = $main::locale;
1546 foreach my $action (qw(update ship_to print e_mail save transfer_out transfer_out_default sort
1547 transfer_in transfer_in_default mark_closed save_as_new invoice delete)) {
1548 if ($form->{"action_${action}"}) {
1554 $form->error($locale->text('No action defined.'));
1557 sub transfer_out_default {
1558 $main::lxdebug->enter_sub();
1560 my $form = $main::form;
1562 transfer_in_out_default('direction' => 'out');
1564 $main::lxdebug->leave_sub();
1567 sub transfer_in_default {
1568 $main::lxdebug->enter_sub();
1570 my $form = $main::form;
1572 transfer_in_out_default('direction' => 'in');
1574 $main::lxdebug->leave_sub();
1577 # Falls das Standardlagerverfahren aktiv ist, wird
1578 # geprüft, ob alle Standardlagerplätze für die Auslager-
1579 # artikel vorhanden sind UND ob die Warenmenge ausreicht zum
1580 # Auslagern. Falls nicht wird entsprechend eine Fehlermeldung
1581 # generiert. Offen Chargennummer / bestbefore wird nicht berücksichtigt
1582 sub transfer_in_out_default {
1583 $main::lxdebug->enter_sub();
1585 my $form = $main::form;
1586 my %myconfig = %main::myconfig;
1587 my $locale = $main::locale;
1590 my (%missing_default_bins, %qty_parts, @all_requests, %part_info_map, $default_warehouse_id, $default_bin_id);
1592 Common::check_params(\%params, qw(direction));
1594 # entsprechende defaults holen, falls standardlagerplatz verwendet werden soll
1595 if ($::instance_conf->get_transfer_default_use_master_default_bin) {
1596 $default_warehouse_id = $::instance_conf->get_warehouse_id;
1597 $default_bin_id = $::instance_conf->get_bin_id;
1601 my @part_ids = map { $form->{"id_${_}"} } (1 .. $form->{rowcount});
1603 my $units = AM->retrieve_units(\%myconfig, $form);
1604 %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
1605 foreach my $i (1 .. $form->{rowcount}) {
1606 next unless ($form->{"id_$i"});
1607 my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
1608 my $qty = $form->parse_amount(\%myconfig, $form->{"qty_$i"}) * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
1610 $form->show_generic_error($locale->text("Cannot transfer negative entries." ), 'back_button' => 1) if ($qty < 0);
1611 # if we do not want to transfer services and this part is a service, set qty to zero
1612 # ... and do not create a hash entry in %qty_parts below (will skip check for bins for the transfer == out case)
1613 # ... 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)
1615 $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});
1616 $qty_parts{$form->{"id_$i"}} += $qty;
1618 delete $qty_parts{$form->{"id_$i"}} unless $qty_parts{$form->{"id_$i"}};
1619 undef $form->{"stock_in_$i"};
1622 $part_info_map{$form->{"id_$i"}}{bin_id} ||= $default_bin_id;
1623 $part_info_map{$form->{"id_$i"}}{warehouse_id} ||= $default_warehouse_id;
1625 push @all_requests, ($qty == 0) ? { } : {
1626 'chargenumber' => '', #?? die müsste entsprechend geholt werden
1627 #'bestbefore' => undef, # TODO wird nicht berücksichtigt
1628 'bin_id' => $part_info_map{$form->{"id_$i"}}{bin_id},
1630 'parts_id' => $form->{"id_$i"},
1631 'comment' => $locale->text("Default transfer delivery order"),
1632 'unit' => $part_info_map{$form->{"id_$i"}}{unit},
1633 'warehouse_id' => $part_info_map{$form->{"id_$i"}}{warehouse_id},
1634 'oe_id' => $form->{id},
1635 'project_id' => $form->{"project_id_$i"} ? $form->{"project_id_$i"} : $form->{globalproject_id}
1639 # jetzt wird erst überprüft, ob die Stückzahl entsprechend stimmt.
1640 # check if bin (transfer in and transfer out and qty (transfer out) is correct
1641 foreach my $key (keys %qty_parts) {
1643 $missing_default_bins{$key}{missing_bin} = 1 unless ($part_info_map{$key}{bin_id});
1644 next unless ($part_info_map{$key}{bin_id}); # abbruch
1646 if ($params{direction} eq 'out') { # wird nur für ausgehende Mengen benötigt
1647 my ($max_qty, $error) = WH->get_max_qty_parts_bin(parts_id => $key, bin_id => $part_info_map{$key}{bin_id});
1649 # wir können nicht entscheiden, welche charge oder mhd (bestbefore) ausgewählt sein soll
1650 # deshalb rückmeldung nach oben geben, manuell auszulagern
1651 # TODO Bei nur einem Treffer mit Charge oder bestbefore wäre das noch möglich
1652 $missing_default_bins{$key}{chargenumber} = 1;
1654 if ($max_qty < $qty_parts{$key}){
1655 $missing_default_bins{$key}{missing_qty} = $max_qty - $qty_parts{$key};
1661 # Abfrage für Fehlerbehandlung (nur bei direction == out)
1662 if (scalar (keys %missing_default_bins)) {
1664 foreach my $fehler (keys %missing_default_bins) {
1666 my $ware = WH->get_part_description(parts_id => $fehler);
1667 if ($missing_default_bins{$fehler}{missing_bin}){
1668 $fehlertext .= "Kein Standardlagerplatz definiert bei $ware <br>";
1670 if ($missing_default_bins{$fehler}{missing_qty}) { # missing_qty
1671 $fehlertext .= "Es fehlen " . $missing_default_bins{$fehler}{missing_qty}*-1 .
1672 " von $ware auf dem Standard-Lagerplatz " . $part_info_map{$fehler}{bin} . " zum Auslagern<br>";
1674 if ($missing_default_bins{$fehler}{chargenumber}){
1675 $fehlertext .= "Die Ware hat eine Chargennummer oder eine Mindesthaltbarkeit definiert.
1676 Hier kann man nicht automatisch entscheiden.
1677 Bitte diesen Lieferschein manuell auslagern.
1680 # auslagern soll immer gehen, auch wenn nicht genügend auf lager ist.
1681 # der lagerplatz ist hier extra konfigurierbar, bspw. Lager-Korrektur mit
1682 # Lagerplatz Lagerplatz-Korrektur
1683 my $default_warehouse_id_ignore_onhand = $::instance_conf->get_warehouse_id_ignore_onhand;
1684 my $default_bin_id_ignore_onhand = $::instance_conf->get_bin_id_ignore_onhand;
1685 if ($::instance_conf->get_transfer_default_ignore_onhand && $default_bin_id_ignore_onhand) {
1686 # entsprechende defaults holen
1687 # falls chargenumber, bestbefore oder anzahl nicht stimmt, auf automatischen
1688 # lagerplatz wegbuchen!
1689 foreach (@all_requests) {
1690 if ($_->{parts_id} eq $fehler){
1691 $_->{bin_id} = $default_bin_id_ignore_onhand;
1692 $_->{warehouse_id} = $default_warehouse_id_ignore_onhand;
1696 #$main::lxdebug->message(0, 'Fehlertext: ' . $fehlertext);
1697 $form->show_generic_error($locale->text("Cannot transfer. <br> Reason:<br>#1", $fehlertext ), 'back_button' => 1);
1703 # hier der eigentliche fallunterschied für in oder out
1704 my $prefix = $params{direction} eq 'in' ? 'in' : 'out';
1706 # dieser array_ref ist für DO->save da:
1707 # einmal die all_requests in YAML verwandeln, damit delivery_order_items_stock
1708 # gefüllt werden kann.
1710 foreach (@all_requests){
1712 next unless scalar(%{ $_ });
1713 $form->{"stock_${prefix}_$i"} = YAML::Dump([$_]);
1716 save(no_redirect => 1); # Wir können auslagern, deshalb beleg speichern
1717 # und in delivery_order_items_stock speichern
1718 DO->transfer_in_out('direction' => $prefix,
1719 'requests' => \@all_requests);
1721 SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
1723 $form->{callback} = 'do.pl?action=edit&type=sales_delivery_order&id=' . $form->escape($form->{id}) if $params{direction} eq 'out';
1724 $form->{callback} = 'do.pl?action=edit&type=purchase_delivery_order&id=' . $form->escape($form->{id}) if $params{direction} eq 'in';
1730 $main::lxdebug->enter_sub();
1734 my $form = $main::form;
1737 croak ("Delivery Order needs to be saved") unless $form->{id};
1739 # hashify partnumbers, positions. key is delivery_order_items_id
1740 for my $i (1 .. ($form->{rowcount}) ) {
1741 $temp_hash{$form->{"delivery_order_items_id_$i"}} = { runningnumber => $form->{"runningnumber_$i"}, partnumber => $form->{"partnumber_$i"} };
1743 # naturally sort partnumbers and get a sorted array of doi_ids
1744 my @sorted_doi_ids = sort { Sort::Naturally::ncmp($temp_hash{$a}->{"partnumber"}, $temp_hash{$b}->{"partnumber"}) } keys %temp_hash;
1749 for (@sorted_doi_ids) {
1750 $form->{"runningnumber_$temp_hash{$_}->{runningnumber}"} = $new_number;
1753 $main::lxdebug->leave_sub();
1765 do.pl - Script for all calls to delivery order
1774 Sorts all position with Natural Sort. Can be activated in form_footer.html like this
1775 C<E<lt>input class="submit" type="submit" name="action_sort" id="sort_button" value="[% 'Sort and Save' | $T8 %]"E<gt>>
1781 Sort and Save can be implemented as an optional button if configuration ca be set by client config.
1782 Example coding for database scripts and templates in (git show af2f24b8), check also
1783 autogeneration for rose (scripts/rose_auto_create_model.pl --h)