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., 51 Franklin Street, Fifth Floor, Boston,
29 #======================================================================
32 #======================================================================
35 use List::MoreUtils qw(uniq);
36 use List::Util qw(max sum);
37 use POSIX qw(strftime);
40 use SL::DB::DeliveryOrder;
44 use SL::MoreCommon qw(ary_diff restore_form save_form);
45 use SL::ReportGenerator;
47 use Sort::Naturally ();
48 require "bin/mozilla/common.pl";
49 require "bin/mozilla/io.pl";
50 require "bin/mozilla/reportgenerator.pl";
59 $main::auth->assert($main::form->{type} . '_edit');
63 $main::lxdebug->enter_sub();
69 my $form = $main::form;
70 my $locale = $main::locale;
72 if ($form->{type} eq 'purchase_delivery_order') {
73 $form->{vc} = 'vendor';
74 $form->{title} = $action eq "edit" ? $locale->text('Edit Purchase Delivery Order') : $locale->text('Add Purchase Delivery Order');
76 $form->{vc} = 'customer';
77 $form->{title} = $action eq "edit" ? $locale->text('Edit Sales Delivery Order') : $locale->text('Add Sales Delivery Order');
80 $form->{heading} = $locale->text('Delivery Order');
82 $main::lxdebug->leave_sub();
86 $main::lxdebug->enter_sub();
90 if (($::form->{type} =~ /purchase/) && !$::instance_conf->get_allow_new_purchase_invoice) {
91 $::form->show_generic_error($::locale->text("You do not have the permissions to access this function."));
94 my $form = $main::form;
98 $form->{show_details} = $::myconfig{show_form_details};
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 $form->{show_details} = $::myconfig{show_form_details};
117 # show history button
118 $form->{javascript} = qq|<script type="text/javascript" src="js/show_history.js"></script>|;
119 #/show hhistory button
121 $form->{simple_save} = 0;
123 set_headings("edit");
125 # editing without stuff to edit? try adding it first
126 if ($form->{rowcount} && !$form->{print_and_save}) {
127 # map { $id++ if $form->{"multi_id_$_"} } (1 .. $form->{rowcount});
131 undef $form->{rowcount};
133 $main::lxdebug->leave_sub();
136 } elsif (!$form->{id}) {
138 $main::lxdebug->leave_sub();
142 my ($language_id, $printer_id);
143 if ($form->{print_and_save}) {
144 $form->{action} = "dispatcher";
145 $form->{action_print} = "1";
146 $form->{resubmit} = 1;
147 $language_id = $form->{language_id};
148 $printer_id = $form->{printer_id};
151 set_headings("edit");
156 if ($form->{print_and_save}) {
157 $form->{language_id} = $language_id;
158 $form->{printer_id} = $printer_id;
163 $main::lxdebug->leave_sub();
167 $main::lxdebug->enter_sub();
171 my $form = $main::form;
172 my %myconfig = %main::myconfig;
174 # retrieve order/quotation
175 my $editing = $form->{id};
177 DO->retrieve('vc' => $form->{vc},
178 'ids' => $form->{id});
180 $form->backup_vars(qw(payment_id language_id taxzone_id salesman_id taxincluded cp_id intnotes delivery_term_id currency));
182 # get customer / vendor
183 if ($form->{vc} eq 'vendor') {
184 IR->get_vendor(\%myconfig, \%$form);
185 $form->{discount} = $form->{vendor_discount};
187 IS->get_customer(\%myconfig, \%$form);
188 $form->{discount} = $form->{customer_discount};
191 $form->restore_vars(qw(payment_id language_id taxzone_id intnotes cp_id delivery_term_id));
192 $form->restore_vars(qw(currency)) if ($form->{id} || $form->{convert_from_oe_ids});
193 $form->restore_vars(qw(taxincluded)) if $form->{id};
194 $form->restore_vars(qw(salesman_id)) if $editing;
196 $main::lxdebug->leave_sub();
200 $main::lxdebug->enter_sub();
204 my $form = $main::form;
205 my %myconfig = %main::myconfig;
207 $form->{formname} = $form->{type} unless $form->{formname};
210 foreach my $ref (@{ $form->{form_details} }) {
211 $form->{rowcount} = ++$i;
213 map { $form->{"${_}_$i"} = $ref->{$_} } keys %{$ref};
215 for my $i (1 .. $form->{rowcount}) {
217 $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"} * 100);
219 $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"});
221 my ($dec) = ($form->{"sellprice_$i"} =~ /\.(\d+)/);
223 my $decimalplaces = ($dec > 2) ? $dec : 2;
225 # copy reqdate from deliverydate for invoice -> order conversion
226 $form->{"reqdate_$i"} = $form->{"deliverydate_$i"} unless $form->{"reqdate_$i"};
228 $form->{"sellprice_$i"} = $form->format_amount(\%myconfig, $form->{"sellprice_$i"}, $decimalplaces);
229 $form->{"lastcost_$i"} = $form->format_amount(\%myconfig, $form->{"lastcost_$i"}, $decimalplaces);
231 (my $dec_qty) = ($form->{"qty_$i"} =~ /\.(\d+)/);
232 $dec_qty = length $dec_qty;
233 $form->{"qty_$i"} = $form->format_amount(\%myconfig, $form->{"qty_$i"}, $dec_qty);
236 $main::lxdebug->leave_sub();
240 $main::lxdebug->enter_sub();
244 my $form = $main::form;
245 my %myconfig = %main::myconfig;
247 my $class = "SL::DB::" . ($form->{vc} eq 'customer' ? 'Customer' : 'Vendor');
248 $form->{VC_OBJ} = $class->load_cached($form->{ $form->{vc} . '_id' });
250 $form->{employee_id} = $form->{old_employee_id} if $form->{old_employee_id};
251 $form->{salesman_id} = $form->{old_salesman_id} if $form->{old_salesman_id};
253 my $vc = $form->{vc} eq "customer" ? "customers" : "vendors";
254 $form->get_lists("price_factors" => "ALL_PRICE_FACTORS",
255 "business_types" => "ALL_BUSINESS_TYPES",
257 $form->{ALL_DEPARTMENTS} = SL::DB::Manager::Department->get_all;
260 my @old_project_ids = uniq grep { $_ } map { $_ * 1 } ($form->{"globalproject_id"}, map { $form->{"project_id_$_"} } 1..$form->{"rowcount"});
261 my @old_ids_cond = @old_project_ids ? (id => \@old_project_ids) : ();
263 if (($vc eq 'customers') && $::instance_conf->get_customer_projects_only_in_sales) {
266 customer_id => $::form->{customer_id},
267 billable_customer_id => $::form->{customer_id},
272 and => [ active => 1, @customer_cond ],
276 $::form->{ALL_PROJECTS} = SL::DB::Manager::Project->get_all_sorted(query => \@conditions);
277 $::form->{ALL_EMPLOYEES} = SL::DB::Manager::Employee->get_all_sorted(query => [ or => [ id => $::form->{employee_id}, deleted => 0 ] ]);
278 $::form->{ALL_SALESMEN} = SL::DB::Manager::Employee->get_all_sorted(query => [ or => [ id => $::form->{salesman_id}, deleted => 0 ] ]);
279 $::form->{ALL_SHIPTO} = SL::DB::Manager::Shipto->get_all_sorted(query => [
280 or => [ trans_id => $::form->{"$::form->{vc}_id"} * 1, and => [ shipto_id => $::form->{shipto_id} * 1, trans_id => undef ] ]
282 $::form->{ALL_CONTACTS} = SL::DB::Manager::Contact->get_all_sorted(query => [
284 cp_cv_id => $::form->{"$::form->{vc}_id"} * 1,
287 cp_id => $::form->{cp_id} * 1
292 my $dispatch_to_popup = '';
293 if ($form->{resubmit} && ($form->{format} eq "html")) {
294 $dispatch_to_popup = "window.open('about:blank','Beleg'); document.do.target = 'Beleg';";
295 $dispatch_to_popup .= "document.do.submit();";
296 } elsif ($form->{resubmit}) {
297 # emulate click for resubmitting actions
298 $dispatch_to_popup = "document.do.${_}.click(); " for grep { /^action_/ } keys %$form;
300 $::request->{layout}->add_javascripts_inline("\$(function(){$dispatch_to_popup});");
303 $form->{follow_up_trans_info} = $form->{donumber} .'('. $form->{VC_OBJ}->name .')';
305 $::request->{layout}->use_javascript(map { "${_}.js" } qw(kivi.File kivi.MassDeliveryOrderPrint kivi.SalesPurchase kivi.Part ckeditor/ckeditor ckeditor/adapters/jquery kivi.io autocomplete_customer));
308 push @custom_hidden, map { "shiptocvar_" . $_->name } @{ SL::DB::Manager::CustomVariableConfig->get_all(where => [ module => 'ShipTo' ]) };
310 $::form->{HIDDENS} = [ map { +{ name => $_, value => $::form->{$_} } } (@custom_hidden) ];
313 # Fix für Bug 1082 Erwartet wird: 'abteilungsNAME--abteilungsID'
314 # und Erweiterung für Bug 1760:
315 # Das war leider nur ein Teil-Fix, da das Verhalten den 'Erneuern'-Knopf
316 # nicht überlebt. Konsequent jetzt auf L umgestellt
317 # $ perldoc SL::Template::Plugin::L
318 # Daher entsprechend nur die Anpassung in form_header
319 # und in DO.pm gemacht. 4 Testfälle:
320 # department_id speichern | i.O.
321 # department_id lesen | i.O.
322 # department leer überlebt erneuern | i.O.
323 # department nicht leer überlebt erneuern | i.O.
324 # $main::lxdebug->message(0, 'ABTEILUNGS ID in form?' . $form->{department_id});
325 print $form->parse_html_template('do/form_header');
327 $main::lxdebug->leave_sub();
331 $main::lxdebug->enter_sub();
335 my $form = $main::form;
337 $form->{PRINT_OPTIONS} = print_options('inline' => 1);
338 $form->{ALL_DELIVERY_TERMS} = SL::DB::Manager::DeliveryTerm->get_all_sorted();
340 print $form->parse_html_template('do/form_footer',
341 {transfer_default => ($::instance_conf->get_transfer_default)});
343 $main::lxdebug->leave_sub();
346 sub update_delivery_order {
347 $main::lxdebug->enter_sub();
351 my $form = $main::form;
352 my %myconfig = %main::myconfig;
354 set_headings($form->{"id"} ? "edit" : "add");
356 $form->{insertdate} = SL::DB::DeliveryOrder->new(id => $form->{id})->load->itime_as_date if $form->{id};
361 $payment_id = $form->{payment_id} if $form->{payment_id};
363 my $vc = $form->{vc};
364 if (($form->{"previous_${vc}_id"} || $form->{"${vc}_id"}) != $form->{"${vc}_id"}) {
365 $::form->{salesman_id} = SL::DB::Manager::Employee->current->id if exists $::form->{salesman_id};
367 IS->get_customer(\%myconfig, $form) if $vc eq 'customer';
368 IR->get_vendor(\%myconfig, $form) if $vc eq 'vendor';
371 $form->{discount} = $form->{"$form->{vc}_discount"} if defined $form->{"$form->{vc}_discount"};
372 # Problem: Wenn man ohne Erneuern einen Kunden/Lieferanten
373 # wechselt, wird der entsprechende Kunden/ Lieferantenrabatt
374 # nicht übernommen. Grundproblem: In Commit 82574e78
375 # hab ich aus discount customer_discount und vendor_discount
376 # gemacht und entsprechend an den Oberflächen richtig hin-
377 # geschoben. Die damals bessere Lösung wäre gewesen:
378 # In den Templates nur die hidden für form-discount wieder ein-
379 # setzen dann wäre die Verrenkung jetzt nicht notwendig.
380 # TODO: Ggf. Bugfix 1284, 1575 und 817 wieder zusammenführen
381 # Testfälle: Kunden mit Rabatt 0 -> Rabatt 20 i.O.
382 # Kunde mit Rabatt 20 -> Rabatt 0 i.O.
383 # Kunde mit Rabatt 20 -> Rabatt 5,5 i.O.
384 $form->{payment_id} = $payment_id if $form->{payment_id} eq "";
386 my $i = $form->{rowcount};
388 if ( ($form->{"partnumber_$i"} eq "")
389 && ($form->{"description_$i"} eq "")
390 && ($form->{"partsgroup_$i"} eq "")) {
397 if ($form->{type} eq 'purchase_delivery_order') {
398 IR->retrieve_item(\%myconfig, $form);
401 IS->retrieve_item(\%myconfig, $form);
405 my $rows = scalar @{ $form->{item_list} };
408 $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
409 if( !$form->{"qty_$i"} ) {
410 $form->{"qty_$i"} = 1;
415 select_item(mode => $mode, pre_entered_qty => $form->{"qty_$i"});
416 $::dispatcher->end_request;
420 my $sellprice = $form->parse_amount(\%myconfig, $form->{"sellprice_$i"});
422 map { $form->{"${_}_$i"} = $form->{item_list}[0]{$_} } keys %{ $form->{item_list}[0] };
424 $form->{"marge_price_factor_$i"} = $form->{item_list}->[0]->{price_factor};
427 $form->{"sellprice_$i"} = $sellprice;
429 my $record = _make_record();
430 my $price_source = SL::PriceSource->new(record_item => $record->items->[$i-1], record => $record);
431 my $best_price = $price_source->best_price;
432 my $best_discount = $price_source->best_discount;
435 $::form->{"sellprice_$i"} = $best_price->price;
436 $::form->{"active_price_source_$i"} = $best_price->source;
438 if ($best_discount) {
439 $::form->{"discount_$i"} = $best_discount->discount;
440 $::form->{"active_discount_source_$i"} = $best_discount->source;
444 $form->{"sellprice_$i"} = $form->format_amount(\%myconfig, $form->{"sellprice_$i"});
445 $form->{"lastcost_$i"} = $form->format_amount(\%myconfig, $form->{"lastcost_$i"});
446 $form->{"qty_$i"} = $form->format_amount(\%myconfig, $form->{"qty_$i"});
447 $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"} * 100.0);
454 # ok, so this is a new part
455 # ask if it is a part or service item
457 if ( $form->{"partsgroup_$i"}
458 && ($form->{"partsnumber_$i"} eq "")
459 && ($form->{"description_$i"} eq "")) {
461 $form->{"discount_$i"} = "";
462 $form->{"not_discountable_$i"} = "";
466 $form->{"id_$i"} = 0;
472 $main::lxdebug->leave_sub();
476 $main::lxdebug->enter_sub();
480 my $form = $main::form;
481 my %myconfig = %main::myconfig;
482 my $locale = $main::locale;
484 $form->{vc} = $form->{type} eq 'purchase_delivery_order' ? 'vendor' : 'customer';
486 $form->get_lists("projects" => { "key" => "ALL_PROJECTS",
488 "business_types" => "ALL_BUSINESS_TYPES");
489 $form->{ALL_EMPLOYEES} = SL::DB::Manager::Employee->get_all_sorted(query => [ deleted => 0 ]);
490 $form->{ALL_DEPARTMENTS} = SL::DB::Manager::Department->get_all;
491 $form->{title} = $locale->text('Delivery Orders');
495 print $form->parse_html_template('do/search');
497 $main::lxdebug->leave_sub();
501 $main::lxdebug->enter_sub();
505 my $form = $main::form;
506 my %myconfig = %main::myconfig;
507 my $locale = $main::locale;
508 my $cgi = $::request->{cgi};
510 $::request->{layout}->use_javascript(map { "${_}.js" } qw(kivi.MassDeliveryOrderPrint));
511 ($form->{ $form->{vc} }, $form->{"$form->{vc}_id"}) = split(/--/, $form->{ $form->{vc} });
513 report_generator_set_default_sort('transdate', 1);
517 $form->{rowcount} = scalar @{ $form->{DO} };
520 ids transdate reqdate
522 ordnumber customernumber cusordnumber
523 name employee salesman
524 shipvia globalprojectnumber
525 transaction_description department
530 $form->{l_open} = $form->{l_closed} = "Y" if ($form->{open} && $form->{closed});
531 $form->{l_delivered} = "Y" if ($form->{delivered} && $form->{notdelivered});
533 $form->{title} = $locale->text('Delivery Orders');
535 my $attachment_basename = $form->{vc} eq 'vendor' ? $locale->text('purchase_delivery_order_list') : $locale->text('sales_delivery_order_list');
537 my $report = SL::ReportGenerator->new(\%myconfig, $form);
539 my @hidden_variables = map { "l_${_}" } @columns;
540 push @hidden_variables, $form->{vc}, qw(l_closed l_notdelivered open closed delivered notdelivered donumber ordnumber serialnumber cusordnumber
541 transaction_description transdatefrom transdateto reqdatefrom reqdateto
542 type vc employee_id salesman_id project_id parts_partnumber parts_description
543 insertdatefrom insertdateto business_id);
545 my $href = build_std_url('action=orders', grep { $form->{$_} } @hidden_variables);
548 'ids' => { 'text' => '<input type="checkbox" id="multi_all" value="1">', 'align' => 'center' },
549 'transdate' => { 'text' => $locale->text('Delivery Order Date'), },
550 'reqdate' => { 'text' => $locale->text('Reqdate'), },
551 'id' => { 'text' => $locale->text('ID'), },
552 'donumber' => { 'text' => $locale->text('Delivery Order'), },
553 'ordnumber' => { 'text' => $locale->text('Order'), },
554 'customernumber' => { 'text' => $locale->text('Customer Number'), },
555 'cusordnumber' => { 'text' => $locale->text('Customer Order Number'), },
556 'name' => { 'text' => $form->{vc} eq 'customer' ? $locale->text('Customer') : $locale->text('Vendor'), },
557 'employee' => { 'text' => $locale->text('Employee'), },
558 'salesman' => { 'text' => $locale->text('Salesman'), },
559 'shipvia' => { 'text' => $locale->text('Ship via'), },
560 'globalprojectnumber' => { 'text' => $locale->text('Project Number'), },
561 'transaction_description' => { 'text' => $locale->text('Transaction description'), },
562 'open' => { 'text' => $locale->text('Open'), },
563 'delivered' => { 'text' => $locale->text('Delivered'), },
564 'department' => { 'text' => $locale->text('Department'), },
565 'insertdate' => { 'text' => $locale->text('Insert Date'), },
568 foreach my $name (qw(id transdate reqdate donumber ordnumber name employee salesman shipvia transaction_description department insertdate)) {
569 my $sortdir = $form->{sort} eq $name ? 1 - $form->{sortdir} : $form->{sortdir};
570 $column_defs{$name}->{link} = $href . "&sort=$name&sortdir=$sortdir";
573 $form->{"l_type"} = "Y";
574 map { $column_defs{$_}->{visible} = $form->{"l_${_}"} ? 1 : 0 } @columns;
576 $column_defs{ids}->{visible} = 'HTML';
578 $report->set_columns(%column_defs);
579 $report->set_column_order(@columns);
581 $report->set_export_options('orders', @hidden_variables, qw(sort sortdir));
583 $report->set_sort_indicator($form->{sort}, $form->{sortdir});
586 if ($form->{customer}) {
587 push @options, $locale->text('Customer') . " : $form->{customer}";
589 if ($form->{vendor}) {
590 push @options, $locale->text('Vendor') . " : $form->{vendor}";
592 if ($form->{cp_name}) {
593 push @options, $locale->text('Contact Person') . " : $form->{cp_name}";
595 if ($form->{department_id}) {
596 push @options, $locale->text('Department') . " : " . SL::DB::Department->new(id => $form->{department_id})->load->description;
598 if ($form->{donumber}) {
599 push @options, $locale->text('Delivery Order Number') . " : $form->{donumber}";
601 if ($form->{ordnumber}) {
602 push @options, $locale->text('Order Number') . " : $form->{ordnumber}";
604 push @options, $locale->text('Serial Number') . " : $form->{serialnumber}" if $form->{serialnumber};
605 if ($form->{business_id}) {
606 my $vc_type_label = $form->{vc} eq 'customer' ? $locale->text('Customer type') : $locale->text('Vendor type');
607 push @options, $vc_type_label . " : " . SL::DB::Business->new(id => $form->{business_id})->load->description;
609 if ($form->{transaction_description}) {
610 push @options, $locale->text('Transaction description') . " : $form->{transaction_description}";
612 if ($form->{parts_description}) {
613 push @options, $locale->text('Part Description') . " : $form->{parts_description}";
615 if ($form->{parts_partnumber}) {
616 push @options, $locale->text('Part Number') . " : $form->{parts_partnumber}";
618 if ( $form->{transdatefrom} or $form->{transdateto} ) {
619 push @options, $locale->text('Delivery Order Date');
620 push @options, $locale->text('From') . " " . $locale->date(\%myconfig, $form->{transdatefrom}, 1) if $form->{transdatefrom};
621 push @options, $locale->text('Bis') . " " . $locale->date(\%myconfig, $form->{transdateto}, 1) if $form->{transdateto};
623 if ( $form->{reqdatefrom} or $form->{reqdateto} ) {
624 push @options, $locale->text('Reqdate');
625 push @options, $locale->text('From') . " " . $locale->date(\%myconfig, $form->{reqdatefrom}, 1) if $form->{reqdatefrom};
626 push @options, $locale->text('Bis') . " " . $locale->date(\%myconfig, $form->{reqdateto}, 1) if $form->{reqdateto};
628 if ( $form->{insertdatefrom} or $form->{insertdateto} ) {
629 push @options, $locale->text('Insert Date');
630 push @options, $locale->text('From') . " " . $locale->date(\%myconfig, $form->{insertdatefrom}, 1) if $form->{insertdatefrom};
631 push @options, $locale->text('Bis') . " " . $locale->date(\%myconfig, $form->{insertdateto}, 1) if $form->{insertdateto};
634 push @options, $locale->text('Open');
636 if ($form->{closed}) {
637 push @options, $locale->text('Closed');
639 if ($form->{delivered}) {
640 push @options, $locale->text('Delivered');
642 if ($form->{notdelivered}) {
643 push @options, $locale->text('Not delivered');
646 my $pr = SL::DB::Manager::Printer->find_by(
647 printer_description => $::locale->text("sales_delivery_order_printer"));
649 $form->{printer_id} = $pr->id;
652 $report->set_options('top_info_text' => join("\n", @options),
653 'raw_top_info_text' => $form->parse_html_template('do/orders_top'),
654 'raw_bottom_info_text' => $form->parse_html_template('do/orders_bottom', { print_options => print_options(inline => 1,hide_language_id => 1) }),
655 'output_format' => 'HTML',
656 'title' => $form->{title},
657 'attachment_basename' => $attachment_basename . strftime('_%Y%m%d', localtime time),
659 $report->set_options_from_form();
660 $locale->set_numberformat_wo_thousands_separator(\%myconfig) if lc($report->{options}->{output_format}) eq 'csv';
662 # add sort and escape callback, this one we use for the add sub
663 $form->{callback} = $href .= "&sort=$form->{sort}";
665 # escape callback for href
666 my $callback = $form->escape($href);
668 my $edit_url = build_std_url('action=edit', 'type', 'vc');
669 my $edit_order_url = build_std_url('script=oe.pl', 'type=' . ($form->{type} eq 'sales_delivery_order' ? 'sales_order' : 'purchase_order'), 'action=edit');
673 foreach my $dord (@{ $form->{DO} }) {
674 $dord->{open} = $dord->{closed} ? $locale->text('No') : $locale->text('Yes');
675 $dord->{delivered} = $dord->{delivered} ? $locale->text('Yes') : $locale->text('No');
677 my $row = { map { $_ => { 'data' => $dord->{$_} } } @columns };
679 my $ord_id = $dord->{id};
681 'raw_data' => $cgi->hidden('-name' => "trans_id_${idx}", '-value' => $ord_id)
682 . $cgi->checkbox('-name' => "multi_id_${idx}",' id' => "multi_id_id_".$ord_id, '-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} . "_id", $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 my $vc = $form->{vc};
730 if (($form->{"previous_${vc}_id"} || $form->{"${vc}_id"}) != $form->{"${vc}_id"}) {
731 $::form->{salesman_id} = SL::DB::Manager::Employee->current->id if exists $::form->{salesman_id};
733 IS->get_customer(\%myconfig, $form) if $vc eq 'customer';
734 IR->get_vendor(\%myconfig, $form) if $vc eq 'vendor';
737 $::dispatcher->end_request;
740 $form->{id} = 0 if $form->{saveasnew};
744 if(!exists $form->{addition}) {
745 $form->{snumbers} = qq|donumber_| . $form->{donumber};
746 $form->{addition} = "SAVED";
749 # /saving the history
751 $form->{simple_save} = 1;
752 if (!$params{no_redirect} && !$form->{print_and_save}) {
753 delete @{$form}{ary_diff([keys %{ $form }], [qw(login id script type cursor_fokus)])};
755 $::dispatcher->end_request;
757 $main::lxdebug->leave_sub();
761 $main::lxdebug->enter_sub();
765 my $form = $main::form;
766 my %myconfig = %main::myconfig;
767 my $locale = $main::locale;
771 if(!exists $form->{addition}) {
772 $form->{snumbers} = qq|donumber_| . $form->{donumber};
773 $form->{addition} = "DELETED";
776 # /saving the history
778 $form->info($locale->text('Delivery Order deleted!'));
779 $::dispatcher->end_request;
782 $form->error($locale->text('Cannot delete delivery order!'));
784 $main::lxdebug->leave_sub();
788 $main::lxdebug->enter_sub();
790 my $form = $main::form;
791 my %myconfig = %main::myconfig;
792 my $locale = $main::locale;
795 $form->mtime_ischanged('delivery_orders');
797 $main::auth->assert($form->{type} eq 'purchase_delivery_order' ? 'vendor_invoice_edit' : 'invoice_edit');
799 $form->{convert_from_do_ids} = $form->{id};
800 $form->{deliverydate} = $form->{transdate};
801 $form->{transdate} = $form->{invdate} = $form->current_date(\%myconfig);
802 $form->{duedate} = $form->current_date(\%myconfig, $form->{invdate}, $form->{terms} * 1);
803 $form->{defaultcurrency} = $form->get_default_currency(\%myconfig);
807 delete @{$form}{qw(id closed delivered)};
809 my ($script, $buysell);
810 if ($form->{type} eq 'purchase_delivery_order') {
811 $form->{title} = $locale->text('Add Vendor Invoice');
812 $form->{script} = 'ir.pl';
817 $form->{title} = $locale->text('Add Sales Invoice');
818 $form->{script} = 'is.pl';
823 for my $i (1 .. $form->{rowcount}) {
824 map { $form->{"${_}_${i}"} = $form->parse_amount(\%myconfig, $form->{"${_}_${i}"}) if $form->{"${_}_${i}"} } qw(ship qty sellprice lastcost basefactor discount);
826 # adds a customer/vendor discount, unless we have a workflow case
827 # CAVEAT: has to be done, after the above parse_amount
828 unless ($form->{"ordnumber"}) {
829 if ($form->{discount}) { # Falls wir einen Lieferanten-/Kundenrabatt haben
830 # und rabattfähig sind, dann
831 unless ($form->{"not_discountable_$i"}) {
832 $form->{"discount_$i"} = $form->{discount}*100; # ... nehmen wir diesen Rabatt
836 $form->{"donumber_$i"} = $form->{donumber};
837 $form->{"converted_from_delivery_order_items_id_$i"} = delete $form->{"delivery_order_items_id_$i"};
840 $form->{type} = "invoice";
843 $main::locale = Locale->new("$myconfig{countrycode}", "$script");
844 $locale = $main::locale;
846 require "bin/mozilla/$form->{script}";
848 my $currency = $form->{currency};
851 if ($form->{ordnumber}) {
852 require SL::DB::Order;
853 if (my $order = SL::DB::Manager::Order->find_by(ordnumber => $form->{ordnumber})) {
855 $form->{orddate} = $order->transdate_as_date;
856 $form->{$_} = $order->$_ for qw(payment_id salesman_id taxzone_id quonumber);
860 $form->{currency} = $currency;
861 $form->{exchangerate} = "";
862 $form->{forex} = $form->check_exchangerate(\%myconfig, $form->{currency}, $form->{invdate}, $buysell);
863 $form->{exchangerate} = $form->{forex} if ($form->{forex});
868 for my $i (1 .. $form->{rowcount}) {
869 $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"});
871 my ($dec) = ($form->{"sellprice_$i"} =~ /\.(\d+)/);
873 my $decimalplaces = ($dec > 2) ? $dec : 2;
875 # copy delivery date from reqdate for order -> invoice conversion
876 $form->{"deliverydate_$i"} = $form->{"reqdate_$i"}
877 unless $form->{"deliverydate_$i"};
880 $form->{"sellprice_$i"} =
881 $form->format_amount(\%myconfig, $form->{"sellprice_$i"},
884 $form->{"lastcost_$i"} =
885 $form->format_amount(\%myconfig, $form->{"lastcost_$i"},
888 (my $dec_qty) = ($form->{"qty_$i"} =~ /\.(\d+)/);
889 $dec_qty = length $dec_qty;
891 $form->format_amount(\%myconfig, $form->{"qty_$i"}, $dec_qty);
897 $main::lxdebug->leave_sub();
901 $main::lxdebug->enter_sub();
903 my $form = $main::form;
904 my %myconfig = %main::myconfig;
905 my $locale = $main::locale;
908 $main::auth->assert($form->{type} eq 'sales_delivery_order' ? 'invoice_edit' : 'vendor_invoice_edit');
910 my @do_ids = map { $form->{"trans_id_$_"} } grep { $form->{"multi_id_$_"} } (1..$form->{rowcount});
912 if (!scalar @do_ids) {
913 $form->show_generic_error($locale->text('You have not selected any delivery order.'));
916 map { delete $form->{$_} } grep { m/^(?:trans|multi)_id_\d+/ } keys %{ $form };
918 if (!DO->retrieve('vc' => $form->{vc}, 'ids' => \@do_ids)) {
919 $form->show_generic_error($form->{vc} eq 'customer' ?
920 $locale->text('You cannot create an invoice for delivery orders for different customers.') :
921 $locale->text('You cannot create an invoice for delivery orders from different vendors.'),
925 my $source_type = $form->{type};
926 $form->{convert_from_do_ids} = join ' ', @do_ids;
927 # bei der auswahl von mehreren Lieferscheinen fuer eine Rechnung, die einfach in donumber_array
928 # zwischenspeichern (DO.pm) und als ' '-separierte Liste wieder zurueckschreiben
929 # Hinweis: delete gibt den wert zurueck und loescht danach das element (nett und einfach)
930 # $shell: perldoc perlunc; /delete EXPR
931 $form->{donumber} = delete $form->{donumber_array};
932 $form->{ordnumber} = delete $form->{ordnumber_array};
933 $form->{cusordnumber} = delete $form->{cusordnumber_array};
934 $form->{deliverydate} = $form->{transdate};
935 $form->{transdate} = $form->current_date(\%myconfig);
936 $form->{duedate} = $form->current_date(\%myconfig, $form->{invdate}, $form->{terms} * 1);
937 $form->{type} = "invoice";
939 $form->{defaultcurrency} = $form->get_default_currency(\%myconfig);
941 my ($script, $buysell);
942 if ($source_type eq 'purchase_delivery_order') {
943 $form->{title} = $locale->text('Add Vendor Invoice');
944 $form->{script} = 'ir.pl';
949 $form->{title} = $locale->text('Add Sales Invoice');
950 $form->{script} = 'is.pl';
955 map { delete $form->{$_} } qw(id subject message cc bcc printed emailed queued);
957 # get vendor or customer discount
959 my $saved_form = save_form();
960 if ($form->{vc} eq 'vendor') {
961 IR->get_vendor(\%myconfig, \%$form);
962 $vc_discount = $form->{vendor_discount};
964 IS->get_customer(\%myconfig, \%$form);
965 $vc_discount = $form->{customer_discount};
967 # use payment terms from customer or vendor
968 restore_form($saved_form,0,qw(payment_id));
970 $form->{rowcount} = 0;
971 foreach my $ref (@{ $form->{form_details} }) {
973 $ref->{reqdate} ||= $ref->{dord_transdate}; # copy transdates into each invoice row
974 map { $form->{"${_}_$form->{rowcount}"} = $ref->{$_} } keys %{ $ref };
975 map { $form->{"${_}_$form->{rowcount}"} = $form->format_amount(\%myconfig, $ref->{$_}) } qw(qty sellprice lastcost);
976 $form->{"converted_from_delivery_order_items_id_$form->{rowcount}"} = delete $form->{"delivery_order_items_id_$form->{rowcount}"};
978 if ($vc_discount){ # falls wir einen Lieferanten/Kundenrabatt haben
979 # und keinen anderen discount wert an $i ...
980 $form->{"discount_$form->{rowcount}"} ||= $vc_discount; # ... nehmen wir diesen Rabatt
983 $form->{"discount_$form->{rowcount}"} = $form->{"discount_$form->{rowcount}"} * 100; #s.a. Bug 1151
984 # Anm.: Eine Änderung des discounts in der SL/DO.pm->retrieve (select (doi.discount * 100) as discount) ergibt in psql einen
985 # Wert von 10.0000001490116. Ferner ist der Rabatt in der Rechnung dann bei 1.0 (?). Deswegen lasse ich das hier. jb 10.10.09
987 $form->{"discount_$form->{rowcount}"} = $form->format_amount(\%myconfig, $form->{"discount_$form->{rowcount}"});
989 delete $form->{form_details};
991 $locale = Locale->new("$myconfig{countrycode}", "$script");
993 require "bin/mozilla/$form->{script}";
1000 $main::lxdebug->leave_sub();
1004 $main::lxdebug->enter_sub();
1008 my $form = $main::form;
1010 $form->{saveasnew} = 1;
1011 $form->{closed} = 0;
1012 $form->{delivered} = 0;
1013 map { delete $form->{$_} } qw(printed emailed queued);
1014 delete @{ $form }{ grep { m/^stock_(?:in|out)_\d+/ } keys %{ $form } };
1015 $form->{"converted_from_delivery_order_items_id_$_"} = delete $form->{"delivery_order_items_id_$_"} for 1 .. $form->{"rowcount"};
1016 # Let kivitendo assign a new order number if the user hasn't changed the
1017 # previous one. If it has been changed manually then use it as-is.
1018 $form->{donumber} =~ s/^\s*//g;
1019 $form->{donumber} =~ s/\s*$//g;
1020 if ($form->{saved_donumber} && ($form->{saved_donumber} eq $form->{donumber})) {
1021 delete($form->{donumber});
1026 $main::lxdebug->leave_sub();
1030 $main::lxdebug->enter_sub();
1034 $::form->mtime_ischanged('delivery_orders','mail');
1036 $::form->{print_and_save} = 1;
1038 my $saved_form = save_form();
1042 restore_form($saved_form, 0, qw(id ordnumber quonumber));
1046 $main::lxdebug->leave_sub();
1049 sub calculate_stock_in_out {
1050 $main::lxdebug->enter_sub();
1052 my $form = $main::form;
1056 if (!$form->{"id_${i}"}) {
1057 $main::lxdebug->leave_sub();
1061 my $all_units = AM->retrieve_all_units();
1063 my $in_out = $form->{type} =~ /^sales/ ? 'out' : 'in';
1064 my $sinfo = DO->unpack_stock_information('packed' => $form->{"stock_${in_out}_${i}"});
1066 my $do_qty = AM->sum_with_unit($::form->{"qty_$i"}, $::form->{"unit_$i"});
1067 my $sum = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $sinfo });
1068 my $matches = $do_qty == $sum;
1070 my $content = $form->format_amount_units('amount' => $sum * 1,
1071 'part_unit' => $form->{"partunit_$i"},
1072 'amount_unit' => $all_units->{$form->{"partunit_$i"}}->{base_unit},
1073 'conv_units' => 'convertible_not_smaller',
1075 $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="?">|;
1077 $main::lxdebug->leave_sub();
1082 sub get_basic_bin_wh_info {
1083 $main::lxdebug->enter_sub();
1085 my $stock_info = shift;
1087 my $form = $main::form;
1089 foreach my $sinfo (@{ $stock_info }) {
1090 next unless ($sinfo->{bin_id});
1092 my $bin_info = WH->get_basic_bin_info('id' => $sinfo->{bin_id});
1093 map { $sinfo->{"${_}_description"} = $sinfo->{"${_}description"} = $bin_info->{"${_}_description"} } qw(bin warehouse);
1096 $main::lxdebug->leave_sub();
1099 sub stock_in_out_form {
1100 $main::lxdebug->enter_sub();
1102 my $form = $main::form;
1104 if ($form->{in_out} eq 'out') {
1110 $main::lxdebug->leave_sub();
1113 sub redo_stock_info {
1114 $main::lxdebug->enter_sub();
1118 my $form = $main::form;
1120 my @non_empty = grep { $_->{qty} } @{ $params{stock_info} };
1122 if ($params{add_empty_row}) {
1124 'warehouse_id' => scalar(@non_empty) ? $non_empty[-1]->{warehouse_id} : undef,
1125 'bin_id' => scalar(@non_empty) ? $non_empty[-1]->{bin_id} : undef,
1129 @{ $params{stock_info} } = @non_empty;
1131 $main::lxdebug->leave_sub();
1134 sub update_stock_in {
1135 $main::lxdebug->enter_sub();
1137 my $form = $main::form;
1138 my %myconfig = %main::myconfig;
1140 my $stock_info = [];
1142 foreach my $i (1..$form->{rowcount}) {
1143 $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
1144 push @{ $stock_info }, { map { $_ => $form->{"${_}_${i}"} } qw(warehouse_id bin_id chargenumber
1145 bestbefore qty unit delivery_order_items_stock_id) };
1148 display_stock_in_form($stock_info);
1150 $main::lxdebug->leave_sub();
1154 $main::lxdebug->enter_sub();
1156 my $form = $main::form;
1158 my $stock_info = DO->unpack_stock_information('packed' => $form->{stock});
1160 display_stock_in_form($stock_info);
1162 $main::lxdebug->leave_sub();
1165 sub display_stock_in_form {
1166 $main::lxdebug->enter_sub();
1168 my $stock_info = shift;
1170 my $form = $main::form;
1171 my %myconfig = %main::myconfig;
1172 my $locale = $main::locale;
1174 $form->{title} = $locale->text('Stock');
1176 my $part_info = IC->get_basic_part_info('id' => $form->{parts_id});
1178 # Standardlagerplatz für Standard-Auslagern verwenden, falls keiner für die Ware explizit definiert wurde
1179 if ($::instance_conf->get_transfer_default_use_master_default_bin) {
1180 $part_info->{warehouse_id} ||= $::instance_conf->get_warehouse_id;
1181 $part_info->{bin_id} ||= $::instance_conf->get_bin_id;
1184 my $units = AM->retrieve_units(\%myconfig, $form);
1185 # der zweite Parameter von unit_select_data gibt den default-Namen (selected) vor
1186 my $units_data = AM->unit_select_data($units, $form->{do_unit}, undef, $part_info->{unit});
1188 $form->get_lists('warehouses' => { 'key' => 'WAREHOUSES',
1189 'bins' => 'BINS' });
1191 redo_stock_info('stock_info' => $stock_info, 'add_empty_row' => !$form->{delivered});
1193 get_basic_bin_wh_info($stock_info);
1195 $form->header(no_layout => 1);
1196 print $form->parse_html_template('do/stock_in_form', { 'UNITS' => $units_data,
1197 'STOCK_INFO' => $stock_info,
1198 'PART_INFO' => $part_info, });
1200 $main::lxdebug->leave_sub();
1203 sub _stock_in_out_set_qty_display {
1204 my $stock_info = shift;
1206 my $all_units = AM->retrieve_all_units();
1207 my $sum = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
1208 $form->{qty_display} = $form->format_amount_units(amount => $sum * 1,
1209 part_unit => $form->{partunit},
1210 amount_unit => $all_units->{ $form->{partunit} }->{base_unit},
1211 conv_units => 'convertible_not_smaller',
1216 $main::lxdebug->enter_sub();
1218 my $form = $main::form;
1219 my %myconfig = %main::myconfig;
1221 my $stock_info = [];
1223 foreach my $i (1..$form->{rowcount}) {
1224 $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
1226 next if ($form->{"qty_$i"} <= 0);
1228 push @{ $stock_info }, { map { $_ => $form->{"${_}_${i}"} } qw(delivery_order_items_stock_id warehouse_id bin_id chargenumber bestbefore qty unit) };
1231 $form->{stock} = YAML::Dump($stock_info);
1233 _stock_in_out_set_qty_display($stock_info);
1235 my $do_qty = AM->sum_with_unit($::form->parse_amount(\%::myconfig, $::form->{do_qty}), $::form->{do_unit});
1236 my $transfer_qty = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
1239 print $form->parse_html_template('do/set_stock_in_out', {
1240 qty_matches => $do_qty == $transfer_qty,
1243 $main::lxdebug->leave_sub();
1246 sub stock_out_form {
1247 $main::lxdebug->enter_sub();
1249 my $form = $main::form;
1250 my %myconfig = %main::myconfig;
1251 my $locale = $main::locale;
1253 $form->{title} = $locale->text('Release From Stock');
1255 my $part_info = IC->get_basic_part_info('id' => $form->{parts_id});
1257 my $units = AM->retrieve_units(\%myconfig, $form);
1258 my $units_data = AM->unit_select_data($units, undef, undef, $part_info->{unit});
1260 my @contents = DO->get_item_availability('parts_id' => $form->{parts_id});
1262 my $stock_info = DO->unpack_stock_information('packed' => $form->{stock});
1264 if (!$form->{delivered}) {
1265 foreach my $row (@contents) {
1266 $row->{available_qty} = $form->format_amount_units('amount' => $row->{qty} * 1,
1267 'part_unit' => $part_info->{unit},
1268 'conv_units' => 'convertible_not_smaller',
1271 foreach my $sinfo (@{ $stock_info }) {
1272 next if (($row->{bin_id} != $sinfo->{bin_id}) ||
1273 ($row->{warehouse_id} != $sinfo->{warehouse_id}) ||
1274 ($row->{chargenumber} ne $sinfo->{chargenumber}) ||
1275 ($row->{bestbefore} ne $sinfo->{bestbefore}));
1277 map { $row->{"stock_$_"} = $sinfo->{$_} } qw(qty unit error delivery_order_items_stock_id);
1282 get_basic_bin_wh_info($stock_info);
1284 foreach my $sinfo (@{ $stock_info }) {
1285 map { $sinfo->{"stock_$_"} = $sinfo->{$_} } qw(qty unit);
1289 $form->header(no_layout => 1);
1290 print $form->parse_html_template('do/stock_out_form', { 'UNITS' => $units_data,
1291 'WHCONTENTS' => $form->{delivered} ? $stock_info : \@contents,
1292 'PART_INFO' => $part_info, });
1294 $main::lxdebug->leave_sub();
1298 $main::lxdebug->enter_sub();
1300 my $form = $main::form;
1301 my %myconfig = %main::myconfig;
1302 my $locale = $main::locale;
1304 my $stock_info = [];
1306 foreach my $i (1 .. $form->{rowcount}) {
1307 $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
1309 next if ($form->{"qty_$i"} <= 0);
1311 push @{ $stock_info }, {
1312 'warehouse_id' => $form->{"warehouse_id_$i"},
1313 'bin_id' => $form->{"bin_id_$i"},
1314 'chargenumber' => $form->{"chargenumber_$i"},
1315 'bestbefore' => $form->{"bestbefore_$i"},
1316 'qty' => $form->{"qty_$i"},
1317 'unit' => $form->{"unit_$i"},
1319 'delivery_order_items_stock_id' => $form->{"delivery_order_items_stock_id_$i"},
1323 my @errors = DO->check_stock_availability('requests' => $stock_info,
1324 'parts_id' => $form->{parts_id});
1326 $form->{stock} = YAML::Dump($stock_info);
1329 $form->{ERRORS} = [];
1330 map { push @{ $form->{ERRORS} }, $locale->text('Error in row #1: The quantity you entered is bigger than the stocked quantity.', $_->{row}); } @errors;
1331 stock_in_out_form();
1334 _stock_in_out_set_qty_display($stock_info);
1336 my $do_qty = AM->sum_with_unit($::form->parse_amount(\%::myconfig, $::form->{do_qty}), $::form->{do_unit});
1337 my $transfer_qty = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
1340 print $form->parse_html_template('do/set_stock_in_out', {
1341 qty_matches => $do_qty == $transfer_qty,
1345 $main::lxdebug->leave_sub();
1349 $main::lxdebug->enter_sub();
1351 my $form = $main::form;
1352 my %myconfig = %main::myconfig;
1353 my $locale = $main::locale;
1355 if ($form->{id} && DO->is_marked_as_delivered(id => $form->{id})) {
1356 $form->show_generic_error($locale->text('The parts for this delivery order have already been transferred in.'));
1359 save(no_redirect => 1);
1361 my @part_ids = map { $form->{"id_${_}"} } grep { $form->{"id_${_}"} && $form->{"stock_in_${_}"} } (1 .. $form->{rowcount});
1365 my $units = AM->retrieve_units(\%myconfig, $form);
1366 my %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
1369 $form->{ERRORS} = [];
1371 foreach my $i (1 .. $form->{rowcount}) {
1372 next unless ($form->{"id_$i"} && $form->{"stock_in_$i"});
1374 my $row_sum_base_qty = 0;
1375 my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
1377 foreach my $request (@{ DO->unpack_stock_information('packed' => $form->{"stock_in_$i"}) }) {
1378 $request->{parts_id} = $form->{"id_$i"};
1379 $row_sum_base_qty += $request->{qty} * $units->{$request->{unit}}->{factor} / $base_unit_factor;
1381 $request->{project_id} = $form->{"project_id_$i"} || $form->{globalproject_id};
1383 push @all_requests, $request;
1386 next if (0 == $row_sum_base_qty);
1388 my $do_base_qty = $form->parse_amount(\%myconfig, $form->{"qty_$i"}) * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
1390 # if ($do_base_qty != $row_sum_base_qty) {
1391 # push @{ $form->{ERRORS} }, $locale->text('Error in position #1: You must either assign no stock at all or the full quantity of #2 #3.',
1392 # $i, $form->{"qty_$i"}, $form->{"unit_$i"});
1396 if (@{ $form->{ERRORS} }) {
1397 push @{ $form->{ERRORS} }, $locale->text('The delivery order has not been marked as delivered. The warehouse contents have not changed.');
1399 set_headings('edit');
1401 $main::lxdebug->leave_sub();
1403 $::dispatcher->end_request;
1407 DO->transfer_in_out('direction' => 'in',
1408 'requests' => \@all_requests);
1410 SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
1412 $form->{callback} = 'do.pl?action=edit&type=purchase_delivery_order&id=' . $form->escape($form->{id});
1415 $main::lxdebug->leave_sub();
1419 $main::lxdebug->enter_sub();
1421 my $form = $main::form;
1422 my %myconfig = %main::myconfig;
1423 my $locale = $main::locale;
1425 if ($form->{id} && DO->is_marked_as_delivered(id => $form->{id})) {
1426 $form->show_generic_error($locale->text('The parts for this delivery order have already been transferred out.'));
1429 save(no_redirect => 1);
1431 my @part_ids = map { $form->{"id_${_}"} } grep { $form->{"id_${_}"} && $form->{"stock_out_${_}"} } (1 .. $form->{rowcount});
1435 my $units = AM->retrieve_units(\%myconfig, $form);
1436 my %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
1439 $form->{ERRORS} = [];
1441 foreach my $i (1 .. $form->{rowcount}) {
1442 next unless ($form->{"id_$i"} && $form->{"stock_out_$i"});
1444 my $row_sum_base_qty = 0;
1445 my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
1447 foreach my $request (@{ DO->unpack_stock_information('packed' => $form->{"stock_out_$i"}) }) {
1448 $request->{parts_id} = $form->{"id_$i"};
1449 $request->{base_qty} = $request->{qty} * $units->{$request->{unit}}->{factor} / $base_unit_factor;
1450 $request->{project_id} = $form->{"project_id_$i"} ? $form->{"project_id_$i"} : $form->{globalproject_id};
1452 my $map_key = join '--', ($form->{"id_$i"}, @{$request}{qw(warehouse_id bin_id chargenumber bestbefore)});
1454 $request_map{$map_key} ||= $request;
1455 $request_map{$map_key}->{sum_base_qty} ||= 0;
1456 $request_map{$map_key}->{sum_base_qty} += $request->{base_qty};
1457 $row_sum_base_qty += $request->{base_qty};
1459 push @all_requests, $request;
1462 next if (0 == $row_sum_base_qty);
1464 my $do_base_qty = $form->{"qty_$i"} * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
1466 # if ($do_base_qty != $row_sum_base_qty) {
1467 # push @{ $form->{ERRORS} }, $locale->text('Error in position #1: You must either assign no transfer at all or the full quantity of #2 #3.',
1468 # $i, $form->{"qty_$i"}, $form->{"unit_$i"});
1473 my @bin_ids = map { $_->{bin_id} } values %request_map;
1474 my %bin_info_map = WH->get_basic_bin_info('id' => \@bin_ids);
1475 my @contents = DO->get_item_availability('parts_id' => \@part_ids);
1477 foreach my $inv (@contents) {
1478 my $map_key = join '--', @{$inv}{qw(parts_id warehouse_id bin_id chargenumber bestbefore)};
1480 next unless ($request_map{$map_key});
1482 my $request = $request_map{$map_key};
1483 $request->{ok} = $request->{sum_base_qty} <= $inv->{qty};
1486 foreach my $request (values %request_map) {
1487 next if ($request->{ok});
1489 my $pinfo = $part_info_map{$request->{parts_id}};
1490 my $binfo = $bin_info_map{$request->{bin_id}};
1492 if ($::instance_conf->get_show_bestbefore) {
1493 push @{ $form->{ERRORS} }, $locale->text("There is not enough available of '#1' at warehouse '#2', bin '#3', #4, #5, for the transfer of #6.",
1494 $pinfo->{description},
1495 $binfo->{warehouse_description},
1496 $binfo->{bin_description},
1497 $request->{chargenumber} ? $locale->text('chargenumber #1', $request->{chargenumber}) : $locale->text('no chargenumber'),
1498 $request->{bestbefore} ? $locale->text('bestbefore #1', $request->{bestbefore}) : $locale->text('no bestbefore'),
1499 $form->format_amount_units('amount' => $request->{sum_base_qty},
1500 'part_unit' => $pinfo->{unit},
1501 'conv_units' => 'convertible_not_smaller'));
1503 push @{ $form->{ERRORS} }, $locale->text("There is not enough available of '#1' at warehouse '#2', bin '#3', #4, for the transfer of #5.",
1504 $pinfo->{description},
1505 $binfo->{warehouse_description},
1506 $binfo->{bin_description},
1507 $request->{chargenumber} ? $locale->text('chargenumber #1', $request->{chargenumber}) : $locale->text('no chargenumber'),
1508 $form->format_amount_units('amount' => $request->{sum_base_qty},
1509 'part_unit' => $pinfo->{unit},
1510 'conv_units' => 'convertible_not_smaller'));
1515 if (@{ $form->{ERRORS} }) {
1516 push @{ $form->{ERRORS} }, $locale->text('The delivery order has not been marked as delivered. The warehouse contents have not changed.');
1518 set_headings('edit');
1520 $main::lxdebug->leave_sub();
1522 $::dispatcher->end_request;
1525 DO->transfer_in_out('direction' => 'out',
1526 'requests' => \@all_requests);
1528 SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
1530 $form->{callback} = 'do.pl?action=edit&type=sales_delivery_order&id=' . $form->escape($form->{id});
1533 $main::lxdebug->leave_sub();
1537 $main::lxdebug->enter_sub();
1539 my $form = $main::form;
1541 DO->close_orders('ids' => [ $form->{id} ]);
1543 $form->{closed} = 1;
1547 $main::lxdebug->leave_sub();
1551 $::lxdebug->enter_sub;
1553 $::auth->assert('purchase_delivery_order_edit | sales_delivery_order_edit');
1556 retrieve_partunits();
1558 my $new_rowcount = $::form->{"rowcount"} * 1 + 1;
1559 $::form->{"project_id_${new_rowcount}"} = $::form->{"globalproject_id"};
1561 $::form->language_payment(\%::myconfig);
1563 Common::webdav_folder($::form);
1566 display_row(++$::form->{rowcount});
1569 $::lxdebug->leave_sub;
1573 call_sub($main::form->{yes_nextsub});
1577 call_sub($main::form->{no_nextsub});
1581 call_sub($main::form->{update_nextsub} || $main::form->{nextsub} || 'update_delivery_order');
1585 my $form = $main::form;
1586 my $locale = $main::locale;
1588 foreach my $action (qw(update ship_to print e_mail save transfer_out transfer_out_default sort
1589 transfer_in transfer_in_default mark_closed save_as_new invoice delete)) {
1590 if ($form->{"action_${action}"}) {
1596 $form->error($locale->text('No action defined.'));
1599 sub transfer_out_default {
1600 $main::lxdebug->enter_sub();
1602 my $form = $main::form;
1604 transfer_in_out_default('direction' => 'out');
1606 $main::lxdebug->leave_sub();
1609 sub transfer_in_default {
1610 $main::lxdebug->enter_sub();
1612 my $form = $main::form;
1614 transfer_in_out_default('direction' => 'in');
1616 $main::lxdebug->leave_sub();
1619 # Falls das Standardlagerverfahren aktiv ist, wird
1620 # geprüft, ob alle Standardlagerplätze für die Auslager-
1621 # artikel vorhanden sind UND ob die Warenmenge ausreicht zum
1622 # Auslagern. Falls nicht wird entsprechend eine Fehlermeldung
1623 # generiert. Offen Chargennummer / bestbefore wird nicht berücksichtigt
1624 sub transfer_in_out_default {
1625 $main::lxdebug->enter_sub();
1627 my $form = $main::form;
1628 my %myconfig = %main::myconfig;
1629 my $locale = $main::locale;
1632 my (%missing_default_bins, %qty_parts, @all_requests, %part_info_map, $default_warehouse_id, $default_bin_id);
1634 Common::check_params(\%params, qw(direction));
1636 # entsprechende defaults holen, falls standardlagerplatz verwendet werden soll
1637 if ($::instance_conf->get_transfer_default_use_master_default_bin) {
1638 $default_warehouse_id = $::instance_conf->get_warehouse_id;
1639 $default_bin_id = $::instance_conf->get_bin_id;
1643 my @part_ids = map { $form->{"id_${_}"} } (1 .. $form->{rowcount});
1645 my $units = AM->retrieve_units(\%myconfig, $form);
1646 %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
1647 foreach my $i (1 .. $form->{rowcount}) {
1648 next unless ($form->{"id_$i"});
1649 my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
1650 my $qty = $form->parse_amount(\%myconfig, $form->{"qty_$i"}) * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
1652 $form->show_generic_error($locale->text("Cannot transfer negative entries." )) if ($qty < 0);
1653 # if we do not want to transfer services and this part is a service, set qty to zero
1654 # ... and do not create a hash entry in %qty_parts below (will skip check for bins for the transfer == out case)
1655 # ... 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)
1657 $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});
1658 $qty_parts{$form->{"id_$i"}} += $qty;
1660 delete $qty_parts{$form->{"id_$i"}} unless $qty_parts{$form->{"id_$i"}};
1661 undef $form->{"stock_in_$i"};
1664 $part_info_map{$form->{"id_$i"}}{bin_id} ||= $default_bin_id;
1665 $part_info_map{$form->{"id_$i"}}{warehouse_id} ||= $default_warehouse_id;
1667 push @all_requests, ($qty == 0) ? { } : {
1668 'chargenumber' => '', #?? die müsste entsprechend geholt werden
1669 #'bestbefore' => undef, # TODO wird nicht berücksichtigt
1670 'bin_id' => $part_info_map{$form->{"id_$i"}}{bin_id},
1672 'parts_id' => $form->{"id_$i"},
1673 'comment' => $locale->text("Default transfer delivery order"),
1674 'unit' => $part_info_map{$form->{"id_$i"}}{unit},
1675 'warehouse_id' => $part_info_map{$form->{"id_$i"}}{warehouse_id},
1676 'oe_id' => $form->{id},
1677 'project_id' => $form->{"project_id_$i"} ? $form->{"project_id_$i"} : $form->{globalproject_id}
1681 # jetzt wird erst überprüft, ob die Stückzahl entsprechend stimmt.
1682 # check if bin (transfer in and transfer out and qty (transfer out) is correct
1683 foreach my $key (keys %qty_parts) {
1685 $missing_default_bins{$key}{missing_bin} = 1 unless ($part_info_map{$key}{bin_id});
1686 next unless ($part_info_map{$key}{bin_id}); # abbruch
1688 if ($params{direction} eq 'out') { # wird nur für ausgehende Mengen benötigt
1689 my ($max_qty, $error) = WH->get_max_qty_parts_bin(parts_id => $key, bin_id => $part_info_map{$key}{bin_id});
1691 # wir können nicht entscheiden, welche charge oder mhd (bestbefore) ausgewählt sein soll
1692 # deshalb rückmeldung nach oben geben, manuell auszulagern
1693 # TODO Bei nur einem Treffer mit Charge oder bestbefore wäre das noch möglich
1694 $missing_default_bins{$key}{chargenumber} = 1;
1696 if ($max_qty < $qty_parts{$key}){
1697 $missing_default_bins{$key}{missing_qty} = $max_qty - $qty_parts{$key};
1703 # Abfrage für Fehlerbehandlung (nur bei direction == out)
1704 if (scalar (keys %missing_default_bins)) {
1706 foreach my $fehler (keys %missing_default_bins) {
1708 my $ware = WH->get_part_description(parts_id => $fehler);
1709 if ($missing_default_bins{$fehler}{missing_bin}){
1710 $fehlertext .= "Kein Standardlagerplatz definiert bei $ware <br>";
1712 if ($missing_default_bins{$fehler}{missing_qty}) { # missing_qty
1713 $fehlertext .= "Es fehlen " . $missing_default_bins{$fehler}{missing_qty}*-1 .
1714 " von $ware auf dem Standard-Lagerplatz " . $part_info_map{$fehler}{bin} . " zum Auslagern<br>";
1716 if ($missing_default_bins{$fehler}{chargenumber}){
1717 $fehlertext .= "Die Ware hat eine Chargennummer oder eine Mindesthaltbarkeit definiert.
1718 Hier kann man nicht automatisch entscheiden.
1719 Bitte diesen Lieferschein manuell auslagern.
1722 # auslagern soll immer gehen, auch wenn nicht genügend auf lager ist.
1723 # der lagerplatz ist hier extra konfigurierbar, bspw. Lager-Korrektur mit
1724 # Lagerplatz Lagerplatz-Korrektur
1725 my $default_warehouse_id_ignore_onhand = $::instance_conf->get_warehouse_id_ignore_onhand;
1726 my $default_bin_id_ignore_onhand = $::instance_conf->get_bin_id_ignore_onhand;
1727 if ($::instance_conf->get_transfer_default_ignore_onhand && $default_bin_id_ignore_onhand) {
1728 # entsprechende defaults holen
1729 # falls chargenumber, bestbefore oder anzahl nicht stimmt, auf automatischen
1730 # lagerplatz wegbuchen!
1731 foreach (@all_requests) {
1732 if ($_->{parts_id} eq $fehler){
1733 $_->{bin_id} = $default_bin_id_ignore_onhand;
1734 $_->{warehouse_id} = $default_warehouse_id_ignore_onhand;
1738 #$main::lxdebug->message(0, 'Fehlertext: ' . $fehlertext);
1739 $form->show_generic_error($locale->text("Cannot transfer. <br> Reason:<br>#1", $fehlertext ));
1745 # hier der eigentliche fallunterschied für in oder out
1746 my $prefix = $params{direction} eq 'in' ? 'in' : 'out';
1748 # dieser array_ref ist für DO->save da:
1749 # einmal die all_requests in YAML verwandeln, damit delivery_order_items_stock
1750 # gefüllt werden kann.
1751 # could be dumped to the form in the first loop,
1752 # but maybe bin_id and warehouse_id has changed to the "korrekturlager" with
1753 # allowed negative qty ($::instance_conf->get_warehouse_id_ignore_onhand) ...
1755 foreach (@all_requests){
1757 next unless scalar(%{ $_ });
1758 $form->{"stock_${prefix}_$i"} = YAML::Dump([$_]);
1761 save(no_redirect => 1); # Wir können auslagern, deshalb beleg speichern
1762 # und in delivery_order_items_stock speichern
1764 # ... and fill back the persistent dois_id for inventory fk
1765 undef (@all_requests);
1766 foreach my $i (1 .. $form->{rowcount}) {
1767 next unless ($form->{"id_$i"} && $form->{"stock_${prefix}_$i"});
1768 push @all_requests, @{ DO->unpack_stock_information('packed' => $form->{"stock_${prefix}_$i"}) };
1770 DO->transfer_in_out('direction' => $prefix,
1771 'requests' => \@all_requests);
1773 SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
1775 $form->{callback} = 'do.pl?action=edit&type=sales_delivery_order&id=' . $form->escape($form->{id}) if $params{direction} eq 'out';
1776 $form->{callback} = 'do.pl?action=edit&type=purchase_delivery_order&id=' . $form->escape($form->{id}) if $params{direction} eq 'in';
1782 $main::lxdebug->enter_sub();
1786 my $form = $main::form;
1789 save(no_redirect => 1); # has to be done, at least for newly added positions
1791 # hashify partnumbers, positions. key is delivery_order_items_id
1792 for my $i (1 .. ($form->{rowcount}) ) {
1793 $temp_hash{$form->{"delivery_order_items_id_$i"}} = { runningnumber => $form->{"runningnumber_$i"}, partnumber => $form->{"partnumber_$i"} };
1794 if ($form->{id} && $form->{"discount_$i"}) {
1795 # prepare_order assumes a db value if there is a form->id and multiplies *100
1796 # We hope for new controller code (no more format_amount/parse_amount distinction)
1797 $form->{"discount_$i"} /=100;
1800 # naturally sort partnumbers and get a sorted array of doi_ids
1801 my @sorted_doi_ids = sort { Sort::Naturally::ncmp($temp_hash{$a}->{"partnumber"}, $temp_hash{$b}->{"partnumber"}) } keys %temp_hash;
1806 for (@sorted_doi_ids) {
1807 $form->{"runningnumber_$temp_hash{$_}->{runningnumber}"} = $new_number;
1810 # all parse_amounts changes are in form (i.e. , to .) therefore we need
1811 # another format_amount to change it back, for the next save ;-(
1812 # works great except for row discounts (see above comment)
1816 $main::lxdebug->leave_sub();
1828 do.pl - Script for all calls to delivery order
1836 Sorts all position with Natural Sort. Can be activated in form_footer.html like this
1837 C<E<lt>input class="submit" type="submit" name="action_sort" id="sort_button" value="[% 'Sort and Save' | $T8 %]"E<gt>>
1843 Sort and Save can be implemented as an optional button if configuration ca be set by client config.
1844 Example coding for database scripts and templates in (git show af2f24b8), check also
1845 autogeneration for rose (scripts/rose_auto_create_model.pl --h)