allow parbreak \ExtraDescription
[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} .'_from'} grep({$_->{type} eq 'date'} @searchable_custom_variables)),
424     map({'cvar_'. $_->{name} .'_to'}   grep({$_->{type} eq 'date'} @searchable_custom_variables)),
425     map({'cvar_'. $_->{name} .'_qtyop'} grep({$_->{type} eq 'number'} @searchable_custom_variables)),
426     map({ "l_$_" } @columns),
427   );
428
429   my $callback         = build_std_url('action=generate_report', grep { $form->{$_} } @hidden_variables);
430
431   my @sort_full        = qw(partnumber description onhand soldtotal deliverydate insertdate shop);
432   my @sort_no_revers   = qw(partsgroup priceupdate invnumber ordnumber quonumber name image drawing serialnumber);
433
434   foreach my $col (@sort_full) {
435     $column_defs{$col}->{link} = join '&', $callback, "sort=$col", map { "$_=" . E($form->{$_}) } qw(revers lastsort);
436   }
437   map { $column_defs{$_}->{link} = "${callback}&sort=$_" } @sort_no_revers;
438
439   # add order to callback
440   $form->{callback} = join '&', ($callback, map { "${_}=" . E($form->{$_}) } qw(sort revers));
441
442   my $report = SL::ReportGenerator->new(\%myconfig, $form);
443
444   my %attachment_basenames = (
445     'part'     => $locale->text('part_list'),
446     'service'  => $locale->text('service_list'),
447     'assembly' => $locale->text('assembly_list'),
448     'article'  => $locale->text('article_list'),
449   );
450
451   $report->set_options('raw_top_info_text'     => $form->parse_html_template('ic/generate_report_top', { options => \@options }),
452                        'raw_bottom_info_text'  => $form->parse_html_template('ic/generate_report_bottom' ,
453                                                   { PART_CLASSIFICATIONS => SL::DB::Manager::PartClassification->get_all_sorted }),
454                        'output_format'         => 'HTML',
455                        'title'                 => $form->{title},
456                        'attachment_basename'   => 'article_list' . strftime('_%Y%m%d', localtime time),
457   );
458   $report->set_options_from_form();
459   $locale->set_numberformat_wo_thousands_separator(\%myconfig) if lc($report->{options}->{output_format}) eq 'csv';
460
461   $report->set_columns(%column_defs);
462   $report->set_column_order(@columns);
463
464   $report->set_export_options('generate_report', @hidden_variables, qw(sort revers));
465
466   $report->set_sort_indicator($form->{sort}, $form->{revers} ? 0 : 1);
467
468   CVar->add_custom_variables_to_report('module'         => 'IC',
469                                        'trans_id_field' => 'id',
470                                        'configs'        => $cvar_configs,
471                                        'column_defs'    => \%column_defs,
472                                        'data'           => $form->{parts});
473
474   CVar->add_custom_variables_to_report('module'         => 'IC',
475                                        'sub_module'     => sub { $_[0]->{ioi} },
476                                        'trans_id_field' => 'ioi_id',
477                                        'configs'        => $cvar_configs,
478                                        'column_defs'    => \%column_defs,
479                                        'data'           => $form->{parts});
480
481   my @subtotal_columns = qw(sellprice listprice lastcost);
482   my %subtotals = map { $_ => 0 } ('onhand', @subtotal_columns);
483   my %totals    = map { $_ => 0 } @subtotal_columns;
484   my $idx       = 0;
485   my $same_item = @{ $form->{parts} } ? $form->{parts}[0]{ $form->{sort} } : undef;
486
487   my $defaults  = AM->get_defaults();
488
489   # postprocess parts
490   foreach my $ref (@{ $form->{parts} }) {
491
492     # fresh row, for inserting later
493     my $row = { map { $_ => { 'data' => $ref->{$_} } } @columns };
494
495     $ref->{exchangerate} ||= 1;
496     $ref->{price_factor} ||= 1;
497     $ref->{sellprice}     *= $ref->{exchangerate} / $ref->{price_factor};
498     $ref->{listprice}     *= $ref->{exchangerate} / $ref->{price_factor};
499     $ref->{lastcost}      *= $ref->{exchangerate} / $ref->{price_factor};
500
501     # use this for assemblies
502     my $soldtotal = $bsooqr_mode ? $ref->{soldtotal} : $ref->{onhand};
503
504     if ($ref->{assemblyitem}) {
505       $row->{partnumber}{align}   = 'right';
506       $row->{soldtotal}{data}     = 0;
507       $soldtotal                  = 0 if ($form->{sold});
508     }
509
510     my $edit_link               = build_std_url('script=controller.pl', 'action=Part/edit', 'part.id=' . E($ref->{id}));
511     $row->{partnumber}->{link}  = $edit_link;
512     $row->{description}->{link} = $edit_link;
513
514     foreach (qw(sellprice listprice lastcost)) {
515       $row->{$_}{data}            = $form->format_amount(\%myconfig, $ref->{$_}, 2);
516       $row->{"linetotal$_"}{data} = $form->format_amount(\%myconfig, $ref->{onhand} * $ref->{$_}, 2);
517     }
518     foreach ( @pricegroup_columns ) {
519       $row->{$_}{data}            = $form->format_amount(\%myconfig, $ref->{"$_"}, 2);
520     };
521
522
523     map { $row->{$_}{data} = $form->format_amount(\%myconfig, $ref->{$_}); } qw(onhand rop weight soldtotal);
524
525     $row->{weight}->{data} .= ' ' . $defaults->{weightunit};
526
527     # 'yes' and 'no' for boolean value shop
528     if ($form->{l_shop}) {
529       $row->{shop}{data} = $row->{shop}{data}? $::locale->text('yes') : $::locale->text('no');
530     }
531
532     if (!$ref->{assemblyitem}) {
533       foreach my $col (@subtotal_columns) {
534         $totals{$col}    += $soldtotal * $ref->{$col};
535         $subtotals{$col} += $soldtotal * $ref->{$col};
536       }
537
538       $subtotals{soldtotal} += $soldtotal;
539     }
540
541     # set module stuff
542     if ($ref->{module} eq 'oe') {
543       # für oe gibt es vier fälle, jeweils nach kunde oder lieferant unterschiedlich:
544       #
545       # | ist bestellt  | Von Kunden bestellt |  -> edit_oe_ord_link
546       # | Anfrage       | Angebot             |  -> edit_oe_quo_link
547
548       my $edit_oe_ord_link = ($::instance_conf->get_feature_experimental_order)
549                            ? build_std_url("script=controller.pl", 'action=Order/edit',
550                                            'type=' . E($ref->{cv} eq 'vendor' ? 'purchase_order' : 'sales_order'),        'id=' . E($ref->{trans_id}), 'callback')
551                            : build_std_url("script=oe.pl",         'action=edit',
552                                            'type=' . E($ref->{cv} eq 'vendor' ? 'purchase_order' : 'sales_order'),        'id=' . E($ref->{trans_id}), 'callback');
553
554       my $edit_oe_quo_link = ($::instance_conf->get_feature_experimental_order)
555                            ? build_std_url("script=controller.pl", 'action=Order/edit',
556                                            'type=' . E($ref->{cv} eq 'vendor' ? 'request_quotation' : 'sales_quotation'), 'id=' . E($ref->{trans_id}), 'callback')
557                            : build_std_url("script=oe.pl",         'action=edit',
558                                            'type=' . E($ref->{cv} eq 'vendor' ? 'request_quotation' : 'sales_quotation'), 'id=' . E($ref->{trans_id}), 'callback');
559
560       $row->{ordnumber}{link} = $edit_oe_ord_link;
561       $row->{quonumber}{link} = $edit_oe_quo_link if (!$ref->{ordnumber});
562
563     } else {
564       $row->{invnumber}{link} = build_std_url("script=$ref->{module}.pl", 'action=edit', 'type=invoice', 'id=' . E($ref->{trans_id}), 'callback') if ($ref->{invnumber});
565     }
566
567     # set properties of images
568     if ($ref->{image} && (lc $report->{options}->{output_format} eq 'html')) {
569       $row->{image}{data}     = '';
570       $row->{image}{raw_data} = '<a href="' . H($ref->{image}) . '"><img src="' . H($ref->{image}) . '" height="32" border="0"></a>';
571     }
572     map { $row->{$_}{link} = $ref->{$_} } qw(drawing microfiche);
573
574     $row->{notes}{data} = SL::HTML::Util->strip($ref->{notes});
575     $row->{type_and_classific}{data} = SL::Presenter::Part::type_abbreviation($ref->{part_type}).
576                                        SL::Presenter::Part::classification_abbreviation($ref->{classification_id});
577
578     $report->add_data($row);
579
580     my $next_ref = $form->{parts}[$idx + 1];
581
582     # insert subtotal rows
583     if (($form->{l_subtotal} eq 'Y') &&
584         (!$next_ref ||
585          (!$next_ref->{assemblyitem} && ($same_item ne $next_ref->{ $form->{sort} })))) {
586       my $row = { map { $_ => { 'class' => 'listsubtotal', } } @columns };
587
588       if ( !$form->{l_assembly} || !$form->{bom}) {
589         $row->{soldtotal}->{data} = $form->format_amount(\%myconfig, $subtotals{soldtotal});
590       }
591
592       map { $row->{"linetotal$_"}->{data} = $form->format_amount(\%myconfig, $subtotals{$_}, 2) } @subtotal_columns;
593       map { $subtotals{$_} = 0 } ('soldtotal', @subtotal_columns);
594
595       $report->add_data($row);
596
597       $same_item = $next_ref->{ $form->{sort} };
598     }
599
600     $idx++;
601   }
602
603   if ($form->{"l_linetotal"} && !$form->{report_generator_csv_options_for_import}) {
604     my $row = { map { $_ => { 'class' => 'listtotal', } } @columns };
605
606     map { $row->{"linetotal$_"}->{data} = $form->format_amount(\%myconfig, $totals{$_}, 2) } @subtotal_columns;
607
608     $report->add_separator();
609     $report->add_data($row);
610   }
611
612   setup_ic_generate_report_action_bar();
613   $report->generate_with_headers();
614
615   $lxdebug->leave_sub();
616 }    #end generate_report
617
618 sub setup_ic_search_action_bar {
619   my %params = @_;
620
621   for my $bar ($::request->layout->get('actionbar')) {
622     $bar->add(
623       action => [
624         t8('Search'),
625         submit    => [ '#form', { action => 'generate_report' } ],
626         accesskey => 'enter',
627       ],
628
629       action => [
630         t8('TOP100'),
631         submit => [ '#form', { action => 'top100' } ],
632       ],
633     );
634   }
635 }
636
637 sub setup_ic_generate_report_action_bar {
638   my %params = @_;
639
640   for my $bar ($::request->layout->get('actionbar')) {
641     $bar->add(
642       combobox => [
643         action => [
644           t8('Add'),
645         ],
646         action => [
647           t8('Add Part'),
648           submit    => [ '#new_form', { action => 'Part/add_part' } ],
649           accesskey => 'enter',
650         ],
651         action => [
652           t8('Add Service'),
653           submit    => [ '#new_form', { action => 'Part/add_service' } ],
654         ],
655         action => [
656           t8('Add Assembly'),
657           submit    => [ '#new_form', { action => 'Part/add_assembly' } ],
658         ],
659         action => [
660           t8('Add Assortment'),
661           submit    => [ '#new_form', { action => 'Part/add_assortment' } ],
662         ],
663       ], # end of combobox "Add part"
664     );
665   }
666 }