Test Failed
Push — master ( dd0813...14557d )
by Antony
06:13
created

Order   B

Complexity

Total Complexity 52

Size/Duplication

Total Lines 480
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 265
dl 0
loc 480
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 201 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 MarkGuinn\SilverShopExtendedPricing\HasGroupPricing;  // not upgraded yet
0 ignored issues
show
Bug introduced by
The type MarkGuinn\SilverShopExte...Pricing\HasGroupPricing was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

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

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
428
                null, // Format of message in log, default [%datetime%] %channel%.%level_name%: %message% %context% %extra%\n
429
                null, // Datetime format
430
                true, // allowInlineLineBreaks option, default false
431
                true  // discard empty Square brackets in the end, default false
432
            );
433
            $logger = new Logger("unleashed-log-body");
0 ignored issues
show
Bug introduced by
The type AntonyThorpe\SilvershopUnleashed\Logger was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
434
            $stream_handler = new StreamHandler('./z_silverstripe-unleashed-body.log', Logger::INFO);
0 ignored issues
show
Bug introduced by
The type AntonyThorpe\SilvershopUnleashed\StreamHandler was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
435
            $stream_handler->setFormatter($line_formatter);
436
            $logger->pushHandler($stream_handler);
437
            $logger->info(print_r($body, true));
438
439
            if (!$member->Guid) {
440
                // The Customer Code does not exists in Unleashed and the email address is unique
441
                // therefore create in Unleashed
442
                $member->Guid = (string) Utils::createGuid();
443
                $body_member = [
444
                    'Addresses' => $body['Addresses'],
445
                    'ContactFirstName' => $member->FirstName,
446
                    'ContactLastName' => $member->Surname,
447
                    'CreatedBy' => $body['CreatedBy'],
448
                    'Currency' => $body['Currency'],
449
                    'CustomerCode' => $body['CustomerCode'],
450
                    'CustomerName' => $body['CustomerName'],
451
                    'CustomerType' => $body['CustomerType'],
452
                    'Email' => $member->Email,
453
                    'Guid' => $member->Guid,
454
                    'PaymentTerm' => $body['PaymentTerm'],
455
                    'PhoneNumber' => $body['PhoneNumber'],
456
                    'PrintPackingSlipInsteadOfInvoice' => $body['PrintPackingSlipInsteadOfInvoice'],
457
                    'SellPriceTier' => $body['SellPriceTier'],
458
                    'SourceId' => $body['SourceId'],
459
                    'Taxable' => $body['Taxable'],
460
                    'TaxCode' => $body['Tax']['TaxCode']
461
                ];
462
463
                foreach ($body_member['Addresses'] as $index => $value) {
464
                    $body_member['Addresses'][$index]['IsDefault'] = true;
465
                }
466
467
                $response = UnleashedAPI::sendCall(
468
                    'POST',
469
                    'https://api.unleashedsoftware.com/Customers/' . $member->Guid,
470
                    ['json' => $body_member ]
471
                );
472
473
                if ($response->getReasonPhrase() == 'Created' && $order->Member()->exists()) {
474
                    $member->write();
475
                }
476
            }
477
478
            // Prepare Sales Order data
479
            // Skip if previous calls to Customer have failed and the Guid has not been set
480
            if ($member->Guid) {
481
                $body['Customer']['Guid'] = $member->Guid;
482
483
                $this->owner->extend('updateUnleashedSalesOrder', $body);
484
485
                $response = UnleashedAPI::sendCall(
486
                    'POST',
487
                    'https://api.unleashedsoftware.com/SalesOrders/' . $order->Guid,
488
                    ['json' => $body]
489
                );
490
                if ($response->getReasonPhrase() == 'Created') {
491
                    $this->owner->OrderSentToUnleashed = DBDatetime::now()->Rfc2822();
492
                    $this->owner->write();
493
                }
494
            }
495
        }
496
    }
497
}
498