9e3da5125f626c85ae59bbeef8f0224a918f4409
[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   );
295
296   my @itemstatus_keys = qw(active obsolete orphaned onhand short);
297   my @callback_keys   = qw(onorder ordered rfq quoted bought sold partnumber partsgroup partsgroup_id serialnumber description make model
298                            drawing microfiche l_soldtotal l_deliverydate transdatefrom transdateto insertdatefrom insertdateto ean shop all);
299
300   # calculate dependencies
301   for (@itemstatus_keys, @callback_keys) {
302     next if ($form->{itemstatus} ne $_ && !$form->{$_});
303     map { $form->{$_} = 'Y' } @{ $dependencies{$_} } if $dependencies{$_};
304   }
305
306   # generate callback and optionstrings
307   my @options;
308   for my  $key (@itemstatus_keys, @callback_keys) {
309     next if ($form->{itemstatus} ne $key && !$form->{$key});
310     push @options, $optiontexts{$key};
311   }
312
313   # special case for lastcost
314   if ($form->{ledgerchecks}){
315     # ledgerchecks don't know about sellprice or lastcost. they just return a
316     # price. so rename sellprice to price, and drop lastcost.
317     $column_defs{sellprice}{text} = $locale->text('Price');
318     $form->{l_lastcost} = ""
319   }
320
321   if ($form->{description}) {
322     $description = $form->{description};
323     $description =~ s/\n/<br>/g;
324   }
325
326   if ($form->{l_linetotal}) {
327     $form->{l_qty} = "Y";
328     $form->{l_linetotalsellprice} = "Y" if $form->{l_sellprice};
329     $form->{l_linetotallastcost}  = $form->{searchitems} eq 'assembly' && !$form->{bom} ? "" : 'Y' if  $form->{l_lastcost};
330     $form->{l_linetotallistprice} = "Y" if $form->{l_listprice};
331   }
332   $form->{"l_type_and_classific"} = "Y";
333
334   if ($form->{l_service} && !$form->{l_assembly} && !$form->{l_part}) {
335
336     # remove warehouse, bin, weight and rop from list
337     map { $form->{"l_$_"} = "" } qw(bin weight rop warehouse);
338
339     $form->{l_onhand} = "";
340
341     # qty is irrelevant unless bought or sold
342     if (   $form->{bought}
343         || $form->{sold}
344         || $form->{onorder}
345         || $form->{ordered}
346         || $form->{rfq}
347         || $form->{quoted}) {
348 #      $form->{l_onhand} = "Y";
349     } else {
350       $form->{l_linetotalsellprice} = "";
351       $form->{l_linetotallastcost}  = "";
352     }
353   }
354
355   # soldtotal doesn't make sense with more than one bsooqr option.
356   # so reset it to sold (the most common option), and issue a warning
357   # ...
358   # also it doesn't make sense without bsooqr. disable and issue a warning too
359   my @bsooqr = qw(sold bought onorder ordered rfq quoted);
360   my $bsooqr_mode = grep { $form->{$_} } @bsooqr;
361   if ($form->{l_subtotal} && 1 < $bsooqr_mode) {
362     my $enabled       = first { $form->{$_} } @bsooqr;
363     $form->{$_}       = ''   for @bsooqr;
364     $form->{$enabled} = 'Y';
365
366     push @options, $::locale->text('Subtotal cannot distinguish betweens record types. Only one of the selected record types will be displayed: #1', $optiontexts{$enabled});
367   }
368   if ($form->{l_soldtotal} && !$bsooqr_mode) {
369     delete $form->{l_soldtotal};
370
371     flash('warning', $::locale->text('Soldtotal does not make sense without any bsooqr options'));
372   }
373   if ($form->{l_soldtotal} && ($form->{l_warehouse} || $form->{l_bin})) {
374     delete $form->{"l_$_"} for  qw(bin warehouse);
375     flash('warning', $::locale->text('Sorry, I am too stupid to figure out the default warehouse/bin and the sold qty. I drop the default warehouse/bin option.'));
376   }
377   if ($form->{l_name} && !$bsooqr_mode) {
378     delete $form->{l_name};
379
380     flash('warning', $::locale->text('Name does not make sense without any bsooqr options'));
381   }
382   IC->all_parts(\%myconfig, \%$form);
383
384   my @columns = qw(
385     partnumber type_and_classific description notes partsgroup warehouse bin
386     make model onhand rop soldtotal unit listprice
387     linetotallistprice sellprice linetotalsellprice lastcost linetotallastcost
388     priceupdate weight image drawing microfiche invnumber ordnumber quonumber
389     transdate name serialnumber deliverydate ean projectnumber projectdescription
390     insertdate shop
391   );
392
393   my $pricegroups = SL::DB::Manager::Pricegroup->get_all_sorted;
394   my @pricegroup_columns;
395   my %column_defs_pricegroups;
396   if ($form->{l_pricegroups}) {
397     @pricegroup_columns      = map { "pricegroup_" . $_->id } @{ $pricegroups };
398     %column_defs_pricegroups = map {
399       "pricegroup_" . $_->id => {
400         text    => $::locale->text('Pricegroup') . ' ' . $_->pricegroup,
401         visible => 1,
402       },
403     }  @{ $pricegroups };
404   }
405   push @columns, @pricegroup_columns;
406
407   my @includeable_custom_variables = grep { $_->{includeable} } @{ $cvar_configs };
408   my @searchable_custom_variables  = grep { $_->{searchable} }  @{ $cvar_configs };
409   my %column_defs_cvars            = map { +"cvar_$_->{name}" => { 'text' => $_->{description} } } @includeable_custom_variables;
410
411   push @columns, map { "cvar_$_->{name}" } @includeable_custom_variables;
412
413   %column_defs = (%column_defs, %column_defs_cvars, %column_defs_pricegroups);
414   map { $column_defs{$_}->{visible} ||= $form->{"l_$_"} ? 1 : 0 } @columns;
415   map { $column_defs{$_}->{align}   = 'right' } qw(onhand sellprice listprice lastcost linetotalsellprice linetotallastcost linetotallistprice rop weight soldtotal shop), @pricegroup_columns;
416
417   my @hidden_variables = (
418     qw(l_subtotal l_linetotal searchitems itemstatus bom l_pricegroups insertdatefrom insertdateto),
419     qw(l_type_and_classific classification_id l_part l_service l_assembly l_assortment),
420     @itemstatus_keys,
421     @callback_keys,
422     map({ "cvar_$_->{name}" } @searchable_custom_variables),
423     map({'cvar_'. $_->{name} .'_qtyop'} grep({$_->{type} eq 'number'} @searchable_custom_variables)),
424     map({ "l_$_" } @columns),
425   );
426
427   my $callback         = build_std_url('action=generate_report', grep { $form->{$_} } @hidden_variables);
428
429   my @sort_full        = qw(partnumber description onhand soldtotal deliverydate insertdate shop);
430   my @sort_no_revers   = qw(partsgroup priceupdate invnumber ordnumber quonumber name image drawing serialnumber);
431
432   foreach my $col (@sort_full) {
433     $column_defs{$col}->{link} = join '&', $callback, "sort=$col", map { "$_=" . E($form->{$_}) } qw(revers lastsort);
434   }
435   map { $column_defs{$_}->{link} = "${callback}&sort=$_" } @sort_no_revers;
436
437   # add order to callback
438   $form->{callback} = join '&', ($callback, map { "${_}=" . E($form->{$_}) } qw(sort revers));
439
440   my $report = SL::ReportGenerator->new(\%myconfig, $form);
441
442   my %attachment_basenames = (
443     'part'     => $locale->text('part_list'),
444     'service'  => $locale->text('service_list'),
445     'assembly' => $locale->text('assembly_list'),
446     'article'  => $locale->text('article_list'),
447   );
448
449   $report->set_options('raw_top_info_text'     => $form->parse_html_template('ic/generate_report_top', { options => \@options }),
450                        'raw_bottom_info_text'  => $form->parse_html_template('ic/generate_report_bottom' ,
451                                                   { PART_CLASSIFICATIONS => SL::DB::Manager::PartClassification->get_all_sorted }),
452                        'output_format'         => 'HTML',
453                        'title'                 => $form->{title},
454                        'attachment_basename'   => 'article_list' . strftime('_%Y%m%d', localtime time),
455   );
456   $report->set_options_from_form();
457   $locale->set_numberformat_wo_thousands_separator(\%myconfig) if lc($report->{options}->{output_format}) eq 'csv';
458
459   $report->set_columns(%column_defs);
460   $report->set_column_order(@columns);
461
462   $report->set_export_options('generate_report', @hidden_variables, qw(sort revers));
463
464   $report->set_sort_indicator($form->{sort}, $form->{revers} ? 0 : 1);
465
466   CVar->add_custom_variables_to_report('module'         => 'IC',
467                                        'trans_id_field' => 'id',
468                                        'configs'        => $cvar_configs,
469                                        'column_defs'    => \%column_defs,
470                                        'data'           => $form->{parts});
471
472   CVar->add_custom_variables_to_report('module'         => 'IC',
473                                        'sub_module'     => sub { $_[0]->{ioi} },
474                                        'trans_id_field' => 'ioi_id',
475                                        'configs'        => $cvar_configs,
476                                        'column_defs'    => \%column_defs,
477                                        'data'           => $form->{parts});
478
479   my @subtotal_columns = qw(sellprice listprice lastcost);
480   my %subtotals = map { $_ => 0 } ('onhand', @subtotal_columns);
481   my %totals    = map { $_ => 0 } @subtotal_columns;
482   my $idx       = 0;
483   my $same_item = @{ $form->{parts} } ? $form->{parts}[0]{ $form->{sort} } : undef;
484
485   my $defaults  = AM->get_defaults();
486
487   # postprocess parts
488   foreach my $ref (@{ $form->{parts} }) {
489
490     # fresh row, for inserting later
491     my $row = { map { $_ => { 'data' => $ref->{$_} } } @columns };
492
493     $ref->{exchangerate} ||= 1;
494     $ref->{price_factor} ||= 1;
495     $ref->{sellprice}     *= $ref->{exchangerate} / $ref->{price_factor};
496     $ref->{listprice}     *= $ref->{exchangerate} / $ref->{price_factor};
497     $ref->{lastcost}      *= $ref->{exchangerate} / $ref->{price_factor};
498
499     # use this for assemblies
500     my $soldtotal = $bsooqr_mode ? $ref->{soldtotal} : $ref->{onhand};
501
502     if ($ref->{assemblyitem}) {
503       $row->{partnumber}{align}   = 'right';
504       $row->{soldtotal}{data}     = 0;
505       $soldtotal                  = 0 if ($form->{sold});
506     }
507
508     my $edit_link               = build_std_url('script=controller.pl', 'action=Part/edit', 'part.id=' . E($ref->{id}));
509     $row->{partnumber}->{link}  = $edit_link;
510     $row->{description}->{link} = $edit_link;
511
512     foreach (qw(sellprice listprice lastcost)) {
513       $row->{$_}{data}            = $form->format_amount(\%myconfig, $ref->{$_}, 2);
514       $row->{"linetotal$_"}{data} = $form->format_amount(\%myconfig, $ref->{onhand} * $ref->{$_}, 2);
515     }
516     foreach ( @pricegroup_columns ) {
517       $row->{$_}{data}            = $form->format_amount(\%myconfig, $ref->{"$_"}, 2);
518     };
519
520
521     map { $row->{$_}{data} = $form->format_amount(\%myconfig, $ref->{$_}); } qw(onhand rop weight soldtotal);
522
523     $row->{weight}->{data} .= ' ' . $defaults->{weightunit};
524
525     # 'yes' and 'no' for boolean value shop
526     if ($form->{l_shop}) {
527       $row->{shop}{data} = $row->{shop}{data}? $::locale->text('yes') : $::locale->text('no');
528     }
529
530     if (!$ref->{assemblyitem}) {
531       foreach my $col (@subtotal_columns) {
532         $totals{$col}    += $soldtotal * $ref->{$col};
533         $subtotals{$col} += $soldtotal * $ref->{$col};
534       }
535
536       $subtotals{soldtotal} += $soldtotal;
537     }
538
539     # set module stuff
540     if ($ref->{module} eq 'oe') {
541       # für oe gibt es vier fälle, jeweils nach kunde oder lieferant unterschiedlich:
542       #
543       # | ist bestellt  | Von Kunden bestellt |  -> edit_oe_ord_link
544       # | Anfrage       | Angebot             |  -> edit_oe_quo_link
545
546       my $edit_oe_ord_link = ($::instance_conf->get_feature_experimental_order)
547                            ? build_std_url("script=controller.pl", 'action=Order/edit',
548                                            'type=' . E($ref->{cv} eq 'vendor' ? 'purchase_order' : 'sales_order'),        'id=' . E($ref->{trans_id}), 'callback')
549                            : build_std_url("script=oe.pl",         'action=edit',
550                                            'type=' . E($ref->{cv} eq 'vendor' ? 'purchase_order' : 'sales_order'),        'id=' . E($ref->{trans_id}), 'callback');
551
552       my $edit_oe_quo_link = ($::instance_conf->get_feature_experimental_order)
553                            ? build_std_url("script=controller.pl", 'action=Order/edit',
554                                            'type=' . E($ref->{cv} eq 'vendor' ? 'request_quotation' : 'sales_quotation'), 'id=' . E($ref->{trans_id}), 'callback')
555                            : build_std_url("script=oe.pl",         'action=edit',
556                                            'type=' . E($ref->{cv} eq 'vendor' ? 'request_quotation' : 'sales_quotation'), 'id=' . E($ref->{trans_id}), 'callback');
557
558       $row->{ordnumber}{link} = $edit_oe_ord_link;
559       $row->{quonumber}{link} = $edit_oe_quo_link if (!$ref->{ordnumber});
560
561     } else {
562       $row->{invnumber}{link} = build_std_url("script=$ref->{module}.pl", 'action=edit', 'type=invoice', 'id=' . E($ref->{trans_id}), 'callback') if ($ref->{invnumber});
563     }
564
565     # set properties of images
566     if ($ref->{image} && (lc $report->{options}->{output_format} eq 'html')) {
567       $row->{image}{data}     = '';
568       $row->{image}{raw_data} = '<a href="' . H($ref->{image}) . '"><img src="' . H($ref->{image}) . '" height="32" border="0"></a>';
569     }
570     map { $row->{$_}{link} = $ref->{$_} } qw(drawing microfiche);
571
572     $row->{notes}{data} = SL::HTML::Util->strip($ref->{notes});
573     $row->{type_and_classific}{data} = SL::Presenter::Part::type_abbreviation($ref->{part_type}).
574                                        SL::Presenter::Part::classification_abbreviation($ref->{classification_id});
575
576     $report->add_data($row);
577
578     my $next_ref = $form->{parts}[$idx + 1];
579
580     # insert subtotal rows
581     if (($form->{l_subtotal} eq 'Y') &&
582         (!$next_ref ||
583          (!$next_ref->{assemblyitem} && ($same_item ne $next_ref->{ $form->{sort} })))) {
584       my $row = { map { $_ => { 'class' => 'listsubtotal', } } @columns };
585
586       if ( !$form->{l_assembly} || !$form->{bom}) {
587         $row->{soldtotal}->{data} = $form->format_amount(\%myconfig, $subtotals{soldtotal});
588       }
589
590       map { $row->{"linetotal$_"}->{data} = $form->format_amount(\%myconfig, $subtotals{$_}, 2) } @subtotal_columns;
591       map { $subtotals{$_} = 0 } ('soldtotal', @subtotal_columns);
592
593       $report->add_data($row);
594
595       $same_item = $next_ref->{ $form->{sort} };
596     }
597
598     $idx++;
599   }
600
601   if ($form->{"l_linetotal"} && !$form->{report_generator_csv_options_for_import}) {
602     my $row = { map { $_ => { 'class' => 'listtotal', } } @columns };
603
604     map { $row->{"linetotal$_"}->{data} = $form->format_amount(\%myconfig, $totals{$_}, 2) } @subtotal_columns;
605
606     $report->add_separator();
607     $report->add_data($row);
608   }
609
610   setup_ic_generate_report_action_bar();
611   $report->generate_with_headers();
612
613   $lxdebug->leave_sub();
614 }    #end generate_report
615
616 sub setup_ic_search_action_bar {
617   my %params = @_;
618
619   for my $bar ($::request->layout->get('actionbar')) {
620     $bar->add(
621       action => [
622         t8('Search'),
623         submit    => [ '#form', { action => 'generate_report' } ],
624         accesskey => 'enter',
625       ],
626
627       action => [
628         t8('TOP100'),
629         submit => [ '#form', { action => 'top100' } ],
630       ],
631     );
632   }
633 }
634
635 sub setup_ic_generate_report_action_bar {
636   my %params = @_;
637
638   for my $bar ($::request->layout->get('actionbar')) {
639     $bar->add(
640       combobox => [
641         action => [
642           t8('Add'),
643         ],
644         action => [
645           t8('Add Part'),
646           submit    => [ '#new_form', { action => 'Part/add_part' } ],
647           accesskey => 'enter',
648         ],
649         action => [
650           t8('Add Service'),
651           submit    => [ '#new_form', { action => 'Part/add_service' } ],
652         ],
653         action => [
654           t8('Add Assembly'),
655           submit    => [ '#new_form', { action => 'Part/add_assembly' } ],
656         ],
657         action => [
658           t8('Add Assortment'),
659           submit    => [ '#new_form', { action => 'Part/add_assortment' } ],
660         ],
661       ], # end of combobox "Add part"
662     );
663   }
664 }