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