XML basiertes Menue, siehe Bug #771
[kivitendo-erp.git] / bin / mozilla / ic.pl
index 3d9d6ef..fc0da26 100644 (file)
 #======================================================================
 #$locale->text('ea');
 
+use POSIX qw(strftime);
+
 use SL::IC;
+use SL::ReportGenerator;
 
 #use SL::PE;
 
-use strict;
+use strict;
 #use warnings;
 
+# global imports
+our ($form, $locale, %myconfig, $lxdebug);
+
 require "bin/mozilla/io.pl";
+require "bin/mozilla/invoice_io.pl";
 require "bin/mozilla/common.pl";
+require "bin/mozilla/reportgenerator.pl";
 
 1;
 
-# global imports
-my $form     = $main::form;
-my $locale   = $main::locale;
-my %myconfig = %main::myconfig;
-my $lxdebug  = $main::lxdebug;
-
 # end of main
 
 sub add {
@@ -108,44 +110,33 @@ sub search {
      |;
 
     #write Trigger
-    $jsscript =
-      Form->write_trigger(\%myconfig, "2", "transdatefrom", "BL", "trigger1",
-                          "transdateto", "BL", "trigger2");
+    $jsscript = Form->write_trigger(\%myconfig, "2", "transdatefrom", "BL", "trigger1", "transdateto", "BL", "trigger2");
   } else {
 
     # without JavaScript Calendar
-    $button1 = qq|
-                              <td><input name=transdatefrom id=transdatefrom size=11 title="$myconfig{dateformat}"></td>|;
-    $button2 = qq|
-                              <td><input name=transdateto id=transdateto size=11 title="$myconfig{dateformat}"></td>|;
+    $button1 = qq| <td><input name=transdatefrom id=transdatefrom size=11 title="$myconfig{dateformat}"></td>|;
+    $button2 = qq| <td><input name=transdateto id=transdateto size=11 title="$myconfig{dateformat}"></td>|;
   }
 
   unless ($form->{searchitems} eq 'service') {
 
-    $onhand = qq|
-            <input name=itemstatus class=radio type=radio value=onhand>&nbsp;|
-      . $locale->text('On Hand') . qq|
-            <input name=itemstatus class=radio type=radio value=short>&nbsp;|
-      . $locale->text('Short') . qq|
+    $onhand = qq| <input name=itemstatus class=radio type=radio value=onhand>&nbsp;| . $locale->text('On Hand') . qq|
+                  <input name=itemstatus class=radio type=radio value=short>&nbsp;| . $locale->text('Short') . qq|
 |;
 
     $makemodel = qq|
         <tr>
-          <th align=right nowrap>| . $locale->text('Make') . qq|</th>
-          <td><input name=make size=20></td>
-          <th align=right nowrap>| . $locale->text('Model') . qq|</th>
-          <td><input name=model size=20></td>
+          <th align=right nowrap>| . $locale->text('Make') . qq|</th> <td><input name=make size=20></td>
+          <th align=right nowrap>| . $locale->text('Model') . qq|</th> <td><input name=model size=20></td>
         </tr>
 |;
 
     $serialnumber = qq|
-          <th align=right nowrap>| . $locale->text('Serial Number') . qq|</th>
-          <td><input name=serialnumber size=20></td>
+          <th align=right nowrap>| . $locale->text('Serial Number') . qq|</th> <td><input name=serialnumber size=20></td>
 |;
 
     $l_serialnumber = qq|
-        <td><input name=l_serialnumber class=checkbox type=checkbox value=Y>&nbsp;|
-      . $locale->text('Serial Number') . qq|</td>
+        <td><input name=l_serialnumber class=checkbox type=checkbox value=Y>&nbsp;| . $locale->text('Serial Number') . qq|</td>
 |;
 
   }
@@ -637,9 +628,7 @@ sub update_prices {
 sub choice {
   $lxdebug->enter_sub();
 
-  my $j       = $main::j;
-  my $lastndx = $main::lastndx;
-
+  our ($j, $lastndx);
   my ($totop100);
 
   $form->{title} = $locale->text('Top 100 hinzufuegen');
@@ -762,7 +751,8 @@ sub choice {
 sub list {
   $lxdebug->enter_sub();
 
-  my $lastndx = $main::lastndx;
+  our ($lastndx);
+  our ($partnumber, $description, $unit, $sellprice, $soldtotal);
 
   my @sortorders = ("", "partnumber", "description", "all");
   my $sortorder = $sortorders[($form->{description} ? 2 : 0) + ($form->{partnumber} ? 1 : 0)];
@@ -853,12 +843,11 @@ sub list {
   if (($form->{ndxs_counter}) > 0) {
     for ($i = 1; ($i < $form->{ndxs_counter} + 1); $i++) {
 
-      # ToDO: does this really make sense?
-      $main::partnumber  = $form->{"totop100_partnumber_$i"};
-      $main::description = $form->{"totop100_description_$i"};
-      $main::unit        = $form->{"totop100_unit_$i"};
-      $main::sellprice   = $form->{"totop100_sellprice_$i"};
-      $main::soldtotal   = $form->{"totop100_soldtotal_$i"};
+      $partnumber  = $form->{"totop100_partnumber_$i"};
+      $description = $form->{"totop100_description_$i"};
+      $unit        = $form->{"totop100_unit_$i"};
+      $sellprice   = $form->{"totop100_sellprice_$i"};
+      $soldtotal   = $form->{"totop100_soldtotal_$i"};
 
       $totop100 .= qq|
 <input type=hidden name=totop100_partnumber_$i value=$form->{"totop100_partnumber_$i"}>
@@ -1514,12 +1503,12 @@ sub generate_report {
   $lxdebug->enter_sub();
 
   my ($revers, $lastsort, $description);
-  my (@column_index, %column_header, %column_data, @columns, @options, @callbacks);
-  my ($totalsellprice, $totallastcost, $totallistprice, $subtotalonhand, $subtotalsellprice, $subtotallastcost, $subtotallistprice);
-  my ($colspan, $sameitem, $onhand, $align);
 
-  $revers   = $form->{revers};
-  $lastsort = $form->{lastsort};
+  $form->{title} = (ucfirst $form->{searchitems}) . "s";
+  $form->{title} = $locale->text($form->{title});
+
+  my $revers     = $form->{revers};
+  my $lastsort   = $form->{lastsort};
 
   # sorting and direction of sorting
   # ToDO: change this to the simpler field+direction method
@@ -1550,7 +1539,7 @@ sub generate_report {
   $form->{ledgerchecks} = 'Y' if (   $form->{bought} || $form->{sold} || $form->{onorder}
                                   || $form->{ordered} || $form->{rfq} || $form->{quoted});
 
-  # if something should be aktivated if something else is active, enter it here
+  # if something should be activated if something else is active, enter it here
   my %dependencies = (
     onhand       => [ qw(l_onhand) ],
     short        => [ qw(l_onhand) ],
@@ -1591,34 +1580,22 @@ sub generate_report {
     l_soldtotal   => $locale->text('soldtotal'),
   );
 
-  # this local subfunction generates a callback token from the input key.
-  # easy to join into a callback later
-  sub callback_token { 
-    map { /\w+$/; return "&$&=$form->{$&}" } @_;
-  }
-
   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 
-                           drawing microfiche l_soldtotal l_deliverydate transdatefrom transdateto);
-  my $callback           = "$form->{script}?action=generate_report";
-  map { $callback .= "&$_=" . $form->escape($form->{$_}) } qw(login password searchitems itemstatus bom l_linetotal title);
-    
+  my @callback_keys   = qw(onorder ordered rfq quoted bought sold partnumber partsgroup serialnumber description make model
+                           drawing microfiche l_soldtotal l_deliverydate transdatefrom transdateto ean);
+
   # calculate dependencies
-  for (@itemstatus_keys, @callback_keys) { 
+  for (@itemstatus_keys, @callback_keys) {
     next if ($form->{itemstatus} ne $_ && !$form->{$_});
     map { $form->{$_} = 'Y' } @{ $dependencies{$_} } if $dependencies{$_};
   }
 
   # generate callback and optionstrings
+  my @options;
   for my  $key (@itemstatus_keys, @callback_keys) { 
     next if ($form->{itemstatus} ne $key && !$form->{$key});
     push @options, $optiontexts{$key};
-    push @callbacks, callback_token($key) if grep { $_ eq $key } @callback_keys;;
   }
-  my $option    = $locale->text('Options') . ': ' . join(', ', grep $_, @options) . '<br>';
-  $callback .= join '', grep $_, @callbacks;
-
-  $lxdebug->message(0, $callback);
 
   IC->all_parts(\%myconfig, \%$form);
 
@@ -1630,11 +1607,6 @@ sub generate_report {
     $description =~ s/\n/<br>/g;
   }
 
-  @columns = $form->sort_columns(
-    qw(partnumber description partsgroup bin onhand rop unit listprice linetotallistprice sellprice linetotalsellprice 
-       lastcost linetotallastcost priceupdate weight image drawing microfiche invnumber ordnumber quonumber name serialnumber soldtotal deliverydate)
-  );
-
   if ($form->{l_linetotal}) {
     $form->{l_onhand} = "Y";
     $form->{l_linetotalsellprice} = "Y" if $form->{l_sellprice};
@@ -1668,330 +1640,179 @@ sub generate_report {
     }
   }
 
-  $form->{l_lastcost} = ""
-    if ($form->{searchitems} eq 'assembly' && !$form->{bom});
-
-  foreach my $item (@columns) {
-    if ($form->{"l_$item"} eq "Y") {
-      push @column_index, $item;
-
-      # add column to callback
-      $callback .= "&l_$item=Y";
-    }
-  }
-
-  if ($form->{l_subtotal} eq 'Y') {
-    $callback .= "&l_subtotal=Y";
-  }
-  $column_header{partnumber} =
-    qq|<th nowrap><a class=listheading href=$callback&sort=partnumber&revers=$form->{revers}&lastsort=$form->{lastsort}>|
-    . $locale->text('Part Number')
-    . qq|</a></th>|;
-  $column_header{description} =
-    qq|<th nowrap><a class=listheading href=$callback&sort=description&revers=$form->{revers}&lastsort=$form->{lastsort}>|
-    . $locale->text('Part Description')
-    . qq|</a></th>|;
-  $column_header{partsgroup} =
-      qq|<th nowrap><a class=listheading href=$callback&sort=partsgroup>|
-    . $locale->text('Group')
-    . qq|</a></th>|;
-  $column_header{bin} =
-      qq|<th><a class=listheading href=$callback&sort=bin>|
-    . $locale->text('Bin')
-    . qq|</a></th>|;
-  $column_header{priceupdate} =
-      qq|<th nowrap><a class=listheading href=$callback&sort=priceupdate>|
-    . $locale->text('Updated')
-    . qq|</a></th>|;
-  $column_header{onhand} =
-    qq|<th nowrap><a  class=listheading href=$callback&sort=onhand&revers=$form->{revers}&lastsort=$form->{lastsort}>|
-    . $locale->text('Qty')
-    . qq|</th>|;
-  $column_header{unit} =
-    qq|<th class=listheading nowrap>| . $locale->text('Unit') . qq|</th>|;
-  $column_header{listprice} =
-      qq|<th class=listheading nowrap>|
-    . $locale->text('List Price')
-    . qq|</th>|;
-  $column_header{lastcost} =
-    qq|<th class=listheading nowrap>| . $locale->text('Last Cost') . qq|</th>|;
-  $column_header{rop} =
-    qq|<th class=listheading nowrap>| . $locale->text('ROP') . qq|</th>|;
-  $column_header{weight} =
-    qq|<th class=listheading nowrap>| . $locale->text('Weight') . qq|</th>|;
-
-  $column_header{invnumber} =
-      qq|<th nowrap><a class=listheading href=$callback&sort=invnumber>|
-    . $locale->text('Invoice Number')
-    . qq|</a></th>|;
-  $column_header{ordnumber} =
-      qq|<th nowrap><a class=listheading href=$callback&sort=ordnumber>|
-    . $locale->text('Order Number')
-    . qq|</a></th>|;
-  $column_header{quonumber} =
-      qq|<th nowrap><a class=listheading href=$callback&sort=quonumber>|
-    . $locale->text('Quotation')
-    . qq|</a></th>|;
-
-  $column_header{name} =
-      qq|<th nowrap><a class=listheading href=$callback&sort=name>|
-    . $locale->text('Name')
-    . qq|</a></th>|;
-
-  $column_header{sellprice} =
-      qq|<th class=listheading nowrap>|
-    . $locale->text('Sell Price')
-    . qq|</th>|;
-  $column_header{linetotalsellprice} =
-    qq|<th class=listheading nowrap>| . $locale->text('Extended') . qq|</th>|;
-  $column_header{linetotallastcost} =
-    qq|<th class=listheading nowrap>| . $locale->text('Extended') . qq|</th>|;
-  $column_header{linetotallistprice} =
-    qq|<th class=listheading nowrap>| . $locale->text('Extended') . qq|</th>|;
-
-  $column_header{image} =
-    qq|<th class=listheading nowrap>| . $locale->text('Image') . qq|</a></th>|;
-  $column_header{drawing} =
-      qq|<th nowrap><a class=listheading href=$callback&sort=drawing>|
-    . $locale->text('Drawing')
-    . qq|</a></th>|;
-  $column_header{microfiche} =
-      qq|<th nowrap><a class=listheading href=$callback&sort=microfiche>|
-    . $locale->text('Microfiche')
-    . qq|</a></th>|;
+  $form->{l_lastcost} = "" if ($form->{searchitems} eq 'assembly' && !$form->{bom});
+
+  my @columns =
+    qw(partnumber description partsgroup bin onhand rop unit listprice linetotallistprice sellprice linetotalsellprice lastcost linetotallastcost
+       priceupdate weight image drawing microfiche invnumber ordnumber quonumber name serialnumber soldtotal deliverydate);
+
+  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'), },
+    'unit'               => { 'text' => $locale->text('Unit'), },
+    'weight'             => { 'text' => $locale->text('Weight'), },
+  );
 
-  $column_header{serialnumber} =
-      qq|<th nowrap><a class=listheading href=$callback&sort=serialnumber>|
-    . $locale->text('Serial Number')
-    . qq|</a></th>|;
-  $column_header{soldtotal} =
-    qq|<th nowrap><a class=listheading href=$callback&sort=soldtotal&revers=$form->{revers}&lastsort=$form->{lastsort}>|
-    . $locale->text('soldtotal')
-    . qq|</a></th>|;
+  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);
 
-  $column_header{deliverydate} =
-    qq|<th nowrap><a class=listheading href=$callback&sort=deliverydate&revers=$form->{revers}&lastsort=$form->{lastsort}>|
-    . $locale->text('deliverydate')
-    . qq|</a></th>|;
+  my @hidden_variables = (qw(l_subtotal l_linetotal searchitems itemstatus bom), @itemstatus_keys, @callback_keys, map { "l_$_" } @columns);
+  my $callback         = build_std_url('action=generate_report', grep { $form->{$_} } @hidden_variables);
 
-  $form->header;
-  $colspan = $#column_index + 1;
+  my @sort_full        = qw(partnumber description onhand soldtotal deliverydate);
+  my @sort_no_revers   = qw(partsgroup bin priceupdate invnumber ordnumber quonumber name image drawing serialnumber);
 
-  print qq|
-<body>
+  foreach my $col (@sort_full) {
+    $column_defs{$col}->{link} = join '&', $callback, "sort=$col", map { "$_=" . E($form->{$_}) } qw(revers lastsort);
+  }
+  map { $column_defs{$_}->{link} = "${callback}&sort=$_" } @sort_no_revers;
 
-<table width=100%>
-  <tr>
-    <th class=listtop colspan=$colspan>$form->{title}</th>
-  </tr>
-  <tr height="5"></tr>
+  # add order to callback
+  $form->{callback} = join '&', ($callback, map { "${_}=" . E($form->{$_}) } qw(sort revers));
 
-  <tr><td colspan=$colspan>$option</td></tr>
+  my $report = SL::ReportGenerator->new(\%myconfig, $form);
 
-  <tr class=listheading>
-|;
+  my %attachment_basenames = (
+    'part'     => $locale->text('part_list'),
+    'service'  => $locale->text('service_list'),
+    'assembly' => $locale->text('assembly_list'),
+  );
 
-  map { print "\n$column_header{$_}" } @column_index;
+  $report->set_options('top_info_text'         => $locale->text('Options') . ': ' . join(', ', grep $_, @options),
+                       'raw_bottom_info_text'  => $form->parse_html_template2('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();
 
-  print qq|
-  </tr>
-  |;
+  $report->set_columns(%column_defs);
+  $report->set_column_order(@columns);
 
-  # add order to callback
-  $form->{callback} = $callback .= "&sort=$form->{sort}";
+  $report->set_export_options('generate_report', @hidden_variables, qw(sort revers));
 
-  # escape callback for href
-  $callback = $form->escape($callback);
+  $report->set_sort_indicator($form->{sort}, $form->{revers} ? 0 : 1);
 
-  if (@{ $form->{parts} }) {
-    $sameitem = $form->{parts}->[0]->{ $form->{sort} };
-  }
+  my @subtotal_columns = qw(sellprice listprice lastcost);
+  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} });
 
+  # postprocess parts
   foreach my $ref (@{ $form->{parts} }) {
-    my $i = 0;
 
-    if ($form->{l_subtotal} eq 'Y' && !$ref->{assemblyitem}) {
-      if ($sameitem ne $ref->{ $form->{sort} }) {
-        &parts_subtotal;
-        $sameitem = $ref->{ $form->{sort} };
-      }
-    }
+    # fresh row, for inserting later
+    my $row = { map { $_ => { 'data' => $ref->{$_} } } @columns };
 
-    $ref->{exchangerate} = 1 unless $ref->{exchangerate};
-    $ref->{sellprice} *= $ref->{exchangerate};
-    $ref->{listprice} *= $ref->{exchangerate};
-    $ref->{lastcost}  *= $ref->{exchangerate};
+    $ref->{exchangerate} ||= 1;
+    $ref->{price_factor} ||= 1;
+    $ref->{sellprice}     *= $ref->{exchangerate} / $ref->{price_factor};
+    $ref->{listprice}     *= $ref->{exchangerate} / $ref->{price_factor};
+    $ref->{lastcost}      *= $ref->{exchangerate} / $ref->{price_factor};
 
     # use this for assemblies
-    $onhand = $ref->{onhand};
+    my $onhand = $ref->{onhand};
 
-    $align = "left";
     if ($ref->{assemblyitem}) {
-      $align = "right";
-      $onhand = 0 if ($form->{sold});
+      $row->{partnumber}{align}   = 'right';
+      $row->{onhand}{data}        = 0;
+      $onhand                     = 0 if ($form->{sold});
     }
 
-    $ref->{description} =~ s/
-/<br>/g;
-
-    $column_data{partnumber} =
-      "<td align=$align><a href=$form->{script}?action=edit&id=$ref->{id}&login=$form->{login}&password=$form->{password}&callback=$callback>$ref->{partnumber}&nbsp;</a></td>";
-    $column_data{description} = "<td><a href=$form->{script}?action=edit&id=$ref->{id}&login=$form->{login}&password=$form->{password}&callback=$callback>$ref->{description}&nbsp;</a></td>";
-    $column_data{partsgroup}  = "<td>$ref->{partsgroup}&nbsp;</td>";
+    my $edit_link               = build_std_url('action=edit', 'id=' . E($ref->{id}), 'callback');
+    $row->{partnumber}->{link}  = $edit_link;
+    $row->{description}->{link} = $edit_link;
 
-    $column_data{onhand} =
-        "<td align=right>"
-      . $form->format_amount(\%myconfig, $ref->{onhand})
-      . "</td>";
-    $column_data{sellprice} =
-        "<td align=right>"
-      . $form->format_amount(\%myconfig, $ref->{sellprice}, -2)
-      . "</td>";
-    $column_data{listprice} =
-        "<td align=right>"
-      . $form->format_amount(\%myconfig, $ref->{listprice}, -2)
-      . "</td>";
-    $column_data{lastcost} =
-        "<td align=right>"
-      . $form->format_amount(\%myconfig, $ref->{lastcost}, -2)
-      . "</td>";
+    foreach (qw(sellprice listprice lastcost)) {
+      $row->{$_}{data}            = $form->format_amount(\%myconfig, $ref->{$_}, -2);
+      $row->{"linetotal$_"}{data} = $form->format_amount(\%myconfig, $ref->{onhand} * $ref->{$_}, 2);
+    }
 
-    $column_data{linetotalsellprice} = "<td align=right>"
-      . $form->format_amount(\%myconfig, $ref->{onhand} * $ref->{sellprice}, 2)
-      . "</td>";
-    $column_data{linetotallastcost} = "<td align=right>"
-      . $form->format_amount(\%myconfig, $ref->{onhand} * $ref->{lastcost}, 2)
-      . "</td>";
-    $column_data{linetotallistprice} = "<td align=right>"
-      . $form->format_amount(\%myconfig, $ref->{onhand} * $ref->{listprice}, 2)
-      . "</td>";
+    map { $row->{$_}{data} = $form->format_amount(\%myconfig, $ref->{$_}); } qw(onhand rop weight soldtotal);
 
     if (!$ref->{assemblyitem}) {
-      $totalsellprice += $onhand * $ref->{sellprice};
-      $totallastcost  += $onhand * $ref->{lastcost};
-      $totallistprice += $onhand * $ref->{listprice};
+      foreach my $col (@subtotal_columns) {
+        $totals{$col}    += $onhand * $ref->{$col};
+        $subtotals{$col} += $onhand * $ref->{$col};
+      }
 
-      $subtotalonhand    += $onhand;
-      $subtotalsellprice += $onhand * $ref->{sellprice};
-      $subtotallastcost  += $onhand * $ref->{lastcost};
-      $subtotallistprice += $onhand * $ref->{listprice};
+      $subtotals{onhand} += $onhand;
     }
 
-    $column_data{rop} =
-      "<td align=right>"
-      . $form->format_amount(\%myconfig, $ref->{rop}) . "</td>";
-    $column_data{weight} =
-        "<td align=right>"
-      . $form->format_amount(\%myconfig, $ref->{weight})
-      . "</td>";
-    $column_data{unit}        = "<td>$ref->{unit}&nbsp;</td>";
-    $column_data{bin}         = "<td>$ref->{bin}&nbsp;</td>";
-    $column_data{priceupdate} = "<td>$ref->{priceupdate}&nbsp;</td>";
-
-    $column_data{invnumber} =
-      ($ref->{module} ne 'oe')
-      ? "<td><a href=$ref->{module}.pl?action=edit&type=invoice&id=$ref->{trans_id}&login=$form->{login}&password=$form->{password}&callback=$callback>$ref->{invnumber}</a></td>"
-      : "<td>$ref->{invnumber}</td>";
-    $column_data{ordnumber} =
-      ($ref->{module} eq 'oe')
-      ? "<td><a href=$ref->{module}.pl?action=edit&type=$ref->{type}&id=$ref->{trans_id}&login=$form->{login}&password=$form->{password}&callback=$callback>$ref->{ordnumber}</a></td>"
-      : "<td>$ref->{ordnumber}</td>";
-    $column_data{quonumber} =
-      ($ref->{module} eq 'oe' && !$ref->{ordnumber})
-      ? "<td><a href=$ref->{module}.pl?action=edit&type=$ref->{type}&id=$ref->{trans_id}&login=$form->{login}&password=$form->{password}&callback=$callback>$ref->{quonumber}</a></td>"
-      : "<td>$ref->{quonumber}</td>";
+    # set module stuff
+    if ($ref->{module} eq 'oe') {
+      my $edit_oe_link = build_std_url("script=oe.pl", 'action=edit', 'type=' . E($ref->{type}), 'id=' . E($ref->{trans_id}), 'callback');
+      $row->{ordnumber}{link} = $edit_oe_link;
+      $row->{quonumber}{link} = $edit_oe_link if (!$ref->{ordnumber});
 
-    $column_data{name} = "<td>$ref->{name}</td>";
+    } else {
+      $row->{invnumber}{link} = build_std_url("script=$ref->{module}.pl", 'action=edit', 'type=invoice', 'id=' . E($ref->{trans_id}), 'callback');
+    }
 
-    $column_data{image} =
-      ($ref->{image})
-      ? "<td><a href=$ref->{image}><img src=$ref->{image} height=32 border=0></a></td>"
-      : "<td>&nbsp;</td>";
-    $column_data{drawing} =
-      ($ref->{drawing})
-      ? "<td><a href=$ref->{drawing}>$ref->{drawing}</a></td>"
-      : "<td>&nbsp;</td>";
-    $column_data{microfiche} =
-      ($ref->{microfiche})
-      ? "<td><a href=$ref->{microfiche}>$ref->{microfiche}</a></td>"
-      : "<td>&nbsp;</td>";
+    # set properties of images
+    if ($ref->{image} && (lc $report->{options}->{output_format} eq 'html')) {
+      $row->{image}{data}     = '';
+      $row->{image}{raw_data} = '<a href="' . H($ref->{image}) . '"><img src="' . H($ref->{image}) . '" height="32" border="0"></a>';
+    }
+    map { $row->{$_}{link} = $ref->{$_} } qw(drawing microfiche);
 
-    $column_data{serialnumber} = "<td>$ref->{serialnumber}</td>";
+    $report->add_data($row);
 
-    $column_data{soldtotal} =
-        "<td align=right>"
-      . $form->format_amount(\%myconfig, $ref->{soldtotal})
-      . "</td>";
+    my $next_ref = $form->{parts}[$idx + 1];
 
-    $column_data{deliverydate} = "<td>$ref->{deliverydate}</td>";
+    # insert subtotal rows
+    if (($form->{l_subtotal} eq 'Y') &&
+        (!$next_ref ||
+         (!$next_ref->{assemblyitem} && ($same_item ne $next_ref->{ $form->{sort} })))) {
+      my $row = { map { $_ => { 'class' => 'listsubtotal', } } @columns };
 
-    $i++;
-    $i %= 2;
-    print "<tr class=listrow$i>";
+      if (($form->{searchitems} ne 'assembly') || !$form->{bom}) {
+        $row->{onhand}->{data} = $form->format_amount(\%myconfig, $subtotals{onhand});
+      }
 
-    map { print "\n$column_data{$_}" } @column_index;
+      map { $row->{"linetotal$_"}->{data} = $form->format_amount(\%myconfig, $subtotals{$_}, 2) } @subtotal_columns;
+      map { $subtotals{$_} = 0 } ('onhand', @subtotal_columns);
 
-    print qq|
-    </tr>
-|;
+      $report->add_data($row);
 
-  }
+      $same_item = $next_ref->{ $form->{sort} };
+    }
 
-  if ($form->{l_subtotal} eq 'Y') {
-    &parts_subtotal;
+    $idx++;
   }
 
   if ($form->{"l_linetotal"}) {
-    map { $column_data{$_} = "<td>&nbsp;</td>" } @column_index;
-    $column_data{linetotalsellprice} =
-        "<th class=listtotal align=right>"
-      . $form->format_amount(\%myconfig, $totalsellprice, 2)
-      . "</th>";
-    $column_data{linetotallastcost} =
-        "<th class=listtotal align=right>"
-      . $form->format_amount(\%myconfig, $totallastcost, 2)
-      . "</th>";
-    $column_data{linetotallistprice} =
-        "<th class=listtotal align=right>"
-      . $form->format_amount(\%myconfig, $totallistprice, 2)
-      . "</th>";
+    my $row = { map { $_ => { 'class' => 'listtotal', } } @columns };
 
-    print "<tr class=listtotal>";
-
-    map { print "\n$column_data{$_}" } @column_index;
+    map { $row->{"linetotal$_"}->{data} = $form->format_amount(\%myconfig, $totals{$_}, 2) } @subtotal_columns;
 
-    print qq|</tr>
-    |;
+    $report->add_separator();
+    $report->add_data($row);
   }
 
-  print qq|
-  <tr><td colspan=$colspan><hr size=3 noshade></td></tr>
-</table>
-
-|;
-
-  print qq|
-
-<br>
-
-<form method=post action=$form->{script}>
-
-<input name=callback type=hidden value="$form->{callback}">
-
-<input type=hidden name=item value=$form->{searchitems}>
-
-<input type=hidden name=login value=$form->{login}>
-<input type=hidden name=password value=$form->{password}>|;
-
-  print qq|
-  <input class=submit type=submit name=action value="|
-    . $locale->text('Add') . qq|">
-
-  </form>
-
-</body>
-</html>
-|;
+  $report->generate_with_headers();
 
   $lxdebug->leave_sub();
 }    #end generate_report
@@ -2000,15 +1821,8 @@ sub parts_subtotal {
   $lxdebug->enter_sub();
   
   # imports
-  my %column_data       = $main::column_data;
-  my @column_index      = $main::column_index;
-  my $subtotalonhand    = $main::subtotalonhand;
-  my $totalsellprice    = $main::totalsellprice;
-  my $totallastcost     = $main::totallastcost;
-  my $totallistprice    = $main::totallistprice;
-  my $subtotalsellprice = $main::subtotalsellprice;
-  my $subtotallastcost  = $main::subtotallastcost;
-  my $subtotallistprice = $main::subtotallistprice;
+  our (%column_data, @column_index);
+  our ($subtotalonhand, $totalsellprice, $totallastcost, $totallistprice, $subtotalsellprice, $subtotallastcost, $subtotallistprice);
 
   map { $column_data{$_} = "<td>&nbsp;</td>" } @column_index;
   $subtotalonhand = 0 if ($form->{searchitems} eq 'assembly' && $form->{bom});
@@ -2086,7 +1900,7 @@ sub link_part {
 
       # if this is a tax field
       if ($key =~ /IC_tax/) {
-        if ($key =~ /$item/) {
+        if ($key =~ /\Q$item\E/) {
           $form->{taxaccounts} .= "$ref->{accno} ";
           $form->{"IC_tax_$ref->{accno}_description"} =
             "$ref->{accno}--$ref->{description}";
@@ -2161,8 +1975,9 @@ sub form_header {
 
   my ($payment, $rows, $notes, $description, $ean, $buchungsgruppe, $partsgroup, $group, $tax, $lastcost, $eur, $linkaccounts, $weight, $n, $rop, $bin, $vegv);
   my ($notdiscountableok, $notdiscountable);
-  my ($formel, $imagelinks, $obsolete, $shopok, $shop);
+  my ($formula, $formula_label, $imagelinks, $obsolete, $shopok, $shop);
 
+  $form->get_lists('price_factors' => 'ALL_PRICE_FACTORS');
 
   map({ $form->{$_} = $form->format_amount(\%myconfig, $form->{$_}, -2) }
       qw(sellprice listprice lastcost gv));
@@ -2421,8 +2236,11 @@ sub form_header {
              </tr>
 |;
 
-  $formel =
-    qq|<ilayer><layer  onmouseover="this.T_STICKY=true;this.T_STATIC=true;return escape('| . $locale->text('The formula needs the following syntax:<br>For regular article:<br>Variablename= Variable Unit;<br>Variablename2= Variable2 Unit2;<br>...<br>###<br>Variable + ( Variable2 / Variable )<br><b>Please be beware of the spaces in the formula</b><br>') . qq|')"><textarea name=formel rows=4 cols=30 wrap=soft>$form->{formel}</textarea></layer><ilayer>|;
+    $formula =
+      qq|<ilayer><layer  onmouseover="this.T_STICKY=true;this.T_STATIC=true;return escape('| . $locale->text('The formula needs the following syntax:<br>For regular article:<br>Variablename= Variable Unit;<br>Variablename2= Variable2 Unit2;<br>...<br>###<br>Variable + ( Variable2 / Variable )<br><b>Please be beware of the spaces in the formula</b><br>') . qq|')"><textarea name=formel rows=4 cols=30 wrap=soft>$form->{formel}</textarea></layer><ilayer>|;
+
+    $formula_label = $locale->text('Formula');
+
     $imagelinks = qq|
   <tr>
     <td>
@@ -2500,6 +2318,22 @@ sub form_header {
     $unit_select .= AM->unit_select_html($units, "unit", $form->{"unit"});
   }
 
+  my $price_factor;
+  if (0 < scalar @{ $form->{ALL_PRICE_FACTORS} }) {
+    my @values = ('', map { $_->{id}                      } @{ $form->{ALL_PRICE_FACTORS} });
+    my %labels =      map { $_->{id} => $_->{description} } @{ $form->{ALL_PRICE_FACTORS} };
+
+    $price_factor =
+        qq|<tr><th align="right">|
+      . $locale->text('Price Factor')
+      . qq|</th><td>|
+      . NTI($cgi->popup_menu('-name'    => 'price_factor_id',
+                             '-default' => $form->{price_factor_id},
+                             '-values'  => \@values,
+                             '-labels'  => \%labels))
+      . qq|</td></tr>|;
+  }
+
   $form->{fokus} = "ic.partnumber";
   $form->header;
 
@@ -2551,26 +2385,24 @@ sub form_header {
         <tr valign=top>
           <td width=70%>
             <table width="100%" height="100%">
-              <tr class="listheading">
-                <th class="listheading" align="center" colspan=2>|
-    . $locale->text('') . qq|</th>
+              <tr>
+                <td colspan=2>
+                  <table>
+                    $buchungsgruppe
+                    $linkaccounts
+                  </table>
+                </td>
               </tr>
-              <td colspan=2>
-                <table>
-                  $buchungsgruppe
-                  $linkaccounts
-                </table>
-              </td>
               <tr>
                 <th align="left">| . $locale->text('Notes') . qq|</th>
-                <th align="left">| . $locale->text('Formula') . qq|</th>
+                <th align="left">$formula_label</th>
               </tr>
               <tr>
                 <td>
                   $notes
                 </td>
                 <td>
-                  $formel
+                  $formula
                 </td>
               </tr>
               <tr>
@@ -2613,6 +2445,7 @@ sub form_header {
                <td><input name=sellprice size=11 value=$form->{sellprice}></td>
              </tr>
              $lastcost
+             $price_factor
              <tr>
                <th align="right" nowrap="true">| . $locale->text('Unit') . qq|</th>
                <td>$unit_select</td>
@@ -2783,7 +2616,7 @@ sub assembly_row {
   my (@column_index, %column_data, %column_header);
   my ($nochange, $callback, $previousform, $linetotal, $href);
 
-  my $deliverydate = $main::deliverydate; # ToDO: cjeck if this indeed comes from global context
+  our ($deliverydate); # ToDO: cjeck if this indeed comes from global context
 
   @column_index =
     qw(runningnumber qty unit bom partnumber description partsgroup total);
@@ -2995,20 +2828,11 @@ sub update {
 
       }
     }
-  }
 
-  if ($form->{item} eq "part") {
+  } elsif (($form->{item} eq 'part') || ($form->{item} eq 'service')) {
     &check_form;
   }
 
-  if ($form->{item} eq 'service') {
-    map({ $form->{$_} = $form->parse_amount(\%myconfig, $form->{$_}) }
-        qw(sellprice listprice lastcost));
-    &form_header;
-    &price_row;
-    &form_footer;
-  }
-
   $lxdebug->leave_sub();
 }
 
@@ -3075,7 +2899,7 @@ sub save {
 
     # now take it apart and restore original values
     foreach my $item (split /&/, $previousform) {
-      my ($key, $value) = split /=/, $item, 2;
+      my ($key, $value) = split m/=/, $item, 2;
       $value =~ s/%26/&/g;
       $form->{$key} = $value;
     }
@@ -3095,8 +2919,7 @@ sub save {
       $form->{weight}    -= $form->{"weight_$i"} * $form->{"qty_$i"};
 
       # change/add values for assembly item
-      map { $form->{"${_}_$i"} = $newform{$_} }
-        qw(partnumber description bin unit weight listprice sellprice inventory_accno income_accno expense_accno);
+      map { $form->{"${_}_$i"} = $newform{$_} } qw(partnumber description bin unit weight listprice sellprice inventory_accno income_accno expense_accno price_factor_id);
 
       $form->{sellprice} += $form->{"sellprice_$i"} * $form->{"qty_$i"};
       $form->{weight}    += $form->{"weight_$i"} * $form->{"qty_$i"};
@@ -3107,15 +2930,17 @@ sub save {
       $i = $form->{rowcount};
       $form->{"qty_$i"} = 1 unless ($form->{"qty_$i"});
 
-      map { $form->{"${_}_$i"} = $newform{$_} }
-        qw(partnumber description bin unit listprice inventory_accno income_accno expense_accno sellprice);
+      map { $form->{"${_}_$i"} = $newform{$_} } qw(partnumber description bin unit listprice inventory_accno income_accno expense_accno sellprice lastcost price_factor_id);
+
       $form->{"sellprice_$i"} = $newform{lastcost} if ($form->{vendor_id});
+
       if ($form->{exchangerate} != 0) {
         $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};
+
+      map { $form->{"taxaccounts_$i"} .= "$_ " } split / /, $newform{taxaccount};
       chop $form->{"taxaccounts_$i"};
       foreach my $item (qw(description rate taxnumber)) {
         my $index = $form->{"taxaccounts_$i"} . "_$item";
@@ -3123,26 +2948,28 @@ sub save {
       }
 
       # credit remaining calculation
-      $amount =
-        $form->{"sellprice_$i"} * (1 - $form->{"discount_$i"} / 100) *
-        $form->{"qty_$i"};
-      map { $form->{"${_}_base"} += $amount }
-        (split / /, $form->{"taxaccounts_$i"});
-      map { $amount += ($form->{"${_}_base"} * $form->{"${_}_rate"}) }
-        split / /, $form->{"taxaccounts_$i"}
-        if !$form->{taxincluded};
+      $amount = $form->{"sellprice_$i"} * (1 - $form->{"discount_$i"} / 100) * $form->{"qty_$i"};
+
+      map { $form->{"${_}_base"} += $amount } (split / /, $form->{"taxaccounts_$i"});
+      map { $amount += ($form->{"${_}_base"} * $form->{"${_}_rate"}) } split / /, $form->{"taxaccounts_$i"} if !$form->{taxincluded};
 
       $form->{creditremaining} -= $amount;
 
       # redo number formatting, because invoice parse them!
-      $i = $form->{rowcount};
-      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 rop);
     }
 
     $form->{"id_$i"} = $parts_id;
+
+    # Get the actual price factor (not just the ID) for the marge calculation.
+    $form->get_lists('price_factors' => 'ALL_PRICE_FACTORS');
+    foreach my $pfac (@{ $form->{ALL_PRICE_FACTORS} }) {
+      next if ($pfac->{id} != $newform{price_factor_id});
+      $form->{"marge_price_factor_$i"} = $pfac->{factor};
+      last;
+    }
+    delete $form->{ALL_PRICE_FACTORS};
+
     delete $form->{action};
 
     # restore original callback
@@ -3242,7 +3069,7 @@ sub price_row {
 sub parts_language_selection {
   $lxdebug->enter_sub();
 
-  my $onload = $main::onload;
+  our ($onload);
 
   my $languages = IC->retrieve_languages(\%myconfig, $form);