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::Presenter::Part;
45 use SL::ReportGenerator;
53 our ($form, $locale, %myconfig, $lxdebug, $auth);
55 require "bin/mozilla/io.pl";
56 require "bin/mozilla/common.pl";
57 require "bin/mozilla/reportgenerator.pl";
62 # type=submit $locale->text('Add Part')
63 # type=submit $locale->text('Add Service')
64 # type=submit $locale->text('Add Assembly')
65 # type=submit $locale->text('Edit Part')
66 # type=submit $locale->text('Edit Service')
67 # type=submit $locale->text('Edit Assembly')
68 # $locale->text('Parts')
69 # $locale->text('Services')
70 # $locale->text('Inventory quantity must be zero before you can set this part obsolete!')
71 # $locale->text('Inventory quantity must be zero before you can set this assembly obsolete!')
72 # $locale->text('Part Number missing!')
73 # $locale->text('Service Number missing!')
74 # $locale->text('Assembly Number missing!')
75 # $locale->text('ea');
80 $lxdebug->enter_sub();
82 $auth->assert('part_service_assembly_details');
84 $form->{revers} = 0; # switch for backward sorting
85 $form->{lastsort} = ""; # memory for which table was sort at last time
86 $form->{ndxs_counter} = 0; # counter for added entries to top100
88 $form->{title} = (ucfirst $form->{searchitems}) . "s";
89 $form->{title} =~ s/ys$/ies/;
90 $form->{title} = $locale->text($form->{title});
92 $form->{CUSTOM_VARIABLES} = CVar->get_configs('module' => 'IC');
93 ($form->{CUSTOM_VARIABLES_FILTER_CODE},
94 $form->{CUSTOM_VARIABLES_INCLUSION_CODE}) = CVar->render_search_options('variables' => $form->{CUSTOM_VARIABLES},
95 'include_prefix' => 'l_',
96 'include_value' => 'Y');
98 setup_ic_search_action_bar();
101 $form->get_lists('partsgroup' => 'ALL_PARTSGROUPS');
102 print $form->parse_html_template('ic/search');
104 $lxdebug->leave_sub();
108 $::lxdebug->enter_sub();
110 $::auth->assert('part_service_assembly_edit');
112 $::form->{l_soldtotal} = "Y";
113 $::form->{sort} = "soldtotal";
114 $::form->{lastsort} = "soldtotal";
116 $::form->{l_qty} = undef;
117 $::form->{l_linetotal} = undef;
118 $::form->{l_number} = "Y";
119 $::form->{number} = "position";
121 unless ( $::form->{bought}
124 || $::form->{quoted}) {
125 $::form->{bought} = $::form->{sold} = 1;
130 $lxdebug->leave_sub();
135 # Warning, deep magic ahead.
136 # This function parses the requested details, sanity checks them, and converts them into a format thats usable for IC->all_parts
138 # flags coming from the form:
140 # searchitems=part revers=0 lastsort=''
143 # partnumber ean description partsgroup classification serialnumber make model drawing microfiche
144 # transdatefrom transdateto
147 # itemstatus = active | onhand | short | obsolete | orphaned
148 # action = continue | top100
151 # bought sold onorder ordered rfq quoted
152 # l_partnumber l_description l_serialnumber l_unit l_listprice l_sellprice l_lastcost
153 # l_linetotal l_priceupdate l_bin l_rop l_weight l_image l_drawing l_microfiche
154 # l_partsgroup l_subtotal l_soldtotal l_deliverydate l_pricegroups
157 # nextsub revers lastsort sort ndxs_counter
159 sub generate_report {
160 $lxdebug->enter_sub();
162 $auth->assert('part_service_assembly_details');
164 my ($revers, $lastsort, $description);
166 my $cvar_configs = CVar->get_configs('module' => 'IC');
168 $form->{title} = $locale->text('Articles');
171 'deliverydate' => { 'text' => $locale->text('deliverydate'), },
172 'description' => { 'text' => $locale->text('Part Description'), },
173 'notes' => { 'text' => $locale->text('Notes'), },
174 'drawing' => { 'text' => $locale->text('Drawing'), },
175 'ean' => { 'text' => $locale->text('EAN'), },
176 'image' => { 'text' => $locale->text('Image'), },
177 'insertdate' => { 'text' => $locale->text('Insert Date'), },
178 'invnumber' => { 'text' => $locale->text('Invoice Number'), },
179 'lastcost' => { 'text' => $locale->text('Last Cost'), },
180 'linetotallastcost' => { 'text' => $locale->text('Extended'), },
181 'linetotallistprice' => { 'text' => $locale->text('Extended'), },
182 'linetotalsellprice' => { 'text' => $locale->text('Extended'), },
183 'listprice' => { 'text' => $locale->text('List Price'), },
184 'microfiche' => { 'text' => $locale->text('Microfiche'), },
185 'name' => { 'text' => $locale->text('Name'), },
186 'onhand' => { 'text' => $locale->text('Stocked Qty'), },
187 'ordnumber' => { 'text' => $locale->text('Order Number'), },
188 'partnumber' => { 'text' => $locale->text('Part Number'), },
189 'partsgroup' => { 'text' => $locale->text('Partsgroup'), },
190 'priceupdate' => { 'text' => $locale->text('Updated'), },
191 'quonumber' => { 'text' => $locale->text('Quotation'), },
192 'rop' => { 'text' => $locale->text('ROP'), },
193 'sellprice' => { 'text' => $locale->text('Sell Price'), },
194 'serialnumber' => { 'text' => $locale->text('Serial Number'), },
195 'soldtotal' => { 'text' => $locale->text('Qty in Selected Records'), },
196 'name' => { 'text' => $locale->text('Name in Selected Records'), },
197 'transdate' => { 'text' => $locale->text('Transdate Record'), },
198 'unit' => { 'text' => $locale->text('Unit'), },
199 'weight' => { 'text' => $locale->text('Weight'), },
200 'shop' => { 'text' => $locale->text('Shop article'), },
201 'type_and_classific' => { 'text' => $locale->text('Type'), },
202 'projectnumber' => { 'text' => $locale->text('Project Number'), },
203 'projectdescription' => { 'text' => $locale->text('Project Description'), },
204 'warehouse' => { 'text' => $locale->text('Default Warehouse'), },
205 'bin' => { 'text' => $locale->text('Default Bin'), },
208 $revers = $form->{revers};
209 $lastsort = $form->{lastsort};
211 # sorting and direction of sorting
212 # ToDO: change this to the simpler field+direction method
213 if (($form->{lastsort} eq "") && ($form->{sort} eq undef)) {
215 $form->{lastsort} = "partnumber";
216 $form->{sort} = "partnumber";
218 if ($form->{lastsort} eq $form->{sort}) {
219 $form->{revers} = 1 - $form->{revers};
222 $form->{lastsort} = $form->{sort};
226 # special case if we have a serialnumber limit search
227 # serialnumbers are only given in invoices and orders,
228 # so they can only pop up in bought, sold, rfq, and quoted stuff
229 $form->{no_sn_joins} = 'Y' if ( !$form->{bought} && !$form->{sold}
230 && !$form->{rfq} && !$form->{quoted}
231 && ($form->{l_serialnumber} || $form->{serialnumber}));
233 # special case for any checkbox of bought | sold | onorder | ordered | rfq | quoted.
234 # if any of these are ticked the behavior changes slightly for lastcost
235 # since all those are aggregation checks for the legder tables this is an internal switch
236 # refered to as ledgerchecks
237 $form->{ledgerchecks} = 'Y' if ( $form->{bought} || $form->{sold} || $form->{onorder}
238 || $form->{ordered} || $form->{rfq} || $form->{quoted});
240 # if something should be activated if something else is active, enter it here
242 onhand => [ qw(l_onhand) ],
243 short => [ qw(l_onhand) ],
244 onorder => [ qw(l_ordnumber) ],
245 ordered => [ qw(l_ordnumber) ],
246 rfq => [ qw(l_quonumber) ],
247 quoted => [ qw(l_quonumber) ],
248 bought => [ qw(l_invnumber) ],
249 sold => [ qw(l_invnumber) ],
250 ledgerchecks => [ qw(l_name) ],
251 serialnumber => [ qw(l_serialnumber) ],
252 no_sn_joins => [ qw(bought sold) ],
255 # get name of partsgroup if id is given
257 if ($form->{partsgroup_id}) {
258 my $pg = SL::DB::PartsGroup->new(id => $form->{partsgroup_id})->load;
259 $pg_name = $pg->{'partsgroup'};
262 # these strings get displayed at the top of the results to indicate the user which switches were used
264 active => $locale->text('Active'),
265 obsolete => $locale->text('Obsolete'),
266 orphaned => $locale->text('Orphaned'),
267 onhand => $locale->text('On Hand'),
268 short => $locale->text('Short'),
269 onorder => $locale->text('On Order'),
270 ordered => $locale->text('Ordered'),
271 rfq => $locale->text('RFQ'),
272 quoted => $locale->text('Quoted'),
273 bought => $locale->text('Bought'),
274 sold => $locale->text('Sold'),
275 transdatefrom => $locale->text('From') . " " . $locale->date(\%myconfig, $form->{transdatefrom}, 1),
276 transdateto => $locale->text('To (time)') . " " . $locale->date(\%myconfig, $form->{transdateto}, 1),
277 partnumber => $locale->text('Part Number') . ": '$form->{partnumber}'",
278 partsgroup => $locale->text('Partsgroup') . ": '$form->{partsgroup}'",
279 partsgroup_id => $locale->text('Partsgroup') . ": '$pg_name'",
280 serialnumber => $locale->text('Serial Number') . ": '$form->{serialnumber}'",
281 description => $locale->text('Part Description') . ": '$form->{description}'",
282 make => $locale->text('Make') . ": '$form->{make}'",
283 model => $locale->text('Model') . ": '$form->{model}'",
284 customername => $locale->text('Customer') . ": '$form->{customername}'",
285 customernumber=> $locale->text('Customer Part Number').": '$form->{customernumber}'",
286 drawing => $locale->text('Drawing') . ": '$form->{drawing}'",
287 microfiche => $locale->text('Microfiche') . ": '$form->{microfiche}'",
288 l_soldtotal => $locale->text('Qty in Selected Records'),
289 ean => $locale->text('EAN') . ": '$form->{ean}'",
290 insertdatefrom => $locale->text('Insert Date') . ": " . $locale->text('From') . " " . $locale->date(\%myconfig, $form->{insertdatefrom}, 1),
291 insertdateto => $locale->text('Insert Date') . ": " . $locale->text('To (time)') . " " . $locale->date(\%myconfig, $form->{insertdateto}, 1),
294 my @itemstatus_keys = qw(active obsolete orphaned onhand short);
295 my @callback_keys = qw(onorder ordered rfq quoted bought sold partnumber partsgroup partsgroup_id serialnumber description make model
296 drawing microfiche l_soldtotal l_deliverydate transdatefrom transdateto insertdatefrom insertdateto ean shop all);
298 # calculate dependencies
299 for (@itemstatus_keys, @callback_keys) {
300 next if ($form->{itemstatus} ne $_ && !$form->{$_});
301 map { $form->{$_} = 'Y' } @{ $dependencies{$_} } if $dependencies{$_};
304 # generate callback and optionstrings
306 for my $key (@itemstatus_keys, @callback_keys) {
307 next if ($form->{itemstatus} ne $key && !$form->{$key});
308 push @options, $optiontexts{$key};
311 # special case for lastcost
312 if ($form->{ledgerchecks}){
313 # ledgerchecks don't know about sellprice or lastcost. they just return a
314 # price. so rename sellprice to price, and drop lastcost.
315 $column_defs{sellprice}{text} = $locale->text('Price');
316 $form->{l_lastcost} = ""
319 if ($form->{description}) {
320 $description = $form->{description};
321 $description =~ s/\n/<br>/g;
324 if ($form->{l_linetotal}) {
325 $form->{l_qty} = "Y";
326 $form->{l_linetotalsellprice} = "Y" if $form->{l_sellprice};
327 $form->{l_linetotallastcost} = $form->{searchitems} eq 'assembly' && !$form->{bom} ? "" : 'Y' if $form->{l_lastcost};
328 $form->{l_linetotallistprice} = "Y" if $form->{l_listprice};
330 $form->{"l_type_and_classific"} = "Y";
332 if ($form->{l_service} && !$form->{l_assembly} && !$form->{l_part}) {
334 # remove warehouse, bin, weight and rop from list
335 map { $form->{"l_$_"} = "" } qw(bin weight rop warehouse);
337 $form->{l_onhand} = "";
339 # qty is irrelevant unless bought or sold
345 || $form->{quoted}) {
346 # $form->{l_onhand} = "Y";
348 $form->{l_linetotalsellprice} = "";
349 $form->{l_linetotallastcost} = "";
353 # soldtotal doesn't make sense with more than one bsooqr option.
354 # so reset it to sold (the most common option), and issue a warning
356 # also it doesn't make sense without bsooqr. disable and issue a warning too
357 my @bsooqr = qw(sold bought onorder ordered rfq quoted);
358 my $bsooqr_mode = grep { $form->{$_} } @bsooqr;
359 if ($form->{l_subtotal} && 1 < $bsooqr_mode) {
360 my $enabled = first { $form->{$_} } @bsooqr;
361 $form->{$_} = '' for @bsooqr;
362 $form->{$enabled} = 'Y';
364 push @options, $::locale->text('Subtotal cannot distinguish betweens record types. Only one of the selected record types will be displayed: #1', $optiontexts{$enabled});
366 if ($form->{l_soldtotal} && !$bsooqr_mode) {
367 delete $form->{l_soldtotal};
369 flash('warning', $::locale->text('Soldtotal does not make sense without any bsooqr options'));
371 if ($form->{l_soldtotal} && ($form->{l_warehouse} || $form->{l_bin})) {
372 delete $form->{"l_$_"} for qw(bin warehouse);
373 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.'));
375 if ($form->{l_name} && !$bsooqr_mode) {
376 delete $form->{l_name};
378 flash('warning', $::locale->text('Name does not make sense without any bsooqr options'));
380 IC->all_parts(\%myconfig, \%$form);
383 partnumber type_and_classific description notes partsgroup warehouse bin
384 onhand rop soldtotal unit listprice
385 linetotallistprice sellprice linetotalsellprice lastcost linetotallastcost
386 priceupdate weight image drawing microfiche invnumber ordnumber quonumber
387 transdate name serialnumber deliverydate ean projectnumber projectdescription
391 my $pricegroups = SL::DB::Manager::Pricegroup->get_all_sorted;
392 my @pricegroup_columns;
393 my %column_defs_pricegroups;
394 if ($form->{l_pricegroups}) {
395 @pricegroup_columns = map { "pricegroup_" . $_->id } @{ $pricegroups };
396 %column_defs_pricegroups = map {
397 "pricegroup_" . $_->id => {
398 text => $::locale->text('Pricegroup') . ' ' . $_->pricegroup,
403 push @columns, @pricegroup_columns;
405 my @includeable_custom_variables = grep { $_->{includeable} } @{ $cvar_configs };
406 my @searchable_custom_variables = grep { $_->{searchable} } @{ $cvar_configs };
407 my %column_defs_cvars = map { +"cvar_$_->{name}" => { 'text' => $_->{description} } } @includeable_custom_variables;
409 push @columns, map { "cvar_$_->{name}" } @includeable_custom_variables;
411 %column_defs = (%column_defs, %column_defs_cvars, %column_defs_pricegroups);
412 map { $column_defs{$_}->{visible} ||= $form->{"l_$_"} ? 1 : 0 } @columns;
413 map { $column_defs{$_}->{align} = 'right' } qw(onhand sellprice listprice lastcost linetotalsellprice linetotallastcost linetotallistprice rop weight soldtotal shop), @pricegroup_columns;
415 my @hidden_variables = (
416 qw(l_subtotal l_linetotal searchitems itemstatus bom l_pricegroups insertdatefrom insertdateto),
417 qw(l_type_and_classific classification_id l_part l_service l_assembly l_assortment),
420 map({ "cvar_$_->{name}" } @searchable_custom_variables),
421 map({'cvar_'. $_->{name} .'_qtyop'} grep({$_->{type} eq 'number'} @searchable_custom_variables)),
422 map({ "l_$_" } @columns),
425 my $callback = build_std_url('action=generate_report', grep { $form->{$_} } @hidden_variables);
427 my @sort_full = qw(partnumber description onhand soldtotal deliverydate insertdate shop);
428 my @sort_no_revers = qw(partsgroup priceupdate invnumber ordnumber quonumber name image drawing serialnumber);
430 foreach my $col (@sort_full) {
431 $column_defs{$col}->{link} = join '&', $callback, "sort=$col", map { "$_=" . E($form->{$_}) } qw(revers lastsort);
433 map { $column_defs{$_}->{link} = "${callback}&sort=$_" } @sort_no_revers;
435 # add order to callback
436 $form->{callback} = join '&', ($callback, map { "${_}=" . E($form->{$_}) } qw(sort revers));
438 my $report = SL::ReportGenerator->new(\%myconfig, $form);
440 my %attachment_basenames = (
441 'part' => $locale->text('part_list'),
442 'service' => $locale->text('service_list'),
443 'assembly' => $locale->text('assembly_list'),
444 'article' => $locale->text('article_list'),
447 $report->set_options('raw_top_info_text' => $form->parse_html_template('ic/generate_report_top', { options => \@options }),
448 'raw_bottom_info_text' => $form->parse_html_template('ic/generate_report_bottom' ,
449 { PART_CLASSIFICATIONS => SL::DB::Manager::PartClassification->get_all_sorted }),
450 'output_format' => 'HTML',
451 'title' => $form->{title},
452 'attachment_basename' => 'article_list' . strftime('_%Y%m%d', localtime time),
454 $report->set_options_from_form();
455 $locale->set_numberformat_wo_thousands_separator(\%myconfig) if lc($report->{options}->{output_format}) eq 'csv';
457 $report->set_columns(%column_defs);
458 $report->set_column_order(@columns);
460 $report->set_export_options('generate_report', @hidden_variables, qw(sort revers));
462 $report->set_sort_indicator($form->{sort}, $form->{revers} ? 0 : 1);
464 CVar->add_custom_variables_to_report('module' => 'IC',
465 'trans_id_field' => 'id',
466 'configs' => $cvar_configs,
467 'column_defs' => \%column_defs,
468 'data' => $form->{parts});
470 CVar->add_custom_variables_to_report('module' => 'IC',
471 'sub_module' => sub { $_[0]->{ioi} },
472 'trans_id_field' => 'ioi_id',
473 'configs' => $cvar_configs,
474 'column_defs' => \%column_defs,
475 'data' => $form->{parts});
477 my @subtotal_columns = qw(sellprice listprice lastcost);
478 my %subtotals = map { $_ => 0 } ('onhand', @subtotal_columns);
479 my %totals = map { $_ => 0 } @subtotal_columns;
481 my $same_item = @{ $form->{parts} } ? $form->{parts}[0]{ $form->{sort} } : undef;
483 my $defaults = AM->get_defaults();
486 foreach my $ref (@{ $form->{parts} }) {
488 # fresh row, for inserting later
489 my $row = { map { $_ => { 'data' => $ref->{$_} } } @columns };
491 $ref->{exchangerate} ||= 1;
492 $ref->{price_factor} ||= 1;
493 $ref->{sellprice} *= $ref->{exchangerate} / $ref->{price_factor};
494 $ref->{listprice} *= $ref->{exchangerate} / $ref->{price_factor};
495 $ref->{lastcost} *= $ref->{exchangerate} / $ref->{price_factor};
497 # use this for assemblies
498 my $soldtotal = $bsooqr_mode ? $ref->{soldtotal} : $ref->{onhand};
500 if ($ref->{assemblyitem}) {
501 $row->{partnumber}{align} = 'right';
502 $row->{soldtotal}{data} = 0;
503 $soldtotal = 0 if ($form->{sold});
506 my $edit_link = build_std_url('script=controller.pl', 'action=Part/edit', 'part.id=' . E($ref->{id}));
507 $row->{partnumber}->{link} = $edit_link;
508 $row->{description}->{link} = $edit_link;
510 foreach (qw(sellprice listprice lastcost)) {
511 $row->{$_}{data} = $form->format_amount(\%myconfig, $ref->{$_}, 2);
512 $row->{"linetotal$_"}{data} = $form->format_amount(\%myconfig, $ref->{onhand} * $ref->{$_}, 2);
514 foreach ( @pricegroup_columns ) {
515 $row->{$_}{data} = $form->format_amount(\%myconfig, $ref->{"$_"}, 2);
519 map { $row->{$_}{data} = $form->format_amount(\%myconfig, $ref->{$_}); } qw(onhand rop weight soldtotal);
521 $row->{weight}->{data} .= ' ' . $defaults->{weightunit};
523 # 'yes' and 'no' for boolean value shop
524 if ($form->{l_shop}) {
525 $row->{shop}{data} = $row->{shop}{data}? $::locale->text('yes') : $::locale->text('no');
528 if (!$ref->{assemblyitem}) {
529 foreach my $col (@subtotal_columns) {
530 $totals{$col} += $soldtotal * $ref->{$col};
531 $subtotals{$col} += $soldtotal * $ref->{$col};
534 $subtotals{soldtotal} += $soldtotal;
538 if ($ref->{module} eq 'oe') {
539 # für oe gibt es vier fälle, jeweils nach kunde oder lieferant unterschiedlich:
541 # | ist bestellt | Von Kunden bestellt | -> edit_oe_ord_link
542 # | Anfrage | Angebot | -> edit_oe_quo_link
544 my $edit_oe_ord_link = ($::instance_conf->get_feature_experimental)
545 ? build_std_url("script=controller.pl", 'action=Order/edit',
546 'type=' . E($ref->{cv} eq 'vendor' ? 'purchase_order' : 'sales_order'), 'id=' . E($ref->{trans_id}), 'callback')
547 : build_std_url("script=oe.pl", 'action=edit',
548 'type=' . E($ref->{cv} eq 'vendor' ? 'purchase_order' : 'sales_order'), 'id=' . E($ref->{trans_id}), 'callback');
550 my $edit_oe_quo_link = ($::instance_conf->get_feature_experimental)
551 ? build_std_url("script=controller.pl", 'action=Order/edit',
552 'type=' . E($ref->{cv} eq 'vendor' ? 'request_quotation' : 'sales_quotation'), 'id=' . E($ref->{trans_id}), 'callback')
553 : build_std_url("script=oe.pl", 'action=edit',
554 'type=' . E($ref->{cv} eq 'vendor' ? 'request_quotation' : 'sales_quotation'), 'id=' . E($ref->{trans_id}), 'callback');
556 $row->{ordnumber}{link} = $edit_oe_ord_link;
557 $row->{quonumber}{link} = $edit_oe_quo_link if (!$ref->{ordnumber});
560 $row->{invnumber}{link} = build_std_url("script=$ref->{module}.pl", 'action=edit', 'type=invoice', 'id=' . E($ref->{trans_id}), 'callback') if ($ref->{invnumber});
563 # set properties of images
564 if ($ref->{image} && (lc $report->{options}->{output_format} eq 'html')) {
565 $row->{image}{data} = '';
566 $row->{image}{raw_data} = '<a href="' . H($ref->{image}) . '"><img src="' . H($ref->{image}) . '" height="32" border="0"></a>';
568 map { $row->{$_}{link} = $ref->{$_} } qw(drawing microfiche);
570 $row->{notes}{data} = SL::HTML::Util->strip($ref->{notes});
571 $row->{type_and_classific}{data} = SL::Presenter::Part::type_abbreviation($ref->{part_type}).
572 SL::Presenter::Part::classification_abbreviation($ref->{classification_id});
574 $report->add_data($row);
576 my $next_ref = $form->{parts}[$idx + 1];
578 # insert subtotal rows
579 if (($form->{l_subtotal} eq 'Y') &&
581 (!$next_ref->{assemblyitem} && ($same_item ne $next_ref->{ $form->{sort} })))) {
582 my $row = { map { $_ => { 'class' => 'listsubtotal', } } @columns };
584 if ( !$form->{l_assembly} || !$form->{bom}) {
585 $row->{soldtotal}->{data} = $form->format_amount(\%myconfig, $subtotals{soldtotal});
588 map { $row->{"linetotal$_"}->{data} = $form->format_amount(\%myconfig, $subtotals{$_}, 2) } @subtotal_columns;
589 map { $subtotals{$_} = 0 } ('soldtotal', @subtotal_columns);
591 $report->add_data($row);
593 $same_item = $next_ref->{ $form->{sort} };
599 if ($form->{"l_linetotal"} && !$form->{report_generator_csv_options_for_import}) {
600 my $row = { map { $_ => { 'class' => 'listtotal', } } @columns };
602 map { $row->{"linetotal$_"}->{data} = $form->format_amount(\%myconfig, $totals{$_}, 2) } @subtotal_columns;
604 $report->add_separator();
605 $report->add_data($row);
608 setup_ic_generate_report_action_bar();
609 $report->generate_with_headers();
611 $lxdebug->leave_sub();
612 } #end generate_report
614 sub setup_ic_search_action_bar {
617 for my $bar ($::request->layout->get('actionbar')) {
621 submit => [ '#form', { action => 'generate_report' } ],
622 accesskey => 'enter',
627 submit => [ '#form', { action => 'top100' } ],
633 sub setup_ic_generate_report_action_bar {
636 for my $bar ($::request->layout->get('actionbar')) {
644 submit => [ '#new_form', { action => 'Part/add_part' } ],
645 accesskey => 'enter',
649 submit => [ '#new_form', { action => 'Part/add_service' } ],
653 submit => [ '#new_form', { action => 'Part/add_assembly' } ],
656 t8('Add Assortment'),
657 submit => [ '#new_form', { action => 'Part/add_assortment' } ],
659 ], # end of combobox "Add part"