X-Git-Url: http://wagnertech.de/git?a=blobdiff_plain;f=SL%2FPriceSource.pm;h=05168ce679f1d7ee3e5caaf4dd4cf5d07b934fac;hb=f05bd96b031d7d4ffaf4804704684ae929a0890a;hp=831f68ebc99a0d035ac07d8053b16ca556b08d2a;hpb=49f71dbacb987abe0b14bb3895be269830b7aed9;p=kivitendo-erp.git diff --git a/SL/PriceSource.pm b/SL/PriceSource.pm index 831f68ebc..05168ce67 100644 --- a/SL/PriceSource.pm +++ b/SL/PriceSource.pm @@ -3,8 +3,14 @@ package SL::PriceSource; 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); @@ -16,46 +22,62 @@ sub init_all_price_sources { my ($self) = @_; [ map { - $_->new(record_item => $self->record_item, record => $self->record) + $self->price_source_by_class($_); } 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) = @_; - 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) = @_; - 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; } -sub best_discount { +sub init_best_discount { 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. -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. @@ -176,12 +198,12 @@ information about rising or falling prices. =head1 STRUCTURE Price sources are managed by this package (L), 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 and the available implementations are recorded in L. Prices and discounts returned by interface methods are instances of L and -L. +L. Returned prices and discounts should be checked for entries in C and C, see documentation in their classes. @@ -213,24 +235,37 @@ Returns all available discounts. =item C -Attempts to get the best available price. returns L if no price is found. +Attempts to get the best available price. returns L if no price is +found. =item C -Attempts to get the best available discount. returns L if no discount is found. +Attempts to get the best available discount. returns L if no +discount is found. =item C -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 -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 + +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 + =head1 SEE ALSO L, @@ -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. +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 * + +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