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