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);
39 use SL::DB::DeliveryOrder;
43 use SL::MoreCommon qw(ary_diff restore_form save_form);
44 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();
239 sub setup_do_action_bar {
240 my @transfer_qty = qw(kivi.SalesPurchase.delivery_order_check_transfer_qty);
241 my @req_trans_desc = qw(kivi.SalesPurchase.check_transaction_description) x!!$::instance_conf->get_require_transaction_description_ps;
242 my $is_customer = $::form->{vc} eq 'customer';
244 for my $bar ($::request->layout->get('actionbar')) {
248 submit => [ '#form', { action => "update" } ],
249 id => 'update_button',
250 accesskey => 'enter',
256 submit => [ '#form', { action => "save" } ],
257 checks => [ 'kivi.validate_form' ],
258 disabled => $::form->{delivered} ? t8('This record has already been delivered.') : undef,
262 submit => [ '#form', { action => "save_as_new" } ],
263 checks => [ 'kivi.validate_form' ],
264 disabled => !$::form->{id},
267 t8('Mark as closed'),
268 submit => [ '#form', { action => "mark_closed" } ],
269 checks => [ 'kivi.validate_form' ],
270 confirm => t8('This will remove the delivery order from showing as open even if contents are not delivered. Proceed?'),
271 disabled => !$::form->{id} ? t8('This record has not been saved yet.')
272 : $::form->{closed} ? t8('This record has already been closed.')
275 ], # end of combobox "Save"
279 submit => [ '#form', { action => "delete" } ],
280 confirm => t8('Do you really want to delete this object?'),
281 disabled => !$::form->{id} ? t8('This record has not been saved yet.')
282 : $::form->{delivered} ? t8('This record has already been delivered.')
283 : ($::form->{vc} eq 'customer' && !$::instance_conf->get_sales_delivery_order_show_delete) ? t8('Deleting this type of record has been disabled in the configuration.')
284 : ($::form->{vc} eq 'vendor' && !$::instance_conf->get_purchase_delivery_order_show_delete) ? t8('Deleting this type of record has been disabled in the configuration.')
291 submit => [ '#form', { action => "transfer_out" } ],
292 checks => [ 'kivi.validate_form', @transfer_qty ],
293 disabled => $::form->{delivered} ? t8('This record has already been delivered.') : undef,
294 only_if => $is_customer,
297 t8('Transfer out via default'),
298 submit => [ '#form', { action => "transfer_out_default" } ],
299 checks => [ 'kivi.validate_form' ],
300 disabled => $::form->{delivered} ? t8('This record has already been delivered.') : undef,
301 only_if => $is_customer && $::instance_conf->get_transfer_default,
305 submit => [ '#form', { action => "transfer_in" } ],
306 checks => [ 'kivi.validate_form', @transfer_qty ],
307 disabled => $::form->{delivered} ? t8('This record has already been delivered.') : undef,
308 only_if => !$is_customer,
311 t8('Transfer in via default'),
312 submit => [ '#form', { action => "transfer_in_default" } ],
313 checks => [ 'kivi.validate_form' ],
314 disabled => $::form->{delivered} ? t8('This record has already been delivered.') : undef,
315 only_if => !$is_customer && $::instance_conf->get_transfer_default,
317 ], # end of combobox "Transfer out"
324 submit => [ '#form', { action => "invoice" } ],
325 disabled => !$::form->{id} ? t8('This record has not been saved yet.') : undef,
326 confirm => $::form->{delivered} ? undef
327 : ($::form->{vc} eq 'customer' && $::instance_conf->get_sales_delivery_order_check_stocked) ? t8('This record has not been stocked out. Proceed?')
328 : ($::form->{vc} eq 'vendor' && $::instance_conf->get_purchase_delivery_order_check_stocked) ? t8('This record has not been stocked in. Proceed?')
333 action => [ t8('Export') ],
336 call => [ 'kivi.SalesPurchase.show_print_dialog' ],
337 checks => [ 'kivi.validate_form' ],
341 call => [ 'kivi.SalesPurchase.show_email_dialog' ],
342 checks => [ 'kivi.validate_form' ],
343 disabled => !$::form->{id} ? t8('This record has not been saved yet.') : undef,
345 ], # end of combobox "Export"
348 action => [ t8('more') ],
351 call => [ 'set_history_window', $::form->{id} * 1, 'id' ],
352 disabled => !$::form->{id} ? t8('This record has not been saved yet.') : undef,
356 call => [ 'follow_up_window' ],
357 disabled => !$::form->{id} ? t8('This record has not been saved yet.') : undef,
359 ], # end if combobox "more"
362 $::request->layout->add_javascripts('kivi.Validator.js');
365 sub setup_do_search_action_bar {
368 for my $bar ($::request->layout->get('actionbar')) {
372 submit => [ '#form' ],
373 accesskey => 'enter',
374 checks => [ 'kivi.validate_form' ],
378 $::request->layout->add_javascripts('kivi.Validator.js');
381 sub setup_do_orders_action_bar {
384 for my $bar ($::request->layout->get('actionbar')) {
388 submit => [ '#form', { action => 'invoice_multi' } ],
389 checks => [ [ 'kivi.check_if_entries_selected', '#form tbody input[type=checkbox]' ] ],
390 accesskey => 'enter',
394 call => [ 'kivi.SalesPurchase.show_print_dialog', 'js:kivi.MassDeliveryOrderPrint.submitMultiOrders' ],
395 checks => [ [ 'kivi.check_if_entries_selected', '#form tbody input[type=checkbox]' ] ],
402 $main::lxdebug->enter_sub();
406 my $form = $main::form;
407 my %myconfig = %main::myconfig;
409 my $class = "SL::DB::" . ($form->{vc} eq 'customer' ? 'Customer' : 'Vendor');
410 $form->{VC_OBJ} = $class->load_cached($form->{ $form->{vc} . '_id' });
412 $form->{CONTACT_OBJ} = $form->{cp_id} ? SL::DB::Contact->load_cached($form->{cp_id}) : undef;
413 my $current_employee = SL::DB::Manager::Employee->current;
414 $form->{employee_id} = $form->{old_employee_id} if $form->{old_employee_id};
415 $form->{salesman_id} = $form->{old_salesman_id} if $form->{old_salesman_id};
416 $form->{employee_id} ||= $current_employee->id;
417 $form->{salesman_id} ||= $current_employee->id;
419 my $vc = $form->{vc} eq "customer" ? "customers" : "vendors";
420 $form->get_lists("price_factors" => "ALL_PRICE_FACTORS",
421 "business_types" => "ALL_BUSINESS_TYPES",
423 $form->{ALL_DEPARTMENTS} = SL::DB::Manager::Department->get_all_sorted;
426 my @old_project_ids = uniq grep { $_ } map { $_ * 1 } ($form->{"globalproject_id"}, map { $form->{"project_id_$_"} } 1..$form->{"rowcount"});
427 my @old_ids_cond = @old_project_ids ? (id => \@old_project_ids) : ();
429 if (($vc eq 'customers') && $::instance_conf->get_customer_projects_only_in_sales) {
432 customer_id => $::form->{customer_id},
433 billable_customer_id => $::form->{customer_id},
438 and => [ active => 1, @customer_cond ],
442 $::form->{ALL_PROJECTS} = SL::DB::Manager::Project->get_all_sorted(query => \@conditions);
443 $::form->{ALL_EMPLOYEES} = SL::DB::Manager::Employee->get_all_sorted(query => [ or => [ id => $::form->{employee_id}, deleted => 0 ] ]);
444 $::form->{ALL_SALESMEN} = SL::DB::Manager::Employee->get_all_sorted(query => [ or => [ id => $::form->{salesman_id}, deleted => 0 ] ]);
445 $::form->{ALL_SHIPTO} = SL::DB::Manager::Shipto->get_all_sorted(query => [
446 or => [ trans_id => $::form->{"$::form->{vc}_id"} * 1, and => [ shipto_id => $::form->{shipto_id} * 1, trans_id => undef ] ]
448 $::form->{ALL_CONTACTS} = SL::DB::Manager::Contact->get_all_sorted(query => [
450 cp_cv_id => $::form->{"$::form->{vc}_id"} * 1,
453 cp_id => $::form->{cp_id} * 1
458 my $dispatch_to_popup = '';
459 if ($form->{resubmit} && ($form->{format} eq "html")) {
460 $dispatch_to_popup = "window.open('about:blank','Beleg'); document.do.target = 'Beleg';";
461 $dispatch_to_popup .= "document.do.submit();";
462 } elsif ($form->{resubmit}) {
463 # emulate click for resubmitting actions
464 $dispatch_to_popup = "document.do.${_}.click(); " for grep { /^action_/ } keys %$form;
466 $::request->{layout}->add_javascripts_inline("\$(function(){$dispatch_to_popup});");
469 $form->{follow_up_trans_info} = $form->{donumber} .'('. $form->{VC_OBJ}->name .')' if $form->{VC_OBJ};
471 $::request->{layout}->use_javascript(map { "${_}.js" } qw(kivi.File kivi.MassDeliveryOrderPrint kivi.SalesPurchase kivi.Part kivi.CustomerVendor kivi.Validator ckeditor/ckeditor ckeditor/adapters/jquery kivi.io));
473 setup_do_action_bar();
476 # Fix für Bug 1082 Erwartet wird: 'abteilungsNAME--abteilungsID'
477 # und Erweiterung für Bug 1760:
478 # Das war leider nur ein Teil-Fix, da das Verhalten den 'Erneuern'-Knopf
479 # nicht überlebt. Konsequent jetzt auf L umgestellt
480 # $ perldoc SL::Template::Plugin::L
481 # Daher entsprechend nur die Anpassung in form_header
482 # und in DO.pm gemacht. 4 Testfälle:
483 # department_id speichern | i.O.
484 # department_id lesen | i.O.
485 # department leer überlebt erneuern | i.O.
486 # department nicht leer überlebt erneuern | i.O.
487 # $main::lxdebug->message(0, 'ABTEILUNGS ID in form?' . $form->{department_id});
488 print $form->parse_html_template('do/form_header');
490 $main::lxdebug->leave_sub();
494 $main::lxdebug->enter_sub();
498 my $form = $main::form;
500 $form->{PRINT_OPTIONS} = setup_sales_purchase_print_options();
501 $form->{ALL_DELIVERY_TERMS} = SL::DB::Manager::DeliveryTerm->get_all_sorted();
503 my $shipto_cvars = SL::DB::Shipto->new->cvars_by_config;
504 foreach my $var (@{ $shipto_cvars }) {
505 my $name = "shiptocvar_" . $var->config->name;
506 $var->value($form->{$name}) if exists $form->{$name};
509 print $form->parse_html_template('do/form_footer',
510 {transfer_default => ($::instance_conf->get_transfer_default),
511 shipto_cvars => $shipto_cvars});
513 $main::lxdebug->leave_sub();
516 sub update_delivery_order {
517 $main::lxdebug->enter_sub();
521 my $form = $main::form;
522 my %myconfig = %main::myconfig;
524 set_headings($form->{"id"} ? "edit" : "add");
526 $form->{insertdate} = SL::DB::DeliveryOrder->new(id => $form->{id})->load->itime_as_date if $form->{id};
531 $payment_id = $form->{payment_id} if $form->{payment_id};
533 my $vc = $form->{vc};
534 if (($form->{"previous_${vc}_id"} || $form->{"${vc}_id"}) != $form->{"${vc}_id"}) {
535 $::form->{salesman_id} = SL::DB::Manager::Employee->current->id if exists $::form->{salesman_id};
537 IS->get_customer(\%myconfig, $form) if $vc eq 'customer';
538 IR->get_vendor(\%myconfig, $form) if $vc eq 'vendor';
541 $form->{discount} = $form->{"$form->{vc}_discount"} if defined $form->{"$form->{vc}_discount"};
542 # Problem: Wenn man ohne Erneuern einen Kunden/Lieferanten
543 # wechselt, wird der entsprechende Kunden/ Lieferantenrabatt
544 # nicht übernommen. Grundproblem: In Commit 82574e78
545 # hab ich aus discount customer_discount und vendor_discount
546 # gemacht und entsprechend an den Oberflächen richtig hin-
547 # geschoben. Die damals bessere Lösung wäre gewesen:
548 # In den Templates nur die hidden für form-discount wieder ein-
549 # setzen dann wäre die Verrenkung jetzt nicht notwendig.
550 # TODO: Ggf. Bugfix 1284, 1575 und 817 wieder zusammenführen
551 # Testfälle: Kunden mit Rabatt 0 -> Rabatt 20 i.O.
552 # Kunde mit Rabatt 20 -> Rabatt 0 i.O.
553 # Kunde mit Rabatt 20 -> Rabatt 5,5 i.O.
554 $form->{payment_id} = $payment_id if $form->{payment_id} eq "";
556 my $i = $form->{rowcount};
558 if ( ($form->{"partnumber_$i"} eq "")
559 && ($form->{"description_$i"} eq "")
560 && ($form->{"partsgroup_$i"} eq "")) {
567 if ($form->{type} eq 'purchase_delivery_order') {
568 IR->retrieve_item(\%myconfig, $form);
571 IS->retrieve_item(\%myconfig, $form);
575 my $rows = scalar @{ $form->{item_list} };
578 $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
579 if( !$form->{"qty_$i"} ) {
580 $form->{"qty_$i"} = 1;
585 select_item(mode => $mode, pre_entered_qty => $form->{"qty_$i"});
586 $::dispatcher->end_request;
590 my $sellprice = $form->parse_amount(\%myconfig, $form->{"sellprice_$i"});
592 map { $form->{"${_}_$i"} = $form->{item_list}[0]{$_} } keys %{ $form->{item_list}[0] };
594 $form->{"marge_price_factor_$i"} = $form->{item_list}->[0]->{price_factor};
597 $form->{"sellprice_$i"} = $sellprice;
599 my $record = _make_record();
600 my $price_source = SL::PriceSource->new(record_item => $record->items->[$i-1], record => $record);
601 my $best_price = $price_source->best_price;
602 my $best_discount = $price_source->best_discount;
605 $::form->{"sellprice_$i"} = $best_price->price;
606 $::form->{"active_price_source_$i"} = $best_price->source;
608 if ($best_discount) {
609 $::form->{"discount_$i"} = $best_discount->discount;
610 $::form->{"active_discount_source_$i"} = $best_discount->source;
614 $form->{"sellprice_$i"} = $form->format_amount(\%myconfig, $form->{"sellprice_$i"});
615 $form->{"lastcost_$i"} = $form->format_amount(\%myconfig, $form->{"lastcost_$i"});
616 $form->{"qty_$i"} = $form->format_amount(\%myconfig, $form->{"qty_$i"});
617 $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"} * 100.0);
624 # ok, so this is a new part
625 # ask if it is a part or service item
627 if ( $form->{"partsgroup_$i"}
628 && ($form->{"partsnumber_$i"} eq "")
629 && ($form->{"description_$i"} eq "")) {
631 $form->{"discount_$i"} = "";
632 $form->{"not_discountable_$i"} = "";
636 $form->{"id_$i"} = 0;
642 $main::lxdebug->leave_sub();
646 $main::lxdebug->enter_sub();
650 my $form = $main::form;
651 my %myconfig = %main::myconfig;
652 my $locale = $main::locale;
654 $form->{vc} = $form->{type} eq 'purchase_delivery_order' ? 'vendor' : 'customer';
656 $form->get_lists("projects" => { "key" => "ALL_PROJECTS",
658 "business_types" => "ALL_BUSINESS_TYPES");
659 $form->{ALL_EMPLOYEES} = SL::DB::Manager::Employee->get_all_sorted(query => [ deleted => 0 ]);
660 $form->{ALL_DEPARTMENTS} = SL::DB::Manager::Department->get_all_sorted;
661 $form->{title} = $locale->text('Delivery Orders');
663 setup_do_search_action_bar();
667 print $form->parse_html_template('do/search');
669 $main::lxdebug->leave_sub();
673 $main::lxdebug->enter_sub();
677 my $form = $main::form;
678 my %myconfig = %main::myconfig;
679 my $locale = $main::locale;
680 my $cgi = $::request->{cgi};
682 $::request->{layout}->use_javascript(map { "${_}.js" } qw(kivi.MassDeliveryOrderPrint kivi.SalesPurchase));
683 ($form->{ $form->{vc} }, $form->{"$form->{vc}_id"}) = split(/--/, $form->{ $form->{vc} });
685 report_generator_set_default_sort('transdate', 1);
689 $form->{rowcount} = scalar @{ $form->{DO} };
692 ids transdate reqdate
694 ordnumber customernumber cusordnumber
695 name employee salesman
696 shipvia globalprojectnumber
697 transaction_description department
702 $form->{l_open} = $form->{l_closed} = "Y" if ($form->{open} && $form->{closed});
703 $form->{l_delivered} = "Y" if ($form->{delivered} && $form->{notdelivered});
705 $form->{title} = $locale->text('Delivery Orders');
707 my $attachment_basename = $form->{vc} eq 'vendor' ? $locale->text('purchase_delivery_order_list') : $locale->text('sales_delivery_order_list');
709 my $report = SL::ReportGenerator->new(\%myconfig, $form);
711 my @hidden_variables = map { "l_${_}" } @columns;
712 push @hidden_variables, $form->{vc}, qw(l_closed l_notdelivered open closed delivered notdelivered donumber ordnumber serialnumber cusordnumber
713 transaction_description transdatefrom transdateto reqdatefrom reqdateto
714 type vc employee_id salesman_id project_id parts_partnumber parts_description
715 insertdatefrom insertdateto business_id all department_id);
717 my $href = build_std_url('action=orders', grep { $form->{$_} } @hidden_variables);
720 'ids' => { raw_header_data => SL::Presenter::Tag::checkbox_tag("", id => "multi_all", checkall => "[data-checkall=1]"), align => 'center' },
721 'transdate' => { 'text' => $locale->text('Delivery Order Date'), },
722 'reqdate' => { 'text' => $locale->text('Reqdate'), },
723 'id' => { 'text' => $locale->text('ID'), },
724 'donumber' => { 'text' => $locale->text('Delivery Order'), },
725 'ordnumber' => { 'text' => $locale->text('Order'), },
726 'customernumber' => { 'text' => $locale->text('Customer Number'), },
727 'cusordnumber' => { 'text' => $locale->text('Customer Order Number'), },
728 'name' => { 'text' => $form->{vc} eq 'customer' ? $locale->text('Customer') : $locale->text('Vendor'), },
729 'employee' => { 'text' => $locale->text('Employee'), },
730 'salesman' => { 'text' => $locale->text('Salesman'), },
731 'shipvia' => { 'text' => $locale->text('Ship via'), },
732 'globalprojectnumber' => { 'text' => $locale->text('Project Number'), },
733 'transaction_description' => { 'text' => $locale->text('Transaction description'), },
734 'open' => { 'text' => $locale->text('Open'), },
735 'delivered' => { 'text' => $locale->text('Delivered'), },
736 'department' => { 'text' => $locale->text('Department'), },
737 'insertdate' => { 'text' => $locale->text('Insert Date'), },
740 foreach my $name (qw(id transdate reqdate donumber ordnumber name employee salesman shipvia transaction_description department insertdate)) {
741 my $sortdir = $form->{sort} eq $name ? 1 - $form->{sortdir} : $form->{sortdir};
742 $column_defs{$name}->{link} = $href . "&sort=$name&sortdir=$sortdir";
745 $form->{"l_type"} = "Y";
746 map { $column_defs{$_}->{visible} = $form->{"l_${_}"} ? 1 : 0 } @columns;
748 $column_defs{ids}->{visible} = 'HTML';
750 $report->set_columns(%column_defs);
751 $report->set_column_order(@columns);
753 $report->set_export_options('orders', @hidden_variables, qw(sort sortdir));
755 $report->set_sort_indicator($form->{sort}, $form->{sortdir});
758 if ($form->{customer}) {
759 push @options, $locale->text('Customer') . " : $form->{customer}";
761 if ($form->{vendor}) {
762 push @options, $locale->text('Vendor') . " : $form->{vendor}";
764 if ($form->{cp_name}) {
765 push @options, $locale->text('Contact Person') . " : $form->{cp_name}";
767 if ($form->{department_id}) {
768 push @options, $locale->text('Department') . " : " . SL::DB::Department->new(id => $form->{department_id})->load->description;
770 if ($form->{donumber}) {
771 push @options, $locale->text('Delivery Order Number') . " : $form->{donumber}";
773 if ($form->{ordnumber}) {
774 push @options, $locale->text('Order Number') . " : $form->{ordnumber}";
776 push @options, $locale->text('Serial Number') . " : $form->{serialnumber}" if $form->{serialnumber};
777 if ($form->{business_id}) {
778 my $vc_type_label = $form->{vc} eq 'customer' ? $locale->text('Customer type') : $locale->text('Vendor type');
779 push @options, $vc_type_label . " : " . SL::DB::Business->new(id => $form->{business_id})->load->description;
781 if ($form->{transaction_description}) {
782 push @options, $locale->text('Transaction description') . " : $form->{transaction_description}";
784 if ($form->{parts_description}) {
785 push @options, $locale->text('Part Description') . " : $form->{parts_description}";
787 if ($form->{parts_partnumber}) {
788 push @options, $locale->text('Part Number') . " : $form->{parts_partnumber}";
790 if ( $form->{transdatefrom} or $form->{transdateto} ) {
791 push @options, $locale->text('Delivery Order Date');
792 push @options, $locale->text('From') . " " . $locale->date(\%myconfig, $form->{transdatefrom}, 1) if $form->{transdatefrom};
793 push @options, $locale->text('Bis') . " " . $locale->date(\%myconfig, $form->{transdateto}, 1) if $form->{transdateto};
795 if ( $form->{reqdatefrom} or $form->{reqdateto} ) {
796 push @options, $locale->text('Reqdate');
797 push @options, $locale->text('From') . " " . $locale->date(\%myconfig, $form->{reqdatefrom}, 1) if $form->{reqdatefrom};
798 push @options, $locale->text('Bis') . " " . $locale->date(\%myconfig, $form->{reqdateto}, 1) if $form->{reqdateto};
800 if ( $form->{insertdatefrom} or $form->{insertdateto} ) {
801 push @options, $locale->text('Insert Date');
802 push @options, $locale->text('From') . " " . $locale->date(\%myconfig, $form->{insertdatefrom}, 1) if $form->{insertdatefrom};
803 push @options, $locale->text('Bis') . " " . $locale->date(\%myconfig, $form->{insertdateto}, 1) if $form->{insertdateto};
806 push @options, $locale->text('Open');
808 if ($form->{closed}) {
809 push @options, $locale->text('Closed');
811 if ($form->{delivered}) {
812 push @options, $locale->text('Delivered');
814 if ($form->{notdelivered}) {
815 push @options, $locale->text('Not delivered');
817 push @options, $locale->text('Quick Search') . " : $form->{all}" if $form->{all};
819 my $pr = SL::DB::Manager::Printer->find_by(
820 printer_description => $::locale->text("sales_delivery_order_printer"));
822 $form->{printer_id} = $pr->id;
825 my $print_options = SL::Helper::PrintOptions->get_print_options(
827 hide_language_id => 1,
833 $report->set_options('top_info_text' => join("\n", @options),
834 'raw_top_info_text' => $form->parse_html_template('do/orders_top'),
835 'raw_bottom_info_text' => $form->parse_html_template('do/orders_bottom', { print_options => $print_options }),
836 'output_format' => 'HTML',
837 'title' => $form->{title},
838 'attachment_basename' => $attachment_basename . strftime('_%Y%m%d', localtime time),
840 $report->set_options_from_form();
841 $locale->set_numberformat_wo_thousands_separator(\%myconfig) if lc($report->{options}->{output_format}) eq 'csv';
843 # add sort and escape callback, this one we use for the add sub
844 $form->{callback} = $href .= "&sort=$form->{sort}";
846 # escape callback for href
847 my $callback = $form->escape($href);
849 my $edit_url = build_std_url('action=edit', 'type', 'vc');
850 my $edit_order_url = ($::instance_conf->get_feature_experimental_order)
851 ? build_std_url('script=controller.pl', 'action=Order/edit', 'type=' . ($form->{type} eq 'sales_delivery_order' ? 'sales_order' : 'purchase_order'))
852 : build_std_url('script=oe.pl', 'action=edit', 'type=' . ($form->{type} eq 'sales_delivery_order' ? 'sales_order' : 'purchase_order'));
856 foreach my $dord (@{ $form->{DO} }) {
857 $dord->{open} = $dord->{closed} ? $locale->text('No') : $locale->text('Yes');
858 $dord->{delivered} = $dord->{delivered} ? $locale->text('Yes') : $locale->text('No');
860 my $row = { map { $_ => { 'data' => $dord->{$_} } } @columns };
862 my $ord_id = $dord->{id};
864 'raw_data' => $cgi->hidden('-name' => "trans_id_${idx}", '-value' => $ord_id)
865 . $cgi->checkbox('-name' => "multi_id_${idx}",' id' => "multi_id_id_".$ord_id, '-value' => 1, 'data-checkall' => 1, '-label' => ''),
866 'valign' => 'center',
870 $row->{donumber}->{link} = $edit_url . "&id=" . E($dord->{id}) . "&callback=${callback}";
871 $row->{ordnumber}->{link} = $edit_order_url . "&id=" . E($dord->{oe_id}) . "&callback=${callback}" if $dord->{oe_id};
872 $report->add_data($row);
877 setup_do_orders_action_bar();
879 $report->generate_with_headers();
881 $main::lxdebug->leave_sub();
885 $main::lxdebug->enter_sub();
891 my $form = $main::form;
892 my %myconfig = %main::myconfig;
893 my $locale = $main::locale;
895 $form->mtime_ischanged('delivery_orders');
897 $form->{defaultcurrency} = $form->get_default_currency(\%myconfig);
899 $form->isblank("transdate", $locale->text('Delivery Order Date missing!'));
901 $form->{donumber} =~ s/^\s*//g;
902 $form->{donumber} =~ s/\s*$//g;
904 my $msg = ucfirst $form->{vc};
905 $form->isblank($form->{vc} . "_id", $locale->text($msg . " missing!"));
907 # $locale->text('Customer missing!');
908 # $locale->text('Vendor missing!');
910 remove_emptied_rows();
913 # if the name changed get new values
914 my $vc = $form->{vc};
915 if (($form->{"previous_${vc}_id"} || $form->{"${vc}_id"}) != $form->{"${vc}_id"}) {
916 $::form->{salesman_id} = SL::DB::Manager::Employee->current->id if exists $::form->{salesman_id};
918 IS->get_customer(\%myconfig, $form) if $vc eq 'customer';
919 IR->get_vendor(\%myconfig, $form) if $vc eq 'vendor';
922 $::dispatcher->end_request;
925 $form->{id} = 0 if $form->{saveasnew};
929 if(!exists $form->{addition}) {
930 $form->{snumbers} = qq|donumber_| . $form->{donumber};
931 $form->{addition} = "SAVED";
934 # /saving the history
936 $form->{simple_save} = 1;
937 if (!$params{no_redirect} && !$form->{print_and_save}) {
938 delete @{$form}{ary_diff([keys %{ $form }], [qw(login id script type cursor_fokus)])};
940 $::dispatcher->end_request;
942 $main::lxdebug->leave_sub();
946 $main::lxdebug->enter_sub();
950 my $form = $main::form;
951 my %myconfig = %main::myconfig;
952 my $locale = $main::locale;
954 if ($ret = DO->delete()) {
956 if(!exists $form->{addition}) {
957 $form->{snumbers} = qq|donumber_| . $form->{donumber};
958 $form->{addition} = "DELETED";
961 # /saving the history
963 $form->info($locale->text('Delivery Order deleted!'));
964 $::dispatcher->end_request;
967 $form->error($locale->text('Cannot delete delivery order!') . $ret);
969 $main::lxdebug->leave_sub();
973 $main::lxdebug->enter_sub();
975 my $form = $main::form;
976 my %myconfig = %main::myconfig;
977 my $locale = $main::locale;
980 $form->mtime_ischanged('delivery_orders');
982 $main::auth->assert($form->{type} eq 'purchase_delivery_order' ? 'vendor_invoice_edit' : 'invoice_edit');
984 $form->{convert_from_do_ids} = $form->{id};
985 # if we have a reqdate (Liefertermin), this is definetely the preferred
986 # deliverydate for invoices
987 $form->{deliverydate} = $form->{reqdate} || $form->{transdate};
988 $form->{transdate} = $form->{invdate} = $form->current_date(\%myconfig);
989 $form->{duedate} = $form->current_date(\%myconfig, $form->{invdate}, $form->{terms} * 1);
990 $form->{defaultcurrency} = $form->get_default_currency(\%myconfig);
994 delete @{$form}{qw(id closed delivered)};
996 my ($script, $buysell);
997 if ($form->{type} eq 'purchase_delivery_order') {
998 $form->{title} = $locale->text('Add Vendor Invoice');
999 $form->{script} = 'ir.pl';
1004 $form->{title} = $locale->text('Add Sales Invoice');
1005 $form->{script} = 'is.pl';
1010 for my $i (1 .. $form->{rowcount}) {
1011 map { $form->{"${_}_${i}"} = $form->parse_amount(\%myconfig, $form->{"${_}_${i}"}) if $form->{"${_}_${i}"} } qw(ship qty sellprice lastcost basefactor discount);
1013 # adds a customer/vendor discount, unless we have a workflow case
1014 # CAVEAT: has to be done, after the above parse_amount
1015 unless ($form->{"ordnumber"}) {
1016 if ($form->{discount}) { # Falls wir einen Lieferanten-/Kundenrabatt haben
1017 # und rabattfähig sind, dann
1018 unless ($form->{"not_discountable_$i"}) {
1019 $form->{"discount_$i"} = $form->{discount}*100; # ... nehmen wir diesen Rabatt
1023 $form->{"donumber_$i"} = $form->{donumber};
1024 $form->{"converted_from_delivery_order_items_id_$i"} = delete $form->{"delivery_order_items_id_$i"};
1027 $form->{type} = "invoice";
1030 $main::locale = Locale->new("$myconfig{countrycode}", "$script");
1031 $locale = $main::locale;
1033 require "bin/mozilla/$form->{script}";
1035 my $currency = $form->{currency};
1038 if ($form->{ordnumber}) {
1039 require SL::DB::Order;
1040 my $vc_id = $form->{type} =~ /^sales/ ? 'customer_id' : 'vendor_id';
1041 if (my $order = SL::DB::Manager::Order->find_by(ordnumber => $form->{ordnumber}, $vc_id => $form->{"$vc_id"})) {
1043 $form->{orddate} = $order->transdate_as_date;
1044 $form->{$_} = $order->$_ for qw(payment_id salesman_id taxzone_id quonumber);
1048 $form->{currency} = $currency;
1049 $form->{exchangerate} = "";
1050 $form->{forex} = $form->check_exchangerate(\%myconfig, $form->{currency}, $form->{invdate}, $buysell);
1051 $form->{exchangerate} = $form->{forex} if ($form->{forex});
1056 for my $i (1 .. $form->{rowcount}) {
1057 $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"});
1059 my ($dec) = ($form->{"sellprice_$i"} =~ /\.(\d+)/);
1061 my $decimalplaces = ($dec > 2) ? $dec : 2;
1063 # copy delivery date from reqdate for order -> invoice conversion
1064 $form->{"deliverydate_$i"} = $form->{"reqdate_$i"}
1065 unless $form->{"deliverydate_$i"};
1068 $form->{"sellprice_$i"} =
1069 $form->format_amount(\%myconfig, $form->{"sellprice_$i"},
1072 $form->{"lastcost_$i"} =
1073 $form->format_amount(\%myconfig, $form->{"lastcost_$i"},
1076 (my $dec_qty) = ($form->{"qty_$i"} =~ /\.(\d+)/);
1077 $dec_qty = length $dec_qty;
1079 $form->format_amount(\%myconfig, $form->{"qty_$i"}, $dec_qty);
1085 $main::lxdebug->leave_sub();
1089 $main::lxdebug->enter_sub();
1091 my $form = $main::form;
1092 my %myconfig = %main::myconfig;
1093 my $locale = $main::locale;
1096 $main::auth->assert($form->{type} eq 'sales_delivery_order' ? 'invoice_edit' : 'vendor_invoice_edit');
1098 my @do_ids = map { $form->{"trans_id_$_"} } grep { $form->{"multi_id_$_"} } (1..$form->{rowcount});
1100 if (!scalar @do_ids) {
1101 $form->show_generic_error($locale->text('You have not selected any delivery order.'));
1104 map { delete $form->{$_} } grep { m/^(?:trans|multi)_id_\d+/ } keys %{ $form };
1106 if (!DO->retrieve('vc' => $form->{vc}, 'ids' => \@do_ids)) {
1107 $form->show_generic_error($form->{vc} eq 'customer' ?
1108 $locale->text('You cannot create an invoice for delivery orders for different customers.') :
1109 $locale->text('You cannot create an invoice for delivery orders from different vendors.'),
1110 'back_button' => 1);
1113 my $source_type = $form->{type};
1114 $form->{convert_from_do_ids} = join ' ', @do_ids;
1115 # bei der auswahl von mehreren Lieferscheinen fuer eine Rechnung, die einfach in donumber_array
1116 # zwischenspeichern (DO.pm) und als ' '-separierte Liste wieder zurueckschreiben
1117 # Hinweis: delete gibt den wert zurueck und loescht danach das element (nett und einfach)
1118 # $shell: perldoc perlunc; /delete EXPR
1119 $form->{donumber} = delete $form->{donumber_array};
1120 $form->{ordnumber} = delete $form->{ordnumber_array};
1121 $form->{cusordnumber} = delete $form->{cusordnumber_array};
1122 $form->{deliverydate} = $form->{transdate};
1123 $form->{transdate} = $form->current_date(\%myconfig);
1124 $form->{duedate} = $form->current_date(\%myconfig, $form->{invdate}, $form->{terms} * 1);
1125 $form->{type} = "invoice";
1126 $form->{closed} = 0;
1127 $form->{defaultcurrency} = $form->get_default_currency(\%myconfig);
1129 my ($script, $buysell);
1130 if ($source_type eq 'purchase_delivery_order') {
1131 $form->{title} = $locale->text('Add Vendor Invoice');
1132 $form->{script} = 'ir.pl';
1137 $form->{title} = $locale->text('Add Sales Invoice');
1138 $form->{script} = 'is.pl';
1143 map { delete $form->{$_} } qw(id subject message cc bcc printed emailed queued);
1145 # get vendor or customer discount
1147 my $saved_form = save_form();
1148 if ($form->{vc} eq 'vendor') {
1149 IR->get_vendor(\%myconfig, \%$form);
1150 $vc_discount = $form->{vendor_discount};
1152 IS->get_customer(\%myconfig, \%$form);
1153 $vc_discount = $form->{customer_discount};
1155 # use payment terms from customer or vendor
1156 restore_form($saved_form,0,qw(payment_id));
1158 $form->{rowcount} = 0;
1159 foreach my $ref (@{ $form->{form_details} }) {
1160 $form->{rowcount}++;
1161 $ref->{reqdate} ||= $ref->{dord_transdate}; # copy transdates into each invoice row
1162 map { $form->{"${_}_$form->{rowcount}"} = $ref->{$_} } keys %{ $ref };
1163 map { $form->{"${_}_$form->{rowcount}"} = $form->format_amount(\%myconfig, $ref->{$_}) } qw(qty sellprice lastcost);
1164 $form->{"converted_from_delivery_order_items_id_$form->{rowcount}"} = delete $form->{"delivery_order_items_id_$form->{rowcount}"};
1166 if ($vc_discount){ # falls wir einen Lieferanten/Kundenrabatt haben
1167 # und keinen anderen discount wert an $i ...
1168 $form->{"discount_$form->{rowcount}"} ||= $vc_discount; # ... nehmen wir diesen Rabatt
1171 $form->{"discount_$form->{rowcount}"} = $form->{"discount_$form->{rowcount}"} * 100; #s.a. Bug 1151
1172 # Anm.: Eine Änderung des discounts in der SL/DO.pm->retrieve (select (doi.discount * 100) as discount) ergibt in psql einen
1173 # Wert von 10.0000001490116. Ferner ist der Rabatt in der Rechnung dann bei 1.0 (?). Deswegen lasse ich das hier. jb 10.10.09
1175 $form->{"discount_$form->{rowcount}"} = $form->format_amount(\%myconfig, $form->{"discount_$form->{rowcount}"});
1177 delete $form->{form_details};
1179 $locale = Locale->new("$myconfig{countrycode}", "$script");
1181 require "bin/mozilla/$form->{script}";
1188 $main::lxdebug->leave_sub();
1192 $main::lxdebug->enter_sub();
1196 my $form = $main::form;
1198 $form->{saveasnew} = 1;
1199 $form->{closed} = 0;
1200 $form->{delivered} = 0;
1201 map { delete $form->{$_} } qw(printed emailed queued);
1202 delete @{ $form }{ grep { m/^stock_(?:in|out)_\d+/ } keys %{ $form } };
1203 $form->{"converted_from_delivery_order_items_id_$_"} = delete $form->{"delivery_order_items_id_$_"} for 1 .. $form->{"rowcount"};
1204 # Let kivitendo assign a new order number if the user hasn't changed the
1205 # previous one. If it has been changed manually then use it as-is.
1206 $form->{donumber} =~ s/^\s*//g;
1207 $form->{donumber} =~ s/\s*$//g;
1208 if ($form->{saved_donumber} && ($form->{saved_donumber} eq $form->{donumber})) {
1209 delete($form->{donumber});
1214 $main::lxdebug->leave_sub();
1217 sub calculate_stock_in_out {
1218 $main::lxdebug->enter_sub();
1220 my $form = $main::form;
1224 if (!$form->{"id_${i}"}) {
1225 $main::lxdebug->leave_sub();
1229 my $all_units = AM->retrieve_all_units();
1231 my $in_out = $form->{type} =~ /^sales/ ? 'out' : 'in';
1232 my $sinfo = DO->unpack_stock_information('packed' => $form->{"stock_${in_out}_${i}"});
1234 my $do_qty = AM->sum_with_unit($::form->{"qty_$i"}, $::form->{"unit_$i"});
1235 my $sum = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $sinfo });
1236 my $matches = $do_qty == $sum;
1238 my $content = $form->format_amount_units('amount' => $sum * 1,
1239 'part_unit' => $form->{"partunit_$i"},
1240 'amount_unit' => $all_units->{$form->{"partunit_$i"}}->{base_unit},
1241 'conv_units' => 'convertible_not_smaller',
1243 $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="?">|;
1245 $main::lxdebug->leave_sub();
1250 sub get_basic_bin_wh_info {
1251 $main::lxdebug->enter_sub();
1253 my $stock_info = shift;
1255 my $form = $main::form;
1257 foreach my $sinfo (@{ $stock_info }) {
1258 next unless ($sinfo->{bin_id});
1260 my $bin_info = WH->get_basic_bin_info('id' => $sinfo->{bin_id});
1261 map { $sinfo->{"${_}_description"} = $sinfo->{"${_}description"} = $bin_info->{"${_}_description"} } qw(bin warehouse);
1264 $main::lxdebug->leave_sub();
1267 sub stock_in_out_form {
1268 $main::lxdebug->enter_sub();
1270 my $form = $main::form;
1272 if ($form->{in_out} eq 'out') {
1278 $main::lxdebug->leave_sub();
1281 sub redo_stock_info {
1282 $main::lxdebug->enter_sub();
1286 my $form = $main::form;
1288 my @non_empty = grep { $_->{qty} } @{ $params{stock_info} };
1290 if ($params{add_empty_row}) {
1292 'warehouse_id' => scalar(@non_empty) ? $non_empty[-1]->{warehouse_id} : undef,
1293 'bin_id' => scalar(@non_empty) ? $non_empty[-1]->{bin_id} : undef,
1297 @{ $params{stock_info} } = @non_empty;
1299 $main::lxdebug->leave_sub();
1302 sub update_stock_in {
1303 $main::lxdebug->enter_sub();
1305 my $form = $main::form;
1306 my %myconfig = %main::myconfig;
1308 my $stock_info = [];
1310 foreach my $i (1..$form->{rowcount}) {
1311 $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
1312 push @{ $stock_info }, { map { $_ => $form->{"${_}_${i}"} } qw(warehouse_id bin_id chargenumber
1313 bestbefore qty unit delivery_order_items_stock_id) };
1316 display_stock_in_form($stock_info);
1318 $main::lxdebug->leave_sub();
1322 $main::lxdebug->enter_sub();
1324 my $form = $main::form;
1326 my $stock_info = DO->unpack_stock_information('packed' => $form->{stock});
1328 display_stock_in_form($stock_info);
1330 $main::lxdebug->leave_sub();
1333 sub display_stock_in_form {
1334 $main::lxdebug->enter_sub();
1336 my $stock_info = shift;
1338 my $form = $main::form;
1339 my %myconfig = %main::myconfig;
1340 my $locale = $main::locale;
1342 $form->{title} = $locale->text('Stock');
1344 my $part_info = IC->get_basic_part_info('id' => $form->{parts_id});
1346 # Standardlagerplatz für Standard-Auslagern verwenden, falls keiner für die Ware explizit definiert wurde
1347 if ($::instance_conf->get_transfer_default_use_master_default_bin) {
1348 $part_info->{warehouse_id} ||= $::instance_conf->get_warehouse_id;
1349 $part_info->{bin_id} ||= $::instance_conf->get_bin_id;
1352 my $units = AM->retrieve_units(\%myconfig, $form);
1353 # der zweite Parameter von unit_select_data gibt den default-Namen (selected) vor
1354 my $units_data = AM->unit_select_data($units, $form->{do_unit}, undef, $part_info->{unit});
1356 $form->get_lists('warehouses' => { 'key' => 'WAREHOUSES',
1357 'bins' => 'BINS' });
1359 redo_stock_info('stock_info' => $stock_info, 'add_empty_row' => !$form->{delivered});
1361 get_basic_bin_wh_info($stock_info);
1363 $form->header(no_layout => 1);
1364 print $form->parse_html_template('do/stock_in_form', { 'UNITS' => $units_data,
1365 'STOCK_INFO' => $stock_info,
1366 'PART_INFO' => $part_info, });
1368 $main::lxdebug->leave_sub();
1371 sub _stock_in_out_set_qty_display {
1372 my $stock_info = shift;
1374 my $all_units = AM->retrieve_all_units();
1375 my $sum = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
1376 $form->{qty_display} = $form->format_amount_units(amount => $sum * 1,
1377 part_unit => $form->{partunit},
1378 amount_unit => $all_units->{ $form->{partunit} }->{base_unit},
1379 conv_units => 'convertible_not_smaller',
1384 $main::lxdebug->enter_sub();
1386 my $form = $main::form;
1387 my %myconfig = %main::myconfig;
1389 my $stock_info = [];
1391 foreach my $i (1..$form->{rowcount}) {
1392 $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
1394 next if ($form->{"qty_$i"} <= 0);
1396 push @{ $stock_info }, { map { $_ => $form->{"${_}_${i}"} } qw(delivery_order_items_stock_id warehouse_id bin_id chargenumber bestbefore qty unit) };
1399 $form->{stock} = SL::YAML::Dump($stock_info);
1401 _stock_in_out_set_qty_display($stock_info);
1403 my $do_qty = AM->sum_with_unit($::form->parse_amount(\%::myconfig, $::form->{do_qty}), $::form->{do_unit});
1404 my $transfer_qty = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
1407 print $form->parse_html_template('do/set_stock_in_out', {
1408 qty_matches => $do_qty == $transfer_qty,
1411 $main::lxdebug->leave_sub();
1414 sub stock_out_form {
1415 $main::lxdebug->enter_sub();
1417 my $form = $main::form;
1418 my %myconfig = %main::myconfig;
1419 my $locale = $main::locale;
1421 $form->{title} = $locale->text('Release From Stock');
1423 my $part_info = IC->get_basic_part_info('id' => $form->{parts_id});
1425 my $units = AM->retrieve_units(\%myconfig, $form);
1426 my $units_data = AM->unit_select_data($units, undef, undef, $part_info->{unit});
1428 my @contents = DO->get_item_availability('parts_id' => $form->{parts_id});
1430 my $stock_info = DO->unpack_stock_information('packed' => $form->{stock});
1432 if (!$form->{delivered}) {
1433 foreach my $row (@contents) {
1434 $row->{available_qty} = $form->format_amount_units('amount' => $row->{qty} * 1,
1435 'part_unit' => $part_info->{unit},
1436 'conv_units' => 'convertible_not_smaller',
1439 foreach my $sinfo (@{ $stock_info }) {
1440 next if (($row->{bin_id} != $sinfo->{bin_id}) ||
1441 ($row->{warehouse_id} != $sinfo->{warehouse_id}) ||
1442 ($row->{chargenumber} ne $sinfo->{chargenumber}) ||
1443 ($row->{bestbefore} ne $sinfo->{bestbefore}));
1445 map { $row->{"stock_$_"} = $sinfo->{$_} } qw(qty unit error delivery_order_items_stock_id);
1450 get_basic_bin_wh_info($stock_info);
1452 foreach my $sinfo (@{ $stock_info }) {
1453 map { $sinfo->{"stock_$_"} = $sinfo->{$_} } qw(qty unit);
1457 $form->header(no_layout => 1);
1458 print $form->parse_html_template('do/stock_out_form', { 'UNITS' => $units_data,
1459 'WHCONTENTS' => $form->{delivered} ? $stock_info : \@contents,
1460 'PART_INFO' => $part_info, });
1462 $main::lxdebug->leave_sub();
1466 $main::lxdebug->enter_sub();
1468 my $form = $main::form;
1469 my %myconfig = %main::myconfig;
1470 my $locale = $main::locale;
1472 my $stock_info = [];
1474 foreach my $i (1 .. $form->{rowcount}) {
1475 $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
1477 next if ($form->{"qty_$i"} <= 0);
1479 push @{ $stock_info }, {
1480 'warehouse_id' => $form->{"warehouse_id_$i"},
1481 'bin_id' => $form->{"bin_id_$i"},
1482 'chargenumber' => $form->{"chargenumber_$i"},
1483 'bestbefore' => $form->{"bestbefore_$i"},
1484 'qty' => $form->{"qty_$i"},
1485 'unit' => $form->{"unit_$i"},
1487 'delivery_order_items_stock_id' => $form->{"delivery_order_items_stock_id_$i"},
1491 my @errors = DO->check_stock_availability('requests' => $stock_info,
1492 'parts_id' => $form->{parts_id});
1494 $form->{stock} = SL::YAML::Dump($stock_info);
1497 $form->{ERRORS} = [];
1498 map { push @{ $form->{ERRORS} }, $locale->text('Error in row #1: The quantity you entered is bigger than the stocked quantity.', $_->{row}); } @errors;
1499 stock_in_out_form();
1502 _stock_in_out_set_qty_display($stock_info);
1504 my $do_qty = AM->sum_with_unit($::form->parse_amount(\%::myconfig, $::form->{do_qty}), $::form->{do_unit});
1505 my $transfer_qty = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
1508 print $form->parse_html_template('do/set_stock_in_out', {
1509 qty_matches => $do_qty == $transfer_qty,
1513 $main::lxdebug->leave_sub();
1517 $main::lxdebug->enter_sub();
1519 my $form = $main::form;
1520 my %myconfig = %main::myconfig;
1521 my $locale = $main::locale;
1523 if ($form->{id} && DO->is_marked_as_delivered(id => $form->{id})) {
1524 $form->show_generic_error($locale->text('The parts for this delivery order have already been transferred in.'));
1527 save(no_redirect => 1);
1529 my @part_ids = map { $form->{"id_${_}"} } grep { $form->{"id_${_}"} && $form->{"stock_in_${_}"} } (1 .. $form->{rowcount});
1533 my $units = AM->retrieve_units(\%myconfig, $form);
1534 my %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
1537 $form->{ERRORS} = [];
1539 foreach my $i (1 .. $form->{rowcount}) {
1540 next unless ($form->{"id_$i"} && $form->{"stock_in_$i"});
1542 my $row_sum_base_qty = 0;
1543 my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
1545 foreach my $request (@{ DO->unpack_stock_information('packed' => $form->{"stock_in_$i"}) }) {
1546 $request->{parts_id} = $form->{"id_$i"};
1547 $row_sum_base_qty += $request->{qty} * $units->{$request->{unit}}->{factor} / $base_unit_factor;
1549 $request->{project_id} = $form->{"project_id_$i"} || $form->{globalproject_id};
1551 push @all_requests, $request;
1554 next if (0 == $row_sum_base_qty);
1556 my $do_base_qty = $form->parse_amount(\%myconfig, $form->{"qty_$i"}) * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
1558 # if ($do_base_qty != $row_sum_base_qty) {
1559 # push @{ $form->{ERRORS} }, $locale->text('Error in position #1: You must either assign no stock at all or the full quantity of #2 #3.',
1560 # $i, $form->{"qty_$i"}, $form->{"unit_$i"});
1564 if (@{ $form->{ERRORS} }) {
1565 push @{ $form->{ERRORS} }, $locale->text('The delivery order has not been marked as delivered. The warehouse contents have not changed.');
1567 set_headings('edit');
1569 $main::lxdebug->leave_sub();
1571 $::dispatcher->end_request;
1575 DO->transfer_in_out('direction' => 'in',
1576 'requests' => \@all_requests);
1578 SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
1580 $form->{callback} = 'do.pl?action=edit&type=purchase_delivery_order&id=' . $form->escape($form->{id});
1583 $main::lxdebug->leave_sub();
1587 $main::lxdebug->enter_sub();
1589 my $form = $main::form;
1590 my %myconfig = %main::myconfig;
1591 my $locale = $main::locale;
1593 if ($form->{id} && DO->is_marked_as_delivered(id => $form->{id})) {
1594 $form->show_generic_error($locale->text('The parts for this delivery order have already been transferred out.'));
1597 save(no_redirect => 1);
1599 my @part_ids = map { $form->{"id_${_}"} } grep { $form->{"id_${_}"} && $form->{"stock_out_${_}"} } (1 .. $form->{rowcount});
1603 my $units = AM->retrieve_units(\%myconfig, $form);
1604 my %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
1607 $form->{ERRORS} = [];
1609 foreach my $i (1 .. $form->{rowcount}) {
1610 next unless ($form->{"id_$i"} && $form->{"stock_out_$i"});
1612 my $row_sum_base_qty = 0;
1613 my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
1615 foreach my $request (@{ DO->unpack_stock_information('packed' => $form->{"stock_out_$i"}) }) {
1616 $request->{parts_id} = $form->{"id_$i"};
1617 $request->{base_qty} = $request->{qty} * $units->{$request->{unit}}->{factor} / $base_unit_factor;
1618 $request->{project_id} = $form->{"project_id_$i"} ? $form->{"project_id_$i"} : $form->{globalproject_id};
1620 my $map_key = join '--', ($form->{"id_$i"}, @{$request}{qw(warehouse_id bin_id chargenumber bestbefore)});
1622 $request_map{$map_key} ||= $request;
1623 $request_map{$map_key}->{sum_base_qty} ||= 0;
1624 $request_map{$map_key}->{sum_base_qty} += $request->{base_qty};
1625 $row_sum_base_qty += $request->{base_qty};
1627 push @all_requests, $request;
1630 next if (0 == $row_sum_base_qty);
1632 my $do_base_qty = $form->{"qty_$i"} * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
1634 # if ($do_base_qty != $row_sum_base_qty) {
1635 # push @{ $form->{ERRORS} }, $locale->text('Error in position #1: You must either assign no transfer at all or the full quantity of #2 #3.',
1636 # $i, $form->{"qty_$i"}, $form->{"unit_$i"});
1641 my @bin_ids = map { $_->{bin_id} } values %request_map;
1642 my %bin_info_map = WH->get_basic_bin_info('id' => \@bin_ids);
1643 my @contents = DO->get_item_availability('parts_id' => \@part_ids);
1645 foreach my $inv (@contents) {
1646 my $map_key = join '--', @{$inv}{qw(parts_id warehouse_id bin_id chargenumber bestbefore)};
1648 next unless ($request_map{$map_key});
1650 my $request = $request_map{$map_key};
1651 $request->{ok} = $request->{sum_base_qty} <= $inv->{qty};
1654 foreach my $request (values %request_map) {
1655 next if ($request->{ok});
1657 my $pinfo = $part_info_map{$request->{parts_id}};
1658 my $binfo = $bin_info_map{$request->{bin_id}};
1660 if ($::instance_conf->get_show_bestbefore) {
1661 push @{ $form->{ERRORS} }, $locale->text("There is not enough available of '#1' at warehouse '#2', bin '#3', #4, #5, for the transfer of #6.",
1662 $pinfo->{description},
1663 $binfo->{warehouse_description},
1664 $binfo->{bin_description},
1665 $request->{chargenumber} ? $locale->text('chargenumber #1', $request->{chargenumber}) : $locale->text('no chargenumber'),
1666 $request->{bestbefore} ? $locale->text('bestbefore #1', $request->{bestbefore}) : $locale->text('no bestbefore'),
1667 $form->format_amount_units('amount' => $request->{sum_base_qty},
1668 'part_unit' => $pinfo->{unit},
1669 'conv_units' => 'convertible_not_smaller'));
1671 push @{ $form->{ERRORS} }, $locale->text("There is not enough available of '#1' at warehouse '#2', bin '#3', #4, for the transfer of #5.",
1672 $pinfo->{description},
1673 $binfo->{warehouse_description},
1674 $binfo->{bin_description},
1675 $request->{chargenumber} ? $locale->text('chargenumber #1', $request->{chargenumber}) : $locale->text('no chargenumber'),
1676 $form->format_amount_units('amount' => $request->{sum_base_qty},
1677 'part_unit' => $pinfo->{unit},
1678 'conv_units' => 'convertible_not_smaller'));
1683 if (@{ $form->{ERRORS} }) {
1684 push @{ $form->{ERRORS} }, $locale->text('The delivery order has not been marked as delivered. The warehouse contents have not changed.');
1686 set_headings('edit');
1688 $main::lxdebug->leave_sub();
1690 $::dispatcher->end_request;
1693 DO->transfer_in_out('direction' => 'out',
1694 'requests' => \@all_requests);
1696 SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
1698 $form->{callback} = 'do.pl?action=edit&type=sales_delivery_order&id=' . $form->escape($form->{id});
1701 $main::lxdebug->leave_sub();
1705 $main::lxdebug->enter_sub();
1707 my $form = $main::form;
1709 DO->close_orders('ids' => [ $form->{id} ]);
1711 $form->{closed} = 1;
1715 $main::lxdebug->leave_sub();
1719 $::lxdebug->enter_sub;
1721 $::auth->assert('purchase_delivery_order_edit | sales_delivery_order_edit');
1724 retrieve_partunits();
1726 my $new_rowcount = $::form->{"rowcount"} * 1 + 1;
1727 $::form->{"project_id_${new_rowcount}"} = $::form->{"globalproject_id"};
1729 $::form->language_payment(\%::myconfig);
1731 Common::webdav_folder($::form);
1734 display_row(++$::form->{rowcount});
1737 $::lxdebug->leave_sub;
1741 call_sub($main::form->{yes_nextsub});
1745 call_sub($main::form->{no_nextsub});
1749 call_sub($main::form->{update_nextsub} || $main::form->{nextsub} || 'update_delivery_order');
1753 my $form = $main::form;
1754 my $locale = $main::locale;
1756 foreach my $action (qw(update print save transfer_out transfer_out_default sort
1757 transfer_in transfer_in_default mark_closed save_as_new invoice delete)) {
1758 if ($form->{"action_${action}"}) {
1764 $form->error($locale->text('No action defined.'));
1767 sub transfer_out_default {
1768 $main::lxdebug->enter_sub();
1770 my $form = $main::form;
1772 transfer_in_out_default('direction' => 'out');
1774 $main::lxdebug->leave_sub();
1777 sub transfer_in_default {
1778 $main::lxdebug->enter_sub();
1780 my $form = $main::form;
1782 transfer_in_out_default('direction' => 'in');
1784 $main::lxdebug->leave_sub();
1787 # Falls das Standardlagerverfahren aktiv ist, wird
1788 # geprüft, ob alle Standardlagerplätze für die Auslager-
1789 # artikel vorhanden sind UND ob die Warenmenge ausreicht zum
1790 # Auslagern. Falls nicht wird entsprechend eine Fehlermeldung
1791 # generiert. Offen Chargennummer / bestbefore wird nicht berücksichtigt
1792 sub transfer_in_out_default {
1793 $main::lxdebug->enter_sub();
1795 my $form = $main::form;
1796 my %myconfig = %main::myconfig;
1797 my $locale = $main::locale;
1800 my (%missing_default_bins, %qty_parts, @all_requests, %part_info_map, $default_warehouse_id, $default_bin_id);
1802 Common::check_params(\%params, qw(direction));
1804 # entsprechende defaults holen, falls standardlagerplatz verwendet werden soll
1805 if ($::instance_conf->get_transfer_default_use_master_default_bin) {
1806 $default_warehouse_id = $::instance_conf->get_warehouse_id;
1807 $default_bin_id = $::instance_conf->get_bin_id;
1811 my @part_ids = map { $form->{"id_${_}"} } (1 .. $form->{rowcount});
1813 my $units = AM->retrieve_units(\%myconfig, $form);
1814 %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
1815 foreach my $i (1 .. $form->{rowcount}) {
1816 next unless ($form->{"id_$i"});
1817 my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
1818 my $qty = $form->parse_amount(\%myconfig, $form->{"qty_$i"}) * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
1820 $form->show_generic_error($locale->text("Cannot transfer negative entries." )) if ($qty < 0);
1821 # if we do not want to transfer services and this part is a service, set qty to zero
1822 # ... and do not create a hash entry in %qty_parts below (will skip check for bins for the transfer == out case)
1823 # ... 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)
1825 $qty = 0 if (!$::instance_conf->get_transfer_default_services && $part_info_map{$form->{"id_$i"}}->{part_type} eq 'service');
1826 $qty_parts{$form->{"id_$i"}} += $qty;
1828 delete $qty_parts{$form->{"id_$i"}} unless $qty_parts{$form->{"id_$i"}};
1829 undef $form->{"stock_in_$i"};
1832 $part_info_map{$form->{"id_$i"}}{bin_id} ||= $default_bin_id;
1833 $part_info_map{$form->{"id_$i"}}{warehouse_id} ||= $default_warehouse_id;
1835 push @all_requests, ($qty == 0) ? { } : {
1836 'chargenumber' => '', #?? die müsste entsprechend geholt werden
1837 #'bestbefore' => undef, # TODO wird nicht berücksichtigt
1838 'bin_id' => $part_info_map{$form->{"id_$i"}}{bin_id},
1840 'parts_id' => $form->{"id_$i"},
1841 'comment' => $locale->text("Default transfer delivery order"),
1842 'unit' => $part_info_map{$form->{"id_$i"}}{unit},
1843 'warehouse_id' => $part_info_map{$form->{"id_$i"}}{warehouse_id},
1844 'oe_id' => $form->{id},
1845 'project_id' => $form->{"project_id_$i"} ? $form->{"project_id_$i"} : $form->{globalproject_id}
1849 # jetzt wird erst überprüft, ob die Stückzahl entsprechend stimmt.
1850 # check if bin (transfer in and transfer out and qty (transfer out) is correct
1851 foreach my $key (keys %qty_parts) {
1853 $missing_default_bins{$key}{missing_bin} = 1 unless ($part_info_map{$key}{bin_id});
1854 next unless ($part_info_map{$key}{bin_id}); # abbruch
1856 if ($params{direction} eq 'out') { # wird nur für ausgehende Mengen benötigt
1857 my ($max_qty, $error) = WH->get_max_qty_parts_bin(parts_id => $key, bin_id => $part_info_map{$key}{bin_id});
1859 # wir können nicht entscheiden, welche charge oder mhd (bestbefore) ausgewählt sein soll
1860 # deshalb rückmeldung nach oben geben, manuell auszulagern
1861 # TODO Bei nur einem Treffer mit Charge oder bestbefore wäre das noch möglich
1862 $missing_default_bins{$key}{chargenumber} = 1;
1864 if ($max_qty < $qty_parts{$key}){
1865 $missing_default_bins{$key}{missing_qty} = $max_qty - $qty_parts{$key};
1871 # Abfrage für Fehlerbehandlung (nur bei direction == out)
1872 if (scalar (keys %missing_default_bins)) {
1874 foreach my $fehler (keys %missing_default_bins) {
1876 my $ware = WH->get_part_description(parts_id => $fehler);
1877 if ($missing_default_bins{$fehler}{missing_bin}){
1878 $fehlertext .= "Kein Standardlagerplatz definiert bei $ware <br>";
1880 if ($missing_default_bins{$fehler}{missing_qty}) { # missing_qty
1881 $fehlertext .= "Es fehlen " . $missing_default_bins{$fehler}{missing_qty}*-1 .
1882 " von $ware auf dem Standard-Lagerplatz " . $part_info_map{$fehler}{bin} . " zum Auslagern<br>";
1884 if ($missing_default_bins{$fehler}{chargenumber}){
1885 $fehlertext .= "Die Ware hat eine Chargennummer oder eine Mindesthaltbarkeit definiert.
1886 Hier kann man nicht automatisch entscheiden.
1887 Bitte diesen Lieferschein manuell auslagern.
1890 # auslagern soll immer gehen, auch wenn nicht genügend auf lager ist.
1891 # der lagerplatz ist hier extra konfigurierbar, bspw. Lager-Korrektur mit
1892 # Lagerplatz Lagerplatz-Korrektur
1893 my $default_warehouse_id_ignore_onhand = $::instance_conf->get_warehouse_id_ignore_onhand;
1894 my $default_bin_id_ignore_onhand = $::instance_conf->get_bin_id_ignore_onhand;
1895 if ($::instance_conf->get_transfer_default_ignore_onhand && $default_bin_id_ignore_onhand) {
1896 # entsprechende defaults holen
1897 # falls chargenumber, bestbefore oder anzahl nicht stimmt, auf automatischen
1898 # lagerplatz wegbuchen!
1899 foreach (@all_requests) {
1900 if ($_->{parts_id} eq $fehler){
1901 $_->{bin_id} = $default_bin_id_ignore_onhand;
1902 $_->{warehouse_id} = $default_warehouse_id_ignore_onhand;
1906 #$main::lxdebug->message(0, 'Fehlertext: ' . $fehlertext);
1907 $form->show_generic_error($locale->text("Cannot transfer. <br> Reason:<br>#1", $fehlertext ));
1913 # hier der eigentliche fallunterschied für in oder out
1914 my $prefix = $params{direction} eq 'in' ? 'in' : 'out';
1916 # dieser array_ref ist für DO->save da:
1917 # einmal die all_requests in YAML verwandeln, damit delivery_order_items_stock
1918 # gefüllt werden kann.
1919 # could be dumped to the form in the first loop,
1920 # but maybe bin_id and warehouse_id has changed to the "korrekturlager" with
1921 # allowed negative qty ($::instance_conf->get_warehouse_id_ignore_onhand) ...
1923 foreach (@all_requests){
1925 next unless scalar(%{ $_ });
1926 $form->{"stock_${prefix}_$i"} = SL::YAML::Dump([$_]);
1929 save(no_redirect => 1); # Wir können auslagern, deshalb beleg speichern
1930 # und in delivery_order_items_stock speichern
1932 # ... and fill back the persistent dois_id for inventory fk
1933 undef (@all_requests);
1934 foreach my $i (1 .. $form->{rowcount}) {
1935 next unless ($form->{"id_$i"} && $form->{"stock_${prefix}_$i"});
1936 push @all_requests, @{ DO->unpack_stock_information('packed' => $form->{"stock_${prefix}_$i"}) };
1938 DO->transfer_in_out('direction' => $prefix,
1939 'requests' => \@all_requests);
1941 SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
1943 $form->{callback} = 'do.pl?action=edit&type=sales_delivery_order&id=' . $form->escape($form->{id}) if $params{direction} eq 'out';
1944 $form->{callback} = 'do.pl?action=edit&type=purchase_delivery_order&id=' . $form->escape($form->{id}) if $params{direction} eq 'in';
1950 $main::lxdebug->enter_sub();
1954 my $form = $main::form;
1957 save(no_redirect => 1); # has to be done, at least for newly added positions
1959 # hashify partnumbers, positions. key is delivery_order_items_id
1960 for my $i (1 .. ($form->{rowcount}) ) {
1961 $temp_hash{$form->{"delivery_order_items_id_$i"}} = { runningnumber => $form->{"runningnumber_$i"}, partnumber => $form->{"partnumber_$i"} };
1962 if ($form->{id} && $form->{"discount_$i"}) {
1963 # prepare_order assumes a db value if there is a form->id and multiplies *100
1964 # We hope for new controller code (no more format_amount/parse_amount distinction)
1965 $form->{"discount_$i"} /=100;
1968 # naturally sort partnumbers and get a sorted array of doi_ids
1969 my @sorted_doi_ids = sort { Sort::Naturally::ncmp($temp_hash{$a}->{"partnumber"}, $temp_hash{$b}->{"partnumber"}) } keys %temp_hash;
1974 for (@sorted_doi_ids) {
1975 $form->{"runningnumber_$temp_hash{$_}->{runningnumber}"} = $new_number;
1978 # all parse_amounts changes are in form (i.e. , to .) therefore we need
1979 # another format_amount to change it back, for the next save ;-(
1980 # works great except for row discounts (see above comment)
1984 $main::lxdebug->leave_sub();
1996 do.pl - Script for all calls to delivery order
2004 Sorts all position with Natural Sort. Can be activated in form_footer.html like this
2005 C<E<lt>input class="submit" type="submit" name="action_sort" id="sort_button" value="[% 'Sort and Save' | $T8 %]"E<gt>>
2011 Sort and Save can be implemented as an optional button if configuration ca be set by client config.
2012 Example coding for database scripts and templates in (git show af2f24b8), check also
2013 autogeneration for rose (scripts/rose_auto_create_model.pl --h)