changelog zu "Dateimanagement: erst nur letzte Version anzeigen ..."
[kivitendo-erp.git] / SL / PriceSource.pm
index 831f68e..05168ce 100644 (file)
@@ -3,8 +3,14 @@ package SL::PriceSource;
 use strict;
 use parent 'SL::DB::Object';
 use Rose::Object::MakeMethods::Generic (
 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) ],
+  scalar => [ qw(record_item record fast) ],
+  'scalar --get_set_init' => [ qw(
+    best_price best_discount
+  ) ],
+  'array --get_set_init' => [ qw(
+    all_price_sources
+    available_prices available_discounts
+  ) ],
 );
 
 use List::UtilsBy qw(min_by max_by);
 );
 
 use List::UtilsBy qw(min_by max_by);
@@ -16,46 +22,62 @@ sub init_all_price_sources {
   my ($self) = @_;
 
   [ map {
   my ($self) = @_;
 
   [ map {
-    $_->new(record_item => $self->record_item, record => $self->record)
+    $self->price_source_by_class($_);
   } SL::PriceSource::ALL->all_enabled_price_sources ]
 }
 
   } SL::PriceSource::ALL->all_enabled_price_sources ]
 }
 
+sub price_source_by_class {
+  my ($self, $class) = @_;
+  return unless $class;
+
+  $self->{price_source_by_name}{$class} //=
+    $class->new(record_item => $self->record_item, record => $self->record, fast => $self->fast);
+}
+
 sub price_from_source {
   my ($self, $source) = @_;
 sub price_from_source {
   my ($self, $source) = @_;
-  my ($source_name, $spec) = split m{/}, $source, 2;
+  return empty_price() if !$source;
 
 
-  my $class = SL::PriceSource::ALL->price_source_class_by_name($source_name);
+  ${ $self->{price_from_source} //= {} }{$source} //= do {
+    my ($source_name, $spec) = split m{/}, $source, 2;
+    my $class = SL::PriceSource::ALL->price_source_class_by_name($source_name);
+    my $source_object = $self->price_source_by_class($class);
 
 
-  return $class
-    ? $class->new(record_item => $self->record_item, record => $self->record)->price_from_source($source, $spec)
-    : empty_price();
+    $source_object
+      ? $source_object->price_from_source($source, $spec)
+      : empty_price();
+  }
 }
 
 sub discount_from_source {
   my ($self, $source) = @_;
 }
 
 sub discount_from_source {
   my ($self, $source) = @_;
-  my ($source_name, $spec) = split m{/}, $source, 2;
+  return empty_discount() if !$source;
 
 
-  my $class = SL::PriceSource::ALL->price_source_class_by_name($source_name);
+  ${ $self->{discount_from_source} //= {} }{$source} //= do {
+    my ($source_name, $spec) = split m{/}, $source, 2;
+    my $class = SL::PriceSource::ALL->price_source_class_by_name($source_name);
+    my $source_object = $self->price_source_by_class($class);
 
 
-  return $class
-    ? $class->new(record_item => $self->record_item, record => $self->record)->discount_from_source($source, $spec)
-    : empty_discount();
+    $source_object
+      ? $source_object->discount_from_source($source, $spec)
+      : empty_discount();
+  }
 }
 
 }
 
-sub available_prices {
-  map { $_->available_prices } $_[0]->all_price_sources;
+sub init_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 init_available_discounts {
+  return [] if $_[0]->record_item->part->not_discountable;
+  [ map { $_->available_discounts } $_[0]->all_price_sources ];
 }
 
 }
 
