manuelle ergänzung zum vorherigen commit
[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::ReportGenerator;
42
43 #use SL::PE;
44
45 use strict;
46 #use warnings;
47
48 # global imports
49 our ($form, $locale, %myconfig, $lxdebug, $auth);
50
51 require "bin/mozilla/io.pl";
52 require "bin/mozilla/invoice_io.pl";
53 require "bin/mozilla/common.pl";
54 require "bin/mozilla/reportgenerator.pl";
55
56 1;
57
58 # Parserhappy(R):
59 # type=submit $locale->text('Add Part')
60 # type=submit $locale->text('Add Service')
61 # type=submit $locale->text('Add Assembly')
62 # type=submit $locale->text('Edit Part')
63 # type=submit $locale->text('Edit Service')
64 # type=submit $locale->text('Edit Assembly')
65 # $locale->text('Parts')
66 # $locale->text('Services')
67 # $locale->text('Inventory quantity must be zero before you can set this part obsolete!')
68 # $locale->text('Inventory quantity must be zero before you can set this assembly obsolete!')
69 # $locale->text('Part Number missing!')
70 # $locale->text('Service Number missing!')
71 # $locale->text('Assembly Number missing!')
72 # $locale->text('ea');
73
74 # end of main
75
76 sub add {
77   $lxdebug->enter_sub();
78
79   $auth->assert('part_service_assembly_edit');
80
81   my $title                = 'Add ' . ucfirst $form->{item};
82   $form->{title}           = $locale->text($title);
83   $form->{callback}        = "$form->{script}?action=add&item=$form->{item}" unless $form->{callback};
84   $form->{unit_changeable} = 1;
85
86   IC->get_pricegroups(\%myconfig, \%$form);
87   &link_part;
88   &display_form;
89
90   $lxdebug->leave_sub();
91 }
92
93 sub search {
94   $lxdebug->enter_sub();
95
96   $auth->assert('part_service_assembly_edit');
97
98   $form->{revers}       = 0;  # switch for backward sorting
99   $form->{lastsort}     = ""; # memory for which table was sort at last time
100   $form->{ndxs_counter} = 0;  # counter for added entries to top100
101
102   my %is_xyz     = map { +"is_$_" => ($form->{searchitems} eq $_) } qw(part service assembly);
103
104   $form->{title} = (ucfirst $form->{searchitems}) . "s";
105   $form->{title} = $locale->text($form->{title});
106   $form->{title} = $locale->text('Assemblies') if ($is_xyz{is_assembly});
107
108   $form->{jsscript} = 1;
109
110   $form->{CUSTOM_VARIABLES}                  = CVar->get_configs('module' => 'IC');
111   ($form->{CUSTOM_VARIABLES_FILTER_CODE},
112    $form->{CUSTOM_VARIABLES_INCLUSION_CODE}) = CVar->render_search_options('variables'      => $form->{CUSTOM_VARIABLES},
113                                                                            'include_prefix' => 'l_',
114                                                                            'include_value'  => 'Y');
115
116   $form->header;
117
118   $form->get_lists('partsgroup'    => 'ALL_PARTSGROUPS');
119   print $form->parse_html_template('ic/search', { %is_xyz,
120                                                   dateformat => $myconfig{dateformat},
121                                                   limit => $myconfig{vclimit}, });
122
123   $lxdebug->leave_sub();
124 }    #end search()
125
126 sub search_update_prices {
127   $lxdebug->enter_sub();
128
129   $auth->assert('part_service_assembly_edit');
130
131   my $pricegroups = IC->get_pricegroups(\%myconfig, \%$form);
132
133   $form->{title} = $locale->text('Update Prices');
134
135   $form->header;
136
137   print $form->parse_html_template('ic/search_update_prices', { PRICE_ROWS => $pricegroups });
138
139   $lxdebug->leave_sub();
140 }    #end search()
141
142 sub confirm_price_update {
143   $lxdebug->enter_sub();
144
145   $auth->assert('part_service_assembly_edit');
146
147   my @errors      = ();
148   my $value_found = undef;
149
150   foreach my $idx (qw(sellprice listprice), (1..$form->{price_rows})) {
151     my $name      = $idx =~ m/\d/ ? $form->{"pricegroup_${idx}"}      : $idx eq 'sellprice' ? $locale->text('Sell Price') : $locale->text('List Price');
152     my $type      = $idx =~ m/\d/ ? $form->{"pricegroup_type_${idx}"} : $form->{"${idx}_type"};
153     my $value_idx = $idx =~ m/\d/ ? "price_${idx}" : $idx;
154     my $value     = $form->parse_amount(\%myconfig, $form->{$value_idx});
155
156     if ((0 > $value) && ($type eq 'percent')) {
157       push @errors, $locale->text('You cannot adjust the price for pricegroup "#1" by a negative percentage.', $name);
158
159     } elsif (!$value && ($form->{$value_idx} ne '')) {
160       push @errors, $locale->text('No valid number entered for pricegroup "#1".', $name);
161
162     } elsif (0 < $value) {
163       $value_found = 1;
164     }
165   }
166
167   push @errors, $locale->text('No prices will be updated because no prices have been entered.') if (!$value_found);
168
169   my $num_matches = IC->get_num_matches_for_priceupdate();
170
171   $form->header();
172
173   if (@errors) {
174     $form->show_generic_error(join('<br>', @errors), 'back_button' => 1);
175   }
176
177   $form->{nextsub} = "update_prices";
178
179   map { delete $form->{$_} } qw(action header);
180
181   print $form->parse_html_template('ic/confirm_price_update', { HIDDENS     => [ map { name => $_, value => $form->{$_} }, keys %$form ],
182                                                                 num_matches => $num_matches });
183
184   $lxdebug->leave_sub();
185 }
186
187 sub update_prices {
188   $lxdebug->enter_sub();
189
190   $auth->assert('part_service_assembly_edit');
191
192   my $num_updated = IC->update_prices(\%myconfig, \%$form);
193
194   if (-1 != $num_updated) {
195     $form->redirect($locale->text('#1 prices were updated.', $num_updated));
196   } else {
197     $form->error($locale->text('Could not update prices!'));
198   }
199
200   $lxdebug->leave_sub();
201 }
202
203 #sub choice {
204 #  $lxdebug->enter_sub();
205 #
206 #  $auth->assert('part_service_assembly_edit');
207 #
208 #  our ($j, $lastndx);
209 #  my ($totop100);
210 #
211 #  $form->{title} = $locale->text('Top 100 hinzufuegen');
212 #
213 #  $form->header;
214 #
215 #  push @custom_hiddens, qw(searchitems title bom titel revers lastsort sort ndxs_counter extras);
216 #  push @custom_hiddens, qw(itemstatus l_linetotal l_partnumber l_description l_onhand l_unit l_sellprice l_linetotalsellprice);
217 #  my @HIDDENS = (
218 #        +{ name => 'row',     value => $j              },
219 #        +{ name => 'nextsub', value => 'item_selected' },
220 #        +{ name => 'test',    value => 'item_selected' },
221 #        +{ name => 'lastndx', value => $lastndx        },
222 #    map(+{ name => $_,        value => $form->{$_}     }, @custom_hiddens),
223 #  );
224 #
225 #  my ($partnumber, $description, $unit, $sellprice, $soldtotal);
226 #  # if choice set data
227 ##  if ($form->{ndx}) {
228 ##    for my $i (0 .. $form->{ndxs_counter}) {
229 ##
230 ##      # insert data into top100
231 ##      push @{ $form->{parts} },
232 ##        { number      => "",
233 ##          partnumber  => $form->{"totop100_partnumber_$j"},
234 ##          description => $form->{"totop100_description_$j"},
235 ##          unit        => $form->{"totop100_unit_$j"},
236 ##          sellprice   => $form->{"totop100_sellprice_$j"},
237 ##          soldtotal   => $form->{"totop100_soldtotal_$j"},
238 ##        };
239 ##    }    #rof
240 ##  }    #fi
241 #
242 #  $totop100 = "";
243 #
244 #  # set data for next page
245 #  for my $i (1 .. $form->{ndxs_counter}) {
246 #    $partnumber  = $form->{"totop100_partnumber_$i"};
247 #    $description = $form->{"totop100_description_$i"};
248 #    $unit        = $form->{"totop100_unit_$i"};
249 #    $sellprice   = $form->{"totop100_sellprice_$i"};
250 #    $soldtotal   = $form->{"totop100_soldtotal_$i"};
251 #
252 #  push @PARTS, {
253 #    totop100_partnumber  => $form->{"totop100_partnumber_$i"},
254 #    totop100_description => $form->{"totop100_description_$i"},
255 #    totop100_unit        => $form->{"totop100_unit_$i"},
256 #    totop100_sellprice   => $form->{"totop100_sellprice_$i"},
257 #    totop100_soldtotal   => $form->{"totop100_soldtotal_$i"},
258 #  }
259 #
260 ##    $totop100 .= qq|
261 ##<input type=hidden name=totop100_partnumber_$i value=$form->{"totop100_partnumber_$i"}>
262 ##<input type=hidden name=totop100_description_$i value=$form->{"totop100_description_$i"}>
263 ##<input type=hidden name=totop100_unit_$i value=$form->{"totop100_unit_$i"}>
264 ##<input type=hidden name=totop100_sellprice_$i value=$form->{"totop100_sellprice_$i"}>
265 ##<input type=hidden name=totop100_soldtotal_$i value=$form->{"totop100_soldtotal_$i"}>
266 ##    |;
267 #  }    #rof
268 #
269 #  print $form->parse_html_template('ic/choice', +{ HIDDENS => \@HIDDENS, PARTS => \@PARTS });
270 #
271 #  $lxdebug->leave_sub();
272 #}    #end choice
273
274 #sub list {
275 #  $lxdebug->enter_sub();
276 #
277 #  $auth->assert('part_service_assembly_edit');
278 #
279 #  our ($lastndx);
280 #  our ($partnumber, $description, $unit, $sellprice, $soldtotal);
281 #
282 #  my @sortorders = ("", "partnumber", "description", "all");
283 #  my $sortorder = $sortorders[($form->{description} ? 2 : 0) + ($form->{partnumber} ? 1 : 0)];
284 #  IC->get_parts(\%myconfig, \%$form, $sortorder);
285 #
286 #  $form->{title} = $locale->text('Top 100 hinzufuegen');
287 #
288 #  $form->header;
289 #
290 #  print qq|
291 #<body>
292 #  <form method=post action=ic.pl>
293 #    <table width=100%>
294 #     <tr>
295 #      <th class=listtop colspan=6>| . $locale->text('choice part') . qq|</th>
296 #     </tr>
297 #        <tr height="5"></tr>
298 #        <tr class=listheading>
299 #          <th>&nbsp;</th>
300 #          <th class=listheading>| . $locale->text('Part Number') . qq|</th>
301 #          <th class=listheading>| . $locale->text('Part Description') . qq|</th>
302 #          <th class=listheading>| . $locale->text('Unit of measure') . qq|</th>
303 #          <th class=listheading>| . $locale->text('Sell Price') . qq|</th>
304 #          <th class=listheading>| . $locale->text('soldtotal') . qq|</th>
305 #        </tr>|;
306 #
307 #  my $j = 0;
308 #  my $i = $form->{rows};
309 #
310 #  for ($j = 1; $j <= $i; $j++) {
311 #
312 #    print qq|
313 #        <tr class=listrow| . ($j % 2) . qq|>|;
314 #    if ($j == 1) {
315 #      print qq|
316 #            <td><input name=ndx class=radio type=radio value=$j checked></td>|;
317 #    } else {
318 #      print qq|
319 #          <td><input name=ndx class=radio type=radio value=$j></td>|;
320 #    }
321 #    print qq|
322 #          <td><input name="new_partnumber_$j" type=hidden value="$form->{"partnumber_$j"}">$form->{"partnumber_$j"}</td>
323 #          <td><input name="new_description_$j" type=hidden value="$form->{"description_$j"}">$form->{"description_$j"}</td>
324 #          <td><input name="new_unit_$j" type=hidden value="$form->{"unit_$j"}">$form->{"unit_$j"}</td>
325 #          <td><input name="new_sellprice_$j" type=hidden value="$form->{"sellprice_$j"}">$form->{"sellprice_$j"}</td>
326 #          <td><input name="new_soldtotal_$j" type=hidden value="$form->{"soldtotal_$j"}">$form->{"soldtotal_$j"}</td>
327 #        </tr>
328 #
329 #        <input name="new_id_$j" type=hidden value="$form->{"id_$j"}">|;
330 #  }
331 #
332 #  print qq|
333 #
334 #</table>
335 #
336 #<br>
337 #
338 #
339 #<input type=hidden name=itemstatus value="$form->{itemstatus}">
340 #<input type=hidden name=l_linetotal value="$form->{l_linetotal}">
341 #<input type=hidden name=l_partnumber value="$form->{l_partnumber}">
342 #<input type=hidden name=l_description value="$form->{l_description}">
343 #<input type=hidden name=l_onhand value="$form->{l_onhand}">
344 #<input type=hidden name=l_unit value="$form->{l_unit}">
345 #<input type=hidden name=l_sellprice value="$form->{l_sellprice}">
346 #<input type=hidden name=l_linetotalsellprice value="$form->{l_linetotalsellprice}">
347 #<input type=hidden name=sort value="$form->{sort}">
348 #<input type=hidden name=revers value="$form->{revers}">
349 #<input type=hidden name=lastsort value="$form->{lastsort}">
350 #
351 #<input type=hidden name=bom value="$form->{bom}">
352 #<input type=hidden name=titel value="$form->{titel}">
353 #<input type=hidden name=searchitems value="$form->{searchitems}">
354 #
355 #<input type=hidden name=row value=$j>
356 #
357 #<input type=hidden name=nextsub value=item_selected>
358 #
359 #<input name=lastndx type=hidden value=$lastndx>
360 #
361 #<input name=ndxs_counter type=hidden value=$form->{ndxs_counter}>|;
362 #
363 #  my $totop100 = "";
364 #
365 #  if (($form->{ndxs_counter}) > 0) {
366 #    for ($i = 1; ($i < $form->{ndxs_counter} + 1); $i++) {
367 #
368 #      $partnumber  = $form->{"totop100_partnumber_$i"};
369 #      $description = $form->{"totop100_description_$i"};
370 #      $unit        = $form->{"totop100_unit_$i"};
371 #      $sellprice   = $form->{"totop100_sellprice_$i"};
372 #      $soldtotal   = $form->{"totop100_soldtotal_$i"};
373 #
374 #      $totop100 .= qq|
375 #<input type=hidden name=totop100_partnumber_$i value=$form->{"totop100_partnumber_$i"}>
376 #<input type=hidden name=totop100_description_$i value=$form->{"totop100_description_$i"}>
377 #<input type=hidden name=totop100_unit_$i value=$form->{"totop100_unit_$i"}>
378 #<input type=hidden name=totop100_sellprice_$i value=$form->{"totop100_sellprice_$i"}>
379 #<input type=hidden name=totop100_soldtotal_$i value=$form->{"totop100_soldtotal_$i"}>
380 #      |;
381 #    }    #rof
382 #  }    #fi
383 #
384 #  print $totop100;
385 #
386 #  print qq|
387 #<input class=submit type=submit name=action value="|
388 #    . $locale->text('TOP100') . qq|">
389 #
390 #</form>
391 #|;
392 #  $lxdebug->leave_sub();
393 #}    #end list()
394
395 sub top100 {
396   $lxdebug->enter_sub();
397
398   $auth->assert('part_service_assembly_edit');
399
400   if ($form->{ndx}) {
401     $form->{ndxs_counter}++;
402
403     if ($form->{ndxs_counter} > 0) {
404
405       my $index = $form->{ndx};
406
407       $form->{"totop100_partnumber_$form->{ndxs_counter}"} = $form->{"new_partnumber_$index"};
408       $form->{"totop100_description_$form->{ndxs_counter}"} = $form->{"new_description_$index"};
409       $form->{"totop100_unit_$form->{ndxs_counter}"} = $form->{"new_unit_$index"};
410       $form->{"totop100_sellprice_$form->{ndxs_counter}"} = $form->{"new_sellprice_$index"};
411       $form->{"totop100_soldtotal_$form->{ndxs_counter}"} = $form->{"new_soldtotal_$index"};
412     }    #fi
413   }    #fi
414   &addtop100();
415   $lxdebug->leave_sub();
416 }    #end top100
417
418 sub addtop100 {
419   $lxdebug->enter_sub();
420
421   $auth->assert('part_service_assembly_edit');
422
423   my ($revers, $lastsort, $callback, $option, $description, $sameitem,
424       $partnumber, $unit, $sellprice, $soldtotal, $totop100, $onhand, $align);
425   my (@column_index, %column_header, %column_data);
426   my ($totalsellprice, $totallastcost, $totallistprice, $subtotalonhand, $subtotalsellprice, $subtotallastcost, $subtotallistprice);
427
428   $form->{top100}      = "top100";
429   $form->{l_soldtotal} = "Y";
430   $form->{soldtotal}   = "soldtotal";
431   $form->{sort}        = "soldtotal";
432   $form->{l_qty}       = "N";
433   $form->{l_linetotal} = "";
434   $form->{revers}      = 1;
435   $form->{number}      = "position";
436   $form->{l_number}    = "Y";
437
438   $totop100 = "";
439
440   $form->{title} = $locale->text('Top 100');
441
442   $revers   = $form->{revers};
443   $lastsort = $form->{lastsort};
444
445   if (($form->{lastsort} eq "") && ($form->{sort} eq undef)) {
446     $form->{revers}   = 0;
447     $form->{lastsort} = "partnumber";
448     $form->{sort}     = "partnumber";
449   }    #fi
450
451   $callback =
452     "$form->{script}?action=top100&searchitems=$form->{searchitems}&itemstatus=$form->{itemstatus}&bom=$form->{bom}&l_linetotal=$form->{l_linetotal}&title="
453     . $form->escape($form->{title}, 1);
454
455   # if we have a serialnumber limit search
456   if ($form->{serialnumber} || $form->{l_serialnumber}) {
457     $form->{l_serialnumber} = "Y";
458     unless (   $form->{bought}
459             || $form->{sold}
460             || $form->{rfq}
461             || $form->{quoted}) {
462       $form->{bought} = $form->{sold} = 1;
463     }
464   }
465   IC->all_parts(\%myconfig, \%$form);
466
467   if ($form->{itemstatus} eq 'active') {
468     $option .= $locale->text('Active') . " : ";
469   }
470   if ($form->{itemstatus} eq 'obsolete') {
471     $option .= $locale->text('Obsolete') . " : ";
472   }
473   if ($form->{itemstatus} eq 'orphaned') {
474     $option .= $locale->text('Orphaned') . " : ";
475   }
476   if ($form->{itemstatus} eq 'onhand') {
477     $option .= $locale->text('On Hand') . " : ";
478     $form->{l_onhand} = "Y";
479   }
480   if ($form->{itemstatus} eq 'short') {
481     $option .= $locale->text('Short') . " : ";
482     $form->{l_onhand} = "Y";
483   }
484   if ($form->{onorder}) {
485     $form->{l_ordnumber} = "Y";
486     $callback .= "&onorder=$form->{onorder}";
487     $option   .= $locale->text('On Order') . " : ";
488   }
489   if ($form->{ordered}) {
490     $form->{l_ordnumber} = "Y";
491     $callback .= "&ordered=$form->{ordered}";
492     $option   .= $locale->text('Ordered') . " : ";
493   }
494   if ($form->{rfq}) {
495     $form->{l_quonumber} = "Y";
496     $callback .= "&rfq=$form->{rfq}";
497     $option   .= $locale->text('RFQ') . " : ";
498   }
499   if ($form->{quoted}) {
500     $form->{l_quonumber} = "Y";
501     $callback .= "&quoted=$form->{quoted}";
502     $option   .= $locale->text('Quoted') . " : ";
503   }
504   if ($form->{bought}) {
505     $form->{l_invnumber} = "Y";
506     $callback .= "&bought=$form->{bought}";
507     $option   .= $locale->text('Bought') . " : ";
508   }
509   if ($form->{sold}) {
510     $form->{l_invnumber} = "Y";
511     $callback .= "&sold=$form->{sold}";
512     $option   .= $locale->text('Sold') . " : ";
513   }
514   if (   $form->{bought}
515       || $form->{sold}
516       || $form->{onorder}
517       || $form->{ordered}
518       || $form->{rfq}
519       || $form->{quoted}) {
520
521     $form->{l_lastcost} = "";
522     $form->{l_name}     = "Y";
523     if ($form->{transdatefrom}) {
524       $callback .= "&transdatefrom=$form->{transdatefrom}";
525       $option   .= "\n<br>"
526         . $locale->text('From')
527         . "&nbsp;"
528         . $locale->date(\%myconfig, $form->{transdatefrom}, 1);
529     }
530     if ($form->{transdateto}) {
531       $callback .= "&transdateto=$form->{transdateto}";
532       $option   .= "\n<br>"
533         . $locale->text('To')
534         . "&nbsp;"
535         . $locale->date(\%myconfig, $form->{transdateto}, 1);
536     }
537   }
538
539   $option .= "<br>";
540
541   if ($form->{partnumber}) {
542     $callback .= "&partnumber=$form->{partnumber}";
543     $option   .= $locale->text('Part Number') . qq| : $form->{partnumber}<br>|;
544   }
545   if ($form->{ean}) {
546     $callback .= "&partnumber=$form->{ean}";
547     $option   .= $locale->text('EAN') . qq| : $form->{ean}<br>|;
548   }
549   if ($form->{partsgroup}) {
550     $callback .= "&partsgroup=$form->{partsgroup}";
551     $option   .= $locale->text('Group') . qq| : $form->{partsgroup}<br>|;
552   }
553   if ($form->{serialnumber}) {
554     $callback .= "&serialnumber=$form->{serialnumber}";
555     $option   .= $locale->text('Serial Number') . qq| : $form->{serialnumber}<br>|;
556   }
557   if ($form->{description}) {
558     $callback   .= "&description=$form->{description}";
559     $description = $form->{description};
560     $description =~ s/\n/<br>/g;
561     $option     .= $locale->text('Part Description') . qq| : $form->{description}<br>|;
562   }
563   if ($form->{make}) {
564     $callback .= "&make=$form->{make}";
565     $option   .= $locale->text('Make') . qq| : $form->{make}<br>|;
566   }
567   if ($form->{model}) {
568     $callback .= "&model=$form->{model}";
569     $option   .= $locale->text('Model') . qq| : $form->{model}<br>|;
570   }
571   if ($form->{drawing}) {
572     $callback .= "&drawing=$form->{drawing}";
573     $option   .= $locale->text('Drawing') . qq| : $form->{drawing}<br>|;
574   }
575   if ($form->{microfiche}) {
576     $callback .= "&microfiche=$form->{microfiche}";
577     $option   .= $locale->text('Microfiche') . qq| : $form->{microfiche}<br>|;
578   }
579   if ($form->{l_soldtotal}) {
580     $callback .= "&soldtotal=$form->{soldtotal}";
581     $option   .= $locale->text('soldtotal') . qq| : $form->{soldtotal}<br>|;
582   }
583
584   my @columns = $form->sort_columns(
585     qw(number partnumber ean description partsgroup bin onhand rop unit listprice linetotallistprice sellprice linetotalsellprice lastcost linetotallastcost priceupdate weight image drawing microfiche invnumber ordnumber quonumber name serialnumber soldtotal)
586   );
587
588   if ($form->{l_linetotal}) {
589     $form->{l_onhand} = "Y";
590     $form->{l_linetotalsellprice} = "Y" if $form->{l_sellprice};
591     if ($form->{l_lastcost}) {
592       $form->{l_linetotallastcost} = "Y";
593       if (($form->{searchitems} eq 'assembly') && !$form->{bom}) {
594         $form->{l_linetotallastcost} = "";
595       }
596     }
597     $form->{l_linetotallistprice} = "Y" if $form->{l_listprice};
598   }
599
600   if ($form->{searchitems} eq 'service') {
601
602     # remove bin, weight and rop from list
603     map { $form->{"l_$_"} = "" } qw(bin weight rop);
604
605     $form->{l_onhand} = "";
606
607     # qty is irrelevant unless bought or sold
608     if (   $form->{bought}
609         || $form->{sold}
610         || $form->{onorder}
611         || $form->{ordered}
612         || $form->{rfq}
613         || $form->{quoted}) {
614       $form->{l_onhand} = "Y";
615     } else {
616       $form->{l_linetotalsellprice} = "";
617       $form->{l_linetotallastcost}  = "";
618     }
619   }
620
621   foreach my $item (@columns) {
622     if ($form->{"l_$item"} eq "Y") {
623       push @column_index, $item;
624
625       # add column to callback
626       $callback .= "&l_$item=Y";
627     }
628   }
629
630   if ($form->{l_subtotal} eq 'Y') {
631     $callback .= "&l_subtotal=Y";
632   }
633
634   $column_header{number} =
635     qq|<th class=listheading nowrap>| . $locale->text('number') . qq|</th>|;
636   $column_header{partnumber} =
637     qq|<th nowrap><a class=listheading href=$callback&sort=partnumber&revers=$form->{revers}&lastsort=$form->{lastsort}>|
638     . $locale->text('Part Number')
639     . qq|</a></th>|;
640   $column_header{description} =
641     qq|<th nowrap><a class=listheading href=$callback&sort=description&revers=$form->{revers}&lastsort=$form->{lastsort}>|
642     . $locale->text('Part Description')
643     . qq|</a></th>|;
644   $column_header{partsgroup} =
645       qq|<th nowrap><a class=listheading href=$callback&sort=partsgroup>|
646     . $locale->text('Group')
647     . qq|</a></th>|;
648   $column_header{bin} =
649       qq|<th><a class=listheading href=$callback&sort=bin>|
650     . $locale->text('Bin')
651     . qq|</a></th>|;
652   $column_header{priceupdate} =
653       qq|<th nowrap><a class=listheading href=$callback&sort=priceupdate>|
654     . $locale->text('Updated')
655     . qq|</a></th>|;
656   $column_header{onhand} =
657     qq|<th nowrap><a  class=listheading href=$callback&sort=onhand&revers=$form->{revers}&lastsort=$form->{lastsort}>|
658     . $locale->text('Qty')
659     . qq|</th>|;
660   $column_header{unit} =
661     qq|<th class=listheading nowrap>| . $locale->text('Unit') . qq|</th>|;
662   $column_header{listprice} =
663       qq|<th class=listheading nowrap>|
664     . $locale->text('List Price')
665     . qq|</th>|;
666   $column_header{lastcost} =
667     qq|<th class=listheading nowrap>| . $locale->text('Last Cost') . qq|</th>|;
668   $column_header{rop} =
669     qq|<th class=listheading nowrap>| . $locale->text('ROP') . qq|</th>|;
670   $column_header{weight} =
671     qq|<th class=listheading nowrap>| . $locale->text('Weight') . qq|</th>|;
672
673   $column_header{invnumber} =
674       qq|<th nowrap><a class=listheading href=$callback&sort=invnumber>|
675     . $locale->text('Invoice Number')
676     . qq|</a></th>|;
677   $column_header{ordnumber} =
678       qq|<th nowrap><a class=listheading href=$callback&sort=ordnumber>|
679     . $locale->text('Order Number')
680     . qq|</a></th>|;
681   $column_header{quonumber} =
682       qq|<th nowrap><a class=listheading href=$callback&sort=quonumber>|
683     . $locale->text('Quotation')
684     . qq|</a></th>|;
685
686   $column_header{name} =
687       qq|<th nowrap><a class=listheading href=$callback&sort=name>|
688     . $locale->text('Name')
689     . qq|</a></th>|;
690
691   $column_header{sellprice} =
692       qq|<th class=listheading nowrap>|
693     . $locale->text('Sell Price')
694     . qq|</th>|;
695   $column_header{linetotalsellprice} =
696     qq|<th class=listheading nowrap>| . $locale->text('Extended') . qq|</th>|;
697   $column_header{linetotallastcost} =
698     qq|<th class=listheading nowrap>| . $locale->text('Extended') . qq|</th>|;
699   $column_header{linetotallistprice} =
700     qq|<th class=listheading nowrap>| . $locale->text('Extended') . qq|</th>|;
701
702   $column_header{image} =
703     qq|<th class=listheading nowrap>| . $locale->text('Image') . qq|</a></th>|;
704   $column_header{drawing} =
705       qq|<th nowrap><a class=listheading href=$callback&sort=drawing>|
706     . $locale->text('Drawing')
707     . qq|</a></th>|;
708   $column_header{microfiche} =
709       qq|<th nowrap><a class=listheading href=$callback&sort=microfiche>|
710     . $locale->text('Microfiche')
711     . qq|</a></th>|;
712
713   $column_header{serialnumber} =
714       qq|<th nowrap><a class=listheading href=$callback&sort=serialnumber>|
715     . $locale->text('Serial Number')
716     . qq|</a></th>|;
717   $column_header{soldtotal} =
718     qq|<th nowrap><a class=listheading href=$callback&sort=soldtotal&revers=$form->{revers}&lastsort=$form->{lastsort}>|
719     . $locale->text('soldtotal')
720     . qq|</a></th>|;
721
722   $form->header;
723   my $colspan = $#column_index + 1;
724
725   print qq|
726 <body>
727
728 <table width=100%>
729   <tr>
730     <th class=listtop colspan=$colspan>$form->{title}</th>
731   </tr>
732   <tr height="5"></tr>
733
734   <tr><td colspan=$colspan>$option</td></tr>
735
736   <tr class=listheading>
737 |;
738
739   map { print "\n$column_header{$_}" } @column_index;
740
741   print qq|
742   </tr>
743   |;
744
745   # add order to callback
746   $form->{callback} = $callback .= "&sort=$form->{sort}";
747
748   # escape callback for href
749   $callback = $form->escape($callback);
750
751   if (@{ $form->{parts} }) {
752     $sameitem = $form->{parts}->[0]->{ $form->{sort} };
753   }
754
755   # insert numbers for top100
756   my $j = 0;
757   foreach my $ref (@{ $form->{parts} }) {
758     $j++;
759     $ref->{number} = $j;
760   }
761
762   # if avaible -> insert choice here
763   if (($form->{ndxs_counter}) > 0) {
764     for (my $i = 1; ($i < $form->{ndxs_counter} + 1); $i++) {
765       $partnumber  = $form->{"totop100_partnumber_$i"};
766       $description = $form->{"totop100_description_$i"};
767       $unit        = $form->{"totop100_unit_$i"};
768       $sellprice   = $form->{"totop100_sellprice_$i"};
769       $soldtotal   = $form->{"totop100_soldtotal_$i"};
770
771       $totop100 .= qq|
772 <input type=hidden name=totop100_partnumber_$i value=$form->{"totop100_partnumber_$i"}>
773 <input type=hidden name=totop100_description_$i value=$form->{"totop100_description_$i"}>
774 <input type=hidden name=totop100_unit_$i value=$form->{"totop100_unit_$i"}>
775 <input type=hidden name=totop100_sellprice_$i value=$form->{"totop100_sellprice_$i"}>
776 <input type=hidden name=totop100_soldtotal_$i value=$form->{"totop100_soldtotal_$i"}>
777       |;
778
779       # insert into list
780       push @{ $form->{parts} },
781         { number      => "",
782           partnumber  => "$partnumber",
783           description => "$description",
784           unit        => "$unit",
785           sellprice   => "$sellprice",
786           soldtotal   => "$soldtotal" };
787     }    #rof
788   }    #fi
789        # build data for columns
790   my $i = 0;
791   foreach my $ref (@{ $form->{parts} }) {
792
793     if ($form->{l_subtotal} eq 'Y' && !$ref->{assemblyitem}) {
794       if ($sameitem ne $ref->{ $form->{sort} }) {
795         parts_subtotal(\@column_index, \$subtotalonhand, \$subtotalsellprice, \$subtotallastcost, \$subtotallistprice);
796         $sameitem = $ref->{ $form->{sort} };
797       }
798     }
799
800     $ref->{exchangerate} = 1 unless $ref->{exchangerate};
801     $ref->{sellprice} *= $ref->{exchangerate};
802     $ref->{listprice} *= $ref->{exchangerate};
803     $ref->{lastcost}  *= $ref->{exchangerate};
804
805     # use this for assemblies
806     $onhand = $ref->{onhand};
807
808     $align = "left";
809     if ($ref->{assemblyitem}) {
810       $align = "right";
811       $onhand = 0 if ($form->{sold});
812     }
813
814     $ref->{description} =~ s/\n/<br>/g;
815
816     $column_data{number} =
817         "<td align=right>"
818       . $form->format_amount(\%myconfig, $ref->{number})
819       . "</td>";
820     $column_data{partnumber} =
821       "<td align=$align>$ref->{partnumber}&nbsp;</a></td>";
822     $column_data{description} = "<td>$ref->{description}&nbsp;</td>";
823     $column_data{partsgroup}  = "<td>$ref->{partsgroup}&nbsp;</td>";
824
825     $column_data{onhand} =
826         "<td align=right>"
827       . $form->format_amount(\%myconfig, $ref->{onhand})
828       . "</td>";
829     $column_data{sellprice} =
830         "<td align=right>"
831       . $form->format_amount(\%myconfig, $ref->{sellprice})
832       . "</td>";
833     $column_data{listprice} =
834         "<td align=right>"
835       . $form->format_amount(\%myconfig, $ref->{listprice})
836       . "</td>";
837     $column_data{lastcost} =
838         "<td align=right>"
839       . $form->format_amount(\%myconfig, $ref->{lastcost})
840       . "</td>";
841
842     $column_data{linetotalsellprice} = "<td align=right>"
843       . $form->format_amount(\%myconfig, $ref->{onhand} * $ref->{sellprice}, 2)
844       . "</td>";
845     $column_data{linetotallastcost} = "<td align=right>"
846       . $form->format_amount(\%myconfig, $ref->{onhand} * $ref->{lastcost}, 2)
847       . "</td>";
848     $column_data{linetotallistprice} = "<td align=right>"
849       . $form->format_amount(\%myconfig, $ref->{onhand} * $ref->{listprice}, 2)
850       . "</td>";
851
852     if (!$ref->{assemblyitem}) {
853       $totalsellprice += $onhand * $ref->{sellprice};
854       $totallastcost  += $onhand * $ref->{lastcost};
855       $totallistprice += $onhand * $ref->{listprice};
856
857       $subtotalonhand    += $onhand;
858       $subtotalsellprice += $onhand * $ref->{sellprice};
859       $subtotallastcost  += $onhand * $ref->{lastcost};
860       $subtotallistprice += $onhand * $ref->{listprice};
861     }
862
863     $column_data{rop} =
864       "<td align=right>"
865       . $form->format_amount(\%myconfig, $ref->{rop}) . "</td>";
866     $column_data{weight} =
867         "<td align=right>"
868       . $form->format_amount(\%myconfig, $ref->{weight})
869       . "</td>";
870     $column_data{unit}        = "<td>$ref->{unit}&nbsp;</td>";
871     $column_data{bin}         = "<td>$ref->{bin}&nbsp;</td>";
872     $column_data{priceupdate} = "<td>$ref->{priceupdate}&nbsp;</td>";
873
874     $column_data{invnumber} =
875       ($ref->{module} ne 'oe')
876       ? "<td><a href=$ref->{module}.pl?action=edit&type=invoice&id=$ref->{trans_id}&callback=$callback>$ref->{invnumber}</a></td>"
877       : "<td>$ref->{invnumber}</td>";
878     $column_data{ordnumber} =
879       ($ref->{module} eq 'oe')
880       ? "<td><a href=$ref->{module}.pl?action=edit&type=$ref->{type}&id=$ref->{trans_id}&callback=$callback>$ref->{ordnumber}</a></td>"
881       : "<td>$ref->{ordnumber}</td>";
882     $column_data{quonumber} =
883       ($ref->{module} eq 'oe' && !$ref->{ordnumber})
884       ? "<td><a href=$ref->{module}.pl?action=edit&type=$ref->{type}&id=$ref->{trans_id}&callback=$callback>$ref->{quonumber}</a></td>"
885       : "<td>$ref->{quonumber}</td>";
886
887     $column_data{name} = "<td>$ref->{name}</td>";
888
889     $column_data{image} =
890       ($ref->{image})
891       ? "<td><a href=$ref->{image}><img src=$ref->{image} height=32 border=0></a></td>"
892       : "<td>&nbsp;</td>";
893     $column_data{drawing} =
894       ($ref->{drawing})
895       ? "<td><a href=$ref->{drawing}>$ref->{drawing}</a></td>"
896       : "<td>&nbsp;</td>";
897     $column_data{microfiche} =
898       ($ref->{microfiche})
899       ? "<td><a href=$ref->{microfiche}>$ref->{microfiche}</a></td>"
900       : "<td>&nbsp;</td>";
901
902     $column_data{serialnumber} = "<td>$ref->{serialnumber}</td>";
903
904     $column_data{soldtotal} = "<td  align=right>$ref->{soldtotal}</td>";
905
906     $i++;
907     $i %= 2;
908     print "<tr class=listrow$i>";
909
910     map { print "\n$column_data{$_}" } @column_index;
911
912     print qq|
913     </tr>
914 |;
915   }
916
917   if ($form->{l_subtotal} eq 'Y') {
918     parts_subtotal(\@column_index, \$subtotalonhand, \$subtotalsellprice, \$subtotallastcost, \$subtotallistprice);
919   }    #fi
920
921   if ($form->{"l_linetotal"}) {
922     map { $column_data{$_} = "<td>&nbsp;</td>" } @column_index;
923     $column_data{linetotalsellprice} =
924         "<th class=listtotal align=right>"
925       . $form->format_amount(\%myconfig, $totalsellprice, 2)
926       . "</th>";
927     $column_data{linetotallastcost} =
928         "<th class=listtotal align=right>"
929       . $form->format_amount(\%myconfig, $totallastcost, 2)
930       . "</th>";
931     $column_data{linetotallistprice} =
932         "<th class=listtotal align=right>"
933       . $form->format_amount(\%myconfig, $totallistprice, 2)
934       . "</th>";
935
936     print "<tr class=listtotal>";
937
938     map { print "\n$column_data{$_}" } @column_index;
939
940     print qq|</tr>
941     |;
942   }
943
944   print qq|
945   <tr><td colspan=$colspan><hr size=3 noshade></td></tr>
946 </table>
947
948 |;
949
950   print qq|
951
952 <br>
953
954 <form method=post action=$form->{script}>
955
956 <input type=hidden name=itemstatus value="$form->{itemstatus}">
957 <input type=hidden name=l_linetotal value="$form->{l_linetotal}">
958 <input type=hidden name=l_partnumber value="$form->{l_partnumber}">
959 <input type=hidden name=l_description value="$form->{l_description}">
960 <input type=hidden name=l_onhand value="$form->{l_onhand}">
961 <input type=hidden name=l_unit value="$form->{l_unit}">
962 <input type=hidden name=l_sellprice value="$form->{l_sellprice}">
963 <input type=hidden name=l_linetotalsellprice value="$form->{l_linetotalsellprice}">
964 <input type=hidden name=sort value="$form->{sort}">
965 <input type=hidden name=revers value="$form->{revers}">
966 <input type=hidden name=lastsort value="$form->{lastsort}">
967 <input type=hidden name=parts value="$form->{parts}">
968
969 <input type=hidden name=bom value="$form->{bom}">
970 <input type=hidden name=titel value="$form->{titel}">
971 <input type=hidden name=searchitems value="$form->{searchitems}">|;
972
973   print $totop100;
974
975   print qq|
976 <!--    <input type=hidden name=ndxs_counter value="$form->{ndxs_counter}">-->
977
978     <input class=submit type=submit name=action value="|
979     . $locale->text('choice') . qq|">
980
981   </form>
982 |;
983
984   $lxdebug->leave_sub();
985 }    # end addtop100
986
987 #
988 # Report for Wares.
989 # Warning, deep magic ahead.
990 # This function parses the requested details, sanity checks them, and converts them into a format thats usable for IC->all_parts
991 #
992 # flags coming from the form:
993 # hardcoded:
994 #  searchitems=part revers=0 lastsort=''
995 #
996 # filter:
997 # partnumber ean description partsgroup serialnumber make model drawing microfiche
998 # transdatefrom transdateto
999 #
1000 # radio:
1001 #  itemstatus = active | onhand | short | obsolete | orphaned
1002 #  action     = continue | top100
1003 #
1004 # checkboxes:
1005 #  bought sold onorder ordered rfq quoted
1006 #  l_partnumber l_description l_serialnumber l_unit l_listprice l_sellprice l_lastcost
1007 #  l_linetotal l_priceupdate l_bin l_rop l_weight l_image l_drawing l_microfiche
1008 #  l_partsgroup l_subtotal l_soldtotal l_deliverydate l_pricegroups
1009 #
1010 # hiddens:
1011 #  nextsub revers lastsort sort ndxs_counter
1012 #
1013 sub generate_report {
1014   $lxdebug->enter_sub();
1015
1016   $auth->assert('part_service_assembly_edit');
1017
1018   my ($revers, $lastsort, $description);
1019
1020   my $cvar_configs = CVar->get_configs('module' => 'IC');
1021
1022   $form->{title} = (ucfirst $form->{searchitems}) . "s";
1023   $form->{title} =~ s/ys$/ies/;
1024   $form->{title} = $locale->text($form->{title});
1025
1026   my %column_defs = (
1027     'bin'                => { 'text' => $locale->text('Bin'), },
1028     'deliverydate'       => { 'text' => $locale->text('deliverydate'), },
1029     'description'        => { 'text' => $locale->text('Part Description'), },
1030     'drawing'            => { 'text' => $locale->text('Drawing'), },
1031     'ean'                => { 'text' => $locale->text('EAN'), },
1032     'image'              => { 'text' => $locale->text('Image'), },
1033     'invnumber'          => { 'text' => $locale->text('Invoice Number'), },
1034     'lastcost'           => { 'text' => $locale->text('Last Cost'), },
1035     'linetotallastcost'  => { 'text' => $locale->text('Extended'), },
1036     'linetotallistprice' => { 'text' => $locale->text('Extended'), },
1037     'linetotalsellprice' => { 'text' => $locale->text('Extended'), },
1038     'listprice'          => { 'text' => $locale->text('List Price'), },
1039     'microfiche'         => { 'text' => $locale->text('Microfiche'), },
1040     'name'               => { 'text' => $locale->text('Name'), },
1041     'onhand'             => { 'text' => $locale->text('Stocked Qty'), },
1042     'ordnumber'          => { 'text' => $locale->text('Order Number'), },
1043     'partnumber'         => { 'text' => $locale->text('Part Number'), },
1044     'partsgroup'         => { 'text' => $locale->text('Group'), },
1045     'priceupdate'        => { 'text' => $locale->text('Updated'), },
1046     'quonumber'          => { 'text' => $locale->text('Quotation'), },
1047     'rop'                => { 'text' => $locale->text('ROP'), },
1048     'sellprice'          => { 'text' => $locale->text('Sell Price'), },
1049     'serialnumber'       => { 'text' => $locale->text('Serial Number'), },
1050     'soldtotal'          => { 'text' => $locale->text('Qty in Selected Records'), },
1051     'transdate'          => { 'text' => $locale->text('Transdate'), },
1052     'unit'               => { 'text' => $locale->text('Unit'), },
1053     'weight'             => { 'text' => $locale->text('Weight'), },
1054     'projectnumber'      => { 'text' => $locale->text('Project Number'), },
1055     'projectdescription' => { 'text' => $locale->text('Project Description'), },
1056   );
1057
1058   $revers     = $form->{revers};
1059   $lastsort   = $form->{lastsort};
1060
1061   # sorting and direction of sorting
1062   # ToDO: change this to the simpler field+direction method
1063   if (($form->{lastsort} eq "") && ($form->{sort} eq undef)) {
1064     $form->{revers}   = 0;
1065     $form->{lastsort} = "partnumber";
1066     $form->{sort}     = "partnumber";
1067   } else {
1068     if ($form->{lastsort} eq $form->{sort}) {
1069       $form->{revers} = 1 - $form->{revers};
1070     } else {
1071       $form->{revers} = 0;
1072       $form->{lastsort} = $form->{sort};
1073     }    #fi
1074   }    #fi
1075
1076   # special case if we have a serialnumber limit search
1077   # serialnumbers are only given in invoices and orders,
1078   # so they can only pop up in bought, sold, rfq, and quoted stuff
1079   $form->{no_sn_joins} = 'Y' if (   !$form->{bought} && !$form->{sold}
1080                                  && !$form->{rfq}    && !$form->{quoted}
1081                                  && ($form->{l_serialnumber} || $form->{serialnumber}));
1082
1083   # special case for any checkbox of bought | sold | onorder | ordered | rfq | quoted.
1084   # if any of these are ticked the behavior changes slightly for lastcost
1085   # since all those are aggregation checks for the legder tables this is an internal switch
1086   # refered to as ledgerchecks
1087   $form->{ledgerchecks} = 'Y' if (   $form->{bought} || $form->{sold} || $form->{onorder}
1088                                   || $form->{ordered} || $form->{rfq} || $form->{quoted});
1089
1090   # if something should be activated if something else is active, enter it here
1091   my %dependencies = (
1092     onhand       => [ qw(l_onhand) ],
1093     short        => [ qw(l_onhand) ],
1094     onorder      => [ qw(l_ordnumber) ],
1095     ordered      => [ qw(l_ordnumber) ],
1096     rfq          => [ qw(l_quonumber) ],
1097     quoted       => [ qw(l_quonumber) ],
1098     bought       => [ qw(l_invnumber) ],
1099     sold         => [ qw(l_invnumber) ],
1100     ledgerchecks => [ qw(l_name) ],
1101     serialnumber => [ qw(l_serialnumber) ],
1102     no_sn_joins  => [ qw(bought sold) ],
1103   );
1104
1105   # get name of partsgroup if id is given
1106   my $pg_name;
1107   if ($form->{partsgroup_id}) {
1108     my $pg = SL::DB::PartsGroup->new(id => $form->{partsgroup_id})->load;
1109     $pg_name = $pg->{'partsgroup'};
1110   }
1111
1112   # these strings get displayed at the top of the results to indicate the user which switches were used
1113   my %optiontexts = (
1114     active        => $locale->text('Active'),
1115     obsolete      => $locale->text('Obsolete'),
1116     orphaned      => $locale->text('Orphaned'),
1117     onhand        => $locale->text('On Hand'),
1118     short         => $locale->text('Short'),
1119     onorder       => $locale->text('On Order'),
1120     ordered       => $locale->text('Ordered'),
1121     rfq           => $locale->text('RFQ'),
1122     quoted        => $locale->text('Quoted'),
1123     bought        => $locale->text('Bought'),
1124     sold          => $locale->text('Sold'),
1125     transdatefrom => $locale->text('From')       . " " . $locale->date(\%myconfig, $form->{transdatefrom}, 1),
1126     transdateto   => $locale->text('To (time)')  . " " . $locale->date(\%myconfig, $form->{transdateto}, 1),
1127     partnumber    => $locale->text('Part Number')      . ": '$form->{partnumber}'",
1128     partsgroup    => $locale->text('Group')            . ": '$form->{partsgroup}'",
1129     partsgroup_id => $locale->text('Group')            . ": '$pg_name'",
1130     serialnumber  => $locale->text('Serial Number')    . ": '$form->{serialnumber}'",
1131     description   => $locale->text('Part Description') . ": '$form->{description}'",
1132     make          => $locale->text('Make')             . ": '$form->{make}'",
1133     model         => $locale->text('Model')            . ": '$form->{model}'",
1134     drawing       => $locale->text('Drawing')          . ": '$form->{drawing}'",
1135     microfiche    => $locale->text('Microfiche')       . ": '$form->{microfiche}'",
1136     l_soldtotal   => $locale->text('Qty in Selected Records'),
1137     ean           => $locale->text('EAN')              . ": '$form->{ean}'",
1138   );
1139
1140   my @itemstatus_keys = qw(active obsolete orphaned onhand short);
1141   my @callback_keys   = qw(onorder ordered rfq quoted bought sold partnumber partsgroup partsgroup_id serialnumber description make model
1142                            drawing microfiche l_soldtotal l_deliverydate transdatefrom transdateto ean);
1143
1144   # calculate dependencies
1145   for (@itemstatus_keys, @callback_keys) {
1146     next if ($form->{itemstatus} ne $_ && !$form->{$_});
1147     map { $form->{$_} = 'Y' } @{ $dependencies{$_} } if $dependencies{$_};
1148   }
1149
1150   # generate callback and optionstrings
1151   my @options;
1152   for my  $key (@itemstatus_keys, @callback_keys) {
1153     next if ($form->{itemstatus} ne $key && !$form->{$key});
1154     push @options, $optiontexts{$key};
1155   }
1156
1157   # special case for lastcost
1158   if ($form->{ledgerchecks}){
1159     # ledgerchecks don't know about sellprice or lastcost. they just return a
1160     # price. so rename sellprice to price, and drop lastcost.
1161     $column_defs{sellprice}{text} = $locale->text('Price');
1162     $form->{l_lastcost} = ""
1163   }
1164
1165   if ($form->{description}) {
1166     $description = $form->{description};
1167     $description =~ s/\n/<br>/g;
1168   }
1169
1170   if ($form->{l_linetotal}) {
1171     $form->{l_qty} = "Y";
1172     $form->{l_linetotalsellprice} = "Y" if $form->{l_sellprice};
1173     $form->{l_linetotallastcost}  = $form->{searchitems} eq 'assembly' && !$form->{bom} ? "" : 'Y' if  $form->{l_lastcost};
1174     $form->{l_linetotallistprice} = "Y" if $form->{l_listprice};
1175   }
1176
1177   if ($form->{searchitems} eq 'service') {
1178
1179     # remove bin, weight and rop from list
1180     map { $form->{"l_$_"} = "" } qw(bin weight rop);
1181
1182     $form->{l_onhand} = "";
1183
1184     # qty is irrelevant unless bought or sold
1185     if (   $form->{bought}
1186         || $form->{sold}
1187         || $form->{onorder}
1188         || $form->{ordered}
1189         || $form->{rfq}
1190         || $form->{quoted}) {
1191 #      $form->{l_onhand} = "Y";
1192     } else {
1193       $form->{l_linetotalsellprice} = "";
1194       $form->{l_linetotallastcost}  = "";
1195     }
1196   }
1197
1198   # soldtotal doesn't make sense with more than one bsooqr option.
1199   # so reset it to sold (the most common option), and issue a warning
1200   my @bsooqr = qw(sold bought onorder ordered rfq quoted);
1201   if ($form->{l_subtotal} && 1 < grep { $form->{$_} } @bsooqr) {
1202     my $enabled       = first { $form->{$_} } @bsooqr;
1203     $form->{$_}       = ''   for @bsooqr;
1204     $form->{$enabled} = 'Y';
1205
1206     push @options, $::locale->text('Subtotal cannot distinguish betweens record types. Only one of the selected record types will be displayed: #1', $optiontexts{$enabled});
1207   }
1208
1209   IC->all_parts(\%myconfig, \%$form);
1210
1211   my @columns = qw(
1212     partnumber description partsgroup bin onhand rop soldtotal unit listprice
1213     linetotallistprice sellprice linetotalsellprice lastcost linetotallastcost
1214     priceupdate weight image drawing microfiche invnumber ordnumber quonumber
1215     transdate name serialnumber deliverydate ean projectnumber projectdescription
1216   );
1217
1218   my $pricegroups = SL::DB::Manager::Pricegroup->get_all(sort => 'id');
1219   my @pricegroup_columns;
1220   my %column_defs_pricegroups;
1221   if ($form->{l_pricegroups}) {
1222     @pricegroup_columns      = map { "pricegroup_" . $_->id } @{ $pricegroups };
1223     %column_defs_pricegroups = map {
1224       "pricegroup_" . $_->id => {
1225         text    => $::locale->text('Pricegroup') . ' ' . $_->pricegroup,
1226         visible => 1,
1227       },
1228     }  @{ $pricegroups };
1229   }
1230   push @columns, @pricegroup_columns;
1231
1232   my @includeable_custom_variables = grep { $_->{includeable} } @{ $cvar_configs };
1233   my @searchable_custom_variables  = grep { $_->{searchable} }  @{ $cvar_configs };
1234   my %column_defs_cvars            = map { +"cvar_$_->{name}" => { 'text' => $_->{description} } } @includeable_custom_variables;
1235
1236   push @columns, map { "cvar_$_->{name}" } @includeable_custom_variables;
1237
1238   %column_defs = (%column_defs, %column_defs_cvars, %column_defs_pricegroups);
1239   map { $column_defs{$_}->{visible} ||= $form->{"l_$_"} ? 1 : 0 } @columns;
1240   map { $column_defs{$_}->{align}   = 'right' } qw(onhand sellprice listprice lastcost linetotalsellprice linetotallastcost linetotallistprice rop weight soldtotal), @pricegroup_columns;
1241
1242   my @hidden_variables = (qw(l_subtotal l_linetotal searchitems itemstatus bom l_pricegroups), @itemstatus_keys, @callback_keys,
1243                               map({ "cvar_$_->{name}" } @searchable_custom_variables), map { "l_$_" } @columns);
1244
1245   my $callback         = build_std_url('action=generate_report', grep { $form->{$_} } @hidden_variables);
1246
1247   my @sort_full        = qw(partnumber description onhand soldtotal deliverydate);
1248   my @sort_no_revers   = qw(partsgroup bin priceupdate invnumber ordnumber quonumber name image drawing serialnumber);
1249
1250   foreach my $col (@sort_full) {
1251     $column_defs{$col}->{link} = join '&', $callback, "sort=$col", map { "$_=" . E($form->{$_}) } qw(revers lastsort);
1252   }
1253   map { $column_defs{$_}->{link} = "${callback}&sort=$_" } @sort_no_revers;
1254
1255   # add order to callback
1256   $form->{callback} = join '&', ($callback, map { "${_}=" . E($form->{$_}) } qw(sort revers));
1257
1258   my $report = SL::ReportGenerator->new(\%myconfig, $form);
1259
1260   my %attachment_basenames = (
1261     'part'     => $locale->text('part_list'),
1262     'service'  => $locale->text('service_list'),
1263     'assembly' => $locale->text('assembly_list'),
1264   );
1265
1266   $report->set_options('top_info_text'         => $locale->text('Options') . ': ' . join(', ', grep $_, @options),
1267                        'raw_bottom_info_text'  => $form->parse_html_template('ic/generate_report_bottom'),
1268                        'output_format'         => 'HTML',
1269                        'title'                 => $form->{title},
1270                        'attachment_basename'   => $attachment_basenames{$form->{searchitems}} . strftime('_%Y%m%d', localtime time),
1271   );
1272   $report->set_options_from_form();
1273   $locale->set_numberformat_wo_thousands_separator(\%myconfig) if lc($report->{options}->{output_format}) eq 'csv';
1274
1275   $report->set_columns(%column_defs);
1276   $report->set_column_order(@columns);
1277
1278   $report->set_export_options('generate_report', @hidden_variables, qw(sort revers));
1279
1280   $report->set_sort_indicator($form->{sort}, $form->{revers} ? 0 : 1);
1281
1282   CVar->add_custom_variables_to_report('module'         => 'IC',
1283                                        'trans_id_field' => 'id',
1284                                        'configs'        => $cvar_configs,
1285                                        'column_defs'    => \%column_defs,
1286                                        'data'           => $form->{parts});
1287
1288   CVar->add_custom_variables_to_report('module'         => 'IC',
1289                                        'sub_module'     => sub { $_[0]->{ioi} },
1290                                        'trans_id_field' => 'ioi_id',
1291                                        'configs'        => $cvar_configs,
1292                                        'column_defs'    => \%column_defs,
1293                                        'data'           => $form->{parts});
1294
1295   my @subtotal_columns = qw(sellprice listprice lastcost);
1296   my %subtotals = map { $_ => 0 } ('onhand', @subtotal_columns);
1297   my %totals    = map { $_ => 0 } @subtotal_columns;
1298   my $idx       = 0;
1299   my $same_item = @{ $form->{parts} } ? $form->{parts}[0]{ $form->{sort} } : undef;
1300
1301   my $defaults  = AM->get_defaults();
1302
1303   # postprocess parts
1304   foreach my $ref (@{ $form->{parts} }) {
1305
1306     # fresh row, for inserting later
1307     my $row = { map { $_ => { 'data' => $ref->{$_} } } @columns };
1308
1309     $ref->{exchangerate} ||= 1;
1310     $ref->{price_factor} ||= 1;
1311     $ref->{sellprice}     *= $ref->{exchangerate} / $ref->{price_factor};
1312     $ref->{listprice}     *= $ref->{exchangerate} / $ref->{price_factor};
1313     $ref->{lastcost}      *= $ref->{exchangerate} / $ref->{price_factor};
1314
1315     # use this for assemblies
1316     my $soldtotal = $ref->{soldtotal};
1317
1318     if ($ref->{assemblyitem}) {
1319       $row->{partnumber}{align}   = 'right';
1320       $row->{soldtotal}{data}     = 0;
1321       $soldtotal                  = 0 if ($form->{sold});
1322     }
1323
1324     my $edit_link               = build_std_url('action=edit', 'id=' . E($ref->{id}), 'callback');
1325     $row->{partnumber}->{link}  = $edit_link;
1326     $row->{description}->{link} = $edit_link;
1327
1328     foreach (qw(sellprice listprice lastcost)) {
1329       $row->{$_}{data}            = $form->format_amount(\%myconfig, $ref->{$_}, 2);
1330       $row->{"linetotal$_"}{data} = $form->format_amount(\%myconfig, $ref->{onhand} * $ref->{$_}, 2);
1331     }
1332     foreach ( @pricegroup_columns ) {
1333       $row->{$_}{data}            = $form->format_amount(\%myconfig, $ref->{"$_"}, 2);
1334     };
1335
1336
1337     map { $row->{$_}{data} = $form->format_amount(\%myconfig, $ref->{$_}); } qw(onhand rop weight soldtotal);
1338
1339     $row->{weight}->{data} .= ' ' . $defaults->{weightunit};
1340
1341     if (!$ref->{assemblyitem}) {
1342       foreach my $col (@subtotal_columns) {
1343         $totals{$col}    += $soldtotal * $ref->{$col};
1344         $subtotals{$col} += $soldtotal * $ref->{$col};
1345       }
1346
1347       $subtotals{soldtotal} += $soldtotal;
1348     }
1349
1350     # set module stuff
1351     if ($ref->{module} eq 'oe') {
1352       # fĂĽr oe gibt es vier fälle, jeweils nach kunde oder lieferant unterschiedlich:
1353       #
1354       # | ist bestellt  | Vom Kunde bestellt |  -> edit_oe_ord_link
1355       # | Anfrage       | Angebot            |  -> edit_oe_quo_link
1356
1357       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');
1358       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');
1359
1360       $row->{ordnumber}{link} = $edit_oe_ord_link;
1361       $row->{quonumber}{link} = $edit_oe_quo_link if (!$ref->{ordnumber});
1362
1363     } else {
1364       $row->{invnumber}{link} = build_std_url("script=$ref->{module}.pl", 'action=edit', 'type=invoice', 'id=' . E($ref->{trans_id}), 'callback');
1365     }
1366
1367     # set properties of images
1368     if ($ref->{image} && (lc $report->{options}->{output_format} eq 'html')) {
1369       $row->{image}{data}     = '';
1370       $row->{image}{raw_data} = '<a href="' . H($ref->{image}) . '"><img src="' . H($ref->{image}) . '" height="32" border="0"></a>';
1371     }
1372     map { $row->{$_}{link} = $ref->{$_} } qw(drawing microfiche);
1373
1374     $report->add_data($row);
1375
1376     my $next_ref = $form->{parts}[$idx + 1];
1377
1378     # insert subtotal rows
1379     if (($form->{l_subtotal} eq 'Y') &&
1380         (!$next_ref ||
1381          (!$next_ref->{assemblyitem} && ($same_item ne $next_ref->{ $form->{sort} })))) {
1382       my $row = { map { $_ => { 'class' => 'listsubtotal', } } @columns };
1383
1384       if (($form->{searchitems} ne 'assembly') || !$form->{bom}) {
1385         $row->{soldtotal}->{data} = $form->format_amount(\%myconfig, $subtotals{soldtotal});
1386       }
1387
1388       map { $row->{"linetotal$_"}->{data} = $form->format_amount(\%myconfig, $subtotals{$_}, 2) } @subtotal_columns;
1389       map { $subtotals{$_} = 0 } ('soldtotal', @subtotal_columns);
1390
1391       $report->add_data($row);
1392
1393       $same_item = $next_ref->{ $form->{sort} };
1394     }
1395
1396     $idx++;
1397   }
1398
1399   if ($form->{"l_linetotal"} && !$form->{report_generator_csv_options_for_import}) {
1400     my $row = { map { $_ => { 'class' => 'listtotal', } } @columns };
1401
1402     map { $row->{"linetotal$_"}->{data} = $form->format_amount(\%myconfig, $totals{$_}, 2) } @subtotal_columns;
1403
1404     $report->add_separator();
1405     $report->add_data($row);
1406   }
1407
1408   $report->generate_with_headers();
1409
1410   $lxdebug->leave_sub();
1411 }    #end generate_report
1412
1413 sub parts_subtotal {
1414   $lxdebug->enter_sub();
1415
1416   $auth->assert('part_service_assembly_edit');
1417
1418   my (%column_data);
1419   my ($column_index, $subtotalonhand, $subtotalsellprice, $subtotallastcost, $subtotallistprice) = @_;
1420
1421   map { $column_data{$_} = "<td>&nbsp;</td>" } @{ $column_index };
1422   $$subtotalonhand = 0 if ($form->{searchitems} eq 'assembly' && $form->{bom});
1423
1424   $column_data{onhand} =
1425       "<th class=listsubtotal align=right>"
1426     . $form->format_amount(\%myconfig, $$subtotalonhand)
1427     . "</th>";
1428
1429   $column_data{linetotalsellprice} =
1430       "<th class=listsubtotal align=right>"
1431     . $form->format_amount(\%myconfig, $$subtotalsellprice, 2)
1432     . "</th>";
1433   $column_data{linetotallistprice} =
1434       "<th class=listsubtotal align=right>"
1435     . $form->format_amount(\%myconfig, $$subtotallistprice, 2)
1436     . "</th>";
1437   $column_data{linetotallastcost} =
1438       "<th class=listsubtotal align=right>"
1439     . $form->format_amount(\%myconfig, $$subtotallastcost, 2)
1440     . "</th>";
1441
1442   $$subtotalonhand    = 0;
1443   $$subtotalsellprice = 0;
1444   $$subtotallistprice = 0;
1445   $$subtotallastcost  = 0;
1446
1447   print "<tr class=listsubtotal>";
1448
1449   map { print "\n$column_data{$_}" } @{ $column_index };
1450
1451   print qq|
1452   </tr>
1453 |;
1454
1455   $lxdebug->leave_sub();
1456 }
1457
1458 sub edit {
1459   $lxdebug->enter_sub();
1460
1461   $auth->assert('part_service_assembly_edit');
1462
1463   # show history button
1464   $form->{javascript} = qq|<script type="text/javascript" src="js/show_history.js"></script>|;
1465   #/show hhistory button
1466   IC->get_part(\%myconfig, \%$form);
1467
1468   $form->{"original_partnumber"} = $form->{"partnumber"};
1469
1470   my $title      = 'Edit ' . ucfirst $form->{item};
1471   $form->{title} = $locale->text($title);
1472
1473   &link_part;
1474   &display_form;
1475
1476   $lxdebug->leave_sub();
1477 }
1478
1479 sub link_part {
1480   $lxdebug->enter_sub();
1481
1482   $auth->assert('part_service_assembly_edit');
1483
1484   IC->create_links("IC", \%myconfig, \%$form);
1485
1486   # currencies
1487   map({ $form->{selectcurrency} .= "<option>$_\n" }
1488       split(/:/, $form->{currencies}));
1489
1490   # parts and assemblies have the same links
1491   my $item = $form->{item};
1492   if ($form->{item} eq 'assembly') {
1493     $item = 'part';
1494   }
1495
1496   # build the popup menus
1497   $form->{taxaccounts} = "";
1498   foreach my $key (keys %{ $form->{IC_links} }) {
1499     foreach my $ref (@{ $form->{IC_links}{$key} }) {
1500
1501       # if this is a tax field
1502       if ($key =~ /IC_tax/) {
1503         if ($key =~ /\Q$item\E/) {
1504           $form->{taxaccounts} .= "$ref->{accno} ";
1505           $form->{"IC_tax_$ref->{accno}_description"} =
1506             "$ref->{accno}--$ref->{description}";
1507
1508           if ($form->{id}) {
1509             if ($form->{amount}{ $ref->{accno} }) {
1510               $form->{"IC_tax_$ref->{accno}"} = "checked";
1511             }
1512           } else {
1513             $form->{"IC_tax_$ref->{accno}"} = "checked";
1514           }
1515         }
1516       } else {
1517
1518         $form->{"select$key"} .=
1519           "<option $ref->{selected}>$ref->{accno}--$ref->{description}\n";
1520         if ($form->{amount}{$key} eq $ref->{accno}) {
1521           $form->{$key} = "$ref->{accno}--$ref->{description}";
1522         }
1523
1524       }
1525     }
1526   }
1527   chop $form->{taxaccounts};
1528
1529   if (($form->{item} eq "part") || ($form->{item} eq "assembly")) {
1530     $form->{selectIC_income}  = $form->{selectIC_sale};
1531     $form->{selectIC_expense} = $form->{selectIC_cogs};
1532     $form->{IC_income}        = $form->{IC_sale};
1533     $form->{IC_expense}       = $form->{IC_cogs};
1534   }
1535
1536   delete $form->{IC_links};
1537   delete $form->{amount};
1538
1539   $form->get_partsgroup(\%myconfig, { all => 1 });
1540
1541   $form->{partsgroup} = "$form->{partsgroup}--$form->{partsgroup_id}";
1542
1543   if (@{ $form->{all_partsgroup} }) {
1544     $form->{selectpartsgroup} = qq|<option>\n|;
1545     map { $form->{selectpartsgroup} .= qq|<option value="$_->{partsgroup}--$_->{id}">$_->{partsgroup}\n| } @{ $form->{all_partsgroup} };
1546   }
1547
1548   if ($form->{item} eq 'assembly') {
1549
1550     foreach my $i (1 .. $form->{assembly_rows}) {
1551       if ($form->{"partsgroup_id_$i"}) {
1552         $form->{"partsgroup_$i"} =
1553           qq|$form->{"partsgroup_$i"}--$form->{"partsgroup_id_$i"}|;
1554       }
1555     }
1556     $form->get_partsgroup(\%myconfig);
1557
1558     if (@{ $form->{all_partsgroup} }) {
1559       $form->{selectassemblypartsgroup} = qq|<option>\n|;
1560
1561       map {
1562         $form->{selectassemblypartsgroup} .=
1563           qq|<option value="$_->{partsgroup}--$_->{id}">$_->{partsgroup}\n|
1564       } @{ $form->{all_partsgroup} };
1565     }
1566   }
1567   $lxdebug->leave_sub();
1568 }
1569
1570 sub form_header {
1571   $lxdebug->enter_sub();
1572
1573   $auth->assert('part_service_assembly_edit');
1574
1575   $form->{pg_keys}          = sub { "$_[0]->{partsgroup}--$_[0]->{id}" };
1576   $form->{description_area} = ($form->{rows} = $form->numtextrows($form->{description}, 40)) > 1;
1577   $form->{notes_rows}       =  max 4, $form->numtextrows($form->{notes}, 40), $form->numtextrows($form->{formel}, 40);
1578
1579   map { $form->{"is_$_"}  = ($form->{item} eq $_) } qw(part service assembly);
1580   map { $form->{$_}       =~ s/"/&quot;/g;        } qw(unit);
1581
1582   $form->get_lists('price_factors' => 'ALL_PRICE_FACTORS',
1583                    'partsgroup'    => 'all_partsgroup',
1584                    'vendors'       => 'ALL_VENDORS',);
1585
1586
1587   IC->retrieve_buchungsgruppen(\%myconfig, $form);
1588   @{ $form->{BUCHUNGSGRUPPEN} } = grep { $_->{id} eq $form->{buchungsgruppen_id} || ($form->{id} && $form->{orphaned}) || !$form->{id} } @{ $form->{BUCHUNGSGRUPPEN} };
1589
1590   # use JavaScript Calendar or not (yes!)
1591   $form->{jsscript} = 1;
1592
1593   my $units = AM->retrieve_units(\%myconfig, $form);
1594   $form->{ALL_UNITS} = [ map +{ name => $_ }, sort { $units->{$a}{sortkey} <=> $units->{$b}{sortkey} } keys %$units ];
1595
1596   $form->{defaults} = AM->get_defaults();
1597
1598   $form->{fokus} = "ic.partnumber";
1599
1600   $form->{CUSTOM_VARIABLES} = CVar->get_custom_variables('module' => 'IC', 'trans_id' => $form->{id});
1601
1602   CVar->render_inputs('variables' => $form->{CUSTOM_VARIABLES}, show_disabled_message => 1)
1603     if (scalar @{ $form->{CUSTOM_VARIABLES} });
1604
1605   $form->header;
1606   #print $form->parse_html_template('ic/form_header', { ALL_PRICE_FACTORS => $form->{ALL_PRICE_FACTORS},
1607   #                                                     ALL_UNITS         => $form->{ALL_UNITS},
1608   #                                                     BUCHUNGSGRUPPEN   => $form->{BUCHUNGSGRUPPEN},
1609   #                                                     payment_terms     => $form->{payment_terms},
1610   #                                                     all_partsgroup    => $form->{all_partsgroup}});
1611   print $form->parse_html_template('ic/form_header');
1612   $lxdebug->leave_sub();
1613 }
1614
1615 sub form_footer {
1616   $lxdebug->enter_sub();
1617
1618   $auth->assert('part_service_assembly_edit');
1619
1620   print $form->parse_html_template('ic/form_footer');
1621
1622   $lxdebug->leave_sub();
1623 }
1624
1625 sub makemodel_row {
1626   $lxdebug->enter_sub();
1627   my ($numrows) = @_;
1628   #hli
1629   my @mm_data = grep { any { $_ ne '' } @$_{qw(make model)} } map +{ make => $form->{"make_$_"}, model => $form->{"model_$_"}, lastcost => $form->{"lastcost_$_"}, lastupdate => $form->{"lastupdate_$_"}, sortorder => $form->{"sortorder_$_"} }, 1 .. $numrows;
1630   delete @{$form}{grep { m/^make_\d+/ || m/^model_\d+/ } keys %{ $form }};
1631   print $form->parse_html_template('ic/makemodel', { MM_DATA => [ @mm_data, {} ], mm_rows => scalar @mm_data + 1 });
1632
1633   $lxdebug->leave_sub();
1634 }
1635
1636 sub assembly_row {
1637   $lxdebug->enter_sub();
1638   my ($numrows) = @_;
1639   my (@column_index);
1640   my ($nochange, $callback, $previousform, $linetotal, $line_purchase_price, $href);
1641
1642   @column_index = qw(runningnumber qty unit bom partnumber description partsgroup lastcost total);
1643
1644   if ($form->{previousform}) {
1645     $nochange     = 1;
1646     @column_index = qw(qty unit bom partnumber description partsgroup total);
1647   } else {
1648
1649     # change callback
1650     $form->{old_callback} = $form->{callback};
1651     $callback             = $form->{callback};
1652     $form->{callback}     = "$form->{script}?action=display_form";
1653
1654     # delete action
1655     map { delete $form->{$_} } qw(action header);
1656
1657     # save form variables in a previousform variable
1658     my %form_to_save = map   { ($_ => m/^ (?: listprice | sellprice | lastcost ) $/x ? $form->format_amount(\%myconfig, $form->{$_}) : $form->{$_}) }
1659                        keys %{ $form };
1660     $previousform    = $::auth->save_form_in_session(form => \%form_to_save);
1661
1662     $form->{callback} = $callback;
1663     $form->{assemblytotal} = 0;
1664     $form->{assembly_purchase_price_total} = 0;
1665     $form->{weight}        = 0;
1666   }
1667
1668   my %header = (
1669    runningnumber => { text =>  $locale->text('No.'),              nowrap => 1, width => '5%'  },
1670    qty           => { text =>  $locale->text('Qty'),              nowrap => 1, width => '10%' },
1671    unit          => { text =>  $locale->text('Unit'),             nowrap => 1, width => '5%'  },
1672    partnumber    => { text =>  $locale->text('Part Number'),      nowrap => 1, width => '20%' },
1673    description   => { text =>  $locale->text('Part Description'), nowrap => 1, width => '50%' },
1674    lastcost      => { text =>  $locale->text('Purchase Prices'),  nowrap => 1, width => '50%' },
1675    total         => { text =>  $locale->text('Sale Prices'),      nowrap => 1,                },
1676    bom           => { text =>  $locale->text('BOM'),                                          },
1677    partsgroup    => { text =>  $locale->text('Group'),                                        },
1678   );
1679
1680   my @ROWS;
1681
1682   for my $i (1 .. $numrows) {
1683     my (%row, @row_hiddens);
1684
1685     $form->{"partnumber_$i"} =~ s/\"/&quot;/g;
1686
1687     $linetotal           = $form->round_amount($form->{"sellprice_$i"} * $form->{"qty_$i"} / ($form->{"price_factor_$i"} || 1), 4);
1688     $line_purchase_price = $form->round_amount($form->{"lastcost_$i"} *  $form->{"qty_$i"} / ($form->{"price_factor_$i"} || 1), 4);
1689     $form->{assemblytotal}                  += $linetotal;
1690     $form->{assembly_purchase_price_total}  += $line_purchase_price;
1691     $form->{"qty_$i"}    = $form->format_amount(\%myconfig, $form->{"qty_$i"});
1692     $linetotal           = $form->format_amount(\%myconfig, $linetotal, 2);
1693     $line_purchase_price = $form->format_amount(\%myconfig, $line_purchase_price, 2);
1694     $href                = qq|$form->{script}?action=edit&id=$form->{"id_$i"}&rowcount=$i&previousform=$previousform|;
1695     map { $row{$_}{data} = "" } qw(qty unit partnumber description bom partsgroup runningnumber);
1696
1697     # last row
1698     if (($i >= 1) && ($i == $numrows)) {
1699       if (!$form->{previousform}) {
1700         $row{partnumber}{data}  = qq|<input name="partnumber_$i" size=15 value="$form->{"partnumber_$i"}">|;
1701         $row{qty}{data}         = qq|<input name="qty_$i" size=5 value="$form->{"qty_$i"}">|;
1702         $row{description}{data} = qq|<input name="description_$i" size=40 value="$form->{"description_$i"}">|;
1703         $row{partsgroup}{data}  = qq|<input name="partsgroup_$i" size=10 value="$form->{"partsgroup_$i"}">|;
1704       }
1705     # other rows
1706     } else {
1707       if ($form->{previousform}) {
1708         push @row_hiddens,          qw(qty bom);
1709         $row{partnumber}{data}    = $form->{"partnumber_$i"};
1710         $row{qty}{data}           = $form->{"qty_$i"};
1711         $row{bom}{data}           = $form->{"bom_$i"} ? "x" : "&nbsp;";
1712         $row{qty}{align}          = 'right';
1713       } else {
1714         $row{partnumber}{data}    = qq|<a href=$href>$form->{"partnumber_$i"}</a>|;
1715         $row{qty}{data}           = qq|<input name="qty_$i" size=5 value="$form->{"qty_$i"}">|;
1716         $row{runningnumber}{data} = qq|<input name="runningnumber_$i" size=3 value="$i">|;
1717         $row{bom}{data}   = sprintf qq|<input name="bom_$i" type=checkbox class=checkbox value=1 %s>|,
1718                                        $form->{"bom_$i"} ? 'checked' : '';
1719       }
1720       push @row_hiddens,        qw(unit description partnumber partsgroup);
1721       $row{unit}{data}        = $form->{"unit_$i"};
1722       #Bei der Artikelbeschreibung und Warengruppe können Sonderzeichen verwendet
1723       #werden, die den HTML Code stören. Daher sollen diese im Template escaped werden
1724       #dies geschieht, wenn die Variable escape gesetzt ist
1725       $row{description}{data}   = $form->{"description_$i"};
1726       $row{description}{escape} = 1;
1727       $row{partsgroup}{data}    = $form->{"partsgroup_$i"};
1728       $row{partsgroup}{escape}  = 1;
1729       $row{bom}{align}          = 'center';
1730     }
1731
1732     $row{lastcost}{data}      = $line_purchase_price;
1733     $row{total}{data}         = $linetotal;
1734     $row{lastcost}{align}     = 'right';
1735     $row{total}{align}        = 'right';
1736     $row{deliverydate}{align} = 'right';
1737
1738     push @row_hiddens, qw(id sellprice lastcost weight price_factor_id price_factor);
1739     $row{hiddens} = [ map +{ name => "${_}_$i", value => $form->{"${_}_$i"} }, @row_hiddens ];
1740
1741     push @ROWS, \%row;
1742   }
1743
1744   print $form->parse_html_template('ic/assembly_row', { COLUMNS => \@column_index, ROWS => \@ROWS, HEADER => \%header });
1745
1746   $lxdebug->leave_sub();
1747 }
1748
1749 sub update {
1750   $lxdebug->enter_sub();
1751
1752   # parse pricegroups. and no, don't rely on check_form for this...
1753   map { $form->{"price_$_"} = $form->parse_amount(\%myconfig, $form->{"price_$_"}) } 1 .. $form->{price_rows};
1754
1755   # same for makemodel lastcosts
1756   # but parse_amount not necessary for assembly component lastcosts
1757   unless ($form->{item} eq "assembly") {
1758     map { $form->{"lastcost_$_"} = $form->parse_amount(\%myconfig, $form->{"lastcost_$_"}) } 1 .. $form->{"makemodel_rows"};
1759   };
1760
1761   if ($form->{item} eq "assembly") {
1762     my $i = $form->{assembly_rows};
1763
1764     # if last row is empty check the form otherwise retrieve item
1765     if (   ($form->{"partnumber_$i"} eq "")
1766         && ($form->{"description_$i"} eq "")
1767         && ($form->{"partsgroup_$i"}  eq "")) {
1768
1769       &check_form;
1770
1771     } else {
1772
1773       IC->assembly_item(\%myconfig, \%$form);
1774
1775       my $rows = scalar @{ $form->{item_list} };
1776
1777       if ($rows) {
1778         $form->{"qty_$i"} = 1 unless ($form->{"qty_$i"});
1779
1780         if ($rows > 1) {
1781           $form->{makemodel_rows}--;
1782           select_item(mode => 'IC');
1783           ::end_of_request();
1784         } else {
1785           map { $form->{item_list}[$i]{$_} =~ s/\"/&quot;/g }
1786             qw(partnumber description unit partsgroup);
1787           map { $form->{"${_}_$i"} = $form->{item_list}[0]{$_} }
1788             keys %{ $form->{item_list}[0] };
1789           $form->{"runningnumber_$i"} = $form->{assembly_rows};
1790           $form->{assembly_rows}++;
1791
1792           &check_form;
1793
1794         }
1795
1796       } else {
1797
1798         $form->{rowcount} = $i;
1799         $form->{assembly_rows}++;
1800
1801         &new_item;
1802
1803       }
1804     }
1805
1806   } elsif (($form->{item} eq 'part') || ($form->{item} eq 'service')) {
1807     &check_form;
1808   }
1809
1810   $lxdebug->leave_sub();
1811 }
1812
1813 sub save {
1814   $lxdebug->enter_sub();
1815
1816   $auth->assert('part_service_assembly_edit');
1817
1818   my ($parts_id, %newform, $amount, $callback);
1819
1820   # check if there is a part number - commented out, cause there is an automatic allocation of numbers
1821   # $form->isblank("partnumber", $locale->text(ucfirst $form->{item}." Part Number missing!"));
1822
1823   # check if there is a description
1824   $form->isblank("description", $locale->text("Part Description missing!"));
1825
1826   $form->error($locale->text("Inventory quantity must be zero before you can set this $form->{item} obsolete!"))
1827     if $form->{obsolete} && $form->{onhand} * 1 && $form->{item} ne 'service';
1828
1829   if (!$form->{buchungsgruppen_id}) {
1830     $form->error($locale->text("Parts must have an entry type.") . " " .
1831      $locale->text("If you see this message, you most likely just setup your LX-Office and haven't added any entry types. If this is the case, the option is accessible for administrators in the System menu.")
1832     );
1833   }
1834
1835   $form->error($locale->text('Description must not be empty!')) unless $form->{description};
1836   $form->error($locale->text('Partnumber must not be set to empty!')) if $form->{id} && !$form->{partnumber};
1837
1838   # save part
1839   if (IC->save(\%myconfig, \%$form) == 3) {
1840     $form->error($locale->text('Partnumber not unique!'));
1841   }
1842   # saving the history
1843   if(!exists $form->{addition}) {
1844     $form->{snumbers} = qq|partnumber_| . $form->{partnumber};
1845     $form->{addition} = "SAVED";
1846     $form->save_history;
1847   }
1848   # /saving the history
1849   $parts_id = $form->{id};
1850
1851   my $i;
1852   # load previous variables
1853   if ($form->{previousform}) {
1854
1855     # save the new form variables before splitting previousform
1856     map { $newform{$_} = $form->{$_} } keys %$form;
1857
1858     # don't trample on previous variables
1859     map { delete $form->{$_} } keys %newform;
1860
1861     my $ic_cvar_configs = CVar->get_configs(module => 'IC');
1862     my @ic_cvar_fields  = map { "cvar_$_->{name}" } @{ $ic_cvar_configs };
1863
1864     # restore original values
1865     $::auth->restore_form_from_session($newform{previousform}, form => $form);
1866     $form->{taxaccounts} = $newform{taxaccount2};
1867
1868     if ($form->{item} eq 'assembly') {
1869
1870       # undo number formatting
1871       map { $form->{$_} = $form->parse_amount(\%myconfig, $form->{$_}) }
1872         qw(weight listprice sellprice rop);
1873
1874       $form->{assembly_rows}--;
1875       $i = $form->{assembly_rows};
1876       $form->{"qty_$i"} = 1 unless ($form->{"qty_$i"});
1877
1878       $form->{sellprice} -= $form->{"sellprice_$i"} * $form->{"qty_$i"};
1879       $form->{weight}    -= $form->{"weight_$i"} * $form->{"qty_$i"};
1880
1881       # change/add values for assembly item
1882       map { $form->{"${_}_$i"} = $newform{$_} } qw(partnumber description bin unit weight listprice sellprice inventory_accno income_accno expense_accno price_factor_id);
1883       map { $form->{"ic_${_}_$i"} = $newform{$_} } @ic_cvar_fields;
1884
1885       # das ist __voll__ bekloppt, dass so auszurechnen jb 22.5.09
1886       #$form->{sellprice} += $form->{"sellprice_$i"} * $form->{"qty_$i"};
1887       $form->{weight}    += $form->{"weight_$i"} * $form->{"qty_$i"};
1888
1889     } else {
1890
1891       # set values for last invoice/order item
1892       $i = $form->{rowcount};
1893       $form->{"qty_$i"} = 1 unless ($form->{"qty_$i"});
1894
1895       map { $form->{"${_}_$i"} = $newform{$_} } qw(partnumber description bin unit listprice inventory_accno income_accno expense_accno sellprice lastcost price_factor_id);
1896       map { $form->{"ic_${_}_$i"} = $newform{$_} } @ic_cvar_fields;
1897
1898       $form->{"longdescription_$i"} = $newform{notes};
1899
1900       $form->{"sellprice_$i"} = $newform{lastcost} if ($form->{vendor_id});
1901
1902       if ($form->{exchangerate} != 0) {
1903         $form->{"sellprice_$i"} /= $form->{exchangerate};
1904       }
1905
1906       map { $form->{"taxaccounts_$i"} .= "$_ " } split / /, $newform{taxaccount};
1907       chop $form->{"taxaccounts_$i"};
1908       foreach my $item (qw(description rate taxnumber)) {
1909         my $index = $form->{"taxaccounts_$i"} . "_$item";
1910         $form->{$index} = $newform{$index};
1911       }
1912
1913       # credit remaining calculation
1914       $amount = $form->{"sellprice_$i"} * (1 - $form->{"discount_$i"} / 100) * $form->{"qty_$i"};
1915
1916       map { $form->{"${_}_base"} += $amount } (split / /, $form->{"taxaccounts_$i"});
1917       map { $amount += ($form->{"${_}_base"} * $form->{"${_}_rate"}) } split / /, $form->{"taxaccounts_$i"} if !$form->{taxincluded};
1918
1919       $form->{creditremaining} -= $amount;
1920
1921       # redo number formatting, because invoice parse them!
1922       map { $form->{"${_}_$i"} = $form->format_amount(\%myconfig, $form->{"${_}_$i"}) } qw(weight listprice sellprice lastcost rop);
1923     }
1924
1925     $form->{"id_$i"} = $parts_id;
1926
1927     # Get the actual price factor (not just the ID) for the marge calculation.
1928     $form->get_lists('price_factors' => 'ALL_PRICE_FACTORS');
1929     foreach my $pfac (@{ $form->{ALL_PRICE_FACTORS} }) {
1930       next if ($pfac->{id} != $newform{price_factor_id});
1931       $form->{"marge_price_factor_$i"} = $pfac->{factor};
1932       last;
1933     }
1934     delete $form->{ALL_PRICE_FACTORS};
1935
1936     delete $form->{action};
1937
1938     # restore original callback
1939     $callback = $form->unescape($form->{callback});
1940     $form->{callback} = $form->unescape($form->{old_callback});
1941     delete $form->{old_callback};
1942
1943     $form->{makemodel_rows}--;
1944
1945     # put callback together
1946     foreach my $key (keys %$form) {
1947
1948       # do single escape for Apache 2.0
1949       my $value = $form->escape($form->{$key}, 1);
1950       $callback .= qq|&$key=$value|;
1951     }
1952     $form->{callback} = $callback;
1953   }
1954
1955   # redirect
1956   $form->redirect;
1957
1958   $lxdebug->leave_sub();
1959 }
1960
1961 sub save_as_new {
1962   $lxdebug->enter_sub();
1963
1964   $auth->assert('part_service_assembly_edit');
1965
1966   # saving the history
1967   if(!exists $form->{addition}) {
1968     $form->{snumbers} = qq|partnumber_| . $form->{partnumber};
1969     $form->{addition} = "SAVED AS NEW";
1970     $form->save_history;
1971   }
1972   # /saving the history
1973   $form->{id} = 0;
1974   if ($form->{"original_partnumber"} &&
1975       ($form->{"partnumber"} eq $form->{"original_partnumber"})) {
1976     $form->{partnumber} = "";
1977   }
1978   &save;
1979   $lxdebug->leave_sub();
1980 }
1981
1982 sub delete {
1983   $lxdebug->enter_sub();
1984
1985   $auth->assert('part_service_assembly_edit');
1986
1987   # saving the history
1988   if(!exists $form->{addition}) {
1989     $form->{snumbers} = qq|partnumber_| . $form->{partnumber};
1990     $form->{addition} = "DELETED";
1991     $form->save_history;
1992   }
1993   # /saving the history
1994   my $rc = IC->delete(\%myconfig, \%$form);
1995
1996   # redirect
1997   $form->redirect($locale->text('Item deleted!')) if ($rc > 0);
1998   $form->error($locale->text('Cannot delete item!'));
1999
2000   $lxdebug->leave_sub();
2001 }
2002
2003 sub price_row {
2004   $lxdebug->enter_sub();
2005
2006   $auth->assert('part_service_assembly_edit');
2007
2008   my ($numrows) = @_;
2009
2010   my @PRICES = map +{
2011     pricegroup    => $form->{"pricegroup_$_"},
2012     pricegroup_id => $form->{"pricegroup_id_$_"},
2013     price         => $form->{"price_$_"},
2014   }, 1 .. $numrows;
2015
2016   print $form->parse_html_template('ic/price_row', { PRICES => \@PRICES });
2017
2018   $lxdebug->leave_sub();
2019 }
2020
2021 sub parts_language_selection {
2022   $lxdebug->enter_sub();
2023
2024   $auth->assert('part_service_assembly_edit');
2025
2026   my $languages = IC->retrieve_languages(\%myconfig, $form);
2027
2028   if ($form->{language_values} ne "") {
2029     foreach my $item (split(/---\+\+\+---/, $form->{language_values})) {
2030       my ($language_id, $translation, $longdescription) = split(/--\+\+--/, $item);
2031
2032       foreach my $language (@{ $languages }) {
2033         next unless ($language->{id} == $language_id);
2034
2035         $language->{translation}     = $translation;
2036         $language->{longdescription} = $longdescription;
2037
2038         $language->{translation_area}     = ($language->{translation_rows} = $form->numtextrows($language->{translation}, 40)) > 1;
2039         $language->{longdescription_rows} = max 4, $form->numtextrows($language->{longdescription}, 40);
2040
2041         last;
2042       }
2043     }
2044   }
2045
2046   my @header_sort = qw(name longdescription);
2047   my %header_title = ( "name" => $locale->text("Name"),
2048                        "longdescription" => $locale->text("Long Description"),
2049                        );
2050
2051   my @header =
2052     map(+{ "column_title" => $header_title{$_},
2053            "column" => $_,
2054          },
2055         @header_sort);
2056
2057   $form->{"title"} = $locale->text("Language Values");
2058   $form->header();
2059   print $form->parse_html_template("ic/parts_language_selection", { "HEADER"    => \@header,
2060                                                                     "LANGUAGES" => $languages, });
2061
2062   $lxdebug->leave_sub();
2063 }
2064
2065 sub ajax_autocomplete {
2066   $main::lxdebug->enter_sub();
2067
2068   my $form     = $main::form;
2069   my %myconfig = %main::myconfig;
2070
2071   $form->{column}          = 'description'     unless $form->{column} =~ /^partnumber|description$/;
2072   $form->{$form->{column}} = $form->{q}           || '';
2073   $form->{limit}           = ($form->{limit} * 1) || 10;
2074   $form->{searchitems}   ||= '';
2075
2076   my @results = IC->all_parts(\%myconfig, $form);
2077
2078   print $form->ajax_response_header(),
2079         $form->parse_html_template('ic/ajax_autocomplete');
2080
2081   $main::lxdebug->leave_sub();
2082 }
2083
2084 sub back_to_record {
2085   _check_io_auth();
2086
2087
2088   delete @{$::form}{qw(action action_add action_back_to_record back_sub description item notes partnumber sellprice taxaccount2 unit vc)};
2089
2090   $::auth->restore_form_from_session($::form->{previousform}, clobber => 1);
2091   $::form->{rowcount}--;
2092   $::form->{action}   = 'display_form';
2093   $::form->{callback} = $::form->{script} . '?' . join('&', map { $::form->escape($_) . '=' . $::form->escape($::form->{$_}) } sort keys %{ $::form });
2094   $::form->redirect;
2095 }
2096
2097 sub continue { call_sub($form->{"nextsub"}); }
2098
2099 sub dispatcher {
2100   my $action = first { $::form->{"action_${_}"} } qw(add back_to_record);
2101   $::form->error($::locale->text('No action defined.')) unless $action;
2102
2103   $::form->{dispatched_action} = $action;
2104   call_sub($action);
2105 }