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 $form->{webdav} = $::instance_conf->get_webdav;
178 my $editing = $form->{id};
180 DO->retrieve('vc' => $form->{vc},
181 'ids' => $form->{id});
183 $form->backup_vars(qw(payment_id language_id taxzone_id salesman_id taxincluded cp_id intnotes delivery_term_id currency));
185 # get customer / vendor
186 if ($form->{vc} eq 'vendor') {
187 IR->get_vendor(\%myconfig, \%$form);
188 $form->{discount} = $form->{vendor_discount};
190 IS->get_customer(\%myconfig, \%$form);
191 $form->{discount} = $form->{customer_discount};
194 $form->restore_vars(qw(payment_id language_id taxzone_id intnotes cp_id delivery_term_id));
195 $form->restore_vars(qw(currency)) if ($form->{id} || $form->{convert_from_oe_ids});
196 $form->restore_vars(qw(taxincluded)) if $form->{id};
197 $form->restore_vars(qw(salesman_id)) if $editing;
199 if ($form->{"all_$form->{vc}"}) {
200 unless ($form->{"$form->{vc}_id"}) {
201 $form->{"$form->{vc}_id"} = $form->{"all_$form->{vc}"}->[0]->{id};
205 ($form->{ $form->{vc} }) = split /--/, $form->{ $form->{vc} };
206 $form->{"old$form->{vc}"} = qq|$form->{$form->{vc}}--$form->{"$form->{vc}_id"}|;
208 $form->{employee} = "$form->{employee}--$form->{employee_id}";
210 $main::lxdebug->leave_sub();
214 $main::lxdebug->enter_sub();
218 my $form = $main::form;
219 my %myconfig = %main::myconfig;
221 $form->{formname} = $form->{type} unless $form->{formname};
224 foreach my $ref (@{ $form->{form_details} }) {
225 $form->{rowcount} = ++$i;
227 map { $form->{"${_}_$i"} = $ref->{$_} } keys %{$ref};
229 for my $i (1 .. $form->{rowcount}) {
231 $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"} * 100);
233 $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"});
235 my ($dec) = ($form->{"sellprice_$i"} =~ /\.(\d+)/);
237 my $decimalplaces = ($dec > 2) ? $dec : 2;
239 # copy reqdate from deliverydate for invoice -> order conversion
240 $form->{"reqdate_$i"} = $form->{"deliverydate_$i"} unless $form->{"reqdate_$i"};
242 $form->{"sellprice_$i"} = $form->format_amount(\%myconfig, $form->{"sellprice_$i"}, $decimalplaces);
243 $form->{"lastcost_$i"} = $form->format_amount(\%myconfig, $form->{"lastcost_$i"}, $decimalplaces);
245 (my $dec_qty) = ($form->{"qty_$i"} =~ /\.(\d+)/);
246 $dec_qty = length $dec_qty;
247 $form->{"qty_$i"} = $form->format_amount(\%myconfig, $form->{"qty_$i"}, $dec_qty);
250 $main::lxdebug->leave_sub();
254 $main::lxdebug->enter_sub();
258 my $form = $main::form;
259 my %myconfig = %main::myconfig;
261 $form->{employee_id} = $form->{old_employee_id} if $form->{old_employee_id};
262 $form->{salesman_id} = $form->{old_salesman_id} if $form->{old_salesman_id};
264 my $vc = $form->{vc} eq "customer" ? "customers" : "vendors";
265 $form->get_lists($vc => "ALL_VC",
266 "price_factors" => "ALL_PRICE_FACTORS",
267 "departments" => "ALL_DEPARTMENTS",
268 "business_types" => "ALL_BUSINESS_TYPES",
272 my @old_project_ids = uniq grep { $_ } map { $_ * 1 } ($form->{"globalproject_id"}, map { $form->{"project_id_$_"} } 1..$form->{"rowcount"});
273 my @old_ids_cond = @old_project_ids ? (id => \@old_project_ids) : ();
275 if (($vc eq 'customers') && $::instance_conf->get_customer_projects_only_in_sales) {
278 customer_id => $::form->{customer_id},
279 billable_customer_id => $::form->{customer_id},
284 and => [ active => 1, @customer_cond ],
288 $::form->{ALL_PROJECTS} = SL::DB::Manager::Project->get_all_sorted(query => \@conditions);
289 $::form->{ALL_EMPLOYEES} = SL::DB::Manager::Employee->get_all_sorted(query => [ or => [ id => $::form->{employee_id}, deleted => 0 ] ]);
290 $::form->{ALL_SALESMEN} = SL::DB::Manager::Employee->get_all_sorted(query => [ or => [ id => $::form->{salesman_id}, deleted => 0 ] ]);
291 $::form->{ALL_SHIPTO} = SL::DB::Manager::Shipto->get_all_sorted(query => [
292 or => [ trans_id => $::form->{"$::form->{vc}_id"} * 1, and => [ shipto_id => $::form->{shipto_id} * 1, trans_id => undef ] ]
294 $::form->{ALL_CONTACTS} = SL::DB::Manager::Contact->get_all_sorted(query => [
296 cp_cv_id => $::form->{"$::form->{vc}_id"} * 1,
299 cp_id => $::form->{cp_id} * 1
304 map { $_->{value} = "$_->{description}--$_->{id}" } @{ $form->{ALL_DEPARTMENTS} };
305 map { $_->{value} = "$_->{name}--$_->{id}" } @{ $form->{ALL_VC} };
307 $form->{SHOW_VC_DROP_DOWN} = $myconfig{vclimit} > scalar @{ $form->{ALL_VC} };
309 $form->{oldvcname} = $form->{"old$form->{vc}"};
310 $form->{oldvcname} =~ s/--.*//;
312 my $dispatch_to_popup = '';
313 if ($form->{resubmit} && ($form->{format} eq "html")) {
314 $dispatch_to_popup = "window.open('about:blank','Beleg'); document.do.target = 'Beleg';";
315 $dispatch_to_popup .= "document.do.submit();";
316 } elsif ($form->{resubmit}) {
317 # emulate click for resubmitting actions
318 $dispatch_to_popup = "document.do.${_}.click(); " for grep { /^action_/ } keys %$form;
320 $::request->{layout}->add_javascripts_inline("\$(function(){$dispatch_to_popup});");
323 my $follow_up_vc = $form->{ $form->{vc} eq 'customer' ? 'customer' : 'vendor' };
324 $follow_up_vc =~ s/--\d*\s*$//;
326 $form->{follow_up_trans_info} = $form->{donumber} .'('. $follow_up_vc .')';
328 $::request->{layout}->use_javascript(map { "${_}.js" } qw(kivi.SalesPurchase ckeditor/ckeditor ckeditor/adapters/jquery kivi.io autocomplete_customer autocomplete_part));
331 # Fix für Bug 1082 Erwartet wird: 'abteilungsNAME--abteilungsID'
332 # und Erweiterung für Bug 1760:
333 # Das war leider nur ein Teil-Fix, da das Verhalten den 'Erneuern'-Knopf
334 # nicht überlebt. Konsequent jetzt auf L umgestellt
335 # $ perldoc SL::Template::Plugin::L
336 # Daher entsprechend nur die Anpassung in form_header
337 # und in DO.pm gemacht. 4 Testfälle:
338 # department_id speichern | i.O.
339 # department_id lesen | i.O.
340 # department leer überlebt erneuern | i.O.
341 # department nicht leer überlebt erneuern | i.O.
342 # $main::lxdebug->message(0, 'ABTEILUNGS ID in form?' . $form->{department_id});
343 print $form->parse_html_template('do/form_header');
345 $main::lxdebug->leave_sub();
349 $main::lxdebug->enter_sub();
353 my $form = $main::form;
355 $form->{PRINT_OPTIONS} = print_options('inline' => 1);
356 $form->{ALL_DELIVERY_TERMS} = SL::DB::Manager::DeliveryTerm->get_all_sorted();
358 print $form->parse_html_template('do/form_footer',
359 {transfer_default => ($::instance_conf->get_transfer_default)});
361 $main::lxdebug->leave_sub();
364 sub update_delivery_order {
365 $main::lxdebug->enter_sub();
369 my $form = $main::form;
370 my %myconfig = %main::myconfig;
372 set_headings($form->{"id"} ? "edit" : "add");
374 $form->{insertdate} = SL::DB::DeliveryOrder->new(id => $form->{id})->load->itime_as_date if $form->{id};
379 $payment_id = $form->{payment_id} if $form->{payment_id};
381 check_name($form->{vc});
382 $form->{discount} = $form->{"$form->{vc}_discount"} if defined $form->{"$form->{vc}_discount"};
383 # Problem: Wenn man ohne Erneuern einen Kunden/Lieferanten
384 # wechselt, wird der entsprechende Kunden/ Lieferantenrabatt
385 # nicht übernommen. Grundproblem: In Commit 82574e78
386 # hab ich aus discount customer_discount und vendor_discount
387 # gemacht und entsprechend an den Oberflächen richtig hin-
388 # geschoben. Die damals bessere Lösung wäre gewesen:
389 # In den Templates nur die hidden für form-discount wieder ein-
390 # setzen dann wäre die Verrenkung jetzt nicht notwendig.
391 # TODO: Ggf. Bugfix 1284, 1575 und 817 wieder zusammenführen
392 # Testfälle: Kunden mit Rabatt 0 -> Rabatt 20 i.O.
393 # Kunde mit Rabatt 20 -> Rabatt 0 i.O.
394 # Kunde mit Rabatt 20 -> Rabatt 5,5 i.O.
395 $form->{payment_id} = $payment_id if $form->{payment_id} eq "";
397 my $i = $form->{rowcount};
399 if ( ($form->{"partnumber_$i"} eq "")
400 && ($form->{"description_$i"} eq "")
401 && ($form->{"partsgroup_$i"} eq "")) {
408 if ($form->{type} eq 'purchase_delivery_order') {
409 IR->retrieve_item(\%myconfig, $form);
412 IS->retrieve_item(\%myconfig, $form);
416 my $rows = scalar @{ $form->{item_list} };
419 $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
420 if( !$form->{"qty_$i"} ) {
421 $form->{"qty_$i"} = 1;
426 select_item(mode => $mode, pre_entered_qty => $form->{"qty_$i"});
431 my $sellprice = $form->parse_amount(\%myconfig, $form->{"sellprice_$i"});
433 map { $form->{"${_}_$i"} = $form->{item_list}[0]{$_} } keys %{ $form->{item_list}[0] };
435 $form->{"marge_price_factor_$i"} = $form->{item_list}->[0]->{price_factor};
438 $form->{"sellprice_$i"} = $sellprice;
440 my $record = _make_record();
441 my $price_source = SL::PriceSource->new(record_item => $record->items->[$i-1], record => $record);
442 my $best_price = $price_source->best_price;
443 my $best_discount = $price_source->best_discount;
446 $::form->{"sellprice_$i"} = $best_price->price;
447 $::form->{"active_price_source_$i"} = $best_price->source;
449 if ($best_discount) {
450 $::form->{"discount_$i"} = $best_discount->discount;
451 $::form->{"active_discount_source_$i"} = $best_discount->source;
455 $form->{"sellprice_$i"} = $form->format_amount(\%myconfig, $form->{"sellprice_$i"});
456 $form->{"lastcost_$i"} = $form->format_amount(\%myconfig, $form->{"lastcost_$i"});
457 $form->{"qty_$i"} = $form->format_amount(\%myconfig, $form->{"qty_$i"});
458 $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"} * 100.0);
465 # ok, so this is a new part
466 # ask if it is a part or service item
468 if ( $form->{"partsgroup_$i"}
469 && ($form->{"partsnumber_$i"} eq "")
470 && ($form->{"description_$i"} eq "")) {
472 $form->{"discount_$i"} = "";
473 $form->{"not_discountable_$i"} = "";
477 $form->{"id_$i"} = 0;
483 $main::lxdebug->leave_sub();
487 $main::lxdebug->enter_sub();
491 my $form = $main::form;
492 my %myconfig = %main::myconfig;
493 my $locale = $main::locale;
495 $form->{vc} = $form->{type} eq 'purchase_delivery_order' ? 'vendor' : 'customer';
497 $form->get_lists("projects" => { "key" => "ALL_PROJECTS",
499 "departments" => "ALL_DEPARTMENTS",
500 "$form->{vc}s" => "ALL_VC",
501 "business_types" => "ALL_BUSINESS_TYPES");
502 $form->{ALL_EMPLOYEES} = SL::DB::Manager::Employee->get_all_sorted(query => [ deleted => 0 ]);
504 $form->{SHOW_VC_DROP_DOWN} = $myconfig{vclimit} > scalar @{ $form->{ALL_VC} };
505 $form->{title} = $locale->text('Delivery Orders');
509 print $form->parse_html_template('do/search');
511 $main::lxdebug->leave_sub();
515 $main::lxdebug->enter_sub();
519 my $form = $main::form;
520 my %myconfig = %main::myconfig;
521 my $locale = $main::locale;
522 my $cgi = $::request->{cgi};
524 $form->{department_id} = (split /--/, $form->{department})[-1];
525 ($form->{ $form->{vc} }, $form->{"$form->{vc}_id"}) = split(/--/, $form->{ $form->{vc} });
527 report_generator_set_default_sort('transdate', 1);
531 $form->{rowcount} = scalar @{ $form->{DO} };
534 ids transdate reqdate
536 ordnumber customernumber cusordnumber
537 name employee salesman
538 shipvia globalprojectnumber
539 transaction_description department
544 $form->{l_open} = $form->{l_closed} = "Y" if ($form->{open} && $form->{closed});
545 $form->{l_delivered} = "Y" if ($form->{delivered} && $form->{notdelivered});
547 $form->{title} = $locale->text('Delivery Orders');
549 my $attachment_basename = $form->{vc} eq 'vendor' ? $locale->text('purchase_delivery_order_list') : $locale->text('sales_delivery_order_list');
551 my $report = SL::ReportGenerator->new(\%myconfig, $form);
553 my @hidden_variables = map { "l_${_}" } @columns;
554 push @hidden_variables, $form->{vc}, qw(l_closed l_notdelivered open closed delivered notdelivered donumber ordnumber serialnumber cusordnumber
555 transaction_description transdatefrom transdateto reqdatefrom reqdateto
556 type vc employee_id salesman_id project_id
557 insertdatefrom insertdateto business_id);
559 my $href = build_std_url('action=orders', grep { $form->{$_} } @hidden_variables);
562 'ids' => { 'text' => '', },
563 'transdate' => { 'text' => $locale->text('Delivery Order Date'), },
564 'reqdate' => { 'text' => $locale->text('Reqdate'), },
565 'id' => { 'text' => $locale->text('ID'), },
566 'donumber' => { 'text' => $locale->text('Delivery Order'), },
567 'ordnumber' => { 'text' => $locale->text('Order'), },
568 'customernumber' => { 'text' => $locale->text('Customer Number'), },
569 'cusordnumber' => { 'text' => $locale->text('Customer Order Number'), },
570 'name' => { 'text' => $form->{vc} eq 'customer' ? $locale->text('Customer') : $locale->text('Vendor'), },
571 'employee' => { 'text' => $locale->text('Employee'), },
572 'salesman' => { 'text' => $locale->text('Salesman'), },
573 'shipvia' => { 'text' => $locale->text('Ship via'), },
574 'globalprojectnumber' => { 'text' => $locale->text('Project Number'), },
575 'transaction_description' => { 'text' => $locale->text('Transaction description'), },
576 'open' => { 'text' => $locale->text('Open'), },
577 'delivered' => { 'text' => $locale->text('Delivered'), },
578 'department' => { 'text' => $locale->text('Department'), },
579 'insertdate' => { 'text' => $locale->text('Insert Date'), },
582 foreach my $name (qw(id transdate reqdate donumber ordnumber name employee salesman shipvia transaction_description department insertdate)) {
583 my $sortdir = $form->{sort} eq $name ? 1 - $form->{sortdir} : $form->{sortdir};
584 $column_defs{$name}->{link} = $href . "&sort=$name&sortdir=$sortdir";
587 $form->{"l_type"} = "Y";
588 map { $column_defs{$_}->{visible} = $form->{"l_${_}"} ? 1 : 0 } @columns;
590 $column_defs{ids}->{visible} = 'HTML';
592 $report->set_columns(%column_defs);
593 $report->set_column_order(@columns);
595 $report->set_export_options('orders', @hidden_variables, qw(sort sortdir));
597 $report->set_sort_indicator($form->{sort}, $form->{sortdir});
600 if ($form->{customer}) {
601 push @options, $locale->text('Customer') . " : $form->{customer}";
603 if ($form->{vendor}) {
604 push @options, $locale->text('Vendor') . " : $form->{vendor}";
606 if ($form->{cp_name}) {
607 push @options, $locale->text('Contact Person') . " : $form->{cp_name}";
609 if ($form->{department}) {
610 my ($department) = split /--/, $form->{department};
611 push @options, $locale->text('Department') . " : $department";
613 if ($form->{donumber}) {
614 push @options, $locale->text('Delivery Order Number') . " : $form->{donumber}";
616 if ($form->{ordnumber}) {
617 push @options, $locale->text('Order Number') . " : $form->{ordnumber}";
619 push @options, $locale->text('Serial Number') . " : $form->{serialnumber}" if $form->{serialnumber};
620 if ($form->{business_id}) {
621 my $vc_type_label = $form->{vc} eq 'customer' ? $locale->text('Customer type') : $locale->text('Vendor type');
622 push @options, $vc_type_label . " : " . SL::DB::Business->new(id => $form->{business_id})->load->description;
624 if ($form->{transaction_description}) {
625 push @options, $locale->text('Transaction description') . " : $form->{transaction_description}";
627 if ( $form->{transdatefrom} or $form->{transdateto} ) {
628 push @options, $locale->text('Delivery Order Date');
629 push @options, $locale->text('From') . " " . $locale->date(\%myconfig, $form->{transdatefrom}, 1) if $form->{transdatefrom};
630 push @options, $locale->text('Bis') . " " . $locale->date(\%myconfig, $form->{transdateto}, 1) if $form->{transdateto};
632 if ( $form->{reqdatefrom} or $form->{reqdateto} ) {
633 push @options, $locale->text('Reqdate');
634 push @options, $locale->text('From') . " " . $locale->date(\%myconfig, $form->{reqdatefrom}, 1) if $form->{reqdatefrom};
635 push @options, $locale->text('Bis') . " " . $locale->date(\%myconfig, $form->{reqdateto}, 1) if $form->{reqdateto};
637 if ( $form->{insertdatefrom} or $form->{insertdateto} ) {
638 push @options, $locale->text('Insert Date');
639 push @options, $locale->text('From') . " " . $locale->date(\%myconfig, $form->{insertdatefrom}, 1) if $form->{insertdatefrom};
640 push @options, $locale->text('Bis') . " " . $locale->date(\%myconfig, $form->{insertdateto}, 1) if $form->{insertdateto};
643 push @options, $locale->text('Open');
645 if ($form->{closed}) {
646 push @options, $locale->text('Closed');
648 if ($form->{delivered}) {
649 push @options, $locale->text('Delivered');
651 if ($form->{notdelivered}) {
652 push @options, $locale->text('Not delivered');
655 $report->set_options('top_info_text' => join("\n", @options),
656 'raw_top_info_text' => $form->parse_html_template('do/orders_top'),
657 'raw_bottom_info_text' => $form->parse_html_template('do/orders_bottom'),
658 'output_format' => 'HTML',
659 'title' => $form->{title},
660 'attachment_basename' => $attachment_basename . strftime('_%Y%m%d', localtime time),
662 $report->set_options_from_form();
663 $locale->set_numberformat_wo_thousands_separator(\%myconfig) if lc($report->{options}->{output_format}) eq 'csv';
665 # add sort and escape callback, this one we use for the add sub
666 $form->{callback} = $href .= "&sort=$form->{sort}";
668 # escape callback for href
669 my $callback = $form->escape($href);
671 my $edit_url = build_std_url('action=edit', 'type', 'vc');
672 my $edit_order_url = build_std_url('script=oe.pl', 'type=' . ($form->{type} eq 'sales_delivery_order' ? 'sales_order' : 'purchase_order'), 'action=edit');
676 foreach my $dord (@{ $form->{DO} }) {
677 $dord->{open} = $dord->{closed} ? $locale->text('No') : $locale->text('Yes');
678 $dord->{delivered} = $dord->{delivered} ? $locale->text('Yes') : $locale->text('No');
680 my $row = { map { $_ => { 'data' => $dord->{$_} } } @columns };
683 'raw_data' => $cgi->hidden('-name' => "trans_id_${idx}", '-value' => $dord->{id})
684 . $cgi->checkbox('-name' => "multi_id_${idx}", '-value' => 1, '-label' => ''),
685 'valign' => 'center',
689 $row->{donumber}->{link} = $edit_url . "&id=" . E($dord->{id}) . "&callback=${callback}";
690 $row->{ordnumber}->{link} = $edit_order_url . "&id=" . E($dord->{oe_id}) . "&callback=${callback}" if $dord->{oe_id};
691 $report->add_data($row);
696 $report->generate_with_headers();
698 $main::lxdebug->leave_sub();
702 $main::lxdebug->enter_sub();
708 my $form = $main::form;
709 my %myconfig = %main::myconfig;
710 my $locale = $main::locale;
712 $form->mtime_ischanged('delivery_orders');
714 $form->{defaultcurrency} = $form->get_default_currency(\%myconfig);
716 $form->isblank("transdate", $locale->text('Delivery Order Date missing!'));
718 $form->{donumber} =~ s/^\s*//g;
719 $form->{donumber} =~ s/\s*$//g;
721 my $msg = ucfirst $form->{vc};
722 $form->isblank($form->{vc}, $locale->text($msg . " missing!"));
724 # $locale->text('Customer missing!');
725 # $locale->text('Vendor missing!');
727 remove_emptied_rows();
730 # if the name changed get new values
731 if (check_name($form->{vc})) {
736 $form->{id} = 0 if $form->{saveasnew};
740 if(!exists $form->{addition}) {
741 $form->{snumbers} = qq|donumber_| . $form->{donumber};
742 $form->{addition} = "SAVED";
745 # /saving the history
747 $form->{simple_save} = 1;
748 if (!$params{no_redirect} && !$form->{print_and_save}) {
749 delete @{$form}{ary_diff([keys %{ $form }], [qw(login id script type cursor_fokus)])};
753 $main::lxdebug->leave_sub();
757 $main::lxdebug->enter_sub();
761 my $form = $main::form;
762 my %myconfig = %main::myconfig;
763 my $locale = $main::locale;
767 if(!exists $form->{addition}) {
768 $form->{snumbers} = qq|donumber_| . $form->{donumber};
769 $form->{addition} = "DELETED";
772 # /saving the history
774 $form->info($locale->text('Delivery Order deleted!'));
778 $form->error($locale->text('Cannot delete delivery order!'));
780 $main::lxdebug->leave_sub();
784 $main::lxdebug->enter_sub();
786 my $form = $main::form;
787 my %myconfig = %main::myconfig;
788 my $locale = $main::locale;
791 $form->mtime_ischanged('delivery_orders');
793 $main::auth->assert($form->{type} eq 'purchase_delivery_order' ? 'vendor_invoice_edit' : 'invoice_edit');
795 $form->{convert_from_do_ids} = $form->{id};
796 $form->{deliverydate} = $form->{transdate};
797 $form->{transdate} = $form->{invdate} = $form->current_date(\%myconfig);
798 $form->{duedate} = $form->current_date(\%myconfig, $form->{invdate}, $form->{terms} * 1);
799 $form->{defaultcurrency} = $form->get_default_currency(\%myconfig);
803 delete @{$form}{qw(id closed delivered)};
805 my ($script, $buysell);
806 if ($form->{type} eq 'purchase_delivery_order') {
807 $form->{title} = $locale->text('Add Vendor Invoice');
808 $form->{script} = 'ir.pl';
813 $form->{title} = $locale->text('Add Sales Invoice');
814 $form->{script} = 'is.pl';
819 for my $i (1 .. $form->{rowcount}) {
821 unless ($form->{"ordnumber"}) {
822 if ($form->{discount}) { # Falls wir einen Lieferanten-/Kundenrabatt haben
823 # und rabattfähig sind, dann
824 unless ($form->{"not_discountable_$i"}) {
825 $form->{"discount_$i"} = $form->{discount}*100; # ... nehmen wir diesen Rabatt
829 map { $form->{"${_}_${i}"} = $form->parse_amount(\%myconfig, $form->{"${_}_${i}"}) if $form->{"${_}_${i}"} } qw(ship qty sellprice lastcost basefactor);
830 $form->{"donumber_$i"} = $form->{donumber};
831 $form->{"converted_from_delivery_order_items_id_$i"} = delete $form->{"delivery_order_items_id_$i"};
834 $form->{type} = "invoice";
837 $main::locale = Locale->new("$myconfig{countrycode}", "$script");
838 $locale = $main::locale;
840 require "bin/mozilla/$form->{script}";
842 my $currency = $form->{currency};
845 if ($form->{ordnumber}) {
846 require SL::DB::Order;
847 if (my $order = SL::DB::Manager::Order->find_by(ordnumber => $form->{ordnumber})) {
849 $form->{orddate} = $order->transdate_as_date;
850 $form->{$_} = $order->$_ for qw(payment_id salesman_id taxzone_id quonumber);
854 $form->{currency} = $currency;
855 $form->{exchangerate} = "";
856 $form->{forex} = $form->check_exchangerate(\%myconfig, $form->{currency}, $form->{invdate}, $buysell);
857 $form->{exchangerate} = $form->{forex} if ($form->{forex});
862 for my $i (1 .. $form->{rowcount}) {
863 $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"});
865 my ($dec) = ($form->{"sellprice_$i"} =~ /\.(\d+)/);
867 my $decimalplaces = ($dec > 2) ? $dec : 2;
869 # copy delivery date from reqdate for order -> invoice conversion
870 $form->{"deliverydate_$i"} = $form->{"reqdate_$i"}
871 unless $form->{"deliverydate_$i"};
874 $form->{"sellprice_$i"} =
875 $form->format_amount(\%myconfig, $form->{"sellprice_$i"},
878 $form->{"lastcost_$i"} =
879 $form->format_amount(\%myconfig, $form->{"lastcost_$i"},
882 (my $dec_qty) = ($form->{"qty_$i"} =~ /\.(\d+)/);
883 $dec_qty = length $dec_qty;
885 $form->format_amount(\%myconfig, $form->{"qty_$i"}, $dec_qty);
891 $main::lxdebug->leave_sub();
895 $main::lxdebug->enter_sub();
897 my $form = $main::form;
898 my %myconfig = %main::myconfig;
899 my $locale = $main::locale;
902 $main::auth->assert($form->{type} eq 'sales_delivery_order' ? 'invoice_edit' : 'vendor_invoice_edit');
904 my @do_ids = map { $form->{"trans_id_$_"} } grep { $form->{"multi_id_$_"} } (1..$form->{rowcount});
906 if (!scalar @do_ids) {
907 $form->show_generic_error($locale->text('You have not selected any delivery order.'), 'back_button' => 1);
910 map { delete $form->{$_} } grep { m/^(?:trans|multi)_id_\d+/ } keys %{ $form };
912 if (!DO->retrieve('vc' => $form->{vc}, 'ids' => \@do_ids)) {
913 $form->show_generic_error($form->{vc} eq 'customer' ?
914 $locale->text('You cannot create an invoice for delivery orders for different customers.') :
915 $locale->text('You cannot create an invoice for delivery orders from different vendors.'),
919 my $source_type = $form->{type};
920 $form->{convert_from_do_ids} = join ' ', @do_ids;
921 # bei der auswahl von mehreren Lieferscheinen fuer eine Rechnung, die einfach in donumber_array
922 # zwischenspeichern (DO.pm) und als ' '-separierte Liste wieder zurueckschreiben
923 # Hinweis: delete gibt den wert zurueck und loescht danach das element (nett und einfach)
924 # $shell: perldoc perlunc; /delete EXPR
925 $form->{donumber} = delete $form->{donumber_array};
926 $form->{ordnumber} = delete $form->{ordnumber_array};
927 $form->{cusordnumber} = delete $form->{cusordnumber_array};
928 $form->{deliverydate} = $form->{transdate};
929 $form->{transdate} = $form->current_date(\%myconfig);
930 $form->{duedate} = $form->current_date(\%myconfig, $form->{invdate}, $form->{terms} * 1);
931 $form->{type} = "invoice";
933 $form->{defaultcurrency} = $form->get_default_currency(\%myconfig);
935 my ($script, $buysell);
936 if ($source_type eq 'purchase_delivery_order') {
937 $form->{title} = $locale->text('Add Vendor Invoice');
938 $form->{script} = 'ir.pl';
943 $form->{title} = $locale->text('Add Sales Invoice');
944 $form->{script} = 'is.pl';
949 map { delete $form->{$_} } qw(id subject message cc bcc printed emailed queued);
951 # get vendor or customer discount
953 my $saved_form = save_form();
954 if ($form->{vc} eq 'vendor') {
955 IR->get_vendor(\%myconfig, \%$form);
956 $vc_discount = $form->{vendor_discount};
958 IS->get_customer(\%myconfig, \%$form);
959 $vc_discount = $form->{customer_discount};
961 restore_form($saved_form);
963 $form->{rowcount} = 0;
964 foreach my $ref (@{ $form->{form_details} }) {
966 $ref->{reqdate} ||= $ref->{dord_transdate}; # copy transdates into each invoice row
967 map { $form->{"${_}_$form->{rowcount}"} = $ref->{$_} } keys %{ $ref };
968 map { $form->{"${_}_$form->{rowcount}"} = $form->format_amount(\%myconfig, $ref->{$_}) } qw(qty sellprice lastcost);
969 $form->{"converted_from_delivery_order_items_id_$form->{rowcount}"} = delete $form->{"delivery_order_items_id_$form->{rowcount}"};
971 if ($vc_discount){ # falls wir einen Lieferanten/Kundenrabatt haben
972 # und keinen anderen discount wert an $i ...
973 $form->{"discount_$form->{rowcount}"} ||= $vc_discount; # ... nehmen wir diesen Rabatt
976 $form->{"discount_$form->{rowcount}"} = $form->{"discount_$form->{rowcount}"} * 100; #s.a. Bug 1151
977 # Anm.: Eine Änderung des discounts in der SL/DO.pm->retrieve (select (doi.discount * 100) as discount) ergibt in psql einen
978 # Wert von 10.0000001490116. Ferner ist der Rabatt in der Rechnung dann bei 1.0 (?). Deswegen lasse ich das hier. jb 10.10.09
980 $form->{"discount_$form->{rowcount}"} = $form->format_amount(\%myconfig, $form->{"discount_$form->{rowcount}"});
982 delete $form->{form_details};
984 $locale = Locale->new("$myconfig{countrycode}", "$script");
986 require "bin/mozilla/$form->{script}";
993 $main::lxdebug->leave_sub();
997 $main::lxdebug->enter_sub();
1001 my $form = $main::form;
1003 $form->{saveasnew} = 1;
1004 $form->{closed} = 0;
1005 $form->{delivered} = 0;
1006 map { delete $form->{$_} } qw(printed emailed queued);
1007 delete @{ $form }{ grep { m/^stock_(?:in|out)_\d+/ } keys %{ $form } };
1008 $form->{"converted_from_delivery_order_items_id_$_"} = delete $form->{"delivery_order_items_id_$_"} for 1 .. $form->{"rowcount"};
1009 # Let kivitendo assign a new order number if the user hasn't changed the
1010 # previous one. If it has been changed manually then use it as-is.
1011 $form->{donumber} =~ s/^\s*//g;
1012 $form->{donumber} =~ s/\s*$//g;
1013 if ($form->{saved_donumber} && ($form->{saved_donumber} eq $form->{donumber})) {
1014 delete($form->{donumber});
1019 $main::lxdebug->leave_sub();
1023 $main::lxdebug->enter_sub();
1027 $::form->mtime_ischanged('delivery_orders','mail');
1029 $::form->{print_and_save} = 1;
1031 my $saved_form = save_form();
1035 restore_form($saved_form, 0, qw(id ordnumber quonumber));
1039 $main::lxdebug->leave_sub();
1042 sub calculate_stock_in_out {
1043 $main::lxdebug->enter_sub();
1045 my $form = $main::form;
1049 if (!$form->{"id_${i}"}) {
1050 $main::lxdebug->leave_sub();
1054 my $all_units = AM->retrieve_all_units();
1056 my $in_out = $form->{type} =~ /^sales/ ? 'out' : 'in';
1057 my $sinfo = DO->unpack_stock_information('packed' => $form->{"stock_${in_out}_${i}"});
1059 my $do_qty = AM->sum_with_unit($::form->{"qty_$i"}, $::form->{"unit_$i"});
1060 my $sum = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $sinfo });
1061 my $matches = $do_qty == $sum;
1063 my $content = $form->format_amount_units('amount' => $sum * 1,
1064 'part_unit' => $form->{"partunit_$i"},
1065 'amount_unit' => $all_units->{$form->{"partunit_$i"}}->{base_unit},
1066 'conv_units' => 'convertible_not_smaller',
1068 $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="?">|;
1070 $main::lxdebug->leave_sub();
1075 sub get_basic_bin_wh_info {
1076 $main::lxdebug->enter_sub();
1078 my $stock_info = shift;
1080 my $form = $main::form;
1082 foreach my $sinfo (@{ $stock_info }) {
1083 next unless ($sinfo->{bin_id});
1085 my $bin_info = WH->get_basic_bin_info('id' => $sinfo->{bin_id});
1086 map { $sinfo->{"${_}_description"} = $sinfo->{"${_}description"} = $bin_info->{"${_}_description"} } qw(bin warehouse);
1089 $main::lxdebug->leave_sub();
1092 sub stock_in_out_form {
1093 $main::lxdebug->enter_sub();
1095 my $form = $main::form;
1097 if ($form->{in_out} eq 'out') {
1103 $main::lxdebug->leave_sub();
1106 sub redo_stock_info {
1107 $main::lxdebug->enter_sub();
1111 my $form = $main::form;
1113 my @non_empty = grep { $_->{qty} } @{ $params{stock_info} };
1115 if ($params{add_empty_row}) {
1117 'warehouse_id' => scalar(@non_empty) ? $non_empty[-1]->{warehouse_id} : undef,
1118 'bin_id' => scalar(@non_empty) ? $non_empty[-1]->{bin_id} : undef,
1122 @{ $params{stock_info} } = @non_empty;
1124 $main::lxdebug->leave_sub();
1127 sub update_stock_in {
1128 $main::lxdebug->enter_sub();
1130 my $form = $main::form;
1131 my %myconfig = %main::myconfig;
1133 my $stock_info = [];
1135 foreach my $i (1..$form->{rowcount}) {
1136 $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
1137 push @{ $stock_info }, { map { $_ => $form->{"${_}_${i}"} } qw(warehouse_id bin_id chargenumber
1138 bestbefore qty unit delivery_order_items_stock_id) };
1141 display_stock_in_form($stock_info);
1143 $main::lxdebug->leave_sub();
1147 $main::lxdebug->enter_sub();
1149 my $form = $main::form;
1151 my $stock_info = DO->unpack_stock_information('packed' => $form->{stock});
1153 display_stock_in_form($stock_info);
1155 $main::lxdebug->leave_sub();
1158 sub display_stock_in_form {
1159 $main::lxdebug->enter_sub();
1161 my $stock_info = shift;
1163 my $form = $main::form;
1164 my %myconfig = %main::myconfig;
1165 my $locale = $main::locale;
1167 $form->{title} = $locale->text('Stock');
1169 my $part_info = IC->get_basic_part_info('id' => $form->{parts_id});
1171 # Standardlagerplatz für Standard-Auslagern verwenden, falls keiner für die Ware explizit definiert wurde
1172 if ($::instance_conf->get_transfer_default_use_master_default_bin) {
1173 $part_info->{warehouse_id} ||= $::instance_conf->get_warehouse_id;
1174 $part_info->{bin_id} ||= $::instance_conf->get_bin_id;
1177 my $units = AM->retrieve_units(\%myconfig, $form);
1178 # der zweite Parameter von unit_select_data gibt den default-Namen (selected) vor
1179 my $units_data = AM->unit_select_data($units, $form->{do_unit}, undef, $part_info->{unit});
1181 $form->get_lists('warehouses' => { 'key' => 'WAREHOUSES',
1182 'bins' => 'BINS' });
1184 redo_stock_info('stock_info' => $stock_info, 'add_empty_row' => !$form->{delivered});
1186 get_basic_bin_wh_info($stock_info);
1188 $form->header(no_layout => 1);
1189 print $form->parse_html_template('do/stock_in_form', { 'UNITS' => $units_data,
1190 'STOCK_INFO' => $stock_info,
1191 'PART_INFO' => $part_info, });
1193 $main::lxdebug->leave_sub();
1196 sub _stock_in_out_set_qty_display {
1197 my $stock_info = shift;
1199 my $all_units = AM->retrieve_all_units();
1200 my $sum = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
1201 $form->{qty_display} = $form->format_amount_units(amount => $sum * 1,
1202 part_unit => $form->{partunit},
1203 amount_unit => $all_units->{ $form->{partunit} }->{base_unit},
1204 conv_units => 'convertible_not_smaller',
1209 $main::lxdebug->enter_sub();
1211 my $form = $main::form;
1212 my %myconfig = %main::myconfig;
1214 my $stock_info = [];
1216 foreach my $i (1..$form->{rowcount}) {
1217 $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
1219 next if ($form->{"qty_$i"} <= 0);
1221 push @{ $stock_info }, { map { $_ => $form->{"${_}_${i}"} } qw(delivery_order_items_stock_id warehouse_id bin_id chargenumber bestbefore qty unit) };
1224 $form->{stock} = YAML::Dump($stock_info);
1226 _stock_in_out_set_qty_display($stock_info);
1228 my $do_qty = AM->sum_with_unit($::form->parse_amount(\%::myconfig, $::form->{do_qty}), $::form->{do_unit});
1229 my $transfer_qty = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
1232 print $form->parse_html_template('do/set_stock_in_out', {
1233 qty_matches => $do_qty == $transfer_qty,
1236 $main::lxdebug->leave_sub();
1239 sub stock_out_form {
1240 $main::lxdebug->enter_sub();
1242 my $form = $main::form;
1243 my %myconfig = %main::myconfig;
1244 my $locale = $main::locale;
1246 $form->{title} = $locale->text('Release From Stock');
1248 my $part_info = IC->get_basic_part_info('id' => $form->{parts_id});
1250 my $units = AM->retrieve_units(\%myconfig, $form);
1251 my $units_data = AM->unit_select_data($units, undef, undef, $part_info->{unit});
1253 my @contents = DO->get_item_availability('parts_id' => $form->{parts_id});
1255 my $stock_info = DO->unpack_stock_information('packed' => $form->{stock});
1257 if (!$form->{delivered}) {
1258 foreach my $row (@contents) {
1259 $row->{available_qty} = $form->format_amount_units('amount' => $row->{qty} * 1,
1260 'part_unit' => $part_info->{unit},
1261 'conv_units' => 'convertible_not_smaller',
1264 foreach my $sinfo (@{ $stock_info }) {
1265 next if (($row->{bin_id} != $sinfo->{bin_id}) ||
1266 ($row->{warehouse_id} != $sinfo->{warehouse_id}) ||
1267 ($row->{chargenumber} ne $sinfo->{chargenumber}) ||
1268 ($row->{bestbefore} ne $sinfo->{bestbefore}));
1270 map { $row->{"stock_$_"} = $sinfo->{$_} } qw(qty unit error delivery_order_items_stock_id);
1275 get_basic_bin_wh_info($stock_info);
1277 foreach my $sinfo (@{ $stock_info }) {
1278 map { $sinfo->{"stock_$_"} = $sinfo->{$_} } qw(qty unit);
1282 $form->header(no_layout => 1);
1283 print $form->parse_html_template('do/stock_out_form', { 'UNITS' => $units_data,
1284 'WHCONTENTS' => $form->{delivered} ? $stock_info : \@contents,
1285 'PART_INFO' => $part_info, });
1287 $main::lxdebug->leave_sub();
1291 $main::lxdebug->enter_sub();
1293 my $form = $main::form;
1294 my %myconfig = %main::myconfig;
1295 my $locale = $main::locale;
1297 my $stock_info = [];
1299 foreach my $i (1 .. $form->{rowcount}) {
1300 $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
1302 next if ($form->{"qty_$i"} <= 0);
1304 push @{ $stock_info }, {
1305 'warehouse_id' => $form->{"warehouse_id_$i"},
1306 'bin_id' => $form->{"bin_id_$i"},
1307 'chargenumber' => $form->{"chargenumber_$i"},
1308 'bestbefore' => $form->{"bestbefore_$i"},
1309 'qty' => $form->{"qty_$i"},
1310 'unit' => $form->{"unit_$i"},
1312 'delivery_order_items_stock_id' => $form->{"delivery_order_items_stock_id_$i"},
1316 my @errors = DO->check_stock_availability('requests' => $stock_info,
1317 'parts_id' => $form->{parts_id});
1319 $form->{stock} = YAML::Dump($stock_info);
1322 $form->{ERRORS} = [];
1323 map { push @{ $form->{ERRORS} }, $locale->text('Error in row #1: The quantity you entered is bigger than the stocked quantity.', $_->{row}); } @errors;
1324 stock_in_out_form();
1327 _stock_in_out_set_qty_display($stock_info);
1329 my $do_qty = AM->sum_with_unit($::form->parse_amount(\%::myconfig, $::form->{do_qty}), $::form->{do_unit});
1330 my $transfer_qty = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
1333 print $form->parse_html_template('do/set_stock_in_out', {
1334 qty_matches => $do_qty == $transfer_qty,
1338 $main::lxdebug->leave_sub();
1342 $main::lxdebug->enter_sub();
1344 my $form = $main::form;
1345 my %myconfig = %main::myconfig;
1346 my $locale = $main::locale;
1348 if ($form->{id} && DO->is_marked_as_delivered(id => $form->{id})) {
1349 $form->show_generic_error($locale->text('The parts for this delivery order have already been transferred in.'), 'back_button' => 1);
1352 save(no_redirect => 1);
1354 my @part_ids = map { $form->{"id_${_}"} } grep { $form->{"id_${_}"} && $form->{"stock_in_${_}"} } (1 .. $form->{rowcount});
1358 my $units = AM->retrieve_units(\%myconfig, $form);
1359 my %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
1362 $form->{ERRORS} = [];
1364 foreach my $i (1 .. $form->{rowcount}) {
1365 next unless ($form->{"id_$i"} && $form->{"stock_in_$i"});
1367 my $row_sum_base_qty = 0;
1368 my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
1370 foreach my $request (@{ DO->unpack_stock_information('packed' => $form->{"stock_in_$i"}) }) {
1371 $request->{parts_id} = $form->{"id_$i"};
1372 $row_sum_base_qty += $request->{qty} * $units->{$request->{unit}}->{factor} / $base_unit_factor;
1374 $request->{project_id} = $form->{"project_id_$i"} || $form->{globalproject_id};
1376 push @all_requests, $request;
1379 next if (0 == $row_sum_base_qty);
1381 my $do_base_qty = $form->parse_amount(\%myconfig, $form->{"qty_$i"}) * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
1383 # if ($do_base_qty != $row_sum_base_qty) {
1384 # push @{ $form->{ERRORS} }, $locale->text('Error in position #1: You must either assign no stock at all or the full quantity of #2 #3.',
1385 # $i, $form->{"qty_$i"}, $form->{"unit_$i"});
1389 if (@{ $form->{ERRORS} }) {
1390 push @{ $form->{ERRORS} }, $locale->text('The delivery order has not been marked as delivered. The warehouse contents have not changed.');
1392 set_headings('edit');
1394 $main::lxdebug->leave_sub();
1400 DO->transfer_in_out('direction' => 'in',
1401 'requests' => \@all_requests);
1403 SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
1405 $form->{callback} = 'do.pl?action=edit&type=purchase_delivery_order&id=' . $form->escape($form->{id});
1408 $main::lxdebug->leave_sub();
1412 $main::lxdebug->enter_sub();
1414 my $form = $main::form;
1415 my %myconfig = %main::myconfig;
1416 my $locale = $main::locale;
1418 if ($form->{id} && DO->is_marked_as_delivered(id => $form->{id})) {
1419 $form->show_generic_error($locale->text('The parts for this delivery order have already been transferred out.'), 'back_button' => 1);
1422 save(no_redirect => 1);
1424 my @part_ids = map { $form->{"id_${_}"} } grep { $form->{"id_${_}"} && $form->{"stock_out_${_}"} } (1 .. $form->{rowcount});
1428 my $units = AM->retrieve_units(\%myconfig, $form);
1429 my %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
1432 $form->{ERRORS} = [];
1434 foreach my $i (1 .. $form->{rowcount}) {
1435 next unless ($form->{"id_$i"} && $form->{"stock_out_$i"});
1437 my $row_sum_base_qty = 0;
1438 my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
1440 foreach my $request (@{ DO->unpack_stock_information('packed' => $form->{"stock_out_$i"}) }) {
1441 $request->{parts_id} = $form->{"id_$i"};
1442 $request->{base_qty} = $request->{qty} * $units->{$request->{unit}}->{factor} / $base_unit_factor;
1443 $request->{project_id} = $form->{"project_id_$i"} ? $form->{"project_id_$i"} : $form->{globalproject_id};
1445 my $map_key = join '--', ($form->{"id_$i"}, @{$request}{qw(warehouse_id bin_id chargenumber bestbefore)});
1447 $request_map{$map_key} ||= $request;
1448 $request_map{$map_key}->{sum_base_qty} ||= 0;
1449 $request_map{$map_key}->{sum_base_qty} += $request->{base_qty};
1450 $row_sum_base_qty += $request->{base_qty};
1452 push @all_requests, $request;
1455 next if (0 == $row_sum_base_qty);
1457 my $do_base_qty = $form->{"qty_$i"} * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
1459 # if ($do_base_qty != $row_sum_base_qty) {
1460 # push @{ $form->{ERRORS} }, $locale->text('Error in position #1: You must either assign no transfer at all or the full quantity of #2 #3.',
1461 # $i, $form->{"qty_$i"}, $form->{"unit_$i"});
1466 my @bin_ids = map { $_->{bin_id} } values %request_map;
1467 my %bin_info_map = WH->get_basic_bin_info('id' => \@bin_ids);
1468 my @contents = DO->get_item_availability('parts_id' => \@part_ids);
1470 foreach my $inv (@contents) {
1471 my $map_key = join '--', @{$inv}{qw(parts_id warehouse_id bin_id chargenumber bestbefore)};
1473 next unless ($request_map{$map_key});
1475 my $request = $request_map{$map_key};
1476 $request->{ok} = $request->{sum_base_qty} <= $inv->{qty};
1479 foreach my $request (values %request_map) {
1480 next if ($request->{ok});
1482 my $pinfo = $part_info_map{$request->{parts_id}};
1483 my $binfo = $bin_info_map{$request->{bin_id}};
1485 if ($::instance_conf->get_show_bestbefore) {
1486 push @{ $form->{ERRORS} }, $locale->text("There is not enough available of '#1' at warehouse '#2', bin '#3', #4, #5, for the transfer of #6.",
1487 $pinfo->{description},
1488 $binfo->{warehouse_description},
1489 $binfo->{bin_description},
1490 $request->{chargenumber} ? $locale->text('chargenumber #1', $request->{chargenumber}) : $locale->text('no chargenumber'),
1491 $request->{bestbefore} ? $locale->text('bestbefore #1', $request->{bestbefore}) : $locale->text('no bestbefore'),
1492 $form->format_amount_units('amount' => $request->{sum_base_qty},
1493 'part_unit' => $pinfo->{unit},
1494 'conv_units' => 'convertible_not_smaller'));
1496 push @{ $form->{ERRORS} }, $locale->text("There is not enough available of '#1' at warehouse '#2', bin '#3', #4, for the transfer of #5.",
1497 $pinfo->{description},
1498 $binfo->{warehouse_description},
1499 $binfo->{bin_description},
1500 $request->{chargenumber} ? $locale->text('chargenumber #1', $request->{chargenumber}) : $locale->text('no chargenumber'),
1501 $form->format_amount_units('amount' => $request->{sum_base_qty},
1502 'part_unit' => $pinfo->{unit},
1503 'conv_units' => 'convertible_not_smaller'));
1508 if (@{ $form->{ERRORS} }) {
1509 push @{ $form->{ERRORS} }, $locale->text('The delivery order has not been marked as delivered. The warehouse contents have not changed.');
1511 set_headings('edit');
1513 $main::lxdebug->leave_sub();
1518 DO->transfer_in_out('direction' => 'out',
1519 'requests' => \@all_requests);
1521 SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
1523 $form->{callback} = 'do.pl?action=edit&type=sales_delivery_order&id=' . $form->escape($form->{id});
1526 $main::lxdebug->leave_sub();
1530 $main::lxdebug->enter_sub();
1532 my $form = $main::form;
1534 DO->close_orders('ids' => [ $form->{id} ]);
1536 $form->{closed} = 1;
1540 $main::lxdebug->leave_sub();
1545 call_sub($main::form->{yes_nextsub});
1549 call_sub($main::form->{no_nextsub});
1553 call_sub($main::form->{update_nextsub} || $main::form->{nextsub} || 'update_delivery_order');
1557 my $form = $main::form;
1558 my $locale = $main::locale;
1560 foreach my $action (qw(update ship_to print e_mail save transfer_out transfer_out_default sort
1561 transfer_in transfer_in_default mark_closed save_as_new invoice delete)) {
1562 if ($form->{"action_${action}"}) {
1568 $form->error($locale->text('No action defined.'));
1571 sub transfer_out_default {
1572 $main::lxdebug->enter_sub();
1574 my $form = $main::form;
1576 transfer_in_out_default('direction' => 'out');
1578 $main::lxdebug->leave_sub();
1581 sub transfer_in_default {
1582 $main::lxdebug->enter_sub();
1584 my $form = $main::form;
1586 transfer_in_out_default('direction' => 'in');
1588 $main::lxdebug->leave_sub();
1591 # Falls das Standardlagerverfahren aktiv ist, wird
1592 # geprüft, ob alle Standardlagerplätze für die Auslager-
1593 # artikel vorhanden sind UND ob die Warenmenge ausreicht zum
1594 # Auslagern. Falls nicht wird entsprechend eine Fehlermeldung
1595 # generiert. Offen Chargennummer / bestbefore wird nicht berücksichtigt
1596 sub transfer_in_out_default {
1597 $main::lxdebug->enter_sub();
1599 my $form = $main::form;
1600 my %myconfig = %main::myconfig;
1601 my $locale = $main::locale;
1604 my (%missing_default_bins, %qty_parts, @all_requests, %part_info_map, $default_warehouse_id, $default_bin_id);
1606 Common::check_params(\%params, qw(direction));
1608 # entsprechende defaults holen, falls standardlagerplatz verwendet werden soll
1609 if ($::instance_conf->get_transfer_default_use_master_default_bin) {
1610 $default_warehouse_id = $::instance_conf->get_warehouse_id;
1611 $default_bin_id = $::instance_conf->get_bin_id;
1615 my @part_ids = map { $form->{"id_${_}"} } (1 .. $form->{rowcount});
1617 my $units = AM->retrieve_units(\%myconfig, $form);
1618 %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
1619 foreach my $i (1 .. $form->{rowcount}) {
1620 next unless ($form->{"id_$i"});
1621 my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
1622 my $qty = $form->parse_amount(\%myconfig, $form->{"qty_$i"}) * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
1624 $form->show_generic_error($locale->text("Cannot transfer negative entries." ), 'back_button' => 1) if ($qty < 0);
1625 # if we do not want to transfer services and this part is a service, set qty to zero
1626 # ... and do not create a hash entry in %qty_parts below (will skip check for bins for the transfer == out case)
1627 # ... 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)
1629 $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});
1630 $qty_parts{$form->{"id_$i"}} += $qty;
1632 delete $qty_parts{$form->{"id_$i"}} unless $qty_parts{$form->{"id_$i"}};
1633 undef $form->{"stock_in_$i"};
1636 $part_info_map{$form->{"id_$i"}}{bin_id} ||= $default_bin_id;
1637 $part_info_map{$form->{"id_$i"}}{warehouse_id} ||= $default_warehouse_id;
1639 push @all_requests, ($qty == 0) ? { } : {
1640 'chargenumber' => '', #?? die müsste entsprechend geholt werden
1641 #'bestbefore' => undef, # TODO wird nicht berücksichtigt
1642 'bin_id' => $part_info_map{$form->{"id_$i"}}{bin_id},
1644 'parts_id' => $form->{"id_$i"},
1645 'comment' => $locale->text("Default transfer delivery order"),
1646 'unit' => $part_info_map{$form->{"id_$i"}}{unit},
1647 'warehouse_id' => $part_info_map{$form->{"id_$i"}}{warehouse_id},
1648 'oe_id' => $form->{id},
1649 'project_id' => $form->{"project_id_$i"} ? $form->{"project_id_$i"} : $form->{globalproject_id}
1653 # jetzt wird erst überprüft, ob die Stückzahl entsprechend stimmt.
1654 # check if bin (transfer in and transfer out and qty (transfer out) is correct
1655 foreach my $key (keys %qty_parts) {
1657 $missing_default_bins{$key}{missing_bin} = 1 unless ($part_info_map{$key}{bin_id});
1658 next unless ($part_info_map{$key}{bin_id}); # abbruch
1660 if ($params{direction} eq 'out') { # wird nur für ausgehende Mengen benötigt
1661 my ($max_qty, $error) = WH->get_max_qty_parts_bin(parts_id => $key, bin_id => $part_info_map{$key}{bin_id});
1663 # wir können nicht entscheiden, welche charge oder mhd (bestbefore) ausgewählt sein soll
1664 # deshalb rückmeldung nach oben geben, manuell auszulagern
1665 # TODO Bei nur einem Treffer mit Charge oder bestbefore wäre das noch möglich
1666 $missing_default_bins{$key}{chargenumber} = 1;
1668 if ($max_qty < $qty_parts{$key}){
1669 $missing_default_bins{$key}{missing_qty} = $max_qty - $qty_parts{$key};
1675 # Abfrage für Fehlerbehandlung (nur bei direction == out)
1676 if (scalar (keys %missing_default_bins)) {
1678 foreach my $fehler (keys %missing_default_bins) {
1680 my $ware = WH->get_part_description(parts_id => $fehler);
1681 if ($missing_default_bins{$fehler}{missing_bin}){
1682 $fehlertext .= "Kein Standardlagerplatz definiert bei $ware <br>";
1684 if ($missing_default_bins{$fehler}{missing_qty}) { # missing_qty
1685 $fehlertext .= "Es fehlen " . $missing_default_bins{$fehler}{missing_qty}*-1 .
1686 " von $ware auf dem Standard-Lagerplatz " . $part_info_map{$fehler}{bin} . " zum Auslagern<br>";
1688 if ($missing_default_bins{$fehler}{chargenumber}){
1689 $fehlertext .= "Die Ware hat eine Chargennummer oder eine Mindesthaltbarkeit definiert.
1690 Hier kann man nicht automatisch entscheiden.
1691 Bitte diesen Lieferschein manuell auslagern.
1694 # auslagern soll immer gehen, auch wenn nicht genügend auf lager ist.
1695 # der lagerplatz ist hier extra konfigurierbar, bspw. Lager-Korrektur mit
1696 # Lagerplatz Lagerplatz-Korrektur
1697 my $default_warehouse_id_ignore_onhand = $::instance_conf->get_warehouse_id_ignore_onhand;
1698 my $default_bin_id_ignore_onhand = $::instance_conf->get_bin_id_ignore_onhand;
1699 if ($::instance_conf->get_transfer_default_ignore_onhand && $default_bin_id_ignore_onhand) {
1700 # entsprechende defaults holen
1701 # falls chargenumber, bestbefore oder anzahl nicht stimmt, auf automatischen
1702 # lagerplatz wegbuchen!
1703 foreach (@all_requests) {
1704 if ($_->{parts_id} eq $fehler){
1705 $_->{bin_id} = $default_bin_id_ignore_onhand;
1706 $_->{warehouse_id} = $default_warehouse_id_ignore_onhand;
1710 #$main::lxdebug->message(0, 'Fehlertext: ' . $fehlertext);
1711 $form->show_generic_error($locale->text("Cannot transfer. <br> Reason:<br>#1", $fehlertext ), 'back_button' => 1);
1717 # hier der eigentliche fallunterschied für in oder out
1718 my $prefix = $params{direction} eq 'in' ? 'in' : 'out';
1720 # dieser array_ref ist für DO->save da:
1721 # einmal die all_requests in YAML verwandeln, damit delivery_order_items_stock
1722 # gefüllt werden kann.
1724 foreach (@all_requests){
1726 next unless scalar(%{ $_ });
1727 $form->{"stock_${prefix}_$i"} = YAML::Dump([$_]);
1730 save(no_redirect => 1); # Wir können auslagern, deshalb beleg speichern
1731 # und in delivery_order_items_stock speichern
1732 DO->transfer_in_out('direction' => $prefix,
1733 'requests' => \@all_requests);
1735 SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
1737 $form->{callback} = 'do.pl?action=edit&type=sales_delivery_order&id=' . $form->escape($form->{id}) if $params{direction} eq 'out';
1738 $form->{callback} = 'do.pl?action=edit&type=purchase_delivery_order&id=' . $form->escape($form->{id}) if $params{direction} eq 'in';
1744 $main::lxdebug->enter_sub();
1748 my $form = $main::form;
1751 croak ("Delivery Order needs to be saved") unless $form->{id};
1753 # hashify partnumbers, positions. key is delivery_order_items_id
1754 for my $i (1 .. ($form->{rowcount}) ) {
1755 $temp_hash{$form->{"delivery_order_items_id_$i"}} = { runningnumber => $form->{"runningnumber_$i"}, partnumber => $form->{"partnumber_$i"} };
1757 # naturally sort partnumbers and get a sorted array of doi_ids
1758 my @sorted_doi_ids = sort { Sort::Naturally::ncmp($temp_hash{$a}->{"partnumber"}, $temp_hash{$b}->{"partnumber"}) } keys %temp_hash;
1763 for (@sorted_doi_ids) {
1764 $form->{"runningnumber_$temp_hash{$_}->{runningnumber}"} = $new_number;
1767 $main::lxdebug->leave_sub();
1779 do.pl - Script for all calls to delivery order
1788 Sorts all position with Natural Sort. Can be activated in form_footer.html like this
1789 C<E<lt>input class="submit" type="submit" name="action_sort" id="sort_button" value="[% 'Sort and Save' | $T8 %]"E<gt>>
1795 Sort and Save can be implemented as an optional button if configuration ca be set by client config.
1796 Example coding for database scripts and templates in (git show af2f24b8), check also
1797 autogeneration for rose (scripts/rose_auto_create_model.pl --h)