src/Service/ExactApi.php line 55

Open in your IDE?
  1. <?php
  2. namespace App\Service;
  3. use App\Entity\Sale;
  4. use App\Repository\SaleRepository;
  5. use Doctrine\ORM\EntityManagerInterface;
  6. use Picqer\Financials\Exact\SalesOrder;
  7. use Picqer\Financials\Exact\SalesOrderLine;
  8. use Symfony\Component\Console\Style\SymfonyStyle;
  9. class ExactApi
  10. {
  11.     private $em;
  12.     private $sr;
  13.     private $geo;
  14.     private $exact;
  15.     public function __construct(EntityManagerInterface $emSaleRepository $srGeocoding $geoExact $exact)
  16.     {
  17.         $this->em $em;
  18.         $this->sr $sr;
  19.         $this->geo $geo;
  20.         $this->exact $exact;
  21.     }
  22.     /**
  23.      * @param $from_date
  24.      * @return SalesOrder[]
  25.      * @throws \Exception
  26.      */
  27.     public function getOrders($from_date)
  28.     {
  29.         try {
  30.             return $this->exact->getOrders($from_date);
  31.         } catch (\Exception $exception) {
  32.             throw new \Exception($exception->getMessage());
  33.         }
  34.     }
  35.     /**
  36.      * @param $order_number
  37.      * @return SalesOrder|null
  38.      * @throws \Exception
  39.      */
  40.     public function getOrder($order_number)
  41.     {
  42.         try {
  43.             return $this->exact->getOrder($order_number);
  44.         } catch (\Exception $exception) {
  45.             throw new \Exception($exception->getMessage());
  46.         }
  47.     }
  48.     public function sync($from_date nullSymfonyStyle $io): void
  49.     {
  50.         try {
  51.             if ($this->exact->doRefetchToken()) {
  52.                 $io->write('Exact requires a new refresh token');
  53.                 return;
  54.             }
  55.             if ($orders $this->getOrders($from_date)) {
  56.                 $io->progressStart(\count($orders));
  57.                 foreach ($orders as $order) {
  58.                     try {
  59.                         $attr $order->attributes();
  60.                         $sale $this->sr->get($attr['OrderNumber']);
  61.                         if ($sale->getId()) {
  62.                             $this->update($order$sale);
  63.                         } else {
  64.                             $this->create($order$sale);
  65.                         }
  66.                         $io->progressAdvance(1);
  67.                     } catch (\Exception $e) {
  68.                         continue;
  69.                     }
  70.                 }
  71.                 $io->progressFinish();
  72.             }
  73.         } catch (\Exception $e) {
  74.             if (false !== stripos($e->getMessage(), 'Could not acquire or refresh tokens')) {
  75.                 $this->exact->setRefetchToken();
  76.             } else {
  77.                 // send email???
  78.             }
  79.             throw new \Exception('error syncing Exact: ' $e->getMessage());
  80.         }
  81.     }
  82.     public function syncOrder($idSymfonyStyle $io): void
  83.     {
  84.         if ($this->exact->doRefetchToken()) {
  85.             $io->write('Exact requires a new refresh token');
  86.             return;
  87.         }
  88.         if ($order $this->getOrder($id)) {
  89.             $attr $order->attributes();
  90.             $sale $this->sr->get($attr['OrderNumber']);
  91.             if ($sale->getId()) {
  92.                 $this->update($order$sale);
  93.                 $io->success('Sync updated order ' $id);
  94.             } else {
  95.                 $this->create($order$sale);
  96.                 $io->success('Sync imported order ' $id);
  97.             }
  98.         } else {
  99.             $io->error('Order ' $id ' does not exist');
  100.         }
  101.     }
  102.     private function create(SalesOrder $orderSale $sale): void
  103.     {
  104.         $attr $order->attributes();
  105.         $ba $this->exact->getAccount($attr['InvoiceTo']);
  106.         [$ba_lat$ba_lng] = $this->geo->getLatLng($ba['AddressLine1'], ''$ba['Postcode'], $ba['City'], $ba['CountryName']);
  107.         $sa $this->exact->getAccount($attr['DeliverTo']);
  108.         [$sa_lat$sa_lng] = $this->geo->getLatLng($sa['AddressLine1'], ''$sa['Postcode'], $sa['City'], $sa['CountryName']);
  109.         if ($attr['InvoiceToContactPerson']) {
  110.             $contact_invoice $this->exact->getContact($attr['InvoiceToContactPerson']);
  111.         } else {
  112.             $contact_invoice $ba;
  113.         }
  114.         if ($attr['DeliverToContactPerson']) {
  115.             $contact_deliver $this->exact->getContact($attr['DeliverToContactPerson']);
  116.         } else {
  117.             $contact_deliver $sa;
  118.         }
  119.         [$items$notes] = $this->getItemsFromOrder($order);
  120.         $info = [
  121.             'order_id' => $attr['OrderNumber'],
  122.             'increment_id' => $attr['OrderNumber'],
  123.             'subtotal' => 0,//(float)$order['subtotal_incl_tax'],
  124.             'shipping' => 0,//(float)$order['shipping_incl_tax'],
  125.             'total' => (float)$attr['AmountFC'],
  126.             'tax' => 0,//(float)$order['tax_amount'],
  127.             'total_paid' => (float)$attr['AmountFC'],
  128.             'total_due' => 0,//(float)$order['total_due'],
  129.             'discount' => $this->getDiscount($attr$items),
  130.             'payment_method' => $this->getPaymentMethod($attr['PaymentCondition']),
  131.             'shipping_method' => $attr['ShippingMethodDescription'],
  132.             'description' => $notes trim(implode("\n\n"$notes)) : '',
  133.             'address' => [
  134.                 'billing' => [
  135.                     'firstname' => '',
  136.                     'lastname' => $attr['InvoiceToName'] ?: '',
  137.                     'email' => $this->getEmail($contact_invoice),
  138.                     'telephone' => $this->getPhone($contact_invoice),
  139.                     'street' => $ba['AddressLine1'],
  140.                     'number' => '',
  141.                     'toevoeging' => '',
  142.                     'postcode' => $ba['Postcode'],
  143.                     'city' => $ba['City'],
  144.                     'country' => $ba['CountryName'],
  145.                     'lat' => $ba_lat,
  146.                     'lng' => $ba_lng,
  147.                 ],
  148.                 'shipping' => [
  149.                     'firstname' => '',
  150.                     'lastname' => $attr['DeliverToName'] ?: '',
  151.                     'email' => $this->getEmail($contact_deliver),
  152.                     'telephone' => $this->getPhone($contact_deliver),
  153.                     'street' => $sa['AddressLine1'],
  154.                     'number' => '',
  155.                     'toevoeging' => '',
  156.                     'postcode' => $sa['Postcode'],
  157.                     'city' => $sa['City'],
  158.                     'country' => $sa['CountryName'],
  159.                     'lat' => $sa_lat,
  160.                     'lng' => $sa_lng,
  161.                 ]
  162.             ],
  163.             'company' => false,
  164.             'items' => $items,
  165.         ];
  166.         $sale->setType(Sale::TYPE_EXACT);
  167.         $sale->setStatus(Sale::STATUS_PROCESSING);
  168.         $sale->setInfo($info);
  169.         $sale->setStore(Sale::STORE_VDGARDE);
  170.         $sale->setPickup(false);
  171.         $sale->setCreatedAt($this->parseDate($attr['Created']));
  172.         $this->em->persist($sale);
  173.         $this->em->flush();
  174.         sleep(1);
  175.     }
  176.     private function getPhone(array $c)
  177.     {
  178.         if (isset($c['Mobile']) && $c['Mobile']) {
  179.             return $c['Mobile'];
  180.         }
  181.         if (isset($c['BusinessMobile']) && $c['BusinessMobile']) {
  182.             return $c['BusinessMobile'];
  183.         }
  184.         if (isset($c['Phone']) && $c['Phone']) {
  185.             return $c['Phone'];
  186.         }
  187.         if (isset($c['BusinessPhone']) && $c['BusinessPhone']) {
  188.             return $c['BusinessPhone'];
  189.         }
  190.         return '';
  191.     }
  192.     private function getEmail(array $c)
  193.     {
  194.         if (isset($c['Email']) && $c['Email']) {
  195.             return $c['Email'];
  196.         }
  197.         if (isset($c['BusinessEmail']) && $c['BusinessEmail']) {
  198.             return $c['BusinessEmail'];
  199.         }
  200.         return '';
  201.     }
  202.     private function parseDate($date_str)
  203.     {
  204.         $dt = (int)str_replace(['/Date('')/'], ''$date_str);
  205.         return new \DateTime(date('Y-m-d H:i:s'$dt 1000));
  206.     }
  207.     private function getPaymentMethod($method)
  208.     {
  209.         switch ((string)$method) {
  210.             case '00': return 'cashondelivery'//'Contant / Pin bij aflevering'
  211.             case 'ID': return 'ideal'//'iDeal Internetbetaling'
  212.             case '21': return 'factuur'//'Binnen 21 dagen na factuurdatum'
  213.             case '99': return 'banktransfer'//'Overmaken op ING rekening o.v.v. factuurnummer'
  214.             case 'bo': return 'bol'//'Voldaan via Bol.com'
  215.             case 'pa': return 'afterpay'//'Afterpay'
  216.             case 'cr': return 'visa'//'Creditcard betaling'
  217.             case 'PP': return 'paypal'//'Paypal Internetbetaling'
  218.             case 'PI': return 'pin';
  219.             case 'MC': return 'mistercash'//'Mister Cash Internetbetaling'
  220.             case '30'//'Netto binnen 30 dagen'
  221.             case '14'//'Binnen 14 dagen na factuurdatum'
  222.             case 'BA'//'Betaling per bank voor levering'
  223.             case '10'//'Is voldaan in winkel'
  224.             case '02'//'Vooraf via betaallink'
  225.                 return 'banktransfer';
  226.             case 'CH':
  227.             case '12':
  228.             case 'KB':
  229.             case 'SP':
  230.             case 'be':
  231.             case 'af':
  232.             case 'vo':
  233.             case 'ma':
  234.             case '03':
  235.                 return $method;
  236.         }
  237.         return $method;
  238.     }
  239.     private function update(SalesOrder $orderSale $sale): void
  240.     {
  241.         try {
  242.             $attr $order->attributes();
  243.             $info $sale->getInfo();
  244.             $info['total_paid'] = (float)$attr['AmountFC'];
  245.             [$items$notes] = $this->getItemsFromOrder($order);
  246.             $info['items'] = $items;
  247.             $info['description'] = $notes trim(implode("\n\n"$notes)) : '';
  248.             $sale->setInfo($info);
  249.             $this->em->persist($sale);
  250.             $this->em->flush();
  251.         } catch (\Exception $e) {
  252.         }
  253.     }
  254.     private function getItemsFromOrder(SalesOrder $order): array
  255.     {
  256.         $order_attr $order->attributes();
  257.         $notes = [];
  258.         if ($description trim($order_attr['Description'] ?? '')) {
  259.             $notes[] = $description;
  260.         }
  261.         $items = [];
  262.         foreach ($order->SalesOrderLines as $item) {
  263.             /* @var SalesOrderLine $item */
  264.             // Error? TODO @improve - do something with this?
  265.             if (is_array($item)) {
  266.                 dump($item);
  267.                 continue;
  268.             }
  269.             try {
  270.                 $attr $item->attributes();
  271.             } catch (\Exception $e) {
  272.                 continue; // TODO @improve - do something with the error?
  273.             }
  274.             $items[] = [
  275.                 'item_id' => $attr['ID'],
  276.                 'product_id' => $attr['Item'],
  277.                 'sku' => $attr['ItemCode'],
  278.                 'name' => $attr['ItemDescription'],
  279.                 'quantity' => (int)$attr['Quantity'],
  280.                 'price' => (float)$attr['UnitPrice'],
  281.                 'total' => (float)$attr['AmountFC'] + (float)$attr['VATAmount'],
  282.                 'tax_invoiced' => (float)$attr['VATAmount'],
  283.                 'tax_percent' => (float)$attr['VATPercentage'] * 100,
  284.                 'options' => [
  285.                     'volume' => $this->exact->getVolume($attr['Item'], (int)$attr['Quantity'])
  286.                 ],
  287.             ];
  288.             if (isset($attr['Notes']) && $attr['Notes']) {
  289.                 $notes[] = trim($attr['Notes']);
  290.             }
  291.         }
  292.         return [$items$notes];
  293.     }
  294.     private function getDiscount(array $attr$items)
  295.     {
  296.         if (!isset($attr['Discount']) || !$attr['Discount']) {
  297.             return 0;
  298.         }
  299.         $total 0;
  300.         foreach ($items as $item) {
  301.             $total += $item['total'];
  302.         }
  303.         return round($total $attr['Discount'], 2);
  304.     }
  305. }