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_order)
 
 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_order)
 
 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"