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 qw(flash);
 
  43 use SL::ReportGenerator;
 
  51 our ($form, $locale, %myconfig, $lxdebug, $auth);
 
  53 require "bin/mozilla/io.pl";
 
  54 require "bin/mozilla/common.pl";
 
  55 require "bin/mozilla/reportgenerator.pl";
 
  60 # type=submit $locale->text('Add Part')
 
  61 # type=submit $locale->text('Add Service')
 
  62 # type=submit $locale->text('Add Assembly')
 
  63 # type=submit $locale->text('Edit Part')
 
  64 # type=submit $locale->text('Edit Service')
 
  65 # type=submit $locale->text('Edit Assembly')
 
  66 # $locale->text('Parts')
 
  67 # $locale->text('Services')
 
  68 # $locale->text('Inventory quantity must be zero before you can set this part obsolete!')
 
  69 # $locale->text('Inventory quantity must be zero before you can set this assembly obsolete!')
 
  70 # $locale->text('Part Number missing!')
 
  71 # $locale->text('Service Number missing!')
 
  72 # $locale->text('Assembly Number missing!')
 
  73 # $locale->text('ea');
 
  78   $lxdebug->enter_sub();
 
  80   $auth->assert('part_service_assembly_edit');
 
  82   my $title                = 'Add ' . ucfirst $form->{item};
 
  83   $form->{title}           = $locale->text($title);
 
  84   $form->{callback}        = "$form->{script}?action=add&item=$form->{item}" unless $form->{callback};
 
  85   $form->{unit_changeable} = 1;
 
  87   IC->get_pricegroups(\%myconfig, \%$form);
 
  91   $lxdebug->leave_sub();
 
  95   $lxdebug->enter_sub();
 
  97   $auth->assert('part_service_assembly_details');
 
  99   $form->{revers}       = 0;  # switch for backward sorting
 
 100   $form->{lastsort}     = ""; # memory for which table was sort at last time
 
 101   $form->{ndxs_counter} = 0;  # counter for added entries to top100
 
 103   my %is_xyz     = map { +"is_$_" => ($form->{searchitems} eq $_) } qw(part service assembly);
 
 105   $form->{title} = (ucfirst $form->{searchitems}) . "s";
 
 106   $form->{title} = $locale->text($form->{title});
 
 107   $form->{title} = $locale->text('Assemblies') if ($is_xyz{is_assembly});
 
 109   $form->{CUSTOM_VARIABLES}                  = CVar->get_configs('module' => 'IC');
 
 110   ($form->{CUSTOM_VARIABLES_FILTER_CODE},
 
 111    $form->{CUSTOM_VARIABLES_INCLUSION_CODE}) = CVar->render_search_options('variables'      => $form->{CUSTOM_VARIABLES},
 
 112                                                                            'include_prefix' => 'l_',
 
 113                                                                            'include_value'  => 'Y');
 
 117   $form->get_lists('partsgroup'    => 'ALL_PARTSGROUPS');
 
 118   print $form->parse_html_template('ic/search', { %is_xyz, });
 
 120   $lxdebug->leave_sub();
 
 123 sub search_update_prices {
 
 124   $lxdebug->enter_sub();
 
 126   $auth->assert('part_service_assembly_edit');
 
 128   my $pricegroups = IC->get_pricegroups(\%myconfig, \%$form);
 
 130   $form->{title} = $locale->text('Update Prices');
 
 134   print $form->parse_html_template('ic/search_update_prices', { PRICE_ROWS => $pricegroups });
 
 136   $lxdebug->leave_sub();
 
 139 sub confirm_price_update {
 
 140   $lxdebug->enter_sub();
 
 142   $auth->assert('part_service_assembly_edit');
 
 145   my $value_found = undef;
 
 147   foreach my $idx (qw(sellprice listprice), (1..$form->{price_rows})) {
 
 148     my $name      = $idx =~ m/\d/ ? $form->{"pricegroup_${idx}"}      : $idx eq 'sellprice' ? $locale->text('Sell Price') : $locale->text('List Price');
 
 149     my $type      = $idx =~ m/\d/ ? $form->{"pricegroup_type_${idx}"} : $form->{"${idx}_type"};
 
 150     my $value_idx = $idx =~ m/\d/ ? "price_${idx}" : $idx;
 
 151     my $value     = $form->parse_amount(\%myconfig, $form->{$value_idx});
 
 153     if ((0 > $value) && ($type eq 'percent')) {
 
 154       push @errors, $locale->text('You cannot adjust the price for pricegroup "#1" by a negative percentage.', $name);
 
 156     } elsif (!$value && ($form->{$value_idx} ne '')) {
 
 157       push @errors, $locale->text('No valid number entered for pricegroup "#1".', $name);
 
 159     } elsif (0 < $value) {
 
 164   push @errors, $locale->text('No prices will be updated because no prices have been entered.') if (!$value_found);
 
 166   my $num_matches = IC->get_num_matches_for_priceupdate();
 
 171     $form->show_generic_error(join('<br>', @errors), 'back_button' => 1);
 
 174   $form->{nextsub} = "update_prices";
 
 176   map { delete $form->{$_} } qw(action header);
 
 178   print $form->parse_html_template('ic/confirm_price_update', { HIDDENS     => [ map { name => $_, value => $form->{$_} }, keys %$form ],
 
 179                                                                 num_matches => $num_matches });
 
 181   $lxdebug->leave_sub();
 
 185   $lxdebug->enter_sub();
 
 187   $auth->assert('part_service_assembly_edit');
 
 189   my $num_updated = IC->update_prices(\%myconfig, \%$form);
 
 191   if (-1 != $num_updated) {
 
 192     $form->redirect($locale->text('#1 prices were updated.', $num_updated));
 
 194     $form->error($locale->text('Could not update prices!'));
 
 197   $lxdebug->leave_sub();
 
 201   $::lxdebug->enter_sub();
 
 203   $::auth->assert('part_service_assembly_edit');
 
 205   $::form->{l_soldtotal} = "Y";
 
 206   $::form->{sort}        = "soldtotal";
 
 207   $::form->{lastsort}    = "soldtotal";
 
 209   $::form->{l_qty}       = undef;
 
 210   $::form->{l_linetotal} = undef;
 
 211   $::form->{l_number}    = "Y";
 
 212   $::form->{number}      = "position";
 
 214   unless (   $::form->{bought}
 
 217           || $::form->{quoted}) {
 
 218     $::form->{bought} = $::form->{sold} = 1;
 
 223   $lxdebug->leave_sub();
 
 228 # Warning, deep magic ahead.
 
 229 # This function parses the requested details, sanity checks them, and converts them into a format thats usable for IC->all_parts
 
 231 # flags coming from the form:
 
 233 #  searchitems=part revers=0 lastsort=''
 
 236 # partnumber ean description partsgroup serialnumber make model drawing microfiche
 
 237 # transdatefrom transdateto
 
 240 #  itemstatus = active | onhand | short | obsolete | orphaned
 
 241 #  action     = continue | top100
 
 244 #  bought sold onorder ordered rfq quoted
 
 245 #  l_partnumber l_description l_serialnumber l_unit l_listprice l_sellprice l_lastcost
 
 246 #  l_linetotal l_priceupdate l_bin l_rop l_weight l_image l_drawing l_microfiche
 
 247 #  l_partsgroup l_subtotal l_soldtotal l_deliverydate l_pricegroups
 
 250 #  nextsub revers lastsort sort ndxs_counter
 
 252 sub generate_report {
 
 253   $lxdebug->enter_sub();
 
 255   $auth->assert('part_service_assembly_details');
 
 257   my ($revers, $lastsort, $description);
 
 259   my $cvar_configs = CVar->get_configs('module' => 'IC');
 
 261   $form->{title} = (ucfirst $form->{searchitems}) . "s";
 
 262   $form->{title} =~ s/ys$/ies/;
 
 263   $form->{title} = $locale->text($form->{title});
 
 266     'bin'                => { 'text' => $locale->text('Bin'), },
 
 267     'deliverydate'       => { 'text' => $locale->text('deliverydate'), },
 
 268     'description'        => { 'text' => $locale->text('Part Description'), },
 
 269     'notes'              => { 'text' => $locale->text('Notes'), },
 
 270     'drawing'            => { 'text' => $locale->text('Drawing'), },
 
 271     'ean'                => { 'text' => $locale->text('EAN'), },
 
 272     'image'              => { 'text' => $locale->text('Image'), },
 
 273     'insertdate'         => { 'text' => $locale->text('Insert Date'), },
 
 274     'invnumber'          => { 'text' => $locale->text('Invoice Number'), },
 
 275     'lastcost'           => { 'text' => $locale->text('Last Cost'), },
 
 276     'linetotallastcost'  => { 'text' => $locale->text('Extended'), },
 
 277     'linetotallistprice' => { 'text' => $locale->text('Extended'), },
 
 278     'linetotalsellprice' => { 'text' => $locale->text('Extended'), },
 
 279     'listprice'          => { 'text' => $locale->text('List Price'), },
 
 280     'microfiche'         => { 'text' => $locale->text('Microfiche'), },
 
 281     'name'               => { 'text' => $locale->text('Name'), },
 
 282     'onhand'             => { 'text' => $locale->text('Stocked Qty'), },
 
 283     'ordnumber'          => { 'text' => $locale->text('Order Number'), },
 
 284     'partnumber'         => { 'text' => $locale->text('Part Number'), },
 
 285     'partsgroup'         => { 'text' => $locale->text('Group'), },
 
 286     'priceupdate'        => { 'text' => $locale->text('Updated'), },
 
 287     'quonumber'          => { 'text' => $locale->text('Quotation'), },
 
 288     'rop'                => { 'text' => $locale->text('ROP'), },
 
 289     'sellprice'          => { 'text' => $locale->text('Sell Price'), },
 
 290     'serialnumber'       => { 'text' => $locale->text('Serial Number'), },
 
 291     'soldtotal'          => { 'text' => $locale->text('Qty in Selected Records'), },
 
 292     'name'               => { 'text' => $locale->text('Name in Selected Records'), },
 
 293     'transdate'          => { 'text' => $locale->text('Transdate'), },
 
 294     'unit'               => { 'text' => $locale->text('Unit'), },
 
 295     'weight'             => { 'text' => $locale->text('Weight'), },
 
 296     'shop'               => { 'text' => $locale->text('Shop article'), },
 
 297     'projectnumber'      => { 'text' => $locale->text('Project Number'), },
 
 298     'projectdescription' => { 'text' => $locale->text('Project Description'), },
 
 301   $revers     = $form->{revers};
 
 302   $lastsort   = $form->{lastsort};
 
 304   # sorting and direction of sorting
 
 305   # ToDO: change this to the simpler field+direction method
 
 306   if (($form->{lastsort} eq "") && ($form->{sort} eq undef)) {
 
 308     $form->{lastsort} = "partnumber";
 
 309     $form->{sort}     = "partnumber";
 
 311     if ($form->{lastsort} eq $form->{sort}) {
 
 312       $form->{revers} = 1 - $form->{revers};
 
 315       $form->{lastsort} = $form->{sort};
 
 319   # special case if we have a serialnumber limit search
 
 320   # serialnumbers are only given in invoices and orders,
 
 321   # so they can only pop up in bought, sold, rfq, and quoted stuff
 
 322   $form->{no_sn_joins} = 'Y' if (   !$form->{bought} && !$form->{sold}
 
 323                                  && !$form->{rfq}    && !$form->{quoted}
 
 324                                  && ($form->{l_serialnumber} || $form->{serialnumber}));
 
 326   # special case for any checkbox of bought | sold | onorder | ordered | rfq | quoted.
 
 327   # if any of these are ticked the behavior changes slightly for lastcost
 
 328   # since all those are aggregation checks for the legder tables this is an internal switch
 
 329   # refered to as ledgerchecks
 
 330   $form->{ledgerchecks} = 'Y' if (   $form->{bought} || $form->{sold} || $form->{onorder}
 
 331                                   || $form->{ordered} || $form->{rfq} || $form->{quoted});
 
 333   # if something should be activated if something else is active, enter it here
 
 335     onhand       => [ qw(l_onhand) ],
 
 336     short        => [ qw(l_onhand) ],
 
 337     onorder      => [ qw(l_ordnumber) ],
 
 338     ordered      => [ qw(l_ordnumber) ],
 
 339     rfq          => [ qw(l_quonumber) ],
 
 340     quoted       => [ qw(l_quonumber) ],
 
 341     bought       => [ qw(l_invnumber) ],
 
 342     sold         => [ qw(l_invnumber) ],
 
 343     ledgerchecks => [ qw(l_name) ],
 
 344     serialnumber => [ qw(l_serialnumber) ],
 
 345     no_sn_joins  => [ qw(bought sold) ],
 
 348   # get name of partsgroup if id is given
 
 350   if ($form->{partsgroup_id}) {
 
 351     my $pg = SL::DB::PartsGroup->new(id => $form->{partsgroup_id})->load;
 
 352     $pg_name = $pg->{'partsgroup'};
 
 355   # these strings get displayed at the top of the results to indicate the user which switches were used
 
 357     active        => $locale->text('Active'),
 
 358     obsolete      => $locale->text('Obsolete'),
 
 359     orphaned      => $locale->text('Orphaned'),
 
 360     onhand        => $locale->text('On Hand'),
 
 361     short         => $locale->text('Short'),
 
 362     onorder       => $locale->text('On Order'),
 
 363     ordered       => $locale->text('Ordered'),
 
 364     rfq           => $locale->text('RFQ'),
 
 365     quoted        => $locale->text('Quoted'),
 
 366     bought        => $locale->text('Bought'),
 
 367     sold          => $locale->text('Sold'),
 
 368     transdatefrom => $locale->text('From')       . " " . $locale->date(\%myconfig, $form->{transdatefrom}, 1),
 
 369     transdateto   => $locale->text('To (time)')  . " " . $locale->date(\%myconfig, $form->{transdateto}, 1),
 
 370     partnumber    => $locale->text('Part Number')      . ": '$form->{partnumber}'",
 
 371     partsgroup    => $locale->text('Group')            . ": '$form->{partsgroup}'",
 
 372     partsgroup_id => $locale->text('Group')            . ": '$pg_name'",
 
 373     serialnumber  => $locale->text('Serial Number')    . ": '$form->{serialnumber}'",
 
 374     description   => $locale->text('Part Description') . ": '$form->{description}'",
 
 375     make          => $locale->text('Make')             . ": '$form->{make}'",
 
 376     model         => $locale->text('Model')            . ": '$form->{model}'",
 
 377     drawing       => $locale->text('Drawing')          . ": '$form->{drawing}'",
 
 378     microfiche    => $locale->text('Microfiche')       . ": '$form->{microfiche}'",
 
 379     l_soldtotal   => $locale->text('Qty in Selected Records'),
 
 380     ean           => $locale->text('EAN')              . ": '$form->{ean}'",
 
 381     insertdatefrom => $locale->text('Insert Date') . ": " . $locale->text('From')       . " " . $locale->date(\%myconfig, $form->{insertdatefrom}, 1),
 
 382     insertdateto   => $locale->text('Insert Date') . ": " . $locale->text('To (time)')  . " " . $locale->date(\%myconfig, $form->{insertdateto}, 1),
 
 385   my @itemstatus_keys = qw(active obsolete orphaned onhand short);
 
 386   my @callback_keys   = qw(onorder ordered rfq quoted bought sold partnumber partsgroup partsgroup_id serialnumber description make model
 
 387                            drawing microfiche l_soldtotal l_deliverydate transdatefrom transdateto insertdatefrom insertdateto ean shop);
 
 389   # calculate dependencies
 
 390   for (@itemstatus_keys, @callback_keys) {
 
 391     next if ($form->{itemstatus} ne $_ && !$form->{$_});
 
 392     map { $form->{$_} = 'Y' } @{ $dependencies{$_} } if $dependencies{$_};
 
 395   # generate callback and optionstrings
 
 397   for my  $key (@itemstatus_keys, @callback_keys) {
 
 398     next if ($form->{itemstatus} ne $key && !$form->{$key});
 
 399     push @options, $optiontexts{$key};
 
 402   # special case for lastcost
 
 403   if ($form->{ledgerchecks}){
 
 404     # ledgerchecks don't know about sellprice or lastcost. they just return a
 
 405     # price. so rename sellprice to price, and drop lastcost.
 
 406     $column_defs{sellprice}{text} = $locale->text('Price');
 
 407     $form->{l_lastcost} = ""
 
 410   if ($form->{description}) {
 
 411     $description = $form->{description};
 
 412     $description =~ s/\n/<br>/g;
 
 415   if ($form->{l_linetotal}) {
 
 416     $form->{l_qty} = "Y";
 
 417     $form->{l_linetotalsellprice} = "Y" if $form->{l_sellprice};
 
 418     $form->{l_linetotallastcost}  = $form->{searchitems} eq 'assembly' && !$form->{bom} ? "" : 'Y' if  $form->{l_lastcost};
 
 419     $form->{l_linetotallistprice} = "Y" if $form->{l_listprice};
 
 422   if ($form->{searchitems} eq 'service') {
 
 424     # remove bin, weight and rop from list
 
 425     map { $form->{"l_$_"} = "" } qw(bin weight rop);
 
 427     $form->{l_onhand} = "";
 
 429     # qty is irrelevant unless bought or sold
 
 435         || $form->{quoted}) {
 
 436 #      $form->{l_onhand} = "Y";
 
 438       $form->{l_linetotalsellprice} = "";
 
 439       $form->{l_linetotallastcost}  = "";
 
 443   # soldtotal doesn't make sense with more than one bsooqr option.
 
 444   # so reset it to sold (the most common option), and issue a warning
 
 446   # also it doesn't make sense without bsooqr. disable and issue a warning too
 
 447   my @bsooqr = qw(sold bought onorder ordered rfq quoted);
 
 448   my $bsooqr_mode = grep { $form->{$_} } @bsooqr;
 
 449   if ($form->{l_subtotal} && 1 < $bsooqr_mode) {
 
 450     my $enabled       = first { $form->{$_} } @bsooqr;
 
 451     $form->{$_}       = ''   for @bsooqr;
 
 452     $form->{$enabled} = 'Y';
 
 454     push @options, $::locale->text('Subtotal cannot distinguish betweens record types. Only one of the selected record types will be displayed: #1', $optiontexts{$enabled});
 
 456   if ($form->{l_soldtotal} && !$bsooqr_mode) {
 
 457     delete $form->{l_soldtotal};
 
 459     flash('warning', $::locale->text('Soldtotal does not make sense without any bsooqr options'));
 
 461   if ($form->{l_name} && !$bsooqr_mode) {
 
 462     delete $form->{l_name};
 
 464     flash('warning', $::locale->text('Name does not make sense without any bsooqr options'));
 
 466   IC->all_parts(\%myconfig, \%$form);
 
 469     partnumber description notes partsgroup bin onhand rop soldtotal unit listprice
 
 470     linetotallistprice sellprice linetotalsellprice lastcost linetotallastcost
 
 471     priceupdate weight image drawing microfiche invnumber ordnumber quonumber
 
 472     transdate name serialnumber deliverydate ean projectnumber projectdescription
 
 476   my $pricegroups = SL::DB::Manager::Pricegroup->get_all(sort => 'id');
 
 477   my @pricegroup_columns;
 
 478   my %column_defs_pricegroups;
 
 479   if ($form->{l_pricegroups}) {
 
 480     @pricegroup_columns      = map { "pricegroup_" . $_->id } @{ $pricegroups };
 
 481     %column_defs_pricegroups = map {
 
 482       "pricegroup_" . $_->id => {
 
 483         text    => $::locale->text('Pricegroup') . ' ' . $_->pricegroup,
 
 488   push @columns, @pricegroup_columns;
 
 490   my @includeable_custom_variables = grep { $_->{includeable} } @{ $cvar_configs };
 
 491   my @searchable_custom_variables  = grep { $_->{searchable} }  @{ $cvar_configs };
 
 492   my %column_defs_cvars            = map { +"cvar_$_->{name}" => { 'text' => $_->{description} } } @includeable_custom_variables;
 
 494   push @columns, map { "cvar_$_->{name}" } @includeable_custom_variables;
 
 496   %column_defs = (%column_defs, %column_defs_cvars, %column_defs_pricegroups);
 
 497   map { $column_defs{$_}->{visible} ||= $form->{"l_$_"} ? 1 : 0 } @columns;
 
 498   map { $column_defs{$_}->{align}   = 'right' } qw(onhand sellprice listprice lastcost linetotalsellprice linetotallastcost linetotallistprice rop weight soldtotal shop), @pricegroup_columns;
 
 500   my @hidden_variables = (
 
 501     qw(l_subtotal l_linetotal searchitems itemstatus bom l_pricegroups insertdatefrom insertdateto),
 
 504     map({ "cvar_$_->{name}" } @searchable_custom_variables),
 
 505     map({'cvar_'. $_->{name} .'_qtyop'} grep({$_->{type} eq 'number'} @searchable_custom_variables)),
 
 506     map({ "l_$_" } @columns),
 
 509   my $callback         = build_std_url('action=generate_report', grep { $form->{$_} } @hidden_variables);
 
 511   my @sort_full        = qw(partnumber description onhand soldtotal deliverydate insertdate shop);
 
 512   my @sort_no_revers   = qw(partsgroup bin priceupdate invnumber ordnumber quonumber name image drawing serialnumber);
 
 514   foreach my $col (@sort_full) {
 
 515     $column_defs{$col}->{link} = join '&', $callback, "sort=$col", map { "$_=" . E($form->{$_}) } qw(revers lastsort);
 
 517   map { $column_defs{$_}->{link} = "${callback}&sort=$_" } @sort_no_revers;
 
 519   # add order to callback
 
 520   $form->{callback} = join '&', ($callback, map { "${_}=" . E($form->{$_}) } qw(sort revers));
 
 522   my $report = SL::ReportGenerator->new(\%myconfig, $form);
 
 524   my %attachment_basenames = (
 
 525     'part'     => $locale->text('part_list'),
 
 526     'service'  => $locale->text('service_list'),
 
 527     'assembly' => $locale->text('assembly_list'),
 
 530   $report->set_options('raw_top_info_text'     => $form->parse_html_template('ic/generate_report_top', { options => \@options }),
 
 531                        'raw_bottom_info_text'  => $form->parse_html_template('ic/generate_report_bottom'),
 
 532                        'output_format'         => 'HTML',
 
 533                        'title'                 => $form->{title},
 
 534                        'attachment_basename'   => $attachment_basenames{$form->{searchitems}} . strftime('_%Y%m%d', localtime time),
 
 536   $report->set_options_from_form();
 
 537   $locale->set_numberformat_wo_thousands_separator(\%myconfig) if lc($report->{options}->{output_format}) eq 'csv';
 
 539   $report->set_columns(%column_defs);
 
 540   $report->set_column_order(@columns);
 
 542   $report->set_export_options('generate_report', @hidden_variables, qw(sort revers));
 
 544   $report->set_sort_indicator($form->{sort}, $form->{revers} ? 0 : 1);
 
 546   CVar->add_custom_variables_to_report('module'         => 'IC',
 
 547                                        'trans_id_field' => 'id',
 
 548                                        'configs'        => $cvar_configs,
 
 549                                        'column_defs'    => \%column_defs,
 
 550                                        'data'           => $form->{parts});
 
 552   CVar->add_custom_variables_to_report('module'         => 'IC',
 
 553                                        'sub_module'     => sub { $_[0]->{ioi} },
 
 554                                        'trans_id_field' => 'ioi_id',
 
 555                                        'configs'        => $cvar_configs,
 
 556                                        'column_defs'    => \%column_defs,
 
 557                                        'data'           => $form->{parts});
 
 559   my @subtotal_columns = qw(sellprice listprice lastcost);
 
 560   my %subtotals = map { $_ => 0 } ('onhand', @subtotal_columns);
 
 561   my %totals    = map { $_ => 0 } @subtotal_columns;
 
 563   my $same_item = @{ $form->{parts} } ? $form->{parts}[0]{ $form->{sort} } : undef;
 
 565   my $defaults  = AM->get_defaults();
 
 568   foreach my $ref (@{ $form->{parts} }) {
 
 570     # fresh row, for inserting later
 
 571     my $row = { map { $_ => { 'data' => $ref->{$_} } } @columns };
 
 573     $ref->{exchangerate} ||= 1;
 
 574     $ref->{price_factor} ||= 1;
 
 575     $ref->{sellprice}     *= $ref->{exchangerate} / $ref->{price_factor};
 
 576     $ref->{listprice}     *= $ref->{exchangerate} / $ref->{price_factor};
 
 577     $ref->{lastcost}      *= $ref->{exchangerate} / $ref->{price_factor};
 
 579     # use this for assemblies
 
 580     my $soldtotal = $bsooqr_mode ? $ref->{soldtotal} : $ref->{onhand};
 
 582     if ($ref->{assemblyitem}) {
 
 583       $row->{partnumber}{align}   = 'right';
 
 584       $row->{soldtotal}{data}     = 0;
 
 585       $soldtotal                  = 0 if ($form->{sold});
 
 588     my $edit_link               = build_std_url('action=edit', 'id=' . E($ref->{id}), 'callback');
 
 589     $row->{partnumber}->{link}  = $edit_link;
 
 590     $row->{description}->{link} = $edit_link;
 
 592     foreach (qw(sellprice listprice lastcost)) {
 
 593       $row->{$_}{data}            = $form->format_amount(\%myconfig, $ref->{$_}, 2);
 
 594       $row->{"linetotal$_"}{data} = $form->format_amount(\%myconfig, $ref->{onhand} * $ref->{$_}, 2);
 
 596     foreach ( @pricegroup_columns ) {
 
 597       $row->{$_}{data}            = $form->format_amount(\%myconfig, $ref->{"$_"}, 2);
 
 601     map { $row->{$_}{data} = $form->format_amount(\%myconfig, $ref->{$_}); } qw(onhand rop weight soldtotal);
 
 603     $row->{weight}->{data} .= ' ' . $defaults->{weightunit};
 
 605     # 'yes' and 'no' for boolean value shop
 
 606     if ($form->{l_shop}) {
 
 607       $row->{shop}{data} = $row->{shop}{data}? $::locale->text('yes') : $::locale->text('no');
 
 610     if (!$ref->{assemblyitem}) {
 
 611       foreach my $col (@subtotal_columns) {
 
 612         $totals{$col}    += $soldtotal * $ref->{$col};
 
 613         $subtotals{$col} += $soldtotal * $ref->{$col};
 
 616       $subtotals{soldtotal} += $soldtotal;
 
 620     if ($ref->{module} eq 'oe') {
 
 621       # für oe gibt es vier fälle, jeweils nach kunde oder lieferant unterschiedlich:
 
 623       # | ist bestellt  | Von Kunden bestellt |  -> edit_oe_ord_link
 
 624       # | Anfrage       | Angebot             |  -> edit_oe_quo_link
 
 626       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');
 
 627       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');
 
 629       $row->{ordnumber}{link} = $edit_oe_ord_link;
 
 630       $row->{quonumber}{link} = $edit_oe_quo_link if (!$ref->{ordnumber});
 
 633       $row->{invnumber}{link} = build_std_url("script=$ref->{module}.pl", 'action=edit', 'type=invoice', 'id=' . E($ref->{trans_id}), 'callback') if ($ref->{invnumber});
 
 636     # set properties of images
 
 637     if ($ref->{image} && (lc $report->{options}->{output_format} eq 'html')) {
 
 638       $row->{image}{data}     = '';
 
 639       $row->{image}{raw_data} = '<a href="' . H($ref->{image}) . '"><img src="' . H($ref->{image}) . '" height="32" border="0"></a>';
 
 641     map { $row->{$_}{link} = $ref->{$_} } qw(drawing microfiche);
 
 643     $row->{notes}{data} = SL::HTML::Util->strip($ref->{notes});
 
 645     $report->add_data($row);
 
 647     my $next_ref = $form->{parts}[$idx + 1];
 
 649     # insert subtotal rows
 
 650     if (($form->{l_subtotal} eq 'Y') &&
 
 652          (!$next_ref->{assemblyitem} && ($same_item ne $next_ref->{ $form->{sort} })))) {
 
 653       my $row = { map { $_ => { 'class' => 'listsubtotal', } } @columns };
 
 655       if (($form->{searchitems} ne 'assembly') || !$form->{bom}) {
 
 656         $row->{soldtotal}->{data} = $form->format_amount(\%myconfig, $subtotals{soldtotal});
 
 659       map { $row->{"linetotal$_"}->{data} = $form->format_amount(\%myconfig, $subtotals{$_}, 2) } @subtotal_columns;
 
 660       map { $subtotals{$_} = 0 } ('soldtotal', @subtotal_columns);
 
 662       $report->add_data($row);
 
 664       $same_item = $next_ref->{ $form->{sort} };
 
 670   if ($form->{"l_linetotal"} && !$form->{report_generator_csv_options_for_import}) {
 
 671     my $row = { map { $_ => { 'class' => 'listtotal', } } @columns };
 
 673     map { $row->{"linetotal$_"}->{data} = $form->format_amount(\%myconfig, $totals{$_}, 2) } @subtotal_columns;
 
 675     $report->add_separator();
 
 676     $report->add_data($row);
 
 679   $report->generate_with_headers();
 
 681   $lxdebug->leave_sub();
 
 682 }    #end generate_report
 
 685   $lxdebug->enter_sub();
 
 687   $auth->assert('part_service_assembly_edit');
 
 690   my ($column_index, $subtotalonhand, $subtotalsellprice, $subtotallastcost, $subtotallistprice) = @_;
 
 692   map { $column_data{$_} = "<td> </td>" } @{ $column_index };
 
 693   $$subtotalonhand = 0 if ($form->{searchitems} eq 'assembly' && $form->{bom});
 
 695   $column_data{onhand} =
 
 696       "<th class=listsubtotal align=right>"
 
 697     . $form->format_amount(\%myconfig, $$subtotalonhand)
 
 700   $column_data{linetotalsellprice} =
 
 701       "<th class=listsubtotal align=right>"
 
 702     . $form->format_amount(\%myconfig, $$subtotalsellprice, 2)
 
 704   $column_data{linetotallistprice} =
 
 705       "<th class=listsubtotal align=right>"
 
 706     . $form->format_amount(\%myconfig, $$subtotallistprice, 2)
 
 708   $column_data{linetotallastcost} =
 
 709       "<th class=listsubtotal align=right>"
 
 710     . $form->format_amount(\%myconfig, $$subtotallastcost, 2)
 
 713   $$subtotalonhand    = 0;
 
 714   $$subtotalsellprice = 0;
 
 715   $$subtotallistprice = 0;
 
 716   $$subtotallastcost  = 0;
 
 718   print "<tr class=listsubtotal>";
 
 720   map { print "\n$column_data{$_}" } @{ $column_index };
 
 726   $lxdebug->leave_sub();
 
 730   $lxdebug->enter_sub();
 
 732   $auth->assert('part_service_assembly_details');
 
 734   # show history button
 
 735   $form->{javascript} = qq|<script type="text/javascript" src="js/show_history.js"></script>|;
 
 736   #/show hhistory button
 
 737   IC->get_part(\%myconfig, \%$form);
 
 739   $form->{"original_partnumber"} = $form->{"partnumber"};
 
 741   my $title      = 'Edit ' . ucfirst $form->{item};
 
 742   $form->{title} = $locale->text($title);
 
 747   $lxdebug->leave_sub();
 
 751   $lxdebug->enter_sub();
 
 753   $auth->assert('part_service_assembly_details');
 
 755   IC->create_links("IC", \%myconfig, \%$form);
 
 758   map({ $form->{selectcurrency} .= "<option>$_\n" } $::form->get_all_currencies());
 
 760   # parts and assemblies have the same links
 
 761   my $item = $form->{item};
 
 762   if ($form->{item} eq 'assembly') {
 
 766   # build the popup menus
 
 767   $form->{taxaccounts} = "";
 
 768   foreach my $key (keys %{ $form->{IC_links} }) {
 
 769     foreach my $ref (@{ $form->{IC_links}{$key} }) {
 
 771       # if this is a tax field
 
 772       if ($key =~ /IC_tax/) {
 
 773         if ($key =~ /\Q$item\E/) {
 
 774           $form->{taxaccounts} .= "$ref->{accno} ";
 
 775           $form->{"IC_tax_$ref->{accno}_description"} =
 
 776             "$ref->{accno}--$ref->{description}";
 
 779             if ($form->{amount}{ $ref->{accno} }) {
 
 780               $form->{"IC_tax_$ref->{accno}"} = "checked";
 
 783             $form->{"IC_tax_$ref->{accno}"} = "checked";
 
 788         $form->{"select$key"} .=
 
 789           "<option $ref->{selected}>$ref->{accno}--$ref->{description}\n";
 
 790         if ($form->{amount}{$key} eq $ref->{accno}) {
 
 791           $form->{$key} = "$ref->{accno}--$ref->{description}";
 
 797   chop $form->{taxaccounts};
 
 799   if (($form->{item} eq "part") || ($form->{item} eq "assembly")) {
 
 800     $form->{selectIC_income}  = $form->{selectIC_sale};
 
 801     $form->{selectIC_expense} = $form->{selectIC_cogs};
 
 802     $form->{IC_income}        = $form->{IC_sale};
 
 803     $form->{IC_expense}       = $form->{IC_cogs};
 
 806   delete $form->{IC_links};
 
 807   delete $form->{amount};
 
 809   $form->get_partsgroup(\%myconfig, { all => 1 });
 
 811   $form->{partsgroup} = "$form->{partsgroup}--$form->{partsgroup_id}";
 
 813   if (@{ $form->{all_partsgroup} }) {
 
 814     $form->{selectpartsgroup} = qq|<option>\n|;
 
 815     map { $form->{selectpartsgroup} .= qq|<option value="$_->{partsgroup}--$_->{id}">$_->{partsgroup}\n| } @{ $form->{all_partsgroup} };
 
 818   if ($form->{item} eq 'assembly') {
 
 820     foreach my $i (1 .. $form->{assembly_rows}) {
 
 821       if ($form->{"partsgroup_id_$i"}) {
 
 822         $form->{"partsgroup_$i"} =
 
 823           qq|$form->{"partsgroup_$i"}--$form->{"partsgroup_id_$i"}|;
 
 826     $form->get_partsgroup(\%myconfig);
 
 828     if (@{ $form->{all_partsgroup} }) {
 
 829       $form->{selectassemblypartsgroup} = qq|<option>\n|;
 
 832         $form->{selectassemblypartsgroup} .=
 
 833           qq|<option value="$_->{partsgroup}--$_->{id}">$_->{partsgroup}\n|
 
 834       } @{ $form->{all_partsgroup} };
 
 837   $lxdebug->leave_sub();
 
 841   $lxdebug->enter_sub();
 
 843   $auth->assert('part_service_assembly_details');
 
 845   $form->{pg_keys}          = sub { "$_[0]->{partsgroup}--$_[0]->{id}" };
 
 846   $form->{description_area} = ($form->{rows} = $form->numtextrows($form->{description}, 40)) > 1;
 
 847   $form->{notes_rows}       =  max 4, $form->numtextrows($form->{notes}, 40), $form->numtextrows($form->{formel}, 40);
 
 849   map { $form->{"is_$_"}  = ($form->{item} eq $_) } qw(part service assembly);
 
 850   map { $form->{$_}       =~ s/"/"/g;        } qw(unit);
 
 852   $form->get_lists('price_factors' => 'ALL_PRICE_FACTORS',
 
 853                    'partsgroup'    => 'all_partsgroup',
 
 854                    'vendors'       => 'ALL_VENDORS',
 
 855                    'warehouses'    => { 'key'    => 'WAREHOUSES',
 
 856                                         'bins'   => 'BINS', });
 
 857   # leerer wert für Lager und Lagerplatz korrekt einstellt
 
 858   # ID 0 sollte in Ordnung sein, da der Zähler sowieso höher ist
 
 859   my $no_default_bin_entry = { 'id' => '0', description => '--', 'BINS' => [ { id => '0', description => ''} ] };
 
 860   push @ { $form->{WAREHOUSES} }, $no_default_bin_entry;
 
 861   if (my $max = scalar @{ $form->{WAREHOUSES} }) {
 
 862     my ($default_warehouse_id, $default_bin_id);
 
 863     if ($form->{action} eq 'add') { # default only for new entries
 
 864       $default_warehouse_id = $::instance_conf->get_warehouse_id;
 
 865       $default_bin_id       = $::instance_conf->get_bin_id;
 
 867     $form->{warehouse_id} ||= $default_warehouse_id || $form->{WAREHOUSES}->[$max -1]->{id};
 
 868     $form->{bin_id}       ||= $default_bin_id       ||  $form->{WAREHOUSES}->[$max -1]->{BINS}->[0]->{id};
 
 871   $form->{LANGUAGES}        = SL::DB::Manager::Language->get_all_sorted;
 
 872   $form->{translations_map} = { map { ($_->{language_id} => $_) } @{ $form->{translations} || [] } };
 
 874   IC->retrieve_buchungsgruppen(\%myconfig, $form);
 
 875   @{ $form->{BUCHUNGSGRUPPEN} } = grep { $_->{id} eq $form->{buchungsgruppen_id} || ($form->{id} && $form->{orphaned}) || !$form->{id} } @{ $form->{BUCHUNGSGRUPPEN} };
 
 877   if (($form->{partnumber} ne '') && !SL::TransNumber->new(number => $form->{partnumber}, type => $form->{item}, id => $form->{id})->is_unique) {
 
 878     flash('info', $::locale->text('This partnumber is not unique. You should change it.'));
 
 881   my $units = AM->retrieve_units(\%myconfig, $form);
 
 882   $form->{ALL_UNITS} = [ map +{ name => $_ }, sort { $units->{$a}{sortkey} <=> $units->{$b}{sortkey} } keys %$units ];
 
 884   $form->{defaults} = AM->get_defaults();
 
 886   $form->{CUSTOM_VARIABLES} = CVar->get_custom_variables('module' => 'IC', 'trans_id' => $form->{id});
 
 888   my ($null, $partsgroup_id) = split /--/, $form->{partsgroup};
 
 890   CVar->render_inputs('variables' => $form->{CUSTOM_VARIABLES}, show_disabled_message => 1, partsgroup_id => $partsgroup_id)
 
 891     if (scalar @{ $form->{CUSTOM_VARIABLES} });
 
 893   $::request->layout->use_javascript("${_}.js") for qw(ckeditor/ckeditor ckeditor/adapters/jquery kivi.PriceRule);
 
 894   $::request->layout->add_javascripts_inline("\$(function(){kivi.PriceRule.load_price_rules_for_part(@{[ $::form->{id} * 1 ]})});") if $::form->{id};
 
 896   #print $form->parse_html_template('ic/form_header', { ALL_PRICE_FACTORS => $form->{ALL_PRICE_FACTORS},
 
 897   #                                                     ALL_UNITS         => $form->{ALL_UNITS},
 
 898   #                                                     BUCHUNGSGRUPPEN   => $form->{BUCHUNGSGRUPPEN},
 
 899   #                                                     payment_terms     => $form->{payment_terms},
 
 900   #                                                     all_partsgroup    => $form->{all_partsgroup}});
 
 902   $form->{show_edit_buttons} = $main::auth->check_right($::myconfig{login}, 'part_service_assembly_edit');
 
 904   print $form->parse_html_template('ic/form_header');
 
 905   $lxdebug->leave_sub();
 
 909   $lxdebug->enter_sub();
 
 911   $auth->assert('part_service_assembly_details');
 
 913   print $form->parse_html_template('ic/form_footer');
 
 915   $lxdebug->leave_sub();
 
 919   $lxdebug->enter_sub();
 
 922   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;
 
 923   delete @{$form}{grep { m/^make_\d+/ || m/^model_\d+/ } keys %{ $form }};
 
 924   print $form->parse_html_template('ic/makemodel', { MM_DATA => [ @mm_data, {} ], mm_rows => scalar @mm_data + 1 });
 
 926   $lxdebug->leave_sub();
 
 930   $lxdebug->enter_sub();
 
 933   my ($nochange, $callback, $previousform, $linetotal, $line_purchase_price, $href);
 
 935   @column_index = qw(runningnumber qty unit bom partnumber description partsgroup lastcost total);
 
 937   if ($form->{previousform}) {
 
 939     @column_index = qw(qty unit bom partnumber description partsgroup total);
 
 943     $form->{old_callback} = $form->{callback};
 
 944     $callback             = $form->{callback};
 
 945     $form->{callback}     = "$form->{script}?action=display_form";
 
 948     map { delete $form->{$_} } qw(action header);
 
 950     # save form variables in a previousform variable
 
 951     my %form_to_save = map   { ($_ => m/^ (?: listprice | sellprice | lastcost ) $/x ? $form->format_amount(\%myconfig, $form->{$_}) : $form->{$_}) }
 
 953     $previousform    = $::auth->save_form_in_session(form => \%form_to_save);
 
 955     $form->{callback} = $callback;
 
 956     $form->{assemblytotal} = 0;
 
 957     $form->{assembly_purchase_price_total} = 0;
 
 962    runningnumber => { text =>  $locale->text('No.'),              nowrap => 1, width => '5%',  align => 'left',},
 
 963    qty           => { text =>  $locale->text('Qty'),              nowrap => 1, width => '10%', align => 'left',},
 
 964    unit          => { text =>  $locale->text('Unit'),             nowrap => 1, width => '5%',  align => 'left',},
 
 965    partnumber    => { text =>  $locale->text('Part Number'),      nowrap => 1, width => '20%', align => 'left',},
 
 966    description   => { text =>  $locale->text('Part Description'), nowrap => 1, width => '50%', align => 'left',},
 
 967    lastcost      => { text =>  $locale->text('Purchase Prices'),  nowrap => 1, width => '50%', align => 'right',},
 
 968    total         => { text =>  $locale->text('Sale Prices'),      nowrap => 1,                 align => 'right',},
 
 969    bom           => { text =>  $locale->text('BOM'),                                           align => 'center',},
 
 970    partsgroup    => { text =>  $locale->text('Group'),                                         align => 'left',},
 
 975   for my $i (1 .. $numrows) {
 
 976     my (%row, @row_hiddens);
 
 978     $form->{"partnumber_$i"} =~ s/\"/"/g;
 
 980     $linetotal           = $form->round_amount($form->{"sellprice_$i"} * $form->{"qty_$i"} / ($form->{"price_factor_$i"} || 1), 4);
 
 981     $line_purchase_price = $form->round_amount($form->{"lastcost_$i"} *  $form->{"qty_$i"} / ($form->{"price_factor_$i"} || 1), 4);
 
 982     $form->{assemblytotal}                  += $linetotal;
 
 983     $form->{assembly_purchase_price_total}  += $line_purchase_price;
 
 984     $form->{"qty_$i"}    = $form->format_amount(\%myconfig, $form->{"qty_$i"});
 
 985     $linetotal           = $form->format_amount(\%myconfig, $linetotal, 2);
 
 986     $line_purchase_price = $form->format_amount(\%myconfig, $line_purchase_price, 2);
 
 987     $href                = build_std_url("action=edit", qq|id=$form->{"id_$i"}|, "rowcount=$numrows", "currow=$i", "previousform=$previousform");
 
 988     map { $row{$_}{data} = "" } qw(qty unit partnumber description bom partsgroup runningnumber);
 
 991     if (($i >= 1) && ($i == $numrows)) {
 
 992       if (!$form->{previousform}) {
 
 993         $row{partnumber}{data}  = qq|<input name="partnumber_$i" size=15 value="$form->{"partnumber_$i"}">|;
 
 994         $row{qty}{data}         = qq|<input name="qty_$i" size=5 value="$form->{"qty_$i"}">|;
 
 995         $row{description}{data} = qq|<input name="description_$i" size=40 value="$form->{"description_$i"}">|;
 
 996         $row{partsgroup}{data}  = qq|<input name="partsgroup_$i" size=10 value="$form->{"partsgroup_$i"}">|;
 
1000       if ($form->{previousform}) {
 
1001         push @row_hiddens,          qw(qty bom);
 
1002         $row{partnumber}{data}    = $form->{"partnumber_$i"};
 
1003         $row{qty}{data}           = $form->{"qty_$i"};
 
1004         $row{bom}{data}           = $form->{"bom_$i"} ? "x" : " ";
 
1005         $row{qty}{align}          = 'right';
 
1007         $row{partnumber}{data}    = qq|$form->{"partnumber_$i"}|;
 
1008         $row{partnumber}{link}     = $href;
 
1009         $row{qty}{data}           = qq|<input name="qty_$i" size=5 value="$form->{"qty_$i"}">|;
 
1010         $row{runningnumber}{data} = qq|<input name="runningnumber_$i" size=3 value="$i">|;
 
1011         $row{bom}{data}   = sprintf qq|<input name="bom_$i" type=checkbox class=checkbox value=1 %s>|,
 
1012                                        $form->{"bom_$i"} ? 'checked' : '';
 
1014       push @row_hiddens,        qw(unit description partnumber partsgroup);
 
1015       $row{unit}{data}        = $form->{"unit_$i"};
 
1016       #Bei der Artikelbeschreibung und Warengruppe können Sonderzeichen verwendet
 
1017       #werden, die den HTML Code stören. Daher sollen diese im Template escaped werden
 
1018       #dies geschieht, wenn die Variable escape gesetzt ist
 
1019       $row{description}{data}   = $form->{"description_$i"};
 
1020       $row{description}{escape} = 1;
 
1021       $row{partsgroup}{data}    = $form->{"partsgroup_$i"};
 
1022       $row{partsgroup}{escape}  = 1;
 
1023       $row{bom}{align}          = 'center';
 
1026     $row{lastcost}{data}      = $line_purchase_price;
 
1027     $row{total}{data}         = $linetotal;
 
1028     $row{lastcost}{align}     = 'right';
 
1029     $row{total}{align}        = 'right';
 
1030     $row{deliverydate}{align} = 'right';
 
1032     push @row_hiddens, qw(id sellprice lastcost weight price_factor_id price_factor);
 
1033     $row{hiddens} = [ map +{ name => "${_}_$i", value => $form->{"${_}_$i"} }, @row_hiddens ];
 
1038   print $form->parse_html_template('ic/assembly_row', { COLUMNS => \@column_index, ROWS => \@ROWS, HEADER => \%header });
 
1040   $lxdebug->leave_sub();
 
1044   $lxdebug->enter_sub();
 
1046   $auth->assert('part_service_assembly_edit');
 
1048   # update checks whether pricegroups, makemodels or assembly items have been changed/added
 
1049   # new items might have been added (and the original form might have been stored and restored)
 
1050   # so at the end the ic form is run through check_form in io.pl
 
1051   # The various combination of events can lead to problems with the order of parse_amount and format_amount
 
1052   # Currently check_form parses some variables in assembly mode, but not in article or service mode
 
1053   # This will only ever really be sanely resolved with a rewrite...
 
1055   # parse pricegroups. and no, don't rely on check_form for this...
 
1056   map { $form->{"price_$_"} = $form->parse_amount(\%myconfig, $form->{"price_$_"}) } 1 .. $form->{price_rows};
 
1058   unless ($form->{item} eq 'assembly') {
 
1059     # for assemblies check_form will parse sellprice and listprice, but not for parts or services
 
1060     $form->{$_} = $form->parse_amount(\%myconfig, $form->{$_}) for qw(sellprice listprice ve gv);
 
1063   if ($form->{item} eq 'part') {
 
1064     $form->{$_} = $form->parse_amount(\%myconfig, $form->{$_}) for qw(weight rop);
 
1067   # same for makemodel lastcosts
 
1068   # but parse_amount not necessary for assembly component lastcosts
 
1069   unless ($form->{item} eq "assembly") {
 
1070     map { $form->{"lastcost_$_"} = $form->parse_amount(\%myconfig, $form->{"lastcost_$_"}) } 1 .. $form->{"makemodel_rows"};
 
1071     $form->{lastcost} = $form->parse_amount(\%myconfig, $form->{lastcost});
 
1074   if ($form->{item} eq "assembly") {
 
1075     my $i = $form->{assembly_rows};
 
1077     # if last row is empty check the form otherwise retrieve item
 
1078     if (   ($form->{"partnumber_$i"} eq "")
 
1079         && ($form->{"description_$i"} eq "")
 
1080         && ($form->{"partsgroup_$i"}  eq "")) {
 
1081       # no new assembly item was added
 
1086       # search db for newly added assemblyitems, via partnumber or description
 
1087       IC->assembly_item(\%myconfig, \%$form);
 
1089       # form->{item_list} contains the possible matches, next check whether the
 
1090       # match is unique or we need to call the page to select the item
 
1091       my $rows = scalar @{ $form->{item_list} };
 
1094         $form->{"qty_$i"} = 1 unless ($form->{"qty_$i"});
 
1097           $form->{makemodel_rows}--;
 
1098           select_item(mode => 'IC', pre_entered_qty => $form->parse_amount(\%myconfig, $form->{"qty_$i"}));
 
1099           $::dispatcher->end_request;
 
1101           map { $form->{item_list}[$i]{$_} =~ s/\"/"/g }
 
1102             qw(partnumber description unit partsgroup);
 
1103           map { $form->{"${_}_$i"} = $form->{item_list}[0]{$_} }
 
1104             keys %{ $form->{item_list}[0] };
 
1105           $form->{"runningnumber_$i"} = $form->{assembly_rows};
 
1106           $form->{assembly_rows}++;
 
1114         $form->{rowcount} = $i;
 
1115         $form->{assembly_rows}++;
 
1122   } elsif (($form->{item} eq 'part') || ($form->{item} eq 'service')) {
 
1126   $lxdebug->leave_sub();
 
1130   $lxdebug->enter_sub();
 
1132   $auth->assert('part_service_assembly_edit');
 
1133   $::form->mtime_ischanged('parts');
 
1134   my ($parts_id, %newform, $amount, $callback);
 
1136   # check if there is a part number - commented out, cause there is an automatic allocation of numbers
 
1137   # $form->isblank("partnumber", $locale->text(ucfirst $form->{item}." Part Number missing!"));
 
1139   # check if there is a description
 
1140   $form->isblank("description", $locale->text("Part Description missing!"));
 
1142   $form->error($locale->text("Inventory quantity must be zero before you can set this $form->{item} obsolete!"))
 
1143     if $form->{obsolete} && $form->{onhand} * 1 && $form->{item} ne 'service';
 
1145   if (!$form->{buchungsgruppen_id}) {
 
1146     $form->error($locale->text("Parts must have an entry type.") . " " .
 
1147      $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.")
 
1151   $form->error($locale->text('Description must not be empty!')) unless $form->{description};
 
1152   $form->error($locale->text('Partnumber must not be set to empty!')) if $form->{id} && !$form->{partnumber};
 
1154   # undef warehouse_id if the empty value is selected
 
1155   if ( ($form->{warehouse_id} == 0) && ($form->{bin_id} == 0) ) {
 
1156     undef $form->{warehouse_id};
 
1157     undef $form->{bin_id};
 
1160   if (IC->save(\%myconfig, \%$form) == 3) {
 
1161     $form->error($locale->text('Partnumber not unique!'));
 
1163   # saving the history
 
1164   if(!exists $form->{addition}) {
 
1165     $form->{snumbers}  = qq|partnumber_| . $form->{partnumber};
 
1166     $form->{what_done} = "part";
 
1167     $form->{addition}  = "SAVED";
 
1168     $form->save_history;
 
1170   # /saving the history
 
1171   $parts_id = $form->{id};
 
1174   # load previous variables
 
1175   if ($form->{previousform}) {
 
1177     # save the new form variables before splitting previousform
 
1178     map { $newform{$_} = $form->{$_} } keys %$form;
 
1180     # don't trample on previous variables
 
1181     map { delete $form->{$_} } keys %newform;
 
1183     my $ic_cvar_configs = CVar->get_configs(module => 'IC');
 
1184     my @ic_cvar_fields  = map { "cvar_$_->{name}" } @{ $ic_cvar_configs };
 
1186     # restore original values
 
1187     $::auth->restore_form_from_session($newform{previousform}, form => $form);
 
1188     $form->{taxaccounts} = $newform{taxaccount2};
 
1190     if ($form->{item} eq 'assembly') {
 
1192       # undo number formatting
 
1193       map { $form->{$_} = $form->parse_amount(\%myconfig, $form->{$_}) }
 
1194         qw(weight listprice sellprice rop);
 
1196       $form->{assembly_rows}--;
 
1197       if ($newform{currow}) {
 
1198         $i = $newform{currow};
 
1200         $i = $form->{assembly_rows};
 
1202       $form->{"qty_$i"} = 1 unless ($form->{"qty_$i"} > 0);
 
1204       $form->{sellprice} -= $form->{"sellprice_$i"} * $form->{"qty_$i"};
 
1205       $form->{weight}    -= $form->{"weight_$i"} * $form->{"qty_$i"};
 
1207       # change/add values for assembly item
 
1208       map { $form->{"${_}_$i"} = $newform{$_} } qw(partnumber description bin unit weight listprice sellprice inventory_accno income_accno expense_accno price_factor_id);
 
1209       map { $form->{"ic_${_}_$i"} = $newform{$_} } @ic_cvar_fields;
 
1211       # das ist __voll__ bekloppt, dass so auszurechnen jb 22.5.09
 
1212       #$form->{sellprice} += $form->{"sellprice_$i"} * $form->{"qty_$i"};
 
1213       $form->{weight}    += $form->{"weight_$i"} * $form->{"qty_$i"};
 
1217       # set values for last invoice/order item
 
1218       $i = $form->{rowcount};
 
1219       $form->{"qty_$i"} = 1 unless ($form->{"qty_$i"} > 0);
 
1221       map { $form->{"${_}_$i"} = $newform{$_} } qw(partnumber description bin unit listprice inventory_accno income_accno expense_accno sellprice lastcost price_factor_id);
 
1222       map { $form->{"ic_${_}_$i"} = $newform{$_} } @ic_cvar_fields;
 
1224       $form->{"longdescription_$i"} = $newform{notes};
 
1226       $form->{"sellprice_$i"} = $newform{lastcost} if ($form->{vendor_id});
 
1228       if ($form->{exchangerate} != 0) {
 
1229         $form->{"sellprice_$i"} /= $form->{exchangerate};
 
1232       map { $form->{"taxaccounts_$i"} .= "$_ " } split / /, $newform{taxaccount};
 
1233       chop $form->{"taxaccounts_$i"};
 
1234       foreach my $item (qw(description rate taxnumber)) {
 
1235         my $index = $form->{"taxaccounts_$i"} . "_$item";
 
1236         $form->{$index} = $newform{$index};
 
1239       # credit remaining calculation
 
1240       $amount = $form->{"sellprice_$i"} * (1 - $form->{"discount_$i"} / 100) * $form->{"qty_$i"};
 
1242       map { $form->{"${_}_base"} += $amount } (split / /, $form->{"taxaccounts_$i"});
 
1243       map { $amount += ($form->{"${_}_base"} * $form->{"${_}_rate"}) } split / /, $form->{"taxaccounts_$i"} if !$form->{taxincluded};
 
1245       $form->{creditremaining} -= $amount;
 
1247       # redo number formatting, because invoice parse them!
 
1248       map { $form->{"${_}_$i"} = $form->format_amount(\%myconfig, $form->{"${_}_$i"}) } qw(weight listprice sellprice lastcost rop);
 
1251     $form->{"id_$i"} = $parts_id;
 
1253     # Get the actual price factor (not just the ID) for the marge calculation.
 
1254     $form->get_lists('price_factors' => 'ALL_PRICE_FACTORS');
 
1255     foreach my $pfac (@{ $form->{ALL_PRICE_FACTORS} }) {
 
1256       next if ($pfac->{id} != $newform{price_factor_id});
 
1257       $form->{"marge_price_factor_$i"} = $pfac->{factor};
 
1260     delete $form->{ALL_PRICE_FACTORS};
 
1262     delete $form->{action};
 
1264     # restore original callback
 
1265     $callback = $form->unescape($form->{callback});
 
1266     $form->{callback} = $form->unescape($form->{old_callback});
 
1267     delete $form->{old_callback};
 
1269     $form->{makemodel_rows}--;
 
1271     # put callback together
 
1272     foreach my $key (keys %$form) {
 
1274       # do single escape for Apache 2.0
 
1275       my $value = $form->escape($form->{$key}, 1);
 
1276       $callback .= qq|&$key=$value|;
 
1278     $form->{callback} = $callback;
 
1284   $lxdebug->leave_sub();
 
1288   $lxdebug->enter_sub();
 
1290   $auth->assert('part_service_assembly_edit');
 
1292   # saving the history
 
1293   if(!exists $form->{addition}) {
 
1294     $form->{snumbers}  = qq|partnumber_| . $form->{partnumber};
 
1295     $form->{addition}  = "SAVED AS NEW";
 
1296     $form->{what_done} = "part";
 
1297     $form->save_history;
 
1299   # /saving the history
 
1301   if ($form->{"original_partnumber"} &&
 
1302       ($form->{"partnumber"} eq $form->{"original_partnumber"})) {
 
1303     $form->{partnumber} = "";
 
1306   $lxdebug->leave_sub();
 
1310   $lxdebug->enter_sub();
 
1312   $auth->assert('part_service_assembly_edit');
 
1314   # saving the history
 
1315   if(!exists $form->{addition}) {
 
1316     $form->{snumbers}  = qq|partnumber_| . $form->{partnumber};
 
1317     $form->{addition}  = "DELETED";
 
1318     $form->{what_done} = "part";
 
1319     $form->save_history;
 
1321   # /saving the history
 
1322   my $rc = IC->delete(\%myconfig, \%$form);
 
1325   $form->redirect($locale->text('Item deleted!')) if ($rc > 0);
 
1326   $form->error($locale->text('Cannot delete item!'));
 
1328   $lxdebug->leave_sub();
 
1332   $lxdebug->enter_sub();
 
1334   $auth->assert('part_service_assembly_details');
 
1339     pricegroup    => $form->{"pricegroup_$_"},
 
1340     pricegroup_id => $form->{"pricegroup_id_$_"},
 
1341     price         => $form->{"price_$_"},
 
1344   print $form->parse_html_template('ic/price_row', { PRICES => \@PRICES });
 
1346   $lxdebug->leave_sub();
 
1349 sub ajax_autocomplete {
 
1350   $main::lxdebug->enter_sub();
 
1352   my $form     = $main::form;
 
1353   my %myconfig = %main::myconfig;
 
1355   $form->{column}          = 'description'     unless $form->{column} =~ /^partnumber|description$/;
 
1356   $form->{$form->{column}} = $form->{q}           || '';
 
1357   $form->{limit}           = ($form->{limit} * 1) || 10;
 
1358   $form->{searchitems}   ||= '';
 
1360   my @results = IC->all_parts(\%myconfig, $form);
 
1362   print $form->ajax_response_header(),
 
1363         $form->parse_html_template('ic/ajax_autocomplete');
 
1365   $main::lxdebug->leave_sub();
 
1369   $::lxdebug->enter_sub;
 
1371   $auth->assert('part_service_assembly_edit');
 
1375   $::form->language_payment(\%::myconfig);
 
1377   Common::webdav_folder($::form);
 
1380   price_row($::form->{price_rows});
 
1381   makemodel_row(++$::form->{makemodel_rows}) if $::form->{item} =~ /^(part|service)$/;
 
1382   assembly_row(++$::form->{assembly_rows})   if $::form->{item} eq 'assembly';
 
1386   $::lxdebug->leave_sub;
 
1389 sub back_to_record {
 
1393   delete @{$::form}{qw(action action_add action_back_to_record back_sub description item notes partnumber sellprice taxaccount2 unit vc)};
 
1395   $::auth->restore_form_from_session($::form->{previousform}, clobber => 1);
 
1396   $::form->{rowcount}--;
 
1397   $::form->{action}   = 'display_form';
 
1398   $::form->{callback} = $::form->{script} . '?' . join('&', map { $::form->escape($_) . '=' . $::form->escape($::form->{$_}) } sort keys %{ $::form });
 
1402 sub continue { call_sub($form->{"nextsub"}); }
 
1405   my $action = first { $::form->{"action_${_}"} } qw(add back_to_record);
 
1406   $::form->error($::locale->text('No action defined.')) unless $action;
 
1408   $::form->{dispatched_action} = $action;