File Coverage

blib/lib/Interchange6/Cart/Product.pm
Criterion Covered Total %
statement 44 44 100.0
branch 10 10 100.0
condition n/a
subroutine 17 17 100.0
pod 2 2 100.0
total 73 73 100.0


line stmt bran cond sub pod time code
1             # Interchange6::Cart::Product - Interchange6 cart product class
2              
3             package Interchange6::Cart::Product;
4              
5 3     3   17 use strict;
  3         5  
  3         72  
6 3     3   2231 use Moo;
  3         31623  
  3         13  
7 3     3   6429 use MooseX::CoverableModifiers;
  3         29804  
  3         27  
8 3     3   3203 use MooX::HandlesVia;
  3         31658  
  3         18  
9 3     3   2963 use Types::Standard qw/Defined HashRef InstanceOf Int Num Str Undef/;
  3         187624  
  3         47  
10 3     3   6190 use Types::Common::Numeric qw/PositiveInt PositiveOrZeroNum/;
  3         30515  
  3         63  
11 3     3   3509 use Types::Common::String qw/NonEmptyStr/;
  3         118338  
  3         45  
12             with 'Interchange6::Role::Costs';
13              
14 3     3   3570 use namespace::clean;
  3         24335  
  3         13  
15              
16             =head1 NAME
17              
18             Interchange6::Cart::Product - Cart product class for Interchange6 Shop Machine
19              
20             =head1 DESCRIPTION
21              
22             Cart product class for L.
23              
24             See L for details of cost attributes and methods.
25              
26             =head1 ATTRIBUTES
27              
28             See also L.
29              
30             Each cart product has the following attributes:
31              
32             =head2 id
33              
34             Can be used by subclasses, e.g. primary key value for cart products in the database.
35              
36             =cut
37              
38             has id => (
39             is => 'ro',
40             isa => Int,
41             );
42              
43             =head2 cart
44              
45             A reference to the Cart object that this Cart::Product belongs to.
46              
47             =over
48              
49             =item Writer: C
50              
51             =back
52              
53             =cut
54              
55             has cart => (
56             is => 'ro',
57             isa => Undef | InstanceOf ['Interchange6::Cart'],
58             default => undef,
59             writer => 'set_cart',
60             );
61              
62             =head2 name
63              
64             Product name is required.
65              
66             =cut
67              
68             has name => (
69             is => 'ro',
70             isa => Defined & NonEmptyStr,
71             required => 1,
72             );
73              
74             =head2 price
75              
76             Product price is required and a positive number or zero.
77              
78             Price is required, because you want to maintain the price that was valid at the time of adding to the cart. Should the price in the shop change in the meantime, it will maintain this price.
79              
80             =over
81              
82             =item Writer: C
83              
84             =back
85              
86             =cut
87              
88             has price => (
89             is => 'ro',
90             isa => PositiveOrZeroNum,
91             required => 1,
92             writer => 'set_price',
93             );
94              
95             =head2 selling_price
96              
97             Selling price is the price after group pricing, tier pricing or promotional discounts have been applied. If it is not set then it defaults to L.
98              
99             =over
100              
101             =item Writer: C
102              
103             =back
104              
105             =cut
106              
107             has selling_price => (
108             is => 'lazy',
109             isa => PositiveOrZeroNum,
110             writer => 'set_selling_price',
111             );
112              
113             sub _build_selling_price {
114 16     16   4049 my $self = shift;
115 16         335 return $self->price;
116             }
117              
118             =head2 discount_percent
119              
120             This is the integer discount percentage calculated from the difference
121             between L and L. This attribute should not normally
122             be set since as it is a calculated value.
123              
124             L is cleared if either L or
125             L methods are called.
126              
127             =cut
128              
129             has discount_percent => (
130             is => 'lazy',
131             clearer => 1
132             );
133              
134             sub _build_discount_percent {
135 3     3   2380 my $self = shift;
136 3 100       142 return 0 if $self->price == $self->selling_price;
137 1         32 return int( ( $self->price - $self->selling_price ) / $self->price * 100 );
138             }
139              
140             after 'set_price', 'set_selling_price' => sub {
141 5     5   9571 shift->clear_discount_percent;
142             };
143              
144             =head2 quantity
145              
146             Product quantity is optional and has to be a natural number greater
147             than zero. Default for quantity is 1.
148              
149             =cut
150              
151             has quantity => (
152             is => 'ro',
153             isa => PositiveInt,
154             default => 1,
155             writer => 'set_quantity',
156             );
157              
158             after quantity => sub {
159 99     99   4736 my $self = shift;
160 99         1884 $self->clear_subtotal;
161 99         1640 $self->clear_total;
162             };
163              
164             =head2 sku
165              
166             Unique product identifier is required.
167              
168             =cut
169              
170             has sku => (
171             is => 'ro',
172             isa => NonEmptyStr,
173             required => 1,
174             );
175              
176             =head2 canonical_sku
177              
178             If this product is a variant of a "parent" product then C
179             is the sku of the parent product.
180              
181             =cut
182              
183             has canonical_sku => (
184             is => 'ro',
185             default => undef,
186             );
187              
188             =head2 subtotal
189              
190             Subtotal calculated as L * L. Lazy set via builder.
191              
192             =cut
193              
194             has subtotal => (
195             is => 'lazy',
196             isa => Num,
197             clearer => 1,
198             predicate => 1,
199             );
200              
201             sub _build_subtotal {
202 33     33   3517 my $self = shift;
203 33         598 return sprintf( "%.2f", $self->selling_price * $self->quantity);
204             }
205              
206             =head2 uri
207              
208             Product uri
209              
210             =cut
211              
212             has uri => (
213             is => 'ro',
214             isa => Str,
215             );
216              
217             =head2 weight
218              
219             Weight of quantity 1 of this product.
220              
221             =cut
222              
223             has weight => (
224             is => 'ro',
225             isa => Num,
226             writer => 'set_weight',
227             );
228              
229             =head2 extra
230              
231             Hash reference of extra things the cart product might want to store such as:
232              
233             =over
234              
235             =item * variant attributes in order to be able to change variant within cart
236              
237             =item * simple attributes to allow display of them within cart
238              
239             =back
240              
241             =cut
242              
243             has extra => (
244             is => 'ro',
245             isa => HashRef,
246             default => sub { {} },
247             handles_via => 'Hash',
248             handles => {
249             get_extra => 'get',
250             set_extra => 'set',
251             delete_extra => 'delete',
252             keys_extra => 'keys',
253             clear_extra => 'clear',
254             exists_extra => 'exists',
255             defined_extra => 'defined',
256             },
257             );
258              
259             =head1 METHODS
260              
261             See also L.
262              
263             =head2 L methods
264              
265             =over
266              
267             =item * get_extra($key, $key2, $key3...)
268              
269             See L
270              
271             =item * set_extra($key => $value, $key2 => $value2...)
272              
273             See L
274              
275             =item * delete_extra($key, $key2, $key3...)
276              
277             See L
278              
279             =item * keys_extra
280              
281             See L
282              
283             =item * clear_extra
284              
285             See L
286              
287             =item * exists_extra($key)
288              
289             See L
290              
291             =item * defined_extra($key)
292              
293             See L
294              
295             =back
296              
297             =head2 L methods
298              
299             =over
300              
301             =item * clear_subtotal
302              
303             Clears L.
304              
305             =item * has_subtotal
306              
307             predicate on L.
308              
309             =back
310              
311             =head2 is_variant
312              
313             Returns 1 if L is defined else 0.
314              
315             =cut
316              
317             sub is_variant {
318 2 100   2 1 15 return defined shift->canonical_sku ? 1 : 0;
319             }
320              
321             =head2 is_canonical
322              
323             Returns 0 if L is defined else 1.
324              
325             =cut
326              
327             sub is_canonical {
328 2 100   2 1 586 return defined shift->canonical_sku ? 0 : 1;
329             }
330              
331             # after cost changes we need to clear the cart subtotal/total
332             # our own total is handled by the Costs role
333              
334             after 'clear_costs', 'cost_set', 'cost_push', 'set_quantity' => sub {
335 14     14   6425 my $self = shift;
336 14 100       166 if ( $self->cart ) {
337 8         168 $self->cart->clear_subtotal;
338 8         345 $self->cart->clear_total;
339             }
340             };
341             after 'set_quantity', 'set_weight' => sub {
342 10     10   189 my $self = shift;
343 10 100       151 if ( $self->cart ) {
344 4         77 $self->cart->clear_weight;
345             }
346             };
347              
348             1;