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 #======================================================================
34 use List::MoreUtils qw(uniq);
35 use List::Util qw(max sum);
36 use POSIX qw(strftime);
39 use SL::DB::DeliveryOrder;
43 use SL::MoreCommon qw(ary_diff);
44 use SL::ReportGenerator;
46 use Sort::Naturally ();
47 require "bin/mozilla/arap.pl";
48 require "bin/mozilla/common.pl";
49 require "bin/mozilla/invoice_io.pl";
50 require "bin/mozilla/io.pl";
51 require "bin/mozilla/reportgenerator.pl";
60 $main::auth->assert($main::form->{type} . '_edit');
64 $main::lxdebug->enter_sub();
70 my $form = $main::form;
71 my $locale = $main::locale;
73 if ($form->{type} eq 'purchase_delivery_order') {
74 $form->{vc} = 'vendor';
75 $form->{title} = $action eq "edit" ? $locale->text('Edit Purchase Delivery Order') : $locale->text('Add Purchase Delivery Order');
77 $form->{vc} = 'customer';
78 $form->{title} = $action eq "edit" ? $locale->text('Edit Sales Delivery Order') : $locale->text('Add Sales Delivery Order');
81 $form->{heading} = $locale->text('Delivery Order');
83 $main::lxdebug->leave_sub();
87 $main::lxdebug->enter_sub();
91 if (($::form->{type} =~ /purchase/) && !$::instance_conf->get_allow_new_purchase_invoice) {
92 $::form->show_generic_error($::locale->text("You do not have the permissions to access this function."));
95 my $form = $main::form;
99 $form->{callback} = build_std_url('action=add', 'type', 'vc') unless ($form->{callback});
105 $main::lxdebug->leave_sub();
109 $main::lxdebug->enter_sub();
113 my $form = $main::form;
115 # show history button
116 $form->{javascript} = qq|<script type="text/javascript" src="js/show_history.js"></script>|;
117 #/show hhistory button
119 $form->{simple_save} = 0;
121 set_headings("edit");
123 # editing without stuff to edit? try adding it first
124 if ($form->{rowcount} && !$form->{print_and_save}) {
125 # map { $id++ if $form->{"multi_id_$_"} } (1 .. $form->{rowcount});
129 undef $form->{rowcount};
131 $main::lxdebug->leave_sub();
134 } elsif (!$form->{id}) {
136 $main::lxdebug->leave_sub();
140 my ($language_id, $printer_id);
141 if ($form->{print_and_save}) {
142 $form->{action} = "dispatcher";
143 $form->{action_print} = "1";
144 $form->{resubmit} = 1;
145 $language_id = $form->{language_id};
146 $printer_id = $form->{printer_id};
149 set_headings("edit");
154 if ($form->{print_and_save}) {
155 $form->{language_id} = $language_id;
156 $form->{printer_id} = $printer_id;
161 $main::lxdebug->leave_sub();
165 $main::lxdebug->enter_sub();
169 my $form = $main::form;
170 my %myconfig = %main::myconfig;
172 # get customer/vendor
173 $form->all_vc(\%myconfig, $form->{vc}, ($form->{vc} eq 'customer') ? "AR" : "AP");
175 # retrieve order/quotation
176 my $editing = $form->{id};
178 DO->retrieve('vc' => $form->{vc},
179 'ids' => $form->{id});
181 $form->backup_vars(qw(payment_id language_id taxzone_id salesman_id taxincluded cp_id intnotes delivery_term_id currency));
183 # get customer / vendor
184 if ($form->{vc} eq 'vendor') {
185 IR->get_vendor(\%myconfig, \%$form);
186 $form->{discount} = $form->{vendor_discount};
188 IS->get_customer(\%myconfig, \%$form);
189 $form->{discount} = $form->{customer_discount};
192 $form->restore_vars(qw(payment_id language_id taxzone_id intnotes cp_id delivery_term_id));
193 $form->restore_vars(qw(currency)) if ($form->{id} || $form->{convert_from_oe_ids});
194 $form->restore_vars(qw(taxincluded)) if $form->{id};
195 $form->restore_vars(qw(salesman_id)) if $editing;
197 if ($form->{"all_$form->{vc}"}) {
198 unless ($form->{"$form->{vc}_id"}) {
199 $form->{"$form->{vc}_id"} = $form->{"all_$form->{vc}"}->[0]->{id};
203 ($form->{ $form->{vc} }) = split /--/, $form->{ $form->{vc} };
204 $form->{"old$form->{vc}"} = qq|$form->{$form->{vc}}--$form->{"$form->{vc}_id"}|;
206 $form->{employee} = "$form->{employee}--$form->{employee_id}";
208 $main::lxdebug->leave_sub();
212 $main::lxdebug->enter_sub();
216 my $form = $main::form;
217 my %myconfig = %main::myconfig;
219 $form->{formname} = $form->{type} unless $form->{formname};
222 foreach my $ref (@{ $form->{form_details} }) {
223 $form->{rowcount} = ++$i;
225 map { $form->{"${_}_$i"} = $ref->{$_} } keys %{$ref};
227 for my $i (1 .. $form->{rowcount}) {
229 $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"} * 100);
231 $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"});
233 my ($dec) = ($form->{"sellprice_$i"} =~ /\.(\d+)/);
235 my $decimalplaces = ($dec > 2) ? $dec : 2;
237 # copy reqdate from deliverydate for invoice -> order conversion
238 $form->{"reqdate_$i"} = $form->{"deliverydate_$i"} unless $form->{"reqdate_$i"};
240 $form->{"sellprice_$i"} = $form->format_amount(\%myconfig, $form->{"sellprice_$i"}, $decimalplaces);
241 $form->{"lastcost_$i"} = $form->format_amount(\%myconfig, $form->{"lastcost_$i"}, $decimalplaces);
243 (my $dec_qty) = ($form->{"qty_$i"} =~ /\.(\d+)/);
244 $dec_qty = length $dec_qty;
245 $form->{"qty_$i"} = $form->format_amount(\%myconfig, $form->{"qty_$i"}, $dec_qty);
248 $main::lxdebug->leave_sub();
252 $main::lxdebug->enter_sub();
256 my $form = $main::form;
257 my %myconfig = %main::myconfig;
259 $form->{employee_id} = $form->{old_employee_id} if $form->{old_employee_id};
260 $form->{salesman_id} = $form->{old_salesman_id} if $form->{old_salesman_id};
262 my $vc = $form->{vc} eq "customer" ? "customers" : "vendors";
263 $form->get_lists($vc => "ALL_VC",
264 "price_factors" => "ALL_PRICE_FACTORS",
265 "departments" => "ALL_DEPARTMENTS",
266 "business_types" => "ALL_BUSINESS_TYPES",
270 my @old_project_ids = uniq grep { $_ } map { $_ * 1 } ($form->{"globalproject_id"}, map { $form->{"project_id_$_"} } 1..$form->{"rowcount"});
271 my @old_ids_cond = @old_project_ids ? (id => \@old_project_ids) : ();
273 if (($vc eq 'customers') && $::instance_conf->get_customer_projects_only_in_sales) {
276 customer_id => $::form->{customer_id},
277 billable_customer_id => $::form->{customer_id},
282 and => [ active => 1, @customer_cond ],
286 $::form->{ALL_PROJECTS} = SL::DB::Manager::Project->get_all_sorted(query => \@conditions);
287 $::form->{ALL_EMPLOYEES} = SL::DB::Manager::Employee->get_all_sorted(query => [ or => [ id => $::form->{employee_id}, deleted => 0 ] ]);
288 $::form->{ALL_SALESMEN} = SL::DB::Manager::Employee->get_all_sorted(query => [ or => [ id => $::form->{salesman_id}, deleted => 0 ] ]);
289 $::form->{ALL_SHIPTO} = SL::DB::Manager::Shipto->get_all_sorted(query => [
290 or => [ trans_id => $::form->{"$::form->{vc}_id"} * 1, and => [ shipto_id => $::form->{shipto_id} * 1, trans_id => undef ] ]
292 $::form->{ALL_CONTACTS} = SL::DB::Manager::Contact->get_all_sorted(query => [
294 cp_cv_id => $::form->{"$::form->{vc}_id"} * 1,
297 cp_id => $::form->{cp_id} * 1
302 map { $_->{value} = "$_->{description}--$_->{id}" } @{ $form->{ALL_DEPARTMENTS} };
303 map { $_->{value} = "$_->{name}--$_->{id}" } @{ $form->{ALL_VC} };
305 $form->{SHOW_VC_DROP_DOWN} = $myconfig{vclimit} > scalar @{ $form->{ALL_VC} };
307 $form->{oldvcname} = $form->{"old$form->{vc}"};
308 $form->{oldvcname} =~ s/--.*//;
310 my $dispatch_to_popup = '';
311 if ($form->{resubmit} && ($form->{format} eq "html")) {
312 $dispatch_to_popup = "window.open('about:blank','Beleg'); document.do.target = 'Beleg';";
313 $dispatch_to_popup .= "document.do.submit();";
314 } elsif ($form->{resubmit}) {
315 # emulate click for resubmitting actions
316 $dispatch_to_popup = "document.do.${_}.click(); " for grep { /^action_/ } keys %$form;
318 $::request->{layout}->add_javascripts_inline("\$(function(){$dispatch_to_popup});");
321 my $follow_up_vc = $form->{ $form->{vc} eq 'customer' ? 'customer' : 'vendor' };
322 $follow_up_vc =~ s/--\d*\s*$//;
324 $form->{follow_up_trans_info} = $form->{donumber} .'('. $follow_up_vc .')';
326 $::request->{layout}->use_javascript(map { "${_}.js" } qw(kivi.SalesPurchase ckeditor/ckeditor ckeditor/adapters/jquery kivi.io autocomplete_customer autocomplete_part));
329 # Fix für Bug 1082 Erwartet wird: 'abteilungsNAME--abteilungsID'
330 # und Erweiterung für Bug 1760:
331 # Das war leider nur ein Teil-Fix, da das Verhalten den 'Erneuern'-Knopf
332 # nicht überlebt. Konsequent jetzt auf L umgestellt
333 # $ perldoc SL::Template::Plugin::L
334 # Daher entsprechend nur die Anpassung in form_header
335 # und in DO.pm gemacht. 4 Testfälle:
336 # department_id speichern | i.O.
337 # department_id lesen | i.O.
338 # department leer überlebt erneuern | i.O.
339 # department nicht leer überlebt erneuern | i.O.
340 # $main::lxdebug->message(0, 'ABTEILUNGS ID in form?' . $form->{department_id});
341 print $form->parse_html_template('do/form_header');
343 $main::lxdebug->leave_sub();
347 $main::lxdebug->enter_sub();
351 my $form = $main::form;
353 $form->{PRINT_OPTIONS} = print_options('inline' => 1);
354 $form->{ALL_DELIVERY_TERMS} = SL::DB::Manager::DeliveryTerm->get_all_sorted();
356 print $form->parse_html_template('do/form_footer',
357 {transfer_default => ($::instance_conf->get_transfer_default)});
359 $main::lxdebug->leave_sub();
362 sub update_delivery_order {
363 $main::lxdebug->enter_sub();
367 my $form = $main::form;
368 my %myconfig = %main::myconfig;
370 set_headings($form->{"id"} ? "edit" : "add");
372 $form->{insertdate} = SL::DB::DeliveryOrder->new(id => $form->{id})->load->itime_as_date if $form->{id};
377 $payment_id = $form->{payment_id} if $form->{payment_id};
379 check_name($form->{vc});
380 $form->{discount} = $form->{"$form->{vc}_discount"} if defined $form->{"$form->{vc}_discount"};
381 # Problem: Wenn man ohne Erneuern einen Kunden/Lieferanten
382 # wechselt, wird der entsprechende Kunden/ Lieferantenrabatt
383 # nicht übernommen. Grundproblem: In Commit 82574e78
384 # hab ich aus discount customer_discount und vendor_discount
385 # gemacht und entsprechend an den Oberflächen richtig hin-
386 # geschoben. Die damals bessere Lösung wäre gewesen:
387 # In den Templates nur die hidden für form-discount wieder ein-
388 # setzen dann wäre die Verrenkung jetzt nicht notwendig.
389 # TODO: Ggf. Bugfix 1284, 1575 und 817 wieder zusammenführen
390 # Testfälle: Kunden mit Rabatt 0 -> Rabatt 20 i.O.
391 # Kunde mit Rabatt 20 -> Rabatt 0 i.O.
392 # Kunde mit Rabatt 20 -> Rabatt 5,5 i.O.
393 $form->{payment_id} = $payment_id if $form->{payment_id} eq "";
395 my $i = $form->{rowcount};
397 if ( ($form->{"partnumber_$i"} eq "")
398 && ($form->{"description_$i"} eq "")
399 && ($form->{"partsgroup_$i"} eq "")) {
406 if ($form->{type} eq 'purchase_delivery_order') {
407 IR->retrieve_item(\%myconfig, $form);
410 IS->retrieve_item(\%myconfig, $form);
414 my $rows = scalar @{ $form->{item_list} };
417 $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
418 if( !$form->{"qty_$i"} ) {
419 $form->{"qty_$i"} = 1;
424 select_item(mode => $mode, pre_entered_qty => $form->{"qty_$i"});
429 my $sellprice = $form->parse_amount(\%myconfig, $form->{"sellprice_$i"});
431 map { $form->{"${_}_$i"} = $form->{item_list}[0]{$_} } keys %{ $form->{item_list}[0] };
433 $form->{"marge_price_factor_$i"} = $form->{item_list}->[0]->{price_factor};
436 $form->{"sellprice_$i"} = $sellprice;
438 my $record = _make_record();
439 my $price_source = SL::PriceSource->new(record_item => $record->items->[$i-1], record => $record);
440 my $best_price = $price_source->best_price;
441 my $best_discount = $price_source->best_discount;
444 $::form->{"sellprice_$i"} = $best_price->price;
445 $::form->{"active_price_source_$i"} = $best_price->source;
447 if ($best_discount) {
448 $::form->{"discount_$i"} = $best_discount->discount;
449 $::form->{"active_discount_source_$i"} = $best_discount->source;
453 $form->{"sellprice_$i"} = $form->format_amount(\%myconfig, $form->{"sellprice_$i"});
454 $form->{"lastcost_$i"} = $form->format_amount(\%myconfig, $form->{"lastcost_$i"});
455 $form->{"qty_$i"} = $form->format_amount(\%myconfig, $form->{"qty_$i"});
456 $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"} * 100.0);
463 # ok, so this is a new part
464 # ask if it is a part or service item
466 if ( $form->{"partsgroup_$i"}
467 && ($form->{"partsnumber_$i"} eq "")
468 && ($form->{"description_$i"} eq "")) {
470 $form->{"discount_$i"} = "";
471 $form->{"not_discountable_$i"} = "";
475 $form->{"id_$i"} = 0;
481 $main::lxdebug->leave_sub();
485 $main::lxdebug->enter_sub();
489 my $form = $main::form;
490 my %myconfig = %main::myconfig;
491 my $locale = $main::locale;
493 $form->{vc} = $form->{type} eq 'purchase_delivery_order' ? 'vendor' : 'customer';
495 $form->get_lists("projects" => { "key" => "ALL_PROJECTS",
497 "departments" => "ALL_DEPARTMENTS",
498 "$form->{vc}s" => "ALL_VC",
499 "business_types" => "ALL_BUSINESS_TYPES");
500 $form->{ALL_EMPLOYEES} = SL::DB::Manager::Employee->get_all_sorted(query => [ deleted => 0 ]);
502 $form->{SHOW_VC_DROP_DOWN} = $myconfig{vclimit} > scalar @{ $form->{ALL_VC} };
503 $form->{title} = $locale->text('Delivery Orders');
507 print $form->parse_html_template('do/search');
509 $main::lxdebug->leave_sub();
513 $main::lxdebug->enter_sub();
517 my $form = $main::form;
518 my %myconfig = %main::myconfig;
519 my $locale = $main::locale;
520 my $cgi = $::request->{cgi};
522 $form->{department_id} = (split /--/, $form->{department})[-1];
523 ($form->{ $form->{vc} }, $form->{"$form->{vc}_id"}) = split(/--/, $form->{ $form->{vc} });
525 report_generator_set_default_sort('transdate', 1);
529 $form->{rowcount} = scalar @{ $form->{DO} };
532 ids transdate reqdate
534 ordnumber customernumber cusordnumber
535 name employee salesman
536 shipvia globalprojectnumber
537 transaction_description department
542 $form->{l_open} = $form->{l_closed} = "Y" if ($form->{open} && $form->{closed});
543 $form->{l_delivered} = "Y" if ($form->{delivered} && $form->{notdelivered});
545 $form->{title} = $locale->text('Delivery Orders');
547 my $attachment_basename = $form->{vc} eq 'vendor' ? $locale->text('purchase_delivery_order_list') : $locale->text('sales_delivery_order_list');
549 my $report = SL::ReportGenerator->new(\%myconfig, $form);
551 my @hidden_variables = map { "l_${_}" } @columns;
552 push @hidden_variables, $form->{vc}, qw(l_closed l_notdelivered open closed delivered notdelivered donumber ordnumber serialnumber cusordnumber
553 transaction_description transdatefrom transdateto reqdatefrom reqdateto
554 type vc employee_id salesman_id project_id
555 insertdatefrom insertdateto business_id);
557 my $href = build_std_url('action=orders', grep { $form->{$_} } @hidden_variables);
560 'ids' => { 'text' => '', },
561 'transdate' => { 'text' => $locale->text('Delivery Order Date'), },
562 'reqdate' => { 'text' => $locale->text('Reqdate'), },
563 'id' => { 'text' => $locale->text('ID'), },
564 'donumber' => { 'text' => $locale->text('Delivery Order'), },
565 'ordnumber' => { 'text' => $locale->text('Order'), },
566 'customernumber' => { 'text' => $locale->text('Customer Number'), },
567 'cusordnumber' => { 'text' => $locale->text('Customer Order Number'), },
568 'name' => { 'text' => $form->{vc} eq 'customer' ? $locale->text('Customer') : $locale->text('Vendor'), },
569 'employee' => { 'text' => $locale->text('Employee'), },
570 'salesman' => { 'text' => $locale->text('Salesman'), },
571 'shipvia' => { 'text' => $locale->text('Ship via'), },
572 'globalprojectnumber' => { 'text' => $locale->text('Project Number'), },
573 'transaction_description' => { 'text' => $locale->text('Transaction description'), },
574 'open' => { 'text' => $locale->text('Open'), },
575 'delivered' => { 'text' => $locale->text('Delivered'), },
576 'department' => { 'text' => $locale->text('Department'), },
577 'insertdate' => { 'text' => $locale->text('Insert Date'), },
580 foreach my $name (qw(id transdate reqdate donumber ordnumber name employee salesman shipvia transaction_description department insertdate)) {
581 my $sortdir = $form->{sort} eq $name ? 1 - $form->{sortdir} : $form->{sortdir};
582 $column_defs{$name}->{link} = $href . "&sort=$name&sortdir=$sortdir";
585 $form->{"l_type"} = "Y";
586 map { $column_defs{$_}->{visible} = $form->{"l_${_}"} ? 1 : 0 } @columns;
588 $column_defs{ids}->{visible} = 'HTML';
590 $report->set_columns(%column_defs);
591 $report->set_column_order(@columns);
593 $report->set_export_options('orders', @hidden_variables, qw(sort sortdir));
595 $report->set_sort_indicator($form->{sort}, $form->{sortdir});
598 if ($form->{customer}) {
599 push @options, $locale->text('Customer') . " : $form->{customer}";
601 if ($form->{vendor}) {
602 push @options, $locale->text('Vendor') . " : $form->{vendor}";
604 if ($form->{cp_name}) {
605 push @options, $locale->text('Contact Person') . " : $form->{cp_name}";
607 if ($form->{department}) {
608 my ($department) = split /--/, $form->{department};
609 push @options, $locale->text('Department') . " : $department";
611 if ($form->{donumber}) {
612 push @options, $locale->text('Delivery Order Number') . " : $form->{donumber}";
614 if ($form->{ordnumber}) {
615 push @options, $locale->text('Order Number') . " : $form->{ordnumber}";
617 push @options, $locale->text('Serial Number') . " : $form->{serialnumber}" if $form->{serialnumber};
618 if ($form->{business_id}) {
619 my $vc_type_label = $form->{vc} eq 'customer' ? $locale->text('Customer type') : $locale->text('Vendor type');
620 push @options, $vc_type_label . " : " . SL::DB::Business->new(id => $form->{business_id})->load->description;
622 if ($form->{transaction_description}) {
623 push @options, $locale->text('Transaction description') . " : $form->{transaction_description}";
625 if ( $form->{transdatefrom} or $form->{transdateto} ) {
626 push @options, $locale->text('Delivery Order Date');
627 push @options, $locale->text('From') . " " . $locale->date(\%myconfig, $form->{transdatefrom}, 1) if $form->{transdatefrom};
628 push @options, $locale->text('Bis') . " " . $locale->date(\%myconfig, $form->{transdateto}, 1) if $form->{transdateto};
630 if ( $form->{reqdatefrom} or $form->{reqdateto} ) {
631 push @options, $locale->text('Reqdate');
632 push @options, $locale->text('From') . " " . $locale->date(\%myconfig, $form->{reqdatefrom}, 1) if $form->{reqdatefrom};
633 push @options, $locale->text('Bis') . " " . $locale->date(\%myconfig, $form->{reqdateto}, 1) if $form->{reqdateto};
635 if ( $form->{insertdatefrom} or $form->{insertdateto} ) {
636 push @options, $locale->text('Insert Date');
637 push @options, $locale->text('From') . " " . $locale->date(\%myconfig, $form->{insertdatefrom}, 1) if $form->{insertdatefrom};
638 push @options, $locale->text('Bis') . " " . $locale->date(\%myconfig, $form->{insertdateto}, 1) if $form->{insertdateto};
641 push @options, $locale->text('Open');
643 if ($form->{closed}) {
644 push @options, $locale->text('Closed');
646 if ($form->{delivered}) {
647 push @options, $locale->text('Delivered');
649 if ($form->{notdelivered}) {
650 push @options, $locale->text('Not delivered');
653 $report->set_options('top_info_text' => join("\n", @options),
654 'raw_top_info_text' => $form->parse_html_template('do/orders_top'),
655 'raw_bottom_info_text' => $form->parse_html_template('do/orders_bottom'),
656 'output_format' => 'HTML',
657 'title' => $form->{title},
658 'attachment_basename' => $attachment_basename . strftime('_%Y%m%d', localtime time),
660 $report->set_options_from_form();
661 $locale->set_numberformat_wo_thousands_separator(\%myconfig) if lc($report->{options}->{output_format}) eq 'csv';
663 # add sort and escape callback, this one we use for the add sub
664 $form->{callback} = $href .= "&sort=$form->{sort}";
666 # escape callback for href
667 my $callback = $form->escape($href);
669 my $edit_url = build_std_url('action=edit', 'type', 'vc');
670 my $edit_order_url = build_std_url('script=oe.pl', 'type=' . ($form->{type} eq 'sales_delivery_order' ? 'sales_order' : 'purchase_order'), 'action=edit');
674 foreach my $dord (@{ $form->{DO} }) {
675 $dord->{open} = $dord->{closed} ? $locale->text('No') : $locale->text('Yes');
676 $dord->{delivered} = $dord->{delivered} ? $locale->text('Yes') : $locale->text('No');
678 my $row = { map { $_ => { 'data' => $dord->{$_} } } @columns };
681 'raw_data' => $cgi->hidden('-name' => "trans_id_${idx}", '-value' => $dord->{id})
682 . $cgi->checkbox('-name' => "multi_id_${idx}", '-value' => 1, '-label' => ''),
683 'valign' => 'center',
687 $row->{donumber}->{link} = $edit_url . "&id=" . E($dord->{id}) . "&callback=${callback}";
688 $row->{ordnumber}->{link} = $edit_order_url . "&id=" . E($dord->{oe_id}) . "&callback=${callback}" if $dord->{oe_id};
689 $report->add_data($row);
694 $report->generate_with_headers();
696 $main::lxdebug->leave_sub();
700 $main::lxdebug->enter_sub();
706 my $form = $main::form;
707 my %myconfig = %main::myconfig;
708 my $locale = $main::locale;
710 $form->mtime_ischanged('delivery_orders');
712 $form->{defaultcurrency} = $form->get_default_currency(\%myconfig);
714 $form->isblank("transdate", $locale->text('Delivery Order Date missing!'));
716 $form->{donumber} =~ s/^\s*//g;
717 $form->{donumber} =~ s/\s*$//g;
719 my $msg = ucfirst $form->{vc};
720 $form->isblank($form->{vc}, $locale->text($msg . " missing!"));
722 # $locale->text('Customer missing!');
723 # $locale->text('Vendor missing!');
725 remove_emptied_rows();
728 # if the name changed get new values
729 if (check_name($form->{vc})) {
734 $form->{id} = 0 if $form->{saveasnew};
738 if(!exists $form->{addition}) {
739 $form->{snumbers} = qq|donumber_| . $form->{donumber};
740 $form->{addition} = "SAVED";
743 # /saving the history
745 $form->{simple_save} = 1;
746 if (!$params{no_redirect} && !$form->{print_and_save}) {
747 delete @{$form}{ary_diff([keys %{ $form }], [qw(login id script type cursor_fokus)])};
751 $main::lxdebug->leave_sub();
755 $main::lxdebug->enter_sub();
759 my $form = $main::form;
760 my %myconfig = %main::myconfig;
761 my $locale = $main::locale;
765 if(!exists $form->{addition}) {
766 $form->{snumbers} = qq|donumber_| . $form->{donumber};
767 $form->{addition} = "DELETED";
770 # /saving the history
772 $form->info($locale->text('Delivery Order deleted!'));
776 $form->error($locale->text('Cannot delete delivery order!'));
778 $main::lxdebug->leave_sub();
782 $main::lxdebug->enter_sub();
784 my $form = $main::form;
785 my %myconfig = %main::myconfig;
786 my $locale = $main::locale;
789 $form->mtime_ischanged('delivery_orders');
791 $main::auth->assert($form->{type} eq 'purchase_delivery_order' ? 'vendor_invoice_edit' : 'invoice_edit');
793 $form->{convert_from_do_ids} = $form->{id};
794 $form->{deliverydate} = $form->{transdate};
795 $form->{transdate} = $form->{invdate} = $form->current_date(\%myconfig);
796 $form->{duedate} = $form->current_date(\%myconfig, $form->{invdate}, $form->{terms} * 1);
797 $form->{defaultcurrency} = $form->get_default_currency(\%myconfig);
801 delete @{$form}{qw(id closed delivered)};
803 my ($script, $buysell);
804 if ($form->{type} eq 'purchase_delivery_order') {
805 $form->{title} = $locale->text('Add Vendor Invoice');
806 $form->{script} = 'ir.pl';
811 $form->{title} = $locale->text('Add Sales Invoice');
812 $form->{script} = 'is.pl';
817 for my $i (1 .. $form->{rowcount}) {
819 unless ($form->{"ordnumber"}) {
820 if ($form->{discount}) { # Falls wir einen Lieferanten-/Kundenrabatt haben
821 # und rabattfähig sind, dann
822 unless ($form->{"not_discountable_$i"}) {
823 $form->{"discount_$i"} = $form->{discount}*100; # ... nehmen wir diesen Rabatt
827 map { $form->{"${_}_${i}"} = $form->parse_amount(\%myconfig, $form->{"${_}_${i}"}) if $form->{"${_}_${i}"} } qw(ship qty sellprice lastcost basefactor);
828 $form->{"donumber_$i"} = $form->{donumber};
829 $form->{"converted_from_delivery_order_items_id_$i"} = delete $form->{"delivery_order_items_id_$i"};
832 $form->{type} = "invoice";
835 $main::locale = Locale->new("$myconfig{countrycode}", "$script");
836 $locale = $main::locale;
838 require "bin/mozilla/$form->{script}";
840 my $currency = $form->{currency};
843 if ($form->{ordnumber}) {
844 require SL::DB::Order;
845 if (my $order = SL::DB::Manager::Order->find_by(ordnumber => $form->{ordnumber})) {
847 $form->{orddate} = $order->transdate_as_date;
848 $form->{$_} = $order->$_ for qw(payment_id salesman_id taxzone_id quonumber);
852 $form->{currency} = $currency;
853 $form->{exchangerate} = "";
854 $form->{forex} = $form->check_exchangerate(\%myconfig, $form->{currency}, $form->{invdate}, $buysell);
855 $form->{exchangerate} = $form->{forex} if ($form->{forex});
860 for my $i (1 .. $form->{rowcount}) {
861 $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"});
863 my ($dec) = ($form->{"sellprice_$i"} =~ /\.(\d+)/);
865 my $decimalplaces = ($dec > 2) ? $dec : 2;
867 # copy delivery date from reqdate for order -> invoice conversion
868 $form->{"deliverydate_$i"} = $form->{"reqdate_$i"}
869 unless $form->{"deliverydate_$i"};
872 $form->{"sellprice_$i"} =
873 $form->format_amount(\%myconfig, $form->{"sellprice_$i"},
876 $form->{"lastcost_$i"} =
877 $form->format_amount(\%myconfig, $form->{"lastcost_$i"},
880 (my $dec_qty) = ($form->{"qty_$i"} =~ /\.(\d+)/);
881 $dec_qty = length $dec_qty;
883 $form->format_amount(\%myconfig, $form->{"qty_$i"}, $dec_qty);
889 $main::lxdebug->leave_sub();
893 $main::lxdebug->enter_sub();
895 my $form = $main::form;
896 my %myconfig = %main::myconfig;
897 my $locale = $main::locale;
900 $main::auth->assert($form->{type} eq 'sales_delivery_order' ? 'invoice_edit' : 'vendor_invoice_edit');
902 my @do_ids = map { $form->{"trans_id_$_"} } grep { $form->{"multi_id_$_"} } (1..$form->{rowcount});
904 if (!scalar @do_ids) {
905 $form->show_generic_error($locale->text('You have not selected any delivery order.'), 'back_button' => 1);
908 map { delete $form->{$_} } grep { m/^(?:trans|multi)_id_\d+/ } keys %{ $form };
910 if (!DO->retrieve('vc' => $form->{vc}, 'ids' => \@do_ids)) {
911 $form->show_generic_error($form->{vc} eq 'customer' ?
912 $locale->text('You cannot create an invoice for delivery orders for different customers.') :
913 $locale->text('You cannot create an invoice for delivery orders from different vendors.'),
917 my $source_type = $form->{type};
918 $form->{convert_from_do_ids} = join ' ', @do_ids;
919 # bei der auswahl von mehreren Lieferscheinen fuer eine Rechnung, die einfach in donumber_array
920 # zwischenspeichern (DO.pm) und als ' '-separierte Liste wieder zurueckschreiben
921 # Hinweis: delete gibt den wert zurueck und loescht danach das element (nett und einfach)
922 # $shell: perldoc perlunc; /delete EXPR
923 $form->{donumber} = delete $form->{donumber_array};
924 $form->{ordnumber} = delete $form->{ordnumber_array};
925 $form->{cusordnumber} = delete $form->{cusordnumber_array};
926 $form->{deliverydate} = $form->{transdate};
927 $form->{transdate} = $form->current_date(\%myconfig);
928 $form->{duedate} = $form->current_date(\%myconfig, $form->{invdate}, $form->{terms} * 1);
929 $form->{type} = "invoice";
931 $form->{defaultcurrency} = $form->get_default_currency(\%myconfig);
933 my ($script, $buysell);
934 if ($source_type eq 'purchase_delivery_order') {
935 $form->{title} = $locale->text('Add Vendor Invoice');
936 $form->{script} = 'ir.pl';
941 $form->{title} = $locale->text('Add Sales Invoice');
942 $form->{script} = 'is.pl';
947 map { delete $form->{$_} } qw(id subject message cc bcc printed emailed queued);
949 # get vendor or customer discount
951 my $saved_form = save_form();
952 if ($form->{vc} eq 'vendor') {
953 IR->get_vendor(\%myconfig, \%$form);
954 $vc_discount = $form->{vendor_discount};
956 IS->get_customer(\%myconfig, \%$form);
957 $vc_discount = $form->{customer_discount};
959 restore_form($saved_form);
961 $form->{rowcount} = 0;
962 foreach my $ref (@{ $form->{form_details} }) {
964 $ref->{reqdate} ||= $ref->{dord_transdate}; # copy transdates into each invoice row
965 map { $form->{"${_}_$form->{rowcount}"} = $ref->{$_} } keys %{ $ref };
966 map { $form->{"${_}_$form->{rowcount}"} = $form->format_amount(\%myconfig, $ref->{$_}) } qw(qty sellprice lastcost);
967 $form->{"converted_from_delivery_order_items_id_$form->{rowcount}"} = delete $form->{"delivery_order_items_id_$form->{rowcount}"};
969 if ($vc_discount){ # falls wir einen Lieferanten/Kundenrabatt haben
970 # und keinen anderen discount wert an $i ...
971 $form->{"discount_$form->{rowcount}"} ||= $vc_discount; # ... nehmen wir diesen Rabatt
974 $form->{"discount_$form->{rowcount}"} = $form->{"discount_$form->{rowcount}"} * 100; #s.a. Bug 1151
975 # Anm.: Eine Änderung des discounts in der SL/DO.pm->retrieve (select (doi.discount * 100) as discount) ergibt in psql einen
976 # Wert von 10.0000001490116. Ferner ist der Rabatt in der Rechnung dann bei 1.0 (?). Deswegen lasse ich das hier. jb 10.10.09
978 $form->{"discount_$form->{rowcount}"} = $form->format_amount(\%myconfig, $form->{"discount_$form->{rowcount}"});
980 delete $form->{form_details};
982 $locale = Locale->new("$myconfig{countrycode}", "$script");
984 require "bin/mozilla/$form->{script}";
991 $main::lxdebug->leave_sub();
995 $main::lxdebug->enter_sub();
999 my $form = $main::form;
1001 $form->{saveasnew} = 1;
1002 $form->{closed} = 0;
1003 $form->{delivered} = 0;
1004 map { delete $form->{$_} } qw(printed emailed queued);
1005 delete @{ $form }{ grep { m/^stock_(?:in|out)_\d+/ } keys %{ $form } };
1006 $form->{"converted_from_delivery_order_items_id_$_"} = delete $form->{"delivery_order_items_id_$_"} for 1 .. $form->{"rowcount"};
1007 # Let kivitendo assign a new order number if the user hasn't changed the
1008 # previous one. If it has been changed manually then use it as-is.
1009 $form->{donumber} =~ s/^\s*//g;
1010 $form->{donumber} =~ s/\s*$//g;
1011 if ($form->{saved_donumber} && ($form->{saved_donumber} eq $form->{donumber})) {
1012 delete($form->{donumber});
1017 $main::lxdebug->leave_sub();
1021 $main::lxdebug->enter_sub();
1025 $::form->mtime_ischanged('delivery_orders','mail');
1027 $::form->{print_and_save} = 1;
1029 my $saved_form = save_form();
1033 restore_form($saved_form, 0, qw(id ordnumber quonumber));
1037 $main::lxdebug->leave_sub();
1040 sub calculate_stock_in_out {
1041 $main::lxdebug->enter_sub();
1043 my $form = $main::form;
1047 if (!$form->{"id_${i}"}) {
1048 $main::lxdebug->leave_sub();
1052 my $all_units = AM->retrieve_all_units();
1054 my $in_out = $form->{type} =~ /^sales/ ? 'out' : 'in';
1055 my $sinfo = DO->unpack_stock_information('packed' => $form->{"stock_${in_out}_${i}"});
1057 my $do_qty = AM->sum_with_unit($::form->{"qty_$i"}, $::form->{"unit_$i"});
1058 my $sum = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $sinfo });
1059 my $matches = $do_qty == $sum;
1061 my $content = $form->format_amount_units('amount' => $sum * 1,
1062 'part_unit' => $form->{"partunit_$i"},
1063 'amount_unit' => $all_units->{$form->{"partunit_$i"}}->{base_unit},
1064 'conv_units' => 'convertible_not_smaller',
1066 $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="?">|;
1068 $main::lxdebug->leave_sub();
1073 sub get_basic_bin_wh_info {
1074 $main::lxdebug->enter_sub();
1076 my $stock_info = shift;
1078 my $form = $main::form;
1080 foreach my $sinfo (@{ $stock_info }) {
1081 next unless ($sinfo->{bin_id});
1083 my $bin_info = WH->get_basic_bin_info('id' => $sinfo->{bin_id});
1084 map { $sinfo->{"${_}_description"} = $sinfo->{"${_}description"} = $bin_info->{"${_}_description"} } qw(bin warehouse);
1087 $main::lxdebug->leave_sub();
1090 sub stock_in_out_form {
1091 $main::lxdebug->enter_sub();
1093 my $form = $main::form;
1095 if ($form->{in_out} eq 'out') {
1101 $main::lxdebug->leave_sub();
1104 sub redo_stock_info {
1105 $main::lxdebug->enter_sub();
1109 my $form = $main::form;
1111 my @non_empty = grep { $_->{qty} } @{ $params{stock_info} };
1113 if ($params{add_empty_row}) {
1115 'warehouse_id' => scalar(@non_empty) ? $non_empty[-1]->{warehouse_id} : undef,
1116 'bin_id' => scalar(@non_empty) ? $non_empty[-1]->{bin_id} : undef,
1120 @{ $params{stock_info} } = @non_empty;
1122 $main::lxdebug->leave_sub();
1125 sub update_stock_in {
1126 $main::lxdebug->enter_sub();
1128 my $form = $main::form;
1129 my %myconfig = %main::myconfig;
1131 my $stock_info = [];
1133 foreach my $i (1..$form->{rowcount}) {
1134 $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
1135 push @{ $stock_info }, { map { $_ => $form->{"${_}_${i}"} } qw(warehouse_id bin_id chargenumber
1136 bestbefore qty unit delivery_order_items_stock_id) };
1139 display_stock_in_form($stock_info);
1141 $main::lxdebug->leave_sub();
1145 $main::lxdebug->enter_sub();
1147 my $form = $main::form;
1149 my $stock_info = DO->unpack_stock_information('packed' => $form->{stock});
1151 display_stock_in_form($stock_info);
1153 $main::lxdebug->leave_sub();
1156 sub display_stock_in_form {
1157 $main::lxdebug->enter_sub();
1159 my $stock_info = shift;
1161 my $form = $main::form;
1162 my %myconfig = %main::myconfig;
1163 my $locale = $main::locale;
1165 $form->{title} = $locale->text('Stock');
1167 my $part_info = IC->get_basic_part_info('id' => $form->{parts_id});
1169 # Standardlagerplatz für Standard-Auslagern verwenden, falls keiner für die Ware explizit definiert wurde
1170 if ($::instance_conf->get_transfer_default_use_master_default_bin) {
1171 $part_info->{warehouse_id} ||= $::instance_conf->get_warehouse_id;
1172 $part_info->{bin_id} ||= $::instance_conf->get_bin_id;
1175 my $units = AM->retrieve_units(\%myconfig, $form);
1176 # der zweite Parameter von unit_select_data gibt den default-Namen (selected) vor
1177 my $units_data = AM->unit_select_data($units, $form->{do_unit}, undef, $part_info->{unit});
1179 $form->get_lists('warehouses' => { 'key' => 'WAREHOUSES',
1180 'bins' => 'BINS' });
1182 redo_stock_info('stock_info' => $stock_info, 'add_empty_row' => !$form->{delivered});
1184 get_basic_bin_wh_info($stock_info);
1186 $form->header(no_layout => 1);
1187 print $form->parse_html_template('do/stock_in_form', { 'UNITS' => $units_data,
1188 'STOCK_INFO' => $stock_info,
1189 'PART_INFO' => $part_info, });
1191 $main::lxdebug->leave_sub();
1194 sub _stock_in_out_set_qty_display {
1195 my $stock_info = shift;
1197 my $all_units = AM->retrieve_all_units();
1198 my $sum = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
1199 $form->{qty_display} = $form->format_amount_units(amount => $sum * 1,
1200 part_unit => $form->{partunit},
1201 amount_unit => $all_units->{ $form->{partunit} }->{base_unit},
1202 conv_units => 'convertible_not_smaller',
1207 $main::lxdebug->enter_sub();
1209 my $form = $main::form;
1210 my %myconfig = %main::myconfig;
1212 my $stock_info = [];
1214 foreach my $i (1..$form->{rowcount}) {
1215 $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
1217 next if ($form->{"qty_$i"} <= 0);
1219 push @{ $stock_info }, { map { $_ => $form->{"${_}_${i}"} } qw(delivery_order_items_stock_id warehouse_id bin_id chargenumber bestbefore qty unit) };
1222 $form->{stock} = YAML::Dump($stock_info);
1224 _stock_in_out_set_qty_display($stock_info);
1226 my $do_qty = AM->sum_with_unit($::form->parse_amount(\%::myconfig, $::form->{do_qty}), $::form->{do_unit});
1227 my $transfer_qty = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
1230 print $form->parse_html_template('do/set_stock_in_out', {
1231 qty_matches => $do_qty == $transfer_qty,
1234 $main::lxdebug->leave_sub();
1237 sub stock_out_form {
1238 $main::lxdebug->enter_sub();
1240 my $form = $main::form;
1241 my %myconfig = %main::myconfig;
1242 my $locale = $main::locale;
1244 $form->{title} = $locale->text('Release From Stock');
1246 my $part_info = IC->get_basic_part_info('id' => $form->{parts_id});
1248 my $units = AM->retrieve_units(\%myconfig, $form);
1249 my $units_data = AM->unit_select_data($units, undef, undef, $part_info->{unit});
1251 my @contents = DO->get_item_availability('parts_id' => $form->{parts_id});
1253 my $stock_info = DO->unpack_stock_information('packed' => $form->{stock});
1255 if (!$form->{delivered}) {
1256 foreach my $row (@contents) {
1257 $row->{available_qty} = $form->format_amount_units('amount' => $row->{qty} * 1,
1258 'part_unit' => $part_info->{unit},
1259 'conv_units' => 'convertible_not_smaller',
1262 foreach my $sinfo (@{ $stock_info }) {
1263 next if (($row->{bin_id} != $sinfo->{bin_id}) ||
1264 ($row->{warehouse_id} != $sinfo->{warehouse_id}) ||
1265 ($row->{chargenumber} ne $sinfo->{chargenumber}) ||
1266 ($row->{bestbefore} ne $sinfo->{bestbefore}));
1268 map { $row->{"stock_$_"} = $sinfo->{$_} } qw(qty unit error delivery_order_items_stock_id);
1273 get_basic_bin_wh_info($stock_info);
1275 foreach my $sinfo (@{ $stock_info }) {
1276 map { $sinfo->{"stock_$_"} = $sinfo->{$_} } qw(qty unit);
1280 $form->header(no_layout => 1);
1281 print $form->parse_html_template('do/stock_out_form', { 'UNITS' => $units_data,
1282 'WHCONTENTS' => $form->{delivered} ? $stock_info : \@contents,
1283 'PART_INFO' => $part_info, });
1285 $main::lxdebug->leave_sub();
1289 $main::lxdebug->enter_sub();
1291 my $form = $main::form;
1292 my %myconfig = %main::myconfig;
1293 my $locale = $main::locale;
1295 my $stock_info = [];
1297 foreach my $i (1 .. $form->{rowcount}) {
1298 $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
1300 next if ($form->{"qty_$i"} <= 0);
1302 push @{ $stock_info }, {
1303 'warehouse_id' => $form->{"warehouse_id_$i"},
1304 'bin_id' => $form->{"bin_id_$i"},
1305 'chargenumber' => $form->{"chargenumber_$i"},
1306 'bestbefore' => $form->{"bestbefore_$i"},
1307 'qty' => $form->{"qty_$i"},
1308 'unit' => $form->{"unit_$i"},
1310 'delivery_order_items_stock_id' => $form->{"delivery_order_items_stock_id_$i"},
1314 my @errors = DO->check_stock_availability('requests' => $stock_info,
1315 'parts_id' => $form->{parts_id});
1317 $form->{stock} = YAML::Dump($stock_info);
1320 $form->{ERRORS} = [];
1321 map { push @{ $form->{ERRORS} }, $locale->text('Error in row #1: The quantity you entered is bigger than the stocked quantity.', $_->{row}); } @errors;
1322 stock_in_out_form();
1325 _stock_in_out_set_qty_display($stock_info);
1327 my $do_qty = AM->sum_with_unit($::form->parse_amount(\%::myconfig, $::form->{do_qty}), $::form->{do_unit});
1328 my $transfer_qty = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
1331 print $form->parse_html_template('do/set_stock_in_out', {
1332 qty_matches => $do_qty == $transfer_qty,
1336 $main::lxdebug->leave_sub();
1340 $main::lxdebug->enter_sub();
1342 my $form = $main::form;
1343 my %myconfig = %main::myconfig;
1344 my $locale = $main::locale;
1346 if ($form->{id} && DO->is_marked_as_delivered(id => $form->{id})) {
1347 $form->show_generic_error($locale->text('The parts for this delivery order have already been transferred in.'), 'back_button' => 1);
1350 save(no_redirect => 1);
1352 my @part_ids = map { $form->{"id_${_}"} } grep { $form->{"id_${_}"} && $form->{"stock_in_${_}"} } (1 .. $form->{rowcount});
1356 my $units = AM->retrieve_units(\%myconfig, $form);
1357 my %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
1360 $form->{ERRORS} = [];
1362 foreach my $i (1 .. $form->{rowcount}) {
1363 next unless ($form->{"id_$i"} && $form->{"stock_in_$i"});
1365 my $row_sum_base_qty = 0;
1366 my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
1368 foreach my $request (@{ DO->unpack_stock_information('packed' => $form->{"stock_in_$i"}) }) {
1369 $request->{parts_id} = $form->{"id_$i"};
1370 $row_sum_base_qty += $request->{qty} * $units->{$request->{unit}}->{factor} / $base_unit_factor;
1372 $request->{project_id} = $form->{"project_id_$i"} || $form->{globalproject_id};
1374 push @all_requests, $request;
1377 next if (0 == $row_sum_base_qty);
1379 my $do_base_qty = $form->parse_amount(\%myconfig, $form->{"qty_$i"}) * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
1381 # if ($do_base_qty != $row_sum_base_qty) {
1382 # push @{ $form->{ERRORS} }, $locale->text('Error in position #1: You must either assign no stock at all or the full quantity of #2 #3.',
1383 # $i, $form->{"qty_$i"}, $form->{"unit_$i"});
1387 if (@{ $form->{ERRORS} }) {
1388 push @{ $form->{ERRORS} }, $locale->text('The delivery order has not been marked as delivered. The warehouse contents have not changed.');
1390 set_headings('edit');
1392 $main::lxdebug->leave_sub();
1398 DO->transfer_in_out('direction' => 'in',
1399 'requests' => \@all_requests);
1401 SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
1403 $form->{callback} = 'do.pl?action=edit&type=purchase_delivery_order&id=' . $form->escape($form->{id});
1406 $main::lxdebug->leave_sub();
1410 $main::lxdebug->enter_sub();
1412 my $form = $main::form;
1413 my %myconfig = %main::myconfig;
1414 my $locale = $main::locale;
1416 if ($form->{id} && DO->is_marked_as_delivered(id => $form->{id})) {
1417 $form->show_generic_error($locale->text('The parts for this delivery order have already been transferred out.'), 'back_button' => 1);
1420 save(no_redirect => 1);
1422 my @part_ids = map { $form->{"id_${_}"} } grep { $form->{"id_${_}"} && $form->{"stock_out_${_}"} } (1 .. $form->{rowcount});
1426 my $units = AM->retrieve_units(\%myconfig, $form);
1427 my %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
1430 $form->{ERRORS} = [];
1432 foreach my $i (1 .. $form->{rowcount}) {
1433 next unless ($form->{"id_$i"} && $form->{"stock_out_$i"});
1435 my $row_sum_base_qty = 0;
1436 my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
1438 foreach my $request (@{ DO->unpack_stock_information('packed' => $form->{"stock_out_$i"}) }) {
1439 $request->{parts_id} = $form->{"id_$i"};
1440 $request->{base_qty} = $request->{qty} * $units->{$request->{unit}}->{factor} / $base_unit_factor;
1441 $request->{project_id} = $form->{"project_id_$i"} ? $form->{"project_id_$i"} : $form->{globalproject_id};
1443 my $map_key = join '--', ($form->{"id_$i"}, @{$request}{qw(warehouse_id bin_id chargenumber bestbefore)});
1445 $request_map{$map_key} ||= $request;
1446 $request_map{$map_key}->{sum_base_qty} ||= 0;
1447 $request_map{$map_key}->{sum_base_qty} += $request->{base_qty};
1448 $row_sum_base_qty += $request->{base_qty};
1450 push @all_requests, $request;
1453 next if (0 == $row_sum_base_qty);
1455 my $do_base_qty = $form->{"qty_$i"} * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
1457 # if ($do_base_qty != $row_sum_base_qty) {
1458 # push @{ $form->{ERRORS} }, $locale->text('Error in position #1: You must either assign no transfer at all or the full quantity of #2 #3.',
1459 # $i, $form->{"qty_$i"}, $form->{"unit_$i"});
1464 my @bin_ids = map { $_->{bin_id} } values %request_map;
1465 my %bin_info_map = WH->get_basic_bin_info('id' => \@bin_ids);
1466 my @contents = DO->get_item_availability('parts_id' => \@part_ids);
1468 foreach my $inv (@contents) {
1469 my $map_key = join '--', @{$inv}{qw(parts_id warehouse_id bin_id chargenumber bestbefore)};
1471 next unless ($request_map{$map_key});
1473 my $request = $request_map{$map_key};
1474 $request->{ok} = $request->{sum_base_qty} <= $inv->{qty};
1477 foreach my $request (values %request_map) {
1478 next if ($request->{ok});
1480 my $pinfo = $part_info_map{$request->{parts_id}};
1481 my $binfo = $bin_info_map{$request->{bin_id}};
1483 if ($::instance_conf->get_show_bestbefore) {
1484 push @{ $form->{ERRORS} }, $locale->text("There is not enough available of '#1' at warehouse '#2', bin '#3', #4, #5, for the transfer of #6.",
1485 $pinfo->{description},
1486 $binfo->{warehouse_description},
1487 $binfo->{bin_description},
1488 $request->{chargenumber} ? $locale->text('chargenumber #1', $request->{chargenumber}) : $locale->text('no chargenumber'),
1489 $request->{bestbefore} ? $locale->text('bestbefore #1', $request->{bestbefore}) : $locale->text('no bestbefore'),
1490 $form->format_amount_units('amount' => $request->{sum_base_qty},
1491 'part_unit' => $pinfo->{unit},
1492 'conv_units' => 'convertible_not_smaller'));
1494 push @{ $form->{ERRORS} }, $locale->text("There is not enough available of '#1' at warehouse '#2', bin '#3', #4, for the transfer of #5.",
1495 $pinfo->{description},
1496 $binfo->{warehouse_description},
1497 $binfo->{bin_description},
1498 $request->{chargenumber} ? $locale->text('chargenumber #1', $request->{chargenumber}) : $locale->text('no chargenumber'),
1499 $form->format_amount_units('amount' => $request->{sum_base_qty},
1500 'part_unit' => $pinfo->{unit},
1501 'conv_units' => 'convertible_not_smaller'));
1506 if (@{ $form->{ERRORS} }) {
1507 push @{ $form->{ERRORS} }, $locale->text('The delivery order has not been marked as delivered. The warehouse contents have not changed.');
1509 set_headings('edit');
1511 $main::lxdebug->leave_sub();
1516 DO->transfer_in_out('direction' => 'out',
1517 'requests' => \@all_requests);
1519 SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
1521 $form->{callback} = 'do.pl?action=edit&type=sales_delivery_order&id=' . $form->escape($form->{id});
1524 $main::lxdebug->leave_sub();
1528 $main::lxdebug->enter_sub();
1530 my $form = $main::form;
1532 DO->close_orders('ids' => [ $form->{id} ]);
1534 $form->{closed} = 1;
1538 $main::lxdebug->leave_sub();
1543 call_sub($main::form->{yes_nextsub});
1547 call_sub($main::form->{no_nextsub});
1551 call_sub($main::form->{update_nextsub} || $main::form->{nextsub} || 'update_delivery_order');
1555 my $form = $main::form;
1556 my $locale = $main::locale;
1558 foreach my $action (qw(update ship_to print e_mail save transfer_out transfer_out_default sort
1559 transfer_in transfer_in_default mark_closed save_as_new invoice delete)) {
1560 if ($form->{"action_${action}"}) {
1566 $form->error($locale->text('No action defined.'));
1569 sub transfer_out_default {
1570 $main::lxdebug->enter_sub();
1572 my $form = $main::form;
1574 transfer_in_out_default('direction' => 'out');
1576 $main::lxdebug->leave_sub();
1579 sub transfer_in_default {
1580 $main::lxdebug->enter_sub();
1582 my $form = $main::form;
1584 transfer_in_out_default('direction' => 'in');
1586 $main::lxdebug->leave_sub();
1589 # Falls das Standardlagerverfahren aktiv ist, wird
1590 # geprüft, ob alle Standardlagerplätze für die Auslager-
1591 # artikel vorhanden sind UND ob die Warenmenge ausreicht zum
1592 # Auslagern. Falls nicht wird entsprechend eine Fehlermeldung
1593 # generiert. Offen Chargennummer / bestbefore wird nicht berücksichtigt
1594 sub transfer_in_out_default {
1595 $main::lxdebug->enter_sub();
1597 my $form = $main::form;
1598 my %myconfig = %main::myconfig;
1599 my $locale = $main::locale;
1602 my (%missing_default_bins, %qty_parts, @all_requests, %part_info_map, $default_warehouse_id, $default_bin_id);
1604 Common::check_params(\%params, qw(direction));
1606 # entsprechende defaults holen, falls standardlagerplatz verwendet werden soll
1607 if ($::instance_conf->get_transfer_default_use_master_default_bin) {
1608 $default_warehouse_id = $::instance_conf->get_warehouse_id;
1609 $default_bin_id = $::instance_conf->get_bin_id;
1613 my @part_ids = map { $form->{"id_${_}"} } (1 .. $form->{rowcount});
1615 my $units = AM->retrieve_units(\%myconfig, $form);
1616 %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
1617 foreach my $i (1 .. $form->{rowcount}) {
1618 next unless ($form->{"id_$i"});
1619 my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
1620 my $qty = $form->parse_amount(\%myconfig, $form->{"qty_$i"}) * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
1622 $form->show_generic_error($locale->text("Cannot transfer negative entries." ), 'back_button' => 1) if ($qty < 0);
1623 # if we do not want to transfer services and this part is a service, set qty to zero
1624 # ... and do not create a hash entry in %qty_parts below (will skip check for bins for the transfer == out case)
1625 # ... 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)
1627 $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});
1628 $qty_parts{$form->{"id_$i"}} += $qty;
1630 delete $qty_parts{$form->{"id_$i"}} unless $qty_parts{$form->{"id_$i"}};
1631 undef $form->{"stock_in_$i"};
1634 $part_info_map{$form->{"id_$i"}}{bin_id} ||= $default_bin_id;
1635 $part_info_map{$form->{"id_$i"}}{warehouse_id} ||= $default_warehouse_id;
1637 push @all_requests, ($qty == 0) ? { } : {
1638 'chargenumber' => '', #?? die müsste entsprechend geholt werden
1639 #'bestbefore' => undef, # TODO wird nicht berücksichtigt
1640 'bin_id' => $part_info_map{$form->{"id_$i"}}{bin_id},
1642 'parts_id' => $form->{"id_$i"},
1643 'comment' => $locale->text("Default transfer delivery order"),
1644 'unit' => $part_info_map{$form->{"id_$i"}}{unit},
1645 'warehouse_id' => $part_info_map{$form->{"id_$i"}}{warehouse_id},
1646 'oe_id' => $form->{id},
1647 'project_id' => $form->{"project_id_$i"} ? $form->{"project_id_$i"} : $form->{globalproject_id}
1651 # jetzt wird erst überprüft, ob die Stückzahl entsprechend stimmt.
1652 # check if bin (transfer in and transfer out and qty (transfer out) is correct
1653 foreach my $key (keys %qty_parts) {
1655 $missing_default_bins{$key}{missing_bin} = 1 unless ($part_info_map{$key}{bin_id});
1656 next unless ($part_info_map{$key}{bin_id}); # abbruch
1658 if ($params{direction} eq 'out') { # wird nur für ausgehende Mengen benötigt
1659 my ($max_qty, $error) = WH->get_max_qty_parts_bin(parts_id => $key, bin_id => $part_info_map{$key}{bin_id});
1661 # wir können nicht entscheiden, welche charge oder mhd (bestbefore) ausgewählt sein soll
1662 # deshalb rückmeldung nach oben geben, manuell auszulagern
1663 # TODO Bei nur einem Treffer mit Charge oder bestbefore wäre das noch möglich
1664 $missing_default_bins{$key}{chargenumber} = 1;
1666 if ($max_qty < $qty_parts{$key}){
1667 $missing_default_bins{$key}{missing_qty} = $max_qty - $qty_parts{$key};
1673 # Abfrage für Fehlerbehandlung (nur bei direction == out)
1674 if (scalar (keys %missing_default_bins)) {
1676 foreach my $fehler (keys %missing_default_bins) {
1678 my $ware = WH->get_part_description(parts_id => $fehler);
1679 if ($missing_default_bins{$fehler}{missing_bin}){
1680 $fehlertext .= "Kein Standardlagerplatz definiert bei $ware <br>";
1682 if ($missing_default_bins{$fehler}{missing_qty}) { # missing_qty
1683 $fehlertext .= "Es fehlen " . $missing_default_bins{$fehler}{missing_qty}*-1 .
1684 " von $ware auf dem Standard-Lagerplatz " . $part_info_map{$fehler}{bin} . " zum Auslagern<br>";
1686 if ($missing_default_bins{$fehler}{chargenumber}){
1687 $fehlertext .= "Die Ware hat eine Chargennummer oder eine Mindesthaltbarkeit definiert.
1688 Hier kann man nicht automatisch entscheiden.
1689 Bitte diesen Lieferschein manuell auslagern.
1692 # auslagern soll immer gehen, auch wenn nicht genügend auf lager ist.
1693 # der lagerplatz ist hier extra konfigurierbar, bspw. Lager-Korrektur mit
1694 # Lagerplatz Lagerplatz-Korrektur
1695 my $default_warehouse_id_ignore_onhand = $::instance_conf->get_warehouse_id_ignore_onhand;
1696 my $default_bin_id_ignore_onhand = $::instance_conf->get_bin_id_ignore_onhand;
1697 if ($::instance_conf->get_transfer_default_ignore_onhand && $default_bin_id_ignore_onhand) {
1698 # entsprechende defaults holen
1699 # falls chargenumber, bestbefore oder anzahl nicht stimmt, auf automatischen
1700 # lagerplatz wegbuchen!
1701 foreach (@all_requests) {
1702 if ($_->{parts_id} eq $fehler){
1703 $_->{bin_id} = $default_bin_id_ignore_onhand;
1704 $_->{warehouse_id} = $default_warehouse_id_ignore_onhand;
1708 #$main::lxdebug->message(0, 'Fehlertext: ' . $fehlertext);
1709 $form->show_generic_error($locale->text("Cannot transfer. <br> Reason:<br>#1", $fehlertext ), 'back_button' => 1);
1715 # hier der eigentliche fallunterschied für in oder out
1716 my $prefix = $params{direction} eq 'in' ? 'in' : 'out';
1718 # dieser array_ref ist für DO->save da:
1719 # einmal die all_requests in YAML verwandeln, damit delivery_order_items_stock
1720 # gefüllt werden kann.
1721 # could be dumped to the form in the first loop,
1722 # but maybe bin_id and warehouse_id has changed to the "korrekturlager" with
1723 # allowed negative qty ($::instance_conf->get_warehouse_id_ignore_onhand) ...
1725 foreach (@all_requests){
1727 next unless scalar(%{ $_ });
1728 $form->{"stock_${prefix}_$i"} = YAML::Dump([$_]);
1731 save(no_redirect => 1); # Wir können auslagern, deshalb beleg speichern
1732 # und in delivery_order_items_stock speichern
1734 # ... and fill back the persistent dois_id for inventory fk
1735 undef (@all_requests);
1736 foreach my $i (1 .. $form->{rowcount}) {
1737 next unless ($form->{"id_$i"} && $form->{"stock_${prefix}_$i"});
1738 push @all_requests, @{ DO->unpack_stock_information('packed' => $form->{"stock_${prefix}_$i"}) };
1740 DO->transfer_in_out('direction' => $prefix,
1741 'requests' => \@all_requests);
1743 SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
1745 $form->{callback} = 'do.pl?action=edit&type=sales_delivery_order&id=' . $form->escape($form->{id}) if $params{direction} eq 'out';
1746 $form->{callback} = 'do.pl?action=edit&type=purchase_delivery_order&id=' . $form->escape($form->{id}) if $params{direction} eq 'in';
1752 $main::lxdebug->enter_sub();
1756 my $form = $main::form;
1759 croak ("Delivery Order needs to be saved") unless $form->{id};
1761 # hashify partnumbers, positions. key is delivery_order_items_id
1762 for my $i (1 .. ($form->{rowcount}) ) {
1763 $temp_hash{$form->{"delivery_order_items_id_$i"}} = { runningnumber => $form->{"runningnumber_$i"}, partnumber => $form->{"partnumber_$i"} };
1765 # naturally sort partnumbers and get a sorted array of doi_ids
1766 my @sorted_doi_ids = sort { Sort::Naturally::ncmp($temp_hash{$a}->{"partnumber"}, $temp_hash{$b}->{"partnumber"}) } keys %temp_hash;
1771 for (@sorted_doi_ids) {
1772 $form->{"runningnumber_$temp_hash{$_}->{runningnumber}"} = $new_number;
1775 $main::lxdebug->leave_sub();
1787 do.pl - Script for all calls to delivery order
1796 Sorts all position with Natural Sort. Can be activated in form_footer.html like this
1797 C<E<lt>input class="submit" type="submit" name="action_sort" id="sort_button" value="[% 'Sort and Save' | $T8 %]"E<gt>>
1803 Sort and Save can be implemented as an optional button if configuration ca be set by client config.
1804 Example coding for database scripts and templates in (git show af2f24b8), check also
1805 autogeneration for rose (scripts/rose_auto_create_model.pl --h)