Fixed corp address

This commit is contained in:
gleemand 2022-03-20 12:25:55 +03:00 committed by GitHub
parent cf97a5e3db
commit d8fa2abbd5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 442 additions and 28 deletions

View File

@ -6,7 +6,7 @@
Prestashop module.
=================
Module allows integrate CMS Prestashop with [Simla.com](https://www.retailcrm.pro) ([Documentation](https://docs.retailcrm.ru/Users/Integration/SiteModules/PrestaShop) page)
Module allows integrate CMS Prestashop with [Simla.com](https://simla.com) ([Documentation](https://www.simla.com/docs/Users/Integration/SiteModules/PrestaShop) page)
#### Features:
@ -27,3 +27,7 @@ Module allows integrate CMS Prestashop with [Simla.com](https://www.retailcrm.pr
#### Customization
You can customize your module behavior using [Custom Filters](doc/3.%20Customization/Filters.md) or [Custom Classes](doc/3.%20Customization/Classes.md)
#### Documentation
[Here](doc/README.md) you can find more information about module setup and workflow

View File

@ -1,3 +1,5 @@
# Configuration
1. [Connection](Connection.md)
1. [Connection](Connection.md)
2. [Payment types](Payment%20types.md)
3. [Stock](Stock.md)

View File

@ -3,4 +3,5 @@
1. [Installation](Installation.md)
2. [Configuration](./Configuration/README.md)
3. [Upgrade](Upgrade.md)
4. [Catalog](Catalog.md)

View File

@ -5,4 +5,5 @@
Каждый запрос получения истории изменений сопровождается параметром _sinceId_, Который хранится в конфигурации модуля
1. [Синхронизация данных клиентов](Customers.md)
2. [Синхронизация данных заказов](Orders.md)
2. [Синхронизация данных заказов](Orders.md)
3. [Inventories](Inventories.md)

View File

@ -1 +1,3 @@
# CLI & Job Manager
# CLI & Job Manager
1. [Job Manager](Job%20Manager.md)

View File

@ -1 +1,13 @@
# Customers
## Адреса корпоративных клиентов
Клиент считается корпоративным, если в его `invoice address` поле `company` не пустое.
При выгрузке в CRM в этом случае создается корпортаивный клиент и привязаный к нему клиент типа `Контактное лицо`.
Если при выгрузке в CRM обнаружено, что создаваемый адрес в корпоративном клиенте в CRM уже существует (проверка по `externalId` и по совпадению поля `text`), то адрес в CRM редактируется (не создается новый).
Для названия адреса корпортаивного клиента в CRM используется поле `alias` (если оно заполнено), либо поле `company`.
При создании заказа в CMS для того же клиента, но с новым названием компании, создается новая компания в первом найденном в CRM корпоративном клиенте с этим контактным лицом.

View File

@ -1 +1,8 @@
# Forward Synchronization
1. [Abandoned carts](Abandoned%20carts.md)
2. [Customers](Customers.md)
3. [Export](Export.md)
4. [ICML](Icml.md)
5. [Orders](Orders.md)
6. [Upload](Upload.md)

View File

@ -0,0 +1,3 @@
# Pipeline
1. [Middlewares](Middlewares.md)

View File

@ -1 +1,8 @@
# Workflow
1. [Backward Synchronization](Backward%20Synchronization/README.md)
2. [CLI & Job Manager](CLI%20&%20Job%20Manager/README.md)
3. [Forward Synchronization](Forward%20Synchronization/README.md)
4. [Pipeline](Pipeline/README.md)
5. [Templates & Views](Templates%20&%20Views/README.md)
6. [Multistore](Multistore.md)

View File

@ -1 +1,5 @@
# Customization
1. [Classes](Classes.md)
2. [Examples](Examples.md)
3. [Filters](Filters.md)

View File

@ -1 +1,6 @@
# Developers documentation
# Documentation
1. [Setup](1.%20Setup/README.md)
2. [Workflow](2.%20Workflow/README.md)
3. [Customization](3.%20Customization/README.md)
4. [Known issues](4.%20Known%20issues/README.md)

View File

@ -243,6 +243,7 @@ class RetailcrmAddressBuilder extends RetailcrmAbstractDataBuilder
private function parseAddress()
{
$state = null;
$name = null;
if (!empty($this->address->id_state)) {
$stateName = State::getNameById($this->address->id_state);
@ -252,6 +253,10 @@ class RetailcrmAddressBuilder extends RetailcrmAbstractDataBuilder
}
}
if (static::MODE_CORPORATE_CUSTOMER === $this->mode) {
$name = isset($this->address->alias) ? $this->address->alias : $this->address->company;
}
return array_filter([
'index' => $this->address->postcode,
'city' => $this->address->city,
@ -263,6 +268,7 @@ class RetailcrmAddressBuilder extends RetailcrmAbstractDataBuilder
])),
'notes' => $this->address->other,
'region' => $state,
'name' => $name,
]);
}

