Revision 418f0e70
Von Sven Schöling vor etwa 10 Jahren hinzugefügt
SL/PriceSource.pm | ||
---|---|---|
|
||
sub empty_price {
|
||
SL::PriceSource::Price->new(
|
||
source => '',
|
||
description => t8('None (PriceSource)'),
|
||
);
|
||
}
|
||
... | ... | |
|
||
SL::PriceSource - mixin for price_sources in record items
|
||
|
||
=head1 SYNOPSIS
|
||
=head1 DESCRIPTION
|
||
|
||
# in record item class
|
||
PriceSource is an interface that allows generic algorithms to be plugged
|
||
together to calculate available prices for a position in a record.
|
||
|
||
use SL::PriceSource;
|
||
Each algorithm can access details of the record to realize dependancies on
|
||
part, customer, vendor, date, quantity etc, which was previously not possible.
|
||
|
||
# later on:
|
||
=head1 BACKGROUND AND PHILOSOPY
|
||
|
||
$record_item->all_price_sources
|
||
$record_item->price_source # get
|
||
$record_item->price_source($c) # set
|
||
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.
|
||
|
||
$record_item->update_price_source # set price to calculated
|
||
Later on additional things joined. 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.
|
||
|
||
=head1 DESCRIPTION
|
||
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 a price
|
||
offer changed.
|
||
|
||
Price sources put that together by making some compromises:
|
||
|
||
=over 4
|
||
|
||
=item 1.
|
||
|
||
Only change the price on creation of a position or when asked to.
|
||
|
||
=item 2.
|
||
|
||
Either set the price from a price source and let it be read only, or use a free
|
||
price.
|
||
|
||
=item 3.
|
||
|
||
Save the origin of each price with the record so that the calculation can be
|
||
reproduced.
|
||
|
||
=item 4.
|
||
|
||
Make price calculation flexible and pluggable.
|
||
|
||
=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. The fourth point ensures that
|
||
insular calculation processes can be developed independant of the core code.
|
||
|
||
=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<available_prices>
|
||
|
||
Returns all available prices.
|
||
|
||
=item C<best_price>
|
||
|
||
This mixin provides a way to use price_source objects from within a record item.
|
||
Record items in this contest mean OrderItems, InvoiceItems and
|
||
DeliveryOrderItems.
|
||
Attempts to get the best available price. returns L<empty_price> if no price is found.
|
||
|
||
=head1 FUNCTIONS
|
||
=item C<empty_price>
|
||
|
||
price_sources
|
||
A special empty price, that does not change the previously entered price, and
|
||
opens the price field to manual changes.
|
||
|
||
returns a list of price_source objects which are created with the current record
|
||
item.
|
||
=back
|
||
|
||
active_price_source
|
||
=head1 SEE ALSO
|
||
|
||
returns the object representing the currently chosen price_source method or
|
||
undef if custom price is chosen. Note that this must not necessarily be the
|
||
active price, if something affecting the price_source has changed, the price
|
||
calculated can differ from the price in the record. It is the responsibility of
|
||
the implementing code to decide what to do in this case.
|
||
L<SL::PriceSource::Base>,
|
||
L<SL::PriceSource::Price>,
|
||
L<SL::PriceSource::ALL>
|
||
|
||
=head1 BUGS
|
||
|
SL/PriceSource/Base.pm | ||
---|---|---|
|
||
=head1 NAME
|
||
|
||
SL::PriceSource::Base - <oneliner description>
|
||
SL::PriceSource::Base - this is the base class for price source adapters
|
||
|
||
=head1 SYNOPSIS
|
||
|
||
# in consuming module
|
||
# TODO: thats bullshit, theres no need to have this pollute the namespace
|
||
# make a manager that handles this
|
||
# working example adapter:
|
||
package SL::PriceSource::FiveOnEverything;
|
||
|
||
my @list_of_price_sources = $record_item->price_sources;
|
||
for (@list_of_price_sources) {
|
||
my $internal_name = $_->name;
|
||
my $translated_name = $_->description;
|
||
my $price = $_->price;
|
||
use parent qw(SL::PriceSource::Base);
|
||
|
||
# used as internal identifier
|
||
sub name { 'simple' }
|
||
|
||
# used in frontend to signal where this comes from
|
||
sub description { t8('Simple') }
|
||
|
||
my $price = SL::PriceSource::Price->new(
|
||
price => 5,
|
||
description => t8('Only today 5$ on everything!'),
|
||
price_source => $self,
|
||
);
|
||
|
||
# give list of prices that this
|
||
sub available_prices {
|
||
return ($price);
|
||
}
|
||
|
||
$record_item->set_active_price_source($price_source) # equivalent to:
|
||
$record_item->active_price_source($price_source->name);
|
||
$record_item->sellprice($price_source->price);
|
||
sub best_price {
|
||
return $price;
|
||
}
|
||
|
||
# for finer control
|
||
$price_source->needed_params
|
||
$price_source->supported_params
|
||
sub price_from_source {
|
||
return $price;
|
||
}
|
||
|
||
=head1 DESCRIPTION
|
||
|
||
PriceSource is an interface that allows generic algorithms to be used, to
|
||
calculate a price for a position in a record.
|
||
See L<SL::PriceSource> for information about the mechanism.
|
||
|
||
This is the base class for a price source algorithm. To play well, you'll have
|
||
to implement a number of interface methods and be aware of a number of corner
|
||
conditions.
|
||
|
||
=head1 AVAILABLE METHODS
|
||
|
||
=over 4
|
||
|
||
=item C<record_item>
|
||
|
||
=item C<record>
|
||
|
||
C<record> can be any one of L<SL::DB::Order>, L<SL::DB::DeliveryOrder>,
|
||
L<SL::DB::Invoice>, L<SL::DB::PurchaseInvoice>. C<record_item> is of the
|
||
corresponding position type.
|
||
|
||
You can assume that both are filled with all information available at the time.
|
||
C<part> and C<customer>/C<vendor> as well as C<is_sales> can be relied upon. You must NOT
|
||
rely on both being linked together, in particular
|
||
|
||
$self->record_item->record # don't do that
|
||
|
||
If any such price_source algorithm is known to the system, a user can chose
|
||
which of them should be used to claculate the price displayed in the record.
|
||
is not guaranteed to work.
|
||
|
||
The algorithm is saved togetherwith the target price, so that changes in the
|
||
record can recalculate the price accordingly, and otherwise manual changes to
|
||
the price can reset the price_source used to custom (aka no price_source).
|
||
Also these are copies and not the original documents. Do not try to change
|
||
anything and do not save those.
|
||
|
||
=item C<part>
|
||
|
||
Shortcut to C<< record_item->part >>
|
||
|
||
=back
|
||
|
||
=head1 INTERFACE METHODS
|
||
|
||
... | ... | |
|
||
=item C<name>
|
||
|
||
Should return a unique internal name. Should be entered in
|
||
L<SL::PriceSource::ALL> so that a name_to_class lookup works.
|
||
Must return a unique internal name. Must be entered in
|
||
L<SL::PriceSource::ALL>.
|
||
|
||
=item C<description>
|
||
|
||
Should return a translated name.
|
||
Must return a translated name to be used in frontend. Will be used, to
|
||
distinguish the origin of different prices.
|
||
|
||
=item C<available_prices>
|
||
|
||
=item C<needed_params>
|
||
Must return a list of all prices that you algorithm can recommend the user
|
||
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.
|
||
|
||
Should return a list of elements that a record_item NEEDS to be used with this calulation.
|
||
=item C<best_price>
|
||
|
||
Both C<needed_params> nad C<supported_params> are purely informational at this point.
|
||
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<supported_params>
|
||
=item C<price_from_source SOURCE, SPEC>
|
||
|
||
Should return a list of elements that a record_item MAY HAVE to be used with this calulation.
|
||
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.
|
||
|
||
Both C<needed_params> nad C<supported_params> are purely informational at this point.
|
||
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
|
||
guard against that.
|
||
|
||
=item C<price>
|
||
If the price for the same coditions changed, return the new price. It will be
|
||
presented as an option to the user if the record is still editable.
|
||
|
||
Calculate a price and return. Do not mutate the record_item. Should will return
|
||
undef if price is not applicable to the current record_item.
|
||
If the price is not valid anymore or not reconstructable, return a price with
|
||
C<price_source> and C<spec> set to the same values as before but with
|
||
C<invalid> or C<missing> set.
|
||
|
||
=back
|
||
|
||
=head1 TRAPS AND CORNER CASES
|
||
|
||
=over 4
|
||
|
||
=item *
|
||
|
||
Be aware that all 8 types of record will be passed to your algorithm. If you
|
||
don't serve some of them, just return emptry lists on C<available_prices> and
|
||
C<best_price>
|
||
|
||
=item *
|
||
|
||
Information in C<record> might be missing. Especially on newly or automatically
|
||
created records there might be fields not set at all.
|
||
|
||
=item *
|
||
|
||
Records will not be calculated. If you need tax data or position totals, you
|
||
need to invoke that for yourself.
|
||
|
||
=item *
|
||
|
||
Accessor methods might not be present in some of the record types.
|
||
|
||
=item *
|
||
|
||
You do not need to do price factor and row discount calculation. These will be
|
||
done automatically afterwards. You do have to include customer/vendor discount
|
||
if your price interacts with those.
|
||
|
||
=item *
|
||
|
||
The price field in purchase records is still C<sellprice>.
|
||
|
||
=item *
|
||
|
||
C<source> and C<spec> are tainted. If you store data directly in C<spec>, sanitize.
|
||
|
||
=head1 SEE ALSO
|
||
|
||
L<SL::PriceSource>,
|
||
L<SL::PriceSource::Price>,
|
||
L<SL::PriceSource::ALL>
|
||
|
||
=head1 BUGS
|
||
|
||
None yet. :)
|
SL/PriceSource/Price.pm | ||
---|---|---|
|
||
use parent 'SL::DB::Object';
|
||
use Rose::Object::MakeMethods::Generic (
|
||
scalar => [ qw(price description spec price_source) ],
|
||
scalar => [ qw(price description spec price_source invalid missing) ],
|
||
array => [ qw(depends_on) ]
|
||
);
|
||
|
||
... | ... | |
}
|
||
|
||
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(
|
||
price => 10.3,
|
||
spec => '10.3', # something you can easily parse later
|
||
description => t8('Fix price 10.3'),
|
||
price_source => $self,
|
||
)
|
||
|
||
# special empty price in SL::PriceSource
|
||
SL::PriceSource::Price->new(
|
||
description => t8('None (PriceSource)'),
|
||
);
|
||
|
||
# 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,
|
||
);
|
||
|
||
# missing price
|
||
SL::PriceSource::Price->new(
|
||
price => $original_price, # will keep last entered price
|
||
spec => $original_spec,
|
||
description => '',
|
||
missing => t8('Um, sorry, cannot find that one'),
|
||
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<price>
|
||
|
||
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.
|
||
|
||
=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.
|
||
|
||
=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.
|
||
|
||
=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
|
Auch abrufbar als: Unified diff
PriceSource: Dokumentation