Auftrags-Controller: Einheiten per Select ändern können und sellprice anpassen.
[kivitendo-erp.git] / SL / PriceSource.pm
index e7a5906..66daad2 100644 (file)
@@ -4,6 +4,7 @@ use strict;
 use parent 'SL::DB::Object';
 use Rose::Object::MakeMethods::Generic (
   scalar => [ qw(record_item record) ],
+  'array --get_set_init' => [ qw(all_price_sources) ],
 );
 
 use List::UtilsBy qw(min_by max_by);
@@ -11,12 +12,12 @@ use SL::PriceSource::ALL;
 use SL::PriceSource::Price;
 use SL::Locale::String;
 
-sub all_price_sources {
+sub init_all_price_sources {
   my ($self) = @_;
 
-  map {
+  map {
     $_->new(record_item => $self->record_item, record => $self->record)
-  } SL::PriceSource::ALL->all_enabled_price_sources
+  } SL::PriceSource::ALL->all_enabled_price_sources ]
 }
 
 sub price_from_source {
@@ -30,20 +31,32 @@ sub price_from_source {
     : empty_price();
 }
 
+sub discount_from_source {
+  my ($self, $source) = @_;
+  my ($source_name, $spec) = split m{/}, $source, 2;
+
+  my $class = SL::PriceSource::ALL->price_source_class_by_name($source_name);
+
+  return $class
+    ? $class->new(record_item => $self->record_item, record => $self->record)->discount_from_source($source, $spec)
+    : empty_discount();
+}
+
 sub available_prices {
   map { $_->available_prices } $_[0]->all_price_sources;
 }
 
 sub available_discounts {
+  return if $_[0]->record_item->part->not_discountable;
   map { $_->available_discounts } $_[0]->all_price_sources;
 }
 
 sub best_price {
-  min_by { $_->price } grep { $_->price > 0 } grep { $_ } map { $_->best_price } $_[0]->all_price_sources;
+  min_by { $_->price } max_by { $_->priority } 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;
+  max_by { $_->discount } max_by { $_->priority } grep { $_->discount } grep { $_ } map { $_->best_discount } $_[0]->all_price_sources;
 }
 
 sub empty_price {
@@ -52,6 +65,12 @@ sub empty_price {
   );
 }
 
+sub empty_discount {
+  SL::PriceSource::Discount->new(
+    description => t8('None (PriceSource Discount)'),
+  );
+}
+
 1;
 
 __END__
@@ -70,16 +89,16 @@ together to calculate available prices for a position in a record.
 Each algorithm can access details of the record to realize dependencies on
 part, customer, vendor, date, quantity etc, which was previously not possible.
 
-=head1 BACKGROUND AND PHILOSOPY
+=head1 BACKGROUND AND PHILOSOPHY
 
 sql ledger and subsequently Lx-Office had three prices per part: sellprice,
 listprice and lastcost. At the moment a part is loaded into a record, the
-applicable price is copied and after that free to be changed.
+applicable price is copied and after that it is free to be changed.
 
-Later on additional things joined. Various types of discount, vendor pricelists
+Later on additional things were added. Various types of discount, vendor pricelists
 and the infamous price groups. The problem is not that those didn't work, the
-problem is, that they had to guess to much when to change a price with the
-available price from database, and when to leave the user entered price.
+problem is, that they had to guess too much when to change a price with the
+available price from the database, and when to leave the user entered price.
 
 Unrelated to that, users asked for more ways to store special prices, based on
 qty (block pricing, bulk discount), based on date (special offers), based on
@@ -134,30 +153,69 @@ not have to be registered in C<record>.
 
 Attempts to retrieve a formerly calculated price with the same conditions
 
+=item C<discount_from_source>
+
+Attempts to retrieve a formerly calculated discount with the same conditions
+
 =item C<available_prices>
 
 Returns all available prices.
 
+=item C<available_discounts>
+
+Returns all available discounts.
+
 =item C<best_price>
 
 Attempts to get the best available price. returns L<empty_price> if no price is found.
 
+=item C<best_discount>
+
+Attempts to get the best available discount. returns L<empty_discount> if no discount is found.
+
 =item C<empty_price>
 
 A special empty price, that does not change the previously entered price, and
 opens the price field to manual changes.
 
+=item C<empty_discount>
+
+A special empty discount, that does not change the previously entered discount, and
+opens the discount field to manual changes.
+
 =back
 
 =head1 SEE ALSO
 
 L<SL::PriceSource::Base>,
 L<SL::PriceSource::Price>,
+L<SL::PriceSource::Discount>,
 L<SL::PriceSource::ALL>
 
-=head1 BUGS
+=head1 BUGS AND CAVEATS
+
+=over 4
 
-None yet. :)
+=item *
+
+The current simple model of price sources providing a simple value in simple
+cases doesn't work well in situations where prices are modified by other
+properties. The same problem also causes headaches when trying to use price
+sources to compute positions in assemblies.
+
+The solution should be to split price sources in simple ones, which do not
+manage their interactions with record_items, but can be used in contexts
+without record_items, and complex ones which do, but have to be fed a dummy
+record_item. For the former there should be a wrapper that handles interactions
+with units, price_factors etc..
+
+=item *
+
+Currently it is only possible to provide additional prices, but not to restrict
+prices. Potential scenarios include credit limit customers which do not receive
+benefits from sales, or general ALLOW, DENY order calculation.
+
+=back
 
 =head1 AUTHOR