Merge branch 'master' of github.com:kivitendo/kivitendo-erp
[kivitendo-erp.git] / bin / mozilla / ic.pl
index b29daa6..473467b 100644 (file)
 #======================================================================
 
 use POSIX qw(strftime);
-use List::Util qw(max);
+use List::Util qw(first max);
 use List::MoreUtils qw(any);
 
 use SL::AM;
 use SL::CVar;
 use SL::IC;
+use SL::Helper::Flash;
 use SL::ReportGenerator;
 
 #use SL::PE;
@@ -78,7 +79,8 @@ sub add {
 
   $auth->assert('part_service_assembly_edit');
 
-  $form->{title}           = $locale->text('Add ' . ucfirst $form->{item});
+  my $title                = 'Add ' . ucfirst $form->{item};
+  $form->{title}           = $locale->text($title);
   $form->{callback}        = "$form->{script}?action=add&item=$form->{item}" unless $form->{callback};
   $form->{unit_changeable} = 1;
 
@@ -92,7 +94,7 @@ sub add {
 sub search {
   $lxdebug->enter_sub();
 
-  $auth->assert('part_service_assembly_edit');
+  $auth->assert('part_service_assembly_details');
 
   $form->{revers}       = 0;  # switch for backward sorting
   $form->{lastsort}     = ""; # memory for which table was sort at last time
@@ -104,8 +106,6 @@ sub search {
   $form->{title} = $locale->text($form->{title});
   $form->{title} = $locale->text('Assemblies') if ($is_xyz{is_assembly});
 
-  $form->{jsscript} = 1;
-
   $form->{CUSTOM_VARIABLES}                  = CVar->get_configs('module' => 'IC');
   ($form->{CUSTOM_VARIABLES_FILTER_CODE},
    $form->{CUSTOM_VARIABLES_INCLUSION_CODE}) = CVar->render_search_options('variables'      => $form->{CUSTOM_VARIABLES},
@@ -114,8 +114,10 @@ sub search {
 
   $form->header;
 
+  $form->get_lists('partsgroup'    => 'ALL_PARTSGROUPS');
   print $form->parse_html_template('ic/search', { %is_xyz,
-                                                  dateformat => $myconfig{dateformat}, });
+                                                  dateformat => $myconfig{dateformat},
+                                                  limit => $myconfig{vclimit}, });
 
   $lxdebug->leave_sub();
 }    #end search()
@@ -127,6 +129,8 @@ sub search_update_prices {
 
   my $pricegroups = IC->get_pricegroups(\%myconfig, \%$form);
 
+  $form->{title} = $locale->text('Update Prices');
+
   $form->header;
 
   print $form->parse_html_template('ic/search_update_prices', { PRICE_ROWS => $pricegroups });
@@ -283,7 +287,6 @@ sub update_prices {
 #  $form->header;
 #
 #  print qq|
-#<body>
 #  <form method=post action=ic.pl>
 #    <table width=100%>
 #     <tr>
@@ -383,8 +386,6 @@ sub update_prices {
 #    . $locale->text('TOP100') . qq|">
 #
 #</form>
-#</body>
-#</html>
 #|;
 #  $lxdebug->leave_sub();
 #}    #end list()
@@ -720,8 +721,6 @@ sub addtop100 {
   my $colspan = $#column_index + 1;
 
   print qq|
-<body>
-
 <table width=100%>
   <tr>
     <th class=listtop colspan=$colspan>$form->{title}</th>
@@ -789,7 +788,7 @@ sub addtop100 {
 
     if ($form->{l_subtotal} eq 'Y' && !$ref->{assemblyitem}) {
       if ($sameitem ne $ref->{ $form->{sort} }) {
-        &parts_subtotal;
+        parts_subtotal(\@column_index, \$subtotalonhand, \$subtotalsellprice, \$subtotallastcost, \$subtotallistprice);
         $sameitem = $ref->{ $form->{sort} };
       }
     }
@@ -912,7 +911,7 @@ sub addtop100 {
   }
 
   if ($form->{l_subtotal} eq 'Y') {
-    &parts_subtotal;
+    parts_subtotal(\@column_index, \$subtotalonhand, \$subtotalsellprice, \$subtotallastcost, \$subtotallistprice);
   }    #fi
 
   if ($form->{"l_linetotal"}) {
@@ -972,13 +971,10 @@ sub addtop100 {
   print qq|
 <!--    <input type=hidden name=ndxs_counter value="$form->{ndxs_counter}">-->
 
-    <input class=submit type=submit name=action value="|
-    . $locale->text('choice') . qq|">
+<!--    <input class=submit type=submit name=action value="|
+    . $locale->text('choice') . qq|"> -->
 
   </form>
-
-</body>
-</html>
 |;
 
   $lxdebug->leave_sub();
@@ -1005,7 +1001,7 @@ sub addtop100 {
 #  bought sold onorder ordered rfq quoted
 #  l_partnumber l_description l_serialnumber l_unit l_listprice l_sellprice l_lastcost
 #  l_linetotal l_priceupdate l_bin l_rop l_weight l_image l_drawing l_microfiche
-#  l_partsgroup l_subtotal l_soldtotal l_deliverydate
+#  l_partsgroup l_subtotal l_soldtotal l_deliverydate l_pricegroups
 #
 # hiddens:
 #  nextsub revers lastsort sort ndxs_counter
@@ -1013,7 +1009,7 @@ sub addtop100 {
 sub generate_report {
   $lxdebug->enter_sub();
 
-  $auth->assert('part_service_assembly_edit');
+  $auth->assert('part_service_assembly_details');
 
   my ($revers, $lastsort, $description);
 
@@ -1023,6 +1019,39 @@ sub generate_report {
   $form->{title} =~ s/ys$/ies/;
   $form->{title} = $locale->text($form->{title});
 
+  my %column_defs = (
+    'bin'                => { 'text' => $locale->text('Bin'), },
+    'deliverydate'       => { 'text' => $locale->text('deliverydate'), },
+    'description'        => { 'text' => $locale->text('Part Description'), },
+    'notes'              => { 'text' => $locale->text('Notes'), },
+    'drawing'            => { 'text' => $locale->text('Drawing'), },
+    'ean'                => { 'text' => $locale->text('EAN'), },
+    'image'              => { 'text' => $locale->text('Image'), },
+    'invnumber'          => { 'text' => $locale->text('Invoice Number'), },
+    'lastcost'           => { 'text' => $locale->text('Last Cost'), },
+    'linetotallastcost'  => { 'text' => $locale->text('Extended'), },
+    'linetotallistprice' => { 'text' => $locale->text('Extended'), },
+    'linetotalsellprice' => { 'text' => $locale->text('Extended'), },
+    'listprice'          => { 'text' => $locale->text('List Price'), },
+    'microfiche'         => { 'text' => $locale->text('Microfiche'), },
+    'name'               => { 'text' => $locale->text('Name'), },
+    'onhand'             => { 'text' => $locale->text('Stocked Qty'), },
+    'ordnumber'          => { 'text' => $locale->text('Order Number'), },
+    'partnumber'         => { 'text' => $locale->text('Part Number'), },
+    'partsgroup'         => { 'text' => $locale->text('Group'), },
+    'priceupdate'        => { 'text' => $locale->text('Updated'), },
+    'quonumber'          => { 'text' => $locale->text('Quotation'), },
+    'rop'                => { 'text' => $locale->text('ROP'), },
+    'sellprice'          => { 'text' => $locale->text('Sell Price'), },
+    'serialnumber'       => { 'text' => $locale->text('Serial Number'), },
+    'soldtotal'          => { 'text' => $locale->text('Qty in Selected Records'), },
+    'transdate'          => { 'text' => $locale->text('Transdate'), },
+    'unit'               => { 'text' => $locale->text('Unit'), },
+    'weight'             => { 'text' => $locale->text('Weight'), },
+    'projectnumber'      => { 'text' => $locale->text('Project Number'), },
+    'projectdescription' => { 'text' => $locale->text('Project Description'), },
+  );
+
   $revers     = $form->{revers};
   $lastsort   = $form->{lastsort};
 
@@ -1070,6 +1099,13 @@ sub generate_report {
     no_sn_joins  => [ qw(bought sold) ],
   );
 
+  # get name of partsgroup if id is given
+  my $pg_name;
+  if ($form->{partsgroup_id}) {
+    my $pg = SL::DB::PartsGroup->new(id => $form->{partsgroup_id})->load;
+    $pg_name = $pg->{'partsgroup'};
+  }
+
   # these strings get displayed at the top of the results to indicate the user which switches were used
   my %optiontexts = (
     active        => $locale->text('Active'),
@@ -1087,17 +1123,19 @@ sub generate_report {
     transdateto   => $locale->text('To (time)')  . " " . $locale->date(\%myconfig, $form->{transdateto}, 1),
     partnumber    => $locale->text('Part Number')      . ": '$form->{partnumber}'",
     partsgroup    => $locale->text('Group')            . ": '$form->{partsgroup}'",
+    partsgroup_id => $locale->text('Group')            . ": '$pg_name'",
     serialnumber  => $locale->text('Serial Number')    . ": '$form->{serialnumber}'",
     description   => $locale->text('Part Description') . ": '$form->{description}'",
     make          => $locale->text('Make')             . ": '$form->{make}'",
     model         => $locale->text('Model')            . ": '$form->{model}'",
     drawing       => $locale->text('Drawing')          . ": '$form->{drawing}'",
     microfiche    => $locale->text('Microfiche')       . ": '$form->{microfiche}'",
-    l_soldtotal   => $locale->text('soldtotal'),
+    l_soldtotal   => $locale->text('Qty in Selected Records'),
+    ean           => $locale->text('EAN')              . ": '$form->{ean}'",
   );
 
   my @itemstatus_keys = qw(active obsolete orphaned onhand short);
-  my @callback_keys   = qw(onorder ordered rfq quoted bought sold partnumber partsgroup serialnumber description make model
+  my @callback_keys   = qw(onorder ordered rfq quoted bought sold partnumber partsgroup partsgroup_id serialnumber description make model
                            drawing microfiche l_soldtotal l_deliverydate transdatefrom transdateto ean);
 
   # calculate dependencies
@@ -1114,7 +1152,12 @@ sub generate_report {
   }
 
   # special case for lastcost
-  $form->{l_lastcost} = "" if $form->{ledgerchecks};
+  if ($form->{ledgerchecks}){
+    # ledgerchecks don't know about sellprice or lastcost. they just return a
+    # price. so rename sellprice to price, and drop lastcost.
+    $column_defs{sellprice}{text} = $locale->text('Price');
+    $form->{l_lastcost} = ""
+  }
 
   if ($form->{description}) {
     $description = $form->{description};
@@ -1122,7 +1165,7 @@ sub generate_report {
   }
 
   if ($form->{l_linetotal}) {
-    $form->{l_onhand} = "Y";
+    $form->{l_qty} = "Y";
     $form->{l_linetotalsellprice} = "Y" if $form->{l_sellprice};
     $form->{l_linetotallastcost}  = $form->{searchitems} eq 'assembly' && !$form->{bom} ? "" : 'Y' if  $form->{l_lastcost};
     $form->{l_linetotallistprice} = "Y" if $form->{l_listprice};
@@ -1142,62 +1185,74 @@ sub generate_report {
         || $form->{ordered}
         || $form->{rfq}
         || $form->{quoted}) {
-      $form->{l_onhand} = "Y";
+#      $form->{l_onhand} = "Y";
     } else {
       $form->{l_linetotalsellprice} = "";
       $form->{l_linetotallastcost}  = "";
     }
   }
 
+  # soldtotal doesn't make sense with more than one bsooqr option.
+  # so reset it to sold (the most common option), and issue a warning
+  # ...
+  # also it doesn't make sense without bsooqr. disable and issue a warning too
+  my @bsooqr = qw(sold bought onorder ordered rfq quoted);
+  my $bsooqr_mode = grep { $form->{$_} } @bsooqr;
+  if ($form->{l_subtotal} && 1 < $bsooqr_mode) {
+    my $enabled       = first { $form->{$_} } @bsooqr;
+    $form->{$_}       = ''   for @bsooqr;
+    $form->{$enabled} = 'Y';
+
+    push @options, $::locale->text('Subtotal cannot distinguish betweens record types. Only one of the selected record types will be displayed: #1', $optiontexts{$enabled});
+  }
+  if ($form->{l_soldtotal} && !$bsooqr_mode) {
+    delete $form->{l_soldtotal};
+
+    flash('warning', $::locale->text('Soldtotal does not make sense without any bsooqr options'));
+  }
+
   IC->all_parts(\%myconfig, \%$form);
 
   my @columns = qw(
-    partnumber description partsgroup bin onhand rop unit listprice
+    partnumber description notes partsgroup bin onhand rop soldtotal unit listprice
     linetotallistprice sellprice linetotalsellprice lastcost linetotallastcost
     priceupdate weight image drawing microfiche invnumber ordnumber quonumber
-    transdate name serialnumber soldtotal deliverydate
+    transdate name serialnumber deliverydate ean projectnumber projectdescription
   );
 
+  my $pricegroups = SL::DB::Manager::Pricegroup->get_all(sort => 'id');
+  my @pricegroup_columns;
+  my %column_defs_pricegroups;
+  if ($form->{l_pricegroups}) {
+    @pricegroup_columns      = map { "pricegroup_" . $_->id } @{ $pricegroups };
+    %column_defs_pricegroups = map {
+      "pricegroup_" . $_->id => {
+        text    => $::locale->text('Pricegroup') . ' ' . $_->pricegroup,
+        visible => 1,
+      },
+    }  @{ $pricegroups };
+  }
+  push @columns, @pricegroup_columns;
+
   my @includeable_custom_variables = grep { $_->{includeable} } @{ $cvar_configs };
   my @searchable_custom_variables  = grep { $_->{searchable} }  @{ $cvar_configs };
   my %column_defs_cvars            = map { +"cvar_$_->{name}" => { 'text' => $_->{description} } } @includeable_custom_variables;
 
   push @columns, map { "cvar_$_->{name}" } @includeable_custom_variables;
 
-  my %column_defs = (
-    'bin'                => { 'text' => $locale->text('Bin'), },
-    'deliverydate'       => { 'text' => $locale->text('deliverydate'), },
-    'description'        => { 'text' => $locale->text('Part Description'), },
-    'drawing'            => { 'text' => $locale->text('Drawing'), },
-    'image'              => { 'text' => $locale->text('Image'), },
-    'invnumber'          => { 'text' => $locale->text('Invoice Number'), },
-    'lastcost'           => { 'text' => $locale->text('Last Cost'), },
-    'linetotallastcost'  => { 'text' => $locale->text('Extended'), },
-    'linetotallistprice' => { 'text' => $locale->text('Extended'), },
-    'linetotalsellprice' => { 'text' => $locale->text('Extended'), },
-    'listprice'          => { 'text' => $locale->text('List Price'), },
-    'microfiche'         => { 'text' => $locale->text('Microfiche'), },
-    'name'               => { 'text' => $locale->text('Name'), },
-    'onhand'             => { 'text' => $locale->text('Qty'), },
-    'ordnumber'          => { 'text' => $locale->text('Order Number'), },
-    'partnumber'         => { 'text' => $locale->text('Part Number'), },
-    'partsgroup'         => { 'text' => $locale->text('Group'), },
-    'priceupdate'        => { 'text' => $locale->text('Updated'), },
-    'quonumber'          => { 'text' => $locale->text('Quotation'), },
-    'rop'                => { 'text' => $locale->text('ROP'), },
-    'sellprice'          => { 'text' => $locale->text('Sell Price'), },
-    'serialnumber'       => { 'text' => $locale->text('Serial Number'), },
-    'soldtotal'          => { 'text' => $locale->text('soldtotal'), },
-    'transdate'          => { 'text' => $locale->text('Transdate'), },
-    'unit'               => { 'text' => $locale->text('Unit'), },
-    'weight'             => { 'text' => $locale->text('Weight'), },
-    %column_defs_cvars,
+  %column_defs = (%column_defs, %column_defs_cvars, %column_defs_pricegroups);
+  map { $column_defs{$_}->{visible} ||= $form->{"l_$_"} ? 1 : 0 } @columns;
+  map { $column_defs{$_}->{align}   = 'right' } qw(onhand sellprice listprice lastcost linetotalsellprice linetotallastcost linetotallistprice rop weight soldtotal), @pricegroup_columns;
+
+  my @hidden_variables = (
+    qw(l_subtotal l_linetotal searchitems itemstatus bom l_pricegroups),
+    @itemstatus_keys,
+    @callback_keys,
+    map({ "cvar_$_->{name}" } @searchable_custom_variables),
+    map({'cvar_'. $_->{name} .'_qtyop'} grep({$_->{type} eq 'number'} @searchable_custom_variables)),
+    map({ "l_$_" } @columns),
   );
 
-  map { $column_defs{$_}->{visible} = $form->{"l_$_"} ? 1 : 0 } @columns;
-  map { $column_defs{$_}->{align}   = 'right' } qw(onhand sellprice listprice lastcost linetotalsellprice linetotallastcost linetotallistprice rop weight soldtotal);
-
-  my @hidden_variables = (qw(l_subtotal l_linetotal searchitems itemstatus bom), @itemstatus_keys, @callback_keys, @searchable_custom_variables, map { "l_$_" } @columns);
   my $callback         = build_std_url('action=generate_report', grep { $form->{$_} } @hidden_variables);
 
   my @sort_full        = qw(partnumber description onhand soldtotal deliverydate);
@@ -1219,13 +1274,14 @@ sub generate_report {
     'assembly' => $locale->text('assembly_list'),
   );
 
-  $report->set_options('top_info_text'         => $locale->text('Options') . ': ' . join(', ', grep $_, @options),
+  $report->set_options('raw_top_info_text'     => $form->parse_html_template('ic/generate_report_top', { options => \@options }),
                        'raw_bottom_info_text'  => $form->parse_html_template('ic/generate_report_bottom'),
                        'output_format'         => 'HTML',
                        'title'                 => $form->{title},
                        'attachment_basename'   => $attachment_basenames{$form->{searchitems}} . strftime('_%Y%m%d', localtime time),
   );
   $report->set_options_from_form();
+  $locale->set_numberformat_wo_thousands_separator(\%myconfig) if lc($report->{options}->{output_format}) eq 'csv';
 
   $report->set_columns(%column_defs);
   $report->set_column_order(@columns);
@@ -1251,7 +1307,7 @@ sub generate_report {
   my %subtotals = map { $_ => 0 } ('onhand', @subtotal_columns);
   my %totals    = map { $_ => 0 } @subtotal_columns;
   my $idx       = 0;
-  my $same_item = $form->{parts}[0]{ $form->{sort} } if (scalar @{ $form->{parts} });
+  my $same_item = @{ $form->{parts} } ? $form->{parts}[0]{ $form->{sort} } : undef;
 
   my $defaults  = AM->get_defaults();
 
@@ -1268,12 +1324,12 @@ sub generate_report {
     $ref->{lastcost}      *= $ref->{exchangerate} / $ref->{price_factor};
 
     # use this for assemblies
-    my $onhand = $ref->{onhand};
+    my $soldtotal = $bsooqr_mode ? $ref->{soldtotal} : $ref->{onhand};
 
     if ($ref->{assemblyitem}) {
       $row->{partnumber}{align}   = 'right';
-      $row->{onhand}{data}        = 0;
-      $onhand                     = 0 if ($form->{sold});
+      $row->{soldtotal}{data}     = 0;
+      $soldtotal                  = 0 if ($form->{sold});
     }
 
     my $edit_link               = build_std_url('action=edit', 'id=' . E($ref->{id}), 'callback');
@@ -1281,9 +1337,13 @@ sub generate_report {
     $row->{description}->{link} = $edit_link;
 
     foreach (qw(sellprice listprice lastcost)) {
-      $row->{$_}{data}            = $form->format_amount(\%myconfig, $ref->{$_}, -2);
+      $row->{$_}{data}            = $form->format_amount(\%myconfig, $ref->{$_}, 2);
       $row->{"linetotal$_"}{data} = $form->format_amount(\%myconfig, $ref->{onhand} * $ref->{$_}, 2);
     }
+    foreach ( @pricegroup_columns ) {
+      $row->{$_}{data}            = $form->format_amount(\%myconfig, $ref->{"$_"}, 2);
+    };
+
 
     map { $row->{$_}{data} = $form->format_amount(\%myconfig, $ref->{$_}); } qw(onhand rop weight soldtotal);
 
@@ -1291,18 +1351,25 @@ sub generate_report {
 
     if (!$ref->{assemblyitem}) {
       foreach my $col (@subtotal_columns) {
-        $totals{$col}    += $onhand * $ref->{$col};
-        $subtotals{$col} += $onhand * $ref->{$col};
+        $totals{$col}    += $soldtotal * $ref->{$col};
+        $subtotals{$col} += $soldtotal * $ref->{$col};
       }
 
-      $subtotals{onhand} += $onhand;
+      $subtotals{soldtotal} += $soldtotal;
     }
 
     # set module stuff
     if ($ref->{module} eq 'oe') {
-      my $edit_oe_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');
-      $row->{ordnumber}{link} = $edit_oe_link;
-      $row->{quonumber}{link} = $edit_oe_link if (!$ref->{ordnumber});
+      # für oe gibt es vier fälle, jeweils nach kunde oder lieferant unterschiedlich:
+      #
+      # | ist bestellt  | Von Kunden bestellt |  -> edit_oe_ord_link
+      # | Anfrage       | Angebot             |  -> edit_oe_quo_link
+
+      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');
+      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');
+
+      $row->{ordnumber}{link} = $edit_oe_ord_link;
+      $row->{quonumber}{link} = $edit_oe_quo_link if (!$ref->{ordnumber});
 
     } else {
       $row->{invnumber}{link} = build_std_url("script=$ref->{module}.pl", 'action=edit', 'type=invoice', 'id=' . E($ref->{trans_id}), 'callback');
@@ -1326,11 +1393,11 @@ sub generate_report {
       my $row = { map { $_ => { 'class' => 'listsubtotal', } } @columns };
 
       if (($form->{searchitems} ne 'assembly') || !$form->{bom}) {
-        $row->{onhand}->{data} = $form->format_amount(\%myconfig, $subtotals{onhand});
+        $row->{soldtotal}->{data} = $form->format_amount(\%myconfig, $subtotals{soldtotal});
       }
 
       map { $row->{"linetotal$_"}->{data} = $form->format_amount(\%myconfig, $subtotals{$_}, 2) } @subtotal_columns;
-      map { $subtotals{$_} = 0 } ('onhand', @subtotal_columns);
+      map { $subtotals{$_} = 0 } ('soldtotal', @subtotal_columns);
 
       $report->add_data($row);
 
@@ -1340,7 +1407,7 @@ sub generate_report {
     $idx++;
   }
 
-  if ($form->{"l_linetotal"}) {
+  if ($form->{"l_linetotal"} && !$form->{report_generator_csv_options_for_import}) {
     my $row = { map { $_ => { 'class' => 'listtotal', } } @columns };
 
     map { $row->{"linetotal$_"}->{data} = $form->format_amount(\%myconfig, $totals{$_}, 2) } @subtotal_columns;
@@ -1359,39 +1426,38 @@ sub parts_subtotal {
 
   $auth->assert('part_service_assembly_edit');
 
-  # imports
-  our (%column_data, @column_index);
-  our ($subtotalonhand, $totalsellprice, $totallastcost, $totallistprice, $subtotalsellprice, $subtotallastcost, $subtotallistprice);
+  my (%column_data);
+  my ($column_index, $subtotalonhand, $subtotalsellprice, $subtotallastcost, $subtotallistprice) = @_;
 
-  map { $column_data{$_} = "<td>&nbsp;</td>" } @column_index;
-  $subtotalonhand = 0 if ($form->{searchitems} eq 'assembly' && $form->{bom});
+  map { $column_data{$_} = "<td>&nbsp;</td>" } @{ $column_index };
+  $$subtotalonhand = 0 if ($form->{searchitems} eq 'assembly' && $form->{bom});
 
   $column_data{onhand} =
       "<th class=listsubtotal align=right>"
-    . $form->format_amount(\%myconfig, $subtotalonhand)
+    . $form->format_amount(\%myconfig, $$subtotalonhand)
     . "</th>";
 
   $column_data{linetotalsellprice} =
       "<th class=listsubtotal align=right>"
-    . $form->format_amount(\%myconfig, $subtotalsellprice, 2)
+    . $form->format_amount(\%myconfig, $$subtotalsellprice, 2)
     . "</th>";
   $column_data{linetotallistprice} =
       "<th class=listsubtotal align=right>"
-    . $form->format_amount(\%myconfig, $subtotallistprice, 2)
+    . $form->format_amount(\%myconfig, $$subtotallistprice, 2)
     . "</th>";
   $column_data{linetotallastcost} =
       "<th class=listsubtotal align=right>"
-    . $form->format_amount(\%myconfig, $subtotallastcost, 2)
+    . $form->format_amount(\%myconfig, $$subtotallastcost, 2)
     . "</th>";
 
-  $subtotalonhand    = 0;
-  $subtotalsellprice = 0;
-  $subtotallistprice = 0;
-  $subtotallastcost  = 0;
+  $$subtotalonhand    = 0;
+  $$subtotalsellprice = 0;
+  $$subtotallistprice = 0;
+  $$subtotallastcost  = 0;
 
   print "<tr class=listsubtotal>";
 
-  map { print "\n$column_data{$_}" } @column_index;
+  map { print "\n$column_data{$_}" } @{ $column_index };
 
   print qq|
   </tr>
@@ -1403,7 +1469,7 @@ sub parts_subtotal {
 sub edit {
   $lxdebug->enter_sub();
 
-  $auth->assert('part_service_assembly_edit');
+  $auth->assert('part_service_assembly_details');
 
   # show history button
   $form->{javascript} = qq|<script type="text/javascript" src="js/show_history.js"></script>|;
@@ -1412,7 +1478,8 @@ sub edit {
 
   $form->{"original_partnumber"} = $form->{"partnumber"};
 
-  $form->{title} = $locale->text('Edit ' . ucfirst $form->{item});
+  my $title      = 'Edit ' . ucfirst $form->{item};
+  $form->{title} = $locale->text($title);
 
   &link_part;
   &display_form;
@@ -1423,13 +1490,12 @@ sub edit {
 sub link_part {
   $lxdebug->enter_sub();
 
-  $auth->assert('part_service_assembly_edit');
+  $auth->assert('part_service_assembly_details');
 
   IC->create_links("IC", \%myconfig, \%$form);
 
   # currencies
-  map({ $form->{selectcurrency} .= "<option>$_\n" }
-      split(/:/, $form->{currencies}));
+  map({ $form->{selectcurrency} .= "<option>$_\n" } $::form->get_all_currencies());
 
   # parts and assemblies have the same links
   my $item = $form->{item};
@@ -1514,9 +1580,8 @@ sub link_part {
 sub form_header {
   $lxdebug->enter_sub();
 
-  $auth->assert('part_service_assembly_edit');
+  $auth->assert('part_service_assembly_details');
 
-  $form->{eur}              = $main::eur; # config dumps into namespace - yuck
   $form->{pg_keys}          = sub { "$_[0]->{partsgroup}--$_[0]->{id}" };
   $form->{description_area} = ($form->{rows} = $form->numtextrows($form->{description}, 40)) > 1;
   $form->{notes_rows}       =  max 4, $form->numtextrows($form->{notes}, 40), $form->numtextrows($form->{formel}, 40);
@@ -1526,22 +1591,38 @@ sub form_header {
 
   $form->get_lists('price_factors' => 'ALL_PRICE_FACTORS',
                    'partsgroup'    => 'all_partsgroup',
-                   'vendors'       => 'ALL_VENDORS',);
+                   'vendors'       => 'ALL_VENDORS',
+                   'warehouses'    => { 'key'    => 'WAREHOUSES',
+                                        'bins'   => 'BINS', });
+  # leerer wert für Lager und Lagerplatz korrekt einstellt
+  # ID 0 sollte in Ordnung sein, da der Zähler sowieso höher ist
+  my $no_default_bin_entry = { 'id' => '0', description => '--', 'BINS' => [ { id => '0', description => ''} ] };
+  push @ { $form->{WAREHOUSES} }, $no_default_bin_entry;
+  if (my $max = scalar @{ $form->{WAREHOUSES} }) {
+    my ($default_warehouse_id, $default_bin_id);
+    if ($form->{action} eq 'add') { # default only for new entries
+      $default_warehouse_id = $::instance_conf->get_warehouse_id;
+      $default_bin_id       = $::instance_conf->get_bin_id;
+    }
+    $form->{warehouse_id} ||= $default_warehouse_id || $form->{WAREHOUSES}->[$max -1]->{id};
+    $form->{bin_id}       ||= $default_bin_id       ||  $form->{WAREHOUSES}->[$max -1]->{BINS}->[0]->{id};
+  }
 
+  $form->{LANGUAGES}        = SL::DB::Manager::Language->get_all_sorted;
+  $form->{translations_map} = { map { ($_->{language_id} => $_) } @{ $form->{translations} || [] } };
 
   IC->retrieve_buchungsgruppen(\%myconfig, $form);
   @{ $form->{BUCHUNGSGRUPPEN} } = grep { $_->{id} eq $form->{buchungsgruppen_id} || ($form->{id} && $form->{orphaned}) || !$form->{id} } @{ $form->{BUCHUNGSGRUPPEN} };
 
-  # use JavaScript Calendar or not (yes!)
-  $form->{jsscript} = 1;
+  if (($form->{partnumber} ne '') && !SL::TransNumber->new(number => $form->{partnumber}, type => $form->{item}, id => $form->{id})->is_unique) {
+    flash('info', $::locale->text('This partnumber is not unique. You should change it.'));
+  }
 
   my $units = AM->retrieve_units(\%myconfig, $form);
   $form->{ALL_UNITS} = [ map +{ name => $_ }, sort { $units->{$a}{sortkey} <=> $units->{$b}{sortkey} } keys %$units ];
 
   $form->{defaults} = AM->get_defaults();
 
-  $form->{fokus} = "ic.partnumber";
-
   $form->{CUSTOM_VARIABLES} = CVar->get_custom_variables('module' => 'IC', 'trans_id' => $form->{id});
 
   CVar->render_inputs('variables' => $form->{CUSTOM_VARIABLES}, show_disabled_message => 1)
@@ -1553,6 +1634,9 @@ sub form_header {
   #                                                     BUCHUNGSGRUPPEN   => $form->{BUCHUNGSGRUPPEN},
   #                                                     payment_terms     => $form->{payment_terms},
   #                                                     all_partsgroup    => $form->{all_partsgroup}});
+
+  $form->{show_edit_buttons} = $main::auth->check_right($form->{login}, 'part_service_assembly_edit');
+
   print $form->parse_html_template('ic/form_header');
   $lxdebug->leave_sub();
 }
@@ -1560,7 +1644,7 @@ sub form_header {
 sub form_footer {
   $lxdebug->enter_sub();
 
-  $auth->assert('part_service_assembly_edit');
+  $auth->assert('part_service_assembly_details');
 
   print $form->parse_html_template('ic/form_footer');
 
@@ -1570,8 +1654,8 @@ sub form_footer {
 sub makemodel_row {
   $lxdebug->enter_sub();
   my ($numrows) = @_;
-
-  my @mm_data = grep { any { $_ ne '' } @$_{qw(make model)} } map +{ make => $form->{"make_$_"}, model => $form->{"model_$_"} }, 1 .. $numrows;
+  #hli
+  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;
   delete @{$form}{grep { m/^make_\d+/ || m/^model_\d+/ } keys %{ $form }};
   print $form->parse_html_template('ic/makemodel', { MM_DATA => [ @mm_data, {} ], mm_rows => scalar @mm_data + 1 });
 
@@ -1584,8 +1668,6 @@ sub assembly_row {
   my (@column_index);
   my ($nochange, $callback, $previousform, $linetotal, $line_purchase_price, $href);
 
-  our ($deliverydate); # ToDO: check if this indeed comes from global context
-
   @column_index = qw(runningnumber qty unit bom partnumber description partsgroup lastcost total);
 
   if ($form->{previousform}) {
@@ -1602,9 +1684,9 @@ sub assembly_row {
     map { delete $form->{$_} } qw(action header);
 
     # save form variables in a previousform variable
-    $previousform = $form->escape($form->escape(join '&', map {
-      sprintf "%s=%s", Q($_), /^listprice|lastcost|sellprice$/ ? $form->format_amount(\%myconfig, $form->{$_}) : $form->{$_}
-    } grep { ref $form->{$_} eq '' && $form->{$_} } grep { !/^select/ } sort keys %$form ));
+    my %form_to_save = map   { ($_ => m/^ (?: listprice | sellprice | lastcost ) $/x ? $form->format_amount(\%myconfig, $form->{$_}) : $form->{$_}) }
+                       keys %{ $form };
+    $previousform    = $::auth->save_form_in_session(form => \%form_to_save);
 
     $form->{callback} = $callback;
     $form->{assemblytotal} = 0;
@@ -1613,15 +1695,15 @@ sub assembly_row {
   }
 
   my %header = (
-   runningnumber => { text =>  $locale->text('No.'),              nowrap => 1, width => '5%'  },
-   qty           => { text =>  $locale->text('Qty'),              nowrap => 1, width => '10%' },
-   unit          => { text =>  $locale->text('Unit'),             nowrap => 1, width => '5%'  },
-   partnumber    => { text =>  $locale->text('Part Number'),      nowrap => 1, width => '20%' },
-   description   => { text =>  $locale->text('Part Description'), nowrap => 1, width => '50%' },
-   lastcost      => { text =>  $locale->text('Purchase Prices'),  nowrap => 1, width => '50%' },
-   total         => { text =>  $locale->text('Sale Prices'),      nowrap => 1,                },
-   bom           => { text =>  $locale->text('BOM'),                                          },
-   partsgroup    => { text =>  $locale->text('Group'),                                        },
+   runningnumber => { text =>  $locale->text('No.'),              nowrap => 1, width => '5%',  align => 'left',},
+   qty           => { text =>  $locale->text('Qty'),              nowrap => 1, width => '10%', align => 'left',},
+   unit          => { text =>  $locale->text('Unit'),             nowrap => 1, width => '5%',  align => 'left',},
+   partnumber    => { text =>  $locale->text('Part Number'),      nowrap => 1, width => '20%', align => 'left',},
+   description   => { text =>  $locale->text('Part Description'), nowrap => 1, width => '50%', align => 'left',},
+   lastcost      => { text =>  $locale->text('Purchase Prices'),  nowrap => 1, width => '50%', align => 'right',},
+   total         => { text =>  $locale->text('Sale Prices'),      nowrap => 1,                 align => 'right',},
+   bom           => { text =>  $locale->text('BOM'),                                           align => 'center',},
+   partsgroup    => { text =>  $locale->text('Group'),                                         align => 'left',},
   );
 
   my @ROWS;
@@ -1638,7 +1720,7 @@ sub assembly_row {
     $form->{"qty_$i"}    = $form->format_amount(\%myconfig, $form->{"qty_$i"});
     $linetotal           = $form->format_amount(\%myconfig, $linetotal, 2);
     $line_purchase_price = $form->format_amount(\%myconfig, $line_purchase_price, 2);
-    $href                = qq|$form->{script}?action=edit&id=$form->{"id_$i"}&rowcount=$i&previousform=$previousform|;
+    $href                = build_std_url("action=edit", qq|id=$form->{"id_$i"}|, "rowcount=$numrows", "currow=$i", "previousform=$previousform");
     map { $row{$_}{data} = "" } qw(qty unit partnumber description bom partsgroup runningnumber);
 
     # last row
@@ -1658,7 +1740,8 @@ sub assembly_row {
         $row{bom}{data}           = $form->{"bom_$i"} ? "x" : "&nbsp;";
         $row{qty}{align}          = 'right';
       } else {
-        $row{partnumber}{data}    = qq|<a href=$href>$form->{"partnumber_$i"}</a>|;
+        $row{partnumber}{data}    = qq|$form->{"partnumber_$i"}|;
+        $row{partnumber}{link}     = $href;
         $row{qty}{data}           = qq|<input name="qty_$i" size=5 value="$form->{"qty_$i"}">|;
         $row{runningnumber}{data} = qq|<input name="runningnumber_$i" size=3 value="$i">|;
         $row{bom}{data}   = sprintf qq|<input name="bom_$i" type=checkbox class=checkbox value=1 %s>|,
@@ -1666,14 +1749,18 @@ sub assembly_row {
       }
       push @row_hiddens,        qw(unit description partnumber partsgroup);
       $row{unit}{data}        = $form->{"unit_$i"};
-      $row{description}{data} = $form->{"description_$i"};
-      $row{partsgroup}{data}  = $form->{"partsgroup_$i"};
-      $row{bom}{align}        = 'center';
+      #Bei der Artikelbeschreibung und Warengruppe können Sonderzeichen verwendet
+      #werden, die den HTML Code stören. Daher sollen diese im Template escaped werden
+      #dies geschieht, wenn die Variable escape gesetzt ist
+      $row{description}{data}   = $form->{"description_$i"};
+      $row{description}{escape} = 1;
+      $row{partsgroup}{data}    = $form->{"partsgroup_$i"};
+      $row{partsgroup}{escape}  = 1;
+      $row{bom}{align}          = 'center';
     }
 
     $row{lastcost}{data}      = $line_purchase_price;
     $row{total}{data}         = $linetotal;
-    $row{deliverydate}{data}  = $deliverydate;
     $row{lastcost}{align}     = 'right';
     $row{total}{align}        = 'right';
     $row{deliverydate}{align} = 'right';
@@ -1692,8 +1779,22 @@ sub assembly_row {
 sub update {
   $lxdebug->enter_sub();
 
+  $auth->assert('part_service_assembly_edit');
+
   # parse pricegroups. and no, don't rely on check_form for this...
   map { $form->{"price_$_"} = $form->parse_amount(\%myconfig, $form->{"price_$_"}) } 1 .. $form->{price_rows};
+  $form->{$_} = $form->parse_amount(\%myconfig, $form->{$_}) for qw(sellprice listprice ve gv);
+
+  if ($form->{item} eq 'part') {
+    $form->{$_} = $form->parse_amount(\%myconfig, $form->{$_}) for qw(weight rop);
+  }
+
+  # same for makemodel lastcosts
+  # but parse_amount not necessary for assembly component lastcosts
+  unless ($form->{item} eq "assembly") {
+    map { $form->{"lastcost_$_"} = $form->parse_amount(\%myconfig, $form->{"lastcost_$_"}) } 1 .. $form->{"makemodel_rows"};
+    $form->{lastcost} = $form->parse_amount(\%myconfig, $form->{lastcost});
+  }
 
   if ($form->{item} eq "assembly") {
     my $i = $form->{assembly_rows};
@@ -1716,8 +1817,8 @@ sub update {
 
         if ($rows > 1) {
           $form->{makemodel_rows}--;
-          &select_item;
-          exit;
+          select_item(mode => 'IC');
+          ::end_of_request();
         } else {
           map { $form->{item_list}[$i]{$_} =~ s/\"/&quot;/g }
             qw(partnumber description unit partsgroup);
@@ -1752,7 +1853,7 @@ sub save {
 
   $auth->assert('part_service_assembly_edit');
 
-  my ($parts_id, %newform, $previousform, $amount, $callback);
+  my ($parts_id, %newform, $amount, $callback);
 
   # check if there is a part number - commented out, cause there is an automatic allocation of numbers
   # $form->isblank("partnumber", $locale->text(ucfirst $form->{item}." Part Number missing!"));
@@ -1772,8 +1873,12 @@ sub save {
   $form->error($locale->text('Description must not be empty!')) unless $form->{description};
   $form->error($locale->text('Partnumber must not be set to empty!')) if $form->{id} && !$form->{partnumber};
 
+  # undef warehouse_id if the empty value is selected
+  if ( ($form->{warehouse_id} == 0) && ($form->{bin_id} == 0) ) {
+    undef $form->{warehouse_id};
+    undef $form->{bin_id};
+  }
   # save part
-  $lxdebug->message($LXDebug::DEBUG1, "ic.pl: sellprice in save = $form->{sellprice}\n");
   if (IC->save(\%myconfig, \%$form) == 3) {
     $form->error($locale->text('Partnumber not unique!'));
   }
@@ -1781,7 +1886,7 @@ sub save {
   if(!exists $form->{addition}) {
     $form->{snumbers} = qq|partnumber_| . $form->{partnumber};
     $form->{addition} = "SAVED";
-    $form->save_history($form->dbconnect(\%myconfig));
+    $form->save_history;
   }
   # /saving the history
   $parts_id = $form->{id};
@@ -1793,20 +1898,14 @@ sub save {
     # save the new form variables before splitting previousform
     map { $newform{$_} = $form->{$_} } keys %$form;
 
-    $previousform = $form->unescape($form->{previousform});
-
     # don't trample on previous variables
     map { delete $form->{$_} } keys %newform;
 
     my $ic_cvar_configs = CVar->get_configs(module => 'IC');
     my @ic_cvar_fields  = map { "cvar_$_->{name}" } @{ $ic_cvar_configs };
 
-    # now take it apart and restore original values
-    foreach my $item (split /&/, $previousform) {
-      my ($key, $value) = split m/=/, $item, 2;
-      $value =~ s/%26/&/g;
-      $form->{$key} = $value;
-    }
+    # restore original values
+    $::auth->restore_form_from_session($newform{previousform}, form => $form);
     $form->{taxaccounts} = $newform{taxaccount2};
 
     if ($form->{item} eq 'assembly') {
@@ -1816,8 +1915,12 @@ sub save {
         qw(weight listprice sellprice rop);
 
       $form->{assembly_rows}--;
-      $i = $newform{rowcount};
-      $form->{"qty_$i"} = 1 unless ($form->{"qty_$i"});
+      if ($newform{currow}) {
+        $i = $newform{currow};
+      } else {
+        $i = $form->{assembly_rows};
+      }
+      $form->{"qty_$i"} = 1 unless ($form->{"qty_$i"} > 0);
 
       $form->{sellprice} -= $form->{"sellprice_$i"} * $form->{"qty_$i"};
       $form->{weight}    -= $form->{"weight_$i"} * $form->{"qty_$i"};
@@ -1834,7 +1937,7 @@ sub save {
 
       # set values for last invoice/order item
       $i = $form->{rowcount};
-      $form->{"qty_$i"} = 1 unless ($form->{"qty_$i"});
+      $form->{"qty_$i"} = 1 unless ($form->{"qty_$i"} > 0);
 
       map { $form->{"${_}_$i"} = $newform{$_} } qw(partnumber description bin unit listprice inventory_accno income_accno expense_accno sellprice lastcost price_factor_id);
       map { $form->{"ic_${_}_$i"} = $newform{$_} } @ic_cvar_fields;
@@ -1847,8 +1950,6 @@ sub save {
         $form->{"sellprice_$i"} /= $form->{exchangerate};
       }
 
-      $lxdebug->message($LXDebug::DEBUG1, qq|sellprice_$i in previousform 2 = | . $form->{"sellprice_$i"} . qq|\n|);
-
       map { $form->{"taxaccounts_$i"} .= "$_ " } split / /, $newform{taxaccount};
       chop $form->{"taxaccounts_$i"};
       foreach my $item (qw(description rate taxnumber)) {
@@ -1865,7 +1966,7 @@ sub save {
       $form->{creditremaining} -= $amount;
 
       # redo number formatting, because invoice parse them!
-      map { $form->{"${_}_$i"} = $form->format_amount(\%myconfig, $form->{"${_}_$i"}) } qw(weight listprice sellprice rop);
+      map { $form->{"${_}_$i"} = $form->format_amount(\%myconfig, $form->{"${_}_$i"}) } qw(weight listprice sellprice lastcost rop);
     }
 
     $form->{"id_$i"} = $parts_id;
@@ -1897,7 +1998,6 @@ sub save {
     }
     $form->{callback} = $callback;
   }
-  $lxdebug->message($LXDebug::DEBUG1, qq|ic.pl: sellprice_$i nach sub save = | . $form->{"sellprice_$i"} . qq|\n|);
 
   # redirect
   $form->redirect;
@@ -1914,7 +2014,7 @@ sub save_as_new {
   if(!exists $form->{addition}) {
     $form->{snumbers} = qq|partnumber_| . $form->{partnumber};
     $form->{addition} = "SAVED AS NEW";
-    $form->save_history($form->dbconnect(\%myconfig));
+    $form->save_history;
   }
   # /saving the history
   $form->{id} = 0;
@@ -1935,7 +2035,7 @@ sub delete {
   if(!exists $form->{addition}) {
     $form->{snumbers} = qq|partnumber_| . $form->{partnumber};
     $form->{addition} = "DELETED";
-    $form->save_history($form->dbconnect(\%myconfig));
+    $form->save_history;
   }
   # /saving the history
   my $rc = IC->delete(\%myconfig, \%$form);
@@ -1950,7 +2050,7 @@ sub delete {
 sub price_row {
   $lxdebug->enter_sub();
 
-  $auth->assert('part_service_assembly_edit');
+  $auth->assert('part_service_assembly_details');
 
   my ($numrows) = @_;
 
@@ -1965,50 +2065,6 @@ sub price_row {
   $lxdebug->leave_sub();
 }
 
-sub parts_language_selection {
-  $lxdebug->enter_sub();
-
-  $auth->assert('part_service_assembly_edit');
-
-  our ($onload, $callback);
-
-  my $languages = IC->retrieve_languages(\%myconfig, $form);
-
-  if ($form->{language_values} ne "") {
-    foreach my $item (split(/---\+\+\+---/, $form->{language_values})) {
-      my ($language_id, $translation, $longdescription) = split(/--\+\+--/, $item);
-
-      foreach my $language (@{ $languages }) {
-        next unless ($language->{id} == $language_id);
-
-        $language->{translation}     = $translation;
-        $language->{longdescription} = $longdescription;
-        last;
-      }
-    }
-  }
-
-  my @header_sort = qw(name longdescription);
-  my %header_title = ( "name" => $locale->text("Name"),
-                       "longdescription" => $locale->text("Long Description"),
-                       );
-
-  my @header =
-    map(+{ "column_title" => $header_title{$_},
-           "column" => $_,
-           "callback" => $callback,
-         },
-        @header_sort);
-
-  $form->{"title"} = $locale->text("Language Values");
-  $form->header();
-  print $form->parse_html_template("ic/parts_language_selection", { "HEADER"    => \@header,
-                                                                    "LANGUAGES" => $languages,
-                                                                    "onload"    => $onload });
-
-  $lxdebug->leave_sub();
-}
-
 sub ajax_autocomplete {
   $main::lxdebug->enter_sub();
 
@@ -2028,4 +2084,25 @@ sub ajax_autocomplete {
   $main::lxdebug->leave_sub();
 }
 
+sub back_to_record {
+  _check_io_auth();
+
+
+  delete @{$::form}{qw(action action_add action_back_to_record back_sub description item notes partnumber sellprice taxaccount2 unit vc)};
+
+  $::auth->restore_form_from_session($::form->{previousform}, clobber => 1);
+  $::form->{rowcount}--;
+  $::form->{action}   = 'display_form';
+  $::form->{callback} = $::form->{script} . '?' . join('&', map { $::form->escape($_) . '=' . $::form->escape($::form->{$_}) } sort keys %{ $::form });
+  $::form->redirect;
+}
+
 sub continue { call_sub($form->{"nextsub"}); }
+
+sub dispatcher {
+  my $action = first { $::form->{"action_${_}"} } qw(add back_to_record);
+  $::form->error($::locale->text('No action defined.')) unless $action;
+
+  $::form->{dispatched_action} = $action;
+  call_sub($action);
+}