1
|
|
|
<?php defined('SYSPATH') OR die('No direct script access.'); |
2
|
|
|
|
3
|
|
|
use Clippings\Freezable\FreezableInterface; |
4
|
|
|
use Clippings\Freezable\FreezableTrait; |
5
|
|
|
|
6
|
|
|
/** |
7
|
|
|
* @package openbuildings\shipping |
8
|
|
|
* @author Ivan Kerin <[email protected]> |
9
|
|
|
* @copyright (c) 2013 OpenBuildings Ltd. |
10
|
|
|
* @license http://spdx.org/licenses/BSD-3-Clause |
11
|
|
|
*/ |
12
|
|
|
class Kohana_Model_Shipping_Item extends Jam_Model implements FreezableInterface { |
13
|
|
|
|
14
|
|
|
use FreezableTrait; |
15
|
|
|
|
16
|
|
|
/** |
17
|
|
|
* @codeCoverageIgnore |
18
|
|
|
*/ |
19
|
|
|
public static function initialize(Jam_Meta $meta) |
20
|
|
|
{ |
21
|
|
|
$meta |
22
|
|
|
->associations(array( |
23
|
|
|
'brand_purchase_shipping' => Jam::association('belongsto', array( |
24
|
|
|
'inverse_of' => 'items' |
25
|
|
|
)), |
26
|
|
|
'purchase_item' => Jam::association('belongsto', array( |
27
|
|
|
'inverse_of' => 'shipping_item', |
28
|
|
|
'foreign_key' => 'purchase_item_id', |
29
|
|
|
'foreign_model' => 'purchase_item_product' |
30
|
|
|
)), |
31
|
|
|
'shipping_group' => Jam::association('belongsto', array( |
32
|
|
|
'inverse_of' => 'shipping_items' |
33
|
|
|
)), |
34
|
|
|
)) |
35
|
|
|
->fields(array( |
36
|
|
|
'id' => Jam::field('primary'), |
37
|
|
|
'model' => Jam::field('polymorphic'), |
38
|
|
|
'total_delivery_time' => Jam::field('range', array( |
39
|
|
|
'format' => 'Model_Shipping::format_shipping_time' |
40
|
|
|
)), |
41
|
|
|
'is_frozen' => Jam::field('boolean'), |
42
|
|
|
)) |
43
|
|
|
->validator('purchase_item', array( |
44
|
|
|
'present' => TRUE |
45
|
|
|
)); |
46
|
|
|
} |
47
|
|
|
|
48
|
|
|
/** |
49
|
|
|
* Filter out Model_Shipping_Item's of shipping_groups that are discounted, |
50
|
|
|
* based on the provided total price |
51
|
|
|
* @param array $items array of Model_Shipping_Item objects |
52
|
|
|
* @param Jam_Price $total |
53
|
|
|
* @return array Model_Shipping_Item objects |
54
|
|
|
*/ |
55
|
4 |
|
public static function filter_discounted_items(array $items, Jam_Price $total) |
56
|
|
|
{ |
57
|
4 |
|
Array_Util::validate_instance_of($items, 'Model_Shipping_Item'); |
58
|
|
|
|
59
|
|
|
return array_filter($items, function($item) use ($total) { |
60
|
4 |
|
return ! $item->is_discounted($total); |
61
|
4 |
|
}); |
62
|
|
|
} |
63
|
|
|
|
64
|
|
|
/** |
65
|
|
|
* Sort Model_Shipping_Item by price, biggest price first |
66
|
|
|
* @param array $items |
67
|
|
|
* @return array |
68
|
|
|
*/ |
69
|
3 |
|
public static function sort_by_price(array $items) |
70
|
|
|
{ |
71
|
3 |
|
Array_Util::validate_instance_of($items, 'Model_Shipping_Item'); |
72
|
|
|
|
73
|
|
|
// Suppress warnings as usort throws "Array was modified by the user comparison function" |
74
|
|
|
// When price method is being mocked. |
75
|
|
|
// Relevant php bug: https://bugs.php.net/bug.php?id=50688 |
76
|
|
|
@ usort($items, function($item1, $item2){ |
|
|
|
|
77
|
3 |
|
return $item1->price()->is(Jam_Price::GREATER_THAN, $item2->price()) ? -1 : 1; |
78
|
3 |
|
}); |
79
|
|
|
|
80
|
3 |
|
return $items; |
81
|
|
|
} |
82
|
|
|
|
83
|
|
|
/** |
84
|
|
|
* Sort and get all the realtive prices of Model_Shipping_Item object (using relative_price method) |
85
|
|
|
* @param array $items |
86
|
|
|
* @return array Jam_Price objects |
87
|
|
|
*/ |
88
|
2 |
|
public static function relative_prices(array $items) |
89
|
|
|
{ |
90
|
2 |
|
Array_Util::validate_instance_of($items, 'Model_Shipping_Item'); |
91
|
|
|
|
92
|
2 |
|
$items = Model_Shipping_Item::sort_by_price($items); |
93
|
|
|
|
94
|
|
|
$prices = array_map(function($item, $index) { |
95
|
2 |
|
return $index == 0 ? $item->total_price() : $item->total_additional_item_price(); |
96
|
2 |
|
}, $items, array_keys($items)); |
97
|
|
|
|
98
|
2 |
|
return $prices; |
99
|
|
|
} |
100
|
|
|
|
101
|
|
|
/** |
102
|
|
|
* Get the shipping object associated with this item |
103
|
|
|
* @return Model_Shipping |
104
|
|
|
* @throws Kohana_Exception If shipping_group or its shipping is NULL |
105
|
|
|
*/ |
106
|
1 |
|
public function shipping_insist() |
107
|
|
|
{ |
108
|
1 |
|
$self = $this; |
109
|
|
|
|
110
|
|
|
return Jam_Behavior_Paranoid::with_filter(Jam_Behavior_Paranoid::ALL, function() use ($self) { |
111
|
1 |
|
return $self->get_insist('shipping_group')->get_insist('shipping'); |
112
|
1 |
|
}); |
113
|
|
|
} |
114
|
|
|
|
115
|
1 |
|
public function purchase_item_shipping() |
116
|
|
|
{ |
117
|
1 |
|
return $this->get_insist('purchase_item')->get_insist('reference')->shipping(); |
118
|
|
|
} |
119
|
|
|
|
120
|
|
|
/** |
121
|
|
|
* Return the date the purchase was paid |
122
|
|
|
*/ |
123
|
1 |
|
public function paid_at() |
124
|
|
|
{ |
125
|
1 |
|
return $this->get_insist('brand_purchase_shipping')->paid_at(); |
126
|
|
|
} |
127
|
|
|
|
128
|
|
|
/** |
129
|
|
|
* Get the currency for pricing calculations |
130
|
|
|
* @return string |
131
|
|
|
* @throws Kohana_Exception If brand_purchase_shipping is NULL |
132
|
|
|
*/ |
133
|
2 |
|
public function currency() |
134
|
|
|
{ |
135
|
2 |
|
return $this->get_insist('brand_purchase_shipping')->currency(); |
136
|
|
|
} |
137
|
|
|
|
138
|
|
|
/** |
139
|
|
|
* Get the monetary object for currency calculations |
140
|
|
|
* @return Monetary |
141
|
|
|
* @throws Kohana_Exception If brand_purchase_shipping is NULL |
142
|
|
|
*/ |
143
|
2 |
|
public function monetary() |
144
|
|
|
{ |
145
|
2 |
|
return $this->get_insist('brand_purchase_shipping')->monetary(); |
146
|
|
|
} |
147
|
|
|
|
148
|
|
|
/** |
149
|
|
|
* Generate a key based on which shipping groups will be devided. |
150
|
|
|
* The items in the same group are shipped toggether, allowing us to use |
151
|
|
|
* additional_item_price instead of price. |
152
|
|
|
* |
153
|
|
|
* Groups by method and ships_from |
154
|
|
|
* @return string |
155
|
|
|
* @throws Kohana_Exception If shipping_group or shipping is NULL |
156
|
|
|
*/ |
157
|
2 |
|
public function group_key() |
158
|
|
|
{ |
159
|
2 |
|
$group = $this->shipping_group; |
|
|
|
|
160
|
|
|
|
161
|
2 |
|
if ( ! $group OR ! $group->shipping) |
162
|
1 |
|
return NULL; |
163
|
|
|
|
164
|
2 |
|
return $group->method_id.'-'.$group->shipping->ships_from_id; |
165
|
|
|
} |
166
|
|
|
|
167
|
|
|
/** |
168
|
|
|
* Get the price from shipping_group, converted into purchase's currency / monetary |
169
|
|
|
* @return Jam_Price |
170
|
|
|
* @throws Kohana_Exception If shipping_group is NULL |
171
|
|
|
*/ |
172
|
2 |
|
public function price() |
173
|
|
|
{ |
174
|
2 |
|
$price = $this->shipping_group_insist()->price ?: new Jam_Price(0, $this->currency()); |
175
|
|
|
|
176
|
|
|
return $price |
177
|
2 |
|
->monetary($this->monetary()) |
178
|
2 |
|
->convert_to($this->currency()); |
179
|
|
|
} |
180
|
|
|
|
181
|
|
|
/** |
182
|
|
|
* Get the additional_item_price from shipping_group, converted into purchase's currency / monetary |
183
|
|
|
* If there is no additional_item_price, return price instead |
184
|
|
|
* @return Jam_Price |
185
|
|
|
* @throws Kohana_Exception If shipping_group is NULL |
186
|
|
|
*/ |
187
|
2 |
|
public function additional_item_price() |
188
|
|
|
{ |
189
|
2 |
|
$group = $this->shipping_group_insist(); |
190
|
|
|
|
191
|
2 |
|
$additional_price = $group->additional_item_price ?: $group->price; |
192
|
|
|
|
193
|
|
|
return $additional_price |
194
|
2 |
|
->monetary($this->monetary()) |
195
|
2 |
|
->convert_to($this->currency()); |
196
|
|
|
} |
197
|
|
|
|
198
|
|
|
/** |
199
|
|
|
* Get shipping_group's is_discounted |
200
|
|
|
* If there is no additional_item_price, return price instead |
201
|
|
|
* @return boolean |
202
|
|
|
* @throws Kohana_Exception If shipping_group is NULL |
203
|
|
|
*/ |
204
|
2 |
|
public function is_discounted(Jam_Price $total) |
205
|
|
|
{ |
206
|
2 |
|
return $this->shipping_group_insist()->is_discounted($total); |
207
|
|
|
} |
208
|
|
|
|
209
|
|
|
/** |
210
|
|
|
* Get purchase_item's quantity |
211
|
|
|
* @return Jam_Price |
212
|
|
|
* @throws Kohana_Exception If shipping_group is NULL |
213
|
|
|
*/ |
214
|
2 |
|
public function quantity() |
215
|
|
|
{ |
216
|
2 |
|
return $this->get_insist('purchase_item')->quantity; |
217
|
|
|
} |
218
|
|
|
|
219
|
|
|
/** |
220
|
|
|
* Return price(), if quantity() > 1 the nany additional items are summed using additional_items_price() |
221
|
|
|
* @return Jam_Price |
222
|
|
|
* @throws Kohana_Exception If shipping_group is NULL |
223
|
|
|
*/ |
224
|
3 |
|
public function total_price() |
225
|
|
|
{ |
226
|
3 |
|
$additional_items_price = $this->additional_item_price()->multiply_by($this->quantity() - 1); |
227
|
|
|
|
228
|
3 |
|
return $this->price()->add($additional_items_price); |
229
|
|
|
} |
230
|
|
|
|
231
|
|
|
/** |
232
|
|
|
* Shipping group's delivery_time |
233
|
|
|
* Freezable |
234
|
|
|
* |
235
|
|
|
* @return Jam_Range |
236
|
|
|
*/ |
237
|
2 |
|
public function total_delivery_time() |
238
|
|
|
{ |
239
|
2 |
|
return $this->isFrozen() |
240
|
|
|
? $this->total_delivery_time |
|
|
|
|
241
|
2 |
|
: $this->shipping_group_insist()->total_delivery_time(); |
242
|
|
|
} |
243
|
|
|
|
244
|
|
|
/** |
245
|
|
|
* Return the shipping date for this item |
246
|
|
|
* @return Jam_Range |
247
|
|
|
*/ |
248
|
1 |
|
public function shipping_date() |
249
|
|
|
{ |
250
|
1 |
|
$paid_at = $this->paid_at(); |
251
|
1 |
|
$days = $this->total_delivery_time(); |
252
|
|
|
|
253
|
1 |
|
$from_day = strtotime("{$paid_at} + {$days->min()} weekdays"); |
254
|
1 |
|
$to_day = strtotime("{$paid_at} + {$days->max()} weekdays"); |
255
|
|
|
|
256
|
1 |
|
return new Jam_Range(array($from_day, $to_day)); |
257
|
|
|
} |
258
|
|
|
|
259
|
|
|
/** |
260
|
|
|
* Return additional_items_price() multiplied by quantity() |
261
|
|
|
* @return Jam_Price |
262
|
|
|
* @throws Kohana_Exception If shipping_group or purchase_item is NULL |
263
|
|
|
*/ |
264
|
3 |
|
public function total_additional_item_price() |
265
|
|
|
{ |
266
|
3 |
|
return $this->additional_item_price()->multiply_by($this->quantity()); |
267
|
|
|
} |
268
|
|
|
|
269
|
|
|
/** |
270
|
|
|
* Use paranoid for shipping group |
271
|
|
|
*/ |
272
|
2 |
|
public function shipping_group_insist() |
273
|
|
|
{ |
274
|
2 |
|
$self = $this; |
275
|
|
|
|
276
|
|
|
return Jam_Behavior_Paranoid::with_filter(Jam_Behavior_Paranoid::ALL, function() use ($self) { |
277
|
2 |
|
return $self->get_insist('shipping_group'); |
278
|
2 |
|
}); |
279
|
|
|
} |
280
|
|
|
|
281
|
1 |
|
public function shipping_method() |
282
|
|
|
{ |
283
|
1 |
|
if ( ! $this->shipping_group) |
|
|
|
|
284
|
1 |
|
return NULL; |
285
|
|
|
|
286
|
1 |
|
return $this->shipping_group->method; |
|
|
|
|
287
|
|
|
} |
288
|
|
|
|
289
|
4 |
|
public function update_address(Model_Brand_Purchase_Shipping $brand_purchase_shipping) |
290
|
|
|
{ |
291
|
4 |
|
$ship_to = $brand_purchase_shipping->ship_to(); |
292
|
|
|
|
293
|
4 |
|
if ( ! $ship_to) |
294
|
|
|
return; |
295
|
|
|
|
296
|
4 |
|
if (! $this->shipping_group OR ! $this->shipping_group->location OR ! $this->shipping_group->location->contains($ship_to)) |
|
|
|
|
297
|
|
|
{ |
298
|
3 |
|
$this->shipping_group = $this->purchase_item_shipping()->cheapest_group_in($ship_to); |
|
|
|
|
299
|
|
|
} |
300
|
4 |
|
} |
301
|
|
|
|
302
|
1 |
|
public function isFrozen() |
303
|
|
|
{ |
304
|
1 |
|
return $this->is_frozen; |
|
|
|
|
305
|
|
|
} |
306
|
|
|
|
307
|
1 |
|
public function setFrozen($frozen) |
308
|
|
|
{ |
309
|
1 |
|
$this->is_frozen = (bool) $frozen; |
|
|
|
|
310
|
1 |
|
} |
311
|
|
|
|
312
|
2 |
|
public function performFreeze() |
313
|
|
|
{ |
314
|
2 |
|
$this->total_delivery_time = $this->total_delivery_time(); |
|
|
|
|
315
|
2 |
|
} |
316
|
|
|
|
317
|
1 |
|
public function performUnfreeze() |
318
|
|
|
{ |
319
|
1 |
|
$this->total_delivery_time = NULL; |
|
|
|
|
320
|
1 |
|
} |
321
|
|
|
} |
322
|
|
|
|
If you suppress an error, we recommend checking for the error condition explicitly: