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