Revision 418f0e70
Von Sven Schöling vor etwa 10 Jahren hinzugefügt
SL/PriceSource.pm | ||
---|---|---|
40 | 40 |
|
41 | 41 |
sub empty_price { |
42 | 42 |
SL::PriceSource::Price->new( |
43 |
source => '', |
|
44 | 43 |
description => t8('None (PriceSource)'), |
45 | 44 |
); |
46 | 45 |
} |
... | ... | |
55 | 54 |
|
56 | 55 |
SL::PriceSource - mixin for price_sources in record items |
57 | 56 |
|
58 |
=head1 SYNOPSIS
|
|
57 |
=head1 DESCRIPTION
|
|
59 | 58 |
|
60 |
# in record item class |
|
59 |
PriceSource is an interface that allows generic algorithms to be plugged |
|
60 |
together to calculate available prices for a position in a record. |
|
61 | 61 |
|
62 |
use SL::PriceSource; |
|
62 |
Each algorithm can access details of the record to realize dependancies on |
|
63 |
part, customer, vendor, date, quantity etc, which was previously not possible. |
|
63 | 64 |
|
64 |
# later on:
|
|
65 |
=head1 BACKGROUND AND PHILOSOPY
|
|
65 | 66 |
|
66 |
$record_item->all_price_sources
|
|
67 |
$record_item->price_source # get
|
|
68 |
$record_item->price_source($c) # set
|
|
67 |
sql ledger and subsequently Lx-Office had three prices per part: sellprice,
|
|
68 |
listprice and lastcost. At the moment a part is loaded into a record, the
|
|
69 |
applicable price is copied and after that free to be changed.
|
|
69 | 70 |
|
70 |
$record_item->update_price_source # set price to calculated |
|
71 |
Later on additional things joined. Various types of discount, vendor pricelists |
|
72 |
and the infamous price groups. The problem is not that those didn't work, the |
|
73 |
problem is, that they had to guess to much when to change a price with the |
|
74 |
available price from database, and when to leave the user entered price. |
|
71 | 75 |
|
72 |
=head1 DESCRIPTION |
|
76 |
Unrelated to that, users asked for more ways to store special prices, based on |
|
77 |
qty (block pricing, bulk discount), based on date (special offers), based on |
|
78 |
customers (special terms), up to full blown calculation modules. |
|
79 |
|
|
80 |
On a third front sales personnel asked for ways to see what price options a |
|
81 |
position in a quotation has, and wanted information available when a price |
|
82 |
offer changed. |
|
83 |
|
|
84 |
Price sources put that together by making some compromises: |
|
85 |
|
|
86 |
=over 4 |
|
87 |
|
|
88 |
=item 1. |
|
89 |
|
|
90 |
Only change the price on creation of a position or when asked to. |
|
91 |
|
|
92 |
=item 2. |
|
93 |
|
|
94 |
Either set the price from a price source and let it be read only, or use a free |
|
95 |
price. |
|
96 |
|
|
97 |
=item 3. |
|
98 |
|
|
99 |
Save the origin of each price with the record so that the calculation can be |
|
100 |
reproduced. |
|
101 |
|
|
102 |
=item 4. |
|
103 |
|
|
104 |
Make price calculation flexible and pluggable. |
|
105 |
|
|
106 |
=back |
|
107 |
|
|
108 |
The first point creates user security by never changing a price for them |
|
109 |
without their explicit consent, eliminating all problems originating from |
|
110 |
trying to be smart. The second and third one ensure that later on the |
|
111 |
calculation can be repeated so that invalid prices can be caught (because for |
|
112 |
example the special offer is no longer valid), and so that sales personnel have |
|
113 |
information about rising or falling prices. The fourth point ensures that |
|
114 |
insular calculation processes can be developed independant of the core code. |
|
115 |
|
|
116 |
=head1 INTERFACE METHODS |
|
117 |
|
|
118 |
=over 4 |
|
119 |
|
|
120 |
=item C<new PARAMS> |
|
121 |
|
|
122 |
C<PARAMS> must contain both C<record> and C<record_item>. C<record_item> does |
|
123 |
not have to be registered in C<record>. |
|
124 |
|
|
125 |
=item C<price_from_source> |
|
126 |
|
|
127 |
Attempts to retrieve a formerly calculated price with the same conditions |
|
128 |
|
|
129 |
=item C<available_prices> |
|
130 |
|
|
131 |
Returns all available prices. |
|
132 |
|
|
133 |
=item C<best_price> |
|
73 | 134 |
|
74 |
This mixin provides a way to use price_source objects from within a record item. |
|
75 |
Record items in this contest mean OrderItems, InvoiceItems and |
|
76 |
DeliveryOrderItems. |
|
135 |
Attempts to get the best available price. returns L<empty_price> if no price is found. |
|
77 | 136 |
|
78 |
=head1 FUNCTIONS
|
|
137 |
=item C<empty_price>
|
|
79 | 138 |
|
80 |
price_sources |
|
139 |
A special empty price, that does not change the previously entered price, and |
|
140 |
opens the price field to manual changes. |
|
81 | 141 |
|
82 |
returns a list of price_source objects which are created with the current record |
|
83 |
item. |
|
142 |
=back |
|
84 | 143 |
|
85 |
active_price_source
|
|
144 |
=head1 SEE ALSO
|
|
86 | 145 |
|
87 |
returns the object representing the currently chosen price_source method or |
|
88 |
undef if custom price is chosen. Note that this must not necessarily be the |
|
89 |
active price, if something affecting the price_source has changed, the price |
|
90 |
calculated can differ from the price in the record. It is the responsibility of |
|
91 |
the implementing code to decide what to do in this case. |
|
146 |
L<SL::PriceSource::Base>, |
|
147 |
L<SL::PriceSource::Price>, |
|
148 |
L<SL::PriceSource::ALL> |
|
92 | 149 |
|
93 | 150 |
=head1 BUGS |
94 | 151 |
|
SL/PriceSource/Base.pm | ||
---|---|---|
29 | 29 |
|
30 | 30 |
=head1 NAME |
31 | 31 |
|
32 |
SL::PriceSource::Base - <oneliner description>
|
|
32 |
SL::PriceSource::Base - this is the base class for price source adapters
|
|
33 | 33 |
|
34 | 34 |
=head1 SYNOPSIS |
35 | 35 |
|
36 |
# in consuming module |
|
37 |
# TODO: thats bullshit, theres no need to have this pollute the namespace |
|
38 |
# make a manager that handles this |
|
36 |
# working example adapter: |
|
37 |
package SL::PriceSource::FiveOnEverything; |
|
39 | 38 |
|
40 |
my @list_of_price_sources = $record_item->price_sources; |
|
41 |
for (@list_of_price_sources) { |
|
42 |
my $internal_name = $_->name; |
|
43 |
my $translated_name = $_->description; |
|
44 |
my $price = $_->price; |
|
39 |
use parent qw(SL::PriceSource::Base); |
|
40 |
|
|
41 |
# used as internal identifier |
|
42 |
sub name { 'simple' } |
|
43 |
|
|
44 |
# used in frontend to signal where this comes from |
|
45 |
sub description { t8('Simple') } |
|
46 |
|
|
47 |
my $price = SL::PriceSource::Price->new( |
|
48 |
price => 5, |
|
49 |
description => t8('Only today 5$ on everything!'), |
|
50 |
price_source => $self, |
|
51 |
); |
|
52 |
|
|
53 |
# give list of prices that this |
|
54 |
sub available_prices { |
|
55 |
return ($price); |
|
45 | 56 |
} |
46 | 57 |
|
47 |
$record_item->set_active_price_source($price_source) # equivalent to:
|
|
48 |
$record_item->active_price_source($price_source->name);
|
|
49 |
$record_item->sellprice($price_source->price);
|
|
58 |
sub best_price {
|
|
59 |
return $price;
|
|
60 |
}
|
|
50 | 61 |
|
51 |
# for finer control
|
|
52 |
$price_source->needed_params
|
|
53 |
$price_source->supported_params
|
|
62 |
sub price_from_source {
|
|
63 |
return $price;
|
|
64 |
}
|
|
54 | 65 |
|
55 | 66 |
=head1 DESCRIPTION |
56 | 67 |
|
57 |
PriceSource is an interface that allows generic algorithms to be used, to |
|
58 |
calculate a price for a position in a record. |
|
68 |
See L<SL::PriceSource> for information about the mechanism. |
|
69 |
|
|
70 |
This is the base class for a price source algorithm. To play well, you'll have |
|
71 |
to implement a number of interface methods and be aware of a number of corner |
|
72 |
conditions. |
|
73 |
|
|
74 |
=head1 AVAILABLE METHODS |
|
75 |
|
|
76 |
=over 4 |
|
77 |
|
|
78 |
=item C<record_item> |
|
79 |
|
|
80 |
=item C<record> |
|
81 |
|
|
82 |
C<record> can be any one of L<SL::DB::Order>, L<SL::DB::DeliveryOrder>, |
|
83 |
L<SL::DB::Invoice>, L<SL::DB::PurchaseInvoice>. C<record_item> is of the |
|
84 |
corresponding position type. |
|
85 |
|
|
86 |
You can assume that both are filled with all information available at the time. |
|
87 |
C<part> and C<customer>/C<vendor> as well as C<is_sales> can be relied upon. You must NOT |
|
88 |
rely on both being linked together, in particular |
|
89 |
|
|
90 |
$self->record_item->record # don't do that |
|
59 | 91 |
|
60 |
If any such price_source algorithm is known to the system, a user can chose |
|
61 |
which of them should be used to claculate the price displayed in the record. |
|
92 |
is not guaranteed to work. |
|
62 | 93 |
|
63 |
The algorithm is saved togetherwith the target price, so that changes in the |
|
64 |
record can recalculate the price accordingly, and otherwise manual changes to |
|
65 |
the price can reset the price_source used to custom (aka no price_source). |
|
94 |
Also these are copies and not the original documents. Do not try to change |
|
95 |
anything and do not save those. |
|
96 |
|
|
97 |
=item C<part> |
|
98 |
|
|
99 |
Shortcut to C<< record_item->part >> |
|
100 |
|
|
101 |
=back |
|
66 | 102 |
|
67 | 103 |
=head1 INTERFACE METHODS |
68 | 104 |
|
... | ... | |
70 | 106 |
|
71 | 107 |
=item C<name> |
72 | 108 |
|
73 |
Should return a unique internal name. Should be entered in
|
|
74 |
L<SL::PriceSource::ALL> so that a name_to_class lookup works.
|
|
109 |
Must return a unique internal name. Must be entered in
|
|
110 |
L<SL::PriceSource::ALL>. |
|
75 | 111 |
|
76 | 112 |
=item C<description> |
77 | 113 |
|
78 |
Should return a translated name. |
|
114 |
Must return a translated name to be used in frontend. Will be used, to |
|
115 |
distinguish the origin of different prices. |
|
116 |
|
|
117 |
=item C<available_prices> |
|
79 | 118 |
|
80 |
=item C<needed_params> |
|
119 |
Must return a list of all prices that you algorithm can recommend the user |
|
120 |
for the current situation. Each price must have a unique spec that can be used |
|
121 |
to recreate it later. Try to be brief, no one needs 20 different price |
|
122 |
suggestions. |
|
81 | 123 |
|
82 |
Should return a list of elements that a record_item NEEDS to be used with this calulation.
|
|
124 |
=item C<best_price>
|
|
83 | 125 |
|
84 |
Both C<needed_params> nad C<supported_params> are purely informational at this point. |
|
126 |
Must return what you think of as the best matching price in your |
|
127 |
C<available_prices>. This does not have to be the lowest price, but it will be |
|
128 |
compared later to other price sources, and the lowest will be set. |
|
85 | 129 |
|
86 |
=item C<supported_params>
|
|
130 |
=item C<price_from_source SOURCE, SPEC>
|
|
87 | 131 |
|
88 |
Should return a list of elements that a record_item MAY HAVE to be used with this calulation. |
|
132 |
Must recreate the price from C<SPEC> and return. For reference, the complete |
|
133 |
C<SOURCE> entry from C<record_item.active_price_source> is included. |
|
89 | 134 |
|
90 |
Both C<needed_params> nad C<supported_params> are purely informational at this point. |
|
135 |
Note that constraints from the rest of the C<record> do not apply anymore. If |
|
136 |
information needed for the retrieval can be deleted elsewhere, then you must |
|
137 |
guard against that. |
|
91 | 138 |
|
92 |
=item C<price> |
|
139 |
If the price for the same coditions changed, return the new price. It will be |
|
140 |
presented as an option to the user if the record is still editable. |
|
93 | 141 |
|
94 |
Calculate a price and return. Do not mutate the record_item. Should will return |
|
95 |
undef if price is not applicable to the current record_item. |
|
142 |
If the price is not valid anymore or not reconstructable, return a price with |
|
143 |
C<price_source> and C<spec> set to the same values as before but with |
|
144 |
C<invalid> or C<missing> set. |
|
96 | 145 |
|
97 | 146 |
=back |
98 | 147 |
|
148 |
=head1 TRAPS AND CORNER CASES |
|
149 |
|
|
150 |
=over 4 |
|
151 |
|
|
152 |
=item * |
|
153 |
|
|
154 |
Be aware that all 8 types of record will be passed to your algorithm. If you |
|
155 |
don't serve some of them, just return emptry lists on C<available_prices> and |
|
156 |
C<best_price> |
|
157 |
|
|
158 |
=item * |
|
159 |
|
|
160 |
Information in C<record> might be missing. Especially on newly or automatically |
|
161 |
created records there might be fields not set at all. |
|
162 |
|
|
163 |
=item * |
|
164 |
|
|
165 |
Records will not be calculated. If you need tax data or position totals, you |
|
166 |
need to invoke that for yourself. |
|
167 |
|
|
168 |
=item * |
|
169 |
|
|
170 |
Accessor methods might not be present in some of the record types. |
|
171 |
|
|
172 |
=item * |
|
173 |
|
|
174 |
You do not need to do price factor and row discount calculation. These will be |
|
175 |
done automatically afterwards. You do have to include customer/vendor discount |
|
176 |
if your price interacts with those. |
|
177 |
|
|
178 |
=item * |
|
179 |
|
|
180 |
The price field in purchase records is still C<sellprice>. |
|
181 |
|
|
182 |
=item * |
|
183 |
|
|
184 |
C<source> and C<spec> are tainted. If you store data directly in C<spec>, sanitize. |
|
185 |
|
|
186 |
=head1 SEE ALSO |
|
187 |
|
|
188 |
L<SL::PriceSource>, |
|
189 |
L<SL::PriceSource::Price>, |
|
190 |
L<SL::PriceSource::ALL> |
|
191 |
|
|
99 | 192 |
=head1 BUGS |
100 | 193 |
|
101 | 194 |
None yet. :) |
SL/PriceSource/Price.pm | ||
---|---|---|
4 | 4 |
|
5 | 5 |
use parent 'SL::DB::Object'; |
6 | 6 |
use Rose::Object::MakeMethods::Generic ( |
7 |
scalar => [ qw(price description spec price_source) ], |
|
7 |
scalar => [ qw(price description spec price_source invalid missing) ],
|
|
8 | 8 |
array => [ qw(depends_on) ] |
9 | 9 |
); |
10 | 10 |
|
... | ... | |
32 | 32 |
} |
33 | 33 |
|
34 | 34 |
1; |
35 |
|
|
36 |
__END__ |
|
37 |
|
|
38 |
=encoding utf-8 |
|
39 |
|
|
40 |
=head1 NAME |
|
41 |
|
|
42 |
SL::PriceSource::Price - contrainer to pass calculated prices around |
|
43 |
|
|
44 |
=head1 SYNOPSIS |
|
45 |
|
|
46 |
# in PriceSource::Base implementation |
|
47 |
$price = SL::PriceSource::Price->new( |
|
48 |
price => 10.3, |
|
49 |
spec => '10.3', # something you can easily parse later |
|
50 |
description => t8('Fix price 10.3'), |
|
51 |
price_source => $self, |
|
52 |
) |
|
53 |
|
|
54 |
# special empty price in SL::PriceSource |
|
55 |
SL::PriceSource::Price->new( |
|
56 |
description => t8('None (PriceSource)'), |
|
57 |
); |
|
58 |
|
|
59 |
# invalid price |
|
60 |
SL::PriceSource::Price->new( |
|
61 |
price => $original_price, |
|
62 |
spec => $original_spec, |
|
63 |
description => $original_description, |
|
64 |
invalid => t8('Offer expired #1 weeks ago', $dt->delta_weeks), |
|
65 |
price_source => $self, |
|
66 |
); |
|
67 |
|
|
68 |
# missing price |
|
69 |
SL::PriceSource::Price->new( |
|
70 |
price => $original_price, # will keep last entered price |
|
71 |
spec => $original_spec, |
|
72 |
description => '', |
|
73 |
missing => t8('Um, sorry, cannot find that one'), |
|
74 |
price_source => $self, |
|
75 |
); |
|
76 |
|
|
77 |
|
|
78 |
=head1 DESCRIPTION |
|
79 |
|
|
80 |
See L<SL::PriceSource> for information about the mechanism. |
|
81 |
|
|
82 |
This is a container for prices that are generated by L<SL::PriceSource::Base> |
|
83 |
implementations. |
|
84 |
|
|
85 |
=head1 CONSTRUCTOR FIELDS |
|
86 |
|
|
87 |
=over 4 |
|
88 |
|
|
89 |
=item C<price> |
|
90 |
|
|
91 |
The price. A price of 0 is special and is considered undesirable. If passed as |
|
92 |
part of C<available_prices> it will be filtered out. If returned as |
|
93 |
C<best_price> or C<price_from_source> it will be warned about. |
|
94 |
|
|
95 |
=item C<spec> |
|
96 |
|
|
97 |
A unique string that can later be understood by the creating implementation. |
|
98 |
Can be empty if the implementation only supports one price for a given |
|
99 |
record_item. |
|
100 |
|
|
101 |
=item C<description> |
|
102 |
|
|
103 |
A localized short description of the origins of this price. |
|
104 |
|
|
105 |
=item C<price_source> |
|
106 |
|
|
107 |
A ref to the creating algorithm. |
|
108 |
|
|
109 |
=item C<missing> |
|
110 |
|
|
111 |
OPTIONAL. Both indicator and localized message that the price with this spec |
|
112 |
could not be reproduced and should be changed. |
|
113 |
|
|
114 |
=item C<invalid> |
|
115 |
|
|
116 |
OPTIONAL. Both indicator and localized message that the conditions for this |
|
117 |
price are no longer valid, and that the price should be changed. |
|
118 |
|
|
119 |
=back |
|
120 |
|
|
121 |
=head1 SEE ALSO |
|
122 |
|
|
123 |
L<SL::PriceSource>, |
|
124 |
L<SL::PriceSource::Base>, |
|
125 |
L<SL::PriceSource::ALL> |
|
126 |
|
|
127 |
=head1 BUGS |
|
128 |
|
|
129 |
None yet. :) |
|
130 |
|
|
131 |
=head1 AUTHOR |
|
132 |
|
|
133 |
Sven Schoeling E<lt>s.schoeling@linet-services.deE<gt> |
|
134 |
|
|
135 |
=cut |
Auch abrufbar als: Unified diff
PriceSource: Dokumentation