1 #=====================================================================
 
   4 # Based on SQL-Ledger Version 2.1.9
 
   5 # Web http://www.lx-office.org
 
   7 #=====================================================================
 
   8 # SQL-Ledger, Accounting
 
  11 #  Author: Dieter Simader
 
  12 #   Email: dsimader@sql-ledger.org
 
  13 #     Web: http://www.sql-ledger.org
 
  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.
 
  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 #======================================================================
 
  30 # Inventory Control module
 
  32 #======================================================================
 
  34 use POSIX qw(strftime);
 
  35 use List::Util qw(first max);
 
  36 use List::MoreUtils qw(any);
 
  41 use SL::Helper::Flash;
 
  43 use SL::ReportGenerator;
 
  51 our ($form, $locale, %myconfig, $lxdebug, $auth);
 
  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";
 
  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');
 
  79   $lxdebug->enter_sub();
 
  81   $auth->assert('part_service_assembly_edit');
 
  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;
 
  88   IC->get_pricegroups(\%myconfig, \%$form);
 
  92   $lxdebug->leave_sub();
 
  96   $lxdebug->enter_sub();
 
  98   $auth->assert('part_service_assembly_details');
 
 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
 
 104   my %is_xyz     = map { +"is_$_" => ($form->{searchitems} eq $_) } qw(part service assembly);
 
 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});
 
 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');
 
 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}, });
 
 123   $lxdebug->leave_sub();
 
 126 sub search_update_prices {
 
 127   $lxdebug->enter_sub();
 
 129   $auth->assert('part_service_assembly_edit');
 
 131   my $pricegroups = IC->get_pricegroups(\%myconfig, \%$form);
 
 133   $form->{title} = $locale->text('Update Prices');
 
 137   print $form->parse_html_template('ic/search_update_prices', { PRICE_ROWS => $pricegroups });
 
 139   $lxdebug->leave_sub();
 
 142 sub confirm_price_update {
 
 143   $lxdebug->enter_sub();
 
 145   $auth->assert('part_service_assembly_edit');
 
 148   my $value_found = undef;
 
 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});
 
 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);
 
 159     } elsif (!$value && ($form->{$value_idx} ne '')) {
 
 160       push @errors, $locale->text('No valid number entered for pricegroup "#1".', $name);
 
 162     } elsif (0 < $value) {
 
 167   push @errors, $locale->text('No prices will be updated because no prices have been entered.') if (!$value_found);
 
 169   my $num_matches = IC->get_num_matches_for_priceupdate();
 
 174     $form->show_generic_error(join('<br>', @errors), 'back_button' => 1);
 
 177   $form->{nextsub} = "update_prices";
 
 179   map { delete $form->{$_} } qw(action header);
 
 181   print $form->parse_html_template('ic/confirm_price_update', { HIDDENS     => [ map { name => $_, value => $form->{$_} }, keys %$form ],
 
 182                                                                 num_matches => $num_matches });
 
 184   $lxdebug->leave_sub();
 
 188   $lxdebug->enter_sub();
 
 190   $auth->assert('part_service_assembly_edit');
 
 192   my $num_updated = IC->update_prices(\%myconfig, \%$form);
 
 194   if (-1 != $num_updated) {
 
 195     $form->redirect($locale->text('#1 prices were updated.', $num_updated));
 
 197     $form->error($locale->text('Could not update prices!'));
 
 200   $lxdebug->leave_sub();
 
 204 #  $lxdebug->enter_sub();
 
 206 #  $auth->assert('part_service_assembly_edit');
 
 208 #  our ($j, $lastndx);
 
 211 #  $form->{title} = $locale->text('Top 100 hinzufuegen');
 
 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);
 
 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),
 
 225 #  my ($partnumber, $description, $unit, $sellprice, $soldtotal);
 
 226 #  # if choice set data
 
 227 ##  if ($form->{ndx}) {
 
 228 ##    for my $i (0 .. $form->{ndxs_counter}) {
 
 230 ##      # insert data into top100
 
 231 ##      push @{ $form->{parts} },
 
 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"},
 
 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"};
 
 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"},
 
 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"}>
 
 269 #  print $form->parse_html_template('ic/choice', +{ HIDDENS => \@HIDDENS, PARTS => \@PARTS });
 
 271 #  $lxdebug->leave_sub();
 
 275 #  $lxdebug->enter_sub();
 
 277 #  $auth->assert('part_service_assembly_edit');
 
 280 #  our ($partnumber, $description, $unit, $sellprice, $soldtotal);
 
 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);
 
 286 #  $form->{title} = $locale->text('Top 100 hinzufuegen');
 
 291 #  <h1>| . $locale->text('choice part') . qq|</h1>
 
 292 #  <form method=post action=ic.pl>
 
 294 #        <tr class=listheading>
 
 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>
 
 304 #  my $i = $form->{rows};
 
 306 #  for ($j = 1; $j <= $i; $j++) {
 
 309 #        <tr class=listrow| . ($j % 2) . qq|>|;
 
 312 #            <td><input name=ndx class=radio type=radio value=$j checked></td>|;
 
 315 #          <td><input name=ndx class=radio type=radio value=$j></td>|;
 
 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>
 
 325 #        <input name="new_id_$j" type=hidden value="$form->{"id_$j"}">|;
 
 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}">
 
 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}">
 
 351 #<input type=hidden name=row value=$j>
 
 353 #<input type=hidden name=nextsub value=item_selected>
 
 355 #<input name=lastndx type=hidden value=$lastndx>
 
 357 #<input name=ndxs_counter type=hidden value=$form->{ndxs_counter}>|;
 
 361 #  if (($form->{ndxs_counter}) > 0) {
 
 362 #    for ($i = 1; ($i < $form->{ndxs_counter} + 1); $i++) {
 
 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"};
 
 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"}>
 
 383 #<input class=submit type=submit name=action value="|
 
 384 #    . $locale->text('TOP100') . qq|">
 
 388 #  $lxdebug->leave_sub();
 
 392   $lxdebug->enter_sub();
 
 394   $auth->assert('part_service_assembly_edit');
 
 397     $form->{ndxs_counter}++;
 
 399     if ($form->{ndxs_counter} > 0) {
 
 401       my $index = $form->{ndx};
 
 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"};
 
 411   $lxdebug->leave_sub();
 
 415   $lxdebug->enter_sub();
 
 417   $auth->assert('part_service_assembly_edit');
 
 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);
 
 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} = "";
 
 431   $form->{number}      = "position";
 
 432   $form->{l_number}    = "Y";
 
 436   $form->{title} = $locale->text('Top 100');
 
 438   $revers   = $form->{revers};
 
 439   $lastsort = $form->{lastsort};
 
 441   if (($form->{lastsort} eq "") && ($form->{sort} eq undef)) {
 
 443     $form->{lastsort} = "partnumber";
 
 444     $form->{sort}     = "partnumber";
 
 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);
 
 451   # if we have a serialnumber limit search
 
 452   if ($form->{serialnumber} || $form->{l_serialnumber}) {
 
 453     $form->{l_serialnumber} = "Y";
 
 454     unless (   $form->{bought}
 
 457             || $form->{quoted}) {
 
 458       $form->{bought} = $form->{sold} = 1;
 
 461   IC->all_parts(\%myconfig, \%$form);
 
 463   if ($form->{itemstatus} eq 'active') {
 
 464     $option .= $locale->text('Active') . " : ";
 
 466   if ($form->{itemstatus} eq 'obsolete') {
 
 467     $option .= $locale->text('Obsolete') . " : ";
 
 469   if ($form->{itemstatus} eq 'orphaned') {
 
 470     $option .= $locale->text('Orphaned') . " : ";
 
 472   if ($form->{itemstatus} eq 'onhand') {
 
 473     $option .= $locale->text('On Hand') . " : ";
 
 474     $form->{l_onhand} = "Y";
 
 476   if ($form->{itemstatus} eq 'short') {
 
 477     $option .= $locale->text('Short') . " : ";
 
 478     $form->{l_onhand} = "Y";
 
 480   if ($form->{onorder}) {
 
 481     $form->{l_ordnumber} = "Y";
 
 482     $callback .= "&onorder=$form->{onorder}";
 
 483     $option   .= $locale->text('On Order') . " : ";
 
 485   if ($form->{ordered}) {
 
 486     $form->{l_ordnumber} = "Y";
 
 487     $callback .= "&ordered=$form->{ordered}";
 
 488     $option   .= $locale->text('Ordered') . " : ";
 
 491     $form->{l_quonumber} = "Y";
 
 492     $callback .= "&rfq=$form->{rfq}";
 
 493     $option   .= $locale->text('RFQ') . " : ";
 
 495   if ($form->{quoted}) {
 
 496     $form->{l_quonumber} = "Y";
 
 497     $callback .= ""ed=$form->{quoted}";
 
 498     $option   .= $locale->text('Quoted') . " : ";
 
 500   if ($form->{bought}) {
 
 501     $form->{l_invnumber} = "Y";
 
 502     $callback .= "&bought=$form->{bought}";
 
 503     $option   .= $locale->text('Bought') . " : ";
 
 506     $form->{l_invnumber} = "Y";
 
 507     $callback .= "&sold=$form->{sold}";
 
 508     $option   .= $locale->text('Sold') . " : ";
 
 515       || $form->{quoted}) {
 
 517     $form->{l_lastcost} = "";
 
 518     $form->{l_name}     = "Y";
 
 519     if ($form->{transdatefrom}) {
 
 520       $callback .= "&transdatefrom=$form->{transdatefrom}";
 
 522         . $locale->text('From')
 
 524         . $locale->date(\%myconfig, $form->{transdatefrom}, 1);
 
 526     if ($form->{transdateto}) {
 
 527       $callback .= "&transdateto=$form->{transdateto}";
 
 529         . $locale->text('To')
 
 531         . $locale->date(\%myconfig, $form->{transdateto}, 1);
 
 537   if ($form->{partnumber}) {
 
 538     $callback .= "&partnumber=$form->{partnumber}";
 
 539     $option   .= $locale->text('Part Number') . qq| : $form->{partnumber}<br>|;
 
 542     $callback .= "&partnumber=$form->{ean}";
 
 543     $option   .= $locale->text('EAN') . qq| : $form->{ean}<br>|;
 
 545   if ($form->{partsgroup}) {
 
 546     $callback .= "&partsgroup=$form->{partsgroup}";
 
 547     $option   .= $locale->text('Group') . qq| : $form->{partsgroup}<br>|;
 
 549   if ($form->{serialnumber}) {
 
 550     $callback .= "&serialnumber=$form->{serialnumber}";
 
 551     $option   .= $locale->text('Serial Number') . qq| : $form->{serialnumber}<br>|;
 
 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>|;
 
 560     $callback .= "&make=$form->{make}";
 
 561     $option   .= $locale->text('Make') . qq| : $form->{make}<br>|;
 
 563   if ($form->{model}) {
 
 564     $callback .= "&model=$form->{model}";
 
 565     $option   .= $locale->text('Model') . qq| : $form->{model}<br>|;
 
 567   if ($form->{drawing}) {
 
 568     $callback .= "&drawing=$form->{drawing}";
 
 569     $option   .= $locale->text('Drawing') . qq| : $form->{drawing}<br>|;
 
 571   if ($form->{microfiche}) {
 
 572     $callback .= "µfiche=$form->{microfiche}";
 
 573     $option   .= $locale->text('Microfiche') . qq| : $form->{microfiche}<br>|;
 
 575   if ($form->{l_soldtotal}) {
 
 576     $callback .= "&soldtotal=$form->{soldtotal}";
 
 577     $option   .= $locale->text('soldtotal') . qq| : $form->{soldtotal}<br>|;
 
 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)
 
 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} = "";
 
 593     $form->{l_linetotallistprice} = "Y" if $form->{l_listprice};
 
 596   if ($form->{searchitems} eq 'service') {
 
 598     # remove bin, weight and rop from list
 
 599     map { $form->{"l_$_"} = "" } qw(bin weight rop);
 
 601     $form->{l_onhand} = "";
 
 603     # qty is irrelevant unless bought or sold
 
 609         || $form->{quoted}) {
 
 610       $form->{l_onhand} = "Y";
 
 612       $form->{l_linetotalsellprice} = "";
 
 613       $form->{l_linetotallastcost}  = "";
 
 617   foreach my $item (@columns) {
 
 618     if ($form->{"l_$item"} eq "Y") {
 
 619       push @column_index, $item;
 
 621       # add column to callback
 
 622       $callback .= "&l_$item=Y";
 
 626   if ($form->{l_subtotal} eq 'Y') {
 
 627     $callback .= "&l_subtotal=Y";
 
 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')
 
 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')
 
 640   $column_header{partsgroup} =
 
 641       qq|<th nowrap><a class=listheading href=$callback&sort=partsgroup>|
 
 642     . $locale->text('Group')
 
 644   $column_header{bin} =
 
 645       qq|<th><a class=listheading href=$callback&sort=bin>|
 
 646     . $locale->text('Bin')
 
 648   $column_header{priceupdate} =
 
 649       qq|<th nowrap><a class=listheading href=$callback&sort=priceupdate>|
 
 650     . $locale->text('Updated')
 
 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')
 
 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')
 
 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>|;
 
 669   $column_header{invnumber} =
 
 670       qq|<th nowrap><a class=listheading href=$callback&sort=invnumber>|
 
 671     . $locale->text('Invoice Number')
 
 673   $column_header{ordnumber} =
 
 674       qq|<th nowrap><a class=listheading href=$callback&sort=ordnumber>|
 
 675     . $locale->text('Order Number')
 
 677   $column_header{quonumber} =
 
 678       qq|<th nowrap><a class=listheading href=$callback&sort=quonumber>|
 
 679     . $locale->text('Quotation')
 
 682   $column_header{name} =
 
 683       qq|<th nowrap><a class=listheading href=$callback&sort=name>|
 
 684     . $locale->text('Name')
 
 687   $column_header{sellprice} =
 
 688       qq|<th class=listheading nowrap>|
 
 689     . $locale->text('Sell Price')
 
 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>|;
 
 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')
 
 704   $column_header{microfiche} =
 
 705       qq|<th nowrap><a class=listheading href=$callback&sort=microfiche>|
 
 706     . $locale->text('Microfiche')
 
 709   $column_header{serialnumber} =
 
 710       qq|<th nowrap><a class=listheading href=$callback&sort=serialnumber>|
 
 711     . $locale->text('Serial Number')
 
 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')
 
 719   my $colspan = $#column_index + 1;
 
 722     <h1>$form->{title}</h1>
 
 726   <tr><td colspan=$colspan>$option</td></tr>
 
 728   <tr class=listheading>
 
 731   map { print "\n$column_header{$_}" } @column_index;
 
 737   # add order to callback
 
 738   $form->{callback} = $callback .= "&sort=$form->{sort}";
 
 740   # escape callback for href
 
 741   $callback = $form->escape($callback);
 
 743   if (@{ $form->{parts} }) {
 
 744     $sameitem = $form->{parts}->[0]->{ $form->{sort} };
 
 747   # insert numbers for top100
 
 749   foreach my $ref (@{ $form->{parts} }) {
 
 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"};
 
 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"}>
 
 772       push @{ $form->{parts} },
 
 774           partnumber  => "$partnumber",
 
 775           description => "$description",
 
 777           sellprice   => "$sellprice",
 
 778           soldtotal   => "$soldtotal" };
 
 781        # build data for columns
 
 783   foreach my $ref (@{ $form->{parts} }) {
 
 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} };
 
 792     $ref->{exchangerate} = 1 unless $ref->{exchangerate};
 
 793     $ref->{sellprice} *= $ref->{exchangerate};
 
 794     $ref->{listprice} *= $ref->{exchangerate};
 
 795     $ref->{lastcost}  *= $ref->{exchangerate};
 
 797     # use this for assemblies
 
 798     $onhand = $ref->{onhand};
 
 801     if ($ref->{assemblyitem}) {
 
 803       $onhand = 0 if ($form->{sold});
 
 806     $ref->{description} =~ s/\n/<br>/g;
 
 808     $column_data{number} =
 
 810       . $form->format_amount(\%myconfig, $ref->{number})
 
 812     $column_data{partnumber} =
 
 813       "<td align=$align>$ref->{partnumber} </a></td>";
 
 814     $column_data{description} = "<td>$ref->{description} </td>";
 
 815     $column_data{partsgroup}  = "<td>$ref->{partsgroup} </td>";
 
 817     $column_data{onhand} =
 
 819       . $form->format_amount(\%myconfig, $ref->{onhand})
 
 821     $column_data{sellprice} =
 
 823       . $form->format_amount(\%myconfig, $ref->{sellprice})
 
 825     $column_data{listprice} =
 
 827       . $form->format_amount(\%myconfig, $ref->{listprice})
 
 829     $column_data{lastcost} =
 
 831       . $form->format_amount(\%myconfig, $ref->{lastcost})
 
 834     $column_data{linetotalsellprice} = "<td align=right>"
 
 835       . $form->format_amount(\%myconfig, $ref->{onhand} * $ref->{sellprice}, 2)
 
 837     $column_data{linetotallastcost} = "<td align=right>"
 
 838       . $form->format_amount(\%myconfig, $ref->{onhand} * $ref->{lastcost}, 2)
 
 840     $column_data{linetotallistprice} = "<td align=right>"
 
 841       . $form->format_amount(\%myconfig, $ref->{onhand} * $ref->{listprice}, 2)
 
 844     if (!$ref->{assemblyitem}) {
 
 845       $totalsellprice += $onhand * $ref->{sellprice};
 
 846       $totallastcost  += $onhand * $ref->{lastcost};
 
 847       $totallistprice += $onhand * $ref->{listprice};
 
 849       $subtotalonhand    += $onhand;
 
 850       $subtotalsellprice += $onhand * $ref->{sellprice};
 
 851       $subtotallastcost  += $onhand * $ref->{lastcost};
 
 852       $subtotallistprice += $onhand * $ref->{listprice};
 
 857       . $form->format_amount(\%myconfig, $ref->{rop}) . "</td>";
 
 858     $column_data{weight} =
 
 860       . $form->format_amount(\%myconfig, $ref->{weight})
 
 862     $column_data{unit}        = "<td>$ref->{unit} </td>";
 
 863     $column_data{bin}         = "<td>$ref->{bin} </td>";
 
 864     $column_data{priceupdate} = "<td>$ref->{priceupdate} </td>";
 
 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>";
 
 879     $column_data{name} = "<td>$ref->{name}</td>";
 
 881     $column_data{image} =
 
 883       ? "<td><a href=$ref->{image}><img src=$ref->{image} height=32 border=0></a></td>"
 
 885     $column_data{drawing} =
 
 887       ? "<td><a href=$ref->{drawing}>$ref->{drawing}</a></td>"
 
 889     $column_data{microfiche} =
 
 891       ? "<td><a href=$ref->{microfiche}>$ref->{microfiche}</a></td>"
 
 894     $column_data{serialnumber} = "<td>$ref->{serialnumber}</td>";
 
 896     $column_data{soldtotal} = "<td  align=right>$ref->{soldtotal}</td>";
 
 900     print "<tr class=listrow$i>";
 
 902     map { print "\n$column_data{$_}" } @column_index;
 
 909   if ($form->{l_subtotal} eq 'Y') {
 
 910     parts_subtotal(\@column_index, \$subtotalonhand, \$subtotalsellprice, \$subtotallastcost, \$subtotallistprice);
 
 913   if ($form->{"l_linetotal"}) {
 
 914     map { $column_data{$_} = "<td> </td>" } @column_index;
 
 915     $column_data{linetotalsellprice} =
 
 916         "<th class=listtotal align=right>"
 
 917       . $form->format_amount(\%myconfig, $totalsellprice, 2)
 
 919     $column_data{linetotallastcost} =
 
 920         "<th class=listtotal align=right>"
 
 921       . $form->format_amount(\%myconfig, $totallastcost, 2)
 
 923     $column_data{linetotallistprice} =
 
 924         "<th class=listtotal align=right>"
 
 925       . $form->format_amount(\%myconfig, $totallistprice, 2)
 
 928     print "<tr class=listtotal>";
 
 930     map { print "\n$column_data{$_}" } @column_index;
 
 937   <tr><td colspan=$colspan><hr size=3 noshade></td></tr>
 
 946 <form method=post action=$form->{script}>
 
 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}">
 
 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}">|;
 
 968 <!--    <input type=hidden name=ndxs_counter value="$form->{ndxs_counter}">-->
 
 970 <!--    <input class=submit type=submit name=action value="|
 
 971     . $locale->text('choice') . qq|"> -->
 
 976   $lxdebug->leave_sub();
 
 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
 
 984 # flags coming from the form:
 
 986 #  searchitems=part revers=0 lastsort=''
 
 989 # partnumber ean description partsgroup serialnumber make model drawing microfiche
 
 990 # transdatefrom transdateto
 
 993 #  itemstatus = active | onhand | short | obsolete | orphaned
 
 994 #  action     = continue | top100
 
 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
 
1003 #  nextsub revers lastsort sort ndxs_counter
 
1005 sub generate_report {
 
1006   $lxdebug->enter_sub();
 
1008   $auth->assert('part_service_assembly_details');
 
1010   my ($revers, $lastsort, $description);
 
1012   my $cvar_configs = CVar->get_configs('module' => 'IC');
 
1014   $form->{title} = (ucfirst $form->{searchitems}) . "s";
 
1015   $form->{title} =~ s/ys$/ies/;
 
1016   $form->{title} = $locale->text($form->{title});
 
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     'insertdate'         => { 'text' => $locale->text('Insert Date'), },
 
1027     'invnumber'          => { 'text' => $locale->text('Invoice Number'), },
 
1028     'lastcost'           => { 'text' => $locale->text('Last Cost'), },
 
1029     'linetotallastcost'  => { 'text' => $locale->text('Extended'), },
 
1030     'linetotallistprice' => { 'text' => $locale->text('Extended'), },
 
1031     'linetotalsellprice' => { 'text' => $locale->text('Extended'), },
 
1032     'listprice'          => { 'text' => $locale->text('List Price'), },
 
1033     'microfiche'         => { 'text' => $locale->text('Microfiche'), },
 
1034     'name'               => { 'text' => $locale->text('Name'), },
 
1035     'onhand'             => { 'text' => $locale->text('Stocked Qty'), },
 
1036     'ordnumber'          => { 'text' => $locale->text('Order Number'), },
 
1037     'partnumber'         => { 'text' => $locale->text('Part Number'), },
 
1038     'partsgroup'         => { 'text' => $locale->text('Group'), },
 
1039     'priceupdate'        => { 'text' => $locale->text('Updated'), },
 
1040     'quonumber'          => { 'text' => $locale->text('Quotation'), },
 
1041     'rop'                => { 'text' => $locale->text('ROP'), },
 
1042     'sellprice'          => { 'text' => $locale->text('Sell Price'), },
 
1043     'serialnumber'       => { 'text' => $locale->text('Serial Number'), },
 
1044     'soldtotal'          => { 'text' => $locale->text('Qty in Selected Records'), },
 
1045     'name'               => { 'text' => $locale->text('Name in Selected Records'), },
 
1046     'transdate'          => { 'text' => $locale->text('Transdate'), },
 
1047     'unit'               => { 'text' => $locale->text('Unit'), },
 
1048     'weight'             => { 'text' => $locale->text('Weight'), },
 
1049     'shop'               => { 'text' => $locale->text('Shopartikel'), },
 
1050     'projectnumber'      => { 'text' => $locale->text('Project Number'), },
 
1051     'projectdescription' => { 'text' => $locale->text('Project Description'), },
 
1054   $revers     = $form->{revers};
 
1055   $lastsort   = $form->{lastsort};
 
1057   # sorting and direction of sorting
 
1058   # ToDO: change this to the simpler field+direction method
 
1059   if (($form->{lastsort} eq "") && ($form->{sort} eq undef)) {
 
1060     $form->{revers}   = 0;
 
1061     $form->{lastsort} = "partnumber";
 
1062     $form->{sort}     = "partnumber";
 
1064     if ($form->{lastsort} eq $form->{sort}) {
 
1065       $form->{revers} = 1 - $form->{revers};
 
1067       $form->{revers} = 0;
 
1068       $form->{lastsort} = $form->{sort};
 
1072   # special case if we have a serialnumber limit search
 
1073   # serialnumbers are only given in invoices and orders,
 
1074   # so they can only pop up in bought, sold, rfq, and quoted stuff
 
1075   $form->{no_sn_joins} = 'Y' if (   !$form->{bought} && !$form->{sold}
 
1076                                  && !$form->{rfq}    && !$form->{quoted}
 
1077                                  && ($form->{l_serialnumber} || $form->{serialnumber}));
 
1079   # special case for any checkbox of bought | sold | onorder | ordered | rfq | quoted.
 
1080   # if any of these are ticked the behavior changes slightly for lastcost
 
1081   # since all those are aggregation checks for the legder tables this is an internal switch
 
1082   # refered to as ledgerchecks
 
1083   $form->{ledgerchecks} = 'Y' if (   $form->{bought} || $form->{sold} || $form->{onorder}
 
1084                                   || $form->{ordered} || $form->{rfq} || $form->{quoted});
 
1086   # if something should be activated if something else is active, enter it here
 
1087   my %dependencies = (
 
1088     onhand       => [ qw(l_onhand) ],
 
1089     short        => [ qw(l_onhand) ],
 
1090     onorder      => [ qw(l_ordnumber) ],
 
1091     ordered      => [ qw(l_ordnumber) ],
 
1092     rfq          => [ qw(l_quonumber) ],
 
1093     quoted       => [ qw(l_quonumber) ],
 
1094     bought       => [ qw(l_invnumber) ],
 
1095     sold         => [ qw(l_invnumber) ],
 
1096     ledgerchecks => [ qw(l_name) ],
 
1097     serialnumber => [ qw(l_serialnumber) ],
 
1098     no_sn_joins  => [ qw(bought sold) ],
 
1101   # get name of partsgroup if id is given
 
1103   if ($form->{partsgroup_id}) {
 
1104     my $pg = SL::DB::PartsGroup->new(id => $form->{partsgroup_id})->load;
 
1105     $pg_name = $pg->{'partsgroup'};
 
1108   # these strings get displayed at the top of the results to indicate the user which switches were used
 
1110     active        => $locale->text('Active'),
 
1111     obsolete      => $locale->text('Obsolete'),
 
1112     orphaned      => $locale->text('Orphaned'),
 
1113     onhand        => $locale->text('On Hand'),
 
1114     short         => $locale->text('Short'),
 
1115     onorder       => $locale->text('On Order'),
 
1116     ordered       => $locale->text('Ordered'),
 
1117     rfq           => $locale->text('RFQ'),
 
1118     quoted        => $locale->text('Quoted'),
 
1119     bought        => $locale->text('Bought'),
 
1120     sold          => $locale->text('Sold'),
 
1121     transdatefrom => $locale->text('From')       . " " . $locale->date(\%myconfig, $form->{transdatefrom}, 1),
 
1122     transdateto   => $locale->text('To (time)')  . " " . $locale->date(\%myconfig, $form->{transdateto}, 1),
 
1123     partnumber    => $locale->text('Part Number')      . ": '$form->{partnumber}'",
 
1124     partsgroup    => $locale->text('Group')            . ": '$form->{partsgroup}'",
 
1125     partsgroup_id => $locale->text('Group')            . ": '$pg_name'",
 
1126     serialnumber  => $locale->text('Serial Number')    . ": '$form->{serialnumber}'",
 
1127     description   => $locale->text('Part Description') . ": '$form->{description}'",
 
1128     make          => $locale->text('Make')             . ": '$form->{make}'",
 
1129     model         => $locale->text('Model')            . ": '$form->{model}'",
 
1130     drawing       => $locale->text('Drawing')          . ": '$form->{drawing}'",
 
1131     microfiche    => $locale->text('Microfiche')       . ": '$form->{microfiche}'",
 
1132     l_soldtotal   => $locale->text('Qty in Selected Records'),
 
1133     ean           => $locale->text('EAN')              . ": '$form->{ean}'",
 
1134     insertdatefrom => $locale->text('Insert Date') . ": " . $locale->text('From')       . " " . $locale->date(\%myconfig, $form->{insertdatefrom}, 1),
 
1135     insertdateto   => $locale->text('Insert Date') . ": " . $locale->text('To (time)')  . " " . $locale->date(\%myconfig, $form->{insertdateto}, 1),
 
1138   my @itemstatus_keys = qw(active obsolete orphaned onhand short);
 
1139   my @callback_keys   = qw(onorder ordered rfq quoted bought sold partnumber partsgroup partsgroup_id serialnumber description make model
 
1140                            drawing microfiche l_soldtotal l_deliverydate transdatefrom transdateto insertdatefrom insertdateto ean shop);
 
1142   # calculate dependencies
 
1143   for (@itemstatus_keys, @callback_keys) {
 
1144     next if ($form->{itemstatus} ne $_ && !$form->{$_});
 
1145     map { $form->{$_} = 'Y' } @{ $dependencies{$_} } if $dependencies{$_};
 
1148   # generate callback and optionstrings
 
1150   for my  $key (@itemstatus_keys, @callback_keys) {
 
1151     next if ($form->{itemstatus} ne $key && !$form->{$key});
 
1152     push @options, $optiontexts{$key};
 
1155   # special case for lastcost
 
1156   if ($form->{ledgerchecks}){
 
1157     # ledgerchecks don't know about sellprice or lastcost. they just return a
 
1158     # price. so rename sellprice to price, and drop lastcost.
 
1159     $column_defs{sellprice}{text} = $locale->text('Price');
 
1160     $form->{l_lastcost} = ""
 
1163   if ($form->{description}) {
 
1164     $description = $form->{description};
 
1165     $description =~ s/\n/<br>/g;
 
1168   if ($form->{l_linetotal}) {
 
1169     $form->{l_qty} = "Y";
 
1170     $form->{l_linetotalsellprice} = "Y" if $form->{l_sellprice};
 
1171     $form->{l_linetotallastcost}  = $form->{searchitems} eq 'assembly' && !$form->{bom} ? "" : 'Y' if  $form->{l_lastcost};
 
1172     $form->{l_linetotallistprice} = "Y" if $form->{l_listprice};
 
1175   if ($form->{searchitems} eq 'service') {
 
1177     # remove bin, weight and rop from list
 
1178     map { $form->{"l_$_"} = "" } qw(bin weight rop);
 
1180     $form->{l_onhand} = "";
 
1182     # qty is irrelevant unless bought or sold
 
1183     if (   $form->{bought}
 
1188         || $form->{quoted}) {
 
1189 #      $form->{l_onhand} = "Y";
 
1191       $form->{l_linetotalsellprice} = "";
 
1192       $form->{l_linetotallastcost}  = "";
 
1196   # soldtotal doesn't make sense with more than one bsooqr option.
 
1197   # so reset it to sold (the most common option), and issue a warning
 
1199   # also it doesn't make sense without bsooqr. disable and issue a warning too
 
1200   my @bsooqr = qw(sold bought onorder ordered rfq quoted);
 
1201   my $bsooqr_mode = grep { $form->{$_} } @bsooqr;
 
1202   if ($form->{l_subtotal} && 1 < $bsooqr_mode) {
 
1203     my $enabled       = first { $form->{$_} } @bsooqr;
 
1204     $form->{$_}       = ''   for @bsooqr;
 
1205     $form->{$enabled} = 'Y';
 
1207     push @options, $::locale->text('Subtotal cannot distinguish betweens record types. Only one of the selected record types will be displayed: #1', $optiontexts{$enabled});
 
1209   if ($form->{l_soldtotal} && !$bsooqr_mode) {
 
1210     delete $form->{l_soldtotal};
 
1212     flash('warning', $::locale->text('Soldtotal does not make sense without any bsooqr options'));
 
1214   if ($form->{l_name} && !$bsooqr_mode) {
 
1215     delete $form->{l_name};
 
1217     flash('warning', $::locale->text('Name does not make sense without any bsooqr options'));
 
1219   IC->all_parts(\%myconfig, \%$form);
 
1222     partnumber description notes partsgroup bin onhand rop soldtotal unit listprice
 
1223     linetotallistprice sellprice linetotalsellprice lastcost linetotallastcost
 
1224     priceupdate weight image drawing microfiche invnumber ordnumber quonumber
 
1225     transdate name serialnumber deliverydate ean projectnumber projectdescription
 
1229   my $pricegroups = SL::DB::Manager::Pricegroup->get_all(sort => 'id');
 
1230   my @pricegroup_columns;
 
1231   my %column_defs_pricegroups;
 
1232   if ($form->{l_pricegroups}) {
 
1233     @pricegroup_columns      = map { "pricegroup_" . $_->id } @{ $pricegroups };
 
1234     %column_defs_pricegroups = map {
 
1235       "pricegroup_" . $_->id => {
 
1236         text    => $::locale->text('Pricegroup') . ' ' . $_->pricegroup,
 
1239     }  @{ $pricegroups };
 
1241   push @columns, @pricegroup_columns;
 
1243   my @includeable_custom_variables = grep { $_->{includeable} } @{ $cvar_configs };
 
1244   my @searchable_custom_variables  = grep { $_->{searchable} }  @{ $cvar_configs };
 
1245   my %column_defs_cvars            = map { +"cvar_$_->{name}" => { 'text' => $_->{description} } } @includeable_custom_variables;
 
1247   push @columns, map { "cvar_$_->{name}" } @includeable_custom_variables;
 
1249   %column_defs = (%column_defs, %column_defs_cvars, %column_defs_pricegroups);
 
1250   map { $column_defs{$_}->{visible} ||= $form->{"l_$_"} ? 1 : 0 } @columns;
 
1251   map { $column_defs{$_}->{align}   = 'right' } qw(onhand sellprice listprice lastcost linetotalsellprice linetotallastcost linetotallistprice rop weight soldtotal shop), @pricegroup_columns;
 
1253   my @hidden_variables = (
 
1254     qw(l_subtotal l_linetotal searchitems itemstatus bom l_pricegroups insertdatefrom insertdateto),
 
1257     map({ "cvar_$_->{name}" } @searchable_custom_variables),
 
1258     map({'cvar_'. $_->{name} .'_qtyop'} grep({$_->{type} eq 'number'} @searchable_custom_variables)),
 
1259     map({ "l_$_" } @columns),
 
1262   my $callback         = build_std_url('action=generate_report', grep { $form->{$_} } @hidden_variables);
 
1264   my @sort_full        = qw(partnumber description onhand soldtotal deliverydate insertdate shop);
 
1265   my @sort_no_revers   = qw(partsgroup bin priceupdate invnumber ordnumber quonumber name image drawing serialnumber);
 
1267   foreach my $col (@sort_full) {
 
1268     $column_defs{$col}->{link} = join '&', $callback, "sort=$col", map { "$_=" . E($form->{$_}) } qw(revers lastsort);
 
1270   map { $column_defs{$_}->{link} = "${callback}&sort=$_" } @sort_no_revers;
 
1272   # add order to callback
 
1273   $form->{callback} = join '&', ($callback, map { "${_}=" . E($form->{$_}) } qw(sort revers));
 
1275   my $report = SL::ReportGenerator->new(\%myconfig, $form);
 
1277   my %attachment_basenames = (
 
1278     'part'     => $locale->text('part_list'),
 
1279     'service'  => $locale->text('service_list'),
 
1280     'assembly' => $locale->text('assembly_list'),
 
1283   $report->set_options('raw_top_info_text'     => $form->parse_html_template('ic/generate_report_top', { options => \@options }),
 
1284                        'raw_bottom_info_text'  => $form->parse_html_template('ic/generate_report_bottom'),
 
1285                        'output_format'         => 'HTML',
 
1286                        'title'                 => $form->{title},
 
1287                        'attachment_basename'   => $attachment_basenames{$form->{searchitems}} . strftime('_%Y%m%d', localtime time),
 
1289   $report->set_options_from_form();
 
1290   $locale->set_numberformat_wo_thousands_separator(\%myconfig) if lc($report->{options}->{output_format}) eq 'csv';
 
1292   $report->set_columns(%column_defs);
 
1293   $report->set_column_order(@columns);
 
1295   $report->set_export_options('generate_report', @hidden_variables, qw(sort revers));
 
1297   $report->set_sort_indicator($form->{sort}, $form->{revers} ? 0 : 1);
 
1299   CVar->add_custom_variables_to_report('module'         => 'IC',
 
1300                                        'trans_id_field' => 'id',
 
1301                                        'configs'        => $cvar_configs,
 
1302                                        'column_defs'    => \%column_defs,
 
1303                                        'data'           => $form->{parts});
 
1305   CVar->add_custom_variables_to_report('module'         => 'IC',
 
1306                                        'sub_module'     => sub { $_[0]->{ioi} },
 
1307                                        'trans_id_field' => 'ioi_id',
 
1308                                        'configs'        => $cvar_configs,
 
1309                                        'column_defs'    => \%column_defs,
 
1310                                        'data'           => $form->{parts});
 
1312   my @subtotal_columns = qw(sellprice listprice lastcost);
 
1313   my %subtotals = map { $_ => 0 } ('onhand', @subtotal_columns);
 
1314   my %totals    = map { $_ => 0 } @subtotal_columns;
 
1316   my $same_item = @{ $form->{parts} } ? $form->{parts}[0]{ $form->{sort} } : undef;
 
1318   my $defaults  = AM->get_defaults();
 
1321   foreach my $ref (@{ $form->{parts} }) {
 
1323     # fresh row, for inserting later
 
1324     my $row = { map { $_ => { 'data' => $ref->{$_} } } @columns };
 
1326     $ref->{exchangerate} ||= 1;
 
1327     $ref->{price_factor} ||= 1;
 
1328     $ref->{sellprice}     *= $ref->{exchangerate} / $ref->{price_factor};
 
1329     $ref->{listprice}     *= $ref->{exchangerate} / $ref->{price_factor};
 
1330     $ref->{lastcost}      *= $ref->{exchangerate} / $ref->{price_factor};
 
1332     # use this for assemblies
 
1333     my $soldtotal = $bsooqr_mode ? $ref->{soldtotal} : $ref->{onhand};
 
1335     if ($ref->{assemblyitem}) {
 
1336       $row->{partnumber}{align}   = 'right';
 
1337       $row->{soldtotal}{data}     = 0;
 
1338       $soldtotal                  = 0 if ($form->{sold});
 
1341     my $edit_link               = build_std_url('action=edit', 'id=' . E($ref->{id}), 'callback');
 
1342     $row->{partnumber}->{link}  = $edit_link;
 
1343     $row->{description}->{link} = $edit_link;
 
1345     foreach (qw(sellprice listprice lastcost)) {
 
1346       $row->{$_}{data}            = $form->format_amount(\%myconfig, $ref->{$_}, 2);
 
1347       $row->{"linetotal$_"}{data} = $form->format_amount(\%myconfig, $ref->{onhand} * $ref->{$_}, 2);
 
1349     foreach ( @pricegroup_columns ) {
 
1350       $row->{$_}{data}            = $form->format_amount(\%myconfig, $ref->{"$_"}, 2);
 
1354     map { $row->{$_}{data} = $form->format_amount(\%myconfig, $ref->{$_}); } qw(onhand rop weight soldtotal);
 
1356     $row->{weight}->{data} .= ' ' . $defaults->{weightunit};
 
1358     # 'yes' and 'no' for boolean value shop
 
1359     if ($form->{l_shop}) {
 
1360       $row->{shop}{data} = $row->{shop}{data}? $::locale->text('yes') : $::locale->text('no');
 
1363     if (!$ref->{assemblyitem}) {
 
1364       foreach my $col (@subtotal_columns) {
 
1365         $totals{$col}    += $soldtotal * $ref->{$col};
 
1366         $subtotals{$col} += $soldtotal * $ref->{$col};
 
1369       $subtotals{soldtotal} += $soldtotal;
 
1373     if ($ref->{module} eq 'oe') {
 
1374       # für oe gibt es vier fälle, jeweils nach kunde oder lieferant unterschiedlich:
 
1376       # | ist bestellt  | Von Kunden bestellt |  -> edit_oe_ord_link
 
1377       # | Anfrage       | Angebot             |  -> edit_oe_quo_link
 
1379       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');
 
1380       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');
 
1382       $row->{ordnumber}{link} = $edit_oe_ord_link;
 
1383       $row->{quonumber}{link} = $edit_oe_quo_link if (!$ref->{ordnumber});
 
1386       $row->{invnumber}{link} = build_std_url("script=$ref->{module}.pl", 'action=edit', 'type=invoice', 'id=' . E($ref->{trans_id}), 'callback');
 
1389     # set properties of images
 
1390     if ($ref->{image} && (lc $report->{options}->{output_format} eq 'html')) {
 
1391       $row->{image}{data}     = '';
 
1392       $row->{image}{raw_data} = '<a href="' . H($ref->{image}) . '"><img src="' . H($ref->{image}) . '" height="32" border="0"></a>';
 
1394     map { $row->{$_}{link} = $ref->{$_} } qw(drawing microfiche);
 
1396     $row->{notes}{data} = SL::HTML::Util->strip($ref->{notes});
 
1398     $report->add_data($row);
 
1400     my $next_ref = $form->{parts}[$idx + 1];
 
1402     # insert subtotal rows
 
1403     if (($form->{l_subtotal} eq 'Y') &&
 
1405          (!$next_ref->{assemblyitem} && ($same_item ne $next_ref->{ $form->{sort} })))) {
 
1406       my $row = { map { $_ => { 'class' => 'listsubtotal', } } @columns };
 
1408       if (($form->{searchitems} ne 'assembly') || !$form->{bom}) {
 
1409         $row->{soldtotal}->{data} = $form->format_amount(\%myconfig, $subtotals{soldtotal});
 
1412       map { $row->{"linetotal$_"}->{data} = $form->format_amount(\%myconfig, $subtotals{$_}, 2) } @subtotal_columns;
 
1413       map { $subtotals{$_} = 0 } ('soldtotal', @subtotal_columns);
 
1415       $report->add_data($row);
 
1417       $same_item = $next_ref->{ $form->{sort} };
 
1423   if ($form->{"l_linetotal"} && !$form->{report_generator_csv_options_for_import}) {
 
1424     my $row = { map { $_ => { 'class' => 'listtotal', } } @columns };
 
1426     map { $row->{"linetotal$_"}->{data} = $form->format_amount(\%myconfig, $totals{$_}, 2) } @subtotal_columns;
 
1428     $report->add_separator();
 
1429     $report->add_data($row);
 
1432   $report->generate_with_headers();
 
1434   $lxdebug->leave_sub();
 
1435 }    #end generate_report
 
1437 sub parts_subtotal {
 
1438   $lxdebug->enter_sub();
 
1440   $auth->assert('part_service_assembly_edit');
 
1443   my ($column_index, $subtotalonhand, $subtotalsellprice, $subtotallastcost, $subtotallistprice) = @_;
 
1445   map { $column_data{$_} = "<td> </td>" } @{ $column_index };
 
1446   $$subtotalonhand = 0 if ($form->{searchitems} eq 'assembly' && $form->{bom});
 
1448   $column_data{onhand} =
 
1449       "<th class=listsubtotal align=right>"
 
1450     . $form->format_amount(\%myconfig, $$subtotalonhand)
 
1453   $column_data{linetotalsellprice} =
 
1454       "<th class=listsubtotal align=right>"
 
1455     . $form->format_amount(\%myconfig, $$subtotalsellprice, 2)
 
1457   $column_data{linetotallistprice} =
 
1458       "<th class=listsubtotal align=right>"
 
1459     . $form->format_amount(\%myconfig, $$subtotallistprice, 2)
 
1461   $column_data{linetotallastcost} =
 
1462       "<th class=listsubtotal align=right>"
 
1463     . $form->format_amount(\%myconfig, $$subtotallastcost, 2)
 
1466   $$subtotalonhand    = 0;
 
1467   $$subtotalsellprice = 0;
 
1468   $$subtotallistprice = 0;
 
1469   $$subtotallastcost  = 0;
 
1471   print "<tr class=listsubtotal>";
 
1473   map { print "\n$column_data{$_}" } @{ $column_index };
 
1479   $lxdebug->leave_sub();
 
1483   $lxdebug->enter_sub();
 
1485   $auth->assert('part_service_assembly_details');
 
1487   # show history button
 
1488   $form->{javascript} = qq|<script type="text/javascript" src="js/show_history.js"></script>|;
 
1489   #/show hhistory button
 
1490   IC->get_part(\%myconfig, \%$form);
 
1492   $form->{"original_partnumber"} = $form->{"partnumber"};
 
1494   my $title      = 'Edit ' . ucfirst $form->{item};
 
1495   $form->{title} = $locale->text($title);
 
1500   $lxdebug->leave_sub();
 
1504   $lxdebug->enter_sub();
 
1506   $auth->assert('part_service_assembly_details');
 
1508   IC->create_links("IC", \%myconfig, \%$form);
 
1511   map({ $form->{selectcurrency} .= "<option>$_\n" } $::form->get_all_currencies());
 
1513   # parts and assemblies have the same links
 
1514   my $item = $form->{item};
 
1515   if ($form->{item} eq 'assembly') {
 
1519   # build the popup menus
 
1520   $form->{taxaccounts} = "";
 
1521   foreach my $key (keys %{ $form->{IC_links} }) {
 
1522     foreach my $ref (@{ $form->{IC_links}{$key} }) {
 
1524       # if this is a tax field
 
1525       if ($key =~ /IC_tax/) {
 
1526         if ($key =~ /\Q$item\E/) {
 
1527           $form->{taxaccounts} .= "$ref->{accno} ";
 
1528           $form->{"IC_tax_$ref->{accno}_description"} =
 
1529             "$ref->{accno}--$ref->{description}";
 
1532             if ($form->{amount}{ $ref->{accno} }) {
 
1533               $form->{"IC_tax_$ref->{accno}"} = "checked";
 
1536             $form->{"IC_tax_$ref->{accno}"} = "checked";
 
1541         $form->{"select$key"} .=
 
1542           "<option $ref->{selected}>$ref->{accno}--$ref->{description}\n";
 
1543         if ($form->{amount}{$key} eq $ref->{accno}) {
 
1544           $form->{$key} = "$ref->{accno}--$ref->{description}";
 
1550   chop $form->{taxaccounts};
 
1552   if (($form->{item} eq "part") || ($form->{item} eq "assembly")) {
 
1553     $form->{selectIC_income}  = $form->{selectIC_sale};
 
1554     $form->{selectIC_expense} = $form->{selectIC_cogs};
 
1555     $form->{IC_income}        = $form->{IC_sale};
 
1556     $form->{IC_expense}       = $form->{IC_cogs};
 
1559   delete $form->{IC_links};
 
1560   delete $form->{amount};
 
1562   $form->get_partsgroup(\%myconfig, { all => 1 });
 
1564   $form->{partsgroup} = "$form->{partsgroup}--$form->{partsgroup_id}";
 
1566   if (@{ $form->{all_partsgroup} }) {
 
1567     $form->{selectpartsgroup} = qq|<option>\n|;
 
1568     map { $form->{selectpartsgroup} .= qq|<option value="$_->{partsgroup}--$_->{id}">$_->{partsgroup}\n| } @{ $form->{all_partsgroup} };
 
1571   if ($form->{item} eq 'assembly') {
 
1573     foreach my $i (1 .. $form->{assembly_rows}) {
 
1574       if ($form->{"partsgroup_id_$i"}) {
 
1575         $form->{"partsgroup_$i"} =
 
1576           qq|$form->{"partsgroup_$i"}--$form->{"partsgroup_id_$i"}|;
 
1579     $form->get_partsgroup(\%myconfig);
 
1581     if (@{ $form->{all_partsgroup} }) {
 
1582       $form->{selectassemblypartsgroup} = qq|<option>\n|;
 
1585         $form->{selectassemblypartsgroup} .=
 
1586           qq|<option value="$_->{partsgroup}--$_->{id}">$_->{partsgroup}\n|
 
1587       } @{ $form->{all_partsgroup} };
 
1590   $lxdebug->leave_sub();
 
1594   $lxdebug->enter_sub();
 
1596   $auth->assert('part_service_assembly_details');
 
1598   $form->{pg_keys}          = sub { "$_[0]->{partsgroup}--$_[0]->{id}" };
 
1599   $form->{description_area} = ($form->{rows} = $form->numtextrows($form->{description}, 40)) > 1;
 
1600   $form->{notes_rows}       =  max 4, $form->numtextrows($form->{notes}, 40), $form->numtextrows($form->{formel}, 40);
 
1602   map { $form->{"is_$_"}  = ($form->{item} eq $_) } qw(part service assembly);
 
1603   map { $form->{$_}       =~ s/"/"/g;        } qw(unit);
 
1605   $form->get_lists('price_factors' => 'ALL_PRICE_FACTORS',
 
1606                    'partsgroup'    => 'all_partsgroup',
 
1607                    'vendors'       => 'ALL_VENDORS',
 
1608                    'warehouses'    => { 'key'    => 'WAREHOUSES',
 
1609                                         'bins'   => 'BINS', });
 
1610   # leerer wert für Lager und Lagerplatz korrekt einstellt
 
1611   # ID 0 sollte in Ordnung sein, da der Zähler sowieso höher ist
 
1612   my $no_default_bin_entry = { 'id' => '0', description => '--', 'BINS' => [ { id => '0', description => ''} ] };
 
1613   push @ { $form->{WAREHOUSES} }, $no_default_bin_entry;
 
1614   if (my $max = scalar @{ $form->{WAREHOUSES} }) {
 
1615     my ($default_warehouse_id, $default_bin_id);
 
1616     if ($form->{action} eq 'add') { # default only for new entries
 
1617       $default_warehouse_id = $::instance_conf->get_warehouse_id;
 
1618       $default_bin_id       = $::instance_conf->get_bin_id;
 
1620     $form->{warehouse_id} ||= $default_warehouse_id || $form->{WAREHOUSES}->[$max -1]->{id};
 
1621     $form->{bin_id}       ||= $default_bin_id       ||  $form->{WAREHOUSES}->[$max -1]->{BINS}->[0]->{id};
 
1624   $form->{LANGUAGES}        = SL::DB::Manager::Language->get_all_sorted;
 
1625   $form->{translations_map} = { map { ($_->{language_id} => $_) } @{ $form->{translations} || [] } };
 
1627   IC->retrieve_buchungsgruppen(\%myconfig, $form);
 
1628   @{ $form->{BUCHUNGSGRUPPEN} } = grep { $_->{id} eq $form->{buchungsgruppen_id} || ($form->{id} && $form->{orphaned}) || !$form->{id} } @{ $form->{BUCHUNGSGRUPPEN} };
 
1630   if (($form->{partnumber} ne '') && !SL::TransNumber->new(number => $form->{partnumber}, type => $form->{item}, id => $form->{id})->is_unique) {
 
1631     flash('info', $::locale->text('This partnumber is not unique. You should change it.'));
 
1634   my $units = AM->retrieve_units(\%myconfig, $form);
 
1635   $form->{ALL_UNITS} = [ map +{ name => $_ }, sort { $units->{$a}{sortkey} <=> $units->{$b}{sortkey} } keys %$units ];
 
1637   $form->{defaults} = AM->get_defaults();
 
1639   $form->{CUSTOM_VARIABLES} = CVar->get_custom_variables('module' => 'IC', 'trans_id' => $form->{id});
 
1641   my ($null, $partsgroup_id) = split /--/, $form->{partsgroup};
 
1643   CVar->render_inputs('variables' => $form->{CUSTOM_VARIABLES}, show_disabled_message => 1, partsgroup_id => $partsgroup_id)
 
1644     if (scalar @{ $form->{CUSTOM_VARIABLES} });
 
1646   $::request->layout->use_javascript("${_}.js") for qw(ckeditor/ckeditor ckeditor/adapters/jquery kivi.PriceRule);
 
1647   $::request->layout->add_javascripts_inline("\$(function(){kivi.PriceRule.load_price_rules_for_part(@{[ $::form->{id} * 1 ]})})") if $::form->{id};
 
1649   #print $form->parse_html_template('ic/form_header', { ALL_PRICE_FACTORS => $form->{ALL_PRICE_FACTORS},
 
1650   #                                                     ALL_UNITS         => $form->{ALL_UNITS},
 
1651   #                                                     BUCHUNGSGRUPPEN   => $form->{BUCHUNGSGRUPPEN},
 
1652   #                                                     payment_terms     => $form->{payment_terms},
 
1653   #                                                     all_partsgroup    => $form->{all_partsgroup}});
 
1655   $form->{show_edit_buttons} = $main::auth->check_right($::myconfig{login}, 'part_service_assembly_edit');
 
1657   print $form->parse_html_template('ic/form_header');
 
1658   $lxdebug->leave_sub();
 
1662   $lxdebug->enter_sub();
 
1664   $auth->assert('part_service_assembly_details');
 
1666   print $form->parse_html_template('ic/form_footer');
 
1668   $lxdebug->leave_sub();
 
1672   $lxdebug->enter_sub();
 
1675   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;
 
1676   delete @{$form}{grep { m/^make_\d+/ || m/^model_\d+/ } keys %{ $form }};
 
1677   print $form->parse_html_template('ic/makemodel', { MM_DATA => [ @mm_data, {} ], mm_rows => scalar @mm_data + 1 });
 
1679   $lxdebug->leave_sub();
 
1683   $lxdebug->enter_sub();
 
1686   my ($nochange, $callback, $previousform, $linetotal, $line_purchase_price, $href);
 
1688   @column_index = qw(runningnumber qty unit bom partnumber description partsgroup lastcost total);
 
1690   if ($form->{previousform}) {
 
1692     @column_index = qw(qty unit bom partnumber description partsgroup total);
 
1696     $form->{old_callback} = $form->{callback};
 
1697     $callback             = $form->{callback};
 
1698     $form->{callback}     = "$form->{script}?action=display_form";
 
1701     map { delete $form->{$_} } qw(action header);
 
1703     # save form variables in a previousform variable
 
1704     my %form_to_save = map   { ($_ => m/^ (?: listprice | sellprice | lastcost ) $/x ? $form->format_amount(\%myconfig, $form->{$_}) : $form->{$_}) }
 
1706     $previousform    = $::auth->save_form_in_session(form => \%form_to_save);
 
1708     $form->{callback} = $callback;
 
1709     $form->{assemblytotal} = 0;
 
1710     $form->{assembly_purchase_price_total} = 0;
 
1711     $form->{weight}        = 0;
 
1715    runningnumber => { text =>  $locale->text('No.'),              nowrap => 1, width => '5%',  align => 'left',},
 
1716    qty           => { text =>  $locale->text('Qty'),              nowrap => 1, width => '10%', align => 'left',},
 
1717    unit          => { text =>  $locale->text('Unit'),             nowrap => 1, width => '5%',  align => 'left',},
 
1718    partnumber    => { text =>  $locale->text('Part Number'),      nowrap => 1, width => '20%', align => 'left',},
 
1719    description   => { text =>  $locale->text('Part Description'), nowrap => 1, width => '50%', align => 'left',},
 
1720    lastcost      => { text =>  $locale->text('Purchase Prices'),  nowrap => 1, width => '50%', align => 'right',},
 
1721    total         => { text =>  $locale->text('Sale Prices'),      nowrap => 1,                 align => 'right',},
 
1722    bom           => { text =>  $locale->text('BOM'),                                           align => 'center',},
 
1723    partsgroup    => { text =>  $locale->text('Group'),                                         align => 'left',},
 
1728   for my $i (1 .. $numrows) {
 
1729     my (%row, @row_hiddens);
 
1731     $form->{"partnumber_$i"} =~ s/\"/"/g;
 
1733     $linetotal           = $form->round_amount($form->{"sellprice_$i"} * $form->{"qty_$i"} / ($form->{"price_factor_$i"} || 1), 4);
 
1734     $line_purchase_price = $form->round_amount($form->{"lastcost_$i"} *  $form->{"qty_$i"} / ($form->{"price_factor_$i"} || 1), 4);
 
1735     $form->{assemblytotal}                  += $linetotal;
 
1736     $form->{assembly_purchase_price_total}  += $line_purchase_price;
 
1737     $form->{"qty_$i"}    = $form->format_amount(\%myconfig, $form->{"qty_$i"});
 
1738     $linetotal           = $form->format_amount(\%myconfig, $linetotal, 2);
 
1739     $line_purchase_price = $form->format_amount(\%myconfig, $line_purchase_price, 2);
 
1740     $href                = build_std_url("action=edit", qq|id=$form->{"id_$i"}|, "rowcount=$numrows", "currow=$i", "previousform=$previousform");
 
1741     map { $row{$_}{data} = "" } qw(qty unit partnumber description bom partsgroup runningnumber);
 
1744     if (($i >= 1) && ($i == $numrows)) {
 
1745       if (!$form->{previousform}) {
 
1746         $row{partnumber}{data}  = qq|<input name="partnumber_$i" size=15 value="$form->{"partnumber_$i"}">|;
 
1747         $row{qty}{data}         = qq|<input name="qty_$i" size=5 value="$form->{"qty_$i"}">|;
 
1748         $row{description}{data} = qq|<input name="description_$i" size=40 value="$form->{"description_$i"}">|;
 
1749         $row{partsgroup}{data}  = qq|<input name="partsgroup_$i" size=10 value="$form->{"partsgroup_$i"}">|;
 
1753       if ($form->{previousform}) {
 
1754         push @row_hiddens,          qw(qty bom);
 
1755         $row{partnumber}{data}    = $form->{"partnumber_$i"};
 
1756         $row{qty}{data}           = $form->{"qty_$i"};
 
1757         $row{bom}{data}           = $form->{"bom_$i"} ? "x" : " ";
 
1758         $row{qty}{align}          = 'right';
 
1760         $row{partnumber}{data}    = qq|$form->{"partnumber_$i"}|;
 
1761         $row{partnumber}{link}     = $href;
 
1762         $row{qty}{data}           = qq|<input name="qty_$i" size=5 value="$form->{"qty_$i"}">|;
 
1763         $row{runningnumber}{data} = qq|<input name="runningnumber_$i" size=3 value="$i">|;
 
1764         $row{bom}{data}   = sprintf qq|<input name="bom_$i" type=checkbox class=checkbox value=1 %s>|,
 
1765                                        $form->{"bom_$i"} ? 'checked' : '';
 
1767       push @row_hiddens,        qw(unit description partnumber partsgroup);
 
1768       $row{unit}{data}        = $form->{"unit_$i"};
 
1769       #Bei der Artikelbeschreibung und Warengruppe können Sonderzeichen verwendet
 
1770       #werden, die den HTML Code stören. Daher sollen diese im Template escaped werden
 
1771       #dies geschieht, wenn die Variable escape gesetzt ist
 
1772       $row{description}{data}   = $form->{"description_$i"};
 
1773       $row{description}{escape} = 1;
 
1774       $row{partsgroup}{data}    = $form->{"partsgroup_$i"};
 
1775       $row{partsgroup}{escape}  = 1;
 
1776       $row{bom}{align}          = 'center';
 
1779     $row{lastcost}{data}      = $line_purchase_price;
 
1780     $row{total}{data}         = $linetotal;
 
1781     $row{lastcost}{align}     = 'right';
 
1782     $row{total}{align}        = 'right';
 
1783     $row{deliverydate}{align} = 'right';
 
1785     push @row_hiddens, qw(id sellprice lastcost weight price_factor_id price_factor);
 
1786     $row{hiddens} = [ map +{ name => "${_}_$i", value => $form->{"${_}_$i"} }, @row_hiddens ];
 
1791   print $form->parse_html_template('ic/assembly_row', { COLUMNS => \@column_index, ROWS => \@ROWS, HEADER => \%header });
 
1793   $lxdebug->leave_sub();
 
1797   $lxdebug->enter_sub();
 
1799   $auth->assert('part_service_assembly_edit');
 
1801   # update checks whether pricegroups, makemodels or assembly items have been changed/added
 
1802   # new items might have been added (and the original form might have been stored and restored)
 
1803   # so at the end the ic form is run through check_form in io.pl
 
1804   # The various combination of events can lead to problems with the order of parse_amount and format_amount
 
1805   # Currently check_form parses some variables in assembly mode, but not in article or service mode
 
1806   # This will only ever really be sanely resolved with a rewrite...
 
1808   # parse pricegroups. and no, don't rely on check_form for this...
 
1809   map { $form->{"price_$_"} = $form->parse_amount(\%myconfig, $form->{"price_$_"}) } 1 .. $form->{price_rows};
 
1811   unless ($form->{item} eq 'assembly') {
 
1812     # for assemblies check_form will parse sellprice and listprice, but not for parts or services
 
1813     $form->{$_} = $form->parse_amount(\%myconfig, $form->{$_}) for qw(sellprice listprice ve gv);
 
1816   if ($form->{item} eq 'part') {
 
1817     $form->{$_} = $form->parse_amount(\%myconfig, $form->{$_}) for qw(weight rop);
 
1820   # same for makemodel lastcosts
 
1821   # but parse_amount not necessary for assembly component lastcosts
 
1822   unless ($form->{item} eq "assembly") {
 
1823     map { $form->{"lastcost_$_"} = $form->parse_amount(\%myconfig, $form->{"lastcost_$_"}) } 1 .. $form->{"makemodel_rows"};
 
1824     $form->{lastcost} = $form->parse_amount(\%myconfig, $form->{lastcost});
 
1827   if ($form->{item} eq "assembly") {
 
1828     my $i = $form->{assembly_rows};
 
1830     # if last row is empty check the form otherwise retrieve item
 
1831     if (   ($form->{"partnumber_$i"} eq "")
 
1832         && ($form->{"description_$i"} eq "")
 
1833         && ($form->{"partsgroup_$i"}  eq "")) {
 
1834       # no new assembly item was added
 
1839       # search db for newly added assemblyitems, via partnumber or description
 
1840       IC->assembly_item(\%myconfig, \%$form);
 
1842       # form->{item_list} contains the possible matches, next check whether the
 
1843       # match is unique or we need to call the page to select the item
 
1844       my $rows = scalar @{ $form->{item_list} };
 
1847         $form->{"qty_$i"} = 1 unless ($form->{"qty_$i"});
 
1850           $form->{makemodel_rows}--;
 
1851           select_item(mode => 'IC', pre_entered_qty => $form->parse_amount(\%myconfig, $form->{"qty_$i"}));
 
1854           map { $form->{item_list}[$i]{$_} =~ s/\"/"/g }
 
1855             qw(partnumber description unit partsgroup);
 
1856           map { $form->{"${_}_$i"} = $form->{item_list}[0]{$_} }
 
1857             keys %{ $form->{item_list}[0] };
 
1858           $form->{"runningnumber_$i"} = $form->{assembly_rows};
 
1859           $form->{assembly_rows}++;
 
1867         $form->{rowcount} = $i;
 
1868         $form->{assembly_rows}++;
 
1875   } elsif (($form->{item} eq 'part') || ($form->{item} eq 'service')) {
 
1879   $lxdebug->leave_sub();
 
1883   $lxdebug->enter_sub();
 
1885   $auth->assert('part_service_assembly_edit');
 
1887   my ($parts_id, %newform, $amount, $callback);
 
1889   # check if there is a part number - commented out, cause there is an automatic allocation of numbers
 
1890   # $form->isblank("partnumber", $locale->text(ucfirst $form->{item}." Part Number missing!"));
 
1892   # check if there is a description
 
1893   $form->isblank("description", $locale->text("Part Description missing!"));
 
1895   $form->error($locale->text("Inventory quantity must be zero before you can set this $form->{item} obsolete!"))
 
1896     if $form->{obsolete} && $form->{onhand} * 1 && $form->{item} ne 'service';
 
1898   if (!$form->{buchungsgruppen_id}) {
 
1899     $form->error($locale->text("Parts must have an entry type.") . " " .
 
1900      $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.")
 
1904   $form->error($locale->text('Description must not be empty!')) unless $form->{description};
 
1905   $form->error($locale->text('Partnumber must not be set to empty!')) if $form->{id} && !$form->{partnumber};
 
1907   # undef warehouse_id if the empty value is selected
 
1908   if ( ($form->{warehouse_id} == 0) && ($form->{bin_id} == 0) ) {
 
1909     undef $form->{warehouse_id};
 
1910     undef $form->{bin_id};
 
1913   if (IC->save(\%myconfig, \%$form) == 3) {
 
1914     $form->error($locale->text('Partnumber not unique!'));
 
1916   # saving the history
 
1917   if(!exists $form->{addition}) {
 
1918     $form->{snumbers} = qq|partnumber_| . $form->{partnumber};
 
1919     $form->{addition} = "SAVED";
 
1920     $form->save_history;
 
1922   # /saving the history
 
1923   $parts_id = $form->{id};
 
1926   # load previous variables
 
1927   if ($form->{previousform}) {
 
1929     # save the new form variables before splitting previousform
 
1930     map { $newform{$_} = $form->{$_} } keys %$form;
 
1932     # don't trample on previous variables
 
1933     map { delete $form->{$_} } keys %newform;
 
1935     my $ic_cvar_configs = CVar->get_configs(module => 'IC');
 
1936     my @ic_cvar_fields  = map { "cvar_$_->{name}" } @{ $ic_cvar_configs };
 
1938     # restore original values
 
1939     $::auth->restore_form_from_session($newform{previousform}, form => $form);
 
1940     $form->{taxaccounts} = $newform{taxaccount2};
 
1942     if ($form->{item} eq 'assembly') {
 
1944       # undo number formatting
 
1945       map { $form->{$_} = $form->parse_amount(\%myconfig, $form->{$_}) }
 
1946         qw(weight listprice sellprice rop);
 
1948       $form->{assembly_rows}--;
 
1949       if ($newform{currow}) {
 
1950         $i = $newform{currow};
 
1952         $i = $form->{assembly_rows};
 
1954       $form->{"qty_$i"} = 1 unless ($form->{"qty_$i"} > 0);
 
1956       $form->{sellprice} -= $form->{"sellprice_$i"} * $form->{"qty_$i"};
 
1957       $form->{weight}    -= $form->{"weight_$i"} * $form->{"qty_$i"};
 
1959       # change/add values for assembly item
 
1960       map { $form->{"${_}_$i"} = $newform{$_} } qw(partnumber description bin unit weight listprice sellprice inventory_accno income_accno expense_accno price_factor_id);
 
1961       map { $form->{"ic_${_}_$i"} = $newform{$_} } @ic_cvar_fields;
 
1963       # das ist __voll__ bekloppt, dass so auszurechnen jb 22.5.09
 
1964       #$form->{sellprice} += $form->{"sellprice_$i"} * $form->{"qty_$i"};
 
1965       $form->{weight}    += $form->{"weight_$i"} * $form->{"qty_$i"};
 
1969       # set values for last invoice/order item
 
1970       $i = $form->{rowcount};
 
1971       $form->{"qty_$i"} = 1 unless ($form->{"qty_$i"} > 0);
 
1973       map { $form->{"${_}_$i"} = $newform{$_} } qw(partnumber description bin unit listprice inventory_accno income_accno expense_accno sellprice lastcost price_factor_id);
 
1974       map { $form->{"ic_${_}_$i"} = $newform{$_} } @ic_cvar_fields;
 
1976       $form->{"longdescription_$i"} = $newform{notes};
 
1978       $form->{"sellprice_$i"} = $newform{lastcost} if ($form->{vendor_id});
 
1980       if ($form->{exchangerate} != 0) {
 
1981         $form->{"sellprice_$i"} /= $form->{exchangerate};
 
1984       map { $form->{"taxaccounts_$i"} .= "$_ " } split / /, $newform{taxaccount};
 
1985       chop $form->{"taxaccounts_$i"};
 
1986       foreach my $item (qw(description rate taxnumber)) {
 
1987         my $index = $form->{"taxaccounts_$i"} . "_$item";
 
1988         $form->{$index} = $newform{$index};
 
1991       # credit remaining calculation
 
1992       $amount = $form->{"sellprice_$i"} * (1 - $form->{"discount_$i"} / 100) * $form->{"qty_$i"};
 
1994       map { $form->{"${_}_base"} += $amount } (split / /, $form->{"taxaccounts_$i"});
 
1995       map { $amount += ($form->{"${_}_base"} * $form->{"${_}_rate"}) } split / /, $form->{"taxaccounts_$i"} if !$form->{taxincluded};
 
1997       $form->{creditremaining} -= $amount;
 
1999       # redo number formatting, because invoice parse them!
 
2000       map { $form->{"${_}_$i"} = $form->format_amount(\%myconfig, $form->{"${_}_$i"}) } qw(weight listprice sellprice lastcost rop);
 
2003     $form->{"id_$i"} = $parts_id;
 
2005     # Get the actual price factor (not just the ID) for the marge calculation.
 
2006     $form->get_lists('price_factors' => 'ALL_PRICE_FACTORS');
 
2007     foreach my $pfac (@{ $form->{ALL_PRICE_FACTORS} }) {
 
2008       next if ($pfac->{id} != $newform{price_factor_id});
 
2009       $form->{"marge_price_factor_$i"} = $pfac->{factor};
 
2012     delete $form->{ALL_PRICE_FACTORS};
 
2014     delete $form->{action};
 
2016     # restore original callback
 
2017     $callback = $form->unescape($form->{callback});
 
2018     $form->{callback} = $form->unescape($form->{old_callback});
 
2019     delete $form->{old_callback};
 
2021     $form->{makemodel_rows}--;
 
2023     # put callback together
 
2024     foreach my $key (keys %$form) {
 
2026       # do single escape for Apache 2.0
 
2027       my $value = $form->escape($form->{$key}, 1);
 
2028       $callback .= qq|&$key=$value|;
 
2030     $form->{callback} = $callback;
 
2036   $lxdebug->leave_sub();
 
2040   $lxdebug->enter_sub();
 
2042   $auth->assert('part_service_assembly_edit');
 
2044   # saving the history
 
2045   if(!exists $form->{addition}) {
 
2046     $form->{snumbers} = qq|partnumber_| . $form->{partnumber};
 
2047     $form->{addition} = "SAVED AS NEW";
 
2048     $form->save_history;
 
2050   # /saving the history
 
2052   if ($form->{"original_partnumber"} &&
 
2053       ($form->{"partnumber"} eq $form->{"original_partnumber"})) {
 
2054     $form->{partnumber} = "";
 
2057   $lxdebug->leave_sub();
 
2061   $lxdebug->enter_sub();
 
2063   $auth->assert('part_service_assembly_edit');
 
2065   # saving the history
 
2066   if(!exists $form->{addition}) {
 
2067     $form->{snumbers} = qq|partnumber_| . $form->{partnumber};
 
2068     $form->{addition} = "DELETED";
 
2069     $form->save_history;
 
2071   # /saving the history
 
2072   my $rc = IC->delete(\%myconfig, \%$form);
 
2075   $form->redirect($locale->text('Item deleted!')) if ($rc > 0);
 
2076   $form->error($locale->text('Cannot delete item!'));
 
2078   $lxdebug->leave_sub();
 
2082   $lxdebug->enter_sub();
 
2084   $auth->assert('part_service_assembly_details');
 
2089     pricegroup    => $form->{"pricegroup_$_"},
 
2090     pricegroup_id => $form->{"pricegroup_id_$_"},
 
2091     price         => $form->{"price_$_"},
 
2094   print $form->parse_html_template('ic/price_row', { PRICES => \@PRICES });
 
2096   $lxdebug->leave_sub();
 
2099 sub ajax_autocomplete {
 
2100   $main::lxdebug->enter_sub();
 
2102   my $form     = $main::form;
 
2103   my %myconfig = %main::myconfig;
 
2105   $form->{column}          = 'description'     unless $form->{column} =~ /^partnumber|description$/;
 
2106   $form->{$form->{column}} = $form->{q}           || '';
 
2107   $form->{limit}           = ($form->{limit} * 1) || 10;
 
2108   $form->{searchitems}   ||= '';
 
2110   my @results = IC->all_parts(\%myconfig, $form);
 
2112   print $form->ajax_response_header(),
 
2113         $form->parse_html_template('ic/ajax_autocomplete');
 
2115   $main::lxdebug->leave_sub();
 
2118 sub back_to_record {
 
2122   delete @{$::form}{qw(action action_add action_back_to_record back_sub description item notes partnumber sellprice taxaccount2 unit vc)};
 
2124   $::auth->restore_form_from_session($::form->{previousform}, clobber => 1);
 
2125   $::form->{rowcount}--;
 
2126   $::form->{action}   = 'display_form';
 
2127   $::form->{callback} = $::form->{script} . '?' . join('&', map { $::form->escape($_) . '=' . $::form->escape($::form->{$_}) } sort keys %{ $::form });
 
2131 sub continue { call_sub($form->{"nextsub"}); }
 
2134   my $action = first { $::form->{"action_${_}"} } qw(add back_to_record);
 
2135   $::form->error($::locale->text('No action defined.')) unless $action;
 
2137   $::form->{dispatched_action} = $action;