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