From 418f0e7084b02c1c057e4f10b858b6bffc25e354 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Sven=20Sch=C3=B6ling?= Date: Mon, 28 Jul 2014 17:56:17 +0200 Subject: [PATCH] PriceSource: Dokumentation --- SL/PriceSource.pm | 103 +++++++++++++++++++------ SL/PriceSource/Base.pm | 161 +++++++++++++++++++++++++++++++--------- SL/PriceSource/Price.pm | 103 ++++++++++++++++++++++++- 3 files changed, 309 insertions(+), 58 deletions(-) diff --git a/SL/PriceSource.pm b/SL/PriceSource.pm index 7a631bab4..470182cc9 100644 --- a/SL/PriceSource.pm +++ b/SL/PriceSource.pm @@ -40,7 +40,6 @@ sub best_price { sub empty_price { SL::PriceSource::Price->new( - source => '', description => t8('None (PriceSource)'), ); } @@ -55,40 +54,98 @@ __END__ 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 + +C must contain both C and C. C does +not have to be registered in C. + +=item C + +Attempts to retrieve a formerly calculated price with the same conditions + +=item C + +Returns all available prices. + +=item C -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 if no price is found. -=head1 FUNCTIONS +=item C -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, +L, +L =head1 BUGS diff --git a/SL/PriceSource/Base.pm b/SL/PriceSource/Base.pm index 618512d94..b095684cf 100644 --- a/SL/PriceSource/Base.pm +++ b/SL/PriceSource/Base.pm @@ -29,40 +29,76 @@ __END__ =head1 NAME -SL::PriceSource::Base - +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 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 + +=item C + +C can be any one of L, L, +L, L. C is of the +corresponding position type. + +You can assume that both are filled with all information available at the time. +C and C/C as well as C 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 + +Shortcut to C<< record_item->part >> + +=back =head1 INTERFACE METHODS @@ -70,32 +106,89 @@ the price can reset the price_source used to custom (aka no price_source). =item C -Should return a unique internal name. Should be entered in -L so that a name_to_class lookup works. +Must return a unique internal name. Must be entered in +L. =item C -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 -=item C +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 -Both C nad C are purely informational at this point. +Must return what you think of as the best matching price in your +C. 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 +=item C -Should return a list of elements that a record_item MAY HAVE to be used with this calulation. +Must recreate the price from C and return. For reference, the complete +C entry from C is included. -Both C nad C are purely informational at this point. +Note that constraints from the rest of the C do not apply anymore. If +information needed for the retrieval can be deleted elsewhere, then you must +guard against that. -=item C +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 and C set to the same values as before but with +C or C 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 and +C + +=item * + +Information in C 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. + +=item * + +C and C are tainted. If you store data directly in C, sanitize. + +=head1 SEE ALSO + +L, +L, +L + =head1 BUGS None yet. :) diff --git a/SL/PriceSource/Price.pm b/SL/PriceSource/Price.pm index 593c5ff90..23d3a9ec2 100644 --- a/SL/PriceSource/Price.pm +++ b/SL/PriceSource/Price.pm @@ -4,7 +4,7 @@ use strict; 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) ] ); @@ -32,3 +32,104 @@ sub to_str { } 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 for information about the mechanism. + +This is a container for prices that are generated by L +implementations. + +=head1 CONSTRUCTOR FIELDS + +=over 4 + +=item C + +The price. A price of 0 is special and is considered undesirable. If passed as +part of C it will be filtered out. If returned as +C or C it will be warned about. + +=item C + +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 + +A localized short description of the origins of this price. + +=item C + +A ref to the creating algorithm. + +=item C + +OPTIONAL. Both indicator and localized message that the price with this spec +could not be reproduced and should be changed. + +=item C + +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, +L, +L + +=head1 BUGS + +None yet. :) + +=head1 AUTHOR + +Sven Schoeling Es.schoeling@linet-services.deE + +=cut -- 2.20.1