PriceSource: Rabattbehandlung
authorSven Schöling <s.schoeling@linet-services.de>
Tue, 14 Oct 2014 13:45:01 +0000 (15:45 +0200)
committerSven Schöling <s.schoeling@linet-services.de>
Thu, 18 Dec 2014 15:18:22 +0000 (16:18 +0100)
27 files changed:
SL/DB/MetaSetup/DeliveryOrderItem.pm
SL/DB/MetaSetup/InvoiceItem.pm
SL/DB/MetaSetup/OrderItem.pm
SL/DO.pm
SL/IR.pm
SL/IS.pm
SL/OE.pm
SL/PriceSource.pm
SL/PriceSource/ALL.pm
SL/PriceSource/Base.pm
SL/PriceSource/Business.pm [new file with mode: 0644]
SL/PriceSource/Customer.pm [new file with mode: 0644]
SL/PriceSource/Discount.pm [new file with mode: 0644]
SL/PriceSource/Makemodel.pm
SL/PriceSource/MasterData.pm
SL/PriceSource/Price.pm
SL/PriceSource/Pricegroup.pm
SL/PriceSource/Vendor.pm [new file with mode: 0644]
bin/mozilla/do.pl
bin/mozilla/io.pl
bin/mozilla/ir.pl
bin/mozilla/is.pl
bin/mozilla/oe.pl
js/kivi.io.js
locale/de/all
sql/Pg-upgrade2/recorditem_active_dicount_source.sql [new file with mode: 0644]
templates/webpages/oe/price_sources_dialog.html

index d4789d6..c9b90e3 100644 (file)
@@ -9,30 +9,31 @@ use base qw(SL::DB::Object);
 __PACKAGE__->meta->table('delivery_order_items');
 
 __PACKAGE__->meta->columns(
-  base_qty            => { type => 'float', scale => 4 },
-  cusordnumber        => { type => 'text' },
-  delivery_order_id   => { type => 'integer', not_null => 1 },
-  description         => { type => 'text' },
-  discount            => { type => 'float', scale => 4 },
-  id                  => { type => 'integer', not_null => 1, sequence => 'delivery_order_items_id' },
-  itime               => { type => 'timestamp', default => 'now()' },
-  lastcost            => { type => 'numeric', precision => 15, scale => 5 },
-  longdescription     => { type => 'text' },
-  marge_price_factor  => { type => 'numeric', default => 1, precision => 15, scale => 5 },
-  mtime               => { type => 'timestamp' },
-  ordnumber           => { type => 'text' },
-  parts_id            => { type => 'integer', not_null => 1 },
-  price_factor        => { type => 'numeric', default => 1, precision => 15, scale => 5 },
-  price_factor_id     => { type => 'integer' },
-  pricegroup_id       => { type => 'integer' },
-  project_id          => { type => 'integer' },
-  qty                 => { type => 'numeric', precision => 25, scale => 5 },
-  reqdate             => { type => 'date' },
-  sellprice           => { type => 'numeric', precision => 15, scale => 5 },
-  serialnumber        => { type => 'text' },
-  transdate           => { type => 'text' },
-  unit                => { type => 'varchar', length => 20 },
-  active_price_source => { type => 'text', default => '', not_null => 1 },
+  active_discount_source => { type => 'text', default => '', not_null => 1 },
+  active_price_source    => { type => 'text', default => '', not_null => 1 },
+  base_qty               => { type => 'float', scale => 4 },
+  cusordnumber           => { type => 'text' },
+  delivery_order_id      => { type => 'integer', not_null => 1 },
+  description            => { type => 'text' },
+  discount               => { type => 'float', scale => 4 },
+  id                     => { type => 'integer', not_null => 1, sequence => 'delivery_order_items_id' },
+  itime                  => { type => 'timestamp', default => 'now()' },
+  lastcost               => { type => 'numeric', precision => 15, scale => 5 },
+  longdescription        => { type => 'text' },
+  marge_price_factor     => { type => 'numeric', default => 1, precision => 15, scale => 5 },
+  mtime                  => { type => 'timestamp' },
+  ordnumber              => { type => 'text' },
+  parts_id               => { type => 'integer', not_null => 1 },
+  price_factor           => { type => 'numeric', default => 1, precision => 15, scale => 5 },
+  price_factor_id        => { type => 'integer' },
+  pricegroup_id          => { type => 'integer' },
+  project_id             => { type => 'integer' },
+  qty                    => { type => 'numeric', precision => 25, scale => 5 },
+  reqdate                => { type => 'date' },
+  sellprice              => { type => 'numeric', precision => 15, scale => 5 },
+  serialnumber           => { type => 'text' },
+  transdate              => { type => 'text' },
+  unit                   => { type => 'varchar', length => 20 },
 );
 
 __PACKAGE__->meta->primary_key_columns([ 'id' ]);
index 26ce472..68ed569 100644 (file)
@@ -9,37 +9,38 @@ use base qw(SL::DB::Object);
 __PACKAGE__->meta->table('invoice');
 
 __PACKAGE__->meta->columns(
-  allocated           => { type => 'float', scale => 4 },
-  assemblyitem        => { type => 'boolean', default => 'false' },
-  base_qty            => { type => 'float', scale => 4 },
-  cusordnumber        => { type => 'text' },
-  deliverydate        => { type => 'date' },
-  description         => { type => 'text' },
-  discount            => { type => 'float', scale => 4 },
-  donumber            => { type => 'text' },
-  fxsellprice         => { type => 'numeric', precision => 15, scale => 5 },
-  id                  => { type => 'integer', not_null => 1, sequence => 'invoiceid' },
-  itime               => { type => 'timestamp', default => 'now()' },
-  lastcost            => { type => 'numeric', precision => 15, scale => 5 },
-  longdescription     => { type => 'text' },
-  marge_percent       => { type => 'numeric', precision => 15, scale => 5 },
-  marge_price_factor  => { type => 'numeric', default => 1, precision => 15, scale => 5 },
-  marge_total         => { type => 'numeric', precision => 15, scale => 5 },
-  mtime               => { type => 'timestamp' },
-  ordnumber           => { type => 'text' },
-  parts_id            => { type => 'integer' },
-  price_factor        => { type => 'numeric', default => 1, precision => 15, scale => 5 },
-  price_factor_id     => { type => 'integer' },
-  pricegroup_id       => { type => 'integer' },
-  project_id          => { type => 'integer' },
-  qty                 => { type => 'float', scale => 4 },
-  sellprice           => { type => 'numeric', precision => 15, scale => 5 },
-  serialnumber        => { type => 'text' },
-  subtotal            => { type => 'boolean', default => 'false' },
-  trans_id            => { type => 'integer' },
-  transdate           => { type => 'text' },
-  unit                => { type => 'varchar', length => 20 },
-  active_price_source => { type => 'text', default => '', not_null => 1 },
+  active_discount_source => { type => 'text', default => '', not_null => 1 },
+  active_price_source    => { type => 'text', default => '', not_null => 1 },
+  allocated              => { type => 'float', scale => 4 },
+  assemblyitem           => { type => 'boolean', default => 'false' },
+  base_qty               => { type => 'float', scale => 4 },
+  cusordnumber           => { type => 'text' },
+  deliverydate           => { type => 'date' },
+  description            => { type => 'text' },
+  discount               => { type => 'float', scale => 4 },
+  donumber               => { type => 'text' },
+  fxsellprice            => { type => 'numeric', precision => 15, scale => 5 },
+  id                     => { type => 'integer', not_null => 1, sequence => 'invoiceid' },
+  itime                  => { type => 'timestamp', default => 'now()' },
+  lastcost               => { type => 'numeric', precision => 15, scale => 5 },
+  longdescription        => { type => 'text' },
+  marge_percent          => { type => 'numeric', precision => 15, scale => 5 },
+  marge_price_factor     => { type => 'numeric', default => 1, precision => 15, scale => 5 },
+  marge_total            => { type => 'numeric', precision => 15, scale => 5 },
+  mtime                  => { type => 'timestamp' },
+  ordnumber              => { type => 'text' },
+  parts_id               => { type => 'integer' },
+  price_factor           => { type => 'numeric', default => 1, precision => 15, scale => 5 },
+  price_factor_id        => { type => 'integer' },
+  pricegroup_id          => { type => 'integer' },
+  project_id             => { type => 'integer' },
+  qty                    => { type => 'float', scale => 4 },
+  sellprice              => { type => 'numeric', precision => 15, scale => 5 },
+  serialnumber           => { type => 'text' },
+  subtotal               => { type => 'boolean', default => 'false' },
+  trans_id               => { type => 'integer' },
+  transdate              => { type => 'text' },
+  unit                   => { type => 'varchar', length => 20 },
 );
 
 __PACKAGE__->meta->primary_key_columns([ 'id' ]);
