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