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'), },
206 'make' => { 'text' => $locale->text('Make'), },
207 'model' => { 'text' => $locale->text('Model'), },
210 $revers = $form->{revers};
211 $lastsort = $form->{lastsort};
213 # sorting and direction of sorting
214 # ToDO: change this to the simpler field+direction method
215 if (($form->{lastsort} eq "") && ($form->{sort} eq undef)) {
217 $form->{lastsort} = "partnumber";
218 $form->{sort} = "partnumber";
220 if ($form->{lastsort} eq $form->{sort}) {
221 $form->{revers} = 1 - $form->{revers};
224 $form->{lastsort} = $form->{sort};
228 # special case if we have a serialnumber limit search
229 # serialnumbers are only given in invoices and orders,
230 # so they can only pop up in bought, sold, rfq, and quoted stuff
231 $form->{no_sn_joins} = 'Y' if ( !$form->{bought} && !$form->{sold}
232 && !$form->{rfq} && !$form->{quoted}
233 && ($form->{l_serialnumber} || $form->{serialnumber}));
235 # special case for any checkbox of bought | sold | onorder | ordered | rfq | quoted.
236 # if any of these are ticked the behavior changes slightly for lastcost
237 # since all those are aggregation checks for the legder tables this is an internal switch
238 # refered to as ledgerchecks
239 $form->{ledgerchecks} = 'Y' if ( $form->{bought} || $form->{sold} || $form->{onorder}
240 || $form->{ordered} || $form->{rfq} || $form->{quoted});
242 # if something should be activated if something else is active, enter it here
244 onhand => [ qw(l_onhand) ],
245 short => [ qw(l_onhand) ],
246 onorder => [ qw(l_ordnumber) ],
247 ordered => [ qw(l_ordnumber) ],
248 rfq => [ qw(l_quonumber) ],
249 quoted => [ qw(l_quonumber) ],
250 bought => [ qw(l_invnumber) ],
251 sold => [ qw(l_invnumber) ],
252 ledgerchecks => [ qw(l_name) ],
253 serialnumber => [ qw(l_serialnumber) ],
254 no_sn_joins => [ qw(bought sold) ],
257 # get name of partsgroup if id is given
259 if ($form->{partsgroup_id}) {
260 my $pg = SL::DB::PartsGroup->new(id => $form->{partsgroup_id})->load;
261 $pg_name = $pg->{'partsgroup'};
264 # these strings get displayed at the top of the results to indicate the user which switches were used
266 active => $locale->text('Active'),
267 obsolete => $locale->text('Obsolete'),
268 orphaned => $locale->text('Orphaned'),
269 onhand => $locale->text('On Hand'),
270 short => $locale->text('Short'),
271 onorder => $locale->text('On Order'),
272 ordered => $locale->text('Ordered'),
273 rfq => $locale->text('RFQ'),
274 quoted => $locale->text('Quoted'),
275 bought => $locale->text('Bought'),
276 sold => $locale->text('Sold'),
277 transdatefrom => $locale->text('From') . " " . $locale->date(\%myconfig, $form->{transdatefrom}, 1),
278 transdateto => $locale->text('To (time)') . " " . $locale->date(\%myconfig, $form->{transdateto}, 1),
279 partnumber => $locale->text('Part Number') . ": '$form->{partnumber}'",
280 partsgroup => $locale->text('Partsgroup') . ": '$form->{partsgroup}'",
281 partsgroup_id => $locale->text('Partsgroup') . ": '$pg_name'",
282 serialnumber => $locale->text('Serial Number') . ": '$form->{serialnumber}'",
283 description => $locale->text('Part Description') . ": '$form->{description}'",
284 make => $locale->text('Make') . ": '$form->{make}'",
285 model => $locale->text('Model') . ": '$form->{model}'",
286 customername => $locale->text('Customer') . ": '$form->{customername}'",
287 customernumber=> $locale->text('Customer Part Number').": '$form->{customernumber}'",
288 drawing => $locale->text('Drawing') . ": '$form->{drawing}'",
289 microfiche => $locale->text('Microfiche') . ": '$form->{microfiche}'",
290 l_soldtotal => $locale->text('Qty in Selected Records'),
291 ean => $locale->text('EAN') . ": '$form->{ean}'",
292 insertdatefrom => $locale->text('Insert Date') . ": " . $locale->text('From') . " " . $locale->date(\%myconfig, $form->{insertdatefrom}, 1),
293 insertdateto => $locale->text('Insert Date') . ": " . $locale->text('To (time)') . " " . $locale->date(\%myconfig, $form->{insertdateto}, 1),
294 l_service => $locale->text('Services'),
295 l_assembly => $locale->text('Assemblies'),
296 l_part => $locale->text('Parts'),
299 my @itemstatus_keys = qw(active obsolete orphaned onhand short);
300 my @callback_keys = qw(onorder ordered rfq quoted bought sold partnumber partsgroup partsgroup_id serialnumber description make model
301 drawing microfiche l_soldtotal l_deliverydate transdatefrom transdateto insertdatefrom insertdateto ean shop all
302 l_service l_assembly l_part);
304 # calculate dependencies
305 for (@itemstatus_keys, @callback_keys) {
306 next if ($form->{itemstatus} ne $_ && !$form->{$_});
307 map { $form->{$_} = 'Y' } @{ $dependencies{$_} } if $dependencies{$_};
310 # generate callback and optionstrings
312 for my $key (@itemstatus_keys, @callback_keys) {
313 next if ($form->{itemstatus} ne $key && !$form->{$key});
314 push @options, $optiontexts{$key};
317 # special case for lastcost
318 if ($form->{ledgerchecks}){
319 # ledgerchecks don't know about sellprice or lastcost. they just return a
320 # price. so rename sellprice to price, and drop lastcost.
321 $column_defs{sellprice}{text} = $locale->text('Price');
322 $form->{l_lastcost} = ""
325 if ($form->{description}) {
326 $description = $form->{description};
327 $description =~ s/\n/<br>/g;
330 if ($form->{l_linetotal}) {
331 $form->{l_qty} = "Y";
332 $form->{l_linetotalsellprice} = "Y" if $form->{l_sellprice};
333 $form->{l_linetotallastcost} = $form->{searchitems} eq 'assembly' && !$form->{bom} ? "" : 'Y' if $form->{l_lastcost};
334 $form->{l_linetotallistprice} = "Y" if $form->{l_listprice};
336 $form->{"l_type_and_classific"} = "Y";
338 if ($form->{l_service} && !$form->{l_assembly} && !$form->{l_part}) {
340 # remove warehouse, bin, weight and rop from list
341 map { $form->{"l_$_"} = "" } qw(bin weight rop warehouse);
343 $form->{l_onhand} = "";
345 # qty is irrelevant unless bought or sold
351 || $form->{quoted}) {
352 # $form->{l_onhand} = "Y";
354 $form->{l_linetotalsellprice} = "";
355 $form->{l_linetotallastcost} = "";
359 # soldtotal doesn't make sense with more than one bsooqr option.
360 # so reset it to sold (the most common option), and issue a warning
362 # also it doesn't make sense without bsooqr. disable and issue a warning too
363 my @bsooqr = qw(sold bought onorder ordered rfq quoted);
364 my $bsooqr_mode = grep { $form->{$_} } @bsooqr;
365 if ($form->{l_subtotal} && 1 < $bsooqr_mode) {
366 my $enabled = first { $form->{$_} } @bsooqr;
367 $form->{$_} = '' for @bsooqr;
368 $form->{$enabled} = 'Y';
370 push @options, $::locale->text('Subtotal cannot distinguish betweens record types. Only one of the selected record types will be displayed: #1', $optiontexts{$enabled});
372 if ($form->{l_soldtotal} && !$bsooqr_mode) {
373 delete $form->{l_soldtotal};
375 flash('warning', $::locale->text('Soldtotal does not make sense without any bsooqr options'));
377 if ($form->{l_soldtotal} && ($form->{l_warehouse} || $form->{l_bin})) {
378 delete $form->{"l_$_"} for qw(bin warehouse);
379 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.'));
381 if ($form->{l_name} && !$bsooqr_mode) {
382 delete $form->{l_name};
384 flash('warning', $::locale->text('Name does not make sense without any bsooqr options'));
386 IC->all_parts(\%myconfig, \%$form);
389 partnumber type_and_classific description notes partsgroup warehouse bin
390 make model onhand rop soldtotal unit listprice
391 linetotallistprice sellprice linetotalsellprice lastcost linetotallastcost
392 priceupdate weight image drawing microfiche invnumber ordnumber quonumber
393 transdate name serialnumber deliverydate ean projectnumber projectdescription
397 my $pricegroups = SL::DB::Manager::Pricegroup->get_all_sorted;
398 my @pricegroup_columns;
399 my %column_defs_pricegroups;
400 if ($form->{l_pricegroups}) {
401 @pricegroup_columns = map { "pricegroup_" . $_->id } @{ $pricegroups };
402 %column_defs_pricegroups = map {
403 "pricegroup_" . $_->id => {
404 text => $::locale->text('Pricegroup') . ' ' . $_->pricegroup,
409 push @columns, @pricegroup_columns;
411 my @includeable_custom_variables = grep { $_->{includeable} } @{ $cvar_configs };
412 my @searchable_custom_variables = grep { $_->{searchable} } @{ $cvar_configs };
413 my %column_defs_cvars = map { +"cvar_$_->{name}" => { 'text' => $_->{description} } } @includeable_custom_variables;
415 push @columns, map { "cvar_$_->{name}" } @includeable_custom_variables;
417 %column_defs = (%column_defs, %column_defs_cvars, %column_defs_pricegroups);
418 map { $column_defs{$_}->{visible} ||= $form->{"l_$_"} ? 1 : 0 } @columns;
419 map { $column_defs{$_}->{align} = 'right' } qw(onhand sellprice listprice lastcost linetotalsellprice linetotallastcost linetotallistprice rop weight soldtotal shop), @pricegroup_columns;
421 my @hidden_variables = (
422 qw(l_subtotal l_linetotal searchitems itemstatus bom l_pricegroups insertdatefrom insertdateto),
423 qw(l_type_and_classific classification_id l_part l_service l_assembly l_assortment),
426 map({ "cvar_$_->{name}" } @searchable_custom_variables),
427 map({'cvar_'. $_->{name} .'_from'} grep({$_->{type} eq 'date'} @searchable_custom_variables)),
428 map({'cvar_'. $_->{name} .'_to'} grep({$_->{type} eq 'date'} @searchable_custom_variables)),
429 map({'cvar_'. $_->{name} .'_qtyop'} grep({$_->{type} eq 'number'} @searchable_custom_variables)),
430 map({ "l_$_" } @columns),
433 my $callback = build_std_url('action=generate_report', grep { $form->{$_} } @hidden_variables);
435 my @sort_full = qw(partnumber description onhand soldtotal deliverydate insertdate shop);
436 my @sort_no_revers = qw(partsgroup priceupdate invnumber ordnumber quonumber name image drawing serialnumber);
438 foreach my $col (@sort_full) {
439 $column_defs{$col}->{link} = join '&', $callback, "sort=$col", map { "$_=" . E($form->{$_}) } qw(revers lastsort);
441 map { $column_defs{$_}->{link} = "${callback}&sort=$_" } @sort_no_revers;
443 # add order to callback
444 $form->{callback} = join '&', ($callback, map { "${_}=" . E($form->{$_}) } qw(sort revers));
446 my $report = SL::ReportGenerator->new(\%myconfig, $form);
448 my %attachment_basenames = (
449 'part' => $locale->text('part_list'),
450 'service' => $locale->text('service_list'),
451 'assembly' => $locale->text('assembly_list'),
452 'article' => $locale->text('article_list'),
455 $report->set_options('raw_top_info_text' => $form->parse_html_template('ic/generate_report_top', { options => \@options }),
456 'raw_bottom_info_text' => $form->parse_html_template('ic/generate_report_bottom' ,
457 { PART_CLASSIFICATIONS => SL::DB::Manager::PartClassification->get_all_sorted }),
458 'output_format' => 'HTML',
459 'title' => $form->{title},
460 'attachment_basename' => 'article_list' . strftime('_%Y%m%d', localtime time),
462 $report->set_options_from_form();
463 $locale->set_numberformat_wo_thousands_separator(\%myconfig) if lc($report->{options}->{output_format}) eq 'csv';
465 $report->set_columns(%column_defs);
466 $report->set_column_order(@columns);
468 $report->set_export_options('generate_report', @hidden_variables, qw(sort revers));
470 $report->set_sort_indicator($form->{sort}, $form->{revers} ? 0 : 1);
472 CVar->add_custom_variables_to_report('module' => 'IC',
473 'trans_id_field' => 'id',
474 'configs' => $cvar_configs,
475 'column_defs' => \%column_defs,
476 'data' => $form->{parts});
478 CVar->add_custom_variables_to_report('module' => 'IC',
479 'sub_module' => sub { $_[0]->{ioi} },
480 'trans_id_field' => 'ioi_id',
481 'configs' => $cvar_configs,
482 'column_defs' => \%column_defs,
483 'data' => $form->{parts});
485 my @subtotal_columns = qw(sellprice listprice lastcost);
486 my %subtotals = map { $_ => 0 } ('onhand', @subtotal_columns);
487 my %totals = map { $_ => 0 } @subtotal_columns;
489 my $same_item = @{ $form->{parts} } ? $form->{parts}[0]{ $form->{sort} } : undef;
491 my $defaults = AM->get_defaults();
494 foreach my $ref (@{ $form->{parts} }) {
496 # fresh row, for inserting later
497 my $row = { map { $_ => { 'data' => $ref->{$_} } } @columns };
499 $ref->{exchangerate} ||= 1;
500 $ref->{price_factor} ||= 1;
501 $ref->{sellprice} *= $ref->{exchangerate} / $ref->{price_factor};
502 $ref->{listprice} *= $ref->{exchangerate} / $ref->{price_factor};
503 $ref->{lastcost} *= $ref->{exchangerate} / $ref->{price_factor};
505 # use this for assemblies
506 my $soldtotal = $bsooqr_mode ? $ref->{soldtotal} : $ref->{onhand};
508 if ($ref->{assemblyitem}) {
509 $row->{partnumber}{align} = 'right';
510 $row->{soldtotal}{data} = 0;
511 $soldtotal = 0 if ($form->{sold});
514 my $edit_link = build_std_url('script=controller.pl', 'action=Part/edit', 'part.id=' . E($ref->{id}));
515 $row->{partnumber}->{link} = $edit_link;
516 $row->{description}->{link} = $edit_link;
518 foreach (qw(sellprice listprice lastcost)) {
519 $row->{$_}{data} = $form->format_amount(\%myconfig, $ref->{$_}, 2);
520 $row->{"linetotal$_"}{data} = $form->format_amount(\%myconfig, $ref->{onhand} * $ref->{$_}, 2);
522 foreach ( @pricegroup_columns ) {
523 $row->{$_}{data} = $form->format_amount(\%myconfig, $ref->{"$_"}, 2);
527 map { $row->{$_}{data} = $form->format_amount(\%myconfig, $ref->{$_}); } qw(onhand rop weight soldtotal);
529 $row->{weight}->{data} .= ' ' . $defaults->{weightunit};
531 # 'yes' and 'no' for boolean value shop
532 if ($form->{l_shop}) {
533 $row->{shop}{data} = $row->{shop}{data}? $::locale->text('yes') : $::locale->text('no');
536 if (!$ref->{assemblyitem}) {
537 foreach my $col (@subtotal_columns) {
538 $totals{$col} += $soldtotal * $ref->{$col};
539 $subtotals{$col} += $soldtotal * $ref->{$col};
542 $subtotals{soldtotal} += $soldtotal;
546 if ($ref->{module} eq 'oe') {
547 # für oe gibt es vier fälle, jeweils nach kunde oder lieferant unterschiedlich:
549 # | ist bestellt | Von Kunden bestellt | -> edit_oe_ord_link
550 # | Anfrage | Angebot | -> edit_oe_quo_link
552 my $edit_oe_ord_link = ($::instance_conf->get_feature_experimental_order)
553 ? build_std_url("script=controller.pl", 'action=Order/edit',
554 'type=' . E($ref->{cv} eq 'vendor' ? 'purchase_order' : 'sales_order'), 'id=' . E($ref->{trans_id}), 'callback')
555 : build_std_url("script=oe.pl", 'action=edit',
556 'type=' . E($ref->{cv} eq 'vendor' ? 'purchase_order' : 'sales_order'), 'id=' . E($ref->{trans_id}), 'callback');
558 my $edit_oe_quo_link = ($::instance_conf->get_feature_experimental_order)
559 ? build_std_url("script=controller.pl", 'action=Order/edit',
560 'type=' . E($ref->{cv} eq 'vendor' ? 'request_quotation' : 'sales_quotation'), 'id=' . E($ref->{trans_id}), 'callback')
561 : build_std_url("script=oe.pl", 'action=edit',
562 'type=' . E($ref->{cv} eq 'vendor' ? 'request_quotation' : 'sales_quotation'), 'id=' . E($ref->{trans_id}), 'callback');
564 $row->{ordnumber}{link} = $edit_oe_ord_link;
565 $row->{quonumber}{link} = $edit_oe_quo_link if (!$ref->{ordnumber});
568 $row->{invnumber}{link} = build_std_url("script=$ref->{module}.pl", 'action=edit', 'type=invoice', 'id=' . E($ref->{trans_id}), 'callback') if ($ref->{invnumber});
571 # set properties of images
572 if ($ref->{image} && (lc $report->{options}->{output_format} eq 'html')) {
573 $row->{image}{data} = '';
574 $row->{image}{raw_data} = '<a href="' . H($ref->{image}) . '"><img src="' . H($ref->{image}) . '" height="32" border="0"></a>';
576 map { $row->{$_}{link} = $ref->{$_} } qw(drawing microfiche);
578 $row->{notes}{data} = SL::HTML::Util->strip($ref->{notes});
579 $row->{type_and_classific}{data} = SL::Presenter::Part::type_abbreviation($ref->{part_type}).
580 SL::Presenter::Part::classification_abbreviation($ref->{classification_id});
582 $report->add_data($row);
584 my $next_ref = $form->{parts}[$idx + 1];
586 # insert subtotal rows
587 if (($form->{l_subtotal} eq 'Y') &&
589 (!$next_ref->{assemblyitem} && ($same_item ne $next_ref->{ $form->{sort} })))) {
590 my $row = { map { $_ => { 'class' => 'listsubtotal', } } @columns };
592 if ( !$form->{l_assembly} || !$form->{bom}) {
593 $row->{soldtotal}->{data} = $form->format_amount(\%myconfig, $subtotals{soldtotal});
596 map { $row->{"linetotal$_"}->{data} = $form->format_amount(\%myconfig, $subtotals{$_}, 2) } @subtotal_columns;
597 map { $subtotals{$_} = 0 } ('soldtotal', @subtotal_columns);
599 $report->add_data($row);
601 $same_item = $next_ref->{ $form->{sort} };
607 if ($form->{"l_linetotal"} && !$form->{report_generator_csv_options_for_import}) {
608 my $row = { map { $_ => { 'class' => 'listtotal', } } @columns };
610 map { $row->{"linetotal$_"}->{data} = $form->format_amount(\%myconfig, $totals{$_}, 2) } @subtotal_columns;
612 $report->add_separator();
613 $report->add_data($row);
616 setup_ic_generate_report_action_bar();
617 $report->generate_with_headers();
619 $lxdebug->leave_sub();
620 } #end generate_report
622 sub setup_ic_search_action_bar {
625 for my $bar ($::request->layout->get('actionbar')) {
629 submit => [ '#form', { action => 'generate_report' } ],
630 accesskey => 'enter',
635 submit => [ '#form', { action => 'top100' } ],
641 sub setup_ic_generate_report_action_bar {
644 for my $bar ($::request->layout->get('actionbar')) {
652 submit => [ '#new_form', { action => 'Part/add_part' } ],
653 accesskey => 'enter',
657 submit => [ '#new_form', { action => 'Part/add_service' } ],
661 submit => [ '#new_form', { action => 'Part/add_assembly' } ],
664 t8('Add Assortment'),
665 submit => [ '#new_form', { action => 'Part/add_assortment' } ],
667 ], # end of combobox "Add part"