1 #=====================================================================
4 # Based on SQL-Ledger Version 2.1.9
5 # Web http://www.lx-office.org
7 #=====================================================================
8 # SQL-Ledger, Accounting
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 #======================================================================
31 # Inventory Control module
33 #======================================================================
35 use POSIX qw(strftime);
36 use List::Util qw(first max);
37 use List::MoreUtils qw(any);
42 use SL::Helper::Flash qw(flash);
44 use SL::ReportGenerator;
52 our ($form, $locale, %myconfig, $lxdebug, $auth);
54 require "bin/mozilla/io.pl";
55 require "bin/mozilla/common.pl";
56 require "bin/mozilla/reportgenerator.pl";
61 # type=submit $locale->text('Add Part')
62 # type=submit $locale->text('Add Service')
63 # type=submit $locale->text('Add Assembly')
64 # type=submit $locale->text('Edit Part')
65 # type=submit $locale->text('Edit Service')
66 # type=submit $locale->text('Edit Assembly')
67 # $locale->text('Parts')
68 # $locale->text('Services')
69 # $locale->text('Inventory quantity must be zero before you can set this part obsolete!')
70 # $locale->text('Inventory quantity must be zero before you can set this assembly obsolete!')
71 # $locale->text('Part Number missing!')
72 # $locale->text('Service Number missing!')
73 # $locale->text('Assembly Number missing!')
74 # $locale->text('ea');
79 $lxdebug->enter_sub();
81 $auth->assert('part_service_assembly_details');
83 $form->{revers} = 0; # switch for backward sorting
84 $form->{lastsort} = ""; # memory for which table was sort at last time
85 $form->{ndxs_counter} = 0; # counter for added entries to top100
87 $form->{title} = (ucfirst $form->{searchitems}) . "s";
88 $form->{title} =~ s/ys$/ies/;
89 $form->{title} = $locale->text($form->{title});
91 $form->{CUSTOM_VARIABLES} = CVar->get_configs('module' => 'IC');
92 ($form->{CUSTOM_VARIABLES_FILTER_CODE},
93 $form->{CUSTOM_VARIABLES_INCLUSION_CODE}) = CVar->render_search_options('variables' => $form->{CUSTOM_VARIABLES},
94 'include_prefix' => 'l_',
95 'include_value' => 'Y');
97 setup_ic_search_action_bar();
100 $form->get_lists('partsgroup' => 'ALL_PARTSGROUPS');
101 print $form->parse_html_template('ic/search');
103 $lxdebug->leave_sub();
107 $::lxdebug->enter_sub();
109 $::auth->assert('part_service_assembly_edit');
111 $::form->{l_soldtotal} = "Y";
112 $::form->{sort} = "soldtotal";
113 $::form->{lastsort} = "soldtotal";
115 $::form->{l_qty} = undef;
116 $::form->{l_linetotal} = undef;
117 $::form->{l_number} = "Y";
118 $::form->{number} = "position";
120 unless ( $::form->{bought}
123 || $::form->{quoted}) {
124 $::form->{bought} = $::form->{sold} = 1;
129 $lxdebug->leave_sub();
134 # Warning, deep magic ahead.
135 # This function parses the requested details, sanity checks them, and converts them into a format thats usable for IC->all_parts
137 # flags coming from the form:
139 # searchitems=part revers=0 lastsort=''
142 # partnumber ean description partsgroup classification serialnumber make model drawing microfiche
143 # transdatefrom transdateto
146 # itemstatus = active | onhand | short | obsolete | orphaned
147 # action = continue | top100
150 # bought sold onorder ordered rfq quoted
151 # l_partnumber l_description l_serialnumber l_unit l_listprice l_sellprice l_lastcost
152 # l_linetotal l_priceupdate l_bin l_rop l_weight l_image l_drawing l_microfiche
153 # l_partsgroup l_subtotal l_soldtotal l_deliverydate l_pricegroups
156 # nextsub revers lastsort sort ndxs_counter
158 sub generate_report {
159 $lxdebug->enter_sub();
161 $auth->assert('part_service_assembly_details');
163 my ($revers, $lastsort, $description);
165 my $cvar_configs = CVar->get_configs('module' => 'IC');
167 $form->{title} = $locale->text('Articles');
170 'deliverydate' => { 'text' => $locale->text('deliverydate'), },
171 'description' => { 'text' => $locale->text('Part Description'), },
172 'notes' => { 'text' => $locale->text('Notes'), },
173 'drawing' => { 'text' => $locale->text('Drawing'), },
174 'ean' => { 'text' => $locale->text('EAN'), },
175 'image' => { 'text' => $locale->text('Image'), },
176 'insertdate' => { 'text' => $locale->text('Insert Date'), },
177 'invnumber' => { 'text' => $locale->text('Invoice Number'), },
178 'lastcost' => { 'text' => $locale->text('Last Cost'), },
179 'linetotallastcost' => { 'text' => $locale->text('Extended'), },
180 'linetotallistprice' => { 'text' => $locale->text('Extended'), },
181 'linetotalsellprice' => { 'text' => $locale->text('Extended'), },
182 'listprice' => { 'text' => $locale->text('List Price'), },
183 'microfiche' => { 'text' => $locale->text('Microfiche'), },
184 'name' => { 'text' => $locale->text('Name'), },
185 'onhand' => { 'text' => $locale->text('Stocked Qty'), },
186 'ordnumber' => { 'text' => $locale->text('Order Number'), },
187 'partnumber' => { 'text' => $locale->text('Part Number'), },
188 'partsgroup' => { 'text' => $locale->text('Partsgroup'), },
189 'priceupdate' => { 'text' => $locale->text('Updated'), },
190 'quonumber' => { 'text' => $locale->text('Quotation'), },
191 'rop' => { 'text' => $locale->text('ROP'), },
192 'sellprice' => { 'text' => $locale->text('Sell Price'), },
193 'serialnumber' => { 'text' => $locale->text('Serial Number'), },
194 'soldtotal' => { 'text' => $locale->text('Qty in Selected Records'), },
195 'name' => { 'text' => $locale->text('Name in Selected Records'), },
196 'transdate' => { 'text' => $locale->text('Transdate Record'), },
197 'unit' => { 'text' => $locale->text('Unit'), },
198 'weight' => { 'text' => $locale->text('Weight'), },
199 'shop' => { 'text' => $locale->text('Shop article'), },
200 'type_and_classific' => { 'text' => $locale->text('Type'), },
201 'projectnumber' => { 'text' => $locale->text('Project Number'), },
202 'projectdescription' => { 'text' => $locale->text('Project Description'), },
203 'warehouse' => { 'text' => $locale->text('Default Warehouse'), },
204 'bin' => { 'text' => $locale->text('Default Bin'), },
207 $revers = $form->{revers};
208 $lastsort = $form->{lastsort};
210 # sorting and direction of sorting
211 # ToDO: change this to the simpler field+direction method
212 if (($form->{lastsort} eq "") && ($form->{sort} eq undef)) {
214 $form->{lastsort} = "partnumber";
215 $form->{sort} = "partnumber";
217 if ($form->{lastsort} eq $form->{sort}) {
218 $form->{revers} = 1 - $form->{revers};
221 $form->{lastsort} = $form->{sort};
225 # special case if we have a serialnumber limit search
226 # serialnumbers are only given in invoices and orders,
227 # so they can only pop up in bought, sold, rfq, and quoted stuff
228 $form->{no_sn_joins} = 'Y' if ( !$form->{bought} && !$form->{sold}
229 && !$form->{rfq} && !$form->{quoted}
230 && ($form->{l_serialnumber} || $form->{serialnumber}));
232 # special case for any checkbox of bought | sold | onorder | ordered | rfq | quoted.
233 # if any of these are ticked the behavior changes slightly for lastcost
234 # since all those are aggregation checks for the legder tables this is an internal switch
235 # refered to as ledgerchecks
236 $form->{ledgerchecks} = 'Y' if ( $form->{bought} || $form->{sold} || $form->{onorder}
237 || $form->{ordered} || $form->{rfq} || $form->{quoted});
239 # if something should be activated if something else is active, enter it here
241 onhand => [ qw(l_onhand) ],
242 short => [ qw(l_onhand) ],
243 onorder => [ qw(l_ordnumber) ],
244 ordered => [ qw(l_ordnumber) ],
245 rfq => [ qw(l_quonumber) ],
246 quoted => [ qw(l_quonumber) ],
247 bought => [ qw(l_invnumber) ],
248 sold => [ qw(l_invnumber) ],
249 ledgerchecks => [ qw(l_name) ],
250 serialnumber => [ qw(l_serialnumber) ],
251 no_sn_joins => [ qw(bought sold) ],
254 # get name of partsgroup if id is given
256 if ($form->{partsgroup_id}) {
257 my $pg = SL::DB::PartsGroup->new(id => $form->{partsgroup_id})->load;
258 $pg_name = $pg->{'partsgroup'};
261 # these strings get displayed at the top of the results to indicate the user which switches were used
263 active => $locale->text('Active'),
264 obsolete => $locale->text('Obsolete'),
265 orphaned => $locale->text('Orphaned'),
266 onhand => $locale->text('On Hand'),
267 short => $locale->text('Short'),
268 onorder => $locale->text('On Order'),
269 ordered => $locale->text('Ordered'),
270 rfq => $locale->text('RFQ'),
271 quoted => $locale->text('Quoted'),
272 bought => $locale->text('Bought'),
273 sold => $locale->text('Sold'),
274 transdatefrom => $locale->text('From') . " " . $locale->date(\%myconfig, $form->{transdatefrom}, 1),
275 transdateto => $locale->text('To (time)') . " " . $locale->date(\%myconfig, $form->{transdateto}, 1),
276 partnumber => $locale->text('Part Number') . ": '$form->{partnumber}'",
277 partsgroup => $locale->text('Partsgroup') . ": '$form->{partsgroup}'",
278 partsgroup_id => $locale->text('Partsgroup') . ": '$pg_name'",
279 serialnumber => $locale->text('Serial Number') . ": '$form->{serialnumber}'",
280 description => $locale->text('Part Description') . ": '$form->{description}'",
281 make => $locale->text('Make') . ": '$form->{make}'",
282 model => $locale->text('Model') . ": '$form->{model}'",
283 drawing => $locale->text('Drawing') . ": '$form->{drawing}'",
284 microfiche => $locale->text('Microfiche') . ": '$form->{microfiche}'",
285 l_soldtotal => $locale->text('Qty in Selected Records'),
286 ean => $locale->text('EAN') . ": '$form->{ean}'",
287 insertdatefrom => $locale->text('Insert Date') . ": " . $locale->text('From') . " " . $locale->date(\%myconfig, $form->{insertdatefrom}, 1),
288 insertdateto => $locale->text('Insert Date') . ": " . $locale->text('To (time)') . " " . $locale->date(\%myconfig, $form->{insertdateto}, 1),
291 my @itemstatus_keys = qw(active obsolete orphaned onhand short);
292 my @callback_keys = qw(onorder ordered rfq quoted bought sold partnumber partsgroup partsgroup_id serialnumber description make model
293 drawing microfiche l_soldtotal l_deliverydate transdatefrom transdateto insertdatefrom insertdateto ean shop all);
295 # calculate dependencies
296 for (@itemstatus_keys, @callback_keys) {
297 next if ($form->{itemstatus} ne $_ && !$form->{$_});
298 map { $form->{$_} = 'Y' } @{ $dependencies{$_} } if $dependencies{$_};
301 # generate callback and optionstrings
303 for my $key (@itemstatus_keys, @callback_keys) {
304 next if ($form->{itemstatus} ne $key && !$form->{$key});
305 push @options, $optiontexts{$key};
308 # special case for lastcost
309 if ($form->{ledgerchecks}){
310 # ledgerchecks don't know about sellprice or lastcost. they just return a
311 # price. so rename sellprice to price, and drop lastcost.
312 $column_defs{sellprice}{text} = $locale->text('Price');
313 $form->{l_lastcost} = ""
316 if ($form->{description}) {
317 $description = $form->{description};
318 $description =~ s/\n/<br>/g;
321 if ($form->{l_linetotal}) {
322 $form->{l_qty} = "Y";
323 $form->{l_linetotalsellprice} = "Y" if $form->{l_sellprice};
324 $form->{l_linetotallastcost} = $form->{searchitems} eq 'assembly' && !$form->{bom} ? "" : 'Y' if $form->{l_lastcost};
325 $form->{l_linetotallistprice} = "Y" if $form->{l_listprice};
327 $form->{"l_type_and_classific"} = "Y";
329 if ($form->{l_service} && !$form->{l_assembly} && !$form->{l_part}) {
331 # remove warehouse, bin, weight and rop from list
332 map { $form->{"l_$_"} = "" } qw(bin weight rop warehouse);
334 $form->{l_onhand} = "";
336 # qty is irrelevant unless bought or sold
342 || $form->{quoted}) {
343 # $form->{l_onhand} = "Y";
345 $form->{l_linetotalsellprice} = "";
346 $form->{l_linetotallastcost} = "";
350 # soldtotal doesn't make sense with more than one bsooqr option.
351 # so reset it to sold (the most common option), and issue a warning
353 # also it doesn't make sense without bsooqr. disable and issue a warning too
354 my @bsooqr = qw(sold bought onorder ordered rfq quoted);
355 my $bsooqr_mode = grep { $form->{$_} } @bsooqr;
356 if ($form->{l_subtotal} && 1 < $bsooqr_mode) {
357 my $enabled = first { $form->{$_} } @bsooqr;
358 $form->{$_} = '' for @bsooqr;
359 $form->{$enabled} = 'Y';
361 push @options, $::locale->text('Subtotal cannot distinguish betweens record types. Only one of the selected record types will be displayed: #1', $optiontexts{$enabled});
363 if ($form->{l_soldtotal} && !$bsooqr_mode) {
364 delete $form->{l_soldtotal};
366 flash('warning', $::locale->text('Soldtotal does not make sense without any bsooqr options'));
368 if ($form->{l_soldtotal} && ($form->{l_warehouse} || $form->{l_bin})) {
369 delete $form->{"l_$_"} for qw(bin warehouse);
370 flash('warning', $::locale->text('Sorry, I am too stupid to figure out the default warehouse/bin and the sold qty. I drop the default warehouse/bin option.'));
372 if ($form->{l_name} && !$bsooqr_mode) {
373 delete $form->{l_name};
375 flash('warning', $::locale->text('Name does not make sense without any bsooqr options'));
377 IC->all_parts(\%myconfig, \%$form);
380 partnumber type_and_classific description notes partsgroup warehouse bin
381 onhand rop soldtotal unit listprice
382 linetotallistprice sellprice linetotalsellprice lastcost linetotallastcost
383 priceupdate weight image drawing microfiche invnumber ordnumber quonumber
384 transdate name serialnumber deliverydate ean projectnumber projectdescription
388 my $pricegroups = SL::DB::Manager::Pricegroup->get_all_sorted;
389 my @pricegroup_columns;
390 my %column_defs_pricegroups;
391 if ($form->{l_pricegroups}) {
392 @pricegroup_columns = map { "pricegroup_" . $_->id } @{ $pricegroups };
393 %column_defs_pricegroups = map {
394 "pricegroup_" . $_->id => {
395 text => $::locale->text('Pricegroup') . ' ' . $_->pricegroup,
400 push @columns, @pricegroup_columns;
402 my @includeable_custom_variables = grep { $_->{includeable} } @{ $cvar_configs };
403 my @searchable_custom_variables = grep { $_->{searchable} } @{ $cvar_configs };
404 my %column_defs_cvars = map { +"cvar_$_->{name}" => { 'text' => $_->{description} } } @includeable_custom_variables;
406 push @columns, map { "cvar_$_->{name}" } @includeable_custom_variables;
408 %column_defs = (%column_defs, %column_defs_cvars, %column_defs_pricegroups);
409 map { $column_defs{$_}->{visible} ||= $form->{"l_$_"} ? 1 : 0 } @columns;
410 map { $column_defs{$_}->{align} = 'right' } qw(onhand sellprice listprice lastcost linetotalsellprice linetotallastcost linetotallistprice rop weight soldtotal shop), @pricegroup_columns;
412 my @hidden_variables = (
413 qw(l_subtotal l_linetotal searchitems itemstatus bom l_pricegroups insertdatefrom insertdateto),
414 qw(l_type_and_classific classification_id l_part l_service l_assembly l_assortment),
417 map({ "cvar_$_->{name}" } @searchable_custom_variables),
418 map({'cvar_'. $_->{name} .'_qtyop'} grep({$_->{type} eq 'number'} @searchable_custom_variables)),
419 map({ "l_$_" } @columns),
422 my $callback = build_std_url('action=generate_report', grep { $form->{$_} } @hidden_variables);
424 my @sort_full = qw(partnumber description onhand soldtotal deliverydate insertdate shop);
425 my @sort_no_revers = qw(partsgroup priceupdate invnumber ordnumber quonumber name image drawing serialnumber);
427 foreach my $col (@sort_full) {
428 $column_defs{$col}->{link} = join '&', $callback, "sort=$col", map { "$_=" . E($form->{$_}) } qw(revers lastsort);
430 map { $column_defs{$_}->{link} = "${callback}&sort=$_" } @sort_no_revers;
432 # add order to callback
433 $form->{callback} = join '&', ($callback, map { "${_}=" . E($form->{$_}) } qw(sort revers));
435 my $report = SL::ReportGenerator->new(\%myconfig, $form);
437 my %attachment_basenames = (
438 'part' => $locale->text('part_list'),
439 'service' => $locale->text('service_list'),
440 'assembly' => $locale->text('assembly_list'),
441 'article' => $locale->text('article_list'),
444 $report->set_options('raw_top_info_text' => $form->parse_html_template('ic/generate_report_top', { options => \@options }),
445 'raw_bottom_info_text' => $form->parse_html_template('ic/generate_report_bottom' ,
446 { PART_CLASSIFICATIONS => SL::DB::Manager::PartClassification->get_all_sorted }),
447 'output_format' => 'HTML',
448 'title' => $form->{title},
449 'attachment_basename' => 'article_list' . strftime('_%Y%m%d', localtime time),
451 $report->set_options_from_form();
452 $locale->set_numberformat_wo_thousands_separator(\%myconfig) if lc($report->{options}->{output_format}) eq 'csv';
454 $report->set_columns(%column_defs);
455 $report->set_column_order(@columns);
457 $report->set_export_options('generate_report', @hidden_variables, qw(sort revers));
459 $report->set_sort_indicator($form->{sort}, $form->{revers} ? 0 : 1);
461 CVar->add_custom_variables_to_report('module' => 'IC',
462 'trans_id_field' => 'id',
463 'configs' => $cvar_configs,
464 'column_defs' => \%column_defs,
465 'data' => $form->{parts});
467 CVar->add_custom_variables_to_report('module' => 'IC',
468 'sub_module' => sub { $_[0]->{ioi} },
469 'trans_id_field' => 'ioi_id',
470 'configs' => $cvar_configs,
471 'column_defs' => \%column_defs,
472 'data' => $form->{parts});
474 my @subtotal_columns = qw(sellprice listprice lastcost);
475 my %subtotals = map { $_ => 0 } ('onhand', @subtotal_columns);
476 my %totals = map { $_ => 0 } @subtotal_columns;
478 my $same_item = @{ $form->{parts} } ? $form->{parts}[0]{ $form->{sort} } : undef;
480 my $defaults = AM->get_defaults();
483 foreach my $ref (@{ $form->{parts} }) {
485 # fresh row, for inserting later
486 my $row = { map { $_ => { 'data' => $ref->{$_} } } @columns };
488 $ref->{exchangerate} ||= 1;
489 $ref->{price_factor} ||= 1;
490 $ref->{sellprice} *= $ref->{exchangerate} / $ref->{price_factor};
491 $ref->{listprice} *= $ref->{exchangerate} / $ref->{price_factor};
492 $ref->{lastcost} *= $ref->{exchangerate} / $ref->{price_factor};
494 # use this for assemblies
495 my $soldtotal = $bsooqr_mode ? $ref->{soldtotal} : $ref->{onhand};
497 if ($ref->{assemblyitem}) {
498 $row->{partnumber}{align} = 'right';
499 $row->{soldtotal}{data} = 0;
500 $soldtotal = 0 if ($form->{sold});
503 my $edit_link = build_std_url('script=controller.pl', 'action=Part/edit', 'part.id=' . E($ref->{id}));
504 $row->{partnumber}->{link} = $edit_link;
505 $row->{description}->{link} = $edit_link;
507 foreach (qw(sellprice listprice lastcost)) {
508 $row->{$_}{data} = $form->format_amount(\%myconfig, $ref->{$_}, 2);
509 $row->{"linetotal$_"}{data} = $form->format_amount(\%myconfig, $ref->{onhand} * $ref->{$_}, 2);
511 foreach ( @pricegroup_columns ) {
512 $row->{$_}{data} = $form->format_amount(\%myconfig, $ref->{"$_"}, 2);
516 map { $row->{$_}{data} = $form->format_amount(\%myconfig, $ref->{$_}); } qw(onhand rop weight soldtotal);
518 $row->{weight}->{data} .= ' ' . $defaults->{weightunit};
520 # 'yes' and 'no' for boolean value shop
521 if ($form->{l_shop}) {
522 $row->{shop}{data} = $row->{shop}{data}? $::locale->text('yes') : $::locale->text('no');
525 if (!$ref->{assemblyitem}) {
526 foreach my $col (@subtotal_columns) {
527 $totals{$col} += $soldtotal * $ref->{$col};
528 $subtotals{$col} += $soldtotal * $ref->{$col};
531 $subtotals{soldtotal} += $soldtotal;
535 if ($ref->{module} eq 'oe') {
536 # für oe gibt es vier fälle, jeweils nach kunde oder lieferant unterschiedlich:
538 # | ist bestellt | Von Kunden bestellt | -> edit_oe_ord_link
539 # | Anfrage | Angebot | -> edit_oe_quo_link
541 my $edit_oe_ord_link = build_std_url("script=oe.pl", 'action=edit', 'type=' . E($ref->{cv} eq 'vendor' ? 'purchase_order' : 'sales_order'), 'id=' . E($ref->{trans_id}), 'callback');
542 my $edit_oe_quo_link = build_std_url("script=oe.pl", 'action=edit', 'type=' . E($ref->{cv} eq 'vendor' ? 'request_quotation' : 'sales_quotation'), 'id=' . E($ref->{trans_id}), 'callback');
544 $row->{ordnumber}{link} = $edit_oe_ord_link;
545 $row->{quonumber}{link} = $edit_oe_quo_link if (!$ref->{ordnumber});
548 $row->{invnumber}{link} = build_std_url("script=$ref->{module}.pl", 'action=edit', 'type=invoice', 'id=' . E($ref->{trans_id}), 'callback') if ($ref->{invnumber});
551 # set properties of images
552 if ($ref->{image} && (lc $report->{options}->{output_format} eq 'html')) {
553 $row->{image}{data} = '';
554 $row->{image}{raw_data} = '<a href="' . H($ref->{image}) . '"><img src="' . H($ref->{image}) . '" height="32" border="0"></a>';
556 map { $row->{$_}{link} = $ref->{$_} } qw(drawing microfiche);
558 $row->{notes}{data} = SL::HTML::Util->strip($ref->{notes});
559 $row->{type_and_classific}{data} = $::request->presenter->type_abbreviation($ref->{part_type}).
560 $::request->presenter->classification_abbreviation($ref->{classification_id});
562 $report->add_data($row);
564 my $next_ref = $form->{parts}[$idx + 1];
566 # insert subtotal rows
567 if (($form->{l_subtotal} eq 'Y') &&
569 (!$next_ref->{assemblyitem} && ($same_item ne $next_ref->{ $form->{sort} })))) {
570 my $row = { map { $_ => { 'class' => 'listsubtotal', } } @columns };
572 if ( !$form->{l_assembly} || !$form->{bom}) {
573 $row->{soldtotal}->{data} = $form->format_amount(\%myconfig, $subtotals{soldtotal});
576 map { $row->{"linetotal$_"}->{data} = $form->format_amount(\%myconfig, $subtotals{$_}, 2) } @subtotal_columns;
577 map { $subtotals{$_} = 0 } ('soldtotal', @subtotal_columns);
579 $report->add_data($row);
581 $same_item = $next_ref->{ $form->{sort} };
587 if ($form->{"l_linetotal"} && !$form->{report_generator_csv_options_for_import}) {
588 my $row = { map { $_ => { 'class' => 'listtotal', } } @columns };
590 map { $row->{"linetotal$_"}->{data} = $form->format_amount(\%myconfig, $totals{$_}, 2) } @subtotal_columns;
592 $report->add_separator();
593 $report->add_data($row);
596 setup_ic_generate_report_action_bar();
597 $report->generate_with_headers();
599 $lxdebug->leave_sub();
600 } #end generate_report
602 sub setup_ic_search_action_bar {
605 for my $bar ($::request->layout->get('actionbar')) {
609 submit => [ '#form', { action => 'generate_report' } ],
610 accesskey => 'enter',
615 submit => [ '#form', { action => 'top100' } ],
621 sub setup_ic_generate_report_action_bar {
624 for my $bar ($::request->layout->get('actionbar')) {
632 submit => [ '#new_form', { action => 'Part/add_part' } ],
633 accesskey => 'enter',
637 submit => [ '#new_form', { action => 'Part/add_service' } ],
641 submit => [ '#new_form', { action => 'Part/add_assembly' } ],
644 t8('Add Assortment'),
645 submit => [ '#new_form', { action => 'Part/add_assortment' } ],
647 ], # end of combobox "Add part"