Completed
Push — master ( 56783c...93285e )
by Antony
10:57
created

Order::setBodyTaxCode()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 6
nc 3
nop 3
dl 0
loc 10
rs 10
c 0
b 0
f 0
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
     * Set the Tax Codes
266
     * @param array $body
267
     * @param object $order
268
     * @param string $tax_modifier_class_name
269
     * @return array $body
270
     */
271
    public function setBodyTaxCode($body, $order, $tax_modifier_class_name)
272
    {
273
        if ($tax_modifier_class_name) {
274
            $tax_modifier = $order->getModifier($tax_modifier_class_name);
275
            if (!empty($tax_modifier)) {
276
                $body['Taxable'] = true;
277
                $body['Tax']['TaxCode'] = $tax_modifier::config()->tax_code;
278
            }
279
        }
280
        return $body;
281
    }
282
283
284
    /**
285
     * Calculate the SubTotal and TaxTotal
286
     * @param array $body
287
     * @param object $order
288
     * @param string $tax_modifier_class_name
289
     * @param int $rounding_precision
290
     * @return array $body
291
     */
292
    public function setBodySubTotalAndTax($body, $order, $tax_modifier_class_name, $rounding_precision)
293
    {
294
        if ($tax_modifier_class_name) {
295
            $tax_modifier = $order->getModifier($tax_modifier_class_name);
296
297
            if (!empty($tax_modifier)) {
298
                $sub_total = 0;
299
                $tax_total = 0;
300
                foreach ($body['SalesOrderLines'] as $item) {
301
                    $sub_total = bcadd(
302
                        $sub_total,
303
                        $item['LineTotal'],
304
                        $rounding_precision
305
                    );
306
                    $tax_total = bcadd(
307
                        $tax_total,
308
                        $item['LineTax'],
309
                        $rounding_precision
310
                    );
311
                }
312
                $body['TaxTotal'] = $tax_total;
313
                $body['SubTotal'] = $sub_total;
314
315
                $rounding = round(floatval($order->Total() - $tax_total - $sub_total), $rounding_precision);
316
                // if there is some rounding, adjust the Tax on the first sales order line
317
                // and adjust the Tax Total by the same amount
318
                if ($rounding) {
319
                    $body['SalesOrderLines'][0]['LineTax'] = round($body['SalesOrderLines'][0]['LineTax'] + $rounding, $rounding_precision);
320
                    $body['TaxTotal'] = round($body['TaxTotal'] + $rounding, $rounding_precision);
321
                }
322
            }
323
        } else {
324
            $body['SubTotal'] = round(floatval($order->Total()), $rounding_precision);
325
        }
326
        return $body;
327
    }
328
329
    /**
330
     * Send a sales order to Unleashed upon paid status
331
     * May need to create the Customer first
332
     */
333
    public function onAfterWrite()
