Files

518 lines
18 KiB
PHP
Executable File

<?php
namespace JR\NetSuite\Mapper;
use JR\NetSuite\Mapper\AbstractMapper;
use Magento\Directory\Model\RegionFactory;
use Magento\Framework\Exception\LocalizedException;
use Magento\Store\Api\Data\StoreInterface;
use Magento\Store\Model\StoreManagerInterface;
use Magento\Framework\Api\SearchCriteriaBuilder;
use JR\NetSuite\Mapper\CustomerMapper;
use JR\NetSuite\Api\LocationRepositoryInterface;
use Magento\Sales\Api\OrderRepositoryInterface;
use Magento\Catalog\Api\ProductRepositoryInterface;
use Magento\Catalog\Model\Product;
use Magento\Customer\Api\GroupRepositoryInterface;
use Magento\Tax\Api\TaxRateRepositoryInterface;
// Handles the specifics of mapping order data.
class OrderMapper
{
const ORDER_STATUS_MAPPER = [
'pendingApproval' => 'pending',
'pendingFulfillment' => 'pending',
'cancelled' => 'canceled',
'partiallyFulfilled' => 'processing',
'pendingBillingPartFulfilled' => 'processing',
'pendingBilling' => 'processing',
'fullyBilled' => 'complete',
'closed' => 'closed',
'undefined' => 'undefined'
];
const SHIP_METHOD_MAPPER = [
'freeshipping_freeshipping' => '911',
'will_call' => '911',
'ups_ups' => '1716'
];
/**
* Shipping method mapping from NetSuite to Magento.
*/
const SHIP_METHOD_MAPPER_R = [
// Will Call
'911' => 'instore_pickup', // Will Call
// UPS
'xxxx' => 'ups_11', // UPS Standard
'1716' => 'ups_03', // UPS® Ground
'7074' => 'ups_59', // UPS 2nd Day Air A.M.
'6849' => 'ups_59', // UPS 2nd Day Air A.M.®
'4622' => 'ups_02', // UPS 2nd Day Air®
'7076' => 'ups_12', // UPS 3 Day Select
'6845' => 'ups_12', // UPS 3 Day Select®
'7077' => 'ups_01', // UPS Next Day Air
'6847' => 'ups_01', // UPS Next Day Air®
'4623' => 'ups_01', // UPS Next Day Air Saver® [***Closest Match***]
'4624' => 'ups_14', // UPS Next Day Air® Early A.M.®
'10720' => 'ups_08', // UPS Worldwide Expedited®
'10721' => 'ups_07', // UPS Worldwide Express®
'xxxxx' => 'ups_54', // UPS Worldwide Express Plus
'10722' => 'ups_65', // UPS Worldwide Saver®
// Fedex (Due to Fedex unavailability, flatrate_flatrate will be used)
'4630' => 'flatrate_flatrate', // 'fedex_FEDEX_2_DAY', // FedEx 2Day®
'4633' => 'flatrate_flatrate', // 'fedex_FEDEX_2_DAY_AM', // FedEx 2Day® A.M.
'4632' => 'flatrate_flatrate', // 'fedex_FEDEX_3_DAY_FREIGHT', // FedEx 3Day® Freight
'4631' => 'flatrate_flatrate', // 'fedex_FEDEX_EXPRESS_SAVER', // FedEx Express Saver®
'4627' => 'flatrate_flatrate', // 'fedex_FIRST_OVERNIGHT', // FedEx First Overnight®
'4634' => 'flatrate_flatrate', // 'fedex_FEDEX_1_DAY_FREIGHT', // FedEx First Overnight® Freight [***Closest Match***]
'1717' => 'flatrate_flatrate', // 'fedex_FEDEX_GROUND', // FedEx Ground®
'4626' => 'flatrate_flatrate', // 'fedex_GROUND_HOME_DELIVERY', // FedEx Home Delivery®
'4636' => 'flatrate_flatrate', // 'fedex_INTERNATIONAL_ECONOMY', // FedEx International Economy®
'4635' => 'flatrate_flatrate', // 'fedex_INTERNATIONAL_PRIORITY', // FedEx International Priority®
'4628' => 'flatrate_flatrate', // 'fedex_PRIORITY_OVERNIGHT', // FedEx Priority Overnight®
'4629' => 'flatrate_flatrate', // 'fedex_STANDARD_OVERNIGHT', // FedEx Standard Overnight®
// Others
'4625' => 'flatrate_flatrate', // USPS/Trucking/International
'7369' => 'flatrate_flatrate' // Customer Provided
];
/**
* @var RegionFactory
*/
protected $regionFactory;
/**
* @var int
*/
protected $storeId;
/**
* @var \JR\NetSuite\Helper\Data
*/
protected $helper;
/**
* @var SearchCriteriaBuilder
*/
protected $searchCriteriaBuilder;
/**
* @var CustomerMapper
*/
protected $customerMapper;
/**
* @var LocationRepository
*/
protected $locationRepository;
/**
* @var OrderRepositoryInterface
*/
protected $orderRepository;
/**
* @var ProductRepositoryInterface
*/
protected $productRepository;
/**
* @var Product
*/
protected $productModel;
/**
* @var GroupRepositoryInterface
*/
protected $groupRepository;
/**
* @var TaxRateRepositoryInterface
*/
protected $taxRateRepository;
/**
* OrderMapper constructor.
*/
public function __construct(
RegionFactory $regionFactory,
\JR\NetSuite\Helper\Data $helper,
SearchCriteriaBuilder $searchCriteriaBuilder,
CustomerMapper $customerMapper,
LocationRepositoryInterface $locationRepository,
OrderRepositoryInterface $orderRepository,
ProductRepositoryInterface $productRepository,
Product $productModel,
GroupRepositoryInterface $groupRepository,
TaxRateRepositoryInterface $taxRateRepository
) {
$this->regionFactory = $regionFactory;
$this->helper = $helper;
$this->storeId = $this->helper->getDefaultStoreId();
$this->searchCriteriaBuilder = $searchCriteriaBuilder;
$this->customerMapper = $customerMapper;
$this->locationRepository = $locationRepository;
$this->orderRepository = $orderRepository;
$this->productRepository = $productRepository;
$this->productModel = $productModel;
$this->groupRepository = $groupRepository;
$this->taxRateRepository = $taxRateRepository;
}
/**
* Convert data from NetSuite format to Magento format.
*
* @param array $netSuiteData
* @return array
*/
public function convertToMagentoFormat($netSuiteData)
{
// convert netsuite data to magento data
$magentoData = [];
foreach ($netSuiteData as $data) {
$convertedData = [
'netsuite_order_location_id' => $data['location'],
'netsuite_order_id' => $data['internalid'],
'netsuite_order_number' => $data['tranid'],
'netsuite_customer_id' => $data['entity'],
'store_id' => $this->storeId,
'email' => $data['email'],
'company' => $data['companyname'],
'status' => self::ORDER_STATUS_MAPPER[$data['status']] ?? 'undefined',
'total' => $data['total'],
'amount' => $data['amount'],
'tax' => $data['taxtotal'],
'shipping_amount' => $data['shippingcost'],
'shipping_description' => $data['shipcarrier'],
'shipping_method' => $data['shipmethod'],
'purchase_date' => $data['datecreated'],
'last_modified_date' => $data['lastmodifieddate'],
'other_ref_num' => $data['otherrefnum'],
];
if ($data['customer'] ?? false) {
$convertedData['customer'] = $this->customerMapper->convertToMagentoFormat([$data['customer']]);
$convertedData['customer'] = reset($convertedData['customer']);
}
// billing address
if ($data['billaddress'] != '') {
$billaddressee = $this->filterFirstNonEmpty([
$data['billattention'],
$data['billaddressee'],
]);
if (empty($billaddressee)) {
$billaddressee = $this->getNameFromAddressText($data['billaddress']);
}
$convertedData['billing_address'] = [
'firstname' => $convertedData['customer']['billing_address']['firstname'] ??
$convertedData['customer']['shipping_address']['firstname'] ??
(count($billaddressee) > 0 ? array_shift($billaddressee) : ''),
'lastname' => $convertedData['customer']['billing_address']['lastname'] ??
$convertedData['customer']['shipping_address']['lastname'] ??
(count($billaddressee) > 0 ? implode(' ', $billaddressee) : $data['companyname']),
'company' => $data['companyname'],
'street' => [!empty($data['billaddress1']) ? $data['billaddress1'] : '-', $data['billaddress2']],
'city' => $data['billcity'],
'country_id' => $data['billcountry'],
'region_id' => $this->getRegion($data['billcountry'], $data['billstate'])->getId(),
'postcode' => !empty($data['billzip']) ? $data['billzip'] : '-' ,
'telephone' => $data['billphone'],
'fax' => '',
'save_in_address_book' => 0
];
}
if ($data['shipaddress'] != '') {
$shipaddressee = $this->filterFirstNonEmpty([
$data['shipattention'],
$data['shipaddressee'],
]);
if (empty($shipaddressee)) {
$shipaddressee = $this->getNameFromAddressText($data['shipaddress']);
}
$convertedData['shipping_address'] = [
'firstname' => $convertedData['customer']['shipping_address']['firstname'] ??
$convertedData['customer']['billing_address']['firstname'] ??
(count($shipaddressee) > 0 ? array_shift($shipaddressee) : ''),
'lastname' => $convertedData['customer']['shipping_address']['lastname'] ??
$convertedData['customer']['billing_address']['lastname'] ??
(count($shipaddressee) > 0 ? implode(' ', $shipaddressee) : $data['companyname']),
'company' => $data['companyname'],
'street' => [!empty($data['shipaddress1']) ? $data['shipaddress1'] : '-', $data['shipaddress2']],
'city' => $data['shipcity'],
'country_id' => $data['shipcountry'],
'region_id' => $this->getRegion($data['shipcountry'], $data['shipstate'])->getId(),
'postcode' => $data['shipzip'],
'telephone' => $data['shipphone'],
'fax' => '',
'save_in_address_book' => 0
];
if (
isset($convertedData['shipping_address'])
&& !empty($convertedData['shipping_address']['firstname'])
&& ($convertedData['shipping_address']['firstname'][0] ?? '' == $convertedData['shipping_address']['street'][0][0] ?? '')
&& isset($convertedData['billing_address'])
) {
$convertedData['shipping_address']['firstname'] = $convertedData['billing_address']['firstname'];
$convertedData['shipping_address']['lastname'] = $convertedData['billing_address']['lastname'];
}
}
// item
$convertedData['items'] = [];
foreach ($data['items'] as $item) {
$convertedData['items'][] = [
'netsuite_order_item_id' => $item['itemId'],
'sku' => $item['itemText'],
'description' => $item['description'],
'qty' => $item['quantity'],
'type' => $item['type'],
'price' => $item['rate'],
'amount' => $item['amount'],
];
}
// payment
$convertedData['payment'] = [
'method' => $data['paymentmethod'],
'paid_amount' => $data['paidamount'],
'date_paid' => $data['paymenteventdate'],
'status' => $data['paymenteventresult'],
'type' => $data['paymenteventtype'],
// 'cc_type' => '',
// 'cc_number_enc' => '',
// 'cc_last4' => '',
// 'cc_owner' => '',
// 'cc_exp_month' => '',
// 'cc_exp_year' => '',
// 'cc_ss_start_month' => '',
// 'cc_ss_start_year' => '',
// 'cc_ss_issue' => '',
];
$magentoData[] = $convertedData;
}
return $magentoData;
}
/**
* Convert data from Magento format to NetSuite format.
*
* @param \Magento\Sales\Model\Order $order
* @return array
*/
public function convertToNetSuiteFormat($order)
{
$data = $this->initializeData($order);
$data['items'] = $this->getOrderItems($order);
return $data;
}
/**
* Initialize variables.
*
* @param \Magento\Sales\Model\Order $order
* @param string $netsuiteCustomerId
* @return array
*/
protected function initializeData($order, $netsuiteCustomerId = '')
{
$shippingAddress = $order->getShippingAddress();
$billingAddress = $order->getBillingAddress();
$phone = preg_replace('/[^0-9]/', '', $shippingAddress->getTelephone());
if (strlen($phone) == 10) {
$phone = '0001' . $phone;
}
$phone = str_pad($phone, 14, '0', STR_PAD_LEFT);
try {
$region = $this->regionFactory->create();
$region->loadByName($shippingAddress->getRegion(), $shippingAddress->getCountryId());
} catch (\Exception $e) {
$region = null;
}
$address = [
'country' => $shippingAddress->getCountryId(),
'city' => $shippingAddress->getCity(),
'state' => $region?->getCode() ?? '',
'zip' => $shippingAddress->getPostcode(),
'addressee' => $shippingAddress->getCompany(),
'attention' => $shippingAddress->getFirstname() . ' ' . $shippingAddress->getLastname(),
'addrphone' => $phone,
'addr1' => $shippingAddress->getStreetLine(1),
'addr2' => $shippingAddress->getStreetLine(2),
];
$payment = $order->getPayment();
$cardInfo = [
'Card #' => '******' . $payment->getCcLast4(),
'Billing address' => implode(', ', array_filter($billingAddress->getStreet())) . ', ' . $billingAddress->getCity() . ', ' . $billingAddress->getRegionCode() . ' ' . $billingAddress->getPostcode() . ', ' . $billingAddress->getCountryId(),
'Card Holder Name' => $billingAddress->getFirstname() . ' ' . $billingAddress->getLastname(),
];
$cardInfo = implode("\n", array_map(function ($key, $value) {
return $key . ': ' . $value;
}, array_keys($cardInfo), $cardInfo));
$phone = preg_replace('/[^0-9]/', '', $shippingAddress->getTelephone() ?? '');
$phone = substr($phone, strlen($phone) - 10, 10);
// get location id based on shipping address
$locationId = $this->getLocationId($shippingAddress->getCity());
if (!$locationId) {
$locationId = $this->getLocationId($region?->getCode() ?? '');
}
$vars = [
'note' => $order->getCustomerNote() ?? '',
'address' => $address,
'orderNo' => $order->getIncrementId(),
'cardInfo' => $cardInfo,
'terms' => '12',
'phone' => $phone,
'custId' => $netsuiteCustomerId, // TODO: get customer ID from NetSuite
'company' => $order->getBillingAddress()->getCompany(),
'location' => '222', // TODO: get location from config
'shipmethod' => self::SHIP_METHOD_MAPPER[$order->getShippingMethod()],
'shippingcost' => $order->getShippingAmount(),
// TODO: get entity from NetSuite
// 'entity' => $netsuiteEntityId,
'email' => $order->getCustomerEmail(),
// 'customer' => $this->customerMapper->convertToNetSuiteFormatByEmail($order->getCustomerEmail()),
];
if ($order->getDiscountAmount() < 0) {
$vars['discountItem'] = $order->getDiscountAmount();
$vars['discountRate'] = round(($order->getDiscountAmount() / $order->getGrandTotal()) * 100, 2);
}
return $vars;
}
/**
* search location list by name
*/
protected function getLocationId($name)
{
$searchCriteria = $this->searchCriteriaBuilder
->addFilter('is_active', 1)
->addFilter('name', $name)
->create();
$locations = $this->locationRepository->getList($searchCriteria)->getItems();
if (count($locations) > 0) {
$location = reset($locations);
return $location->getLocationId() ?? '';
}
return '';
}
/**
* get order by field and value.
*
* @param string $field
* @return \Magento\Sales\Model\Order | null
*/
public function getOrderBy($field, $value)
{
$searchCriteria = $this->searchCriteriaBuilder
->addFilter($field, $value)
->create();
$orders = $this->orderRepository->getList($searchCriteria)->getItems();
$order = count($orders) > 0 ? reset($orders) : null;
return $order;
}
/**
* Get order items.
*
* @param \Magento\Sales\Model\Order $order
* @return array
*/
protected function getOrderItems($order)
{
$items = [];
foreach ($order->getAllItems() as $item) {
$product = $this->productModel->load($item->getProductId());
$items[] = [
'id' => $product->getCustomAttribute('netsuite_id')?->getValue() ?? '',
'quantity' => $item->getQtyOrdered(),
'rate' => $item->getPrice(),
];
}
return $items;
}
/**
* filter first non-empty value from array and return as array.
*
* @param array $array
* @return array
*/
protected function filterFirstNonEmpty($array)
{
foreach ($array as $value) {
if ($value != '') {
$value = preg_replace('/[^\p{L}\p{M}\,\-\_\.\'\s\d]/', ' ', $value);
$value = explode(' ', $value);
return $value;
}
}
return [];
}
/**
* Get name from address text.
*
* @param string $address
* @return array
*/
protected function getNameFromAddressText($address)
{
if (empty($address)) {
return [];
}
return $this->filterFirstNonEmpty(explode("\n", $address));
}
/**
* Get region by country code and region code.
*
* @param string $countryCode
* @param string $regionCode
* @return \Magento\Directory\Model\Region
*/
protected function getRegion($countryCode, $regionCode)
{
try {
$region = $this->regionFactory->create();
$region->loadByCode($regionCode, $countryCode);
return $region;
} catch (\Exception $e) {
throw new LocalizedException(__("Error fetching region ID: %1", $e->getMessage()));
}
}
/**
* Convert a given NetSuite shipping method to a corresponding Magento shipping method.
* If the given NetSuite shipping method does not exist in the mapping, 'flatrate_flatrate'
* will be returned.
*
* @param string $nsShipMethod NetSuite ship method internal ID.
* @return string
*/
public function parseNetSuiteShippingMethod($nsShipMethod)
{
if (array_key_exists($nsShipMethod, self::SHIP_METHOD_MAPPER_R)) {
return self::SHIP_METHOD_MAPPER_R[$nsShipMethod];
}
return 'flatrate_flatrate';
}
}