7b77ecda82810380a877493c9e9cf0283f553c52
[kivitendo-erp.git] / bin / mozilla / ic.pl
1 #=====================================================================
2 # LX-Office ERP
3 # Copyright (C) 2004
4 # Based on SQL-Ledger Version 2.1.9
5 # Web http://www.lx-office.org
6 #
7 #=====================================================================
8 # SQL-Ledger, Accounting
9 # Copyright (c) 2001
10 #
11 #  Author: Dieter Simader
12 #   Email: dsimader@sql-ledger.org
13 #     Web: http://www.sql-ledger.org
14 #
15 #
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.
20 #
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,
28 # MA 02110-1335, USA.
29 #======================================================================
30 #
31 # Inventory Control module
32 #
33 #======================================================================
34
35 use POSIX qw(strftime);
36 use List::Util qw(first max);
37 use List::MoreUtils qw(any);
38
39 use SL::AM;
40 use SL::CVar;
41 use SL::IC;
42 use SL::Helper::Flash qw(flash);
43 use SL::HTML::Util;
44 use SL::Presenter::Part;
45 use SL::ReportGenerator;
46
47 #use SL::PE;
48
49 use strict;
50 #use warnings;
51
52 # global imports
53 our ($form, $locale, %myconfig, $lxdebug, $auth);
54
55 require "bin/mozilla/io.pl";
56 require "bin/mozilla/common.pl";
57 require "bin/mozilla/reportgenerator.pl";
58
59 1;
60
61 # Parserhappy(R):
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');
76
77 # end of main
78
79 sub search {
80   $lxdebug->enter_sub();
81
82   $auth->assert('part_service_assembly_details');
83
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
87
88   $form->{title} = (ucfirst $form->{searchitems}) . "s";
89   $form->{title} =~ s/ys$/ies/;
90   $form->{title} = $locale->text($form->{title});
91
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');
97
98   setup_ic_search_action_bar();
99   $form->header;
100
101   $form->get_lists('partsgroup'    => 'ALL_PARTSGROUPS');
102   print $form->parse_html_template('ic/search');
103
104   $lxdebug->leave_sub();
105 }    #end search()
106
107 sub top100 {
108   $::lxdebug->enter_sub();
109
110   $::auth->assert('part_service_assembly_edit');
111
112   $::form->{l_soldtotal} = "Y";
113   $::form->{sort}        = "soldtotal";
114   $::form->{lastsort}    = "soldtotal";
115
116   $::form->{l_qty}       = undef;
117   $::form->{l_linetotal} = undef;
118   $::form->{l_number}    = "Y";
119   $::form->{number}      = "position";
120
121   unless (   $::form->{bought}
122           || $::form->{sold}
123           || $::form->{rfq}
124           || $::form->{quoted}) {
125     $::form->{bought} = $::form->{sold} = 1;
126   }
127
128   generate_report();
129
130   $lxdebug->leave_sub();
131 }
132
133 #
134 # Report for Wares.
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
137 #
138 # flags coming from the form:
139 # hardcoded:
140 #  searchitems=part revers=0 lastsort=''
141 #
142 # filter:
143 # partnumber ean description partsgroup classification serialnumber make model drawing microfiche
144 # transdatefrom transdateto
145 #
146 # radio:
147 #  itemstatus = active | onhand | short | obsolete | orphaned
148 #  action     = continue | top100
149 #
150 # checkboxes:
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
155 #
156 # hiddens:
157 #  nextsub revers lastsort sort ndxs_counter
158 #
159 sub generate_report {
160   $lxdebug->enter_sub();
161
162   $auth->assert('part_service_assembly_details');
163
164   my ($revers, $lastsort, $description);
165
166   my $cvar_configs = CVar->get_configs('module' => 'IC');
167
168   $form->{title} = $locale->text('Articles');
169
170   my %column_defs = (
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'), },
208   );
209
210   $revers     = $form->{revers};
211   $lastsort   = $form->{lastsort};
212
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)) {
216     $form->{revers}   = 0;
217     $form->{lastsort} = "partnumber";
218     $form->{sort}     = "partnumber";
219   } else {
220     if ($form->{lastsort} eq $form->{sort}) {
221       $form->{revers} = 1 - $form->{revers};
222     } else {
223       $form->{revers} = 0;
224       $form->{lastsort} = $form->{sort};
225     }    #fi
226   }    #fi
227
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}));
234
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});
241
242   # if something should be activated if something else is active, enter it here
243   my %dependencies = (
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) ],
255   );
256
257   # get name of partsgroup if id is given
258   my $pg_name;
259   if ($form->{partsgroup_id}) {
260     my $pg = SL::DB::PartsGroup->new(id => $form->{partsgroup_id})->load;
261     $pg_name = $pg->{'partsgroup'};
262   }
263
264   # these strings get displayed at the top of the results to indicate the user which switches were used
265   my %optiontexts = (
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'),
297   );
298
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);
303
304   # calculate dependencies
305   for (@itemstatus_keys, @callback_keys) {
306     next if ($form->{itemstatus} ne $_ && !$form->{$_});
307     map { $form->{$_} = 'Y' } @{ $dependencies{$_} } if $dependencies{$_};
308   }
309
310   # generate callback and optionstrings
311   my @options;
312   for my  $key (@itemstatus_keys, @callback_keys) {
313     next if ($form->{itemstatus} ne $key && !$form->{$key});
314     push @options, $optiontexts{$key};
315   }
316
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} = ""
323   }
324
325   if ($form->{description}) {
326     $description = $form->{description};
327     $description =~ s/\n/<br>/g;
328   }
329
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};
335   }
336   $form->{"l_type_and_classific"} = "Y";
337
338   if ($form->{l_service} && !$form->{l_assembly} && !$form->{l_part}) {
339
340     # remove warehouse, bin, weight and rop from list
341     map { $form->{"l_$_"} = "" } qw(bin weight rop warehouse);
342
343     $form->{l_onhand} = "";
344
345     # qty is irrelevant unless bought or sold
346     if (   $form->{bought}
347         || $form->{sold}
348         || $form->{onorder}
349         || $form->{ordered}
350         || $form->{rfq}
351         || $form->{quoted}) {
352 #      $form->{l_onhand} = "Y";
353     } else {
354       $form->{l_linetotalsellprice} = "";
355       $form->{l_linetotallastcost}  = "";
356     }
357   }
358
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
361   # ...
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';
369
370     push @options, $::locale->text('Subtotal cannot distinguish betweens record types. Only one of the selected record types will be displayed: #1', $optiontexts{$enabled});
371   }
372   if ($form->{l_soldtotal} && !$bsooqr_mode) {
373     delete $form->{l_soldtotal};
374
375     flash('warning', $::locale->text('Soldtotal does not make sense without any bsooqr options'));
376   }
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.'));
380   }
381   if ($form->{l_name} && !$bsooqr_mode) {
382     delete $form->{l_name};
383
384     flash('warning', $::locale->text('Name does not make sense without any bsooqr options'));
385   }
386   IC->all_parts(\%myconfig, \%$form);
387
388   my @columns = qw(
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
394     insertdate shop
395   );
396
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,
405         visible => 1,
406       },
407     }  @{ $pricegroups };
408   }
409   push @columns, @pricegroup_columns;
410
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;
414
415   push @columns, map { "cvar_$_->{name}" } @includeable_custom_variables;
416
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;
420
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),
424     @itemstatus_keys,
425     @callback_keys,
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),
431   );
432
433   my $callback         = build_std_url('action=generate_report', grep { $form->{$_} } @hidden_variables);
434
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);
437
438   foreach my $col (@sort_full) {
439     $column_defs{$col}->{link} = join '&', $callback, "sort=$col", map { "$_=" . E($form->{$_}) } qw(revers lastsort);
440   }
441   map { $column_defs{$_}->{link} = "${callback}&sort=$_" } @sort_no_revers;
442
443   # add order to callback
444   $form->{callback} = join '&', ($callback, map { "${_}=" . E($form->{$_}) } qw(sort revers));
445
446   my $report = SL::ReportGenerator->new(\%myconfig, $form);
447
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'),
453   );
454
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),
461   );
462   $report->set_options_from_form();
463   $locale->set_numberformat_wo_thousands_separator(\%myconfig) if lc($report->{options}->{output_format}) eq 'csv';
464
465   $report->set_columns(%column_defs);
466   $report->set_column_order(@columns);
467
468   $report->set_export_options('generate_report', @hidden_variables, qw(sort revers));
469
470   $report->set_sort_indicator($form->{sort}, $form->{revers} ? 0 : 1);
471
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});
477
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});
484
485   my @subtotal_columns = qw(sellprice listprice lastcost);
486   my %subtotals = map { $_ => 0 } ('onhand', @subtotal_columns);
487   my %totals    = map { $_ => 0 } @subtotal_columns;
488   my $idx       = 0;
489   my $same_item = @{ $form->{parts} } ? $form->{parts}[0]{ $form->{sort} } : undef;
490
491   my $defaults  = AM->get_defaults();
492
493   # postprocess parts
494   foreach my $ref (@{ $form->{parts} }) {
495
496     # fresh row, for inserting later
497     my $row = { map { $_ => { 'data' => $ref->{$_} } } @columns };
498
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};
504
505     # use this for assemblies
506     my $soldtotal = $bsooqr_mode ? $ref->{soldtotal} : $ref->{onhand};
507
508     if ($ref->{assemblyitem}) {
509       $row->{partnumber}{align}   = 'right';
510       $row->{soldtotal}{data}     = 0;
511       $soldtotal                  = 0 if ($form->{sold});
512     }
513
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;
517
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);
521     }
522     foreach ( @pricegroup_columns ) {
523       $row->{$_}{data}            = $form->format_amount(\%myconfig, $ref->{"$_"}, 2);
524     };
525
526
527     map { $row->{$_}{data} = $form->format_amount(\%myconfig, $ref->{$_}); } qw(onhand rop weight soldtotal);
528
529     $row->{weight}->{data} .= ' ' . $defaults->{weightunit};
530
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');
534     }
535
536     if (!$ref->{assemblyitem}) {
537       foreach my $col (@subtotal_columns) {
538         $totals{$col}    += $soldtotal * $ref->{$col};
539         $subtotals{$col} += $soldtotal * $ref->{$col};
540       }
541
542       $subtotals{soldtotal} += $soldtotal;
543     }
544
545     # set module stuff
546     if ($ref->{module} eq 'oe') {
547       # für oe gibt es vier fälle, jeweils nach kunde oder lieferant unterschiedlich:
548       #
549       # | ist bestellt  | Von Kunden bestellt |  -> edit_oe_ord_link
550       # | Anfrage       | Angebot             |  -> edit_oe_quo_link
551
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');
557
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');
563
564       $row->{ordnumber}{link} = $edit_oe_ord_link;
565       $row->{quonumber}{link} = $edit_oe_quo_link if (!$ref->{ordnumber});
566
567     } else {
568       $row->{invnumber}{link} = build_std_url("script=$ref->{module}.pl", 'action=edit', 'type=invoice', 'id=' . E($ref->{trans_id}), 'callback') if ($ref->{invnumber});
569     }
570
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>';
575     }
576     map { $row->{$_}{link} = $ref->{$_} } qw(drawing microfiche);
577
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});
581
582     $report->add_data($row);
583
584     my $next_ref = $form->{parts}[$idx + 1];
585
586     # insert subtotal rows
587     if (($form->{l_subtotal} eq 'Y') &&
588         (!$next_ref ||
589          (!$next_ref->{assemblyitem} && ($same_item ne $next_ref->{ $form->{sort} })))) {
590       my $row = { map { $_ => { 'class' => 'listsubtotal', } } @columns };
591
592       if ( !$form->{l_assembly} || !$form->{bom}) {
593         $row->{soldtotal}->{data} = $form->format_amount(\%myconfig, $subtotals{soldtotal});
594       }
595
596       map { $row->{"linetotal$_"}->{data} = $form->format_amount(\%myconfig, $subtotals{$_}, 2) } @subtotal_columns;
597       map { $subtotals{$_} = 0 } ('soldtotal', @subtotal_columns);
598
599       $report->add_data($row);
600
601       $same_item = $next_ref->{ $form->{sort} };
602     }
603
604     $idx++;
605   }
606
607   if ($form->{"l_linetotal"} && !$form->{report_generator_csv_options_for_import}) {
608     my $row = { map { $_ => { 'class' => 'listtotal', } } @columns };
609
610     map { $row->{"linetotal$_"}->{data} = $form->format_amount(\%myconfig, $totals{$_}, 2) } @subtotal_columns;
611
612     $report->add_separator();
613     $report->add_data($row);
614   }
615
616   setup_ic_generate_report_action_bar();
617   $report->generate_with_headers();
618
619   $lxdebug->leave_sub();
620 }    #end generate_report
621
622 sub setup_ic_search_action_bar {
623   my %params = @_;
624
625   for my $bar ($::request->layout->get('actionbar')) {
626     $bar->add(
627       action => [
628         t8('Search'),
629         submit    => [ '#form', { action => 'generate_report' } ],
630         accesskey => 'enter',
631       ],
632
633       action => [
634         t8('TOP100'),
635         submit => [ '#form', { action => 'top100' } ],
636       ],
637     );
638   }
639 }
640
641 sub setup_ic_generate_report_action_bar {
642   my %params = @_;
643
644   for my $bar ($::request->layout->get('actionbar')) {
645     $bar->add(
646       combobox => [
647         action => [
648           t8('Add'),
649         ],
650         action => [
651           t8('Add Part'),
652           submit    => [ '#new_form', { action => 'Part/add_part' } ],
653           accesskey => 'enter',
654         ],
655         action => [
656           t8('Add Service'),
657           submit    => [ '#new_form', { action => 'Part/add_service' } ],
658         ],
659         action => [
660           t8('Add Assembly'),
661           submit    => [ '#new_form', { action => 'Part/add_assembly' } ],
662         ],
663         action => [
664           t8('Add Assortment'),
665           submit    => [ '#new_form', { action => 'Part/add_assortment' } ],
666         ],
667       ], # end of combobox "Add part"
668     );
669   }
670 }