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),
 
 296   my @itemstatus_keys = qw(active obsolete orphaned onhand short);
 
 297   my @callback_keys   = qw(onorder ordered rfq quoted bought sold partnumber partsgroup partsgroup_id serialnumber description make model
 
 298                            drawing microfiche l_soldtotal l_deliverydate transdatefrom transdateto insertdatefrom insertdateto ean shop all);
 
 300   # calculate dependencies
 
 301   for (@itemstatus_keys, @callback_keys) {
 
 302     next if ($form->{itemstatus} ne $_ && !$form->{$_});
 
 303     map { $form->{$_} = 'Y' } @{ $dependencies{$_} } if $dependencies{$_};
 
 306   # generate callback and optionstrings
 
 308   for my  $key (@itemstatus_keys, @callback_keys) {
 
 309     next if ($form->{itemstatus} ne $key && !$form->{$key});
 
 310     push @options, $optiontexts{$key};
 
 313   # special case for lastcost
 
 314   if ($form->{ledgerchecks}){
 
 315     # ledgerchecks don't know about sellprice or lastcost. they just return a
 
 316     # price. so rename sellprice to price, and drop lastcost.
 
 317     $column_defs{sellprice}{text} = $locale->text('Price');
 
 318     $form->{l_lastcost} = ""
 
 321   if ($form->{description}) {
 
 322     $description = $form->{description};
 
 323     $description =~ s/\n/<br>/g;
 
 326   if ($form->{l_linetotal}) {
 
 327     $form->{l_qty} = "Y";
 
 328     $form->{l_linetotalsellprice} = "Y" if $form->{l_sellprice};
 
 329     $form->{l_linetotallastcost}  = $form->{searchitems} eq 'assembly' && !$form->{bom} ? "" : 'Y' if  $form->{l_lastcost};
 
 330     $form->{l_linetotallistprice} = "Y" if $form->{l_listprice};
 
 332   $form->{"l_type_and_classific"} = "Y";
 
 334   if ($form->{l_service} && !$form->{l_assembly} && !$form->{l_part}) {
 
 336     # remove warehouse, bin, weight and rop from list
 
 337     map { $form->{"l_$_"} = "" } qw(bin weight rop warehouse);
 
 339     $form->{l_onhand} = "";
 
 341     # qty is irrelevant unless bought or sold
 
 347         || $form->{quoted}) {
 
 348 #      $form->{l_onhand} = "Y";
 
 350       $form->{l_linetotalsellprice} = "";
 
 351       $form->{l_linetotallastcost}  = "";
 
 355   # soldtotal doesn't make sense with more than one bsooqr option.
 
 356   # so reset it to sold (the most common option), and issue a warning
 
 358   # also it doesn't make sense without bsooqr. disable and issue a warning too
 
 359   my @bsooqr = qw(sold bought onorder ordered rfq quoted);
 
 360   my $bsooqr_mode = grep { $form->{$_} } @bsooqr;
 
 361   if ($form->{l_subtotal} && 1 < $bsooqr_mode) {
 
 362     my $enabled       = first { $form->{$_} } @bsooqr;
 
 363     $form->{$_}       = ''   for @bsooqr;
 
 364     $form->{$enabled} = 'Y';
 
 366     push @options, $::locale->text('Subtotal cannot distinguish betweens record types. Only one of the selected record types will be displayed: #1', $optiontexts{$enabled});
 
 368   if ($form->{l_soldtotal} && !$bsooqr_mode) {
 
 369     delete $form->{l_soldtotal};
 
 371     flash('warning', $::locale->text('Soldtotal does not make sense without any bsooqr options'));
 
 373   if ($form->{l_soldtotal} && ($form->{l_warehouse} || $form->{l_bin})) {
 
 374     delete $form->{"l_$_"} for  qw(bin warehouse);
 
 375     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.'));
 
 377   if ($form->{l_name} && !$bsooqr_mode) {
 
 378     delete $form->{l_name};
 
 380     flash('warning', $::locale->text('Name does not make sense without any bsooqr options'));
 
 382   IC->all_parts(\%myconfig, \%$form);
 
 385     partnumber type_and_classific description notes partsgroup warehouse bin
 
 386     make model onhand rop soldtotal unit listprice
 
 387     linetotallistprice sellprice linetotalsellprice lastcost linetotallastcost
 
 388     priceupdate weight image drawing microfiche invnumber ordnumber quonumber
 
 389     transdate name serialnumber deliverydate ean projectnumber projectdescription
 
 393   my $pricegroups = SL::DB::Manager::Pricegroup->get_all_sorted;
 
 394   my @pricegroup_columns;
 
 395   my %column_defs_pricegroups;
 
 396   if ($form->{l_pricegroups}) {
 
 397     @pricegroup_columns      = map { "pricegroup_" . $_->id } @{ $pricegroups };
 
 398     %column_defs_pricegroups = map {
 
 399       "pricegroup_" . $_->id => {
 
 400         text    => $::locale->text('Pricegroup') . ' ' . $_->pricegroup,
 
 405   push @columns, @pricegroup_columns;
 
 407   my @includeable_custom_variables = grep { $_->{includeable} } @{ $cvar_configs };
 
 408   my @searchable_custom_variables  = grep { $_->{searchable} }  @{ $cvar_configs };
 
 409   my %column_defs_cvars            = map { +"cvar_$_->{name}" => { 'text' => $_->{description} } } @includeable_custom_variables;
 
 411   push @columns, map { "cvar_$_->{name}" } @includeable_custom_variables;
 
 413   %column_defs = (%column_defs, %column_defs_cvars, %column_defs_pricegroups);
 
 414   map { $column_defs{$_}->{visible} ||= $form->{"l_$_"} ? 1 : 0 } @columns;
 
 415   map { $column_defs{$_}->{align}   = 'right' } qw(onhand sellprice listprice lastcost linetotalsellprice linetotallastcost linetotallistprice rop weight soldtotal shop), @pricegroup_columns;
 
 417   my @hidden_variables = (
 
 418     qw(l_subtotal l_linetotal searchitems itemstatus bom l_pricegroups insertdatefrom insertdateto),
 
 419     qw(l_type_and_classific classification_id l_part l_service l_assembly l_assortment),
 
 422     map({ "cvar_$_->{name}" } @searchable_custom_variables),
 
 423     map({'cvar_'. $_->{name} .'_from'} grep({$_->{type} eq 'date'} @searchable_custom_variables)),
 
 424     map({'cvar_'. $_->{name} .'_to'}   grep({$_->{type} eq 'date'} @searchable_custom_variables)),
 
 425     map({'cvar_'. $_->{name} .'_qtyop'} grep({$_->{type} eq 'number'} @searchable_custom_variables)),
 
 426     map({ "l_$_" } @columns),
 
 429   my $callback         = build_std_url('action=generate_report', grep { $form->{$_} } @hidden_variables);
 
 431   my @sort_full        = qw(partnumber description onhand soldtotal deliverydate insertdate shop);
 
 432   my @sort_no_revers   = qw(partsgroup priceupdate invnumber ordnumber quonumber name image drawing serialnumber);
 
 434   foreach my $col (@sort_full) {
 
 435     $column_defs{$col}->{link} = join '&', $callback, "sort=$col", map { "$_=" . E($form->{$_}) } qw(revers lastsort);
 
 437   map { $column_defs{$_}->{link} = "${callback}&sort=$_" } @sort_no_revers;
 
 439   # add order to callback
 
 440   $form->{callback} = join '&', ($callback, map { "${_}=" . E($form->{$_}) } qw(sort revers));
 
 442   my $report = SL::ReportGenerator->new(\%myconfig, $form);
 
 444   my %attachment_basenames = (
 
 445     'part'     => $locale->text('part_list'),
 
 446     'service'  => $locale->text('service_list'),
 
 447     'assembly' => $locale->text('assembly_list'),
 
 448     'article'  => $locale->text('article_list'),
 
 451   $report->set_options('raw_top_info_text'     => $form->parse_html_template('ic/generate_report_top', { options => \@options }),
 
 452                        'raw_bottom_info_text'  => $form->parse_html_template('ic/generate_report_bottom' ,
 
 453                                                   { PART_CLASSIFICATIONS => SL::DB::Manager::PartClassification->get_all_sorted }),
 
 454                        'output_format'         => 'HTML',
 
 455                        'title'                 => $form->{title},
 
 456                        'attachment_basename'   => 'article_list' . strftime('_%Y%m%d', localtime time),
 
 458   $report->set_options_from_form();
 
 459   $locale->set_numberformat_wo_thousands_separator(\%myconfig) if lc($report->{options}->{output_format}) eq 'csv';
 
 461   $report->set_columns(%column_defs);
 
 462   $report->set_column_order(@columns);
 
 464   $report->set_export_options('generate_report', @hidden_variables, qw(sort revers));
 
 466   $report->set_sort_indicator($form->{sort}, $form->{revers} ? 0 : 1);
 
 468   CVar->add_custom_variables_to_report('module'         => 'IC',
 
 469                                        'trans_id_field' => 'id',
 
 470                                        'configs'        => $cvar_configs,
 
 471                                        'column_defs'    => \%column_defs,
 
 472                                        'data'           => $form->{parts});
 
 474   CVar->add_custom_variables_to_report('module'         => 'IC',
 
 475                                        'sub_module'     => sub { $_[0]->{ioi} },
 
 476                                        'trans_id_field' => 'ioi_id',
 
 477                                        'configs'        => $cvar_configs,
 
 478                                        'column_defs'    => \%column_defs,
 
 479                                        'data'           => $form->{parts});
 
 481   my @subtotal_columns = qw(sellprice listprice lastcost);
 
 482   my %subtotals = map { $_ => 0 } ('onhand', @subtotal_columns);
 
 483   my %totals    = map { $_ => 0 } @subtotal_columns;
 
 485   my $same_item = @{ $form->{parts} } ? $form->{parts}[0]{ $form->{sort} } : undef;
 
 487   my $defaults  = AM->get_defaults();
 
 490   foreach my $ref (@{ $form->{parts} }) {
 
 492     # fresh row, for inserting later
 
 493     my $row = { map { $_ => { 'data' => $ref->{$_} } } @columns };
 
 495     $ref->{exchangerate} ||= 1;
 
 496     $ref->{price_factor} ||= 1;
 
 497     $ref->{sellprice}     *= $ref->{exchangerate} / $ref->{price_factor};
 
 498     $ref->{listprice}     *= $ref->{exchangerate} / $ref->{price_factor};
 
 499     $ref->{lastcost}      *= $ref->{exchangerate} / $ref->{price_factor};
 
 501     # use this for assemblies
 
 502     my $soldtotal = $bsooqr_mode ? $ref->{soldtotal} : $ref->{onhand};
 
 504     if ($ref->{assemblyitem}) {
 
 505       $row->{partnumber}{align}   = 'right';
 
 506       $row->{soldtotal}{data}     = 0;
 
 507       $soldtotal                  = 0 if ($form->{sold});
 
 510     my $edit_link               = build_std_url('script=controller.pl', 'action=Part/edit', 'part.id=' . E($ref->{id}));
 
 511     $row->{partnumber}->{link}  = $edit_link;
 
 512     $row->{description}->{link} = $edit_link;
 
 514     foreach (qw(sellprice listprice lastcost)) {
 
 515       $row->{$_}{data}            = $form->format_amount(\%myconfig, $ref->{$_}, 2);
 
 516       $row->{"linetotal$_"}{data} = $form->format_amount(\%myconfig, $ref->{onhand} * $ref->{$_}, 2);
 
 518     foreach ( @pricegroup_columns ) {
 
 519       $row->{$_}{data}            = $form->format_amount(\%myconfig, $ref->{"$_"}, 2);
 
 523     map { $row->{$_}{data} = $form->format_amount(\%myconfig, $ref->{$_}); } qw(onhand rop weight soldtotal);
 
 525     $row->{weight}->{data} .= ' ' . $defaults->{weightunit};
 
 527     # 'yes' and 'no' for boolean value shop
 
 528     if ($form->{l_shop}) {
 
 529       $row->{shop}{data} = $row->{shop}{data}? $::locale->text('yes') : $::locale->text('no');
 
 532     if (!$ref->{assemblyitem}) {
 
 533       foreach my $col (@subtotal_columns) {
 
 534         $totals{$col}    += $soldtotal * $ref->{$col};
 
 535         $subtotals{$col} += $soldtotal * $ref->{$col};
 
 538       $subtotals{soldtotal} += $soldtotal;
 
 542     if ($ref->{module} eq 'oe') {
 
 543       # für oe gibt es vier fälle, jeweils nach kunde oder lieferant unterschiedlich:
 
 545       # | ist bestellt  | Von Kunden bestellt |  -> edit_oe_ord_link
 
 546       # | Anfrage       | Angebot             |  -> edit_oe_quo_link
 
 548       my $edit_oe_ord_link = ($::instance_conf->get_feature_experimental_order)
 
 549                            ? build_std_url("script=controller.pl", 'action=Order/edit',
 
 550                                            'type=' . E($ref->{cv} eq 'vendor' ? 'purchase_order' : 'sales_order'),        'id=' . E($ref->{trans_id}), 'callback')
 
 551                            : build_std_url("script=oe.pl",         'action=edit',
 
 552                                            'type=' . E($ref->{cv} eq 'vendor' ? 'purchase_order' : 'sales_order'),        'id=' . E($ref->{trans_id}), 'callback');
 
 554       my $edit_oe_quo_link = ($::instance_conf->get_feature_experimental_order)
 
 555                            ? build_std_url("script=controller.pl", 'action=Order/edit',
 
 556                                            'type=' . E($ref->{cv} eq 'vendor' ? 'request_quotation' : 'sales_quotation'), 'id=' . E($ref->{trans_id}), 'callback')
 
 557                            : build_std_url("script=oe.pl",         'action=edit',
 
 558                                            'type=' . E($ref->{cv} eq 'vendor' ? 'request_quotation' : 'sales_quotation'), 'id=' . E($ref->{trans_id}), 'callback');
 
 560       $row->{ordnumber}{link} = $edit_oe_ord_link;
 
 561       $row->{quonumber}{link} = $edit_oe_quo_link if (!$ref->{ordnumber});
 
 564       $row->{invnumber}{link} = build_std_url("script=$ref->{module}.pl", 'action=edit', 'type=invoice', 'id=' . E($ref->{trans_id}), 'callback') if ($ref->{invnumber});
 
 567     # set properties of images
 
 568     if ($ref->{image} && (lc $report->{options}->{output_format} eq 'html')) {
 
 569       $row->{image}{data}     = '';
 
 570       $row->{image}{raw_data} = '<a href="' . H($ref->{image}) . '"><img src="' . H($ref->{image}) . '" height="32" border="0"></a>';
 
 572     map { $row->{$_}{link} = $ref->{$_} } qw(drawing microfiche);
 
 574     $row->{notes}{data} = SL::HTML::Util->strip($ref->{notes});
 
 575     $row->{type_and_classific}{data} = SL::Presenter::Part::type_abbreviation($ref->{part_type}).
 
 576                                        SL::Presenter::Part::classification_abbreviation($ref->{classification_id});
 
 578     $report->add_data($row);
 
 580     my $next_ref = $form->{parts}[$idx + 1];
 
 582     # insert subtotal rows
 
 583     if (($form->{l_subtotal} eq 'Y') &&
 
 585          (!$next_ref->{assemblyitem} && ($same_item ne $next_ref->{ $form->{sort} })))) {
 
 586       my $row = { map { $_ => { 'class' => 'listsubtotal', } } @columns };
 
 588       if ( !$form->{l_assembly} || !$form->{bom}) {
 
 589         $row->{soldtotal}->{data} = $form->format_amount(\%myconfig, $subtotals{soldtotal});
 
 592       map { $row->{"linetotal$_"}->{data} = $form->format_amount(\%myconfig, $subtotals{$_}, 2) } @subtotal_columns;
 
 593       map { $subtotals{$_} = 0 } ('soldtotal', @subtotal_columns);
 
 595       $report->add_data($row);
 
 597       $same_item = $next_ref->{ $form->{sort} };
 
 603   if ($form->{"l_linetotal"} && !$form->{report_generator_csv_options_for_import}) {
 
 604     my $row = { map { $_ => { 'class' => 'listtotal', } } @columns };
 
 606     map { $row->{"linetotal$_"}->{data} = $form->format_amount(\%myconfig, $totals{$_}, 2) } @subtotal_columns;
 
 608     $report->add_separator();
 
 609     $report->add_data($row);
 
 612   setup_ic_generate_report_action_bar();
 
 613   $report->generate_with_headers();
 
 615   $lxdebug->leave_sub();
 
 616 }    #end generate_report
 
 618 sub setup_ic_search_action_bar {
 
 621   for my $bar ($::request->layout->get('actionbar')) {
 
 625         submit    => [ '#form', { action => 'generate_report' } ],
 
 626         accesskey => 'enter',
 
 631         submit => [ '#form', { action => 'top100' } ],
 
 637 sub setup_ic_generate_report_action_bar {
 
 640   for my $bar ($::request->layout->get('actionbar')) {
 
 648           submit    => [ '#new_form', { action => 'Part/add_part' } ],
 
 649           accesskey => 'enter',
 
 653           submit    => [ '#new_form', { action => 'Part/add_service' } ],
 
 657           submit    => [ '#new_form', { action => 'Part/add_assembly' } ],
 
 660           t8('Add Assortment'),
 
 661           submit    => [ '#new_form', { action => 'Part/add_assortment' } ],
 
 663       ], # end of combobox "Add part"