Kunden-Spezifische Artikeleigenschaften
[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   );
207
208   $revers     = $form->{revers};
209   $lastsort   = $form->{lastsort};
210
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)) {
214     $form->{revers}   = 0;
215     $form->{lastsort} = "partnumber";
216     $form->{sort}     = "partnumber";
217   } else {
218     if ($form->{lastsort} eq $form->{sort}) {
219       $form->{revers} = 1 - $form->{revers};
220     } else {
221       $form->{revers} = 0;
222       $form->{lastsort} = $form->{sort};
223     }    #fi
224   }    #fi
225
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}));
232
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});
239
240   # if something should be activated if something else is active, enter it here
241   my %dependencies = (
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) ],
253   );
254
255   # get name of partsgroup if id is given
256   my $pg_name;
257   if ($form->{partsgroup_id}) {
258     my $pg = SL::DB::PartsGroup->new(id => $form->{partsgroup_id})->load;
259     $pg_name = $pg->{'partsgroup'};
260   }
261
262   # these strings get displayed at the top of the results to indicate the user which switches were used
263   my %optiontexts = (
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),
292   );
293
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);
297
298   # calculate dependencies
299   for (@itemstatus_keys, @callback_keys) {
300     next if ($form->{itemstatus} ne $_ && !$form->{$_});
301     map { $form->{$_} = 'Y' } @{ $dependencies{$_} } if $dependencies{$_};
302   }
303
304   # generate callback and optionstrings
305   my @options;
306   for my  $key (@itemstatus_keys, @callback_keys) {
307     next if ($form->{itemstatus} ne $key && !$form->{$key});
308     push @options, $optiontexts{$key};
309   }
310
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} = ""
317   }
318
319   if ($form->{description}) {
320     $description = $form->{description};
321     $description =~ s/\n/<br>/g;
322   }
323
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};
329   }
330   $form->{"l_type_and_classific"} = "Y";
331
332   if ($form->{l_service} && !$form->{l_assembly} && !$form->{l_part}) {
333
334     # remove warehouse, bin, weight and rop from list
335     map { $form->{"l_$_"} = "" } qw(bin weight rop warehouse);
336
337     $form->{l_onhand} = "";
338
339     # qty is irrelevant unless bought or sold
340     if (   $form->{bought}
341         || $form->{sold}
342         || $form->{onorder}
343         || $form->{ordered}
344         || $form->{rfq}
345         || $form->{quoted}) {
346 #      $form->{l_onhand} = "Y";
347     } else {
348       $form->{l_linetotalsellprice} = "";
349       $form->{l_linetotallastcost}  = "";
350     }
351   }
352
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
355   # ...
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';
363
364     push @options, $::locale->text('Subtotal cannot distinguish betweens record types. Only one of the selected record types will be displayed: #1', $optiontexts{$enabled});
365   }
366   if ($form->{l_soldtotal} && !$bsooqr_mode) {
367     delete $form->{l_soldtotal};
368
369     flash('warning', $::locale->text('Soldtotal does not make sense without any bsooqr options'));
370   }
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.'));
374   }
375   if ($form->{l_name} && !$bsooqr_mode) {
376     delete $form->{l_name};
377
378     flash('warning', $::locale->text('Name does not make sense without any bsooqr options'));
379   }
380   IC->all_parts(\%myconfig, \%$form);
381
382   my @columns = qw(
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
388     insertdate shop
389   );
390
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,
399         visible => 1,
400       },
401     }  @{ $pricegroups };
402   }
403   push @columns, @pricegroup_columns;
404
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;
408
409   push @columns, map { "cvar_$_->{name}" } @includeable_custom_variables;
410
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;
414
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),
418     @itemstatus_keys,
419     @callback_keys,
420     map({ "cvar_$_->{name}" } @searchable_custom_variables),
421     map({'cvar_'. $_->{name} .'_qtyop'} grep({$_->{type} eq 'number'} @searchable_custom_variables)),
422     map({ "l_$_" } @columns),
423   );
424
425   my $callback         = build_std_url('action=generate_report', grep { $form->{$_} } @hidden_variables);
426
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);
429
430   foreach my $col (@sort_full) {
431     $column_defs{$col}->{link} = join '&', $callback, "sort=$col", map { "$_=" . E($form->{$_}) } qw(revers lastsort);
432   }
433   map { $column_defs{$_}->{link} = "${callback}&sort=$_" } @sort_no_revers;
434
435   # add order to callback
436   $form->{callback} = join '&', ($callback, map { "${_}=" . E($form->{$_}) } qw(sort revers));
437
438   my $report = SL::ReportGenerator->new(\%myconfig, $form);
439
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'),
445   );
446
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),
453   );
454   $report->set_options_from_form();
455   $locale->set_numberformat_wo_thousands_separator(\%myconfig) if lc($report->{options}->{output_format}) eq 'csv';
456
457   $report->set_columns(%column_defs);
458   $report->set_column_order(@columns);
459
460   $report->set_export_options('generate_report', @hidden_variables, qw(sort revers));
461
462   $report->set_sort_indicator($form->{sort}, $form->{revers} ? 0 : 1);
463
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});
469
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});
476
477   my @subtotal_columns = qw(sellprice listprice lastcost);
478   my %subtotals = map { $_ => 0 } ('onhand', @subtotal_columns);
479   my %totals    = map { $_ => 0 } @subtotal_columns;
480   my $idx       = 0;
481   my $same_item = @{ $form->{parts} } ? $form->{parts}[0]{ $form->{sort} } : undef;
482
483   my $defaults  = AM->get_defaults();
484
485   # postprocess parts
486   foreach my $ref (@{ $form->{parts} }) {
487
488     # fresh row, for inserting later
489     my $row = { map { $_ => { 'data' => $ref->{$_} } } @columns };
490
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};
496
497     # use this for assemblies
498     my $soldtotal = $bsooqr_mode ? $ref->{soldtotal} : $ref->{onhand};
499
500     if ($ref->{assemblyitem}) {
501       $row->{partnumber}{align}   = 'right';
502       $row->{soldtotal}{data}     = 0;
503       $soldtotal                  = 0 if ($form->{sold});
504     }
505
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;
509
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);
513     }
514     foreach ( @pricegroup_columns ) {
515       $row->{$_}{data}            = $form->format_amount(\%myconfig, $ref->{"$_"}, 2);
516     };
517
518
519     map { $row->{$_}{data} = $form->format_amount(\%myconfig, $ref->{$_}); } qw(onhand rop weight soldtotal);
520
521     $row->{weight}->{data} .= ' ' . $defaults->{weightunit};
522
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');
526     }
527
528     if (!$ref->{assemblyitem}) {
529       foreach my $col (@subtotal_columns) {
530         $totals{$col}    += $soldtotal * $ref->{$col};
531         $subtotals{$col} += $soldtotal * $ref->{$col};
532       }
533
534       $subtotals{soldtotal} += $soldtotal;
535     }
536
537     # set module stuff
538     if ($ref->{module} eq 'oe') {
539       # für oe gibt es vier fälle, jeweils nach kunde oder lieferant unterschiedlich:
540       #
541       # | ist bestellt  | Von Kunden bestellt |  -> edit_oe_ord_link
542       # | Anfrage       | Angebot             |  -> edit_oe_quo_link
543
544       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');
545       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');
546
547       $row->{ordnumber}{link} = $edit_oe_ord_link;
548       $row->{quonumber}{link} = $edit_oe_quo_link if (!$ref->{ordnumber});
549
550     } else {
551       $row->{invnumber}{link} = build_std_url("script=$ref->{module}.pl", 'action=edit', 'type=invoice', 'id=' . E($ref->{trans_id}), 'callback') if ($ref->{invnumber});
552     }
553
554     # set properties of images
555     if ($ref->{image} && (lc $report->{options}->{output_format} eq 'html')) {
556       $row->{image}{data}     = '';
557       $row->{image}{raw_data} = '<a href="' . H($ref->{image}) . '"><img src="' . H($ref->{image}) . '" height="32" border="0"></a>';
558     }
559     map { $row->{$_}{link} = $ref->{$_} } qw(drawing microfiche);
560
561     $row->{notes}{data} = SL::HTML::Util->strip($ref->{notes});
562     $row->{type_and_classific}{data} = SL::Presenter::Part::type_abbreviation($ref->{part_type}).
563                                        SL::Presenter::Part::classification_abbreviation($ref->{classification_id});
564
565     $report->add_data($row);
566
567     my $next_ref = $form->{parts}[$idx + 1];
568
569     # insert subtotal rows
570     if (($form->{l_subtotal} eq 'Y') &&
571         (!$next_ref ||
572          (!$next_ref->{assemblyitem} && ($same_item ne $next_ref->{ $form->{sort} })))) {
573       my $row = { map { $_ => { 'class' => 'listsubtotal', } } @columns };
574
575       if ( !$form->{l_assembly} || !$form->{bom}) {
576         $row->{soldtotal}->{data} = $form->format_amount(\%myconfig, $subtotals{soldtotal});
577       }
578
579       map { $row->{"linetotal$_"}->{data} = $form->format_amount(\%myconfig, $subtotals{$_}, 2) } @subtotal_columns;
580       map { $subtotals{$_} = 0 } ('soldtotal', @subtotal_columns);
581
582       $report->add_data($row);
583
584       $same_item = $next_ref->{ $form->{sort} };
585     }
586
587     $idx++;
588   }
589
590   if ($form->{"l_linetotal"} && !$form->{report_generator_csv_options_for_import}) {
591     my $row = { map { $_ => { 'class' => 'listtotal', } } @columns };
592
593     map { $row->{"linetotal$_"}->{data} = $form->format_amount(\%myconfig, $totals{$_}, 2) } @subtotal_columns;
594
595     $report->add_separator();
596     $report->add_data($row);
597   }
598
599   setup_ic_generate_report_action_bar();
600   $report->generate_with_headers();
601
602   $lxdebug->leave_sub();
603 }    #end generate_report
604
605 sub setup_ic_search_action_bar {
606   my %params = @_;
607
608   for my $bar ($::request->layout->get('actionbar')) {
609     $bar->add(
610       action => [
611         t8('Search'),
612         submit    => [ '#form', { action => 'generate_report' } ],
613         accesskey => 'enter',
614       ],
615
616       action => [
617         t8('TOP100'),
618         submit => [ '#form', { action => 'top100' } ],
619       ],
620     );
621   }
622 }
623
624 sub setup_ic_generate_report_action_bar {
625   my %params = @_;
626
627   for my $bar ($::request->layout->get('actionbar')) {
628     $bar->add(
629       combobox => [
630         action => [
631           t8('Add'),
632         ],
633         action => [
634           t8('Add Part'),
635           submit    => [ '#new_form', { action => 'Part/add_part' } ],
636           accesskey => 'enter',
637         ],
638         action => [
639           t8('Add Service'),
640           submit    => [ '#new_form', { action => 'Part/add_service' } ],
641         ],
642         action => [
643           t8('Add Assembly'),
644           submit    => [ '#new_form', { action => 'Part/add_assembly' } ],
645         ],
646         action => [
647           t8('Add Assortment'),
648           submit    => [ '#new_form', { action => 'Part/add_assortment' } ],
649         ],
650       ], # end of combobox "Add part"
651     );
652   }
653 }