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');
 
 262     ''       => $locale->text('Articles'),
 
 263     part     => $locale->text('Parts'),
 
 264     service  => $locale->text('Services'),
 
 265     assembly => $locale->text('Assemblies'),
 
 268   $form->{title} = $titles{$form->{searchitems}};
 
 271     'bin'                => { 'text' => $locale->text('Bin'), },
 
 272     'deliverydate'       => { 'text' => $locale->text('deliverydate'), },
 
 273     'description'        => { 'text' => $locale->text('Part Description'), },
 
 274     'notes'              => { 'text' => $locale->text('Notes'), },
 
 275     'drawing'            => { 'text' => $locale->text('Drawing'), },
 
 276     'ean'                => { 'text' => $locale->text('EAN'), },
 
 277     'image'              => { 'text' => $locale->text('Image'), },
 
 278     'insertdate'         => { 'text' => $locale->text('Insert Date'), },
 
 279     'invnumber'          => { 'text' => $locale->text('Invoice Number'), },
 
 280     'lastcost'           => { 'text' => $locale->text('Last Cost'), },
 
 281     'linetotallastcost'  => { 'text' => $locale->text('Extended'), },
 
 282     'linetotallistprice' => { 'text' => $locale->text('Extended'), },
 
 283     'linetotalsellprice' => { 'text' => $locale->text('Extended'), },
 
 284     'listprice'          => { 'text' => $locale->text('List Price'), },
 
 285     'microfiche'         => { 'text' => $locale->text('Microfiche'), },
 
 286     'name'               => { 'text' => $locale->text('Name'), },
 
 287     'onhand'             => { 'text' => $locale->text('Stocked Qty'), },
 
 288     'ordnumber'          => { 'text' => $locale->text('Order Number'), },
 
 289     'partnumber'         => { 'text' => $locale->text('Part Number'), },
 
 290     'partsgroup'         => { 'text' => $locale->text('Group'), },
 
 291     'priceupdate'        => { 'text' => $locale->text('Updated'), },
 
 292     'quonumber'          => { 'text' => $locale->text('Quotation'), },
 
 293     'rop'                => { 'text' => $locale->text('ROP'), },
 
 294     'sellprice'          => { 'text' => $locale->text('Sell Price'), },
 
 295     'serialnumber'       => { 'text' => $locale->text('Serial Number'), },
 
 296     'soldtotal'          => { 'text' => $locale->text('Qty in Selected Records'), },
 
 297     'name'               => { 'text' => $locale->text('Name in Selected Records'), },
 
 298     'transdate'          => { 'text' => $locale->text('Transdate'), },
 
 299     'unit'               => { 'text' => $locale->text('Unit'), },
 
 300     'weight'             => { 'text' => $locale->text('Weight'), },
 
 301     'shop'               => { 'text' => $locale->text('Shop article'), },
 
 302     'projectnumber'      => { 'text' => $locale->text('Project Number'), },
 
 303     'projectdescription' => { 'text' => $locale->text('Project Description'), },
 
 306   $revers     = $form->{revers};
 
 307   $lastsort   = $form->{lastsort};
 
 309   # sorting and direction of sorting
 
 310   # ToDO: change this to the simpler field+direction method
 
 311   if (($form->{lastsort} eq "") && ($form->{sort} eq undef)) {
 
 313     $form->{lastsort} = "partnumber";
 
 314     $form->{sort}     = "partnumber";
 
 316     if ($form->{lastsort} eq $form->{sort}) {
 
 317       $form->{revers} = 1 - $form->{revers};
 
 320       $form->{lastsort} = $form->{sort};
 
 324   # special case if we have a serialnumber limit search
 
 325   # serialnumbers are only given in invoices and orders,
 
 326   # so they can only pop up in bought, sold, rfq, and quoted stuff
 
 327   $form->{no_sn_joins} = 'Y' if (   !$form->{bought} && !$form->{sold}
 
 328                                  && !$form->{rfq}    && !$form->{quoted}
 
 329                                  && ($form->{l_serialnumber} || $form->{serialnumber}));
 
 331   # special case for any checkbox of bought | sold | onorder | ordered | rfq | quoted.
 
 332   # if any of these are ticked the behavior changes slightly for lastcost
 
 333   # since all those are aggregation checks for the legder tables this is an internal switch
 
 334   # refered to as ledgerchecks
 
 335   $form->{ledgerchecks} = 'Y' if (   $form->{bought} || $form->{sold} || $form->{onorder}
 
 336                                   || $form->{ordered} || $form->{rfq} || $form->{quoted});
 
 338   # if something should be activated if something else is active, enter it here
 
 340     onhand       => [ qw(l_onhand) ],
 
 341     short        => [ qw(l_onhand) ],
 
 342     onorder      => [ qw(l_ordnumber) ],
 
 343     ordered      => [ qw(l_ordnumber) ],
 
 344     rfq          => [ qw(l_quonumber) ],
 
 345     quoted       => [ qw(l_quonumber) ],
 
 346     bought       => [ qw(l_invnumber) ],
 
 347     sold         => [ qw(l_invnumber) ],
 
 348     ledgerchecks => [ qw(l_name) ],
 
 349     serialnumber => [ qw(l_serialnumber) ],
 
 350     no_sn_joins  => [ qw(bought sold) ],
 
 353   # get name of partsgroup if id is given
 
 355   if ($form->{partsgroup_id}) {
 
 356     my $pg = SL::DB::PartsGroup->new(id => $form->{partsgroup_id})->load;
 
 357     $pg_name = $pg->{'partsgroup'};
 
 360   # these strings get displayed at the top of the results to indicate the user which switches were used
 
 362     active        => $locale->text('Active'),
 
 363     obsolete      => $locale->text('Obsolete'),
 
 364     orphaned      => $locale->text('Orphaned'),
 
 365     onhand        => $locale->text('On Hand'),
 
 366     short         => $locale->text('Short'),
 
 367     onorder       => $locale->text('On Order'),
 
 368     ordered       => $locale->text('Ordered'),
 
 369     rfq           => $locale->text('RFQ'),
 
 370     quoted        => $locale->text('Quoted'),
 
 371     bought        => $locale->text('Bought'),
 
 372     sold          => $locale->text('Sold'),
 
 373     transdatefrom => $locale->text('From')       . " " . $locale->date(\%myconfig, $form->{transdatefrom}, 1),
 
 374     transdateto   => $locale->text('To (time)')  . " " . $locale->date(\%myconfig, $form->{transdateto}, 1),
 
 375     partnumber    => $locale->text('Part Number')      . ": '$form->{partnumber}'",
 
 376     partsgroup    => $locale->text('Group')            . ": '$form->{partsgroup}'",
 
 377     partsgroup_id => $locale->text('Group')            . ": '$pg_name'",
 
 378     serialnumber  => $locale->text('Serial Number')    . ": '$form->{serialnumber}'",
 
 379     description   => $locale->text('Part Description') . ": '$form->{description}'",
 
 380     make          => $locale->text('Make')             . ": '$form->{make}'",
 
 381     model         => $locale->text('Model')            . ": '$form->{model}'",
 
 382     drawing       => $locale->text('Drawing')          . ": '$form->{drawing}'",
 
 383     microfiche    => $locale->text('Microfiche')       . ": '$form->{microfiche}'",
 
 384     l_soldtotal   => $locale->text('Qty in Selected Records'),
 
 385     ean           => $locale->text('EAN')              . ": '$form->{ean}'",
 
 386     insertdatefrom => $locale->text('Insert Date') . ": " . $locale->text('From')       . " " . $locale->date(\%myconfig, $form->{insertdatefrom}, 1),
 
 387     insertdateto   => $locale->text('Insert Date') . ": " . $locale->text('To (time)')  . " " . $locale->date(\%myconfig, $form->{insertdateto}, 1),
 
 390   my @itemstatus_keys = qw(active obsolete orphaned onhand short);
 
 391   my @callback_keys   = qw(onorder ordered rfq quoted bought sold partnumber partsgroup partsgroup_id serialnumber description make model
 
 392                            drawing microfiche l_soldtotal l_deliverydate transdatefrom transdateto insertdatefrom insertdateto ean shop all);
 
 394   # calculate dependencies
 
 395   for (@itemstatus_keys, @callback_keys) {
 
 396     next if ($form->{itemstatus} ne $_ && !$form->{$_});
 
 397     map { $form->{$_} = 'Y' } @{ $dependencies{$_} } if $dependencies{$_};
 
 400   # generate callback and optionstrings
 
 402   for my  $key (@itemstatus_keys, @callback_keys) {
 
 403     next if ($form->{itemstatus} ne $key && !$form->{$key});
 
 404     push @options, $optiontexts{$key};
 
 407   # special case for lastcost
 
 408   if ($form->{ledgerchecks}){
 
 409     # ledgerchecks don't know about sellprice or lastcost. they just return a
 
 410     # price. so rename sellprice to price, and drop lastcost.
 
 411     $column_defs{sellprice}{text} = $locale->text('Price');
 
 412     $form->{l_lastcost} = ""
 
 415   if ($form->{description}) {
 
 416     $description = $form->{description};
 
 417     $description =~ s/\n/<br>/g;
 
 420   if ($form->{l_linetotal}) {
 
 421     $form->{l_qty} = "Y";
 
 422     $form->{l_linetotalsellprice} = "Y" if $form->{l_sellprice};
 
 423     $form->{l_linetotallastcost}  = $form->{searchitems} eq 'assembly' && !$form->{bom} ? "" : 'Y' if  $form->{l_lastcost};
 
 424     $form->{l_linetotallistprice} = "Y" if $form->{l_listprice};
 
 427   if ($form->{searchitems} eq 'service') {
 
 429     # remove bin, weight and rop from list
 
 430     map { $form->{"l_$_"} = "" } qw(bin weight rop);
 
 432     $form->{l_onhand} = "";
 
 434     # qty is irrelevant unless bought or sold
 
 440         || $form->{quoted}) {
 
 441 #      $form->{l_onhand} = "Y";
 
 443       $form->{l_linetotalsellprice} = "";
 
 444       $form->{l_linetotallastcost}  = "";
 
 448   # soldtotal doesn't make sense with more than one bsooqr option.
 
 449   # so reset it to sold (the most common option), and issue a warning
 
 451   # also it doesn't make sense without bsooqr. disable and issue a warning too
 
 452   my @bsooqr = qw(sold bought onorder ordered rfq quoted);
 
 453   my $bsooqr_mode = grep { $form->{$_} } @bsooqr;
 
 454   if ($form->{l_subtotal} && 1 < $bsooqr_mode) {
 
 455     my $enabled       = first { $form->{$_} } @bsooqr;
 
 456     $form->{$_}       = ''   for @bsooqr;
 
 457     $form->{$enabled} = 'Y';
 
 459     push @options, $::locale->text('Subtotal cannot distinguish betweens record types. Only one of the selected record types will be displayed: #1', $optiontexts{$enabled});
 
 461   if ($form->{l_soldtotal} && !$bsooqr_mode) {
 
 462     delete $form->{l_soldtotal};
 
 464     flash('warning', $::locale->text('Soldtotal does not make sense without any bsooqr options'));
 
 466   if ($form->{l_name} && !$bsooqr_mode) {
 
 467     delete $form->{l_name};
 
 469     flash('warning', $::locale->text('Name does not make sense without any bsooqr options'));
 
 471   IC->all_parts(\%myconfig, \%$form);
 
 474     partnumber description notes partsgroup bin onhand rop soldtotal unit listprice
 
 475     linetotallistprice sellprice linetotalsellprice lastcost linetotallastcost
 
 476     priceupdate weight image drawing microfiche invnumber ordnumber quonumber
 
 477     transdate name serialnumber deliverydate ean projectnumber projectdescription
 
 481   my $pricegroups = SL::DB::Manager::Pricegroup->get_all(sort => 'id');
 
 482   my @pricegroup_columns;
 
 483   my %column_defs_pricegroups;
 
 484   if ($form->{l_pricegroups}) {
 
 485     @pricegroup_columns      = map { "pricegroup_" . $_->id } @{ $pricegroups };
 
 486     %column_defs_pricegroups = map {
 
 487       "pricegroup_" . $_->id => {
 
 488         text    => $::locale->text('Pricegroup') . ' ' . $_->pricegroup,
 
 493   push @columns, @pricegroup_columns;
 
 495   my @includeable_custom_variables = grep { $_->{includeable} } @{ $cvar_configs };
 
 496   my @searchable_custom_variables  = grep { $_->{searchable} }  @{ $cvar_configs };
 
 497   my %column_defs_cvars            = map { +"cvar_$_->{name}" => { 'text' => $_->{description} } } @includeable_custom_variables;
 
 499   push @columns, map { "cvar_$_->{name}" } @includeable_custom_variables;
 
 501   %column_defs = (%column_defs, %column_defs_cvars, %column_defs_pricegroups);
 
 502   map { $column_defs{$_}->{visible} ||= $form->{"l_$_"} ? 1 : 0 } @columns;
 
 503   map { $column_defs{$_}->{align}   = 'right' } qw(onhand sellprice listprice lastcost linetotalsellprice linetotallastcost linetotallistprice rop weight soldtotal shop), @pricegroup_columns;
 
 505   my @hidden_variables = (
 
 506     qw(l_subtotal l_linetotal searchitems itemstatus bom l_pricegroups insertdatefrom insertdateto),
 
 509     map({ "cvar_$_->{name}" } @searchable_custom_variables),
 
 510     map({'cvar_'. $_->{name} .'_qtyop'} grep({$_->{type} eq 'number'} @searchable_custom_variables)),
 
 511     map({ "l_$_" } @columns),
 
 514   my $callback         = build_std_url('action=generate_report', grep { $form->{$_} } @hidden_variables);
 
 516   my @sort_full        = qw(partnumber description onhand soldtotal deliverydate insertdate shop);
 
 517   my @sort_no_revers   = qw(partsgroup bin priceupdate invnumber ordnumber quonumber name image drawing serialnumber);
 
 519   foreach my $col (@sort_full) {
 
 520     $column_defs{$col}->{link} = join '&', $callback, "sort=$col", map { "$_=" . E($form->{$_}) } qw(revers lastsort);
 
 522   map { $column_defs{$_}->{link} = "${callback}&sort=$_" } @sort_no_revers;
 
 524   # add order to callback
 
 525   $form->{callback} = join '&', ($callback, map { "${_}=" . E($form->{$_}) } qw(sort revers));
 
 527   my $report = SL::ReportGenerator->new(\%myconfig, $form);
 
 529   my %attachment_basenames = (
 
 530     'part'     => $locale->text('part_list'),
 
 531     'service'  => $locale->text('service_list'),
 
 532     'assembly' => $locale->text('assembly_list'),
 
 535   $report->set_options('raw_top_info_text'     => $form->parse_html_template('ic/generate_report_top', { options => \@options }),
 
 536                        'raw_bottom_info_text'  => $form->parse_html_template('ic/generate_report_bottom'),
 
 537                        'output_format'         => 'HTML',
 
 538                        'title'                 => $form->{title},
 
 539                        'attachment_basename'   => $attachment_basenames{$form->{searchitems}} . strftime('_%Y%m%d', localtime time),
 
 541   $report->set_options_from_form();
 
 542   $locale->set_numberformat_wo_thousands_separator(\%myconfig) if lc($report->{options}->{output_format}) eq 'csv';
 
 544   $report->set_columns(%column_defs);
 
 545   $report->set_column_order(@columns);
 
 547   $report->set_export_options('generate_report', @hidden_variables, qw(sort revers));
 
 549   $report->set_sort_indicator($form->{sort}, $form->{revers} ? 0 : 1);
 
 551   CVar->add_custom_variables_to_report('module'         => 'IC',
 
 552                                        'trans_id_field' => 'id',
 
 553                                        'configs'        => $cvar_configs,
 
 554                                        'column_defs'    => \%column_defs,
 
 555                                        'data'           => $form->{parts});
 
 557   CVar->add_custom_variables_to_report('module'         => 'IC',
 
 558                                        'sub_module'     => sub { $_[0]->{ioi} },
 
 559                                        'trans_id_field' => 'ioi_id',
 
 560                                        'configs'        => $cvar_configs,
 
 561                                        'column_defs'    => \%column_defs,
 
 562                                        'data'           => $form->{parts});
 
 564   my @subtotal_columns = qw(sellprice listprice lastcost);
 
 565   my %subtotals = map { $_ => 0 } ('onhand', @subtotal_columns);
 
 566   my %totals    = map { $_ => 0 } @subtotal_columns;
 
 568   my $same_item = @{ $form->{parts} } ? $form->{parts}[0]{ $form->{sort} } : undef;
 
 570   my $defaults  = AM->get_defaults();
 
 573   foreach my $ref (@{ $form->{parts} }) {
 
 575     # fresh row, for inserting later
 
 576     my $row = { map { $_ => { 'data' => $ref->{$_} } } @columns };
 
 578     $ref->{exchangerate} ||= 1;
 
 579     $ref->{price_factor} ||= 1;
 
 580     $ref->{sellprice}     *= $ref->{exchangerate} / $ref->{price_factor};
 
 581     $ref->{listprice}     *= $ref->{exchangerate} / $ref->{price_factor};
 
 582     $ref->{lastcost}      *= $ref->{exchangerate} / $ref->{price_factor};
 
 584     # use this for assemblies
 
 585     my $soldtotal = $bsooqr_mode ? $ref->{soldtotal} : $ref->{onhand};
 
 587     if ($ref->{assemblyitem}) {
 
 588       $row->{partnumber}{align}   = 'right';
 
 589       $row->{soldtotal}{data}     = 0;
 
 590       $soldtotal                  = 0 if ($form->{sold});
 
 593     my $edit_link               = build_std_url('action=edit', 'id=' . E($ref->{id}), 'callback');
 
 594     $row->{partnumber}->{link}  = $edit_link;
 
 595     $row->{description}->{link} = $edit_link;
 
 597     foreach (qw(sellprice listprice lastcost)) {
 
 598       $row->{$_}{data}            = $form->format_amount(\%myconfig, $ref->{$_}, 2);
 
 599       $row->{"linetotal$_"}{data} = $form->format_amount(\%myconfig, $ref->{onhand} * $ref->{$_}, 2);
 
 601     foreach ( @pricegroup_columns ) {
 
 602       $row->{$_}{data}            = $form->format_amount(\%myconfig, $ref->{"$_"}, 2);
 
 606     map { $row->{$_}{data} = $form->format_amount(\%myconfig, $ref->{$_}); } qw(onhand rop weight soldtotal);
 
 608     $row->{weight}->{data} .= ' ' . $defaults->{weightunit};
 
 610     # 'yes' and 'no' for boolean value shop
 
 611     if ($form->{l_shop}) {
 
 612       $row->{shop}{data} = $row->{shop}{data}? $::locale->text('yes') : $::locale->text('no');
 
 615     if (!$ref->{assemblyitem}) {
 
 616       foreach my $col (@subtotal_columns) {
 
 617         $totals{$col}    += $soldtotal * $ref->{$col};
 
 618         $subtotals{$col} += $soldtotal * $ref->{$col};
 
 621       $subtotals{soldtotal} += $soldtotal;
 
 625     if ($ref->{module} eq 'oe') {
 
 626       # für oe gibt es vier fälle, jeweils nach kunde oder lieferant unterschiedlich:
 
 628       # | ist bestellt  | Von Kunden bestellt |  -> edit_oe_ord_link
 
 629       # | Anfrage       | Angebot             |  -> edit_oe_quo_link
 
 631       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');
 
 632       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');
 
 634       $row->{ordnumber}{link} = $edit_oe_ord_link;
 
 635       $row->{quonumber}{link} = $edit_oe_quo_link if (!$ref->{ordnumber});
 
 638       $row->{invnumber}{link} = build_std_url("script=$ref->{module}.pl", 'action=edit', 'type=invoice', 'id=' . E($ref->{trans_id}), 'callback') if ($ref->{invnumber});
 
 641     # set properties of images
 
 642     if ($ref->{image} && (lc $report->{options}->{output_format} eq 'html')) {
 
 643       $row->{image}{data}     = '';
 
 644       $row->{image}{raw_data} = '<a href="' . H($ref->{image}) . '"><img src="' . H($ref->{image}) . '" height="32" border="0"></a>';
 
 646     map { $row->{$_}{link} = $ref->{$_} } qw(drawing microfiche);
 
 648     $row->{notes}{data} = SL::HTML::Util->strip($ref->{notes});
 
 650     $report->add_data($row);
 
 652     my $next_ref = $form->{parts}[$idx + 1];
 
 654     # insert subtotal rows
 
 655     if (($form->{l_subtotal} eq 'Y') &&
 
 657          (!$next_ref->{assemblyitem} && ($same_item ne $next_ref->{ $form->{sort} })))) {
 
 658       my $row = { map { $_ => { 'class' => 'listsubtotal', } } @columns };
 
 660       if (($form->{searchitems} ne 'assembly') || !$form->{bom}) {
 
 661         $row->{soldtotal}->{data} = $form->format_amount(\%myconfig, $subtotals{soldtotal});
 
 664       map { $row->{"linetotal$_"}->{data} = $form->format_amount(\%myconfig, $subtotals{$_}, 2) } @subtotal_columns;
 
 665       map { $subtotals{$_} = 0 } ('soldtotal', @subtotal_columns);
 
 667       $report->add_data($row);
 
 669       $same_item = $next_ref->{ $form->{sort} };
 
 675   if ($form->{"l_linetotal"} && !$form->{report_generator_csv_options_for_import}) {
 
 676     my $row = { map { $_ => { 'class' => 'listtotal', } } @columns };
 
 678     map { $row->{"linetotal$_"}->{data} = $form->format_amount(\%myconfig, $totals{$_}, 2) } @subtotal_columns;
 
 680     $report->add_separator();
 
 681     $report->add_data($row);
 
 684   $report->generate_with_headers();
 
 686   $lxdebug->leave_sub();
 
 687 }    #end generate_report
 
 690   $lxdebug->enter_sub();
 
 692   $auth->assert('part_service_assembly_edit');
 
 695   my ($column_index, $subtotalonhand, $subtotalsellprice, $subtotallastcost, $subtotallistprice) = @_;
 
 697   map { $column_data{$_} = "<td> </td>" } @{ $column_index };
 
 698   $$subtotalonhand = 0 if ($form->{searchitems} eq 'assembly' && $form->{bom});
 
 700   $column_data{onhand} =
 
 701       "<th class=listsubtotal align=right>"
 
 702     . $form->format_amount(\%myconfig, $$subtotalonhand)
 
 705   $column_data{linetotalsellprice} =
 
 706       "<th class=listsubtotal align=right>"
 
 707     . $form->format_amount(\%myconfig, $$subtotalsellprice, 2)
 
 709   $column_data{linetotallistprice} =
 
 710       "<th class=listsubtotal align=right>"
 
 711     . $form->format_amount(\%myconfig, $$subtotallistprice, 2)
 
 713   $column_data{linetotallastcost} =
 
 714       "<th class=listsubtotal align=right>"
 
 715     . $form->format_amount(\%myconfig, $$subtotallastcost, 2)
 
 718   $$subtotalonhand    = 0;
 
 719   $$subtotalsellprice = 0;
 
 720   $$subtotallistprice = 0;
 
 721   $$subtotallastcost  = 0;
 
 723   print "<tr class=listsubtotal>";
 
 725   map { print "\n$column_data{$_}" } @{ $column_index };
 
 731   $lxdebug->leave_sub();
 
 735   $lxdebug->enter_sub();
 
 737   $auth->assert('part_service_assembly_details');
 
 739   # show history button
 
 740   $form->{javascript} = qq|<script type="text/javascript" src="js/show_history.js"></script>|;
 
 741   #/show hhistory button
 
 742   IC->get_part(\%myconfig, \%$form);
 
 744   $form->{"original_partnumber"} = $form->{"partnumber"};
 
 746   my $title      = 'Edit ' . ucfirst $form->{item};
 
 747   $form->{title} = $locale->text($title);
 
 752   $lxdebug->leave_sub();
 
 756   $lxdebug->enter_sub();
 
 758   $auth->assert('part_service_assembly_details');
 
 760   IC->create_links("IC", \%myconfig, \%$form);
 
 763   map({ $form->{selectcurrency} .= "<option>$_\n" } $::form->get_all_currencies());
 
 765   # parts and assemblies have the same links
 
 766   my $item = $form->{item};
 
 767   if ($form->{item} eq 'assembly') {
 
 771   # build the popup menus
 
 772   $form->{taxaccounts} = "";
 
 773   foreach my $key (keys %{ $form->{IC_links} }) {
 
 774     foreach my $ref (@{ $form->{IC_links}{$key} }) {
 
 776       # if this is a tax field
 
 777       if ($key =~ /IC_tax/) {
 
 778         if ($key =~ /\Q$item\E/) {
 
 779           $form->{taxaccounts} .= "$ref->{accno} ";
 
 780           $form->{"IC_tax_$ref->{accno}_description"} =
 
 781             "$ref->{accno}--$ref->{description}";
 
 784             if ($form->{amount}{ $ref->{accno} }) {
 
 785               $form->{"IC_tax_$ref->{accno}"} = "checked";
 
 788             $form->{"IC_tax_$ref->{accno}"} = "checked";
 
 793         $form->{"select$key"} .=
 
 794           "<option $ref->{selected}>$ref->{accno}--$ref->{description}\n";
 
 795         if ($form->{amount}{$key} eq $ref->{accno}) {
 
 796           $form->{$key} = "$ref->{accno}--$ref->{description}";
 
 802   chop $form->{taxaccounts};
 
 804   if (($form->{item} eq "part") || ($form->{item} eq "assembly")) {
 
 805     $form->{selectIC_income}  = $form->{selectIC_sale};
 
 806     $form->{selectIC_expense} = $form->{selectIC_cogs};
 
 807     $form->{IC_income}        = $form->{IC_sale};
 
 808     $form->{IC_expense}       = $form->{IC_cogs};
 
 811   delete $form->{IC_links};
 
 812   delete $form->{amount};
 
 814   $form->get_partsgroup(\%myconfig, { all => 1 });
 
 816   $form->{partsgroup} = "$form->{partsgroup}--$form->{partsgroup_id}";
 
 818   if (@{ $form->{all_partsgroup} }) {
 
 819     $form->{selectpartsgroup} = qq|<option>\n|;
 
 820     map { $form->{selectpartsgroup} .= qq|<option value="$_->{partsgroup}--$_->{id}">$_->{partsgroup}\n| } @{ $form->{all_partsgroup} };
 
 823   if ($form->{item} eq 'assembly') {
 
 825     foreach my $i (1 .. $form->{assembly_rows}) {
 
 826       if ($form->{"partsgroup_id_$i"}) {
 
 827         $form->{"partsgroup_$i"} =
 
 828           qq|$form->{"partsgroup_$i"}--$form->{"partsgroup_id_$i"}|;
 
 831     $form->get_partsgroup(\%myconfig);
 
 833     if (@{ $form->{all_partsgroup} }) {
 
 834       $form->{selectassemblypartsgroup} = qq|<option>\n|;
 
 837         $form->{selectassemblypartsgroup} .=
 
 838           qq|<option value="$_->{partsgroup}--$_->{id}">$_->{partsgroup}\n|
 
 839       } @{ $form->{all_partsgroup} };
 
 842   $lxdebug->leave_sub();
 
 846   $lxdebug->enter_sub();
 
 848   $auth->assert('part_service_assembly_details');
 
 850   $form->{pg_keys}          = sub { "$_[0]->{partsgroup}--$_[0]->{id}" };
 
 851   $form->{description_area} = ($form->{rows} = $form->numtextrows($form->{description}, 40)) > 1;
 
 852   $form->{notes_rows}       =  max 4, $form->numtextrows($form->{notes}, 40), $form->numtextrows($form->{formel}, 40);
 
 854   map { $form->{"is_$_"}  = ($form->{item} eq $_) } qw(part service assembly);
 
 855   map { $form->{$_}       =~ s/"/"/g;        } qw(unit);
 
 857   $form->get_lists('price_factors' => 'ALL_PRICE_FACTORS',
 
 858                    'partsgroup'    => 'all_partsgroup',
 
 859                    'vendors'       => 'ALL_VENDORS',
 
 860                    'warehouses'    => { 'key'    => 'WAREHOUSES',
 
 861                                         'bins'   => 'BINS', });
 
 862   # leerer wert für Lager und Lagerplatz korrekt einstellt
 
 863   # ID 0 sollte in Ordnung sein, da der Zähler sowieso höher ist
 
 864   my $no_default_bin_entry = { 'id' => '0', description => '--', 'BINS' => [ { id => '0', description => ''} ] };
 
 865   push @ { $form->{WAREHOUSES} }, $no_default_bin_entry;
 
 866   if (my $max = scalar @{ $form->{WAREHOUSES} }) {
 
 867     my ($default_warehouse_id, $default_bin_id);
 
 868     if ($form->{action} eq 'add') { # default only for new entries
 
 869       $default_warehouse_id = $::instance_conf->get_warehouse_id;
 
 870       $default_bin_id       = $::instance_conf->get_bin_id;
 
 872     $form->{warehouse_id} ||= $default_warehouse_id || $form->{WAREHOUSES}->[$max -1]->{id};
 
 873     $form->{bin_id}       ||= $default_bin_id       ||  $form->{WAREHOUSES}->[$max -1]->{BINS}->[0]->{id};
 
 876   $form->{LANGUAGES}        = SL::DB::Manager::Language->get_all_sorted;
 
 877   $form->{translations_map} = { map { ($_->{language_id} => $_) } @{ $form->{translations} || [] } };
 
 879   IC->retrieve_buchungsgruppen(\%myconfig, $form);
 
 880   @{ $form->{BUCHUNGSGRUPPEN} } = grep { $_->{id} eq $form->{buchungsgruppen_id} || ($form->{id} && $form->{orphaned}) || !$form->{id} } @{ $form->{BUCHUNGSGRUPPEN} };
 
 882   if (($form->{partnumber} ne '') && !SL::TransNumber->new(number => $form->{partnumber}, type => $form->{item}, id => $form->{id})->is_unique) {
 
 883     flash('info', $::locale->text('This partnumber is not unique. You should change it.'));
 
 886   my $units = AM->retrieve_units(\%myconfig, $form);
 
 887   $form->{ALL_UNITS} = [ map +{ name => $_ }, sort { $units->{$a}{sortkey} <=> $units->{$b}{sortkey} } keys %$units ];
 
 889   $form->{defaults} = AM->get_defaults();
 
 891   $form->{CUSTOM_VARIABLES} = CVar->get_custom_variables('module' => 'IC', 'trans_id' => $form->{id});
 
 893   my ($null, $partsgroup_id) = split /--/, $form->{partsgroup};
 
 895   CVar->render_inputs('variables' => $form->{CUSTOM_VARIABLES}, show_disabled_message => 1, partsgroup_id => $partsgroup_id)
 
 896     if (scalar @{ $form->{CUSTOM_VARIABLES} });
 
 898   $::request->layout->use_javascript("${_}.js") for qw(ckeditor/ckeditor ckeditor/adapters/jquery kivi.PriceRule);
 
 899   $::request->layout->add_javascripts_inline("\$(function(){kivi.PriceRule.load_price_rules_for_part(@{[ $::form->{id} * 1 ]})});") if $::form->{id};
 
 901   #print $form->parse_html_template('ic/form_header', { ALL_PRICE_FACTORS => $form->{ALL_PRICE_FACTORS},
 
 902   #                                                     ALL_UNITS         => $form->{ALL_UNITS},
 
 903   #                                                     BUCHUNGSGRUPPEN   => $form->{BUCHUNGSGRUPPEN},
 
 904   #                                                     payment_terms     => $form->{payment_terms},
 
 905   #                                                     all_partsgroup    => $form->{all_partsgroup}});
 
 907   $form->{show_edit_buttons} = $main::auth->check_right($::myconfig{login}, 'part_service_assembly_edit');
 
 909   print $form->parse_html_template('ic/form_header');
 
 910   $lxdebug->leave_sub();
 
 914   $lxdebug->enter_sub();
 
 916   $auth->assert('part_service_assembly_details');
 
 918   print $form->parse_html_template('ic/form_footer');
 
 920   $lxdebug->leave_sub();
 
 924   $lxdebug->enter_sub();
 
 927   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;
 
 928   delete @{$form}{grep { m/^make_\d+/ || m/^model_\d+/ } keys %{ $form }};
 
 929   print $form->parse_html_template('ic/makemodel', { MM_DATA => [ @mm_data, {} ], mm_rows => scalar @mm_data + 1 });
 
 931   $lxdebug->leave_sub();
 
 935   $lxdebug->enter_sub();
 
 938   my ($nochange, $callback, $previousform, $linetotal, $line_purchase_price, $href);
 
 940   @column_index = qw(runningnumber qty unit bom partnumber description partsgroup lastcost total);
 
 942   if ($form->{previousform}) {
 
 944     @column_index = qw(qty unit bom partnumber description partsgroup total);
 
 948     $form->{old_callback} = $form->{callback};
 
 949     $callback             = $form->{callback};
 
 950     $form->{callback}     = "$form->{script}?action=display_form";
 
 953     map { delete $form->{$_} } qw(action header);
 
 955     # save form variables in a previousform variable
 
 956     my %form_to_save = map   { ($_ => m/^ (?: listprice | sellprice | lastcost ) $/x ? $form->format_amount(\%myconfig, $form->{$_}) : $form->{$_}) }
 
 958     $previousform    = $::auth->save_form_in_session(form => \%form_to_save);
 
 960     $form->{callback} = $callback;
 
 961     $form->{assemblytotal} = 0;
 
 962     $form->{assembly_purchase_price_total} = 0;
 
 967    runningnumber => { text =>  $locale->text('No.'),              nowrap => 1, width => '5%',  align => 'left',},
 
 968    qty           => { text =>  $locale->text('Qty'),              nowrap => 1, width => '10%', align => 'left',},
 
 969    unit          => { text =>  $locale->text('Unit'),             nowrap => 1, width => '5%',  align => 'left',},
 
 970    partnumber    => { text =>  $locale->text('Part Number'),      nowrap => 1, width => '20%', align => 'left',},
 
 971    description   => { text =>  $locale->text('Part Description'), nowrap => 1, width => '50%', align => 'left',},
 
 972    lastcost      => { text =>  $locale->text('Purchase Prices'),  nowrap => 1, width => '50%', align => 'right',},
 
 973    total         => { text =>  $locale->text('Sale Prices'),      nowrap => 1,                 align => 'right',},
 
 974    bom           => { text =>  $locale->text('BOM'),                                           align => 'center',},
 
 975    partsgroup    => { text =>  $locale->text('Group'),                                         align => 'left',},
 
 980   for my $i (1 .. $numrows) {
 
 981     my (%row, @row_hiddens);
 
 983     $form->{"partnumber_$i"} =~ s/\"/"/g;
 
 985     $linetotal           = $form->round_amount($form->{"sellprice_$i"} * $form->{"qty_$i"} / ($form->{"price_factor_$i"} || 1), 4);
 
 986     $line_purchase_price = $form->round_amount($form->{"lastcost_$i"} *  $form->{"qty_$i"} / ($form->{"price_factor_$i"} || 1), 4);
 
 987     $form->{assemblytotal}                  += $linetotal;
 
 988     $form->{assembly_purchase_price_total}  += $line_purchase_price;
 
 989     $form->{"qty_$i"}    = $form->format_amount(\%myconfig, $form->{"qty_$i"});
 
 990     $linetotal           = $form->format_amount(\%myconfig, $linetotal, 2);
 
 991     $line_purchase_price = $form->format_amount(\%myconfig, $line_purchase_price, 2);
 
 992     $href                = build_std_url("action=edit", qq|id=$form->{"id_$i"}|, "rowcount=$numrows", "currow=$i", "previousform=$previousform");
 
 993     map { $row{$_}{data} = "" } qw(qty unit partnumber description bom partsgroup runningnumber);
 
 996     if (($i >= 1) && ($i == $numrows)) {
 
 997       if (!$form->{previousform}) {
 
 998         $row{partnumber}{data}  = qq|<input name="partnumber_$i" size=15 value="$form->{"partnumber_$i"}">|;
 
 999         $row{qty}{data}         = qq|<input name="qty_$i" size=5 value="$form->{"qty_$i"}">|;
 
1000         $row{description}{data} = qq|<input name="description_$i" size=40 value="$form->{"description_$i"}">|;
 
1001         $row{partsgroup}{data}  = qq|<input name="partsgroup_$i" size=10 value="$form->{"partsgroup_$i"}">|;
 
1005       if ($form->{previousform}) {
 
1006         push @row_hiddens,          qw(qty bom);
 
1007         $row{partnumber}{data}    = $form->{"partnumber_$i"};
 
1008         $row{qty}{data}           = $form->{"qty_$i"};
 
1009         $row{bom}{data}           = $form->{"bom_$i"} ? "x" : " ";
 
1010         $row{qty}{align}          = 'right';
 
1012         $row{partnumber}{data}    = qq|$form->{"partnumber_$i"}|;
 
1013         $row{partnumber}{link}     = $href;
 
1014         $row{qty}{data}           = qq|<input name="qty_$i" size=5 value="$form->{"qty_$i"}">|;
 
1015         $row{runningnumber}{data} = qq|<input name="runningnumber_$i" size=3 value="$i">|;
 
1016         $row{bom}{data}   = sprintf qq|<input name="bom_$i" type=checkbox class=checkbox value=1 %s>|,
 
1017                                        $form->{"bom_$i"} ? 'checked' : '';
 
1019       push @row_hiddens,        qw(unit description partnumber partsgroup);
 
1020       $row{unit}{data}        = $form->{"unit_$i"};
 
1021       #Bei der Artikelbeschreibung und Warengruppe können Sonderzeichen verwendet
 
1022       #werden, die den HTML Code stören. Daher sollen diese im Template escaped werden
 
1023       #dies geschieht, wenn die Variable escape gesetzt ist
 
1024       $row{description}{data}   = $form->{"description_$i"};
 
1025       $row{description}{escape} = 1;
 
1026       $row{partsgroup}{data}    = $form->{"partsgroup_$i"};
 
1027       $row{partsgroup}{escape}  = 1;
 
1028       $row{bom}{align}          = 'center';
 
1031     $row{lastcost}{data}      = $line_purchase_price;
 
1032     $row{total}{data}         = $linetotal;
 
1033     $row{lastcost}{align}     = 'right';
 
1034     $row{total}{align}        = 'right';
 
1035     $row{deliverydate}{align} = 'right';
 
1037     push @row_hiddens, qw(id sellprice lastcost weight price_factor_id price_factor);
 
1038     $row{hiddens} = [ map +{ name => "${_}_$i", value => $form->{"${_}_$i"} }, @row_hiddens ];
 
1043   print $form->parse_html_template('ic/assembly_row', { COLUMNS => \@column_index, ROWS => \@ROWS, HEADER => \%header });
 
1045   $lxdebug->leave_sub();
 
1049   $lxdebug->enter_sub();
 
1051   $auth->assert('part_service_assembly_edit');
 
1053   # update checks whether pricegroups, makemodels or assembly items have been changed/added
 
1054   # new items might have been added (and the original form might have been stored and restored)
 
1055   # so at the end the ic form is run through check_form in io.pl
 
1056   # The various combination of events can lead to problems with the order of parse_amount and format_amount
 
1057   # Currently check_form parses some variables in assembly mode, but not in article or service mode
 
1058   # This will only ever really be sanely resolved with a rewrite...
 
1060   # parse pricegroups. and no, don't rely on check_form for this...
 
1061   map { $form->{"price_$_"} = $form->parse_amount(\%myconfig, $form->{"price_$_"}) } 1 .. $form->{price_rows};
 
1063   unless ($form->{item} eq 'assembly') {
 
1064     # for assemblies check_form will parse sellprice and listprice, but not for parts or services
 
1065     $form->{$_} = $form->parse_amount(\%myconfig, $form->{$_}) for qw(sellprice listprice ve gv);
 
1068   if ($form->{item} eq 'part') {
 
1069     $form->{$_} = $form->parse_amount(\%myconfig, $form->{$_}) for qw(weight rop);
 
1072   # same for makemodel lastcosts
 
1073   # but parse_amount not necessary for assembly component lastcosts
 
1074   unless ($form->{item} eq "assembly") {
 
1075     map { $form->{"lastcost_$_"} = $form->parse_amount(\%myconfig, $form->{"lastcost_$_"}) } 1 .. $form->{"makemodel_rows"};
 
1076     $form->{lastcost} = $form->parse_amount(\%myconfig, $form->{lastcost});
 
1079   if ($form->{item} eq "assembly") {
 
1080     my $i = $form->{assembly_rows};
 
1082     # if last row is empty check the form otherwise retrieve item
 
1083     if (   ($form->{"partnumber_$i"} eq "")
 
1084         && ($form->{"description_$i"} eq "")
 
1085         && ($form->{"partsgroup_$i"}  eq "")) {
 
1086       # no new assembly item was added
 
1091       # search db for newly added assemblyitems, via partnumber or description
 
1092       IC->assembly_item(\%myconfig, \%$form);
 
1094       # form->{item_list} contains the possible matches, next check whether the
 
1095       # match is unique or we need to call the page to select the item
 
1096       my $rows = scalar @{ $form->{item_list} };
 
1099         $form->{"qty_$i"} = 1 unless ($form->{"qty_$i"});
 
1102           $form->{makemodel_rows}--;
 
1103           select_item(mode => 'IC', pre_entered_qty => $form->parse_amount(\%myconfig, $form->{"qty_$i"}));
 
1104           $::dispatcher->end_request;
 
1106           map { $form->{item_list}[$i]{$_} =~ s/\"/"/g }
 
1107             qw(partnumber description unit partsgroup);
 
1108           map { $form->{"${_}_$i"} = $form->{item_list}[0]{$_} }
 
1109             keys %{ $form->{item_list}[0] };
 
1110           $form->{"runningnumber_$i"} = $form->{assembly_rows};
 
1111           $form->{assembly_rows}++;
 
1119         $form->{rowcount} = $i;
 
1120         $form->{assembly_rows}++;
 
1127   } elsif (($form->{item} eq 'part') || ($form->{item} eq 'service')) {
 
1131   $lxdebug->leave_sub();
 
1135   $lxdebug->enter_sub();
 
1137   $auth->assert('part_service_assembly_edit');
 
1138   $::form->mtime_ischanged('parts');
 
1139   my ($parts_id, %newform, $amount, $callback);
 
1141   # check if there is a part number - commented out, cause there is an automatic allocation of numbers
 
1142   # $form->isblank("partnumber", $locale->text(ucfirst $form->{item}." Part Number missing!"));
 
1144   # check if there is a description
 
1145   $form->isblank("description", $locale->text("Part Description missing!"));
 
1147   $form->error($locale->text("Inventory quantity must be zero before you can set this $form->{item} obsolete!"))
 
1148     if $form->{obsolete} && $form->{onhand} * 1 && $form->{item} ne 'service';
 
1150   if (!$form->{buchungsgruppen_id}) {
 
1151     $form->error($locale->text("Parts must have an entry type.") . " " .
 
1152      $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.")
 
1156   $form->error($locale->text('Description must not be empty!')) unless $form->{description};
 
1157   $form->error($locale->text('Partnumber must not be set to empty!')) if $form->{id} && !$form->{partnumber};
 
1159   # undef warehouse_id if the empty value is selected
 
1160   if ( ($form->{warehouse_id} == 0) && ($form->{bin_id} == 0) ) {
 
1161     undef $form->{warehouse_id};
 
1162     undef $form->{bin_id};
 
1165   if (IC->save(\%myconfig, \%$form) == 3) {
 
1166     $form->error($locale->text('Partnumber not unique!'));
 
1168   # saving the history
 
1169   if(!exists $form->{addition}) {
 
1170     $form->{snumbers}  = qq|partnumber_| . $form->{partnumber};
 
1171     $form->{what_done} = "part";
 
1172     $form->{addition}  = "SAVED";
 
1173     $form->save_history;
 
1175   # /saving the history
 
1176   $parts_id = $form->{id};
 
1179   # load previous variables
 
1180   if ($form->{previousform}) {
 
1182     # save the new form variables before splitting previousform
 
1183     map { $newform{$_} = $form->{$_} } keys %$form;
 
1185     # don't trample on previous variables
 
1186     map { delete $form->{$_} } keys %newform;
 
1188     my $ic_cvar_configs = CVar->get_configs(module => 'IC');
 
1189     my @ic_cvar_fields  = map { "cvar_$_->{name}" } @{ $ic_cvar_configs };
 
1191     # restore original values
 
1192     $::auth->restore_form_from_session($newform{previousform}, form => $form);
 
1193     $form->{taxaccounts} = $newform{taxaccount2};
 
1195     if ($form->{item} eq 'assembly') {
 
1197       # undo number formatting
 
1198       map { $form->{$_} = $form->parse_amount(\%myconfig, $form->{$_}) }
 
1199         qw(weight listprice sellprice rop);
 
1201       $form->{assembly_rows}--;
 
1202       if ($newform{currow}) {
 
1203         $i = $newform{currow};
 
1205         $i = $form->{assembly_rows};
 
1207       $form->{"qty_$i"} = 1 unless ($form->{"qty_$i"} > 0);
 
1209       $form->{sellprice} -= $form->{"sellprice_$i"} * $form->{"qty_$i"};
 
1210       $form->{weight}    -= $form->{"weight_$i"} * $form->{"qty_$i"};
 
1212       # change/add values for assembly item
 
1213       map { $form->{"${_}_$i"} = $newform{$_} } qw(partnumber description bin unit weight listprice sellprice inventory_accno income_accno expense_accno price_factor_id);
 
1214       map { $form->{"ic_${_}_$i"} = $newform{$_} } @ic_cvar_fields;
 
1216       # das ist __voll__ bekloppt, dass so auszurechnen jb 22.5.09
 
1217       #$form->{sellprice} += $form->{"sellprice_$i"} * $form->{"qty_$i"};
 
1218       $form->{weight}    += $form->{"weight_$i"} * $form->{"qty_$i"};
 
1222       # set values for last invoice/order item
 
1223       $i = $form->{rowcount};
 
1224       $form->{"qty_$i"} = 1 unless ($form->{"qty_$i"} > 0);
 
1226       map { $form->{"${_}_$i"} = $newform{$_} } qw(partnumber description bin unit listprice inventory_accno income_accno expense_accno sellprice lastcost price_factor_id);
 
1227       map { $form->{"ic_${_}_$i"} = $newform{$_} } @ic_cvar_fields;
 
1229       $form->{"longdescription_$i"} = $newform{notes};
 
1231       $form->{"sellprice_$i"} = $newform{lastcost} if ($form->{vendor_id});
 
1233       if ($form->{exchangerate} != 0) {
 
1234         $form->{"sellprice_$i"} /= $form->{exchangerate};
 
1237       map { $form->{"taxaccounts_$i"} .= "$_ " } split / /, $newform{taxaccount};
 
1238       chop $form->{"taxaccounts_$i"};
 
1239       foreach my $item (qw(description rate taxnumber)) {
 
1240         my $index = $form->{"taxaccounts_$i"} . "_$item";
 
1241         $form->{$index} = $newform{$index};
 
1244       # credit remaining calculation
 
1245       $amount = $form->{"sellprice_$i"} * (1 - $form->{"discount_$i"} / 100) * $form->{"qty_$i"};
 
1247       map { $form->{"${_}_base"} += $amount } (split / /, $form->{"taxaccounts_$i"});
 
1248       map { $amount += ($form->{"${_}_base"} * $form->{"${_}_rate"}) } split / /, $form->{"taxaccounts_$i"} if !$form->{taxincluded};
 
1250       $form->{creditremaining} -= $amount;
 
1252       # redo number formatting, because invoice parse them!
 
1253       map { $form->{"${_}_$i"} = $form->format_amount(\%myconfig, $form->{"${_}_$i"}) } qw(weight listprice sellprice lastcost rop);
 
1256     $form->{"id_$i"} = $parts_id;
 
1258     # Get the actual price factor (not just the ID) for the marge calculation.
 
1259     $form->get_lists('price_factors' => 'ALL_PRICE_FACTORS');
 
1260     foreach my $pfac (@{ $form->{ALL_PRICE_FACTORS} }) {
 
1261       next if ($pfac->{id} != $newform{price_factor_id});
 
1262       $form->{"marge_price_factor_$i"} = $pfac->{factor};
 
1265     delete $form->{ALL_PRICE_FACTORS};
 
1267     delete $form->{action};
 
1269     # restore original callback
 
1270     $callback = $form->unescape($form->{callback});
 
1271     $form->{callback} = $form->unescape($form->{old_callback});
 
1272     delete $form->{old_callback};
 
1274     $form->{makemodel_rows}--;
 
1276     # put callback together
 
1277     foreach my $key (keys %$form) {
 
1279       # do single escape for Apache 2.0
 
1280       my $value = $form->escape($form->{$key}, 1);
 
1281       $callback .= qq|&$key=$value|;
 
1283     $form->{callback} = $callback;
 
1289   $lxdebug->leave_sub();
 
1293   $lxdebug->enter_sub();
 
1295   $auth->assert('part_service_assembly_edit');
 
1297   # saving the history
 
1298   if(!exists $form->{addition}) {
 
1299     $form->{snumbers}  = qq|partnumber_| . $form->{partnumber};
 
1300     $form->{addition}  = "SAVED AS NEW";
 
1301     $form->{what_done} = "part";
 
1302     $form->save_history;
 
1304   # /saving the history
 
1306   if ($form->{"original_partnumber"} &&
 
1307       ($form->{"partnumber"} eq $form->{"original_partnumber"})) {
 
1308     $form->{partnumber} = "";
 
1311   $lxdebug->leave_sub();
 
1315   $lxdebug->enter_sub();
 
1317   $auth->assert('part_service_assembly_edit');
 
1319   # saving the history
 
1320   if(!exists $form->{addition}) {
 
1321     $form->{snumbers}  = qq|partnumber_| . $form->{partnumber};
 
1322     $form->{addition}  = "DELETED";
 
1323     $form->{what_done} = "part";
 
1324     $form->save_history;
 
1326   # /saving the history
 
1327   my $rc = IC->delete(\%myconfig, \%$form);
 
1330   $form->redirect($locale->text('Item deleted!')) if ($rc > 0);
 
1331   $form->error($locale->text('Cannot delete item!'));
 
1333   $lxdebug->leave_sub();
 
1337   $lxdebug->enter_sub();
 
1339   $auth->assert('part_service_assembly_details');
 
1344     pricegroup    => $form->{"pricegroup_$_"},
 
1345     pricegroup_id => $form->{"pricegroup_id_$_"},
 
1346     price         => $form->{"price_$_"},
 
1349   print $form->parse_html_template('ic/price_row', { PRICES => \@PRICES });
 
1351   $lxdebug->leave_sub();
 
1354 sub ajax_autocomplete {
 
1355   $main::lxdebug->enter_sub();
 
1357   my $form     = $main::form;
 
1358   my %myconfig = %main::myconfig;
 
1360   $form->{column}          = 'description'     unless $form->{column} =~ /^partnumber|description$/;
 
1361   $form->{$form->{column}} = $form->{q}           || '';
 
1362   $form->{limit}           = ($form->{limit} * 1) || 10;
 
1363   $form->{searchitems}   ||= '';
 
1365   my @results = IC->all_parts(\%myconfig, $form);
 
1367   print $form->ajax_response_header(),
 
1368         $form->parse_html_template('ic/ajax_autocomplete');
 
1370   $main::lxdebug->leave_sub();
 
1374   $::lxdebug->enter_sub;
 
1376   $auth->assert('part_service_assembly_edit');
 
1380   $::form->language_payment(\%::myconfig);
 
1382   Common::webdav_folder($::form);
 
1385   price_row($::form->{price_rows});
 
1386   makemodel_row(++$::form->{makemodel_rows}) if $::form->{item} =~ /^(part|service)$/;
 
1387   assembly_row(++$::form->{assembly_rows})   if $::form->{item} eq 'assembly';
 
1391   $::lxdebug->leave_sub;
 
1394 sub back_to_record {
 
1398   delete @{$::form}{qw(action action_add action_back_to_record back_sub description item notes partnumber sellprice taxaccount2 unit vc)};
 
1400   $::auth->restore_form_from_session($::form->{previousform}, clobber => 1);
 
1401   $::form->{rowcount}--;
 
1402   $::form->{action}   = 'display_form';
 
1403   $::form->{callback} = $::form->{script} . '?' . join('&', map { $::form->escape($_) . '=' . $::form->escape($::form->{$_}) } sort keys %{ $::form });
 
1407 sub continue { call_sub($form->{"nextsub"}); }
 
1410   my $action = first { $::form->{"action_${_}"} } qw(add back_to_record);
 
1411   $::form->error($::locale->text('No action defined.')) unless $action;
 
1413   $::form->{dispatched_action} = $action;