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