Artikelbericht (IC.pm): Standardlager und Standardlagerplatz optional anzeigen
[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::ReportGenerator;
45
46 #use SL::PE;
47
48 use strict;
49 #use warnings;
50
51 # global imports
52 our ($form, $locale, %myconfig, $lxdebug, $auth);
53
54 require "bin/mozilla/io.pl";
55 require "bin/mozilla/common.pl";
56 require "bin/mozilla/reportgenerator.pl";
57
58 1;
59
60 # Parserhappy(R):
61 # type=submit $locale->text('Add Part')
62 # type=submit $locale->text('Add Service')
63 # type=submit $locale->text('Add Assembly')
64 # type=submit $locale->text('Edit Part')
65 # type=submit $locale->text('Edit Service')
66 # type=submit $locale->text('Edit Assembly')
67 # $locale->text('Parts')
68 # $locale->text('Services')
69 # $locale->text('Inventory quantity must be zero before you can set this part obsolete!')
70 # $locale->text('Inventory quantity must be zero before you can set this assembly obsolete!')
71 # $locale->text('Part Number missing!')
72 # $locale->text('Service Number missing!')
73 # $locale->text('Assembly Number missing!')
74 # $locale->text('ea');
75
76 # end of main
77
78 sub search {
79   $lxdebug->enter_sub();
80
81   $auth->assert('part_service_assembly_details');
82
83   $form->{revers}       = 0;  # switch for backward sorting
84   $form->{lastsort}     = ""; # memory for which table was sort at last time
85   $form->{ndxs_counter} = 0;  # counter for added entries to top100
86
87   $form->{title} = (ucfirst $form->{searchitems}) . "s";
88   $form->{title} =~ s/ys$/ies/;
89   $form->{title} = $locale->text($form->{title});
90
91   $form->{CUSTOM_VARIABLES}                  = CVar->get_configs('module' => 'IC');
92   ($form->{CUSTOM_VARIABLES_FILTER_CODE},
93    $form->{CUSTOM_VARIABLES_INCLUSION_CODE}) = CVar->render_search_options('variables'      => $form->{CUSTOM_VARIABLES},
94                                                                            'include_prefix' => 'l_',
95                                                                            'include_value'  => 'Y');
96
97   setup_ic_search_action_bar();
98   $form->header;
99
100   $form->get_lists('partsgroup'    => 'ALL_PARTSGROUPS');
101   print $form->parse_html_template('ic/search');
102
103   $lxdebug->leave_sub();
104 }    #end search()
105
106 sub top100 {
107   $::lxdebug->enter_sub();
108
109   $::auth->assert('part_service_assembly_edit');
110
111   $::form->{l_soldtotal} = "Y";
112   $::form->{sort}        = "soldtotal";
113   $::form->{lastsort}    = "soldtotal";
114
115   $::form->{l_qty}       = undef;
116   $::form->{l_linetotal} = undef;
117   $::form->{l_number}    = "Y";
118   $::form->{number}      = "position";
119
120   unless (   $::form->{bought}
121           || $::form->{sold}
122           || $::form->{rfq}
123           || $::form->{quoted}) {
124     $::form->{bought} = $::form->{sold} = 1;
125   }
126
127   generate_report();
128
129   $lxdebug->leave_sub();
130 }
131
132 #
133 # Report for Wares.
134 # Warning, deep magic ahead.
135 # This function parses the requested details, sanity checks them, and converts them into a format thats usable for IC->all_parts
136 #
137 # flags coming from the form:
138 # hardcoded:
139 #  searchitems=part revers=0 lastsort=''
140 #
141 # filter:
142 # partnumber ean description partsgroup classification serialnumber make model drawing microfiche
143 # transdatefrom transdateto
144 #
145 # radio:
146 #  itemstatus = active | onhand | short | obsolete | orphaned
147 #  action     = continue | top100
148 #
149 # checkboxes:
150 #  bought sold onorder ordered rfq quoted
151 #  l_partnumber l_description l_serialnumber l_unit l_listprice l_sellprice l_lastcost
152 #  l_linetotal l_priceupdate l_bin l_rop l_weight l_image l_drawing l_microfiche
153 #  l_partsgroup l_subtotal l_soldtotal l_deliverydate l_pricegroups
154 #
155 # hiddens:
156 #  nextsub revers lastsort sort ndxs_counter
157 #
158 sub generate_report {
159   $lxdebug->enter_sub();
160
161   $auth->assert('part_service_assembly_details');
162
163   my ($revers, $lastsort, $description);
164
165   my $cvar_configs = CVar->get_configs('module' => 'IC');
166
167   $form->{title} = $locale->text('Articles');
168
169   my %column_defs = (
170     'deliverydate'       => { 'text' => $locale->text('deliverydate'), },
171     'description'        => { 'text' => $locale->text('Part Description'), },
172     'notes'              => { 'text' => $locale->text('Notes'), },
173     'drawing'            => { 'text' => $locale->text('Drawing'), },
174     'ean'                => { 'text' => $locale->text('EAN'), },
175     'image'              => { 'text' => $locale->text('Image'), },
176     'insertdate'         => { 'text' => $locale->text('Insert Date'), },
177     'invnumber'          => { 'text' => $locale->text('Invoice Number'), },
178     'lastcost'           => { 'text' => $locale->text('Last Cost'), },
179     'linetotallastcost'  => { 'text' => $locale->text('Extended'), },
180     'linetotallistprice' => { 'text' => $locale->text('Extended'), },
181     'linetotalsellprice' => { 'text' => $locale->text('Extended'), },
182     'listprice'          => { 'text' => $locale->text('List Price'), },
183     'microfiche'         => { 'text' => $locale->text('Microfiche'), },
184     'name'               => { 'text' => $locale->text('Name'), },
185     'onhand'             => { 'text' => $locale->text('Stocked Qty'), },
186     'ordnumber'          => { 'text' => $locale->text('Order Number'), },
187     'partnumber'         => { 'text' => $locale->text('Part Number'), },
188     'partsgroup'         => { 'text' => $locale->text('Partsgroup'), },
189     'priceupdate'        => { 'text' => $locale->text('Updated'), },
190     'quonumber'          => { 'text' => $locale->text('Quotation'), },
191     'rop'                => { 'text' => $locale->text('ROP'), },
192     'sellprice'          => { 'text' => $locale->text('Sell Price'), },
193     'serialnumber'       => { 'text' => $locale->text('Serial Number'), },
194     'soldtotal'          => { 'text' => $locale->text('Qty in Selected Records'), },
195     'name'               => { 'text' => $locale->text('Name in Selected Records'), },
196     'transdate'          => { 'text' => $locale->text('Transdate Record'), },
197     'unit'               => { 'text' => $locale->text('Unit'), },
198     'weight'             => { 'text' => $locale->text('Weight'), },
199     'shop'               => { 'text' => $locale->text('Shop article'), },
200     'type_and_classific' => { 'text' => $locale->text('Type'), },
201     'projectnumber'      => { 'text' => $locale->text('Project Number'), },
202     'projectdescription' => { 'text' => $locale->text('Project Description'), },
203     'warehouse'          => { 'text' => $locale->text('Default Warehouse'), },
204     'bin'                => { 'text' => $locale->text('Default Bin'), },
205   );
206
207   $revers     = $form->{revers};
208   $lastsort   = $form->{lastsort};
209
210   # sorting and direction of sorting
211   # ToDO: change this to the simpler field+direction method
212   if (($form->{lastsort} eq "") && ($form->{sort} eq undef)) {
213     $form->{revers}   = 0;
214     $form->{lastsort} = "partnumber";
215     $form->{sort}     = "partnumber";
216   } else {
217     if ($form->{lastsort} eq $form->{sort}) {
218       $form->{revers} = 1 - $form->{revers};
219     } else {
220       $form->{revers} = 0;
221       $form->{lastsort} = $form->{sort};
222     }    #fi
223   }    #fi
224
225   # special case if we have a serialnumber limit search
226   # serialnumbers are only given in invoices and orders,
227   # so they can only pop up in bought, sold, rfq, and quoted stuff
228   $form->{no_sn_joins} = 'Y' if (   !$form->{bought} && !$form->{sold}
229                                  && !$form->{rfq}    && !$form->{quoted}
230                                  && ($form->{l_serialnumber} || $form->{serialnumber}));
231
232   # special case for any checkbox of bought | sold | onorder | ordered | rfq | quoted.
233   # if any of these are ticked the behavior changes slightly for lastcost
234   # since all those are aggregation checks for the legder tables this is an internal switch
235   # refered to as ledgerchecks
236   $form->{ledgerchecks} = 'Y' if (   $form->{bought} || $form->{sold} || $form->{onorder}
237                                   || $form->{ordered} || $form->{rfq} || $form->{quoted});
238
239   # if something should be activated if something else is active, enter it here
240   my %dependencies = (
241     onhand       => [ qw(l_onhand) ],
242     short        => [ qw(l_onhand) ],
243     onorder      => [ qw(l_ordnumber) ],
244     ordered      => [ qw(l_ordnumber) ],
245     rfq          => [ qw(l_quonumber) ],
246     quoted       => [ qw(l_quonumber) ],
247     bought       => [ qw(l_invnumber) ],
248     sold         => [ qw(l_invnumber) ],
249     ledgerchecks => [ qw(l_name) ],
250     serialnumber => [ qw(l_serialnumber) ],
251     no_sn_joins  => [ qw(bought sold) ],
252   );
253
254   # get name of partsgroup if id is given
255   my $pg_name;
256   if ($form->{partsgroup_id}) {
257     my $pg = SL::DB::PartsGroup->new(id => $form->{partsgroup_id})->load;
258     $pg_name = $pg->{'partsgroup'};
259   }
260
261   # these strings get displayed at the top of the results to indicate the user which switches were used
262   my %optiontexts = (
263     active        => $locale->text('Active'),
264     obsolete      => $locale->text('Obsolete'),
265     orphaned      => $locale->text('Orphaned'),
266     onhand        => $locale->text('On Hand'),
267     short         => $locale->text('Short'),
268     onorder       => $locale->text('On Order'),
269     ordered       => $locale->text('Ordered'),
270     rfq           => $locale->text('RFQ'),
271     quoted        => $locale->text('Quoted'),
272     bought        => $locale->text('Bought'),
273     sold          => $locale->text('Sold'),
274     transdatefrom => $locale->text('From')       . " " . $locale->date(\%myconfig, $form->{transdatefrom}, 1),
275     transdateto   => $locale->text('To (time)')  . " " . $locale->date(\%myconfig, $form->{transdateto}, 1),
276     partnumber    => $locale->text('Part Number')      . ": '$form->{partnumber}'",
277     partsgroup    => $locale->text('Partsgroup')       . ": '$form->{partsgroup}'",
278     partsgroup_id => $locale->text('Partsgroup')       . ": '$pg_name'",
279     serialnumber  => $locale->text('Serial Number')    . ": '$form->{serialnumber}'",
280     description   => $locale->text('Part Description') . ": '$form->{description}'",
281     make          => $locale->text('Make')             . ": '$form->{make}'",
282     model         => $locale->text('Model')            . ": '$form->{model}'",
283     drawing       => $locale->text('Drawing')          . ": '$form->{drawing}'",
284     microfiche    => $locale->text('Microfiche')       . ": '$form->{microfiche}'",
285     l_soldtotal   => $locale->text('Qty in Selected Records'),
286     ean           => $locale->text('EAN')              . ": '$form->{ean}'",
287     insertdatefrom => $locale->text('Insert Date') . ": " . $locale->text('From')       . " " . $locale->date(\%myconfig, $form->{insertdatefrom}, 1),
288     insertdateto   => $locale->text('Insert Date') . ": " . $locale->text('To (time)')  . " " . $locale->date(\%myconfig, $form->{insertdateto}, 1),
289   );
290
291   my @itemstatus_keys = qw(active obsolete orphaned onhand short);
292   my @callback_keys   = qw(onorder ordered rfq quoted bought sold partnumber partsgroup partsgroup_id serialnumber description make model
293                            drawing microfiche l_soldtotal l_deliverydate transdatefrom transdateto insertdatefrom insertdateto ean shop all);
294
295   # calculate dependencies
296   for (@itemstatus_keys, @callback_keys) {
297     next if ($form->{itemstatus} ne $_ && !$form->{$_});
298     map { $form->{$_} = 'Y' } @{ $dependencies{$_} } if $dependencies{$_};
299   }
300
301   # generate callback and optionstrings
302   my @options;
303   for my  $key (@itemstatus_keys, @callback_keys) {
304     next if ($form->{itemstatus} ne $key && !$form->{$key});
305     push @options, $optiontexts{$key};
306   }
307
308   # special case for lastcost
309   if ($form->{ledgerchecks}){
310     # ledgerchecks don't know about sellprice or lastcost. they just return a
311     # price. so rename sellprice to price, and drop lastcost.
312     $column_defs{sellprice}{text} = $locale->text('Price');
313     $form->{l_lastcost} = ""
314   }
315
316   if ($form->{description}) {
317     $description = $form->{description};
318     $description =~ s/\n/<br>/g;
319   }
320
321   if ($form->{l_linetotal}) {
322     $form->{l_qty} = "Y";
323     $form->{l_linetotalsellprice} = "Y" if $form->{l_sellprice};
324     $form->{l_linetotallastcost}  = $form->{searchitems} eq 'assembly' && !$form->{bom} ? "" : 'Y' if  $form->{l_lastcost};
325     $form->{l_linetotallistprice} = "Y" if $form->{l_listprice};
326   }
327   $form->{"l_type_and_classific"} = "Y";
328
329   if ($form->{l_service} && !$form->{l_assembly} && !$form->{l_part}) {
330
331     # remove warehouse, bin, weight and rop from list
332     map { $form->{"l_$_"} = "" } qw(bin weight rop warehouse);
333
334     $form->{l_onhand} = "";
335
336     # qty is irrelevant unless bought or sold
337     if (   $form->{bought}
338         || $form->{sold}
339         || $form->{onorder}
340         || $form->{ordered}
341         || $form->{rfq}
342         || $form->{quoted}) {
343 #      $form->{l_onhand} = "Y";
344     } else {
345       $form->{l_linetotalsellprice} = "";
346       $form->{l_linetotallastcost}  = "";
347     }
348   }
349
350   # soldtotal doesn't make sense with more than one bsooqr option.
351   # so reset it to sold (the most common option), and issue a warning
352   # ...
353   # also it doesn't make sense without bsooqr. disable and issue a warning too
354   my @bsooqr = qw(sold bought onorder ordered rfq quoted);
355   my $bsooqr_mode = grep { $form->{$_} } @bsooqr;
356   if ($form->{l_subtotal} && 1 < $bsooqr_mode) {
357     my $enabled       = first { $form->{$_} } @bsooqr;
358     $form->{$_}       = ''   for @bsooqr;
359     $form->{$enabled} = 'Y';
360
361     push @options, $::locale->text('Subtotal cannot distinguish betweens record types. Only one of the selected record types will be displayed: #1', $optiontexts{$enabled});
362   }
363   if ($form->{l_soldtotal} && !$bsooqr_mode) {
364     delete $form->{l_soldtotal};
365
366     flash('warning', $::locale->text('Soldtotal does not make sense without any bsooqr options'));
367   }
368   if ($form->{l_soldtotal} && ($form->{l_warehouse} || $form->{l_bin})) {
369     delete $form->{"l_$_"} for  qw(bin warehouse);
370     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.'));
371   }
372   if ($form->{l_name} && !$bsooqr_mode) {
373     delete $form->{l_name};
374
375     flash('warning', $::locale->text('Name does not make sense without any bsooqr options'));
376   }
377   IC->all_parts(\%myconfig, \%$form);
378
379   my @columns = qw(
380     partnumber type_and_classific description notes partsgroup warehouse bin
381     onhand rop soldtotal unit listprice
382     linetotallistprice sellprice linetotalsellprice lastcost linetotallastcost
383     priceupdate weight image drawing microfiche invnumber ordnumber quonumber
384     transdate name serialnumber deliverydate ean projectnumber projectdescription
385     insertdate shop
386   );
387
388   my $pricegroups = SL::DB::Manager::Pricegroup->get_all_sorted;
389   my @pricegroup_columns;
390   my %column_defs_pricegroups;
391   if ($form->{l_pricegroups}) {
392     @pricegroup_columns      = map { "pricegroup_" . $_->id } @{ $pricegroups };
393     %column_defs_pricegroups = map {
394       "pricegroup_" . $_->id => {
395         text    => $::locale->text('Pricegroup') . ' ' . $_->pricegroup,
396         visible => 1,
397       },
398     }  @{ $pricegroups };
399   }
400   push @columns, @pricegroup_columns;
401
402   my @includeable_custom_variables = grep { $_->{includeable} } @{ $cvar_configs };
403   my @searchable_custom_variables  = grep { $_->{searchable} }  @{ $cvar_configs };
404   my %column_defs_cvars            = map { +"cvar_$_->{name}" => { 'text' => $_->{description} } } @includeable_custom_variables;
405
406   push @columns, map { "cvar_$_->{name}" } @includeable_custom_variables;
407
408   %column_defs = (%column_defs, %column_defs_cvars, %column_defs_pricegroups);
409   map { $column_defs{$_}->{visible} ||= $form->{"l_$_"} ? 1 : 0 } @columns;
410   map { $column_defs{$_}->{align}   = 'right' } qw(onhand sellprice listprice lastcost linetotalsellprice linetotallastcost linetotallistprice rop weight soldtotal shop), @pricegroup_columns;
411
412   my @hidden_variables = (
413     qw(l_subtotal l_linetotal searchitems itemstatus bom l_pricegroups insertdatefrom insertdateto),
414     qw(l_type_and_classific classification_id l_part l_service l_assembly l_assortment),
415     @itemstatus_keys,
416     @callback_keys,
417     map({ "cvar_$_->{name}" } @searchable_custom_variables),
418     map({'cvar_'. $_->{name} .'_qtyop'} grep({$_->{type} eq 'number'} @searchable_custom_variables)),
419     map({ "l_$_" } @columns),
420   );
421
422   my $callback         = build_std_url('action=generate_report', grep { $form->{$_} } @hidden_variables);
423
424   my @sort_full        = qw(partnumber description onhand soldtotal deliverydate insertdate shop);
425   my @sort_no_revers   = qw(partsgroup priceupdate invnumber ordnumber quonumber name image drawing serialnumber);
426
427   foreach my $col (@sort_full) {
428     $column_defs{$col}->{link} = join '&', $callback, "sort=$col", map { "$_=" . E($form->{$_}) } qw(revers lastsort);
429   }
430   map { $column_defs{$_}->{link} = "${callback}&sort=$_" } @sort_no_revers;
431
432   # add order to callback
433   $form->{callback} = join '&', ($callback, map { "${_}=" . E($form->{$_}) } qw(sort revers));
434
435   my $report = SL::ReportGenerator->new(\%myconfig, $form);
436
437   my %attachment_basenames = (
438     'part'     => $locale->text('part_list'),
439     'service'  => $locale->text('service_list'),
440     'assembly' => $locale->text('assembly_list'),
441     'article'  => $locale->text('article_list'),
442   );
443
444   $report->set_options('raw_top_info_text'     => $form->parse_html_template('ic/generate_report_top', { options => \@options }),
445                        'raw_bottom_info_text'  => $form->parse_html_template('ic/generate_report_bottom' ,
446                                                   { PART_CLASSIFICATIONS => SL::DB::Manager::PartClassification->get_all_sorted }),
447                        'output_format'         => 'HTML',
448                        'title'                 => $form->{title},
449                        'attachment_basename'   => 'article_list' . strftime('_%Y%m%d', localtime time),
450   );
451   $report->set_options_from_form();
452   $locale->set_numberformat_wo_thousands_separator(\%myconfig) if lc($report->{options}->{output_format}) eq 'csv';
453
454   $report->set_columns(%column_defs);
455   $report->set_column_order(@columns);
456
457   $report->set_export_options('generate_report', @hidden_variables, qw(sort revers));
458
459   $report->set_sort_indicator($form->{sort}, $form->{revers} ? 0 : 1);
460
461   CVar->add_custom_variables_to_report('module'         => 'IC',
462                                        'trans_id_field' => 'id',
463                                        'configs'        => $cvar_configs,
464                                        'column_defs'    => \%column_defs,
465                                        'data'           => $form->{parts});
466
467   CVar->add_custom_variables_to_report('module'         => 'IC',
468                                        'sub_module'     => sub { $_[0]->{ioi} },
469                                        'trans_id_field' => 'ioi_id',
470                                        'configs'        => $cvar_configs,
471                                        'column_defs'    => \%column_defs,
472                                        'data'           => $form->{parts});
473
474   my @subtotal_columns = qw(sellprice listprice lastcost);
475   my %subtotals = map { $_ => 0 } ('onhand', @subtotal_columns);
476   my %totals    = map { $_ => 0 } @subtotal_columns;
477   my $idx       = 0;
478   my $same_item = @{ $form->{parts} } ? $form->{parts}[0]{ $form->{sort} } : undef;
479
480   my $defaults  = AM->get_defaults();
481
482   # postprocess parts
483   foreach my $ref (@{ $form->{parts} }) {
484
485     # fresh row, for inserting later
486     my $row = { map { $_ => { 'data' => $ref->{$_} } } @columns };
487
488     $ref->{exchangerate} ||= 1;
489     $ref->{price_factor} ||= 1;
490     $ref->{sellprice}     *= $ref->{exchangerate} / $ref->{price_factor};
491     $ref->{listprice}     *= $ref->{exchangerate} / $ref->{price_factor};
492     $ref->{lastcost}      *= $ref->{exchangerate} / $ref->{price_factor};
493
494     # use this for assemblies
495     my $soldtotal = $bsooqr_mode ? $ref->{soldtotal} : $ref->{onhand};
496
497     if ($ref->{assemblyitem}) {
498       $row->{partnumber}{align}   = 'right';
499       $row->{soldtotal}{data}     = 0;
500       $soldtotal                  = 0 if ($form->{sold});
501     }
502
503     my $edit_link               = build_std_url('script=controller.pl', 'action=Part/edit', 'part.id=' . E($ref->{id}), 'callback');
504     $row->{partnumber}->{link}  = $edit_link;
505     $row->{description}->{link} = $edit_link;
506
507     foreach (qw(sellprice listprice lastcost)) {
508       $row->{$_}{data}            = $form->format_amount(\%myconfig, $ref->{$_}, 2);
509       $row->{"linetotal$_"}{data} = $form->format_amount(\%myconfig, $ref->{onhand} * $ref->{$_}, 2);
510     }
511     foreach ( @pricegroup_columns ) {
512       $row->{$_}{data}            = $form->format_amount(\%myconfig, $ref->{"$_"}, 2);
513     };
514
515
516     map { $row->{$_}{data} = $form->format_amount(\%myconfig, $ref->{$_}); } qw(onhand rop weight soldtotal);
517
518     $row->{weight}->{data} .= ' ' . $defaults->{weightunit};
519
520     # 'yes' and 'no' for boolean value shop
521     if ($form->{l_shop}) {
522       $row->{shop}{data} = $row->{shop}{data}? $::locale->text('yes') : $::locale->text('no');
523     }
524
525     if (!$ref->{assemblyitem}) {
526       foreach my $col (@subtotal_columns) {
527         $totals{$col}    += $soldtotal * $ref->{$col};
528         $subtotals{$col} += $soldtotal * $ref->{$col};
529       }
530
531       $subtotals{soldtotal} += $soldtotal;
532     }
533
534     # set module stuff
535     if ($ref->{module} eq 'oe') {
536       # für oe gibt es vier fälle, jeweils nach kunde oder lieferant unterschiedlich:
537       #
538       # | ist bestellt  | Von Kunden bestellt |  -> edit_oe_ord_link
539       # | Anfrage       | Angebot             |  -> edit_oe_quo_link
540
541       my $edit_oe_ord_link = build_std_url("script=oe.pl", 'action=edit', 'type=' . E($ref->{cv} eq 'vendor' ? 'purchase_order' : 'sales_order'), 'id=' . E($ref->{trans_id}), 'callback');
542       my $edit_oe_quo_link = build_std_url("script=oe.pl", 'action=edit', 'type=' . E($ref->{cv} eq 'vendor' ? 'request_quotation' : 'sales_quotation'), 'id=' . E($ref->{trans_id}), 'callback');
543
544       $row->{ordnumber}{link} = $edit_oe_ord_link;
545       $row->{quonumber}{link} = $edit_oe_quo_link if (!$ref->{ordnumber});
546
547     } else {
548       $row->{invnumber}{link} = build_std_url("script=$ref->{module}.pl", 'action=edit', 'type=invoice', 'id=' . E($ref->{trans_id}), 'callback') if ($ref->{invnumber});
549     }
550
551     # set properties of images
552     if ($ref->{image} && (lc $report->{options}->{output_format} eq 'html')) {
553       $row->{image}{data}     = '';
554       $row->{image}{raw_data} = '<a href="' . H($ref->{image}) . '"><img src="' . H($ref->{image}) . '" height="32" border="0"></a>';
555     }
556     map { $row->{$_}{link} = $ref->{$_} } qw(drawing microfiche);
557
558     $row->{notes}{data} = SL::HTML::Util->strip($ref->{notes});
559     $row->{type_and_classific}{data} = $::request->presenter->type_abbreviation($ref->{part_type}).
560                                        $::request->presenter->classification_abbreviation($ref->{classification_id});
561
562     $report->add_data($row);
563
564     my $next_ref = $form->{parts}[$idx + 1];
565
566     # insert subtotal rows
567     if (($form->{l_subtotal} eq 'Y') &&
568         (!$next_ref ||
569          (!$next_ref->{assemblyitem} && ($same_item ne $next_ref->{ $form->{sort} })))) {
570       my $row = { map { $_ => { 'class' => 'listsubtotal', } } @columns };
571
572       if ( !$form->{l_assembly} || !$form->{bom}) {
573         $row->{soldtotal}->{data} = $form->format_amount(\%myconfig, $subtotals{soldtotal});
574       }
575
576       map { $row->{"linetotal$_"}->{data} = $form->format_amount(\%myconfig, $subtotals{$_}, 2) } @subtotal_columns;
577       map { $subtotals{$_} = 0 } ('soldtotal', @subtotal_columns);
578
579       $report->add_data($row);
580
581       $same_item = $next_ref->{ $form->{sort} };
582     }
583
584     $idx++;
585   }
586
587   if ($form->{"l_linetotal"} && !$form->{report_generator_csv_options_for_import}) {
588     my $row = { map { $_ => { 'class' => 'listtotal', } } @columns };
589
590     map { $row->{"linetotal$_"}->{data} = $form->format_amount(\%myconfig, $totals{$_}, 2) } @subtotal_columns;
591
592     $report->add_separator();
593     $report->add_data($row);
594   }
595
596   setup_ic_generate_report_action_bar();
597   $report->generate_with_headers();
598
599   $lxdebug->leave_sub();
600 }    #end generate_report
601
602 sub setup_ic_search_action_bar {
603   my %params = @_;
604
605   for my $bar ($::request->layout->get('actionbar')) {
606     $bar->add(
607       action => [
608         t8('Search'),
609         submit    => [ '#form', { action => 'generate_report' } ],
610         accesskey => 'enter',
611       ],
612
613       action => [
614         t8('TOP100'),
615         submit => [ '#form', { action => 'top100' } ],
616       ],
617     );
618   }
619 }
620
621 sub setup_ic_generate_report_action_bar {
622   my %params = @_;
623
624   for my $bar ($::request->layout->get('actionbar')) {
625     $bar->add(
626       combobox => [
627         action => [
628           t8('Add'),
629         ],
630         action => [
631           t8('Add Part'),
632           submit    => [ '#new_form', { action => 'Part/add_part' } ],
633           accesskey => 'enter',
634         ],
635         action => [
636           t8('Add Service'),
637           submit    => [ '#new_form', { action => 'Part/add_service' } ],
638         ],
639         action => [
640           t8('Add Assembly'),
641           submit    => [ '#new_form', { action => 'Part/add_assembly' } ],
642         ],
643         action => [
644           t8('Add Assortment'),
645           submit    => [ '#new_form', { action => 'Part/add_assortment' } ],
646         ],
647       ], # end of combobox "Add part"
648     );
649   }
650 }