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