e7a5906dd2f968b750b6ac9aae7c39e1fc792cd2
[kivitendo-erp.git] / SL / PriceSource.pm
1 package SL::PriceSource;
2
3 use strict;
4 use parent 'SL::DB::Object';
5 use Rose::Object::MakeMethods::Generic (
6   scalar => [ qw(record_item record) ],
7 );
8
9 use List::UtilsBy qw(min_by max_by);
10 use SL::PriceSource::ALL;
11 use SL::PriceSource::Price;
12 use SL::Locale::String;
13
14 sub all_price_sources {
15   my ($self) = @_;
16
17   map {
18     $_->new(record_item => $self->record_item, record => $self->record)
19   } SL::PriceSource::ALL->all_enabled_price_sources
20 }
21
22 sub price_from_source {
23   my ($self, $source) = @_;
24   my ($source_name, $spec) = split m{/}, $source, 2;
25
26   my $class = SL::PriceSource::ALL->price_source_class_by_name($source_name);
27
28   return $class
29     ? $class->new(record_item => $self->record_item, record => $self->record)->price_from_source($source, $spec)
30     : empty_price();
31 }
32
33 sub available_prices {
34   map { $_->available_prices } $_[0]->all_price_sources;
35 }
36
37 sub available_discounts {
38   map { $_->available_discounts } $_[0]->all_price_sources;
39 }
40
41 sub best_price {
42   min_by { $_->price } grep { $_->price > 0 } grep { $_ } map { $_->best_price } $_[0]->all_price_sources;
43 }
44
45 sub best_discount {
46   max_by { $_->discount } grep { $_->discount } grep { $_ } map { $_->best_discount } $_[0]->all_price_sources;
47 }
48
49 sub empty_price {
50   SL::PriceSource::Price->new(
51     description => t8('None (PriceSource)'),
52   );
53 }
54
55 1;
56
57 __END__
58
59 =encoding utf-8
60
61 =head1 NAME
62
63 SL::PriceSource - mixin for price_sources in record items
64
65 =head1 DESCRIPTION
66
67 PriceSource is an interface that allows generic algorithms to be plugged
68 together to calculate available prices for a position in a record.
69
70 Each algorithm can access details of the record to realize dependencies on
71 part, customer, vendor, date, quantity etc, which was previously not possible.
72
73 =head1 BACKGROUND AND PHILOSOPY
74
75 sql ledger and subsequently Lx-Office had three prices per part: sellprice,
76 listprice and lastcost. At the moment a part is loaded into a record, the
77 applicable price is copied and after that free to be changed.
78
79 Later on additional things joined. Various types of discount, vendor pricelists
80 and the infamous price groups. The problem is not that those didn't work, the
81 problem is, that they had to guess to much when to change a price with the
82 available price from database, and when to leave the user entered price.
83
84 Unrelated to that, users asked for more ways to store special prices, based on
85 qty (block pricing, bulk discount), based on date (special offers), based on
86 customers (special terms), up to full blown calculation modules.
87
88 On a third front sales personnel asked for ways to see what price options a
89 position in a quotation has, and wanted information available when a price
90 offer changed.
91
92 Price sources put that together by making some compromises:
93
94 =over 4
95
96 =item 1.
97
98 Only change the price on creation of a position or when asked to.
99
100 =item 2.
101
102 Either set the price from a price source and let it be read only, or use a free
103 price.
104
105 =item 3.
106
107 Save the origin of each price with the record so that the calculation can be
108 reproduced.
109
110 =item 4.
111
112 Make price calculation flexible and pluggable.
113
114 =back
115
116 The first point creates user security by never changing a price for them
117 without their explicit consent, eliminating all problems originating from
118 trying to be smart. The second and third one ensure that later on the
119 calculation can be repeated so that invalid prices can be caught (because for
120 example the special offer is no longer valid), and so that sales personnel have
121 information about rising or falling prices. The fourth point ensures that
122 insular calculation processes can be developed independent of the core code.
123
124 =head1 INTERFACE METHODS
125
126 =over 4
127
128 =item C<new PARAMS>
129
130 C<PARAMS> must contain both C<record> and C<record_item>. C<record_item> does
131 not have to be registered in C<record>.
132
133 =item C<price_from_source>
134
135 Attempts to retrieve a formerly calculated price with the same conditions
136
137 =item C<available_prices>
138
139 Returns all available prices.
140
141 =item C<best_price>
142
143 Attempts to get the best available price. returns L<empty_price> if no price is found.
144
145 =item C<empty_price>
146
147 A special empty price, that does not change the previously entered price, and
148 opens the price field to manual changes.
149
150 =back
151
152 =head1 SEE ALSO
153
154 L<SL::PriceSource::Base>,
155 L<SL::PriceSource::Price>,
156 L<SL::PriceSource::ALL>
157
158 =head1 BUGS
159
160 None yet. :)
161
162 =head1 AUTHOR
163
164 Sven Schoeling E<lt>s.schoeling@linet-services.deE<gt>
165
166 =cut