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