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