View File

@ -401,6 +401,7 @@ class RetailcrmHistory
$addressInvoice = $corporateCustomerBuilder->getData()->getCustomerAddress();
} else {
$customerBuilder = new RetailcrmCustomerBuilder();
if ($customerId) {
$customerBuilder->setCustomer(new Customer($customerId));
}

View File

@ -396,6 +396,8 @@ class RetailcrmOrderBuilder
*/
private function createCorporateIfNotExist()
{
$crmAddressId = 0;
$companyWasFound = true;
$corporateWasFound = true;
$this->validateCmsCustomerInDb();
@ -416,14 +418,33 @@ class RetailcrmOrderBuilder
$crmCorporate = $this->findCorporateCustomerByCompany($this->invoiceAddress->company);
}
if (empty($crmCorporate)) {
$crmCorporate = $this->findCorporateCustomerByContact($customer['id']);
if (!empty($crmCorporate)) {
$companyWasFound = false;
}
}
if (empty($crmCorporate)) {
$crmCorporate = $this->createCorporateCustomer($customer['externalId']);
$corporateWasFound = false;
} elseif (isset($crmCorporate['id'])) {
$this->appendAdditionalAddressToCorporate($crmCorporate['id']);
$result = $this->appendAdditionalAddressToCorporate($crmCorporate['id']);
$crmAddressId = isset($result['id']) ? $result['id'] : 0;
}
if ($corporateWasFound) {
if (!$companyWasFound) {
$company = $this->buildCorporateCompany($crmAddressId);
$this->api->customersCorporateCompaniesCreate(
$crmCorporate['id'],
$company,
'id',
$this->getApiSite()
);
}
$contactList = $this->api->customersCorporateContacts(
$crmCorporate['id'],
['contactIds' => [$customer['id']]],
@ -526,6 +547,8 @@ class RetailcrmOrderBuilder
* Append new address to corporate customer if new address is not present in corporate customer.
*
* @param string|int $corporateId
*
* @return bool|array|\RetailcrmApiResponse
*/
private function appendAdditionalAddressToCorporate($corporateId)
{
@ -549,7 +572,7 @@ class RetailcrmOrderBuilder
foreach ($addresses as $addressInCrm) {
if (!empty($addressInCrm['externalId']) && $addressInCrm['externalId'] == $this->invoiceAddress->id) {
$this->api->customersCorporateAddressesEdit(
return $this->api->customersCorporateAddressesEdit(
$corporateId,
$addressInCrm['externalId'],
$address,
@ -557,8 +580,17 @@ class RetailcrmOrderBuilder
'externalId',
$this->getApiSite()
);
}
return;
if (RetailCrmTools::clearAddress($address['text']) === RetailCrmTools::clearAddress($addressInCrm['text'])) {
return $this->api->customersCorporateAddressesEdit(
$corporateId,
$addressInCrm['id'],
$address,
'id',
'id',
$this->getApiSite()
);
}
}
@ -658,6 +690,32 @@ class RetailcrmOrderBuilder
return [];
}
/**
* Find corporate customer by contact id
*
* @param $contactId
*
* @return array
*/
private function findCorporateCustomerByContact($contactId)
{
$crmCorporate = $this->api->customersCorporateList([
'contactIds' => [$contactId],
]);
if ($crmCorporate instanceof RetailcrmApiResponse
&& $crmCorporate->isSuccessful()
&& $crmCorporate->offsetExists('customersCorporate')
&& 0 < count($crmCorporate['customersCorporate'])
) {
$crmCorporate = $crmCorporate['customersCorporate'];
return reset($crmCorporate);
}
return [];
}
/**
* Get corporate companies, extract company data by provided identifiers
*
@ -1200,6 +1258,7 @@ class RetailcrmOrderBuilder
'birthday' => RetailcrmTools::verifyDate($object->birthday, 'Y-m-d')
? $object->birthday : '',
'sex' => '1' == $object->id_gender ? 'male' : ('2' == $object->id_gender ? 'female' : ''),
'isContact' => isset($address['company']),
],
$address
), function ($value) {

View File

@ -140,6 +140,36 @@ class RetailcrmTools
return 0;
}
/**
* Clears address string from any unwanted characters (to compare)
*
* @param string $address
*
* @return string
*/
public static function clearAddress($address)
{
$replacements = [
'Š' => 'S', 'š' => 's', 'Ž' => 'Z', 'ž' => 'z', 'À' => 'A', 'Á' => 'A', 'Â' => 'A', 'Ã' => 'A', 'Ä' => 'A',
'Å' => 'A', 'Æ' => 'A', 'Ç' => 'C', 'È' => 'E', 'É' => 'E', 'Ê' => 'E', 'Ë' => 'E', 'Ì' => 'I', 'Í' => 'I',
'Î' => 'I', 'Ï' => 'I', 'Ñ' => 'N', 'Ò' => 'O', 'Ó' => 'O', 'Ô' => 'O', 'Õ' => 'O', 'Ö' => 'O', 'Ø' => 'O',
'Ù' => 'U', 'Ú' => 'U', 'Û' => 'U', 'Ü' => 'U', 'Ý' => 'Y', 'Þ' => 'B', 'ß' => 'Ss', 'à' => 'a', 'á' => 'a',
'â' => 'a', 'ã' => 'a', 'ä' => 'a', 'å' => 'a', 'æ' => 'a', 'ç' => 'c', 'è' => 'e', 'é' => 'e', 'ê' => 'e',
'ë' => 'e', 'ì' => 'i', 'í' => 'i', 'î' => 'i', 'ï' => 'i', 'ð' => 'o', 'ñ' => 'n', 'ò' => 'o', 'ó' => 'o',
'ô' => 'o', 'õ' => 'o', 'ö' => 'o', 'ø' => 'o', 'ù' => 'u', 'ú' => 'u', 'û' => 'u', 'ü' => 'u', 'ý' => 'y',
'þ' => 'b', 'ÿ' => 'y',
];
$result = str_replace(
[' ', '-', ',', '.', "'", '`', '/', '|', '&', '#', '^', '(', ')', chr(0xC2) . chr(0xA0)],
'',
$address
);
$result = strtr($result, $replacements);
return strtolower($result);
}
/**
* Returns 'true' if provided date string is valid
*

View File

@ -38,9 +38,24 @@
class RetailcrmOrderBuilderTest extends RetailcrmTestCase
{
/**
* @var RetailcrmProxy
*/
private $apiMock;
protected function setUp()
{
parent::setUp();
$this->apiMock = $this->getApiMock(
[
'method',
'credentials',
'customersCorporateAddresses',
'customersCorporateAddressesEdit',
'customersCorporateAddressesCreate',
]
);
}
public function testInitialPriceZero()
@ -52,6 +67,86 @@ class RetailcrmOrderBuilderTest extends RetailcrmTestCase
$this->assertEquals(0, $resultItem['initialPrice']);
}
/**
* @dataProvider buildCrmCustomerAddresses
*/
public function testBuildCrmCustomer($address, $isCorp)
{
$customer = new Customer(1);
$crmCustomer = RetailcrmOrderBuilder::buildCrmCustomer($customer, $address);
$this->assertEquals($isCorp, $crmCustomer['isContact']);
$this->assertNotEmpty($crmCustomer['email']);
}
/**
* @dataProvider appendAdditionalAddressToCorporateAddresses
*/
public function testAppendAdditionalAddressToCorporate($address, $method)
{
$this->apiClientMock->expects($this->any())
->method('credentials')
->willReturn(
new RetailcrmApiResponse(
'200',
json_encode(
[
'success' => true,
]
)
)
)
;
$this->apiClientMock->expects($this->once())
->method('customersCorporateAddresses')
->willReturn(
new RetailcrmApiResponse(
'200',
json_encode(
[
'pagination' => [
'totalPageCount' => 1,
'currentPage' => 1,
],
'addresses' => $this->getCorpAddresses(),
]
)
)
)
;
$this->apiClientMock->expects($this->once())
->method($method)
->willReturn(
new RetailcrmApiResponse(
'200',
json_encode(
[
'pagination' => [
'totalPageCount' => 1,
'currentPage' => 1,
],
'addresses' => $this->getCorpAddresses(),
]
)
)
)
;
$orderBuilder = new RetailcrmOrderBuilder();
$orderBuilder->setApi($this->apiMock);
$class = new ReflectionClass($orderBuilder);
$property = $class->getProperty('invoiceAddress');
$property->setAccessible(true);
$property->setValue($orderBuilder, $this->convertAddress($address));
$method = $class->getMethod('appendAdditionalAddressToCorporate');
$method->setAccessible(true);
$method->invoke($orderBuilder, 0);
}
public function testBuildCrmOrder()
{
$order = new Order(1);
@ -70,6 +165,50 @@ class RetailcrmOrderBuilderTest extends RetailcrmTestCase
$this->assertEquals($order->reference, $crmOrder['number']);
}
private function convertAddress($crmAddress)
{
$address = new Address();
$address->address1 = $crmAddress['text'];
$address->alias = $crmAddress['name'];
$address->city = $crmAddress['city'];
$address->id_country = Country::getByIso($crmAddress['countryIso']);
$address->country = Country::getNameById((int) Configuration::get('PS_LANG_DEFAULT'), $address->id_country);
$address->postcode = $crmAddress['index'];
$address->company = $crmAddress['name'];
$address->id = isset($crmAddress['externalId']) ? $crmAddress['externalId'] : null;
return $address;
}
public function appendAdditionalAddressToCorporateAddresses()
{
return [
[
$this->getCorpAddresses()[0],
'customersCorporateAddressesEdit',
],
[
$this->getCorpAddresses()[1],
'customersCorporateAddressesEdit',
],
[
[
'id' => '3',
'externalId' => '30',
'index' => '423120',
'city' => 'Kazan',
'countryIso' => 'RU',
'text' => 'ul. Fuchika, d. 129',
'notes' => 'Building under the big tree',
'region' => 'Republic of Tatarstan',
'company' => 'MyCompany',
'name' => 'Home',
],
'customersCorporateAddressesCreate',
],
];
}
/**
* @return array
*/
@ -93,6 +232,22 @@ class RetailcrmOrderBuilderTest extends RetailcrmTestCase
];
}
public function buildCrmCustomerAddresses()
{
return [
[
'Address' => [
'company' => 'RetailCRM',
],
'Is corporate address?' => true,
],
[
'Address' => [],
'Is corporate address?' => false,
],
];
}
/**
* @return array
*/
@ -212,4 +367,34 @@ class RetailcrmOrderBuilderTest extends RetailcrmTestCase
return $order;
}
private function getCorpAddresses()
{
return [
[
'id' => '1',
'externalId' => '10',
'index' => '452320',
'city' => 'Dyurtyuli',
'countryIso' => 'RU',
'text' => 'ul. Matrosova, d. 8',
'notes' => 'from 12:00 to 15:00',
'region' => 'Republic of Bashkortostan',
'company' => 'MyCompany',
'name' => 'Office',
],
[
'id' => '2',
'externalId' => '90',
'index' => '760021',
'city' => 'Cali',
'countryIso' => 'CO',
'text' => 'Av 6 A NORTE No. 28 N-10, C.P 76001',
'notes' => 'Red door next to the road',
'region' => 'Cali',
'company' => 'MyCompany',
'name' => 'Warehouse',
],
];
}
}

