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