Completed
Branch master (e562f1)
by Antony
02:11
created

Order   B

Complexity

Total Complexity 52

Size/Duplication

Total Lines 469
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 255
dl 0
loc 469
rs 7.44
c 0
b 0
f 0
wmc 52

10 Methods

Rating   Name   Duplication   Size   Complexity  
B setBodySalesOrderLines() 0 65 8
A onBeforeWrite() 0 5 2
A setBodySubTotalAndTax() 0 13 2
F onAfterWrite() 0 190 25
A getAddressName() 0 16 4
A setBodyCurrencyCode() 0 4 1
A setBodyAddress() 0 60 3
A setBodyCustomerCodeAndName() 0 13 2
A matchCustomerAddress() 0 10 3
A setBodyDeliveryMethodAndDeliveryName() 0 8 2

How to fix   Complexity   

Complex Class

Complex classes like Order often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Order, and based on these observations, apply Extract Interface, too.

1
<?php namespace AntonyThorpe\SilvershopUnleashed;
2
3
use DateTime;
4
use SilverStripe\Security\Member;
5
use SilverStripe\ORM\FieldType\DBDatetime;
6
use SilverStripe\ORM\DataExtension;
7
use SilverShop\Extension\ShopConfigExtension;
8
use AntonyThorpe\SilverShopUnleashed\UnleashedAPI;
9
use AntonyThorpe\SilverShopUnleashed\Defaults;
10
use AntonyThorpe\SilverShopUnleashed\Utils;
11
12
class Order extends DataExtension
13
{
14
    /**
15
     * Record when an order is sent to Unleashed
16
     */
17
    private static $db = [
0 ignored issues
show
introduced by
The private property $db is not used, and could be removed.
Loading history...
18
        'OrderSentToUnleashed' => 'Datetime'
19
    ];
20
21
    /**
22
     * Apply Guid if absent
23
     */
24
    public function onBeforeWrite()
25
    {
26
        parent::onBeforeWrite();
27
        if (!$this->owner->getField("Guid")) {
28
            $this->owner->Guid = (string) Utils::createGuid();
29
        }
30
    }
31
32
    /**
33
     * Return the Address Name
34
     * @return string
35
     */
36
    public function getAddressName($address)
37
    {
38
        if (is_array($address)) {
39
            $address_name = $address['StreetAddress'];
40
            if ($address['StreetAddress2']) {
41
                $address_name .= ' ' . $address['StreetAddress2'];
42
            }
43
            $address_name .= ' ' . $address['City'];
44
        } else {
45
            $address_name = $address->Address;
46
            if ($address->AddressLine2) {
47
                $address_name .= ' ' . $address->AddressLine2;
48
            }
49
            $address_name .= ' ' . $address->City;
50
        }
51
        return $address_name;
52
    }
53
54
    /**
55
     * Match the order's shipping address to items returned from Unleashed
56
     * @return boolean
57
     */
58
    public function matchCustomerAddress($items, $shipping_address)
59
    {
60
        // Obtain the delivery address
61
        $address = $items[0]['Addresses'][0];
62
        if ($address['AddressType'] != "Physical") {
63
            if (isset($items[0]['Addresses'][1])) {
64
                $address = $items[0]['Addresses'][1];
65
            }
66
        }
67
        return strtoupper($this->getAddressName($shipping_address)) == strtoupper($this->getAddressName($address));
68
    }
69
70
    /**
71
     * add the address components to the body array
72
     * @param array $body
73
     * @param object $order
74
     * @param string $type (either Postal or Physical)
75
     * @return array $body
76
     */
77
    public function setBodyAddress($body, $order, $type)
78
    {
79
        $countries = ShopConfigExtension::config()->iso_3166_country_codes;
80
81
        if ($type == 'Postal') {
82
            $address = $order->BillingAddress();
83
            array_push(
84
                $body['Addresses'],
85
                [
86
                    'AddressName' => $this->getAddressName($address),
87
                    'AddressType' => $type,
88
                    'City' => $address->City,
89
                    'Country' => $countries[$address->Country],
90
                    'PostalCode' => $address->PostalCode,
91
                    'Region' => $address->State,
92
                    'StreetAddress' => $address->Address,
93
                    'StreetAddress2' => $address->AddressLine2
94
                ]
95
            );
96
        }
97
98
        if ($type == 'Physical') {
99
            $address = $order->ShippingAddress();
100
            $body['DeliveryCity'] = $address->City;
101
            $body['DeliveryCountry'] = $countries[$address->Country];
102
            $body['DeliveryPostCode'] = $address->PostalCode;
103
            $body['DeliveryRegion'] = $address->State;
104
            $body['DeliveryStreetAddress'] = $address->Address;
105
            $body['DeliveryStreetAddress2'] = $address->AddressLine2;
106
107
            array_push(
108
                $body['Addresses'],
109
                [
110
                    'AddressName' => $this->getAddressName($address),
111
                    'AddressType' => 'Physical',
112
                    'City' => $address->City,
113
                    'Country' => $countries[$address->Country],
114
                    'PostalCode' => $address->PostalCode,
115
                    'Region' => $address->State,
116
                    'StreetAddress' => $address->Address,
117
                    'StreetAddress2' => $address->AddressLine2
118
                ]
119
            );
120
121
            array_push(
122
                $body['Addresses'],
123
                [
124
                    'AddressName' => $this->getAddressName($address),
125
                    'AddressType' => 'Shipping',
126
                    'City' => $address->City,
127
                    'Country' => $countries[$address->Country],
128
                    'PostalCode' => $address->PostalCode,
129
                    'Region' => $address->State,
130
                    'StreetAddress' => $address->Address,
131
                    'StreetAddress2' => $address->AddressLine2
132
                ]
133
            );
134
        }
135
136
        return $body;
137
    }
138
139
    /**
140
     * add the currency code to the body array
141
     * @param array $body
142
     * @param object $order
143
     * @return array $body
144
     */
145
    public function setBodyCurrencyCode($body, $order)
146
    {
147
        $body['Currency']['CurrencyCode'] = $order->Currency();
148
        return $body;
149
    }
150
151
    /**
152
     * Add the Customer Code/Name (use Company field of BillingAddress to allow for B2B eCommerce sites)
153
     * @param array $body
154
     * @param object $order
155
     * @return array $body
156
     */
157
    public function setBodyCustomerCodeAndName($body, $order)
158
    {
159
        $billing_address = $order->BillingAddress();
160
        if ($billing_address->Company) {
161
            // use Organisation name
162
            $body['CustomerCode'] = $billing_address->Company;
163
            $body['CustomerName'] = $billing_address->Company;
164
        } else {
165
            // use Contact full name instead
166
            $body['CustomerCode'] = $order->getName();
167
            $body['CustomerName'] = $order->getName();
168
        }
169
        return $body;
170
    }
171
172
    /**
173
     * Set Delivery Method and Delivery Name
174
     * Allow for the SilverShop Shipping module
175
     * @param array $body
176
     * @param object $order
177
     * @return array $body
178
     */
179
    public function setBodyDeliveryMethodAndDeliveryName($body, $order, $shipping_modifier_class_name)
180
    {
181
        $shipping_modifier = $order->getModifier($shipping_modifier_class_name);
182
        if (!empty($shipping_modifier)) {
183
            $body['DeliveryMethod'] = $shipping_modifier::config()->product_code;
184
            $body['DeliveryName'] = $shipping_modifier::config()->product_code;
185
        }
186
        return $body;
187
    }
188
189
    /**
190
     * Set Sales Order Lines
191
     * @param array $body
192
     * @param object $order
193
     * @param string $tax_modifier_class_name
194
     * @param int $rounding_precision
195
     * @return array $body
196
     */
197
    public function setBodySalesOrderLines($body, $order, $tax_modifier_class_name, $rounding_precision)
198
    {
199
        $line_number = 0;
200
201
        // Sales Order Lines
202
        foreach ($order->Items()->getIterator() as $item) {
203
            // Definitions
204
            $product = $item->Product();
205
            $line_number += 1;
206
            $sales_order_line = [
207
                'DiscountRate' => 0,
208
                'Guid' => $item->Guid,
209
                'LineNumber' => (int) $line_number,
210
                'LineType' => null,
211
                'LineTotal' => round(floatval($item->Total()), $rounding_precision),
212
                'OrderQuantity' => (int) $item->Quantity,
213
                'Product' => [
214
                    'Guid' => $product->Guid
215
                ],
216
                'UnitPrice' => round(floatval($product->getPrice()), $rounding_precision)
217
            ];
218
            if ($tax_modifier_class_name) {
219
                $tax_calculator = new $tax_modifier_class_name;
220
                $sales_order_line['LineTax'] = round(
221
                    $tax_calculator->value($item->Total()),
222
                    $rounding_precision
223
                );
224
                $sales_order_line['LineTaxCode'] = $body['Tax']['TaxCode'];
225
            }
226
            $body['SalesOrderLines'][] = $sales_order_line;
227
        }
228
229
        // Add Modifiers that have a product_code
230
        foreach ($order->Modifiers()->sort('Sort')->getIterator() as $modifier) {
231
            $line_total = round(floatval($modifier->Amount), $rounding_precision);
232
233
            if ($modifier::config()->product_code &&
234
                $modifier->Type !== 'Ignored' &&
235
                !empty($line_total)
236
            ) {
237
                $line_number += 1;
238
                $sales_order_line = [
239
                    'DiscountRate' => 0,
240
                    'Guid' => $modifier->Guid,
241
                    'LineNumber' => (int) $line_number,
242
                    'LineTotal' => $line_total,
243
                    'LineType' => null,
244
                    'OrderQuantity' => 1,
245
                    'Product' => [
246
                        'ProductCode' => $modifier::config()->product_code,
247
                    ],
248
                    'UnitPrice' => round(floatval($modifier->Amount), $rounding_precision)
249
                ];
250
                if ($tax_modifier_class_name) {
251
                    $tax_calculator = new $tax_modifier_class_name;
252
                    $sales_order_line['LineTax'] = round(
253
                        $tax_calculator->value($modifier->Amount),
254
                        $rounding_precision
255
                    );
256
                    $sales_order_line['LineTaxCode'] = $body['Tax']['TaxCode'];
257
                }
258
                $body['SalesOrderLines'][] = $sales_order_line;
259
            }
260
        }
261
        return $body;
262
    }
263
264
    /**
265
     * Calculate the SubTotal
266
     * @param array $body
267
     * @param object $order
268
     * @param string $tax_modifier_class_name
269
     * @param int $rounding_precision
270
     * @return array $body
271
     */
272
    public function setBodySubTotalAndTax($body, $order, $tax_modifier_class_name, $rounding_precision)
273
    {
274
        $subtotal = round(floatval($order->Total()), $rounding_precision);  // Subtotal = Total less Tax Modifier
275
        $tax_modifier = $order->getModifier($tax_modifier_class_name);
276
277
        if (!empty($tax_modifier)) {
278
            $subtotal = bcsub($subtotal, $tax_modifier->Amount, $rounding_precision);
279
            $body['Taxable'] = true;
280
            $body['Tax']['TaxCode'] = $tax_modifier::config()->tax_code;
281
            $body['TaxTotal'] = round(floatval($tax_modifier->Amount), $rounding_precision);
282
        }
283
        $body['SubTotal'] = $subtotal;
284
        return $body;
285
    }
286
287
    /**
288
     * Send a sales order to Unleashed upon paid status
289
     * May need to create the Customer first
290
     */
291
    public function onAfterWrite()
292
    {
293
        parent::onAfterWrite();
294
        $config = $this->owner->config();
295
        $defaults = Defaults::config();
296
297
        if ($defaults->send_sales_orders_to_unleashed
298
            && $this->owner->Status == 'Paid'
299
            && !$this->owner->OrderSentToUnleashed) {
300
            // Definitions
301
            $order = $this->owner;
302
            $member = $order->Member();
303
            $date_paid = new DateTime($order->Paid);
304
            $date_placed = new DateTime($order->Placed);
305
            $body = [
306
                'Addresses' => [],
307
                'Currency' => [],
308
                'Customer' => [],
309
                'DiscountRate' => 0,
310
                'Guid' => $order->Guid,
311
                'OrderDate' => $date_placed->format('Y-m-d\TH:i:s'),
312
                'OrderNumber' => $order->Reference,
313
                'OrderStatus' => $defaults->order_status,
314
                'PaymentDueDate' => $date_paid->format('Y-m-d\TH:i:s'),
315
                'PaymentTerm' => $defaults->payment_term,
316
                'PrintPackingSlipInsteadOfInvoice' => $defaults->print_packingslip_instead_of_invoice,
317
                'ReceivedDate' => $date_placed->format('Y-m-d\TH:i:s'),
318
                'SalesOrderLines' => [],
319
                'SellPriceTier' => ShopConfigExtension::current()->CustomerGroup()->Title,
320
                'Taxable' => false,
321
                'Tax'  => [],
322
                'Total' => round(floatval($order->Total()), $config->rounding_precision),
323
            ];
324
325
            $body = $this->setBodyAddress($body, $order, 'Postal');
326
            $body = $this->setBodyAddress($body, $order, 'Physical');
327
            $body = $this->setBodyCurrencyCode($body, $order);
328
            $body = $this->setBodyCustomerCodeAndName($body, $order);
329
            $body = $this->setBodySubTotalAndTax($body, $order, $defaults->tax_modifier_class_name, $config->rounding_precision);
330
            $body = $this->setBodyDeliveryMethodAndDeliveryName($body, $order, $defaults->shipping_modifier_class_name);
331
            $body = $this->setBodySalesOrderLines($body, $order, $defaults->tax_modifier_class_name, $config->rounding_precision);
332
333
            // Add optional defaults
334
            if ($defaults->created_by) {
335
                $body['CreatedBy'] = $defaults->created_by;
336
            }
337
338
            if ($defaults->customer_type) {
339
                $body['CustomerType'] = $defaults->customer_type;
340
            }
341
342
            if ($defaults->sales_order_group) {
343
                $body['SalesOrderGroup'] = $defaults->sales_order_group;
344
            }
345
346
            if ($defaults->source_id) {
347
                $body['SourceId'] = $defaults->source_id;
348
            }
349
350
            // add phone number if available
351
            if ($order->BillingAddress()->Phone) {
352
                $body['PhoneNumber'] = $order->BillingAddress()->Phone;
353
            }
354
355
            // add required date
356
            $date_required = new DateTime($order->Paid);
357
            if ($defaults->expected_days_to_deliver) {
358
                $date_required->modify('+' . $defaults->expected_days_to_deliver . 'day');
359
            }
360
            $body['RequiredDate'] = $date_required->format('Y-m-d\TH:i:s');
361
362
            if ($order->Notes) {
363
                $body['Comments'] = $order->Notes;
364
            }
365
366
            // Create Member for Guests
367
            if (!$member->exists()) {
368
                $member = Member::create();
369
                $member->FirstName = $order->FirstName;
370
                $member->Surname = $order->Surname;
371
                $member->Email = $order->getLatestEmail();
372
            }
373
374
            // See if New Customer/Guest has previously purchased
375
            if (!$member->Guid) {
376
                $response = UnleashedAPI::sendCall(
377
                    'GET',
378
                    'https://api.unleashedsoftware.com/Customers?contactEmail=' .  $member->Email
379
                );
380
381
                if ($response->getStatusCode() == '200') {
382
                    $contents = json_decode($response->getBody()->getContents(), true);
383
                    $items = $contents['Items'];
384
                    if ($items) {
385
                        // Email address exists
386
                        $member->Guid = $items[0]['Guid'];
387
                    } else {
388
                        // A Customer is not returned, we have a unique email address.
389
                        // Check to see if the Customer Code exists (note that the Customer Code cannot be doubled up)
390
                        $response = UnleashedAPI::sendCall(
391
                            'GET',
392
                            'https://api.unleashedsoftware.com/Customers?customerCode=' . $body['CustomerCode']
393
                        );
394
395
                        if ($response->getStatusCode() == '200') {
396
                            $contents = json_decode($response->getBody()->getContents(), true);
397
                            $items = $contents['Items'];
398
                            if ($items) {
399
                                // A Customer Code already exists (and the email address is unique).
400
                                // If the address is the same then this is the Customer
401
                                if ($this->matchCustomerAddress($items, $order->ShippingAddress())) {
402
                                    $member->Guid = $items[0]['Guid'];
403
404
                                    //Note the existing email address in the Comment
405
                                    //PUT Customer is not available in Unleashed
406
                                    if ($body['Comments']) {
407
                                        $body['Comments'] .= PHP_EOL;
408
                                    }
409
                                    $body['Comments'] .= _t(
410
                                        'UnleashedAPI.addEmailToCustomerComment',
411
                                        'Add email to Customer: {email_address}',
412
                                        '',
413
                                        ['email_address' => $member->Email]
414
                                    );
415
                                } else {
416
                                    // The Customer Code already exists, we have a unique email address, but
417
                                    // the delivery address is new.
418
                                    // Therefore, we need to create a new Customer with a unique Customer Code.
419
                                    $body['CustomerCode'] .= rand(10000000, 99999999);
420
                                }
421
                            }
422
                        }
423
                    }
424
                }
425
            }
426
427
            if (!$member->Guid) {
428
                // The Customer Code does not exists in Unleashed and the email address is unique
429
                // therefore create in Unleashed
430
                $member->Guid = (string) Utils::createGuid();
431
                $body_member = [
432
                    'Addresses' => $body['Addresses'],
433
                    'ContactFirstName' => $member->FirstName,
434
                    'ContactLastName' => $member->Surname,
435
                    'CreatedBy' => $body['CreatedBy'],
436
                    'Currency' => $body['Currency'],
437
                    'CustomerCode' => $body['CustomerCode'],
438
                    'CustomerName' => $body['CustomerName'],
439
                    'CustomerType' => $body['CustomerType'],
440
                    'Email' => $member->Email,
441
                    'Guid' => $member->Guid,
442
                    'PaymentTerm' => $body['PaymentTerm'],
443
                    'PhoneNumber' => $body['PhoneNumber'],
444
                    'PrintPackingSlipInsteadOfInvoice' => $body['PrintPackingSlipInsteadOfInvoice'],
445
                    'SellPriceTier' => $body['SellPriceTier'],
446
                    'SourceId' => $body['SourceId'],
447
                    'Taxable' => $body['Taxable'],
448
                    'TaxCode' => $body['Tax']['TaxCode']
449
                ];
450
451
                foreach ($body_member['Addresses'] as $index => $value) {
452
                    $body_member['Addresses'][$index]['IsDefault'] = true;
453
                }
454
455
                $response = UnleashedAPI::sendCall(
456
                    'POST',
457
                    'https://api.unleashedsoftware.com/Customers/' . $member->Guid,
458
                    ['json' => $body_member ]
459
                );
460
461
                if ($response->getReasonPhrase() == 'Created' && $order->Member()->exists()) {
462
                    $member->write();
463
                }
464
            }
465
466
            // Prepare Sales Order data
467
            // Skip if previous calls to Customer have failed and the Guid has not been set
468
            if ($member->Guid) {
469
                $body['Customer']['Guid'] = $member->Guid;
470
471
                $this->owner->extend('updateUnleashedSalesOrder', $body);
472
473
                $response = UnleashedAPI::sendCall(
474
                    'POST',
475
                    'https://api.unleashedsoftware.com/SalesOrders/' . $order->Guid,
476
                    ['json' => $body]
477
                );
478
                if ($response->getReasonPhrase() == 'Created') {
479
                    $this->owner->OrderSentToUnleashed = DBDatetime::now()->Rfc2822();
480
                    $this->owner->write();
481
                }
482
            }
483
        }
484
    }
485
}
486