index 2bd6bf5..7e4b2c9 100644 (file)
@@ -9,34 +9,35 @@ use base qw(SL::DB::Object);
 __PACKAGE__->meta->table('orderitems');
 
 __PACKAGE__->meta->columns(
-  base_qty            => { type => 'float', scale => 4 },
-  cusordnumber        => { type => 'text' },
-  description         => { type => 'text' },
-  discount            => { type => 'float', scale => 4 },
-  id                  => { type => 'integer', not_null => 1, sequence => 'orderitemsid' },
-  itime               => { type => 'timestamp', default => 'now()' },
-  lastcost            => { type => 'numeric', precision => 15, scale => 5 },
-  longdescription     => { type => 'text' },
-  marge_percent       => { type => 'numeric', precision => 15, scale => 5 },
-  marge_price_factor  => { type => 'numeric', default => 1, precision => 15, scale => 5 },
-  marge_total         => { type => 'numeric', precision => 15, scale => 5 },
-  mtime               => { type => 'timestamp' },
-  ordnumber           => { type => 'text' },
-  parts_id            => { type => 'integer' },
-  price_factor        => { type => 'numeric', default => 1, precision => 15, scale => 5 },
-  price_factor_id     => { type => 'integer' },
-  pricegroup_id       => { type => 'integer' },
-  project_id          => { type => 'integer' },
-  qty                 => { type => 'float', scale => 4 },
-  reqdate             => { type => 'date' },
-  sellprice           => { type => 'numeric', precision => 15, scale => 5 },
-  serialnumber        => { type => 'text' },
-  ship                => { type => 'float', scale => 4 },
-  subtotal            => { type => 'boolean', default => 'false' },
-  trans_id            => { type => 'integer' },
-  transdate           => { type => 'text' },
-  unit                => { type => 'varchar', length => 20 },
-  active_price_source => { type => 'text', default => '', not_null => 1 },
+  active_discount_source => { type => 'text', default => '', not_null => 1 },
+  active_price_source    => { type => 'text', default => '', not_null => 1 },
+  base_qty               => { type => 'float', scale => 4 },
+  cusordnumber           => { type => 'text' },
+  description            => { type => 'text' },
+  discount               => { type => 'float', scale => 4 },
+  id                     => { type => 'integer', not_null => 1, sequence => 'orderitemsid' },
+  itime                  => { type => 'timestamp', default => 'now()' },
+  lastcost               => { type => 'numeric', precision => 15, scale => 5 },
+  longdescription        => { type => 'text' },
+  marge_percent          => { type => 'numeric', precision => 15, scale => 5 },
+  marge_price_factor     => { type => 'numeric', default => 1, precision => 15, scale => 5 },
+  marge_total            => { type => 'numeric', precision => 15, scale => 5 },
+  mtime                  => { type => 'timestamp' },
+  ordnumber              => { type => 'text' },
+  parts_id               => { type => 'integer' },
+  price_factor           => { type => 'numeric', default => 1, precision => 15, scale => 5 },
+  price_factor_id        => { type => 'integer' },
+  pricegroup_id          => { type => 'integer' },
+  project_id             => { type => 'integer' },
+  qty                    => { type => 'float', scale => 4 },
+  reqdate                => { type => 'date' },
+  sellprice              => { type => 'numeric', precision => 15, scale => 5 },
+  serialnumber           => { type => 'text' },
+  ship                   => { type => 'float', scale => 4 },
+  subtotal               => { type => 'boolean', default => 'false' },
+  trans_id               => { type => 'integer' },
+  transdate              => { type => 'text' },
+  unit                   => { type => 'varchar', length => 20 },
 );
 
 __PACKAGE__->meta->primary_key_columns([ 'id' ]);
index a37e516..478a125 100644 (file)
--- a/SL/DO.pm
+++ b/SL/DO.pm
@@ -285,9 +285,9 @@ sub save {
          sellprice, discount, unit, reqdate, project_id, serialnumber,
          ordnumber, transdate, cusordnumber,
          lastcost, price_factor_id, price_factor, marge_price_factor, pricegroup_id,
-         active_price_source)
+         active_price_source, active_discount_source)
        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
-         (SELECT factor FROM price_factors WHERE id = ?), ?, ?, ?)|;
+         (SELECT factor FROM price_factors WHERE id = ?), ?, ?, ?, ?)|;
   my $h_item = prepare_query($form, $dbh, $q_item);
 
   my $q_item_stock =
@@ -343,7 +343,7 @@ sub save {
                conv_i($form->{"price_factor_id_$i"}), conv_i($form->{"price_factor_id_$i"}),
                conv_i($form->{"marge_price_factor_$i"}),
                $pricegroup_id,
-               $form->{"active_price_source_$i"});
+               $form->{"active_price_source_$i"}, $form->{"active_discount_source_$i"});
     do_statement($form, $h_item, $q_item, @values);
 
     my $stock_info = DO->unpack_stock_information('packed' => $form->{"stock_${in_out}_$i"});
@@ -690,7 +690,7 @@ sub retrieve {
          doi.reqdate, doi.project_id, doi.serialnumber, doi.lastcost,
          doi.ordnumber, doi.transdate, doi.cusordnumber, doi.longdescription,
          doi.price_factor_id, doi.price_factor, doi.marge_price_factor, doi.pricegroup_id,
-         doi.active_price_source,
+         doi.active_price_source, doi.active_discount_source,
          pr.projectnumber, dord.transdate AS dord_transdate, dord.donumber,
          pg.partsgroup
        FROM delivery_order_items doi
index 6c5c501..ee676cb 100644 (file)
--- a/SL/IR.pm
+++ b/SL/IR.pm
@@ -381,15 +381,16 @@ sub post_invoice {
     $query =
       qq|INSERT INTO invoice (id, trans_id, parts_id, description, longdescription, qty, base_qty,
                               sellprice, fxsellprice, discount, allocated, unit, deliverydate,
-                              project_id, serialnumber, price_factor_id, price_factor, marge_price_factor)
-         VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, (SELECT factor FROM price_factors WHERE id = ?), ?, ?)|;
+                              project_id, serialnumber, price_factor_id, price_factor, marge_price_factor,
+                              active_price_source, active_discount_source)
+         VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, (SELECT factor FROM price_factors WHERE id = ?), ?, ?, ?)|;
     @values = ($invoice_id, conv_i($form->{id}), conv_i($form->{"id_$i"}),
                $form->{"description_$i"}, $restricter->process($form->{"longdescription_$i"}), $form->{"qty_$i"} * -1,
                $baseqty * -1, $form->{"sellprice_$i"}, $fxsellprice, $form->{"discount_$i"}, $allocated,
                $form->{"unit_$i"}, conv_date($form->{deliverydate}),
                conv_i($form->{"project_id_$i"}), $form->{"serialnumber_$i"},
                conv_i($form->{"price_factor_id_$i"}), conv_i($form->{"price_factor_id_$i"}), conv_i($form->{"marge_price_factor_$i"}),
-               conv_i($form->{"active_price_source_$i"}),
+               conv_i($form->{"active_price_source_$i"}), conv_i($form->{"active_discount_source_$i"}),
                );
     do_query($form, $dbh, $query, @values);
 
