* @copyright 2019 DPD France S.A.S.
* @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0)
*/
class AdminDPDFranceController extends ModuleAdminController
{
public $identifier = 'DPDFrance';
public function __construct()
{
$this->name = 'DPDFrance';
$this->bootstrap = true;
$this->display = 'view';
$this->meta_title = 'Gestion des expéditions';
parent::__construct();
if (!$this->module->active) {
Tools::redirectAdmin($this->context->link->getAdminLink('AdminHome'));
}
}
/* Converts country ISO code to DPD Station format */
public static function getIsoCodebyIdCountry($idcountry)
{
$sql='
SELECT `iso_code`
FROM `'._DB_PREFIX_.'country`
WHERE `id_country` = \''.pSQL($idcountry).'\'';
$result=Db::getInstance('_PS_USE_SQL_SLAVE_')->getRow($sql);
$isops=array('DE', 'AD', 'AT', 'BE', 'BA', 'BG', 'HR', 'DK', 'ES', 'EE', 'FI', 'FR', 'GB', 'GR', 'GG', 'HU', 'IM', 'IE', 'IT', 'JE', 'LV', 'LI', 'LT', 'LU', 'MC', 'NO', 'NL', 'PL', 'PT', 'CZ', 'RO', 'RS', 'SK', 'SI', 'SE', 'CH');
$isoep=array('D', 'AND', 'A', 'B', 'BA', 'BG', 'CRO', 'DK', 'E', 'EST', 'SF', 'F', 'GB', 'GR', 'GG', 'H', 'IM', 'IRL', 'I', 'JE', 'LET', 'LIE', 'LIT', 'L', 'F', 'N', 'NL', 'PL', 'P', 'CZ', 'RO', 'RS', 'SK', 'SLO', 'S', 'CH');
if (in_array($result['iso_code'], $isops)) {
// If the ISO code is in Europe, then convert it to DPD Station format
$code_iso=str_replace($isops, $isoep, $result['iso_code']);
} else {
// If not, then it will be 'INT' (intercontinental)
$code_iso=str_replace($result['iso_code'], 'INT', $result['iso_code']);
}
return $code_iso;
}
/* Get all orders but statuses cancelled, delivered, error */
public static function getAllOrders($id_shop)
{
if ($id_shop==0) {
$id_shop='LIKE "%"';
} else {
$id_shop='= '.(int) $id_shop;
}
$sql=' SELECT id_order
FROM '._DB_PREFIX_.'orders O
WHERE `current_state` NOT IN('.(int) Configuration::get('DPDFRANCE_ETAPE_LIVRE', null, null, (int) $id_shop).',0,5,6,7,8) AND O.id_shop '.$id_shop.'
ORDER BY id_order DESC
LIMIT 1000';
$result=Db::getInstance()->ExecuteS($sql);
$orders=array();
if (!empty($result)) {
foreach ($result as $order) {
$orders[]=(int) $order['id_order'];
}
}
return $orders;
}
/* Formats GSM numbers */
public static function formatGSM($tel_dest, $code_pays_dest)
{
$tel_dest=str_replace(array(' ', '.', '-', ',', ';', '/', '\\', '(', ')'), '', $tel_dest);
// Chrome autofill fix
if (Tools::substr($tel_dest, 0, 2)==33) {
$tel_dest=substr_replace($tel_dest, '0', 0, 2);
}
switch ($code_pays_dest) {
case 'F':
if (preg_match('/^((\+33|0)[67])(?:[ _.-]?(\d{2})){4}$/', $tel_dest)) {
return $tel_dest;
} else {
return false;
}
break;
case 'D':
if (preg_match('/^(\+|00)49(15|16|17)(\s?\d{8,9})$/', $tel_dest)) {
return $tel_dest;
} else {
return false;
}
break;
case 'B':
if (preg_match('/^(\+|00)324([56789])(\s?\d{7})$/', $tel_dest)) {
return $tel_dest;
} else {
return false;
}
break;
case 'AT':
if (preg_match('/^(\+|00)436([56789])(\s?\d{4,10})$/', $tel_dest)) {
return $tel_dest;
} else {
return false;
}
break;
case 'GB':
if (preg_match('/^(\+|00)447([3456789])(\s?\d{7})$/', $tel_dest)) {
return $tel_dest;
} else {
return false;
}
break;
case 'NL':
if (preg_match('/^(\+|00)316(\s?\d{8})$/', $tel_dest)) {
return $tel_dest;
} else {
return false;
}
break;
case 'P':
if (preg_match('/^(\+|00)3519(\s?\d{7})$/', $tel_dest)) {
return $tel_dest;
} else {
return false;
}
break;
case 'IRL':
if (preg_match('/^(\+|00)3538(\s?\d{8})$/', $tel_dest)) {
return $tel_dest;
} else {
return false;
}
break;
case 'E':
if (preg_match('/^(\+|00)34(6|7)(\s?\d{8})$/', $tel_dest)) {
return $tel_dest;
} else {
return false;
}
break;
case 'I':
if (preg_match('/^(\+|00)393(\s?\d{9})$/', $tel_dest)) {
return $tel_dest;
} else {
return false;
}
break;
case 'CH':
if (preg_match('/^(\+|00)417([56789])(\s?\d{7})$/', $tel_dest)) {
return $tel_dest;
} else {
return false;
}
break;
default:
return $tel_dest;
break;
}
}
/* Get delivery service for a cart ID & checks if id_carrier matches */
public static function getService($order, $lang_id)
{
$sql=Db::getInstance()->getRow('SELECT * FROM `'._DB_PREFIX_.'dpdfrance_shipping` WHERE `id_cart` = '.(int) $order->id_cart.' AND `id_carrier` = '.(int) $order->id_carrier);
if (!empty($sql)) {
$service=$sql['service'];
} else {
$service=null;
}
// Service override, forcing Relais or Predict shipment on eligible orders
if (!$service) {
$address_invoice=new Address($order->id_address_invoice, (int) $lang_id);
$address_delivery=new Address($order->id_address_delivery, (int) $lang_id);
$code_pays_dest=self::getIsoCodebyIdCountry((int) $address_delivery->id_country);
$tel_dest=(($address_delivery->phone_mobile)?$address_delivery->phone_mobile:(($address_invoice->phone_mobile)?$address_invoice->phone_mobile:(($address_delivery->phone)?$address_delivery->phone:(($address_invoice->phone)?$address_invoice->phone:''))));
$mobile=self::formatGSM($tel_dest, $code_pays_dest);
if (preg_match('/P\d{5}/i', $address_delivery->company)) {
$service='REL';
} elseif ($mobile&&$code_pays_dest!='INT'&&$order->id_carrier!=Configuration::get('DPDFRANCE_CLASSIC_CARRIER_ID', null, null, (int) $order->id_shop)) {
$service='PRE';
}
}
return $service;
}
/* Sync order status with parcel status, adds tracking number */
public function syncShipments($id_employee, $force)
{
/* Check if last tracking call is more than 1h old */
if ((time() - (int)Configuration::get('DPDFRANCE_LAST_TRACKING') < 3600) && $force == 0) {
die('DPD France parcel tracking update is done once every hour. - Last update on : '.date('d/m/Y - H:i:s', Configuration::get('DPDFRANCE_LAST_TRACKING')));
}
Configuration::updateValue('DPDFRANCE_LAST_TRACKING', time());
$predict_carrier_log = $classic_carrier_log = $relais_carrier_log = $predict_carrier_sql = $classic_carrier_sql = $relais_carrier_sql = '';
if (Configuration::get('DPDFRANCE_MARKETPLACE_MODE')) {
$europe_carrier_sql = 'CA.name LIKE \'%%\'';
} else {
$europe_carrier_sql = 'CA.name LIKE \'%DPD%\'';
}
if (Configuration::get('DPDFRANCE_PREDICT_CARRIER_ID')) {
$predict_carrier_log = Configuration::get('DPDFRANCE_PREDICT_CARRIER_ID').','.implode(',', array_map('intval', explode('|', Tools::substr(Configuration::get('DPDFRANCE_PREDICT_CARRIER_LOG'), 1))));
$predict_carrier_sql = 'CA.id_carrier IN ('.$predict_carrier_log.') OR ';
}
if (Configuration::get('DPDFRANCE_CLASSIC_CARRIER_ID')) {
$classic_carrier_log = Configuration::get('DPDFRANCE_CLASSIC_CARRIER_ID').','.implode(',', array_map('intval', explode('|', Tools::substr(Configuration::get('DPDFRANCE_CLASSIC_CARRIER_LOG'), 1))));
$classic_carrier_sql = 'CA.id_carrier IN ('.$classic_carrier_log.') OR ';
}
if (Configuration::get('DPDFRANCE_RELAIS_CARRIER_ID')) {
$relais_carrier_log = Configuration::get('DPDFRANCE_RELAIS_CARRIER_ID').','.implode(',', array_map('intval', explode('|', Tools::substr(Configuration::get('DPDFRANCE_RELAIS_CARRIER_LOG'), 1))));
$relais_carrier_sql = 'CA.id_carrier IN ('.$relais_carrier_log.') OR ';
}
$sql = 'SELECT O.reference as reference, O.id_carrier as id_carrier, O.id_order as id_order, O.shipping_number as shipping_number, O.id_shop as id_shop
FROM '._DB_PREFIX_.'orders AS O, '._DB_PREFIX_.'carrier AS CA
WHERE CA.id_carrier=O.id_carrier AND O.current_state
NOT IN ('.(int) Configuration::get('DPDFRANCE_ETAPE_LIVRE').',0,5,6,7,8) AND
('.$predict_carrier_sql.$classic_carrier_sql.$relais_carrier_sql.$europe_carrier_sql.')
ORDER BY id_order DESC
LIMIT 1000';
$orderlist=Db::getInstance()->ExecuteS($sql);
if (!empty($orderlist)) {
echo 'DPD France - Sync started
';
foreach ($orderlist as $orderinfos) {
$statuslist=array();
if (Validate::isLoadedObject($order = new Order($orderinfos['id_order']))) {
$internalref = $order->reference;
// Check past order states
$past_states = 0;
$orderhistory = $order->getHistory($order->id_lang);
foreach ($orderhistory as $state) {
if ($state['id_order_state'] == (int)Configuration::get('DPDFRANCE_ETAPE_EXPEDIEE', null, null, (int)$order->id_shop)) {
$past_states = 1;
} else {
if ($state['id_order_state'] == (int)Configuration::get('DPDFRANCE_ETAPE_LIVRE', null, null, (int)$order->id_shop)) {
$past_states = 2;
break;
}
}
}
// Exclude already delivered orders from sync
if ($past_states == 2) {
continue;
}
// Retrieve DPD service
$service=self::getService($order, Context::getContext()->language->id);
switch ($service) {
case 'PRE':
$compte_chargeur=Configuration::get('DPDFRANCE_PREDICT_SHIPPER_CODE', null, null, (int) $order->id_shop);
$depot_code=Configuration::get('DPDFRANCE_PREDICT_DEPOT_CODE', null, null, (int) $order->id_shop);
break;
case 'REL':
$compte_chargeur=Configuration::get('DPDFRANCE_RELAIS_SHIPPER_CODE', null, null, (int) $order->id_shop);
$depot_code=Configuration::get('DPDFRANCE_RELAIS_DEPOT_CODE', null, null, (int) $order->id_shop);
break;
default:
$compte_chargeur=Configuration::get('DPDFRANCE_CLASSIC_SHIPPER_CODE', null, null, (int) $order->id_shop);
$depot_code=Configuration::get('DPDFRANCE_CLASSIC_DEPOT_CODE', null, null, (int) $order->id_shop);
break;
}
if (!$compte_chargeur || !$depot_code) {
continue;
}
$variables=array( 'customer_center'=>'3',
'customer'=>'1064',
'password'=>'Pr2%5sHg',
'reference'=>$internalref,
'shipping_date'=>'',
'shipping_customer_center'=>$depot_code,
'shipping_customer'=>$compte_chargeur,
'searchmode'=>'SearchMode_Equals',
'language'=>'F',
);
$serviceurl='http://webtrace.dpd.fr/dpd-webservices/webtrace_service.asmx?WSDL';
// Call WS for traces by reference
try {
$client=new SoapClient($serviceurl, array('connection_timeout'=>5, 'exceptions'=>true));
$response=$client->getShipmentTraceByReferenceGlobalWithCenterAsArray($variables);
$result=$response->getShipmentTraceByReferenceGlobalWithCenterAsArrayResult->clsShipmentTrace;
if (!empty($result->LastError)) {
echo 'Order'.' '.$internalref.' - '.'Error: '.$result->LastError.'
';
} else {
// Only one parcel per reference
if (!is_array($result)) {
$traces=$result->Traces->clsTrace;
$returned_ref=$result->Reference;
if ($internalref == $returned_ref) {
// Parcels with only one status
if (!is_array($traces)) {
// Exclude CEDI-only parcels
if ($traces->StatusNumber != 8) {
$statuslist[$result->ShipmentNumber][]=$traces->StatusNumber;
}
} else {
// Parcel with multiple statuses
foreach ($traces as $status) {
$statuslist[$result->ShipmentNumber][]=$status->StatusNumber;
}
}
}
} else {
// Multiple parcels per reference
foreach ($result as $shipment) {
$returned_ref=$shipment->Reference;
if ($internalref == $returned_ref) {
$variables2=array( 'customer_center'=>'3',
'customer'=>'1064',
'password'=>'Pr2%5sHg',
'shipmentnumber'=>$shipment->ShipmentNumber
);
$response2=$client->getShipmentTrace($variables2);
$traces=$response2->getShipmentTraceResult->Traces->clsTrace;
// Parcels with only one status
if (!is_array($traces)) {
// Exclude CEDI-only parcels
if ($traces->StatusNumber == 8) {
continue;
}
$statuslist[$shipment->ShipmentNumber][]=$traces->StatusNumber;
} else {
// Parcel with multiple statuses
foreach ($traces as $status) {
$statuslist[$shipment->ShipmentNumber][]=$status->StatusNumber;
}
}
}
break; // Stop at first parcel
}
}
if (!empty($statuslist)) {
// Check delivery state
$tracking_number = (key($statuslist));
$delivery_state = 0;
foreach ($statuslist as $events) {
// Check if en-route event has been applied
if (array_intersect(array(10, 28, 44, 89), $events)) {
$delivery_state = 1;
}
// Check if delivered event has been applied
if (array_intersect(array(40, 400), $events)) {
$delivery_state = 2;
}
}
// Add tracking number if empty
if (!$order->shipping_number && $delivery_state != 0) {
if (Configuration::get('DPDFRANCE_AUTO_UPDATE') == 2) {
$url = 'http://www.dpd.fr/traces_'.$tracking_number;
$order->shipping_number=$tracking_number;
} else {
$url = 'http://www.dpd.fr/tracex_'.$internalref.'_'.$depot_code.$compte_chargeur;
$order->shipping_number=$internalref.'_'.$depot_code.$compte_chargeur;
}
Db::getInstance()->execute('UPDATE ' . _DB_PREFIX_ . 'orders SET shipping_number = "' . pSQL($order->shipping_number) . '" WHERE id_order = "' . (int)$order->id . '"');
Db::getInstance()->execute('UPDATE ' . _DB_PREFIX_ . 'order_carrier SET tracking_number = "' . pSQL($order->shipping_number) . '" WHERE id_order = "' . (int)$order->id . '"');
$order->update();
echo 'Order' . ' ' . $internalref . ' - ' . 'Tracking number' . ' ' . $tracking_number . ' ' . 'added' . '
';
}
// Update to delivered status only if parcel is delivered and there is no previous delivered status applied to that order
if ($delivery_state == 2 && $past_states != 2) {
$history = new OrderHistory();
$history->id_order = (int)$order->id;
$history->id_employee = (int)$id_employee;
$history->id_order_state = (int)Configuration::get('DPDFRANCE_ETAPE_LIVRE', null, null, (int)$order->id_shop);
$history->changeIdOrderState((int)Configuration::get('DPDFRANCE_ETAPE_LIVRE', null, null, (int)$order->id_shop), $order->id);
$history->addWithemail();
echo 'Order' . ' ' . $internalref . ' - ' . 'Tracking number' . ' ' . $tracking_number . ' ' . 'is delivered' . '
';
} else {
// Update to shipped status only if parcel is en route and there are no previous shipped or delivered status applied to that order
if ($delivery_state == 1 && $past_states == 0) {
$customer = new Customer((int)$order->id_customer);
$history = new OrderHistory();
$history->id_order = (int)$order->id;
$history->id_employee = (int)$id_employee;
$history->id_order_state = (int)Configuration::get('DPDFRANCE_ETAPE_EXPEDIEE', null, null, (int)$order->id_shop);
$history->changeIdOrderState((int)Configuration::get('DPDFRANCE_ETAPE_EXPEDIEE', null, null, (int)$order->id_shop), $order->id);
$template_vars = array('{followup}' => $url, '{firstname}' => $customer->firstname, '{lastname}' => $customer->lastname, '{order_name}' => $internalref, '{id_order}' => (int)$order->id);
switch (Language::getIsoById((int)$order->id_lang)) {
case 'fr':
$subject = 'Votre commande sera livrée par DPD';
break;
case 'en':
$subject = 'Your parcel will be delivered by DPD';
break;
case 'es':
$subject = 'Su pedido será enviado por DPD';
break;
case 'it':
$subject = 'Il vostro pacchetto sará trasportato da DPD';
break;
case 'de':
$subject = 'Ihre Bestellung wird per DPD geliefert werden';
break;
}
$history->addWithemail(true, $template_vars);
Mail::Send((int)$order->id_lang, 'in_transit', $subject, $template_vars, $customer->email, $customer->firstname . ' ' . $customer->lastname);
echo 'Order' . ' ' . $internalref . ' - ' . 'Parcel' . ' ' . $tracking_number . ' ' . 'is handled by DPD' . '
';
} else {
echo 'Order' . ' ' . $internalref . ' - ' . 'No update for parcel' . ' ' . $tracking_number . '
';
}
}
} else {
echo 'Order' . ' ' . $internalref . ' - ' . 'Parcel is found, not yet handled by DPD' . '
';
}
}
} catch (SoapFault $e) {
echo 'Order' . ' ' . $internalref . ' - ' . 'Error: '.$e->getMessage().'
';
continue;
}
}
}
echo 'DPD France - Sync complete.';
} else {
echo 'DPD France - No orders to update.';
}
}
/* Get eligible orders and builds up display */
public function renderView()
{
$this->fields_form[]['form'] = array();
$helper = $this->buildHelper();
$msg = '';
// RSS stream
$stream=array();
$rss=@simplexml_load_string(Tools::file_get_contents('http://www.dpd.fr/extensions/rss/flux_info_dpdfr.xml'));
if (!empty($rss)) {
if (empty($rss->channel->item)) {
$stream['error']=true;
} else {
$i=0;
foreach ($rss->channel->item as $item) {
$stream[$i]=array( 'category'=>(string) $item->category,
'title'=>(string) $item->title,
'description'=>(string) $item->description,
'date'=>strtotime((string) $item->pubDate)
);
if (strtotime("-30 day", strtotime(date('d-m-Y')))>$stream[$i]['date']) {
unset($stream[$i]);
}
$i++;
}
}
if (empty($stream)) {
$stream['error']=true;
}
} else {
$stream['error']=true;
}
// Update delivered orders
if (Tools::getIsset('updateDeliveredOrders')) {
if (Tools::getIsset('checkbox')) {
$orders=Tools::getValue('checkbox');
if (is_string($orders)) {
$orders = explode(',', $orders);
}
if (!empty($orders)) {
$sql='SELECT O.`id_order` AS id_order
FROM '._DB_PREFIX_.'orders AS O,
'._DB_PREFIX_.'carrier AS CA
WHERE CA.id_carrier=O.id_carrier AND
id_order IN ('.implode(',', array_map('intval', $orders)).')';
$orderlist=Db::getInstance()->ExecuteS($sql);
if (!empty($orderlist)) {
// Check if there are DPD orders
foreach ($orderlist as $orders) {
$id_order=$orders['id_order'];
if (Validate::isLoadedObject($order = new Order($id_order))) {
$history=new OrderHistory();
$history->id_order=(int) $id_order;
$history->id_order_state=(int) Configuration::get('DPDFRANCE_ETAPE_LIVRE', null, null, (int) $order->id_shop);
$history->changeIdOrderState((int) Configuration::get('DPDFRANCE_ETAPE_LIVRE', null, null, (int) $order->id_shop), $id_order);
$history->id_employee=(int) Context::getContext()->employee->id;
$history->addWithemail();
}
}
$msg = '
';
} else {
$type = 'Predict
';
}
$compte_chargeur = Configuration::get('DPDFRANCE_PREDICT_SHIPPER_CODE', null, null, (int)$order->id_shop);
$depot_code = Configuration::get('DPDFRANCE_PREDICT_DEPOT_CODE', null, null, (int)$order->id_shop);
$address = ''.($address_delivery->company ? $address_delivery->company.'
';
$compte_chargeur = Configuration::get('DPDFRANCE_RELAIS_SHIPPER_CODE', null, null, (int)$order->id_shop);
$depot_code = Configuration::get('DPDFRANCE_RELAIS_DEPOT_CODE', null, null, (int)$order->id_shop);
$relay_id='';
preg_match('/P\d{5}/i', $address_delivery->company, $matches, PREG_OFFSET_CAPTURE);
if ($matches) {
$relay_id=$matches[0][0];
}
$address = ''.$address_delivery->company.'
';
} else {
$type = 'Classic
';
}
$compte_chargeur = Configuration::get('DPDFRANCE_CLASSIC_SHIPPER_CODE', null, null, (int)$order->id_shop);
$depot_code = Configuration::get('DPDFRANCE_CLASSIC_DEPOT_CODE', null, null, (int)$order->id_shop);
$address = ''.($address_delivery->company ? $address_delivery->company.'