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/arap.pl";
49 require "bin/mozilla/common.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->{show_details} = $::myconfig{show_form_details};
100 $form->{callback} = build_std_url('action=add', 'type', 'vc') unless ($form->{callback});
106 $main::lxdebug->leave_sub();
110 $main::lxdebug->enter_sub();
114 my $form = $main::form;
116 $form->{show_details} = $::myconfig{show_form_details};
118 # show history button
119 $form->{javascript} = qq|<script type="text/javascript" src="js/show_history.js"></script>|;
120 #/show hhistory button
122 $form->{simple_save} = 0;
124 set_headings("edit");
126 # editing without stuff to edit? try adding it first
127 if ($form->{rowcount} && !$form->{print_and_save}) {
128 # map { $id++ if $form->{"multi_id_$_"} } (1 .. $form->{rowcount});
132 undef $form->{rowcount};
134 $main::lxdebug->leave_sub();
137 } elsif (!$form->{id}) {
139 $main::lxdebug->leave_sub();
143 my ($language_id, $printer_id);
144 if ($form->{print_and_save}) {
145 $form->{action} = "dispatcher";
146 $form->{action_print} = "1";
147 $form->{resubmit} = 1;
148 $language_id = $form->{language_id};
149 $printer_id = $form->{printer_id};
152 set_headings("edit");
157 if ($form->{print_and_save}) {
158 $form->{language_id} = $language_id;
159 $form->{printer_id} = $printer_id;
164 $main::lxdebug->leave_sub();
168 $main::lxdebug->enter_sub();
172 my $form = $main::form;
173 my %myconfig = %main::myconfig;
175 # retrieve order/quotation
176 my $editing = $form->{id};
178 DO->retrieve('vc' => $form->{vc},
179 'ids' => $form->{id});
181 $form->backup_vars(qw(payment_id language_id taxzone_id salesman_id taxincluded cp_id intnotes delivery_term_id currency));
183 # get customer / vendor
184 if ($form->{vc} eq 'vendor') {
185 IR->get_vendor(\%myconfig, \%$form);
186 $form->{discount} = $form->{vendor_discount};
188 IS->get_customer(\%myconfig, \%$form);
189 $form->{discount} = $form->{customer_discount};
192 $form->restore_vars(qw(payment_id language_id taxzone_id intnotes cp_id delivery_term_id));
193 $form->restore_vars(qw(currency)) if ($form->{id} || $form->{convert_from_oe_ids});
194 $form->restore_vars(qw(taxincluded)) if $form->{id};
195 $form->restore_vars(qw(salesman_id)) if $editing;
197 $main::lxdebug->leave_sub();
201 $main::lxdebug->enter_sub();
205 my $form = $main::form;
206 my %myconfig = %main::myconfig;
208 $form->{formname} = $form->{type} unless $form->{formname};
211 foreach my $ref (@{ $form->{form_details} }) {
212 $form->{rowcount} = ++$i;
214 map { $form->{"${_}_$i"} = $ref->{$_} } keys %{$ref};
216 for my $i (1 .. $form->{rowcount}) {
218 $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"} * 100);
220 $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"});
222 my ($dec) = ($form->{"sellprice_$i"} =~ /\.(\d+)/);
224 my $decimalplaces = ($dec > 2) ? $dec : 2;
226 # copy reqdate from deliverydate for invoice -> order conversion
227 $form->{"reqdate_$i"} = $form->{"deliverydate_$i"} unless $form->{"reqdate_$i"};
229 $form->{"sellprice_$i"} = $form->format_amount(\%myconfig, $form->{"sellprice_$i"}, $decimalplaces);
230 $form->{"lastcost_$i"} = $form->format_amount(\%myconfig, $form->{"lastcost_$i"}, $decimalplaces);
232 (my $dec_qty) = ($form->{"qty_$i"} =~ /\.(\d+)/);
233 $dec_qty = length $dec_qty;
234 $form->{"qty_$i"} = $form->format_amount(\%myconfig, $form->{"qty_$i"}, $dec_qty);
237 $main::lxdebug->leave_sub();
241 $main::lxdebug->enter_sub();
245 my $form = $main::form;
246 my %myconfig = %main::myconfig;
248 my $class = "SL::DB::" . ($form->{vc} eq 'customer' ? 'Customer' : 'Vendor');
249 $form->{VC_OBJ} = $class->load_cached($form->{ $form->{vc} . '_id' });
251 $form->{employee_id} = $form->{old_employee_id} if $form->{old_employee_id};
252 $form->{salesman_id} = $form->{old_salesman_id} if $form->{old_salesman_id};
254 my $vc = $form->{vc} eq "customer" ? "customers" : "vendors";
255 $form->get_lists("price_factors" => "ALL_PRICE_FACTORS",
256 "business_types" => "ALL_BUSINESS_TYPES",
258 $form->{ALL_DEPARTMENTS} = SL::DB::Manager::Department->get_all;
261 my @old_project_ids = uniq grep { $_ } map { $_ * 1 } ($form->{"globalproject_id"}, map { $form->{"project_id_$_"} } 1..$form->{"rowcount"});
262 my @old_ids_cond = @old_project_ids ? (id => \@old_project_ids) : ();
264 if (($vc eq 'customers') && $::instance_conf->get_customer_projects_only_in_sales) {
267 customer_id => $::form->{customer_id},
268 billable_customer_id => $::form->{customer_id},
273 and => [ active => 1, @customer_cond ],
277 $::form->{ALL_PROJECTS} = SL::DB::Manager::Project->get_all_sorted(query => \@conditions);
278 $::form->{ALL_EMPLOYEES} = SL::DB::Manager::Employee->get_all_sorted(query => [ or => [ id => $::form->{employee_id}, deleted => 0 ] ]);
279 $::form->{ALL_SALESMEN} = SL::DB::Manager::Employee->get_all_sorted(query => [ or => [ id => $::form->{salesman_id}, deleted => 0 ] ]);
280 $::form->{ALL_SHIPTO} = SL::DB::Manager::Shipto->get_all_sorted(query => [
281 or => [ trans_id => $::form->{"$::form->{vc}_id"} * 1, and => [ shipto_id => $::form->{shipto_id} * 1, trans_id => undef ] ]
283 $::form->{ALL_CONTACTS} = SL::DB::Manager::Contact->get_all_sorted(query => [
285 cp_cv_id => $::form->{"$::form->{vc}_id"} * 1,
288 cp_id => $::form->{cp_id} * 1
293 my $dispatch_to_popup = '';
294 if ($form->{resubmit} && ($form->{format} eq "html")) {
295 $dispatch_to_popup = "window.open('about:blank','Beleg'); document.do.target = 'Beleg';";
296 $dispatch_to_popup .= "document.do.submit();";
297 } elsif ($form->{resubmit}) {
298 # emulate click for resubmitting actions
299 $dispatch_to_popup = "document.do.${_}.click(); " for grep { /^action_/ } keys %$form;
301 $::request->{layout}->add_javascripts_inline("\$(function(){$dispatch_to_popup});");
304 $form->{follow_up_trans_info} = $form->{donumber} .'('. $form->{VC_OBJ}->name .')';
306 $::request->{layout}->use_javascript(map { "${_}.js" } qw(kivi.SalesPurchase ckeditor/ckeditor ckeditor/adapters/jquery kivi.io autocomplete_customer autocomplete_part));
309 push @custom_hidden, map { "shiptocvar_" . $_->name } @{ SL::DB::Manager::CustomVariableConfig->get_all(where => [ module => 'ShipTo' ]) };
311 $::form->{HIDDENS} = [ map { +{ name => $_, value => $::form->{$_} } } (@custom_hidden) ];
314 # Fix für Bug 1082 Erwartet wird: 'abteilungsNAME--abteilungsID'
315 # und Erweiterung für Bug 1760:
316 # Das war leider nur ein Teil-Fix, da das Verhalten den 'Erneuern'-Knopf
317 # nicht überlebt. Konsequent jetzt auf L umgestellt
318 # $ perldoc SL::Template::Plugin::L
319 # Daher entsprechend nur die Anpassung in form_header
320 # und in DO.pm gemacht. 4 Testfälle:
321 # department_id speichern | i.O.
322 # department_id lesen | i.O.
323 # department leer überlebt erneuern | i.O.
324 # department nicht leer überlebt erneuern | i.O.
325 # $main::lxdebug->message(0, 'ABTEILUNGS ID in form?' . $form->{department_id});
326 print $form->parse_html_template('do/form_header');
328 $main::lxdebug->leave_sub();
332 $main::lxdebug->enter_sub();
336 my $form = $main::form;
338 $form->{PRINT_OPTIONS} = print_options('inline' => 1);
339 $form->{ALL_DELIVERY_TERMS} = SL::DB::Manager::DeliveryTerm->get_all_sorted();
341 print $form->parse_html_template('do/form_footer',
342 {transfer_default => ($::instance_conf->get_transfer_default)});
344 $main::lxdebug->leave_sub();
347 sub update_delivery_order {
348 $main::lxdebug->enter_sub();
352 my $form = $main::form;
353 my %myconfig = %main::myconfig;
355 set_headings($form->{"id"} ? "edit" : "add");
357 $form->{insertdate} = SL::DB::DeliveryOrder->new(id => $form->{id})->load->itime_as_date if $form->{id};
362 $payment_id = $form->{payment_id} if $form->{payment_id};
364 my $vc = $form->{vc};
365 if (($form->{"previous_${vc}_id"} || $form->{"${vc}_id"}) != $form->{"${vc}_id"}) {
366 $::form->{salesman_id} = SL::DB::Manager::Employee->current->id if exists $::form->{salesman_id};
368 IS->get_customer(\%myconfig, $form) if $vc eq 'customer';
369 IR->get_vendor(\%myconfig, $form) if $vc eq 'vendor';
372 $form->{discount} = $form->{"$form->{vc}_discount"} if defined $form->{"$form->{vc}_discount"};
373 # Problem: Wenn man ohne Erneuern einen Kunden/Lieferanten
374 # wechselt, wird der entsprechende Kunden/ Lieferantenrabatt
375 # nicht übernommen. Grundproblem: In Commit 82574e78
376 # hab ich aus discount customer_discount und vendor_discount
377 # gemacht und entsprechend an den Oberflächen richtig hin-
378 # geschoben. Die damals bessere Lösung wäre gewesen:
379 # In den Templates nur die hidden für form-discount wieder ein-
380 # setzen dann wäre die Verrenkung jetzt nicht notwendig.
381 # TODO: Ggf. Bugfix 1284, 1575 und 817 wieder zusammenführen
382 # Testfälle: Kunden mit Rabatt 0 -> Rabatt 20 i.O.
383 # Kunde mit Rabatt 20 -> Rabatt 0 i.O.
384 # Kunde mit Rabatt 20 -> Rabatt 5,5 i.O.
385 $form->{payment_id} = $payment_id if $form->{payment_id} eq "";
387 my $i = $form->{rowcount};
389 if ( ($form->{"partnumber_$i"} eq "")
390 && ($form->{"description_$i"} eq "")
391 && ($form->{"partsgroup_$i"} eq "")) {
398 if ($form->{type} eq 'purchase_delivery_order') {
399 IR->retrieve_item(\%myconfig, $form);
402 IS->retrieve_item(\%myconfig, $form);
406 my $rows = scalar @{ $form->{item_list} };
409 $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
410 if( !$form->{"qty_$i"} ) {
411 $form->{"qty_$i"} = 1;
416 select_item(mode => $mode, pre_entered_qty => $form->{"qty_$i"});
417 $::dispatcher->end_request;
421 my $sellprice = $form->parse_amount(\%myconfig, $form->{"sellprice_$i"});
423 map { $form->{"${_}_$i"} = $form->{item_list}[0]{$_} } keys %{ $form->{item_list}[0] };
425 $form->{"marge_price_factor_$i"} = $form->{item_list}->[0]->{price_factor};
428 $form->{"sellprice_$i"} = $sellprice;
430 my $record = _make_record();
431 my $price_source = SL::PriceSource->new(record_item => $record->items->[$i-1], record => $record);
432 my $best_price = $price_source->best_price;
433 my $best_discount = $price_source->best_discount;
436 $::form->{"sellprice_$i"} = $best_price->price;
437 $::form->{"active_price_source_$i"} = $best_price->source;
439 if ($best_discount) {
440 $::form->{"discount_$i"} = $best_discount->discount;
441 $::form->{"active_discount_source_$i"} = $best_discount->source;
445 $form->{"sellprice_$i"} = $form->format_amount(\%myconfig, $form->{"sellprice_$i"});
446 $form->{"lastcost_$i"} = $form->format_amount(\%myconfig, $form->{"lastcost_$i"});
447 $form->{"qty_$i"} = $form->format_amount(\%myconfig, $form->{"qty_$i"});
448 $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"} * 100.0);
455 # ok, so this is a new part
456 # ask if it is a part or service item
458 if ( $form->{"partsgroup_$i"}
459 && ($form->{"partsnumber_$i"} eq "")
460 && ($form->{"description_$i"} eq "")) {
462 $form->{"discount_$i"} = "";
463 $form->{"not_discountable_$i"} = "";
467 $form->{"id_$i"} = 0;
473 $main::lxdebug->leave_sub();
477 $main::lxdebug->enter_sub();
481 my $form = $main::form;
482 my %myconfig = %main::myconfig;
483 my $locale = $main::locale;
485 $form->{vc} = $form->{type} eq 'purchase_delivery_order' ? 'vendor' : 'customer';
487 $form->get_lists("projects" => { "key" => "ALL_PROJECTS",
489 "business_types" => "ALL_BUSINESS_TYPES");
490 $form->{ALL_EMPLOYEES} = SL::DB::Manager::Employee->get_all_sorted(query => [ deleted => 0 ]);
491 $form->{ALL_DEPARTMENTS} = SL::DB::Manager::Department->get_all;
492 $form->{title} = $locale->text('Delivery Orders');
496 print $form->parse_html_template('do/search');
498 $main::lxdebug->leave_sub();
502 $main::lxdebug->enter_sub();
506 my $form = $main::form;
507 my %myconfig = %main::myconfig;
508 my $locale = $main::locale;
509 my $cgi = $::request->{cgi};
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' => '', },
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 $report->set_options('top_info_text' => join("\n", @options),
647 'raw_top_info_text' => $form->parse_html_template('do/orders_top'),
648 'raw_bottom_info_text' => $form->parse_html_template('do/orders_bottom'),
649 'output_format' => 'HTML',
650 'title' => $form->{title},
651 'attachment_basename' => $attachment_basename . strftime('_%Y%m%d', localtime time),
653 $report->set_options_from_form();
654 $locale->set_numberformat_wo_thousands_separator(\%myconfig) if lc($report->{options}->{output_format}) eq 'csv';
656 # add sort and escape callback, this one we use for the add sub
657 $form->{callback} = $href .= "&sort=$form->{sort}";
659 # escape callback for href
660 my $callback = $form->escape($href);
662 my $edit_url = build_std_url('action=edit', 'type', 'vc');
663 my $edit_order_url = build_std_url('script=oe.pl', 'type=' . ($form->{type} eq 'sales_delivery_order' ? 'sales_order' : 'purchase_order'), 'action=edit');
667 foreach my $dord (@{ $form->{DO} }) {
668 $dord->{open} = $dord->{closed} ? $locale->text('No') : $locale->text('Yes');
669 $dord->{delivered} = $dord->{delivered} ? $locale->text('Yes') : $locale->text('No');
671 my $row = { map { $_ => { 'data' => $dord->{$_} } } @columns };
674 'raw_data' => $cgi->hidden('-name' => "trans_id_${idx}", '-value' => $dord->{id})
675 . $cgi->checkbox('-name' => "multi_id_${idx}", '-value' => 1, '-label' => ''),
676 'valign' => 'center',
680 $row->{donumber}->{link} = $edit_url . "&id=" . E($dord->{id}) . "&callback=${callback}";
681 $row->{ordnumber}->{link} = $edit_order_url . "&id=" . E($dord->{oe_id}) . "&callback=${callback}" if $dord->{oe_id};
682 $report->add_data($row);
687 $report->generate_with_headers();
689 $main::lxdebug->leave_sub();
693 $main::lxdebug->enter_sub();
699 my $form = $main::form;
700 my %myconfig = %main::myconfig;
701 my $locale = $main::locale;
703 $form->mtime_ischanged('delivery_orders');
705 $form->{defaultcurrency} = $form->get_default_currency(\%myconfig);
707 $form->isblank("transdate", $locale->text('Delivery Order Date missing!'));
709 $form->{donumber} =~ s/^\s*//g;
710 $form->{donumber} =~ s/\s*$//g;
712 my $msg = ucfirst $form->{vc};
713 $form->isblank($form->{vc} . "_id", $locale->text($msg . " missing!"));
715 # $locale->text('Customer missing!');
716 # $locale->text('Vendor missing!');
718 remove_emptied_rows();
721 # if the name changed get new values
722 my $vc = $form->{vc};
723 if (($form->{"previous_${vc}_id"} || $form->{"${vc}_id"}) != $form->{"${vc}_id"}) {
724 $::form->{salesman_id} = SL::DB::Manager::Employee->current->id if exists $::form->{salesman_id};
726 IS->get_customer(\%myconfig, $form) if $vc eq 'customer';
727 IR->get_vendor(\%myconfig, $form) if $vc eq 'vendor';
730 $::dispatcher->end_request;
733 $form->{id} = 0 if $form->{saveasnew};
737 if(!exists $form->{addition}) {
738 $form->{snumbers} = qq|donumber_| . $form->{donumber};
739 $form->{addition} = "SAVED";
742 # /saving the history
744 $form->{simple_save} = 1;
745 if (!$params{no_redirect} && !$form->{print_and_save}) {
746 delete @{$form}{ary_diff([keys %{ $form }], [qw(login id script type cursor_fokus)])};
748 $::dispatcher->end_request;
750 $main::lxdebug->leave_sub();
754 $main::lxdebug->enter_sub();
758 my $form = $main::form;
759 my %myconfig = %main::myconfig;
760 my $locale = $main::locale;
764 if(!exists $form->{addition}) {
765 $form->{snumbers} = qq|donumber_| . $form->{donumber};
766 $form->{addition} = "DELETED";
769 # /saving the history
771 $form->info($locale->text('Delivery Order deleted!'));
772 $::dispatcher->end_request;
775 $form->error($locale->text('Cannot delete delivery order!'));
777 $main::lxdebug->leave_sub();
781 $main::lxdebug->enter_sub();
783 my $form = $main::form;
784 my %myconfig = %main::myconfig;
785 my $locale = $main::locale;
788 $form->mtime_ischanged('delivery_orders');
790 $main::auth->assert($form->{type} eq 'purchase_delivery_order' ? 'vendor_invoice_edit' : 'invoice_edit');
792 $form->{convert_from_do_ids} = $form->{id};
793 $form->{deliverydate} = $form->{transdate};
794 $form->{transdate} = $form->{invdate} = $form->current_date(\%myconfig);
795 $form->{duedate} = $form->current_date(\%myconfig, $form->{invdate}, $form->{terms} * 1);
796 $form->{defaultcurrency} = $form->get_default_currency(\%myconfig);
800 delete @{$form}{qw(id closed delivered)};
802 my ($script, $buysell);
803 if ($form->{type} eq 'purchase_delivery_order') {
804 $form->{title} = $locale->text('Add Vendor Invoice');
805 $form->{script} = 'ir.pl';
810 $form->{title} = $locale->text('Add Sales Invoice');
811 $form->{script} = 'is.pl';
816 for my $i (1 .. $form->{rowcount}) {
817 map { $form->{"${_}_${i}"} = $form->parse_amount(\%myconfig, $form->{"${_}_${i}"}) if $form->{"${_}_${i}"} } qw(ship qty sellprice lastcost basefactor discount);
819 # adds a customer/vendor discount, unless we have a workflow case
820 # CAVEAT: has to be done, after the above parse_amount
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 $form->{"donumber_$i"} = $form->{donumber};
830 $form->{"converted_from_delivery_order_items_id_$i"} = delete $form->{"delivery_order_items_id_$i"};
833 $form->{type} = "invoice";
836 $main::locale = Locale->new("$myconfig{countrycode}", "$script");
837 $locale = $main::locale;
839 require "bin/mozilla/$form->{script}";
841 my $currency = $form->{currency};
844 if ($form->{ordnumber}) {
845 require SL::DB::Order;
846 if (my $order = SL::DB::Manager::Order->find_by(ordnumber => $form->{ordnumber})) {
848 $form->{orddate} = $order->transdate_as_date;
849 $form->{$_} = $order->$_ for qw(payment_id salesman_id taxzone_id quonumber);
853 $form->{currency} = $currency;
854 $form->{exchangerate} = "";
855 $form->{forex} = $form->check_exchangerate(\%myconfig, $form->{currency}, $form->{invdate}, $buysell);
856 $form->{exchangerate} = $form->{forex} if ($form->{forex});
861 for my $i (1 .. $form->{rowcount}) {
862 $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"});
864 my ($dec) = ($form->{"sellprice_$i"} =~ /\.(\d+)/);
866 my $decimalplaces = ($dec > 2) ? $dec : 2;
868 # copy delivery date from reqdate for order -> invoice conversion
869 $form->{"deliverydate_$i"} = $form->{"reqdate_$i"}
870 unless $form->{"deliverydate_$i"};
873 $form->{"sellprice_$i"} =
874 $form->format_amount(\%myconfig, $form->{"sellprice_$i"},
877 $form->{"lastcost_$i"} =
878 $form->format_amount(\%myconfig, $form->{"lastcost_$i"},
881 (my $dec_qty) = ($form->{"qty_$i"} =~ /\.(\d+)/);
882 $dec_qty = length $dec_qty;
884 $form->format_amount(\%myconfig, $form->{"qty_$i"}, $dec_qty);
890 $main::lxdebug->leave_sub();
894 $main::lxdebug->enter_sub();
896 my $form = $main::form;
897 my %myconfig = %main::myconfig;
898 my $locale = $main::locale;
901 $main::auth->assert($form->{type} eq 'sales_delivery_order' ? 'invoice_edit' : 'vendor_invoice_edit');
903 my @do_ids = map { $form->{"trans_id_$_"} } grep { $form->{"multi_id_$_"} } (1..$form->{rowcount});
905 if (!scalar @do_ids) {
906 $form->show_generic_error($locale->text('You have not selected any delivery order.'));
909 map { delete $form->{$_} } grep { m/^(?:trans|multi)_id_\d+/ } keys %{ $form };
911 if (!DO->retrieve('vc' => $form->{vc}, 'ids' => \@do_ids)) {
912 $form->show_generic_error($form->{vc} eq 'customer' ?
913 $locale->text('You cannot create an invoice for delivery orders for different customers.') :
914 $locale->text('You cannot create an invoice for delivery orders from different vendors.'),
918 my $source_type = $form->{type};
919 $form->{convert_from_do_ids} = join ' ', @do_ids;
920 # bei der auswahl von mehreren Lieferscheinen fuer eine Rechnung, die einfach in donumber_array
921 # zwischenspeichern (DO.pm) und als ' '-separierte Liste wieder zurueckschreiben
922 # Hinweis: delete gibt den wert zurueck und loescht danach das element (nett und einfach)
923 # $shell: perldoc perlunc; /delete EXPR
924 $form->{donumber} = delete $form->{donumber_array};
925 $form->{ordnumber} = delete $form->{ordnumber_array};
926 $form->{cusordnumber} = delete $form->{cusordnumber_array};
927 $form->{deliverydate} = $form->{transdate};
928 $form->{transdate} = $form->current_date(\%myconfig);
929 $form->{duedate} = $form->current_date(\%myconfig, $form->{invdate}, $form->{terms} * 1);
930 $form->{type} = "invoice";
932 $form->{defaultcurrency} = $form->get_default_currency(\%myconfig);
934 my ($script, $buysell);
935 if ($source_type eq 'purchase_delivery_order') {
936 $form->{title} = $locale->text('Add Vendor Invoice');
937 $form->{script} = 'ir.pl';
942 $form->{title} = $locale->text('Add Sales Invoice');
943 $form->{script} = 'is.pl';
948 map { delete $form->{$_} } qw(id subject message cc bcc printed emailed queued);
950 # get vendor or customer discount
952 my $saved_form = save_form();
953 if ($form->{vc} eq 'vendor') {
954 IR->get_vendor(\%myconfig, \%$form);
955 $vc_discount = $form->{vendor_discount};
957 IS->get_customer(\%myconfig, \%$form);
958 $vc_discount = $form->{customer_discount};
960 # use payment terms from customer or vendor
961 restore_form($saved_form,0,qw(payment_id));
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.'));
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();
1396 $::dispatcher->end_request;
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.'));
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();
1515 $::dispatcher->end_request;
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();
1544 $::lxdebug->enter_sub;
1546 $::auth->assert('purchase_delivery_order_edit | sales_delivery_order_edit');
1549 retrieve_partunits();
1551 my $new_rowcount = $::form->{"rowcount"} * 1 + 1;
1552 $::form->{"project_id_${new_rowcount}"} = $::form->{"globalproject_id"};
1554 $::form->language_payment(\%::myconfig);
1556 Common::webdav_folder($::form);
1559 display_row(++$::form->{rowcount});
1562 $::lxdebug->leave_sub;
1566 call_sub($main::form->{yes_nextsub});
1570 call_sub($main::form->{no_nextsub});
1574 call_sub($main::form->{update_nextsub} || $main::form->{nextsub} || 'update_delivery_order');
1578 my $form = $main::form;
1579 my $locale = $main::locale;
1581 foreach my $action (qw(update ship_to print e_mail save transfer_out transfer_out_default sort
1582 transfer_in transfer_in_default mark_closed save_as_new invoice delete)) {
1583 if ($form->{"action_${action}"}) {
1589 $form->error($locale->text('No action defined.'));
1592 sub transfer_out_default {
1593 $main::lxdebug->enter_sub();
1595 my $form = $main::form;
1597 transfer_in_out_default('direction' => 'out');
1599 $main::lxdebug->leave_sub();
1602 sub transfer_in_default {
1603 $main::lxdebug->enter_sub();
1605 my $form = $main::form;
1607 transfer_in_out_default('direction' => 'in');
1609 $main::lxdebug->leave_sub();
1612 # Falls das Standardlagerverfahren aktiv ist, wird
1613 # geprüft, ob alle Standardlagerplätze für die Auslager-
1614 # artikel vorhanden sind UND ob die Warenmenge ausreicht zum
1615 # Auslagern. Falls nicht wird entsprechend eine Fehlermeldung
1616 # generiert. Offen Chargennummer / bestbefore wird nicht berücksichtigt
1617 sub transfer_in_out_default {
1618 $main::lxdebug->enter_sub();
1620 my $form = $main::form;
1621 my %myconfig = %main::myconfig;
1622 my $locale = $main::locale;
1625 my (%missing_default_bins, %qty_parts, @all_requests, %part_info_map, $default_warehouse_id, $default_bin_id);
1627 Common::check_params(\%params, qw(direction));
1629 # entsprechende defaults holen, falls standardlagerplatz verwendet werden soll
1630 if ($::instance_conf->get_transfer_default_use_master_default_bin) {
1631 $default_warehouse_id = $::instance_conf->get_warehouse_id;
1632 $default_bin_id = $::instance_conf->get_bin_id;
1636 my @part_ids = map { $form->{"id_${_}"} } (1 .. $form->{rowcount});
1638 my $units = AM->retrieve_units(\%myconfig, $form);
1639 %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
1640 foreach my $i (1 .. $form->{rowcount}) {
1641 next unless ($form->{"id_$i"});
1642 my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
1643 my $qty = $form->parse_amount(\%myconfig, $form->{"qty_$i"}) * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
1645 $form->show_generic_error($locale->text("Cannot transfer negative entries." )) if ($qty < 0);
1646 # if we do not want to transfer services and this part is a service, set qty to zero
1647 # ... and do not create a hash entry in %qty_parts below (will skip check for bins for the transfer == out case)
1648 # ... 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)
1650 $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});
1651 $qty_parts{$form->{"id_$i"}} += $qty;
1653 delete $qty_parts{$form->{"id_$i"}} unless $qty_parts{$form->{"id_$i"}};
1654 undef $form->{"stock_in_$i"};
1657 $part_info_map{$form->{"id_$i"}}{bin_id} ||= $default_bin_id;
1658 $part_info_map{$form->{"id_$i"}}{warehouse_id} ||= $default_warehouse_id;
1660 push @all_requests, ($qty == 0) ? { } : {
1661 'chargenumber' => '', #?? die müsste entsprechend geholt werden
1662 #'bestbefore' => undef, # TODO wird nicht berücksichtigt
1663 'bin_id' => $part_info_map{$form->{"id_$i"}}{bin_id},
1665 'parts_id' => $form->{"id_$i"},
1666 'comment' => $locale->text("Default transfer delivery order"),
1667 'unit' => $part_info_map{$form->{"id_$i"}}{unit},
1668 'warehouse_id' => $part_info_map{$form->{"id_$i"}}{warehouse_id},
1669 'oe_id' => $form->{id},
1670 'project_id' => $form->{"project_id_$i"} ? $form->{"project_id_$i"} : $form->{globalproject_id}
1674 # jetzt wird erst überprüft, ob die Stückzahl entsprechend stimmt.
1675 # check if bin (transfer in and transfer out and qty (transfer out) is correct
1676 foreach my $key (keys %qty_parts) {
1678 $missing_default_bins{$key}{missing_bin} = 1 unless ($part_info_map{$key}{bin_id});
1679 next unless ($part_info_map{$key}{bin_id}); # abbruch
1681 if ($params{direction} eq 'out') { # wird nur für ausgehende Mengen benötigt
1682 my ($max_qty, $error) = WH->get_max_qty_parts_bin(parts_id => $key, bin_id => $part_info_map{$key}{bin_id});
1684 # wir können nicht entscheiden, welche charge oder mhd (bestbefore) ausgewählt sein soll
1685 # deshalb rückmeldung nach oben geben, manuell auszulagern
1686 # TODO Bei nur einem Treffer mit Charge oder bestbefore wäre das noch möglich
1687 $missing_default_bins{$key}{chargenumber} = 1;
1689 if ($max_qty < $qty_parts{$key}){
1690 $missing_default_bins{$key}{missing_qty} = $max_qty - $qty_parts{$key};
1696 # Abfrage für Fehlerbehandlung (nur bei direction == out)
1697 if (scalar (keys %missing_default_bins)) {
1699 foreach my $fehler (keys %missing_default_bins) {
1701 my $ware = WH->get_part_description(parts_id => $fehler);
1702 if ($missing_default_bins{$fehler}{missing_bin}){
1703 $fehlertext .= "Kein Standardlagerplatz definiert bei $ware <br>";
1705 if ($missing_default_bins{$fehler}{missing_qty}) { # missing_qty
1706 $fehlertext .= "Es fehlen " . $missing_default_bins{$fehler}{missing_qty}*-1 .
1707 " von $ware auf dem Standard-Lagerplatz " . $part_info_map{$fehler}{bin} . " zum Auslagern<br>";
1709 if ($missing_default_bins{$fehler}{chargenumber}){
1710 $fehlertext .= "Die Ware hat eine Chargennummer oder eine Mindesthaltbarkeit definiert.
1711 Hier kann man nicht automatisch entscheiden.
1712 Bitte diesen Lieferschein manuell auslagern.
1715 # auslagern soll immer gehen, auch wenn nicht genügend auf lager ist.
1716 # der lagerplatz ist hier extra konfigurierbar, bspw. Lager-Korrektur mit
1717 # Lagerplatz Lagerplatz-Korrektur
1718 my $default_warehouse_id_ignore_onhand = $::instance_conf->get_warehouse_id_ignore_onhand;
1719 my $default_bin_id_ignore_onhand = $::instance_conf->get_bin_id_ignore_onhand;
1720 if ($::instance_conf->get_transfer_default_ignore_onhand && $default_bin_id_ignore_onhand) {
1721 # entsprechende defaults holen
1722 # falls chargenumber, bestbefore oder anzahl nicht stimmt, auf automatischen
1723 # lagerplatz wegbuchen!
1724 foreach (@all_requests) {
1725 if ($_->{parts_id} eq $fehler){
1726 $_->{bin_id} = $default_bin_id_ignore_onhand;
1727 $_->{warehouse_id} = $default_warehouse_id_ignore_onhand;
1731 #$main::lxdebug->message(0, 'Fehlertext: ' . $fehlertext);
1732 $form->show_generic_error($locale->text("Cannot transfer. <br> Reason:<br>#1", $fehlertext ));
1738 # hier der eigentliche fallunterschied für in oder out
1739 my $prefix = $params{direction} eq 'in' ? 'in' : 'out';
1741 # dieser array_ref ist für DO->save da:
1742 # einmal die all_requests in YAML verwandeln, damit delivery_order_items_stock
1743 # gefüllt werden kann.
1744 # could be dumped to the form in the first loop,
1745 # but maybe bin_id and warehouse_id has changed to the "korrekturlager" with
1746 # allowed negative qty ($::instance_conf->get_warehouse_id_ignore_onhand) ...
1748 foreach (@all_requests){
1750 next unless scalar(%{ $_ });
1751 $form->{"stock_${prefix}_$i"} = YAML::Dump([$_]);
1754 save(no_redirect => 1); # Wir können auslagern, deshalb beleg speichern
1755 # und in delivery_order_items_stock speichern
1757 # ... and fill back the persistent dois_id for inventory fk
1758 undef (@all_requests);
1759 foreach my $i (1 .. $form->{rowcount}) {
1760 next unless ($form->{"id_$i"} && $form->{"stock_${prefix}_$i"});
1761 push @all_requests, @{ DO->unpack_stock_information('packed' => $form->{"stock_${prefix}_$i"}) };
1763 DO->transfer_in_out('direction' => $prefix,
1764 'requests' => \@all_requests);
1766 SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
1768 $form->{callback} = 'do.pl?action=edit&type=sales_delivery_order&id=' . $form->escape($form->{id}) if $params{direction} eq 'out';
1769 $form->{callback} = 'do.pl?action=edit&type=purchase_delivery_order&id=' . $form->escape($form->{id}) if $params{direction} eq 'in';
1775 $main::lxdebug->enter_sub();
1779 my $form = $main::form;
1782 save(no_redirect => 1); # has to be done, at least for newly added positions
1784 # hashify partnumbers, positions. key is delivery_order_items_id
1785 for my $i (1 .. ($form->{rowcount}) ) {
1786 $temp_hash{$form->{"delivery_order_items_id_$i"}} = { runningnumber => $form->{"runningnumber_$i"}, partnumber => $form->{"partnumber_$i"} };
1787 if ($form->{id} && $form->{"discount_$i"}) {
1788 # prepare_order assumes a db value if there is a form->id and multiplies *100
1789 # We hope for new controller code (no more format_amount/parse_amount distinction)
1790 $form->{"discount_$i"} /=100;
1793 # naturally sort partnumbers and get a sorted array of doi_ids
1794 my @sorted_doi_ids = sort { Sort::Naturally::ncmp($temp_hash{$a}->{"partnumber"}, $temp_hash{$b}->{"partnumber"}) } keys %temp_hash;
1799 for (@sorted_doi_ids) {
1800 $form->{"runningnumber_$temp_hash{$_}->{runningnumber}"} = $new_number;
1803 # all parse_amounts changes are in form (i.e. , to .) therefore we need
1804 # another format_amount to change it back, for the next save ;-(
1805 # works great except for row discounts (see above comment)
1809 $main::lxdebug->leave_sub();
1821 do.pl - Script for all calls to delivery order
1830 Sorts all position with Natural Sort. Can be activated in form_footer.html like this
1831 C<E<lt>input class="submit" type="submit" name="action_sort" id="sort_button" value="[% 'Sort and Save' | $T8 %]"E<gt>>
1837 Sort and Save can be implemented as an optional button if configuration ca be set by client config.
1838 Example coding for database scripts and templates in (git show af2f24b8), check also
1839 autogeneration for rose (scripts/rose_auto_create_model.pl --h)