Passed
Branch master (8d4084)
by Antony
03:09 queued 01:05
created

Order::setBodySalesOrderLines()   B

Complexity

Conditions 8
Paths 12

Size

Total Lines 65
Code Lines 45

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 45
nc 12
nop 4
dl 0
loc 65
rs 7.9555
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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