-sub best_price {
+sub init_best_price {
   min_by { $_->price } max_by { $_->priority } 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 {
+sub init_best_discount {
   max_by { $_->discount } max_by { $_->priority } 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;
 }
 
@@ -154,7 +176,7 @@ from messing it up. By default this means the price will be read-only.
 Implementations can choose to make prices editable, but even then deviations
 from the calculatied price will be marked.
 
 Implementations can choose to make prices editable, but even then deviations
 from the calculatied price will be marked.
 
-A that is not set from a source will not have any of this.
+A price that is not set from a source will not have any of this.
 
 =item 3.
 
 
 =item 3.
 
@@ -176,12 +198,12 @@ information about rising or falling prices.
 =head1 STRUCTURE
 
 Price sources are managed by this package (L<SL::PriceSource>), and all
 =head1 STRUCTURE
 
 Price sources are managed by this package (L<SL::PriceSource>), and all
-external access should be by using it's interface.
+external access should be by using its interface.
 
 Each source is an instance of L<SL::PriceSource::Base> and the available
 implementations are recorded in L<SL::PriceSource::ALL>. Prices and discounts
 returned by interface methods are instances of L<SL::PriceSource::Price> and
 
 Each source is an instance of L<SL::PriceSource::Base> and the available
 implementations are recorded in L<SL::PriceSource::ALL>. Prices and discounts
 returned by interface methods are instances of L<SL::PriceSource::Price> and
-L<SL::PriceSource::Discout>.
+L<SL::PriceSource::Discount>.
 
 Returned prices and discounts should be checked for entries in C<invalid> and
 C<missing>, see documentation in their classes.
 
 Returned prices and discounts should be checked for entries in C<invalid> and
 C<missing>, see documentation in their classes.
@@ -213,24 +235,37 @@ Returns all available discounts.
 
 =item C<best_price>
 
 
 =item C<best_price>
 
-Attempts to get the best available price. returns L<empty_price> if no price is found.
+Attempts to get the best available price. returns L<empty_price> if no price is
+found.
 
 =item C<best_discount>
 
 
 =item C<best_discount>
 
-Attempts to get the best available discount. returns L<empty_discount> if no discount is found.
+Attempts to get the best available discount. returns L<empty_discount> if no
+discount is found.
 
 =item C<empty_price>
 
 
 =item C<empty_price>
 
-A special empty price, that does not change the previously entered price, and
+A special empty price that does not change the previously entered price and
 opens the price field to manual changes.
 
 =item C<empty_discount>
 
 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.
+A special empty discount that does not change the previously entered discount
+and opens the discount field to manual changes.
+
+=item C<fast>
+
+If set to true, indicates that calls may skip doing intensive work and instead
+return a price or discount flagged as unknown. The caller must be prepared to
+deal with those.
+
+Typically this is intended to delay expensive calculations until they can be
+done in a second batch pass. If the information is already present, it is still
+encouraged that implementations return the correct values.
 
 =back
 
 
 =back
 
+
 =head1 SEE ALSO
 
 L<SL::PriceSource::Base>,
 =head1 SEE ALSO
 
 L<SL::PriceSource::Base>,
@@ -305,11 +340,34 @@ Specifically when changing from sales to purchase records prices don't make
 sense anymore. The guarantees should be updated to reflect this and
 transposition guidelines should be documented.
 
 sense anymore. The guarantees should be updated to reflect this and
 transposition guidelines should be documented.
 
+The previously mentioned linked prices can emulated by allowing price sources
+to set a new price when changing to a new record in the workflow. The decision
+about whether a price is eligable to be set can be suggested by the price
+source implementation but is ultimately up to the surrounding framework, which
+can make this configurable.
+
 =item *
 
 Prices were originally planned as a context element rather than a modal popup.
 It would be great to have this now with better framework.
 
 =item *
 
 Prices were originally planned as a context element rather than a modal popup.
 It would be great to have this now with better framework.
 
+=item *
+
+Large records (30 positions or more) in combination with complicated price
+sources run into n+1 problems. There should be an extra hook that allows price
+source implementations to make bulk calculations before the actual position loop.
+
+=item *
+
+Prices have defined information channels for missing and invalid, but it would
+be deriable to have more information flow. For example a limited offer might
+expire in three days while the record is valid for 20 days. THis mismatch is
+impossible to resolve automatically, but informing the user about it would be a
+nice thing.
+
+This can also extend to diagnostics on class level, where implementations can
+call attention to likely misconfigurations.
+
 =back
 
 =head1 AUTHOR
 =back
 
 =head1 AUTHOR