item_selected: Multiselect bzw. Mengeneingabe beim Artikelselektor
[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     'shop'               => { 'text' => $locale->text('Shopartikel'), },
1049     'projectnumber'      => { 'text' => $locale->text('Project Number'), },
1050     'projectdescription' => { 'text' => $locale->text('Project Description'), },
1051   );
1052
1053   $revers     = $form->{revers};
1054   $lastsort   = $form->{lastsort};
1055
1056   # sorting and direction of sorting
1057   # ToDO: change this to the simpler field+direction method
1058   if (($form->{lastsort} eq "") && ($form->{sort} eq undef)) {
1059     $form->{revers}   = 0;
1060     $form->{lastsort} = "partnumber";
1061     $form->{sort}     = "partnumber";
1062   } else {
1063     if ($form->{lastsort} eq $form->{sort}) {
1064       $form->{revers} = 1 - $form->{revers};
1065     } else {
1066       $form->{revers} = 0;
1067       $form->{lastsort} = $form->{sort};
1068     }    #fi
1069   }    #fi
1070
1071   # special case if we have a serialnumber limit search
1072   # serialnumbers are only given in invoices and orders,
1073   # so they can only pop up in bought, sold, rfq, and quoted stuff
1074   $form->{no_sn_joins} = 'Y' if (   !$form->{bought} && !$form->{sold}
1075                                  && !$form->{rfq}    && !$form->{quoted}
1076                                  && ($form->{l_serialnumber} || $form->{serialnumber}));
1077
1078   # special case for any checkbox of bought | sold | onorder | ordered | rfq | quoted.
1079   # if any of these are ticked the behavior changes slightly for lastcost
1080   # since all those are aggregation checks for the legder tables this is an internal switch
1081   # refered to as ledgerchecks
1082   $form->{ledgerchecks} = 'Y' if (   $form->{bought} || $form->{sold} || $form->{onorder}
1083                                   || $form->{ordered} || $form->{rfq} || $form->{quoted});
1084
1085   # if something should be activated if something else is active, enter it here
1086   my %dependencies = (
1087     onhand       => [ qw(l_onhand) ],
1088     short        => [ qw(l_onhand) ],
1089     onorder      => [ qw(l_ordnumber) ],
1090     ordered      => [ qw(l_ordnumber) ],
1091     rfq          => [ qw(l_quonumber) ],
1092     quoted       => [ qw(l_quonumber) ],
1093     bought       => [ qw(l_invnumber) ],
1094     sold         => [ qw(l_invnumber) ],
1095     ledgerchecks => [ qw(l_name) ],
1096     serialnumber => [ qw(l_serialnumber) ],
1097     no_sn_joins  => [ qw(bought sold) ],
1098   );
1099
1100   # get name of partsgroup if id is given
1101   my $pg_name;
1102   if ($form->{partsgroup_id}) {
1103     my $pg = SL::DB::PartsGroup->new(id => $form->{partsgroup_id})->load;
1104     $pg_name = $pg->{'partsgroup'};
1105   }
1106
1107   # these strings get displayed at the top of the results to indicate the user which switches were used
1108   my %optiontexts = (
1109     active        => $locale->text('Active'),
1110     obsolete      => $locale->text('Obsolete'),
1111     orphaned      => $locale->text('Orphaned'),
1112     onhand        => $locale->text('On Hand'),
1113     short         => $locale->text('Short'),
1114     onorder       => $locale->text('On Order'),
1115     ordered       => $locale->text('Ordered'),
1116     rfq           => $locale->text('RFQ'),
1117     quoted        => $locale->text('Quoted'),
1118     bought        => $locale->text('Bought'),
1119     sold          => $locale->text('Sold'),
1120     transdatefrom => $locale->text('From')       . " " . $locale->date(\%myconfig, $form->{transdatefrom}, 1),
1121     transdateto   => $locale->text('To (time)')  . " " . $locale->date(\%myconfig, $form->{transdateto}, 1),
1122     partnumber    => $locale->text('Part Number')      . ": '$form->{partnumber}'",
1123     partsgroup    => $locale->text('Group')            . ": '$form->{partsgroup}'",
1124     partsgroup_id => $locale->text('Group')            . ": '$pg_name'",
1125     serialnumber  => $locale->text('Serial Number')    . ": '$form->{serialnumber}'",
1126     description   => $locale->text('Part Description') . ": '$form->{description}'",
1127     make          => $locale->text('Make')             . ": '$form->{make}'",
1128     model         => $locale->text('Model')            . ": '$form->{model}'",
1129     drawing       => $locale->text('Drawing')          . ": '$form->{drawing}'",
1130     microfiche    => $locale->text('Microfiche')       . ": '$form->{microfiche}'",
1131     l_soldtotal   => $locale->text('Qty in Selected Records'),
1132     ean           => $locale->text('EAN')              . ": '$form->{ean}'",
1133   );
1134
1135   my @itemstatus_keys = qw(active obsolete orphaned onhand short);
1136   my @callback_keys   = qw(onorder ordered rfq quoted bought sold partnumber partsgroup partsgroup_id serialnumber description make model
1137                            drawing microfiche l_soldtotal l_deliverydate transdatefrom transdateto ean shop);
1138
1139   # calculate dependencies
1140   for (@itemstatus_keys, @callback_keys) {
1141     next if ($form->{itemstatus} ne $_ && !$form->{$_});
1142     map { $form->{$_} = 'Y' } @{ $dependencies{$_} } if $dependencies{$_};
1143   }
1144
1145   # generate callback and optionstrings
1146   my @options;
1147   for my  $key (@itemstatus_keys, @callback_keys) {
1148     next if ($form->{itemstatus} ne $key && !$form->{$key});
1149     push @options, $optiontexts{$key};
1150   }
1151
1152   # special case for lastcost
1153   if ($form->{ledgerchecks}){
1154     # ledgerchecks don't know about sellprice or lastcost. they just return a
1155     # price. so rename sellprice to price, and drop lastcost.
1156     $column_defs{sellprice}{text} = $locale->text('Price');
1157     $form->{l_lastcost} = ""
1158   }
1159
1160   if ($form->{description}) {
1161     $description = $form->{description};
1162     $description =~ s/\n/<br>/g;
1163   }
1164
1165   if ($form->{l_linetotal}) {
1166     $form->{l_qty} = "Y";
1167     $form->{l_linetotalsellprice} = "Y" if $form->{l_sellprice};
1168     $form->{l_linetotallastcost}  = $form->{searchitems} eq 'assembly' && !$form->{bom} ? "" : 'Y' if  $form->{l_lastcost};
1169     $form->{l_linetotallistprice} = "Y" if $form->{l_listprice};
1170   }
1171
1172   if ($form->{searchitems} eq 'service') {
1173
1174     # remove bin, weight and rop from list
1175     map { $form->{"l_$_"} = "" } qw(bin weight rop);
1176
1177     $form->{l_onhand} = "";
1178
1179     # qty is irrelevant unless bought or sold
1180     if (   $form->{bought}
1181         || $form->{sold}
1182         || $form->{onorder}
1183         || $form->{ordered}
1184         || $form->{rfq}
1185         || $form->{quoted}) {
1186 #      $form->{l_onhand} = "Y";
1187     } else {
1188       $form->{l_linetotalsellprice} = "";
1189       $form->{l_linetotallastcost}  = "";
1190     }
1191   }
1192
1193   # soldtotal doesn't make sense with more than one bsooqr option.
1194   # so reset it to sold (the most common option), and issue a warning
1195   # ...
1196   # also it doesn't make sense without bsooqr. disable and issue a warning too
1197   my @bsooqr = qw(sold bought onorder ordered rfq quoted);
1198   my $bsooqr_mode = grep { $form->{$_} } @bsooqr;
1199   if ($form->{l_subtotal} && 1 < $bsooqr_mode) {
1200     my $enabled       = first { $form->{$_} } @bsooqr;
1201     $form->{$_}       = ''   for @bsooqr;
1202     $form->{$enabled} = 'Y';
1203
1204     push @options, $::locale->text('Subtotal cannot distinguish betweens record types. Only one of the selected record types will be displayed: #1', $optiontexts{$enabled});
1205   }
1206   if ($form->{l_soldtotal} && !$bsooqr_mode) {
1207     delete $form->{l_soldtotal};
1208
1209     flash('warning', $::locale->text('Soldtotal does not make sense without any bsooqr options'));
1210   }
1211   if ($form->{l_name} && !$bsooqr_mode) {
1212     delete $form->{l_name};
1213
1214     flash('warning', $::locale->text('Name does not make sense without any bsooqr options'));
1215   }
1216   IC->all_parts(\%myconfig, \%$form);
1217
1218   my @columns = qw(
1219     partnumber description notes partsgroup bin onhand rop soldtotal unit listprice
1220     linetotallistprice sellprice linetotalsellprice lastcost linetotallastcost
1221     priceupdate weight image drawing microfiche invnumber ordnumber quonumber
1222     transdate name serialnumber deliverydate ean projectnumber projectdescription
1223     shop
1224   );
1225
1226   my $pricegroups = SL::DB::Manager::Pricegroup->get_all(sort => 'id');
1227   my @pricegroup_columns;
1228   my %column_defs_pricegroups;
1229   if ($form->{l_pricegroups}) {
1230     @pricegroup_columns      = map { "pricegroup_" . $_->id } @{ $pricegroups };
1231     %column_defs_pricegroups = map {
1232       "pricegroup_" . $_->id => {
1233         text    => $::locale->text('Pricegroup') . ' ' . $_->pricegroup,
1234         visible => 1,
1235       },
1236     }  @{ $pricegroups };
1237   }
1238   push @columns, @pricegroup_columns;
1239
1240   my @includeable_custom_variables = grep { $_->{includeable} } @{ $cvar_configs };
1241   my @searchable_custom_variables  = grep { $_->{searchable} }  @{ $cvar_configs };
1242   my %column_defs_cvars            = map { +"cvar_$_->{name}" => { 'text' => $_->{description} } } @includeable_custom_variables;
1243
1244   push @columns, map { "cvar_$_->{name}" } @includeable_custom_variables;
1245
1246   %column_defs = (%column_defs, %column_defs_cvars, %column_defs_pricegroups);
1247   map { $column_defs{$_}->{visible} ||= $form->{"l_$_"} ? 1 : 0 } @columns;
1248   map { $column_defs{$_}->{align}   = 'right' } qw(onhand sellprice listprice lastcost linetotalsellprice linetotallastcost linetotallistprice rop weight soldtotal shop), @pricegroup_columns;
1249
1250   my @hidden_variables = (
1251     qw(l_subtotal l_linetotal searchitems itemstatus bom l_pricegroups),
1252     @itemstatus_keys,
1253     @callback_keys,
1254     map({ "cvar_$_->{name}" } @searchable_custom_variables),
1255     map({'cvar_'. $_->{name} .'_qtyop'} grep({$_->{type} eq 'number'} @searchable_custom_variables)),
1256     map({ "l_$_" } @columns),
1257   );
1258
1259   my $callback         = build_std_url('action=generate_report', grep { $form->{$_} } @hidden_variables);
1260
1261   my @sort_full        = qw(partnumber description onhand soldtotal deliverydate shop);
1262   my @sort_no_revers   = qw(partsgroup bin priceupdate invnumber ordnumber quonumber name image drawing serialnumber);
1263
1264   foreach my $col (@sort_full) {
1265     $column_defs{$col}->{link} = join '&', $callback, "sort=$col", map { "$_=" . E($form->{$_}) } qw(revers lastsort);
1266   }
1267   map { $column_defs{$_}->{link} = "${callback}&sort=$_" } @sort_no_revers;
1268
1269   # add order to callback
1270   $form->{callback} = join '&', ($callback, map { "${_}=" . E($form->{$_}) } qw(sort revers));
1271
1272   my $report = SL::ReportGenerator->new(\%myconfig, $form);
1273
1274   my %attachment_basenames = (
1275     'part'     => $locale->text('part_list'),
1276     'service'  => $locale->text('service_list'),
1277     'assembly' => $locale->text('assembly_list'),
1278   );
1279
1280   $report->set_options('raw_top_info_text'     => $form->parse_html_template('ic/generate_report_top', { options => \@options }),
1281                        'raw_bottom_info_text'  => $form->parse_html_template('ic/generate_report_bottom'),
1282                        'output_format'         => 'HTML',
1283                        'title'                 => $form->{title},
1284                        'attachment_basename'   => $attachment_basenames{$form->{searchitems}} . strftime('_%Y%m%d', localtime time),
1285   );
1286   $report->set_options_from_form();
1287   $locale->set_numberformat_wo_thousands_separator(\%myconfig) if lc($report->{options}->{output_format}) eq 'csv';
1288
1289   $report->set_columns(%column_defs);
1290   $report->set_column_order(@columns);
1291
1292   $report->set_export_options('generate_report', @hidden_variables, qw(sort revers));
1293
1294   $report->set_sort_indicator($form->{sort}, $form->{revers} ? 0 : 1);
1295
1296   CVar->add_custom_variables_to_report('module'         => 'IC',
1297                                        'trans_id_field' => 'id',
1298                                        'configs'        => $cvar_configs,
1299                                        'column_defs'    => \%column_defs,
1300                                        'data'           => $form->{parts});
1301
1302   CVar->add_custom_variables_to_report('module'         => 'IC',
1303                                        'sub_module'     => sub { $_[0]->{ioi} },
1304                                        'trans_id_field' => 'ioi_id',
1305                                        'configs'        => $cvar_configs,
1306                                        'column_defs'    => \%column_defs,
1307                                        'data'           => $form->{parts});
1308
1309   my @subtotal_columns = qw(sellprice listprice lastcost);
1310   my %subtotals = map { $_ => 0 } ('onhand', @subtotal_columns);
1311   my %totals    = map { $_ => 0 } @subtotal_columns;
1312   my $idx       = 0;
1313   my $same_item = @{ $form->{parts} } ? $form->{parts}[0]{ $form->{sort} } : undef;
1314
1315   my $defaults  = AM->get_defaults();
1316
1317   # postprocess parts
1318   foreach my $ref (@{ $form->{parts} }) {
1319
1320     # fresh row, for inserting later
1321     my $row = { map { $_ => { 'data' => $ref->{$_} } } @columns };
1322
1323     $ref->{exchangerate} ||= 1;
1324     $ref->{price_factor} ||= 1;
1325     $ref->{sellprice}     *= $ref->{exchangerate} / $ref->{price_factor};
1326     $ref->{listprice}     *= $ref->{exchangerate} / $ref->{price_factor};
1327     $ref->{lastcost}      *= $ref->{exchangerate} / $ref->{price_factor};
1328
1329     # use this for assemblies
1330     my $soldtotal = $bsooqr_mode ? $ref->{soldtotal} : $ref->{onhand};
1331
1332     if ($ref->{assemblyitem}) {
1333       $row->{partnumber}{align}   = 'right';
1334       $row->{soldtotal}{data}     = 0;
1335       $soldtotal                  = 0 if ($form->{sold});
1336     }
1337
1338     my $edit_link               = build_std_url('action=edit', 'id=' . E($ref->{id}), 'callback');
1339     $row->{partnumber}->{link}  = $edit_link;
1340     $row->{description}->{link} = $edit_link;
1341
1342     foreach (qw(sellprice listprice lastcost)) {
1343       $row->{$_}{data}            = $form->format_amount(\%myconfig, $ref->{$_}, 2);
1344       $row->{"linetotal$_"}{data} = $form->format_amount(\%myconfig, $ref->{onhand} * $ref->{$_}, 2);
1345     }
1346     foreach ( @pricegroup_columns ) {
1347       $row->{$_}{data}            = $form->format_amount(\%myconfig, $ref->{"$_"}, 2);
1348     };
1349
1350
1351     map { $row->{$_}{data} = $form->format_amount(\%myconfig, $ref->{$_}); } qw(onhand rop weight soldtotal);
1352
1353     $row->{weight}->{data} .= ' ' . $defaults->{weightunit};
1354
1355     # 'yes' and 'no' for boolean value shop
1356     if ($form->{l_shop}) {
1357       $row->{shop}{data} = $row->{shop}{data}? $::locale->text('yes') : $::locale->text('no');
1358     }
1359
1360     if (!$ref->{assemblyitem}) {
1361       foreach my $col (@subtotal_columns) {
1362         $totals{$col}    += $soldtotal * $ref->{$col};
1363         $subtotals{$col} += $soldtotal * $ref->{$col};
1364       }
1365
1366       $subtotals{soldtotal} += $soldtotal;
1367     }
1368
1369     # set module stuff
1370     if ($ref->{module} eq 'oe') {
1371       # für oe gibt es vier fälle, jeweils nach kunde oder lieferant unterschiedlich:
1372       #
1373       # | ist bestellt  | Von Kunden bestellt |  -> edit_oe_ord_link
1374       # | Anfrage       | Angebot             |  -> edit_oe_quo_link
1375
1376       my $edit_oe_ord_link = build_std_url("script=oe.pl", 'action=edit', 'type=' . E($ref->{cv} eq 'vendor' ? 'purchase_order' : 'sales_order'), 'id=' . E($ref->{trans_id}), 'callback');
1377       my $edit_oe_quo_link = build_std_url("script=oe.pl", 'action=edit', 'type=' . E($ref->{cv} eq 'vendor' ? 'request_quotation' : 'sales_quotation'), 'id=' . E($ref->{trans_id}), 'callback');
1378
1379       $row->{ordnumber}{link} = $edit_oe_ord_link;
1380       $row->{quonumber}{link} = $edit_oe_quo_link if (!$ref->{ordnumber});
1381
1382     } else {
1383       $row->{invnumber}{link} = build_std_url("script=$ref->{module}.pl", 'action=edit', 'type=invoice', 'id=' . E($ref->{trans_id}), 'callback');
1384     }
1385
1386     # set properties of images
1387     if ($ref->{image} && (lc $report->{options}->{output_format} eq 'html')) {
1388       $row->{image}{data}     = '';
1389       $row->{image}{raw_data} = '<a href="' . H($ref->{image}) . '"><img src="' . H($ref->{image}) . '" height="32" border="0"></a>';
1390     }
1391     map { $row->{$_}{link} = $ref->{$_} } qw(drawing microfiche);
1392
1393     $row->{notes}{data} = SL::HTML::Util->strip($ref->{notes});
1394
1395     $report->add_data($row);
1396
1397     my $next_ref = $form->{parts}[$idx + 1];
1398
1399     # insert subtotal rows
1400     if (($form->{l_subtotal} eq 'Y') &&
1401         (!$next_ref ||
1402          (!$next_ref->{assemblyitem} && ($same_item ne $next_ref->{ $form->{sort} })))) {
1403       my $row = { map { $_ => { 'class' => 'listsubtotal', } } @columns };
1404
1405       if (($form->{searchitems} ne 'assembly') || !$form->{bom}) {
1406         $row->{soldtotal}->{data} = $form->format_amount(\%myconfig, $subtotals{soldtotal});
1407       }
1408
1409       map { $row->{"linetotal$_"}->{data} = $form->format_amount(\%myconfig, $subtotals{$_}, 2) } @subtotal_columns;
1410       map { $subtotals{$_} = 0 } ('soldtotal', @subtotal_columns);
1411
1412       $report->add_data($row);
1413
1414       $same_item = $next_ref->{ $form->{sort} };
1415     }
1416
1417     $idx++;
1418   }
1419
1420   if ($form->{"l_linetotal"} && !$form->{report_generator_csv_options_for_import}) {
1421     my $row = { map { $_ => { 'class' => 'listtotal', } } @columns };
1422
1423     map { $row->{"linetotal$_"}->{data} = $form->format_amount(\%myconfig, $totals{$_}, 2) } @subtotal_columns;
1424
1425     $report->add_separator();
1426     $report->add_data($row);
1427   }
1428
1429   $report->generate_with_headers();
1430
1431   $lxdebug->leave_sub();
1432 }    #end generate_report
1433
1434 sub parts_subtotal {
1435   $lxdebug->enter_sub();
1436
1437   $auth->assert('part_service_assembly_edit');
1438
1439   my (%column_data);
1440   my ($column_index, $subtotalonhand, $subtotalsellprice, $subtotallastcost, $subtotallistprice) = @_;
1441
1442   map { $column_data{$_} = "<td>&nbsp;</td>" } @{ $column_index };
1443   $$subtotalonhand = 0 if ($form->{searchitems} eq 'assembly' && $form->{bom});
1444
1445   $column_data{onhand} =
1446       "<th class=listsubtotal align=right>"
1447     . $form->format_amount(\%myconfig, $$subtotalonhand)
1448     . "</th>";
1449
1450   $column_data{linetotalsellprice} =
1451       "<th class=listsubtotal align=right>"
1452     . $form->format_amount(\%myconfig, $$subtotalsellprice, 2)
1453     . "</th>";
1454   $column_data{linetotallistprice} =
1455       "<th class=listsubtotal align=right>"
1456     . $form->format_amount(\%myconfig, $$subtotallistprice, 2)
1457     . "</th>";
1458   $column_data{linetotallastcost} =
1459       "<th class=listsubtotal align=right>"
1460     . $form->format_amount(\%myconfig, $$subtotallastcost, 2)
1461     . "</th>";
1462
1463   $$subtotalonhand    = 0;
1464   $$subtotalsellprice = 0;
1465   $$subtotallistprice = 0;
1466   $$subtotallastcost  = 0;
1467
1468   print "<tr class=listsubtotal>";
1469
1470   map { print "\n$column_data{$_}" } @{ $column_index };
1471
1472   print qq|
1473   </tr>
1474 |;
1475
1476   $lxdebug->leave_sub();
1477 }
1478
1479 sub edit {
1480   $lxdebug->enter_sub();
1481
1482   $auth->assert('part_service_assembly_details');
1483
1484   # show history button
1485   $form->{javascript} = qq|<script type="text/javascript" src="js/show_history.js"></script>|;
1486   #/show hhistory button
1487   IC->get_part(\%myconfig, \%$form);
1488
1489   $form->{"original_partnumber"} = $form->{"partnumber"};
1490
1491   my $title      = 'Edit ' . ucfirst $form->{item};
1492   $form->{title} = $locale->text($title);
1493
1494   &link_part;
1495   &display_form;
1496
1497   $lxdebug->leave_sub();
1498 }
1499
1500 sub link_part {
1501   $lxdebug->enter_sub();
1502
1503   $auth->assert('part_service_assembly_details');
1504
1505   IC->create_links("IC", \%myconfig, \%$form);
1506
1507   # currencies
1508   map({ $form->{selectcurrency} .= "<option>$_\n" } $::form->get_all_currencies());
1509
1510   # parts and assemblies have the same links
1511   my $item = $form->{item};
1512   if ($form->{item} eq 'assembly') {
1513     $item = 'part';
1514   }
1515
1516   # build the popup menus
1517   $form->{taxaccounts} = "";
1518   foreach my $key (keys %{ $form->{IC_links} }) {
1519     foreach my $ref (@{ $form->{IC_links}{$key} }) {
1520
1521       # if this is a tax field
1522       if ($key =~ /IC_tax/) {
1523         if ($key =~ /\Q$item\E/) {
1524           $form->{taxaccounts} .= "$ref->{accno} ";
1525           $form->{"IC_tax_$ref->{accno}_description"} =
1526             "$ref->{accno}--$ref->{description}";
1527
1528           if ($form->{id}) {
1529             if ($form->{amount}{ $ref->{accno} }) {
1530               $form->{"IC_tax_$ref->{accno}"} = "checked";
1531             }
1532           } else {
1533             $form->{"IC_tax_$ref->{accno}"} = "checked";
1534           }
1535         }
1536       } else {
1537
1538         $form->{"select$key"} .=
1539           "<option $ref->{selected}>$ref->{accno}--$ref->{description}\n";
1540         if ($form->{amount}{$key} eq $ref->{accno}) {
1541           $form->{$key} = "$ref->{accno}--$ref->{description}";
1542         }
1543
1544       }
1545     }
1546   }
1547   chop $form->{taxaccounts};
1548
1549   if (($form->{item} eq "part") || ($form->{item} eq "assembly")) {
1550     $form->{selectIC_income}  = $form->{selectIC_sale};
1551     $form->{selectIC_expense} = $form->{selectIC_cogs};
1552     $form->{IC_income}        = $form->{IC_sale};
1553     $form->{IC_expense}       = $form->{IC_cogs};
1554   }
1555
1556   delete $form->{IC_links};
1557   delete $form->{amount};
1558
1559   $form->get_partsgroup(\%myconfig, { all => 1 });
1560
1561   $form->{partsgroup} = "$form->{partsgroup}--$form->{partsgroup_id}";
1562
1563   if (@{ $form->{all_partsgroup} }) {
1564     $form->{selectpartsgroup} = qq|<option>\n|;
1565     map { $form->{selectpartsgroup} .= qq|<option value="$_->{partsgroup}--$_->{id}">$_->{partsgroup}\n| } @{ $form->{all_partsgroup} };
1566   }
1567
1568   if ($form->{item} eq 'assembly') {
1569
1570     foreach my $i (1 .. $form->{assembly_rows}) {
1571       if ($form->{"partsgroup_id_$i"}) {
1572         $form->{"partsgroup_$i"} =
1573           qq|$form->{"partsgroup_$i"}--$form->{"partsgroup_id_$i"}|;
1574       }
1575     }
1576     $form->get_partsgroup(\%myconfig);
1577
1578     if (@{ $form->{all_partsgroup} }) {
1579       $form->{selectassemblypartsgroup} = qq|<option>\n|;
1580
1581       map {
1582         $form->{selectassemblypartsgroup} .=
1583           qq|<option value="$_->{partsgroup}--$_->{id}">$_->{partsgroup}\n|
1584       } @{ $form->{all_partsgroup} };
1585     }
1586   }
1587   $lxdebug->leave_sub();
1588 }
1589
1590 sub form_header {
1591   $lxdebug->enter_sub();
1592
1593   $auth->assert('part_service_assembly_details');
1594
1595   $form->{pg_keys}          = sub { "$_[0]->{partsgroup}--$_[0]->{id}" };
1596   $form->{description_area} = ($form->{rows} = $form->numtextrows($form->{description}, 40)) > 1;
1597   $form->{notes_rows}       =  max 4, $form->numtextrows($form->{notes}, 40), $form->numtextrows($form->{formel}, 40);
1598
1599   map { $form->{"is_$_"}  = ($form->{item} eq $_) } qw(part service assembly);
1600   map { $form->{$_}       =~ s/"/&quot;/g;        } qw(unit);
1601
1602   $form->get_lists('price_factors' => 'ALL_PRICE_FACTORS',
1603                    'partsgroup'    => 'all_partsgroup',
1604                    'vendors'       => 'ALL_VENDORS',
1605                    'warehouses'    => { 'key'    => 'WAREHOUSES',
1606                                         'bins'   => 'BINS', });
1607   # leerer wert für Lager und Lagerplatz korrekt einstellt
1608   # ID 0 sollte in Ordnung sein, da der Zähler sowieso höher ist
1609   my $no_default_bin_entry = { 'id' => '0', description => '--', 'BINS' => [ { id => '0', description => ''} ] };
1610   push @ { $form->{WAREHOUSES} }, $no_default_bin_entry;
1611   if (my $max = scalar @{ $form->{WAREHOUSES} }) {
1612     my ($default_warehouse_id, $default_bin_id);
1613     if ($form->{action} eq 'add') { # default only for new entries
1614       $default_warehouse_id = $::instance_conf->get_warehouse_id;
1615       $default_bin_id       = $::instance_conf->get_bin_id;
1616     }
1617     $form->{warehouse_id} ||= $default_warehouse_id || $form->{WAREHOUSES}->[$max -1]->{id};
1618     $form->{bin_id}       ||= $default_bin_id       ||  $form->{WAREHOUSES}->[$max -1]->{BINS}->[0]->{id};
1619   }
1620
1621   $form->{LANGUAGES}        = SL::DB::Manager::Language->get_all_sorted;
1622   $form->{translations_map} = { map { ($_->{language_id} => $_) } @{ $form->{translations} || [] } };
1623
1624   IC->retrieve_buchungsgruppen(\%myconfig, $form);
1625   @{ $form->{BUCHUNGSGRUPPEN} } = grep { $_->{id} eq $form->{buchungsgruppen_id} || ($form->{id} && $form->{orphaned}) || !$form->{id} } @{ $form->{BUCHUNGSGRUPPEN} };
1626
1627   if (($form->{partnumber} ne '') && !SL::TransNumber->new(number => $form->{partnumber}, type => $form->{item}, id => $form->{id})->is_unique) {
1628     flash('info', $::locale->text('This partnumber is not unique. You should change it.'));
1629   }
1630
1631   my $units = AM->retrieve_units(\%myconfig, $form);
1632   $form->{ALL_UNITS} = [ map +{ name => $_ }, sort { $units->{$a}{sortkey} <=> $units->{$b}{sortkey} } keys %$units ];
1633
1634   $form->{defaults} = AM->get_defaults();
1635
1636   $form->{CUSTOM_VARIABLES} = CVar->get_custom_variables('module' => 'IC', 'trans_id' => $form->{id});
1637
1638   my ($null, $partsgroup_id) = split /--/, $form->{partsgroup};
1639
1640   CVar->render_inputs('variables' => $form->{CUSTOM_VARIABLES}, show_disabled_message => 1, partsgroup_id => $partsgroup_id)
1641     if (scalar @{ $form->{CUSTOM_VARIABLES} });
1642
1643   $::request->layout->use_javascript("${_}.js") for qw(ckeditor/ckeditor ckeditor/adapters/jquery kivi.PriceRule);
1644   $::request->layout->add_javascripts_inline("\$(function(){kivi.PriceRule.load_price_rules_for_part(@{[ $::form->{id} * 1 ]})})") if $::form->{id};
1645   $form->header;
1646   #print $form->parse_html_template('ic/form_header', { ALL_PRICE_FACTORS => $form->{ALL_PRICE_FACTORS},
1647   #                                                     ALL_UNITS         => $form->{ALL_UNITS},
1648   #                                                     BUCHUNGSGRUPPEN   => $form->{BUCHUNGSGRUPPEN},
1649   #                                                     payment_terms     => $form->{payment_terms},
1650   #                                                     all_partsgroup    => $form->{all_partsgroup}});
1651
1652   $form->{show_edit_buttons} = $main::auth->check_right($::myconfig{login}, 'part_service_assembly_edit');
1653
1654   print $form->parse_html_template('ic/form_header');
1655   $lxdebug->leave_sub();
1656 }
1657
1658 sub form_footer {
1659   $lxdebug->enter_sub();
1660
1661   $auth->assert('part_service_assembly_details');
1662
1663   print $form->parse_html_template('ic/form_footer');
1664
1665   $lxdebug->leave_sub();
1666 }
1667
1668 sub makemodel_row {
1669   $lxdebug->enter_sub();
1670   my ($numrows) = @_;
1671   #hli
1672   my @mm_data = grep { any { $_ ne '' } @$_{qw(make model)} } map +{ make => $form->{"make_$_"}, model => $form->{"model_$_"}, lastcost => $form->{"lastcost_$_"}, lastupdate => $form->{"lastupdate_$_"}, sortorder => $form->{"sortorder_$_"} }, 1 .. $numrows;
1673   delete @{$form}{grep { m/^make_\d+/ || m/^model_\d+/ } keys %{ $form }};
1674   print $form->parse_html_template('ic/makemodel', { MM_DATA => [ @mm_data, {} ], mm_rows => scalar @mm_data + 1 });
1675
1676   $lxdebug->leave_sub();
1677 }
1678
1679 sub assembly_row {
1680   $lxdebug->enter_sub();
1681   my ($numrows) = @_;
1682   my (@column_index);
1683   my ($nochange, $callback, $previousform, $linetotal, $line_purchase_price, $href);
1684
1685   @column_index = qw(runningnumber qty unit bom partnumber description partsgroup lastcost total);
1686
1687   if ($form->{previousform}) {
1688     $nochange     = 1;
1689     @column_index = qw(qty unit bom partnumber description partsgroup total);
1690   } else {
1691
1692     # change callback
1693     $form->{old_callback} = $form->{callback};
1694     $callback             = $form->{callback};
1695     $form->{callback}     = "$form->{script}?action=display_form";
1696
1697     # delete action
1698     map { delete $form->{$_} } qw(action header);
1699
1700     # save form variables in a previousform variable
1701     my %form_to_save = map   { ($_ => m/^ (?: listprice | sellprice | lastcost ) $/x ? $form->format_amount(\%myconfig, $form->{$_}) : $form->{$_}) }
1702                        keys %{ $form };
1703     $previousform    = $::auth->save_form_in_session(form => \%form_to_save);
1704
1705     $form->{callback} = $callback;
1706     $form->{assemblytotal} = 0;
1707     $form->{assembly_purchase_price_total} = 0;
1708     $form->{weight}        = 0;
1709   }
1710
1711   my %header = (
1712    runningnumber => { text =>  $locale->text('No.'),              nowrap => 1, width => '5%',  align => 'left',},
1713    qty           => { text =>  $locale->text('Qty'),              nowrap => 1, width => '10%', align => 'left',},
1714    unit          => { text =>  $locale->text('Unit'),             nowrap => 1, width => '5%',  align => 'left',},
1715    partnumber    => { text =>  $locale->text('Part Number'),      nowrap => 1, width => '20%', align => 'left',},
1716    description   => { text =>  $locale->text('Part Description'), nowrap => 1, width => '50%', align => 'left',},
1717    lastcost      => { text =>  $locale->text('Purchase Prices'),  nowrap => 1, width => '50%', align => 'right',},
1718    total         => { text =>  $locale->text('Sale Prices'),      nowrap => 1,                 align => 'right',},
1719    bom           => { text =>  $locale->text('BOM'),                                           align => 'center',},
1720    partsgroup    => { text =>  $locale->text('Group'),                                         align => 'left',},
1721   );
1722
1723   my @ROWS;
1724
1725   for my $i (1 .. $numrows) {
1726     my (%row, @row_hiddens);
1727
1728     $form->{"partnumber_$i"} =~ s/\"/&quot;/g;
1729
1730     $linetotal           = $form->round_amount($form->{"sellprice_$i"} * $form->{"qty_$i"} / ($form->{"price_factor_$i"} || 1), 4);
1731     $line_purchase_price = $form->round_amount($form->{"lastcost_$i"} *  $form->{"qty_$i"} / ($form->{"price_factor_$i"} || 1), 4);
1732     $form->{assemblytotal}                  += $linetotal;
1733     $form->{assembly_purchase_price_total}  += $line_purchase_price;
1734     $form->{"qty_$i"}    = $form->format_amount(\%myconfig, $form->{"qty_$i"});
1735     $linetotal           = $form->format_amount(\%myconfig, $linetotal, 2);
1736     $line_purchase_price = $form->format_amount(\%myconfig, $line_purchase_price, 2);
1737     $href                = build_std_url("action=edit", qq|id=$form->{"id_$i"}|, "rowcount=$numrows", "currow=$i", "previousform=$previousform");
1738     map { $row{$_}{data} = "" } qw(qty unit partnumber description bom partsgroup runningnumber);
1739
1740     # last row
1741     if (($i >= 1) && ($i == $numrows)) {
1742       if (!$form->{previousform}) {
1743         $row{partnumber}{data}  = qq|<input name="partnumber_$i" size=15 value="$form->{"partnumber_$i"}">|;
1744         $row{qty}{data}         = qq|<input name="qty_$i" size=5 value="$form->{"qty_$i"}">|;
1745         $row{description}{data} = qq|<input name="description_$i" size=40 value="$form->{"description_$i"}">|;
1746         $row{partsgroup}{data}  = qq|<input name="partsgroup_$i" size=10 value="$form->{"partsgroup_$i"}">|;
1747       }
1748     # other rows
1749     } else {
1750       if ($form->{previousform}) {
1751         push @row_hiddens,          qw(qty bom);
1752         $row{partnumber}{data}    = $form->{"partnumber_$i"};
1753         $row{qty}{data}           = $form->{"qty_$i"};
1754         $row{bom}{data}           = $form->{"bom_$i"} ? "x" : "&nbsp;";
1755         $row{qty}{align}          = 'right';
1756       } else {
1757         $row{partnumber}{data}    = qq|$form->{"partnumber_$i"}|;
1758         $row{partnumber}{link}     = $href;
1759         $row{qty}{data}           = qq|<input name="qty_$i" size=5 value="$form->{"qty_$i"}">|;
1760         $row{runningnumber}{data} = qq|<input name="runningnumber_$i" size=3 value="$i">|;
1761         $row{bom}{data}   = sprintf qq|<input name="bom_$i" type=checkbox class=checkbox value=1 %s>|,
1762                                        $form->{"bom_$i"} ? 'checked' : '';
1763       }
1764       push @row_hiddens,        qw(unit description partnumber partsgroup);
1765       $row{unit}{data}        = $form->{"unit_$i"};
1766       #Bei der Artikelbeschreibung und Warengruppe können Sonderzeichen verwendet
1767       #werden, die den HTML Code stören. Daher sollen diese im Template escaped werden
1768       #dies geschieht, wenn die Variable escape gesetzt ist
1769       $row{description}{data}   = $form->{"description_$i"};
1770       $row{description}{escape} = 1;
1771       $row{partsgroup}{data}    = $form->{"partsgroup_$i"};
1772       $row{partsgroup}{escape}  = 1;
1773       $row{bom}{align}          = 'center';
1774     }
1775
1776     $row{lastcost}{data}      = $line_purchase_price;
1777     $row{total}{data}         = $linetotal;
1778     $row{lastcost}{align}     = 'right';
1779     $row{total}{align}        = 'right';
1780     $row{deliverydate}{align} = 'right';
1781
1782     push @row_hiddens, qw(id sellprice lastcost weight price_factor_id price_factor);
1783     $row{hiddens} = [ map +{ name => "${_}_$i", value => $form->{"${_}_$i"} }, @row_hiddens ];
1784
1785     push @ROWS, \%row;
1786   }
1787
1788   print $form->parse_html_template('ic/assembly_row', { COLUMNS => \@column_index, ROWS => \@ROWS, HEADER => \%header });
1789
1790   $lxdebug->leave_sub();
1791 }
1792
1793 sub update {
1794   $lxdebug->enter_sub();
1795
1796   $auth->assert('part_service_assembly_edit');
1797
1798   # update checks whether pricegroups, makemodels or assembly items have been changed/added
1799   # new items might have been added (and the original form might have been stored and restored)
1800   # so at the end the ic form is run through check_form in io.pl
1801   # The various combination of events can lead to problems with the order of parse_amount and format_amount
1802   # Currently check_form parses some variables in assembly mode, but not in article or service mode
1803   # This will only ever really be sanely resolved with a rewrite...
1804
1805   # parse pricegroups. and no, don't rely on check_form for this...
1806   map { $form->{"price_$_"} = $form->parse_amount(\%myconfig, $form->{"price_$_"}) } 1 .. $form->{price_rows};
1807
1808   unless ($form->{item} eq 'assembly') {
1809     # for assemblies check_form will parse sellprice and listprice, but not for parts or services
1810     $form->{$_} = $form->parse_amount(\%myconfig, $form->{$_}) for qw(sellprice listprice ve gv);
1811   };
1812
1813   if ($form->{item} eq 'part') {
1814     $form->{$_} = $form->parse_amount(\%myconfig, $form->{$_}) for qw(weight rop);
1815   }
1816
1817   # same for makemodel lastcosts
1818   # but parse_amount not necessary for assembly component lastcosts
1819   unless ($form->{item} eq "assembly") {
1820     map { $form->{"lastcost_$_"} = $form->parse_amount(\%myconfig, $form->{"lastcost_$_"}) } 1 .. $form->{"makemodel_rows"};
1821     $form->{lastcost} = $form->parse_amount(\%myconfig, $form->{lastcost});
1822   }
1823
1824   if ($form->{item} eq "assembly") {
1825     my $i = $form->{assembly_rows};
1826
1827     # if last row is empty check the form otherwise retrieve item
1828     if (   ($form->{"partnumber_$i"} eq "")
1829         && ($form->{"description_$i"} eq "")
1830         && ($form->{"partsgroup_$i"}  eq "")) {
1831       # no new assembly item was added
1832
1833       &check_form;
1834
1835     } else {
1836       # search db for newly added assemblyitems, via partnumber or description
1837       IC->assembly_item(\%myconfig, \%$form);
1838
1839       # form->{item_list} contains the possible matches, next check whether the
1840       # match is unique or we need to call the page to select the item
1841       my $rows = scalar @{ $form->{item_list} };
1842
1843       if ($rows) {
1844         $form->{"qty_$i"} = 1 unless ($form->{"qty_$i"});
1845
1846         if ($rows > 1) {
1847           $form->{makemodel_rows}--;
1848           select_item(mode => 'IC', pre_entered_qty => $form->parse_amount(\%myconfig, $form->{"qty_$i"}));
1849           ::end_of_request();
1850         } else {
1851           map { $form->{item_list}[$i]{$_} =~ s/\"/&quot;/g }
1852             qw(partnumber description unit partsgroup);
1853           map { $form->{"${_}_$i"} = $form->{item_list}[0]{$_} }
1854             keys %{ $form->{item_list}[0] };
1855           $form->{"runningnumber_$i"} = $form->{assembly_rows};
1856           $form->{assembly_rows}++;
1857
1858           &check_form;
1859
1860         }
1861
1862       } else {
1863
1864         $form->{rowcount} = $i;
1865         $form->{assembly_rows}++;
1866
1867         &new_item;
1868
1869       }
1870     }
1871
1872   } elsif (($form->{item} eq 'part') || ($form->{item} eq 'service')) {
1873     &check_form;
1874   }
1875
1876   $lxdebug->leave_sub();
1877 }
1878
1879 sub save {
1880   $lxdebug->enter_sub();
1881
1882   $auth->assert('part_service_assembly_edit');
1883
1884   my ($parts_id, %newform, $amount, $callback);
1885
1886   # check if there is a part number - commented out, cause there is an automatic allocation of numbers
1887   # $form->isblank("partnumber", $locale->text(ucfirst $form->{item}." Part Number missing!"));
1888
1889   # check if there is a description
1890   $form->isblank("description", $locale->text("Part Description missing!"));
1891
1892   $form->error($locale->text("Inventory quantity must be zero before you can set this $form->{item} obsolete!"))
1893     if $form->{obsolete} && $form->{onhand} * 1 && $form->{item} ne 'service';
1894
1895   if (!$form->{buchungsgruppen_id}) {
1896     $form->error($locale->text("Parts must have an entry type.") . " " .
1897      $locale->text("If you see this message, you most likely just setup your LX-Office and haven't added any entry types. If this is the case, the option is accessible for administrators in the System menu.")
1898     );
1899   }
1900
1901   $form->error($locale->text('Description must not be empty!')) unless $form->{description};
1902   $form->error($locale->text('Partnumber must not be set to empty!')) if $form->{id} && !$form->{partnumber};
1903
1904   # undef warehouse_id if the empty value is selected
1905   if ( ($form->{warehouse_id} == 0) && ($form->{bin_id} == 0) ) {
1906     undef $form->{warehouse_id};
1907     undef $form->{bin_id};
1908   }
1909   # save part
1910   if (IC->save(\%myconfig, \%$form) == 3) {
1911     $form->error($locale->text('Partnumber not unique!'));
1912   }
1913   # saving the history
1914   if(!exists $form->{addition}) {
1915     $form->{snumbers} = qq|partnumber_| . $form->{partnumber};
1916     $form->{addition} = "SAVED";
1917     $form->save_history;
1918   }
1919   # /saving the history
1920   $parts_id = $form->{id};
1921
1922   my $i;
1923   # load previous variables
1924   if ($form->{previousform}) {
1925
1926     # save the new form variables before splitting previousform
1927     map { $newform{$_} = $form->{$_} } keys %$form;
1928
1929     # don't trample on previous variables
1930     map { delete $form->{$_} } keys %newform;
1931
1932     my $ic_cvar_configs = CVar->get_configs(module => 'IC');
1933     my @ic_cvar_fields  = map { "cvar_$_->{name}" } @{ $ic_cvar_configs };
1934
1935     # restore original values
1936     $::auth->restore_form_from_session($newform{previousform}, form => $form);
1937     $form->{taxaccounts} = $newform{taxaccount2};
1938
1939     if ($form->{item} eq 'assembly') {
1940
1941       # undo number formatting
1942       map { $form->{$_} = $form->parse_amount(\%myconfig, $form->{$_}) }
1943         qw(weight listprice sellprice rop);
1944
1945       $form->{assembly_rows}--;
1946       if ($newform{currow}) {
1947         $i = $newform{currow};
1948       } else {
1949         $i = $form->{assembly_rows};
1950       }
1951       $form->{"qty_$i"} = 1 unless ($form->{"qty_$i"} > 0);
1952
1953       $form->{sellprice} -= $form->{"sellprice_$i"} * $form->{"qty_$i"};
1954       $form->{weight}    -= $form->{"weight_$i"} * $form->{"qty_$i"};
1955
1956       # change/add values for assembly item
1957       map { $form->{"${_}_$i"} = $newform{$_} } qw(partnumber description bin unit weight listprice sellprice inventory_accno income_accno expense_accno price_factor_id);
1958       map { $form->{"ic_${_}_$i"} = $newform{$_} } @ic_cvar_fields;
1959
1960       # das ist __voll__ bekloppt, dass so auszurechnen jb 22.5.09
1961       #$form->{sellprice} += $form->{"sellprice_$i"} * $form->{"qty_$i"};
1962       $form->{weight}    += $form->{"weight_$i"} * $form->{"qty_$i"};
1963
1964     } else {
1965
1966       # set values for last invoice/order item
1967       $i = $form->{rowcount};
1968       $form->{"qty_$i"} = 1 unless ($form->{"qty_$i"} > 0);
1969
1970       map { $form->{"${_}_$i"} = $newform{$_} } qw(partnumber description bin unit listprice inventory_accno income_accno expense_accno sellprice lastcost price_factor_id);
1971       map { $form->{"ic_${_}_$i"} = $newform{$_} } @ic_cvar_fields;
1972
1973       $form->{"longdescription_$i"} = $newform{notes};
1974
1975       $form->{"sellprice_$i"} = $newform{lastcost} if ($form->{vendor_id});
1976
1977       if ($form->{exchangerate} != 0) {
1978         $form->{"sellprice_$i"} /= $form->{exchangerate};
1979       }
1980
1981       map { $form->{"taxaccounts_$i"} .= "$_ " } split / /, $newform{taxaccount};
1982       chop $form->{"taxaccounts_$i"};
1983       foreach my $item (qw(description rate taxnumber)) {
1984         my $index = $form->{"taxaccounts_$i"} . "_$item";
1985         $form->{$index} = $newform{$index};
1986       }
1987
1988       # credit remaining calculation
1989       $amount = $form->{"sellprice_$i"} * (1 - $form->{"discount_$i"} / 100) * $form->{"qty_$i"};
1990
1991       map { $form->{"${_}_base"} += $amount } (split / /, $form->{"taxaccounts_$i"});
1992       map { $amount += ($form->{"${_}_base"} * $form->{"${_}_rate"}) } split / /, $form->{"taxaccounts_$i"} if !$form->{taxincluded};
1993
1994       $form->{creditremaining} -= $amount;
1995
1996       # redo number formatting, because invoice parse them!
1997       map { $form->{"${_}_$i"} = $form->format_amount(\%myconfig, $form->{"${_}_$i"}) } qw(weight listprice sellprice lastcost rop);
1998     }
1999
2000     $form->{"id_$i"} = $parts_id;
2001
2002     # Get the actual price factor (not just the ID) for the marge calculation.
2003     $form->get_lists('price_factors' => 'ALL_PRICE_FACTORS');
2004     foreach my $pfac (@{ $form->{ALL_PRICE_FACTORS} }) {
2005       next if ($pfac->{id} != $newform{price_factor_id});
2006       $form->{"marge_price_factor_$i"} = $pfac->{factor};
2007       last;
2008     }
2009     delete $form->{ALL_PRICE_FACTORS};
2010
2011     delete $form->{action};
2012
2013     # restore original callback
2014     $callback = $form->unescape($form->{callback});
2015     $form->{callback} = $form->unescape($form->{old_callback});
2016     delete $form->{old_callback};
2017
2018     $form->{makemodel_rows}--;
2019
2020     # put callback together
2021     foreach my $key (keys %$form) {
2022
2023       # do single escape for Apache 2.0
2024       my $value = $form->escape($form->{$key}, 1);
2025       $callback .= qq|&$key=$value|;
2026     }
2027     $form->{callback} = $callback;
2028   }
2029
2030   # redirect
2031   $form->redirect;
2032
2033   $lxdebug->leave_sub();
2034 }
2035
2036 sub save_as_new {
2037   $lxdebug->enter_sub();
2038
2039   $auth->assert('part_service_assembly_edit');
2040
2041   # saving the history
2042   if(!exists $form->{addition}) {
2043     $form->{snumbers} = qq|partnumber_| . $form->{partnumber};
2044     $form->{addition} = "SAVED AS NEW";
2045     $form->save_history;
2046   }
2047   # /saving the history
2048   $form->{id} = 0;
2049   if ($form->{"original_partnumber"} &&
2050       ($form->{"partnumber"} eq $form->{"original_partnumber"})) {
2051     $form->{partnumber} = "";
2052   }
2053   &save;
2054   $lxdebug->leave_sub();
2055 }
2056
2057 sub delete {
2058   $lxdebug->enter_sub();
2059
2060   $auth->assert('part_service_assembly_edit');
2061
2062   # saving the history
2063   if(!exists $form->{addition}) {
2064     $form->{snumbers} = qq|partnumber_| . $form->{partnumber};
2065     $form->{addition} = "DELETED";
2066     $form->save_history;
2067   }
2068   # /saving the history
2069   my $rc = IC->delete(\%myconfig, \%$form);
2070
2071   # redirect
2072   $form->redirect($locale->text('Item deleted!')) if ($rc > 0);
2073   $form->error($locale->text('Cannot delete item!'));
2074
2075   $lxdebug->leave_sub();
2076 }
2077
2078 sub price_row {
2079   $lxdebug->enter_sub();
2080
2081   $auth->assert('part_service_assembly_details');
2082
2083   my ($numrows) = @_;
2084
2085   my @PRICES = map +{
2086     pricegroup    => $form->{"pricegroup_$_"},
2087     pricegroup_id => $form->{"pricegroup_id_$_"},
2088     price         => $form->{"price_$_"},
2089   }, 1 .. $numrows;
2090
2091   print $form->parse_html_template('ic/price_row', { PRICES => \@PRICES });
2092
2093   $lxdebug->leave_sub();
2094 }
2095
2096 sub ajax_autocomplete {
2097   $main::lxdebug->enter_sub();
2098
2099   my $form     = $main::form;
2100   my %myconfig = %main::myconfig;
2101
2102   $form->{column}          = 'description'     unless $form->{column} =~ /^partnumber|description$/;
2103   $form->{$form->{column}} = $form->{q}           || '';
2104   $form->{limit}           = ($form->{limit} * 1) || 10;
2105   $form->{searchitems}   ||= '';
2106
2107   my @results = IC->all_parts(\%myconfig, $form);
2108
2109   print $form->ajax_response_header(),
2110         $form->parse_html_template('ic/ajax_autocomplete');
2111
2112   $main::lxdebug->leave_sub();
2113 }
2114
2115 sub back_to_record {
2116   _check_io_auth();
2117
2118
2119   delete @{$::form}{qw(action action_add action_back_to_record back_sub description item notes partnumber sellprice taxaccount2 unit vc)};
2120
2121   $::auth->restore_form_from_session($::form->{previousform}, clobber => 1);
2122   $::form->{rowcount}--;
2123   $::form->{action}   = 'display_form';
2124   $::form->{callback} = $::form->{script} . '?' . join('&', map { $::form->escape($_) . '=' . $::form->escape($::form->{$_}) } sort keys %{ $::form });
2125   $::form->redirect;
2126 }
2127
2128 sub continue { call_sub($form->{"nextsub"}); }
2129
2130 sub dispatcher {
2131   my $action = first { $::form->{"action_${_}"} } qw(add back_to_record);
2132   $::form->error($::locale->text('No action defined.')) unless $action;
2133
2134   $::form->{dispatched_action} = $action;
2135   call_sub($action);
2136 }