@@ -978,7 +979,7 @@ sub retrieve_invoice {
 
         i.id AS invoice_id,
         i.description, i.longdescription, i.qty, i.fxsellprice AS sellprice, i.parts_id AS id, i.unit, i.deliverydate, i.project_id, i.serialnumber,
-        i.price_factor_id, i.price_factor, i.marge_price_factor, i.discount, i.active_price_source,
+        i.price_factor_id, i.price_factor, i.marge_price_factor, i.discount, i.active_price_source, i.active_discount_source,
         p.partnumber, p.inventory_accno_id AS part_inventory_accno_id,  pr.projectnumber, pg.partsgroup
 
         FROM invoice i
index e7a437e..227b442 100644 (file)
--- a/SL/IS.pm
+++ b/SL/IS.pm
@@ -761,9 +761,10 @@ sub post_invoice {
                                 sellprice, fxsellprice, discount, allocated, assemblyitem,
                                 unit, deliverydate, project_id, serialnumber, pricegroup_id,
                                 ordnumber, donumber, transdate, cusordnumber, base_qty, subtotal,
-                                marge_percent, marge_total, lastcost, active_price_source,
+                                marge_percent, marge_total, lastcost, active_price_source, active_discount_source,
+
                                 price_factor_id, price_factor, marge_price_factor)
-           VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
+           VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
                    (SELECT factor FROM price_factors WHERE id = ?), ?)|;
 
       @values = ($invoice_id, conv_i($form->{id}), conv_i($form->{"id_$i"}),
@@ -776,7 +777,7 @@ sub post_invoice {
                  $form->{"cusordnumber_$i"}, $baseqty, $form->{"subtotal_$i"} ? 't' : 'f',
                  $form->{"marge_percent_$i"}, $form->{"marge_absolut_$i"},
                  $form->{"lastcost_$i"},
-                 $form->{"active_price_source_$i"},
+                 $form->{"active_price_source_$i"}, $form->{"active_discount_source_$i"},
                  conv_i($form->{"price_factor_id_$i"}), conv_i($form->{"price_factor_id_$i"}),
                  conv_i($form->{"marge_price_factor_$i"}));
       do_query($form, $dbh, $query, @values);
@@ -1678,7 +1679,7 @@ sub retrieve_invoice {
            i.id AS invoice_id,
            i.description, i.longdescription, i.qty, i.fxsellprice AS sellprice, i.discount, i.parts_id AS id, i.unit, i.deliverydate AS reqdate,
            i.project_id, i.serialnumber, i.id AS invoice_pos, i.pricegroup_id, i.ordnumber, i.donumber, i.transdate, i.cusordnumber, i.subtotal, i.lastcost,
-           i.price_factor_id, i.price_factor, i.marge_price_factor, i.active_price_source,
+           i.price_factor_id, i.price_factor, i.marge_price_factor, i.active_price_source, i.active_discount_source,
            p.partnumber, p.assembly, p.notes AS partnotes, p.inventory_accno_id AS part_inventory_accno_id, p.formel, p.listprice,
            pr.projectnumber, pg.partsgroup, prg.pricegroup
 
index 506db6b..b454498 100644 (file)
--- a/SL/OE.pm
+++ b/SL/OE.pm
@@ -532,7 +532,7 @@ sub save {
           sellprice = ?, discount = ?, unit = ?, reqdate = ?, project_id = ?, serialnumber = ?, ship = ?,
           pricegroup_id = ?, ordnumber = ?, transdate = ?, cusordnumber = ?, subtotal = ?,
           marge_percent = ?, marge_total = ?, lastcost = ?, price_factor_id = ?,
-          active_price_source = ?,
+          active_price_source = ?, active_discount_source = ?,
           price_factor = (SELECT factor FROM price_factors WHERE id = ?), marge_price_factor = ?
         WHERE id = ?
 SQL
@@ -547,7 +547,7 @@ SQL
            $form->{"cusordnumber_$i"}, $form->{"subtotal_$i"} ? 't' : 'f',
            $form->{"marge_percent_$i"}, $form->{"marge_absolut_$i"},
            $form->{"lastcost_$i"},
-           $form->{"active_price_source_$i"},
+           $form->{"active_price_source_$i"}, $form->{"active_discount_source_$i"},
            conv_i($form->{"price_factor_id_$i"}), conv_i($form->{"price_factor_id_$i"}),
            conv_i($form->{"marge_price_factor_$i"}),
            conv_i($orderitems_id),
@@ -947,7 +947,7 @@ sub retrieve {
            o.sellprice, o.parts_id AS id, o.unit, o.discount, p.notes AS partnotes, p.inventory_accno_id AS part_inventory_accno_id,
            o.reqdate, o.project_id, o.serialnumber, o.ship, o.lastcost,
            o.ordnumber, o.transdate, o.cusordnumber, o.subtotal, o.longdescription,
-           o.price_factor_id, o.price_factor, o.marge_price_factor, o.active_price_source,
+           o.price_factor_id, o.price_factor, o.marge_price_factor, o.active_price_source, o.active_discount_source,
            pr.projectnumber, p.formel,
            pg.partsgroup, o.pricegroup_id, (SELECT pricegroup FROM pricegroup WHERE id=o.pricegroup_id) as pricegroup
          FROM orderitems o
index 241ae04..e7a5906 100644 (file)
@@ -6,7 +6,7 @@ use Rose::Object::MakeMethods::Generic (
   scalar => [ qw(record_item record) ],
 );
 
-use List::UtilsBy qw(min_by);
+use List::UtilsBy qw(min_by max_by);
 use SL::PriceSource::ALL;
 use SL::PriceSource::Price;
 use SL::Locale::String;
@@ -14,7 +14,7 @@ use SL::Locale::String;
 sub all_price_sources {
   my ($self) = @_;
 
-  return map {
+  map {
     $_->new(record_item => $self->record_item, record => $self->record)
   } SL::PriceSource::ALL->all_enabled_price_sources
 }
@@ -26,7 +26,7 @@ sub price_from_source {
   my $class = SL::PriceSource::ALL->price_source_class_by_name($source_name);
 
   return $class
-    ? $class->new(record_item => $self->record_item)->price_from_source($source, $spec)
+    ? $class->new(record_item => $self->record_item, record => $self->record)->price_from_source($source, $spec)
     : empty_price();
 }
 
@@ -34,8 +34,16 @@ sub available_prices {
   map { $_->available_prices } $_[0]->all_price_sources;
 }
 
+sub available_discounts {
+  map { $_->available_discounts } $_[0]->all_price_sources;
+}
+
 sub best_price {
-  min_by { $_->price } grep { $_->price > 0 } map { $_->best_price } $_[0]->all_price_sources;
+  min_by { $_->price } grep { $_->price > 0 } grep { $_ } map { $_->best_price } $_[0]->all_price_sources;
+}
+
+sub best_discount {
+  max_by { $_->discount } grep { $_->discount } grep { $_ } map { $_->best_discount } $_[0]->all_price_sources;
 }
 
 sub empty_price {
index a15d6b6..f9d8600 100644 (file)
@@ -4,17 +4,26 @@ use strict;
 use SL::PriceSource::Pricegroup;
 use SL::PriceSource::MasterData;
 use SL::PriceSource::Makemodel;
+use SL::PriceSource::Customer;
+use SL::PriceSource::Vendor;
+use SL::PriceSource::Business;
 
 my %price_sources_by_name = (
   master_data => 'SL::PriceSource::MasterData',
+  customer    => 'SL::PriceSource::Customer',
+  vendor      => 'SL::PriceSource::Vendor',
   pricegroup  => 'SL::PriceSource::Pricegroup',
   makemodel   => 'SL::PriceSource::Makemodel',
+  business    => 'SL::PriceSource::Business',
 );
 
 my @price_sources_order = qw(
   master_data
+  customer
+  vendor
   pricegroup
   makemodel
+  business
 );
 
 sub all_enabled_price_sources {
index 261897d..513f3d4 100644 (file)
@@ -13,14 +13,22 @@ sub description { die 'description needs to be implemented' }
 
 sub available_prices { die 'available_prices needs to be implemented' }
 
+sub available_discounts { die 'available_discounts needs to be implemented' }
+
 sub best_price { die 'best_price needs to be implemented' }
 
+sub best_discounts { die 'best_discounts needs to be implemented' }
+
 sub price_from_source { die 'price_from_source needs to be implemented:' . "@_" }
 
 sub part {
   $_[0]->record_item->part;
 }
 
+sub customer_vendor {
+  $_[0]->record->is_sales ? $_[0]->record->customer : $_[0]->record->vendor;
+}
+
 1;
 
 __END__
@@ -98,6 +106,10 @@ anything and do not save those.
 
 Shortcut to C<< record_item->part >>
 
+=item C<customer_vendor>
+
+Shortcut to C<< record->is_sales ? record->customer : record->vendor >>
+
 =back
 
 =head1 INTERFACE METHODS
@@ -121,16 +133,30 @@ for the current situation. Each price must have a unique spec that can be used
 to recreate it later. Try to be brief, no one needs 20 different price
 suggestions.
 
+=item C<available_discounts>
+
+Must return a list of all prices that you algorithm can recommend the user
+for the current situation. Each discount must have a unique spec that can be
+used to recreate it later. Try to be brief, no one needs 20 different discount
+suggestions.
+
 =item C<best_price>
 
 Must return what you think of as the best matching price in your
 C<available_prices>. This does not have to be the lowest price, but it will be
 compared later to other price sources, and the lowest will be set.
 
+=item C<best_discount>
+
+Must return what you think of as the best matching discount in your
+C<available_discounts>. This does not have to be the highest discount, but it
+will be compared later to other price sources, and the highest will be set.
+
 =item C<price_from_source SOURCE, SPEC>
 
-Must recreate the price from C<SPEC> and return. For reference, the complete
-C<SOURCE> entry from C<record_item.active_price_source> is included.
+Must recreate the price or discount from C<SPEC> and return. For reference, the
+complete C<SOURCE> entry from C<record_item.active_price_source> or
+C<record_item.active_discount_source> is included.
 
 Note that constraints from the rest of the C<record> do not apply anymore. If
 information needed for the retrieval can be deleted elsewhere, then you must
diff --git a/SL/PriceSource/Business.pm b/SL/PriceSource/Business.pm
new file mode 100644 (file)
index 0000000..ca66c5d
--- /dev/null
@@ -0,0 +1,78 @@
+package SL::PriceSource::Business;
+
+use strict;
+use parent qw(SL::PriceSource::Base);
+
+use SL::DB::Business;
+use SL::PriceSource::Discount;
+use SL::Locale::String;
+
+sub name { 'business' }
+
+sub description { t8('Business') }
+
+sub available_prices { }
+
+sub available_discounts {
+  my ($self, %params) = @_;
+
+  return unless $self->customer_vendor;
+  return unless $self->customer_vendor->business;
+  return unless $self->customer_vendor->business->discount != 0;
+
+  SL::PriceSource::Discount->new(
+    discount     => $self->customer_vendor->business->discount,
+    spec         => $self->customer_vendor->business->id,
+    description  => t8('Business Discount'),
+    price_source => $self,
+  );
+}
+
+sub price_from_source {
+  my ($self, $source, $spec) = @_;
+
+  my $business = SL::DB::Business->load_cached($spec);
+
+  if (!$business) {
+    return SL::PriceSource::Discount->new(
+      missing      => t8('Could not load this business'),
+      price_source => $self,
+    )
+  }
+
+  if (!$self->customer_vendor) {
+    return SL::PriceSource::Discount->new(
+      discount     => $business->discount,
+      spec         => $business->id,
+      description  => t8('Business Discount'),
+      price_source => $self,
+      invalid      => t8('This discount is only valid in records with customer or vendor'),
+    )
+  }
+
+  if ($business->id != $self->customer_vendor->business->id) {
+    return SL::PriceSource::Discount->new(
+      discount     => $business->discount,
+      spec         => $business->id,
+      description  => t8('Business Discount'),
+      price_source => $self,
+      invalid      => t8('This discount is only valid for business #1', $business->full_description),
+    )
+  }
+
+  return SL::PriceSource::Discount->new(
+    discount     => $business->discount,
+    spec         => $business->id,
+    description  => t8('Business Discount'),
+    price_source => $self,
+  );
+}
+
+sub best_price { }
+
+sub best_discount {
+  &available_discounts;
+}
+
+1;
+
diff --git a/SL/PriceSource/Customer.pm b/SL/PriceSource/Customer.pm
new file mode 100644 (file)
index 0000000..6bf06bc
--- /dev/null
@@ -0,0 +1,78 @@
+package SL::PriceSource::Customer;
+
+use strict;
+use parent qw(SL::PriceSource::Base);
+
+use SL::DB::Customer;
+use SL::PriceSource::Discount;
+use SL::Locale::String;
+
+sub name { 'customer_discount' }
+
+sub description { t8('Customer Discount') }
+
+sub available_prices { }
+
+sub available_discounts {
+  my ($self, %params) = @_;
+
+  return unless $self->record->is_sales;
+  return unless $self->record->customer;
+  return unless $self->record->customer->discount != 0;
+
+  SL::PriceSource::Discount->new(
+    discount     => $self->record->customer->discount,
+    spec         => $self->record->customer->id,
+    description  => t8('Customer Discount'),
+    price_source => $self,
+  );
+}
+
+sub price_from_source {
+  my ($self, $source, $spec) = @_;
+
+  my $customer = SL::DB::Customer->load_cached($spec);
+
+  if (!$customer) {
+    return SL::PriceSource::Discount->new(
+      missing      => t8('Could not load this customer'),
+      price_source => $self,
+    )
+  }
+
+  if (!$self->record->customer) {
+    return SL::PriceSource::Discount->new(
+      discount     => $customer->discount,
+      spec         => $customer->id,
+      description  => t8('Customer Discount'),
+      price_source => $self,
+      invalid      => t8('This discount is only valid in sales documents'),
+    )
+  }
+
+  if ($customer->id != $self->record->customer->id) {
+    return SL::PriceSource::Discount->new(
+      discount     => $customer->discount,
+      spec         => $customer->id,
+      description  => t8('Customer Discount'),
+      price_source => $self,
+      invalid      => t8('This discount is only valid for customer #1', $customer->full_description),
+    )
+  }
+
+  return SL::PriceSource::Discount->new(
+    discount     => $customer->discount,
+    spec         => $customer->id,
+    description  => t8('Customer Discount'),
+    price_source => $self,
+  );
+}
+
+sub best_price { }
+
+sub best_discount {
+  &available_discounts;
+}
+
+1;
+
diff --git a/SL/PriceSource/Discount.pm b/SL/PriceSource/Discount.pm
new file mode 100644 (file)
index 0000000..6c95f90
--- /dev/null
@@ -0,0 +1,142 @@
+package SL::PriceSource::Discount;
+
+use strict;
+
+use parent 'SL::DB::Object';
+use Rose::Object::MakeMethods::Generic (
+  scalar => [ qw(discount description spec price_source invalid missing) ],
+);
+
+require SL::DB::Helper::Attr;
+SL::DB::Helper::Attr::make(__PACKAGE__,
+  discount => 'numeric(15,5)',
+);
+
+sub source {
+  $_[0]->price_source
+  ? $_[0]->price_source->name . '/' . $_[0]->spec
+  : '';
+}
+
+sub full_description {
+  my ($self) = @_;
+
+  $self->price_source
+    ? $self->price_source->description . ': ' . $self->description
+    : $self->description
+}
+
+sub source_description {
+  my ($self) = @_;
+
+  $self->price_source
+    ? $self->price_source->description
+    : $self->description
+}
+
+sub to_str {
+  "source: @{[ $_[0]->source ]}, discount: @{[ $_[0]->discount ]}, description: @{[ $_[0]->description ]}"
+}
+
+1;
+
+__END__
+
+=encoding utf-8
+
+=head1 NAME
+
+SL::PriceSource::Price - contrainer to pass calculated prices around
+
+=head1 SYNOPSIS
+
+  # in PriceSource::Base implementation
+  $price = SL::PriceSource::Price->new(
+    discount     => 10,
+    spec         => 'summersale2014', # something you can easily parse later
+    description  => t8('10% discount during summer sale 2014'),
+    price_source => $self,
+  )
+
+  # special empty price in SL::PriceSource, for internal use.
+  SL::PriceSource::Price->new(
+    description => t8('None (PriceSource)'),
+  );
+
+  # price can't be restored
+  SL::PriceSource::Price->new(
+    missing      => t8('Um, sorry, cannot find that one'),
+    price_source => $self,
+  );
+
+  # invalid price
+  SL::PriceSource::Price->new(
+    price        => $original_price,
+    spec         => $original_spec,
+    description  => $original_description,
+    invalid      => t8('Offer expired #1 weeks ago', $dt->delta_weeks),
+    price_source => $self,
+  );
+
+=head1 DESCRIPTION
+
+See L<SL::PriceSource> for information about the mechanism.
+
+This is a container for prices that are generated by L<SL::PriceSource::Base>
+implementations.
+
+=head1 CONSTRUCTOR FIELDS
+
+=over 4
+
+=item C<discount>
+
+The discount in percent. A discount of 0 will be ignored. If passed as
+part of C<available_prices> it will be filtered out. If returned as
+C<best_price> or C<price_from_source> it will trigger a warning.
+
+=item C<spec>
+
+A unique string that can later be understood by the creating implementation.
+Can be empty if the implementation only supports one price for a given
+record_item.
+
+=item C<description>
+
+A localized short description of the origins of this price.
+
+=item C<price_source>
+
+A ref to the creating algorithm.
+
+=item C<missing>
+
+OPTIONAL. Both indicator and localized message that the price with this spec
+could not be reproduced and should be changed.
+
+If price is missing, you do not need to supply anything except C<source>.
+
+=item C<invalid>
+
+OPTIONAL. Both indicator and localized message that the conditions for this
+price are no longer valid, and that the price should be changed.
+
+If price is missing, you do not need to supply anything except C<source>.
+
+=back
+
+=head1 SEE ALSO
+
+L<SL::PriceSource>,
+L<SL::PriceSource::Base>,
+L<SL::PriceSource::ALL>
+
+=head1 BUGS
+
+None yet. :)
+
+=head1 AUTHOR
+
+Sven Schoeling E<lt>s.schoeling@linet-services.deE<gt>
+
+=cut
index 2a4f5b0..e1146a3 100644 (file)
@@ -23,6 +23,8 @@ sub available_prices {
   $self->part->makemodels;
 }
 
+sub available_discounts { }
+
 sub price_from_source {
   my ($self, $source, $spec) = @_;
 
@@ -42,6 +44,8 @@ sub best_price {
 
 }
 
+sub best_discount { }
+
 sub make_price_from_makemodel {
   my ($self, $makemodel) = @_;
 
index 3f7c11d..e7ca2a3 100644 (file)
@@ -20,6 +20,8 @@ sub available_prices {
     : ($self->make_lastcost,  $self->make_listprice);
 }
 
+sub available_discounts { }
+
 sub price_from_source {
   my ($self, $source, $spec) = @_;
 
@@ -35,6 +37,8 @@ sub best_price {
   : $_[0]->make_lastcost
 }
 
+sub best_discount { }
+
 sub make_sellprice {
   my ($self) = @_;
 
index 1e8f0a2..71bd1ff 100644 (file)
@@ -7,14 +7,14 @@ use Rose::Object::MakeMethods::Generic (
   scalar => [ qw(price description spec price_source invalid missing) ],
 );
 
-use SL::DB::Helper::Attr;
+require SL::DB::Helper::Attr;
 SL::DB::Helper::Attr::make(__PACKAGE__,
   price => 'numeric(15,5)',
 );
 
 sub source {
   $_[0]->price_source
-  ?  $_[0]->price_source->name . '/' . $_[0]->spec
+  ? $_[0]->price_source->name . '/' . $_[0]->spec
   : '';
 }
 
@@ -31,11 +31,11 @@ sub source_description {
 
   $self->price_source
     ? $self->price_source->description
-    : $self->description 
+    : $self->description
 }
 
 sub to_str {
-  "source: @{[ $_[0]->source ]}, price: @{[ $_[0]->price]}, description: @{[ $_[0]->description ]}"
+  "source: @{[ $_[0]->source ]}, price: @{[ $_[0]->price ]}, description: @{[ $_[0]->description ]}"
 }
 
 1;
@@ -53,35 +53,31 @@ SL::PriceSource::Price - contrainer to pass calculated prices around
   # in PriceSource::Base implementation
   $price = SL::PriceSource::Price->new(
     price        => 10.3,
-    spec         => '10.3', # something you can easily parse later
-    description  => t8('Fix price 10.3'),
+    spec         => '3', # something you can easily parse later
+    description  => t8('Fix price 10.3 for customer 3'),
     price_source => $self,
   )
 
-  # special empty price in SL::PriceSource
+  # special empty price in SL::PriceSource, for internal use.
   SL::PriceSource::Price->new(
     description => t8('None (PriceSource)'),
   );
 
-  # invalid price
+  # price can't be restored
   SL::PriceSource::Price->new(
-    price        => $original_price,
-    spec         => $original_spec,
-    description  => $original_description,
-    invalid      => t8('Offer expired #1 weeks ago', $dt->delta_weeks),
+    missing      => t8('Um, sorry, cannot find that one'),
     price_source => $self,
   );
 
-  # missing price
+  # invalid price
   SL::PriceSource::Price->new(
-    price        => $original_price,              # will keep last entered price
+    price        => $original_price,
     spec         => $original_spec,
-    description  => '',
-    missing      => t8('Um, sorry, cannot find that one'),
+    description  => $original_description,
+    invalid      => t8('Offer expired #1 weeks ago', $dt->delta_weeks),
     price_source => $self,
   );
 
-
 =head1 DESCRIPTION
 
 See L<SL::PriceSource> for information about the mechanism.
@@ -97,7 +93,7 @@ implementations.
 
 The price. A price of 0 is special and is considered undesirable. If passed as
 part of C<available_prices> it will be filtered out. If returned as
-C<best_price> or C<price_from_source> it will be warned about.
+C<best_price> or C<price_from_source> it will trigger a warning.
 
 =item C<spec>
 
@@ -118,11 +114,15 @@ A ref to the creating algorithm.
 OPTIONAL. Both indicator and localized message that the price with this spec
 could not be reproduced and should be changed.
 
+If price is missing, you do not need to supply anything except C<source>.
+
 =item C<invalid>
 
 OPTIONAL. Both indicator and localized message that the conditions for this
 price are no longer valid, and that the price should be changed.
 
+If price is missing, you do not need to supply anything except C<source>.
+
 =back
 
 =head1 SEE ALSO
index 127ec66..8a322c5 100644 (file)
@@ -33,6 +33,8 @@ sub available_prices {
   } @$prices;
 }
 
+sub available_discounts { }
+
 sub price_from_source {
   my ($self, $source, $spec) = @_;
 
@@ -57,6 +59,8 @@ sub best_price {
   return $best_price || ();
 }
 
+sub best_discount { }
+
 sub make_price {
   my ($self, $price_obj) = @_;
 
diff --git a/SL/PriceSource/Vendor.pm b/SL/PriceSource/Vendor.pm
new file mode 100644 (file)
index 0000000..94d9bd0
--- /dev/null
@@ -0,0 +1,79 @@
+package SL::PriceSource::Vendor;
+
+use strict;
+use parent qw(SL::PriceSource::Base);
+
+use SL::DB::Vendor;
+use SL::PriceSource::Price;
+use SL::Locale::String;
+
+sub name { 'vendor_discount' }
+
+sub description { t8('Vendor Discount') }
+
+sub available_prices { }
+
+sub available_discounts {
+  my ($self, %params) = @_;
+
+  return if     $self->record->is_sales;
+  return unless $self->record->vendor;
+  return unless $self->record->vendor->discount != 0;
+
+  SL::PriceSource::Vendor->new(
+    discount     => $self->record->vendor->discount,
+    spec         => $self->record->vendor->id,
+    description  => t8('Vendor Discount'),
+    price_source => $self,
+  );
+}
+
+sub price_from_source {
+  my ($self, $source, $spec) = @_;
+
+  my $vendor = SL::DB::Vendor->load_cached($spec);
+
+  if (!$vendor) {
+    return SL::PriceSource::Discount->new(
+      missing      => t8('Could not load this vendor'),
+      price_source => $self,
+    )
+  }
+
+  if (!$self->record->vendor) {
+    return SL::PriceSource::Discount->new(
+      discount     => $vendor->discount,
+      spec         => $vendor->id,
+      description  => t8('Vendor Discount'),
+      price_source => $self,
+      invalid      => t8('This discount is only valid in purchase documents'),
+    )
+  }
+
+  if ($vendor->id != $self->record->vendor->id) {
+    return SL::PriceSource::Discount->new(
+      discount     => $vendor->discount,
+      spec         => $vendor->id,
+      description  => t8('Vendor Discount'),
+      price_source => $self,
+      invalid      => t8('This discount is only valid for vendor #1', $vendor->full_description),
+    )
+  }
+
+  return SL::PriceSource::Discount->new(
+    discount     => $vendor->discount,
+    spec         => $vendor->id,
+    description  => t8('Vendor Discount'),
+    price_source => $self,
+  );
+}
+
+
+sub best_price { }
+
+sub best_discount {
+  &available_discounts;
+}
+
+1;
+
index b242acb..362447a 100644 (file)
@@ -427,7 +427,7 @@ sub update_delivery_order {
         map { $form->{"${_}_$i"} = $form->{item_list}[0]{$_} } keys %{ $form->{item_list}[0] };
 
         $form->{"marge_price_factor_$i"} = $form->{item_list}->[0]->{price_factor};
-        $form->{"sellprice_$i"}          = $form->format_amount(\%myconfig, $form->{"sellprice_$i"} * (1 - $form->{tradediscount}));
+        $form->{"sellprice_$i"}          = $form->format_amount(\%myconfig, $form->{"sellprice_$i"});
         $form->{"lastcost_$i"}          = $form->format_amount(\%myconfig, $form->{"lastcost_$i"});
         $form->{"qty_$i"}                = $form->format_amount(\%myconfig, $form->{"qty_$i"});
       }
index 565b3c5..57c2b8c 100644 (file)
@@ -226,6 +226,8 @@ sub display_row {
   for my $i (1 .. $numrows) {
     my %column_data = ();
 
+    my $record_item = $record->id && $record->items ? $record->items->[$i-1] : _make_record_item($i);
+
     # undo formatting
     map { $form->{"${_}_$i"} = $form->parse_amount(\%myconfig, $form->{"${_}_$i"}) }
       qw(qty discount sellprice lastcost price_new price_old)
@@ -235,8 +237,6 @@ sub display_row {
       $form->{"sellprice_$i"} = $form->{"price_new_$i"};
     }
 
-    my $record_item = $record->id && $record->items ? $record->items->[$i-1] : _make_record_item($i);
-
 # unit begin
     $form->{"unit_old_$i"}      ||= $form->{"unit_$i"};
     $form->{"selected_unit_$i"} ||= $form->{"unit_$i"};
@@ -310,14 +310,15 @@ sub display_row {
     }
 
     my $sellprice_value = $form->format_amount(\%myconfig, $form->{"sellprice_$i"}, $decimalplaces);
+    my $discount_value  = $form->format_amount(\%myconfig, $form->{"discount_$i"});
     my $edit_prices     = $main::auth->assert('edit_prices', 1) && !$::form->{"active_price_source_$i"};
+    my $edit_discounts  = $main::auth->assert('edit_prices', 1) && !$::form->{"active_discount_source_$i"};
     $column_data{sellprice}   = (!$edit_prices)
                                 ? $cgi->hidden(   -name => "sellprice_$i", -id => "sellprice_$i", -value => $sellprice_value) . $sellprice_value
                                 : $cgi->textfield(-name => "sellprice_$i", -id => "sellprice_$i", -size => 10, -onBlur => "check_right_number_format(this)", -value => $sellprice_value);
-    $column_data{discount}    = (!$edit_prices)
-                                  ? $cgi->textfield(-readonly => "readonly",
-                                                    -name => "discount_$i", -size => 3, -value => $form->format_amount(\%myconfig, $form->{"discount_$i"}))
-                                  : $cgi->textfield(-name => "discount_$i", -size => 3, -value => $form->format_amount(\%myconfig, $form->{"discount_$i"}));
+    $column_data{discount}    = (!$edit_discounts)
+                                  ? $cgi->hidden(   -name => "discount_$i", -id => "discount_$i", -value => $discount_value) . $discount_value . ' %'
+                                  : $cgi->textfield(-name => "discount_$i", -id => "discount_$i", -size => 3, -value => $discount_value);
     $column_data{linetotal}   = $form->format_amount(\%myconfig, $linetotal, 2);
     $column_data{bin}         = $form->{"bin_$i"};
 
@@ -325,14 +326,26 @@ sub display_row {
 
     if ($form->{"id_${i}"} && !$is_delivery_order) {
       my $price_source = SL::PriceSource->new(record_item => $record_item, record => $record);
-      my $price = $price_source->price_from_source($::form->{"active_price_source_$i"});
+      my $price    = $price_source->price_from_source($::form->{"active_price_source_$i"});
+      my $discount = $price_source->price_from_source($::form->{"active_discount_source_$i"});
       $column_data{price_source} .= $cgi->button(-value => $price->source_description, -onClick => "kivi.io.price_chooser($i)");
       if ($price->source) {
         $column_data{price_source} .= ' ' . $cgi->img({src => 'image/flag-red.png', alt => $price->invalid, title => $price->invalid }) if $price->invalid;
         $column_data{price_source} .= ' ' . $cgi->img({src => 'image/flag-red.png', alt => $price->missing, title => $price->missing }) if $price->missing;
-        $column_data{price_source} .= ' ' . $cgi->img({src => 'image/up.png',   alt => t8('This price has since gone up'),      title => t8('This price has since gone up' )     }) if $price->price > $record_item->sellprice;
-        $column_data{price_source} .= ' ' . $cgi->img({src => 'image/down.png', alt => t8('This price has since gone down'),    title => t8('This price has since gone down')    }) if $price->price < $record_item->sellprice;
-        $column_data{price_source} .= ' ' . $cgi->img({src => 'image/ok.png',   alt => t8('There is a better price available'), title => t8('There is a better price available') }) if $price->source ne $price_source->best_price->source;
+        if (!$price->missing && !$price->invalid) {
+          $column_data{price_source} .= ' ' . $cgi->img({src => 'image/up.png',   alt => t8('This price has since gone up'),      title => t8('This price has since gone up' )     }) if $price->price > $record_item->sellprice;
+          $column_data{price_source} .= ' ' . $cgi->img({src => 'image/down.png', alt => t8('This price has since gone down'),    title => t8('This price has since gone down')    }) if $price->price < $record_item->sellprice;
+          $column_data{price_source} .= ' ' . $cgi->img({src => 'image/ok.png',   alt => t8('There is a better price available'), title => t8('There is a better price available') }) if $price->source ne $price_source->best_price->source;
+        }
+      }
+      if ($discount->source) {
+        $column_data{discount_source} .= ' ' . $cgi->img({src => 'image/flag-red.png', alt => $discount->invalid, title => $discount->invalid }) if $discount->invalid;
+        $column_data{discount_source} .= ' ' . $cgi->img({src => 'image/flag-red.png', alt => $discount->missing, title => $discount->missing }) if $discount->missing;
+        if (!$discount->missing && !$discount->invalid) {
+          $column_data{price_source} .= ' ' . $cgi->img({src => 'image/up.png',   alt => t8('This discount has since gone up'),      title => t8('This discount has since gone up')      }) if $discount->discount * 100 > $record_item->discount;
+          $column_data{price_source} .= ' ' . $cgi->img({src => 'image/down.png', alt => t8('This discount has since gone down'),    title => t8('This discount has since gone down')    }) if $discount->discount * 100 < $record_item->discount;
+          $column_data{price_source} .= ' ' . $cgi->img({src => 'image/ok.png',   alt => t8('There is a better discount available'), title => t8('There is a better discount available') }) if $discount->source ne $price_source->best_discount->source;
+        }
       }
     }
 
@@ -428,7 +441,7 @@ sub display_row {
           $cgi->hidden("-name" => "unit_old_$i", "-value" => $form->{"selected_unit_$i"}),
           $cgi->hidden("-name" => "price_new_$i", "-value" => $form->format_amount(\%myconfig, $form->{"price_new_$i"})),
           map { ($cgi->hidden("-name" => $_, "-id" => $_, "-value" => $form->{$_})); } map { $_."_$i" }
-            (qw(orderitems_id bo price_old id inventory_accno bin partsgroup partnotes active_price_source
+            (qw(orderitems_id bo price_old id inventory_accno bin partsgroup partnotes active_price_source active_discount_source
                 income_accno expense_accno listprice assembly taxaccounts ordnumber donumber transdate cusordnumber
                 longdescription basefactor marge_absolut marge_percent marge_price_factor weight), @hidden_vars)
     );
@@ -469,7 +482,6 @@ sub select_item {
   $::form->header;
 
   my @item_list = map {
-    $_->{display_sellprice}  = $_->{sellprice} * (1 - $::form->{tradediscount});
     $_->{display_sellprice} /= $_->{price_factor} if ($_->{price_factor});
     $_;
   } @{ $::form->{item_list} };
@@ -536,6 +548,13 @@ sub item_selected {
     $::form->{"active_price_source_$i"} = $best_price->source;
   }
 
+  my $best_discount = $price_source->best_discount;
+
+  if ($best_discount) {
+    $::form->{"discount_$i"}               = $best_discount->discount;
+    $::form->{"active_discount_source_$i"} = $best_discount->source;
+  }
+
 
   $form->{"marge_price_factor_$i"} = $new_item->{price_factor};
 
@@ -557,11 +576,6 @@ sub item_selected {
       $form->{"sellprice_$i"} =
         $form->round_amount($form->{"sellprice_$i"}, $decimalplaces);
     }
-
-    # tradediscount
-    if ($::form->{tradediscount}) {
-      $::form->{"sellprice_$i"} *= 1 - $::form->{tradediscount};
-    }
   }
 
   map { $form->{$_} = $form->parse_amount(\%myconfig, $form->{$_}) }
@@ -710,7 +724,7 @@ sub remove_emptied_rows {
                 transdate longdescription basefactor marge_total marge_percent
                 marge_price_factor lastcost price_factor_id partnotes
                 stock_out stock_in has_sernumber reqdate orderitems_id
-                active_price_source);
+                active_price_source active_discount_source);
 
   my $ic_cvar_configs = CVar->get_configs(module => 'IC');
   push @flds, map { "ic_cvar_$_->{name}" } @{ $ic_cvar_configs };
index d982e7c..303eae0 100644 (file)
@@ -511,6 +511,20 @@ sub update {
         if ($sellprice) {
           $form->{"sellprice_$i"} = $sellprice;
         } else {
+          my $record        = _make_record();
+          my $price_source  = SL::PriceSource->new(record_item => $record->items->[$i-1], record => $record);
+          my $best_price    = $price_source->best_price;
+          my $best_discount = $price_source->best_discount;
+
+          if ($best_price) {
+            $::form->{"sellprice_$i"}           = $best_price->price;
+            $::form->{"active_price_source_$i"} = $best_price->source;
+          }
+          if ($best_discount) {
+            $::form->{"discount_$i"}               = $best_discount->discount;
+            $::form->{"active_discount_source_$i"} = $best_discount->source;
+          }
+
           # if there is an exchange rate adjust sellprice
           $form->{"sellprice_$i"} /= $exchangerate;
         }
index 66f6b44..f7ef78d 100644 (file)
@@ -587,8 +587,21 @@ sub update {
         if ($sellprice) {
           $form->{"sellprice_$i"} = $sellprice;
         } else {
+          my $record        = _make_record();
+          my $price_source  = SL::PriceSource->new(record_item => $record->items->[$i-1], record => $record);
+          my $best_price    = $price_source->best_price;
+          my $best_discount = $price_source->best_discount;
+
+          if ($best_price) {
+            $::form->{"sellprice_$i"}           = $best_price->price;
+            $::form->{"active_price_source_$i"} = $best_price->source;
+          }
+          if ($best_discount) {
+            $::form->{"discount_$i"}               = $best_discount->discount;
+            $::form->{"active_discount_source_$i"} = $best_discount->source;
+          }
+
           # if there is an exchange rate adjust sellprice
-          $form->{"sellprice_$i"} *= (1 - $form->{tradediscount});
           $form->{"sellprice_$i"} /= $exchangerate;
         }
 
index cc67e74..860d722 100644 (file)
@@ -678,16 +678,20 @@ sub update {
         if ($sellprice) {
           $form->{"sellprice_$i"} = $sellprice;
         } else {
-          my $record       = _make_record();
-          my $price_source = SL::PriceSource->new(record_item => $record->items->[$i-1], record => $record);
-          my $best_price   = $price_source->best_price;
+          my $record        = _make_record();
+          my $price_source  = SL::PriceSource->new(record_item => $record->items->[$i-1], record => $record);
+          my $best_price    = $price_source->best_price;
+          my $best_discount = $price_source->best_discount;
 
           if ($best_price) {
             $::form->{"sellprice_$i"}           = $best_price->price;
             $::form->{"active_price_source_$i"} = $best_price->source;
           }
+          if ($best_discount) {
+            $::form->{"discount_$i"}               = $best_discount->discount;
+            $::form->{"active_discount_source_$i"} = $best_discount->source;
+          }
 
-          $form->{"sellprice_$i"} *= (1 - $form->{tradediscount});
           $form->{"sellprice_$i"} /= $exchangerate;   # if there is an exchange rate adjust sellprice
         }
 
index 109b200..4758583 100644 (file)
@@ -43,4 +43,10 @@ namespace('kivi.io', function(ns) {
     if (price_str) $('#sellprice_' + row).val(price_str);
     $('#update_button').click();
   }
+
+  ns.update_discount_source = function(row, source, discount_str) {
+    $('#active_discount_source_' + row).val(source);
+    if (discount_str) $('#discount_' + row).val(discount_str);
+    $('#update_button').click();
+  }
 });
index c45b0ed..67093eb 100755 (executable)
@@ -342,6 +342,7 @@ $self->{texts} = {
   'Beratername'                 => 'Beratername',
   'Beraternummer'               => 'Beraternummer',
   'Best Before'                 => 'Mindesthaltbarkeit',
+  'Best Discount'               => 'Bester Rabatt',
   'Best Price'                  => 'Bester Preis',
   'Bilanz'                      => 'Bilanz',
   'Billable amount'             => 'Abrechenbarer Betrag',
@@ -383,6 +384,8 @@ $self->{texts} = {
   'Buchungsgruppen'             => 'Buchungsgruppen',
   'Buchungskonto'               => 'Buchungskonto',
   'Buchungsnummer'              => 'Buchungsnummer',
+  'Business'                    => 'Kunden-/Lieferantentyp',
+  'Business Discount'           => 'Kunden-/Lieferantentyp-Rabatt',
   'Business Number'             => 'Firmennummer',
   'Business Volume'             => 'Geschäftsvolumen',
   'Business evaluation'         => 'Betriebswirtschaftliche Auswertung',
@@ -561,6 +564,9 @@ $self->{texts} = {
   'Could not load class #1 (#2): "#3"' => 'Konnte Klasse #1 (#2) nicht laden: "#3"',
   'Could not load class #1, #2' => 'Konnte Klasse #1 nicht laden: "#2"',
   'Could not load employee'     => 'Konnte Benutzer nicht laden',
+  'Could not load this business' => 'Konnte diesen Kunden-/Lieferantentyp nicht laden',
+  'Could not load this customer' => 'Konnte diesen Kunden nicht laden',
+  'Could not load this vendor'  => 'Konnte diesen Lieferanten nicht laden',
   'Could not print dunning.'    => 'Die Mahnungen konnten nicht gedruckt werden.',
   'Could not spawn ghostscript.' => 'Die Anwendung "ghostscript" konnte nicht gestartet werden.',
   'Could not spawn the printer command.' => 'Die Druckanwendung konnte nicht gestartet werden.',
@@ -669,6 +675,7 @@ $self->{texts} = {
   'Customer'                    => 'Kunde',
   'Customer (database ID)'      => 'Kunde (Datenbank-ID)',
   'Customer (name)'             => 'Kunde (Name)',
+  'Customer Discount'           => 'Kundenrabatt',
   'Customer Master Data'        => 'Kundenstammdaten',
   'Customer Name'               => 'Kundenname',
   'Customer Number'             => 'Kundennummer',
@@ -825,6 +832,7 @@ $self->{texts} = {
   'Destination warehouse'       => 'Ziellager',
   'Destination warehouse and bin' => 'Ziellager und -lagerplatz',
   'Detail view'                 => 'Detailanzeige',
+  'Details'                     => 'Details',
   'Details (one letter abbreviation)' => 'D',
   'Dial command missing in kivitendo configuration\'s [cti] section' => 'Wählbefehl fehlt im Abschnitt [cti] der kivitendo-Konfiguration',
   'Difference'                  => 'Differenz',
@@ -834,6 +842,7 @@ $self->{texts} = {
   'Discard duplicate entries in CSV file' => 'Doppelte Einträge in CSV-Datei verwerfen',
   'Discard entries with duplicates in database or CSV file' => 'Einträge aus CSV-Datei verwerfen, die es bereits in der Datenbank oder der CSV-Datei gibt',
   'Discount'                    => 'Rabatt',
+  'Discounts'                   => 'Rabatte',
   'Display'                     => 'Anzeigen',
   'Display file'                => 'Datei anzeigen',
   'Display options'             => 'Anzeigeoptionen',
@@ -1601,6 +1610,7 @@ $self->{texts} = {
   'No.'                         => 'Position',
   'No/individual shipping address' => 'Keine/individuelle Lieferadresse',
   'None'                        => 'Kein',
+  'None (PriceSource Discount)' => 'Freier Rabatt',
   'None (PriceSource)'          => 'Freier Preis',
   'Normal users cannot log in.' => 'Normale Benutzer können sich nicht anmelden.',
   'Normalize Customer / Vendor names' => 'Normalisierung Kunden- / Lieferantennamen',
@@ -1853,6 +1863,7 @@ $self->{texts} = {
   'Pricegroup missing!'         => 'Preisgruppe fehlt!',
   'Pricegroup saved!'           => 'Preisgruppe gespeichert!',
   'Pricegroups'                 => 'Preisgruppen',
+  'Prices'                      => 'Preise',
   'Print'                       => 'Drucken',
   'Print and Post'              => 'Drucken und Buchen',
   'Print automatically'         => 'Automatisch ausdrucken',
@@ -2634,6 +2645,7 @@ $self->{texts} = {
   'There are still transfers not matching the qty of the delivery order. Stock operations can not be changed later. Do you really want to proceed?' => 'Einige der Lagerbewegungen sind nicht vollständig und Lagerbewegungen können nachträglich nicht mehr verändert werden. Wollen Sie wirklich fortfahren?',
   'There are undefined currencies in your system.' => 'In Ihrer Datenbank wurden Währungen benutzt, die nicht ordnungsgemäß in den Währungen eingetragen wurden.',
   'There are usually three ways to install Perl modules.' => 'Es gibt normalerweise drei Arten, ein Perlmodul zu installieren.',
+  'There is a better discount available' => 'Es is ein besserer Rabatt verfügbar',
   'There is a better price available' => 'Es ist ein besserer Preis verfügbar',
   'There is already a taxkey 0 with tax rate not 0.' => 'Es existiert bereits ein Steuerschlüssel mit Steuersatz ungleich 0%.',
   'There is an inconsistancy in your database.' => 'In Ihrer Datenbank sind Unstimmigkeiten vorhanden.',
@@ -2653,6 +2665,14 @@ $self->{texts} = {
   'This can be done with the following query:' => 'Dies kann mit der folgenden Datenbankabfrage erreicht werden:',
   'This could have happened for two reasons:' => 'Dies kann aus zwei Gründen geschehen sein:',
   'This customer number is already in use.' => 'Diese Kundennummer wird bereits verwendet.',
+  'This discount has since gone down' => 'Dieser Rabatt ist mittlerweile niedriger',
+  'This discount has since gone up' => 'Dieser Rabatt ist mittlerweile höher',
+  'This discount is only valid for business #1' => 'Dieser Rabatt ist nur für Kunden-/Lieferantentyp #1 gültig',
+  'This discount is only valid for customer #1' => 'Dieser Rabatt ist nur für Kunde #1 gültig',
+  'This discount is only valid for vendor #1' => 'Dieser Rabatt ist nur für Lieferant #1 gültig',
+  'This discount is only valid in purchase documents' => 'Dieser Rabatt ist nur in Einkaufsdokumenten gültig',
+  'This discount is only valid in records with customer or vendor' => 'Dieser Rabatt ist nur in Dokumenten mit Kunde oder Lieferant gültig',
+  'This discount is only valid in sales documents' => 'Dieser Rabatt ist nur in Verkaufsdokumenten gültig',
   'This feature especially prevents mistakes by mixing up prior tax and sales tax.' => 'Dieses Feature vermeidet insbesondere Verwechslungen von Umsatz- und Vorsteuer.',
   'This function requires the presence of articles with a time-based unit such as "h" or "min".' => 'Für diese Funktion mussen Artikel mit einer Zeit-basierten Einheit wie "Std" oder "min" existieren.',
   'This group is valid for the following clients' => 'Diese Gruppe ist für die folgenden Mandanten gültig',
@@ -2779,6 +2799,7 @@ $self->{texts} = {
   'Unsupported image type (supported types: #1)' => 'Nicht unterstützter Bildtyp (unterstützte Typen: #1)',
   'Until'                       => 'Bis',
   'Update'                      => 'Erneuern',
+  'Update Discount'             => 'Rabatt übernehmen',
   'Update Price'                => 'Preis übernehmen',
   'Update Prices'               => 'Preise aktualisieren',
   'Update SKR04: new tax account 3804 (19%)' => 'Update SKR04: neues Steuerkonto 3804 (19%) für innergemeinschaftlichen Erwerb',
@@ -2830,6 +2851,7 @@ $self->{texts} = {
   'Vendor'                      => 'Lieferant',
   'Vendor (database ID)'        => '(Datenbank-ID)',
   'Vendor (name)'               => 'Lieferant (Name)',
+  'Vendor Discount'             => 'Lieferantenrabatt',
   'Vendor Invoice'              => 'Einkaufsrechnung',
   'Vendor Invoices & AP Transactions' => 'Einkaufsrechnungen & Kreditorenbuchungen',
   'Vendor Name'                 => 'Lieferantenname',
diff --git a/sql/Pg-upgrade2/recorditem_active_dicount_source.sql b/sql/Pg-upgrade2/recorditem_active_dicount_source.sql
new file mode 100644 (file)
index 0000000..aea966a
--- /dev/null
@@ -0,0 +1,8 @@
+-- @tag: recorditem_active_record_source
+-- @description: Preisquellen: Rabatte
+-- @depends: release_3_1_0 recorditem_active_price_source
+-- @encoding: utf-8
+
+ALTER TABLE orderitems           ADD COLUMN active_discount_source TEXT NOT NULL DEFAULT '';
+ALTER TABLE delivery_order_items ADD COLUMN active_discount_source TEXT NOT NULL DEFAULT '';
+ALTER TABLE invoice              ADD COLUMN active_discount_source TEXT NOT NULL DEFAULT '';
index 92ee4eb..263cfd6 100644 (file)
@@ -3,12 +3,16 @@
 [%- USE L %]
 [%- USE LxERP %]
 [% SET best_price = price_source.best_price %]
+[% SET best_discount = price_source.best_discount %]
+  <h2>[% 'Prices' | $T8 %]</h2>
+
   <table>
    <tr class='listheading'>
     <th></th>
     <th>[% 'Price Source' | $T8 %]</th>
     <th>[% 'Price' | $T8 %]</th>
     <th>[% 'Best Price' | $T8 %]</th>
+    <th>[% 'Details' | $T8 %]</th>
    </tr>
    <tr class='listrow'>
 [%- IF price_source.record_item.active_price_source %]
     <td>[% 'None (PriceSource)' | $T8 %]</td>
     <td>-</td>
     <td></td>
+    <td></td>
    </tr>
    [%- FOREACH price IN price_source.available_prices %]
     <tr class='listrow'>
 [%- IF price_source.record_item.active_price_source != price.source %]
      <td>[% L.button_tag('kivi.io.update_price_source(' _ FORM.row _ ', \'' _ price.source _ '\', \'' _ LxERP.format_amount(price.price, -2) _ '\')', LxERP.t8('Select')) %]</td>
-[%- ELSIF price_source.record_item.sellprice_as_number != price.price_as_number %]
+[%- ELSIF price_source.record_item.sellprice != price.price %]
      <td>[% L.button_tag('kivi.io.update_price_source(' _ FORM.row _ ', \'' _ price.source _ '\', \'' _ LxERP.format_amount(price.price, -2) _ '\')', LxERP.t8('Update Price')) %]</td>
 [%- ELSE %]
     <td><b>[% 'Selected' | $T8 %]</b></td>
 [% END %]
-     <td>[% price.full_description | html %]</td>
+     <td>[% price.source_description | html %]</td>
      <td>[% price.price_as_number %]</td>
 [% IF price.source == best_price.source %]
      <td align='center'>&#x2022;</td>
 [% ELSE %]
      <td></td>
 [% END %]
+     <td>[% price.description | html %]</td>
+    </tr>
+   [%- END %]
+  </table>
+
+  <h2>[% 'Discounts' | $T8 %]</h2>
+
+  <table>
+   <tr class='listheading'>
+    <th></th>
+    <th>[% 'Price Source' | $T8 %]</th>
+    <th>[% 'Discount' | $T8 %]</th>
+    <th>[% 'Best Discount' | $T8 %]</th>
+    <th>[% 'Details' | $T8 %]</th>
+   </tr>
+   <tr class='listrow'>
+[%- IF price_source.record_item.active_discount_source %]
+    <td>[% L.button_tag('kivi.io.update_discount_source(' _ FORM.row _ ', \'\')', LxERP.t8('Select')) %]</td>
+[%- ELSE %]
+    <td><b>[% 'Selected' | $T8 %]</b></td>
+[%- END %]
+    <td>[% 'None (PriceSource Discount)' | $T8 %]</td>
+    <td>-</td>
+    <td></td>
+    <td></td>
+   </tr>
+   [%- FOREACH price IN price_source.available_discounts %]
+    <tr class='listrow'>
+[%- IF price_source.record_item.active_discount_source != price.source %]
+     <td>[% L.button_tag('kivi.io.update_discount_source(' _ FORM.row _ ', \'' _ price.source _ '\', \'' _ price.discount_as_percent _ '\')', LxERP.t8('Select')) %]</td>
+[%- ELSIF price_source.record_item.discount != price.discount * 100 %]
+     <td>[% L.button_tag('kivi.io.update_discount_source(' _ FORM.row _ ', \'' _ price.source _ '\', \'' _ price.discount_as_percent  _ '\')', LxERP.t8('Update Discount')) %]</td>
+[%- ELSE %]
+    <td><b>[% 'Selected' | $T8 %]</b></td>
+[% END %]
+     <td>[% price.source_description | html %]</td>
+     <td>[% price.discount_as_percent %] %</td>
+[% IF price.source == best_discount.source %]
+     <td align='center'>&#x2022;</td>
+[% ELSE %]
+     <td></td>
+[% END %]
+     <td>[% price.description | html %]</td>
     </tr>
    [%- END %]
   </table>