Projekt

Allgemein

Profil

Herunterladen (8,37 KB) Statistiken
| Zweig: | Markierung: | Revision:
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) ],
);

use List::UtilsBy qw(min_by max_by);
use SL::PriceSource::ALL;
use SL::PriceSource::Price;
use SL::Locale::String;

sub init_all_price_sources {
my ($self) = @_;

[ map {
$_->new(record_item => $self->record_item, record => $self->record)
} SL::PriceSource::ALL->all_enabled_price_sources ]
}

sub price_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)->price_from_source($source, $spec)
: 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 } max_by { $_->priority } grep { $_->price > 0 } grep { $_ } map { $_->best_price } $_[0]->all_price_sources;
}

sub best_discount {
max_by { $_->discount } max_by { $_->priority } grep { $_->discount } grep { $_ } map { $_->best_discount } $_[0]->all_price_sources;
}

sub empty_price {
SL::PriceSource::Price->new(
description => t8('None (PriceSource)'),
);
}

sub empty_discount {
SL::PriceSource::Discount->new(
description => t8('None (PriceSource Discount)'),
);
}

1;

__END__

=encoding utf-8

=head1 NAME

SL::PriceSource - mixin for price_sources in record items

=head1 DESCRIPTION

PriceSource is an interface that allows generic algorithms to be plugged
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 PHILOSOPHY

sql ledger and subsequently Lx-Office had three prices per part: sellprice,
listprice and lastcost. When adding an item to a record, the applicable price
was copied and after that it was free to be changed.

Later on additional things were added. Various types of discount, vendor pricelists
and the infamous price groups. The problem was not that those didn't work, the
problem was 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.

The result was that the price of an item in a record seemed to change on a
whim, and the origin of the price itself being opaque.

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
customers (special terms), up to full blown calculation modules.

On a third front sales personnel asked for ways to see what price options a
position in a quotation has, and wanted information available when prices
changed to make better informed choices about sales later in the workflow.

Price sources now extend the previous pricing by attaching a source to every
price in records. The information it provides are:

=over 4

=item 1.

Where did this price originate?

=item 2.

If this price would be calculated today, is it still the same as it was when
this record was created?

=item 3.

If I want to price an item in this record now, which prices are available?

=item 4.

Which one is the "best"?

=back

=head1 GUARANTEES

To ensure price source prices are comprehensible and reproducible, some
invariants are guaranteed:

=over 4

=item 1.

Price sources will never on their own change a price. They will offer options,
and it is up to the user to change a price.

=item 2.

If a price is set from a source, it is read only. A price edited manually is by
definition not a sourced price.

=item 3.

A price should be able to repeat the calculations done to arrive at the price
when it was first used. If these calculations are no longer applicable (special
offer expired) this should be signalled. If the calculations result in a
different price, this should be signalled. If the calculations fail (needed
information is no longer present) this must be signalled.

=back

The first point creates user security by never changing a price for them
without their explicit consent, eliminating all problems originating from
trying to be smart. The second and third one ensure that later on the
calculation can be repeated so that invalid prices can be caught (because for
example the special offer is no longer valid), and so that sales personnel have
information about rising or falling prices.

=head1 STRUCTURE

Price sources are managed by this package (L<SL::PriceSource>), and all
external access should be by using it's 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
L<SL::PriceSource::Discout>.

Returned prices and discounts should be checked for entries in C<invalid> and
C<missing>, see documentation in their classes.

=head1 INTERFACE METHODS

=over 4

=item C<new PARAMS>

C<PARAMS> must contain both C<record> and C<record_item>. C<record_item> does
not have to be registered in C<record>.

=item C<price_from_source>

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 AND CAVEATS

=over 4

=item *

The current model of price sources requires a record and a record_item for
every price calculation. This means that price structures can never be used
when no record is available, such as calculation the worth of assembly rows.

A possible solution is to either split price sources into simple and complex
ones (where the former do not require records).

Another would be to have default values for the inpout normally taken from
records (like qty defaulting to 1).

A last one would be to provide an alternative input channel for needed
properties.

=item *

Discount sources were implemented as a copy of the prices with slightly
different semantics. Need to do a real design. A requirement is, that a sinle
source can provide both prices and discounts (needed for price_rules).

=item *

Priorities are implemented ad hoc. The semantics which are chosen by the "best"
accessors are unintuitive because they do not guarantee anything. Better
terminology might help.

=item *

It is currently not possible to link a price to the price of the generating
record_item (i.e. the price of a delivery order item to the order item it was
generated from). This is crucial to enterprises that calculate all their prices
in orders, and update those after they made delivery orders.

=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.

=item *

Composing price sources is disallowed for clarity, but all price sources need
to be aware of units. This is madness.

=back

=head1 AUTHOR

Sven Schoeling E<lt>s.schoeling@linet-services.deE<gt>

=cut
(58-58/77)