334
    {
335
        parent::onAfterWrite();
336
        $config = $this->owner->config();
337
        $defaults = Defaults::config();
338
339
        if ($defaults->send_sales_orders_to_unleashed
340
            && $this->owner->Status == 'Paid'
341
            && !$this->owner->OrderSentToUnleashed) {
342
            // Definitions
343
            $order = $this->owner;
344
            $member = $order->Member();
345
            $date_paid = new DateTime($order->Paid);
346
            $date_placed = new DateTime($order->Placed);
347
            $body = [
348
                'Addresses' => [],
349
                'Currency' => [],
350
                'Customer' => [],
351
                'DiscountRate' => 0,
352
                'Guid' => $order->Guid,
353
                'OrderDate' => $date_placed->format('Y-m-d\TH:i:s'),
354
                'OrderNumber' => $order->Reference,
355
                'OrderStatus' => $defaults->order_status,
356
                'PaymentDueDate' => $date_paid->format('Y-m-d\TH:i:s'),
357
                'PaymentTerm' => $defaults->payment_term,
358
                'PrintPackingSlipInsteadOfInvoice' => $defaults->print_packingslip_instead_of_invoice,
359
                'ReceivedDate' => $date_placed->format('Y-m-d\TH:i:s'),
360
                'SalesOrderLines' => [],
361
                'SellPriceTier' => ShopConfigExtension::current()->CustomerGroup()->Title,
362
                'Taxable' => false,
363
                'Tax'  => [],
364
                'Total' => round(floatval($order->Total()), $config->rounding_precision),
365
            ];
366
367
            $body = $this->setBodyAddress($body, $order, 'Postal');
368
            $body = $this->setBodyAddress($body, $order, 'Physical');
369
            $body = $this->setBodyCurrencyCode($body, $order);
370
            $body = $this->setBodyCustomerCodeAndName($body, $order);
371
            $body = $this->setBodyDeliveryMethodAndDeliveryName($body, $order, $defaults->shipping_modifier_class_name);
372
            $body = $this->setBodyTaxCode($body, $order, $defaults->tax_modifier_class_name);
373
            $body = $this->setBodySalesOrderLines($body, $order, $defaults->tax_modifier_class_name, $config->rounding_precision);
374
            $body = $this->setBodySubTotalAndTax($body, $order, $defaults->tax_modifier_class_name, $config->rounding_precision);
375
376
            // Add optional defaults
377
            if ($defaults->created_by) {
378
                $body['CreatedBy'] = $defaults->created_by;
379
            }
380
381
            if ($defaults->customer_type) {
382
                $body['CustomerType'] = $defaults->customer_type;
383
            }
384
385
            if ($defaults->sales_order_group) {
386
                $body['SalesOrderGroup'] = $defaults->sales_order_group;
387
            }
388
389
            if ($defaults->source_id) {
390
                $body['SourceId'] = $defaults->source_id;
391
            }
392
393
            // add phone number if available
394
            if ($order->BillingAddress()->Phone) {
395
                $body['PhoneNumber'] = $order->BillingAddress()->Phone;
396
            }
397
398
            // add required date
399
            $date_required = new DateTime($order->Paid);
400
            if ($defaults->expected_days_to_deliver) {
401
                $date_required->modify('+' . $defaults->expected_days_to_deliver . 'day');
402
            }
403
            $body['RequiredDate'] = $date_required->format('Y-m-d\TH:i:s');
404
405
            if ($order->Notes) {
406
                $body['Comments'] = $order->Notes;
407
            }
408
409
            // Create Member for Guests
410
            if (!$member->exists()) {
411
                $member = Member::create();
412
                $member->FirstName = $order->FirstName;
413
                $member->Surname = $order->Surname;
414
                $member->Email = $order->getLatestEmail();
415
            }
416
417
            // See if New Customer/Guest has previously purchased
418
            if (!$member->Guid) {
419
                $response = UnleashedAPI::sendCall(
420
                    'GET',
421
                    'https://api.unleashedsoftware.com/Customers?contactEmail=' .  $member->Email
422
                );
423
424
                if ($response->getStatusCode() == '200') {
425
                    $contents = json_decode($response->getBody()->getContents(), true);
426
                    $items = $contents['Items'];
427
                    if ($items) {
428
                        // Email address exists
429
                        $member->Guid = $items[0]['Guid'];
430
                    } else {
431
                        // A Customer is not returned, we have a unique email address.
432
                        // Check to see if the Customer Code exists (note that the Customer Code cannot be doubled up)
433
                        $response = UnleashedAPI::sendCall(
434
                            'GET',
435
                            'https://api.unleashedsoftware.com/Customers?customerCode=' . $body['CustomerCode']
436
                        );
437
438
                        if ($response->getStatusCode() == '200') {
439
                            $contents = json_decode($response->getBody()->getContents(), true);
440
                            $items = $contents['Items'];
441
                            if ($items) {
442
                                // A Customer Code already exists (and the email address is unique).
443
                                // If the address is the same then this is the Customer
444
                                if ($this->matchCustomerAddress($items, $order->ShippingAddress())) {
445
                                    $member->Guid = $items[0]['Guid'];
446
447
                                    //Note the existing email address in the Comment
448
                                    //PUT Customer is not available in Unleashed
449
                                    if ($body['Comments']) {
450
                                        $body['Comments'] .= PHP_EOL;
451
                                    }
452
                                    $body['Comments'] .= _t(
453
                                        'UnleashedAPI.addEmailToCustomerComment',
454
                                        'Add email to Customer: {email_address}',
455
                                        '',
456
                                        ['email_address' => $member->Email]
457
                                    );
458
                                } else {
459
                                    // The Customer Code already exists, we have a unique email address, but
460
                                    // the delivery address is new.
461
                                    // Therefore, we need to create a new Customer with a unique Customer Code.
462
                                    $body['CustomerCode'] .= rand(10000000, 99999999);
463
                                }
464
                            }
465
                        }
466
                    }
467
                }
468
            }
469
470
            if (!$member->Guid) {
471
                // The Customer Code does not exists in Unleashed and the email address is unique
472
                // therefore create in Unleashed
473
                $member->Guid = (string) Utils::createGuid();
474
                $body_member = [
475
                    'Addresses' => $body['Addresses'],
476
                    'ContactFirstName' => $member->FirstName,
477
                    'ContactLastName' => $member->Surname,
478
                    'CreatedBy' => $body['CreatedBy'],
479
                    'Currency' => $body['Currency'],
480
                    'CustomerCode' => $body['CustomerCode'],
481
                    'CustomerName' => $body['CustomerName'],
482
                    'CustomerType' => $body['CustomerType'],
483
                    'Email' => $member->Email,
484
                    'Guid' => $member->Guid,
485
                    'PaymentTerm' => $body['PaymentTerm'],
486
                    'PhoneNumber' => $body['PhoneNumber'],
487
                    'PrintPackingSlipInsteadOfInvoice' => $body['PrintPackingSlipInsteadOfInvoice'],
488
                    'SellPriceTier' => $body['SellPriceTier'],
489
                    'SourceId' => $body['SourceId'],
490
                    'Taxable' => $body['Taxable'],
491
                    'TaxCode' => $body['Tax']['TaxCode']
492
                ];
493
494
                foreach ($body_member['Addresses'] as $index => $value) {
495
                    $body_member['Addresses'][$index]['IsDefault'] = true;
496
                }
497
498
                $response = UnleashedAPI::sendCall(
499
                    'POST',
500
                    'https://api.unleashedsoftware.com/Customers/' . $member->Guid,
501
                    ['json' => $body_member ]
502
                );
503
504
                if ($response->getReasonPhrase() == 'Created' && $order->Member()->exists()) {
505
                    $member->write();
506
                }
507
            }
508
509
            // Prepare Sales Order data
510
            // Skip if previous calls to Customer have failed and the Guid has not been set
511
            if ($member->Guid) {
512
                $body['Customer']['Guid'] = $member->Guid;
513
514
                $this->owner->extend('updateUnleashedSalesOrder', $body);
515
516
                $response = UnleashedAPI::sendCall(
517
                    'POST',
518
                    'https://api.unleashedsoftware.com/SalesOrders/' . $order->Guid,
519
                    ['json' => $body]
520
                );
521
                if ($response->getReasonPhrase() == 'Created') {
522
                    $this->owner->OrderSentToUnleashed = DBDatetime::now()->Rfc2822();
523
                    $this->owner->write();
524
                }
525
            }
526
        }
527
    }
528
}
529