View File

@ -42,14 +42,6 @@ if (class_exists('LegacyTests\Unit\ContextMocker')) {
abstract class RetailcrmTestCase extends \PHPUnit\Framework\TestCase
{
/**
* @var RetailcrmProxy
*/
private $apiMock;
/**
* @var PHPUnit_Framework_MockObject_MockObject
*/
protected $apiClientMock;
protected $contextMock;
@ -68,10 +60,10 @@ abstract class RetailcrmTestCase extends \PHPUnit\Framework\TestCase
{
$this->apiClientMock = $this->apiMockBuilder($methods)->getMock();
$this->apiMock = new RetailcrmProxy('https://test.test', 'test_key');
$this->apiMock->setClient($this->apiClientMock);
$apiMock = new RetailcrmProxy('https://test.test', 'test_key');
$apiMock->setClient($this->apiClientMock);
return $this->apiMock;
return $apiMock;
}
protected function setConfig()

View File

@ -51,6 +51,11 @@ class RetailcrmAddressBuilderTest extends RetailcrmTestCase
{
parent::setUp();
$this->address = $this->prepareAddress();
}
private function prepareAddress()
{
$this->defaultLang = (int) Configuration::get('PS_LANG_DEFAULT');
$address = new Address();
$address->id_state = State::getIdByName('Alabama');
@ -65,7 +70,36 @@ class RetailcrmAddressBuilderTest extends RetailcrmTestCase
$address->phone = '123';
$address->phone_mobile = '123';
$address->postcode = '333000';
$this->address = $address;
return $address;
}
/**
* @dataProvider parseAddresses
*/
public function testParseAddress($address, $mode, $addressName)
{
$addressBuilder = new RetailcrmAddressBuilder();
$class = new ReflectionClass($addressBuilder);
$property = $class->getProperty('address');
$property->setAccessible(true);
$property->setValue($addressBuilder, $address);
$property = $class->getProperty('mode');
$property->setAccessible(true);
$property->setValue($addressBuilder, $mode);
$method = $class->getMethod('parseAddress');
$method->setAccessible(true);
$result = $method->invoke($addressBuilder);
if (RetailcrmAddressBuilder::MODE_CORPORATE_CUSTOMER === $mode) {
$this->assertArrayHasKey('name', $result);
$this->assertEquals($addressName, $result['name']);
} else {
$this->assertArrayNotHasKey('name', $result);
}
}
public function testBuildRegular()
@ -178,6 +212,39 @@ class RetailcrmAddressBuilderTest extends RetailcrmTestCase
}
}
public function parseAddresses()
{
$addressWithAlias = $this->prepareAddress();
$addressWithAlias->alias = 'test_alias';
$addressWithCompany = $this->prepareAddress();
unset($addressWithCompany->alias);
$addressWithCompany->company = 'test_company';
return [
[
'Address' => $addressWithAlias,
'Mode' => RetailcrmAddressBuilder::MODE_CORPORATE_CUSTOMER,
'Address name' => $addressWithAlias->alias,
],
[
'Address' => $addressWithCompany,
'Mode' => RetailcrmAddressBuilder::MODE_CORPORATE_CUSTOMER,
'Address name' => $addressWithCompany->company,
],
[
'Address' => $addressWithAlias,
'Mode' => RetailcrmAddressBuilder::MODE_CUSTOMER,
'Address name' => null,
],
[
'Address' => $addressWithCompany,
'Mode' => RetailcrmAddressBuilder::MODE_CUSTOMER,
'Address name' => null,
],
];
}
public function getAddressLines()
{
return [

View File

@ -38,6 +38,32 @@
class RetailcrmToolsTest extends RetailcrmTestCase
{
/**
* @dataProvider clearAddresses
*/
public function testClearAddress($address, $result)
{
$this->assertEquals($result, RetailcrmTools::clearAddress($address));
}
public function clearAddresses()
{
return [
[
'Calle 66 # 11 -27, Casa || Casa',
'calle661127casacasa',
],
[
'Calle 111 # 7c 10 || 202 torre 6',
'calle1117c10202torre6',
],
[
'Bogotá, D.C. Calle 4B 20-85 || 312',
'bogotadccalle4b2085312',
],
];
}
/**
* @dataProvider equalCustomerAddresses
*/
@ -49,7 +75,7 @@ class RetailcrmToolsTest extends RetailcrmTestCase
public function equalCustomerAddresses()
{
return [
'Equal addresses' => [
'equal_addresses' => [
[
'phones' => [
['number' => '111'],
@ -72,7 +98,7 @@ class RetailcrmToolsTest extends RetailcrmTestCase
],
true,
],
'Changed phone' => [
'changed_phone' => [
[
'phones' => [
['number' => '222'],
@ -95,7 +121,7 @@ class RetailcrmToolsTest extends RetailcrmTestCase
],
false,
],
'Changed index' => [
'changed_index' => [
[
'phones' => [
['number' => '111'],
@ -118,7 +144,7 @@ class RetailcrmToolsTest extends RetailcrmTestCase
],
false,
],
'Reduced address' => [
'reduced_address' => [
[
'phones' => [
['number' => '111'],
@ -139,7 +165,7 @@ class RetailcrmToolsTest extends RetailcrmTestCase
],
false,
],
'Expanded address' => [
'expanded_address' => [
[
'phones' => [
['number' => '111'],
@ -160,7 +186,7 @@ class RetailcrmToolsTest extends RetailcrmTestCase
],
false,
],
'Reduced phone' => [
'reduced_phone' => [
[
'phones' => [
['number' => '111'],
@ -184,7 +210,7 @@ class RetailcrmToolsTest extends RetailcrmTestCase
],
false,
],
'Expanded phone' => [
'expanded_phone' => [
[
'phones' => [
['number' => '111'],
@ -208,7 +234,7 @@ class RetailcrmToolsTest extends RetailcrmTestCase
],
false,
],
'Replaced field' => [
'replaced_field' => [
[
'phones' => [
['number' => '111'],