1129 lines
40 KiB
PHP
1129 lines
40 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace External\PompoBundle\DRS\Synchronizer;
|
|
|
|
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
|
|
use External\PompoBundle\Email\UserPOSRegistrationEmail;
|
|
use External\PompoBundle\Util\Configuration;
|
|
use External\PompoBundle\Util\User\PompoUserUtil;
|
|
use External\PompoBundle\Util\User\UserCardUtil;
|
|
use KupShop\AdminBundle\Util\ActivityLog;
|
|
use KupShop\KupShopBundle\Context\ContextManager;
|
|
use KupShop\KupShopBundle\Util\ArrayUtil;
|
|
use KupShop\KupShopBundle\Util\Database\QueryHint;
|
|
use KupShop\KupShopBundle\Util\Functional\Mapping;
|
|
use KupShop\KupShopBundle\Util\StringUtil;
|
|
use Query\Operator;
|
|
use Symfony\Contracts\Service\Attribute\Required;
|
|
|
|
/**
|
|
* Synchronizace uzivatelu do DRSu, zpetne z DRSu stahujeme adresy, karty a deti.
|
|
*/
|
|
class UserSynchronizer extends AbstractSynchronizer
|
|
{
|
|
#[Required]
|
|
public PompoUserUtil $pompoUserUtil;
|
|
#[Required]
|
|
public UserCardUtil $userCardUtil;
|
|
#[Required]
|
|
public Configuration $configuration;
|
|
#[Required]
|
|
public UserPOSRegistrationEmail $registrationEmail;
|
|
#[Required]
|
|
public ContextManager $contextManager;
|
|
|
|
protected static $type = 'user';
|
|
|
|
private ?int $timestampSql = null;
|
|
private ?int $timestampLocal = null;
|
|
|
|
private const USER_DELETED_NOTE = 'Uživatel smazán v DRSu';
|
|
private const USER_GROUP_EMPLOYEE = 10247;
|
|
|
|
private const MAX_RESULT_ITERATION = 10_000;
|
|
|
|
public function process(): void
|
|
{
|
|
$this->processFromDRS();
|
|
$this->timestampLocal = time();
|
|
$this->processToDRS();
|
|
|
|
$this->processBonusPointsToDRS();
|
|
|
|
$this->updateTimestamps();
|
|
}
|
|
|
|
/**
|
|
* Zapisovani novych uzivatelu a aktualizace upravenych uzivatelu na e-shopu do DRSu.
|
|
*/
|
|
public function processToDRS(): void
|
|
{
|
|
if (isDevelopment()) {
|
|
return;
|
|
}
|
|
|
|
$timestamp = $this->getUserTimestamp()['local'] ?? null;
|
|
|
|
$qb = sqlQueryBuilder()
|
|
->select('u.*, du.id_drs')
|
|
->from('users', 'u')
|
|
->leftJoin('u', 'drs_users', 'du', 'du.id_user = u.id')
|
|
->andWhere('u.date_reg IS NOT NULL')
|
|
->groupBy('u.id');
|
|
|
|
if ($timestamp) {
|
|
// uzivatel byl aktualizovan - provedla se na nem nejaka zmena, takze ho poslu do DRSu
|
|
$qb->andWhere('u.date_updated IS NOT NULL AND u.date_updated >= :lastSync')
|
|
->setParameter('lastSync', date('Y-m-d H:i:s', $timestamp));
|
|
|
|
$qb->setMaxResults(self::MAX_RESULT_ITERATION);
|
|
}
|
|
|
|
foreach ($qb->execute() as $item) {
|
|
$customerId = $item['id_drs'];
|
|
|
|
if ($customerId && str_contains($item['email'], $customerId) && str_contains($item['email'], 'pompo.cz')) {
|
|
continue;
|
|
}
|
|
|
|
$this->debugLogUserChange((int) $item['id'], 'processToDRS::start', [
|
|
'timestamp' => $timestamp,
|
|
'item' => $item,
|
|
'drsId' => $customerId,
|
|
]);
|
|
|
|
// Zamestnanecky ucet - nechceme ho v DRSu aktualizovat, abychom jim neprepisovali nejaky jejich data.
|
|
// 5 cislic a zacina cislem 6
|
|
// NEBO pokud byl uzivatel "smazan" z DRSu, tak ho uz do DRSu nesynchronizujeme
|
|
if ($this->isUserEmployer($customerId ? (int) $customerId : null) || $item['note'] === self::USER_DELETED_NOTE) {
|
|
$this->debugLogUserChange((int) $item['id'], 'processToDRS::skip', [
|
|
'isUserDeleted' => $item['note'] === self::USER_DELETED_NOTE,
|
|
'isUserEmployer' => $this->isUserEmployer($customerId ? (int) $customerId : null),
|
|
]);
|
|
|
|
continue;
|
|
}
|
|
|
|
$user = new \User();
|
|
$user->loadData($item);
|
|
|
|
if (empty($customerId)) {
|
|
// zkusim najit uzivatele v DRSu, abych ho pripadne nezakladal znovu
|
|
if ($customer = $this->drsApi->getUserByEmail($user->email)) {
|
|
if ($customerId = $customer['@attributes']['contactNumber'] ?? null) {
|
|
$drsCustomer = $this->drsApi->getSQLUserByContactNumber((int) $customerId);
|
|
// pokud je ten uzivatel v DRSu smazanej, tak uz ho nejspis i mame na shopu, takze to na nej
|
|
// nechceme parovat, protoze nedava smysl to parovat na smazanej ucet
|
|
// takze nastavim $customerId na null, aby se vytvoril novej uzivatel v DRSu
|
|
if ($drsCustomer && $this->isUserDeletedInDRS($drsCustomer)) {
|
|
$customerId = null;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$userData = $this->getUserData($user, $customerId ? (int) $customerId : null);
|
|
|
|
// Ulozim / updatuju uzivatele v DRSu
|
|
$customerId = $this->saveUser(
|
|
$userData
|
|
);
|
|
|
|
$this->debugLogUserChange((int) $item['id'], 'processToDRS::saveUser', [
|
|
'saveUserResult' => $customerId,
|
|
'userData' => $userData,
|
|
]);
|
|
|
|
// Vytvorim si mapping, pokud jeste neexistoval
|
|
if ($customerId && empty($item['id_drs'])) {
|
|
$this->createUserMapping((int) $user->id, $customerId);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Aktualizace bodu smerem z e-shopu do DRSu.
|
|
*
|
|
* Oddeleno od synchronizace uzivatelu do DRSu kvuli problemum, ktere to zpusobovalo. Pri aktualizaci bodu nechceme
|
|
* aktualizovat e-mail, primarne chceme aktualizovat pouze ty body, ale bohuzel je potreba posilat i dalsi udaje.
|
|
*/
|
|
public function processBonusPointsToDRS(?int $timestamp = null): void
|
|
{
|
|
if (isDevelopment()) {
|
|
return;
|
|
}
|
|
|
|
$timestamp = $timestamp ?: ($this->getUserTimestamp()['local'] ?? null);
|
|
|
|
$qb = sqlQueryBuilder()
|
|
->select('u.*, du.id_drs')
|
|
->from('users', 'u')
|
|
->join('u', 'drs_users', 'du', 'du.id_user = u.id')
|
|
->andWhere('u.date_reg IS NOT NULL')
|
|
->groupBy('u.id');
|
|
|
|
if ($timestamp) {
|
|
// byla provedena nejaka zmena na bodech uzivatele, takze ho chci poslat do DRSu, aby se mu aktualizovali body
|
|
$qb->andWhere('(u.id IN (SELECT bp.id_user FROM bonus_points bp WHERE bp.date_created >= :lastSync GROUP BY bp.id_user))')
|
|
->setParameter('lastSync', date('Y-m-d H:i:s', $timestamp));
|
|
|
|
$qb->setMaxResults(self::MAX_RESULT_ITERATION);
|
|
}
|
|
|
|
foreach ($qb->execute() as $item) {
|
|
$customerId = (int) $item['id_drs'];
|
|
if ($this->isUserEmployer($customerId) || $item['note'] === self::USER_DELETED_NOTE) {
|
|
continue;
|
|
}
|
|
|
|
$points = $this->getUserBonusPoints((int) $item['id']);
|
|
|
|
try {
|
|
$this->drsApi->updateUserBonusPoints($customerId, $points);
|
|
} catch (\Throwable $e) {
|
|
$this->logger->logException($e, sprintf('[DRS] Aktualizace bodů z e-shopu do DRSu se nezdařila: %s', $customerId), [
|
|
'points' => $points,
|
|
'drsId' => $customerId,
|
|
]);
|
|
}
|
|
|
|
$this->debugLogUserChange((int) $item['id'], 'processBonusPointsToDRS', [
|
|
'drsId' => $customerId,
|
|
'points' => $points,
|
|
]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Stahovani zmen z DRSu k uzivateli na e-shopu.
|
|
*/
|
|
public function processFromDRS(?int $timestamp = null, ?int $limit = 1_000): void
|
|
{
|
|
if (!$timestamp) {
|
|
$timestamp = $this->getUserTimestamp()['sql'] ?? null;
|
|
}
|
|
|
|
$this->debugLogUserChange(0, 'processFromDRS::start', []);
|
|
$iteration = 0;
|
|
|
|
// ziskam si uzivatele, u kterych se neco zmenilo
|
|
// nacitam max `$limit` uzivatelu v ramci jednoho sync runu, abych nebloknul sync v pripade sileneho mnozstvi zmenenych uzivatelu
|
|
$users = $this->drsApi->getUsersWithAdditionalData(
|
|
timestamp: $timestamp,
|
|
limit: $limit,
|
|
onBatchProcessed: function () {
|
|
// aktualizovat sql timestamp v settingach po tom, co se zpracuje batch, aby se to prubezne posouvalo
|
|
$this->updateTimestamps(local: false);
|
|
}
|
|
);
|
|
|
|
foreach ($users as $item) {
|
|
$this->debugLogUserChange(0, 'processFromDRS::processing', ['item' => $item]);
|
|
try {
|
|
$this->updateUser($item);
|
|
} catch (\SoapFault|UniqueConstraintViolationException $e) {
|
|
// ignoruju SOAP chyby a duplicity
|
|
}
|
|
|
|
// ulozit timestamp, kterej se pak ulozi do DB
|
|
$this->timestampSql = max((int) $item['timestamp'], $this->timestampSql ?? (int) $item['timestamp']);
|
|
|
|
if ($this->mode !== self::MODE_FULL) {
|
|
if ($iteration > self::MAX_RESULT_ITERATION) {
|
|
break;
|
|
}
|
|
|
|
$iteration++;
|
|
}
|
|
}
|
|
|
|
// zaktivovat uzivatele, kterym se doplnil mail
|
|
$this->updateUsersVisibility();
|
|
}
|
|
|
|
public function processDRSUser(int $drsId): void
|
|
{
|
|
if ($user = $this->drsApi->getUser($drsId)) {
|
|
$this->updateUser($user);
|
|
}
|
|
}
|
|
|
|
public function updateUser(array $item, array $config = []): void
|
|
{
|
|
try {
|
|
// nactu si uzivatele pres SOAP, protoze tam jsou data, ktere potrebuji a je tezsi se k nim dostat jinak
|
|
if (!($customer = $this->drsApi->getUserById((int) $item['id']))) {
|
|
return;
|
|
}
|
|
} catch (\SoapFault $e) {
|
|
// nejspis se jedna o smazaneho uzivatele, a tak ho nejde pres SOAP nacist, ale potrebuju mu udelat update na shopu i tak
|
|
if (!str_contains($e->getMessage(), 'Odkaz na objekt není nastaven na instanci objektu')) {
|
|
throw $e;
|
|
}
|
|
|
|
$customer = [];
|
|
}
|
|
|
|
$item['attributes'] = Mapping::mapKeys(
|
|
json_decode(($item['attributes'] ?? null) ?: '', true) ?: [],
|
|
fn ($k, $v) => [$v['name'], $v['value'] ?? null]
|
|
);
|
|
|
|
$deleted = $this->isUserDeletedInDRS($item);
|
|
|
|
// pokud uzivatel neexistuje, tak ho zkusim zalozit
|
|
if (!($userId = $this->getUserIdByDRSId($item['id']))) {
|
|
// pokud je smazanej, tak ho ani nezakladam
|
|
if ($deleted) {
|
|
return;
|
|
}
|
|
|
|
// nemam data podle kterych bych mo mel zalozit
|
|
if (empty($customer)) {
|
|
return;
|
|
}
|
|
|
|
// Pokud na eshopu uzivatel neexistuje, tak ho vytvorim
|
|
if (!($userId = $this->createDRSUser($customer))) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// pokud uzivatele v DRSu smazali, tak ho "odstanim" i u nas
|
|
if ($deleted) {
|
|
$this->updateDeletedUser($userId, $item);
|
|
$this->updateUserRelatedData($userId, $item, $customer, $config);
|
|
|
|
return;
|
|
}
|
|
|
|
$this->debugLogUserChange($userId, 'updateUser', [
|
|
'databaseUser' => $item,
|
|
'apiUser' => $customer,
|
|
]);
|
|
|
|
$update = ['get_news' => ($customer['@attributes']['wantsEmails'] ?? false) === 'True' ? 'Y' : 'N'];
|
|
if (!empty($item['email']) && filter_var($item['email'], FILTER_VALIDATE_EMAIL)) {
|
|
$update['email'] = $item['email'];
|
|
}
|
|
|
|
// aktualizace mailu a newsletteru
|
|
$this->updateUserEmail($userId, (int) $item['id'], $update, (string) ($item['Country'] ?? ''));
|
|
// aktualizace skupin uzivatele
|
|
$this->updateUserGroups($userId, $item);
|
|
|
|
// aktualizace fakturacnich udaju
|
|
$this->updateUserDRSAddress($userId, [
|
|
'firstName' => $item['FirstName'] ?? '',
|
|
'lastName' => $item['LastName'] ?? '',
|
|
'street' => $item['Street'] ?? '',
|
|
'city' => $item['City'] ?? '',
|
|
'zipCode' => $item['Zip'] ?? '',
|
|
'phone' => html_entity_decode($item['Phone'] ?? ''),
|
|
'country' => $item['Country'] ?? '',
|
|
]);
|
|
|
|
// aktualizace dodaci adresy
|
|
// vypinam synchronizaci dodaci adresy, protoze se nam ji stejnak nedari zapisovat tak, jak bychom potrebovali
|
|
// a akorat pak v uctu na e-shopu mizi zakaznicke adresy, viz. https://klient.wpj.cz/service/task/8390/change/
|
|
// kde jsem to resil...
|
|
// if (empty($config) || ($config['addresses'] ?? false) === true) {
|
|
// foreach ($this->getItemsAsIndexedArray($customer['address'] ?? []) as $address) {
|
|
// $address = $address['@attributes'] ?? [];
|
|
// // adresa neni aktivni
|
|
// if ($address['status'] != 1 || $address['addressType'] != 1) {
|
|
// continue;
|
|
// }
|
|
//
|
|
// $this->updateUserDRSAddress($userId, $address, 'delivery');
|
|
// // pouze delivery adresa - vic by jich tam ani byt nemelo
|
|
// break;
|
|
// }
|
|
// }
|
|
|
|
// aktualizace zakaznicnich karet a deti
|
|
$this->updateUserRelatedData($userId, $item, $customer, $config);
|
|
}
|
|
|
|
/**
|
|
* Provedu "smazani" uzivatele v e-shopu.
|
|
*/
|
|
public function updateDeletedUser(int $userId, array $customer): void
|
|
{
|
|
sqlQueryBuilder()
|
|
->update('users')
|
|
->directValues(
|
|
[
|
|
'email' => $customer['id'].'@pompo.cz',
|
|
'figure' => 'N',
|
|
'note' => self::USER_DELETED_NOTE,
|
|
]
|
|
)
|
|
->where(Operator::equals(['id' => $userId]))
|
|
->execute();
|
|
}
|
|
|
|
private function updateUserGroups(int $userId, array $item): void
|
|
{
|
|
sqlGetConnection()->transactional(function () use ($userId, $item) {
|
|
sqlQueryBuilder()
|
|
->delete('users_groups_relations')
|
|
->andWhere(Operator::equals(['id_user' => $userId]))
|
|
->andWhere(Operator::inIntArray([self::USER_GROUP_EMPLOYEE], 'id_group'))
|
|
->execute();
|
|
|
|
if (($item['attributes']['IsEmployee'] ?? false) === 'True') {
|
|
sqlQueryBuilder()
|
|
->insert('users_groups_relations')
|
|
->directValues(['id_user' => $userId, 'id_group' => self::USER_GROUP_EMPLOYEE])
|
|
->execute();
|
|
}
|
|
});
|
|
}
|
|
|
|
private function updateUserRelatedData(int $userId, array $item, array $customer, array $config): void
|
|
{
|
|
// aktualizace zakaznicnich karet
|
|
if (empty($config) || ($config['cards'] ?? false) === true) {
|
|
$this->updateUserCards(
|
|
$userId,
|
|
$this->getItemsAsIndexedArray($customer['customercard'] ?? [])
|
|
);
|
|
}
|
|
|
|
// aktualizace deti - na prodejne si je uzivatel schopen napr. pridat dite
|
|
if (empty($config) || ($config['children'] ?? false) === true) {
|
|
$this->updateUserChildren(
|
|
$userId,
|
|
$this->prepareChildrenFromDatabaseUser($item)
|
|
);
|
|
}
|
|
}
|
|
|
|
private function updateUserCards(int $userId, array $cards): void
|
|
{
|
|
// aktualizace zakaznickych karet
|
|
$currentCards = array_map(fn ($x) => $x['code'], QueryHint::withRouteToMaster(fn () => $this->userCardUtil->getUserCards($userId)));
|
|
|
|
// karty, ktere budu na konci mazat
|
|
$cardsToDelete = $currentCards;
|
|
|
|
foreach ($cards as $card) {
|
|
$card = $card['@attributes'] ?? [];
|
|
|
|
// pokud by nemela cislo karty, tak skipuju
|
|
if (empty($card['number'])) {
|
|
continue;
|
|
}
|
|
|
|
// pokud karta uz existuje, tak nic nedelam a jen ji odeberu z karet ke smazani
|
|
if (($cardId = array_search($card['number'], $currentCards)) !== false) {
|
|
unset($cardsToDelete[$cardId]);
|
|
continue;
|
|
}
|
|
|
|
if (($card['status'] ?? false) !== '1') {
|
|
continue;
|
|
}
|
|
|
|
try {
|
|
$this->createUserCard($userId, $card['number']);
|
|
} catch (UniqueConstraintViolationException) {
|
|
}
|
|
}
|
|
|
|
if (!empty($cardsToDelete)) {
|
|
sqlQueryBuilder()
|
|
->delete('user_cards')
|
|
->andWhere(Operator::equals(['id_user' => $userId]))
|
|
->andWhere(Operator::inIntArray(array_keys($cardsToDelete), 'id'))
|
|
->execute();
|
|
}
|
|
}
|
|
|
|
private function updateUserChildren(int $userId, array $children): void
|
|
{
|
|
sqlGetConnection()->transactional(function () use ($children, $userId) {
|
|
$qb = sqlQueryBuilder()
|
|
->select('*')
|
|
->from('users_family')
|
|
->where(Operator::equals(['id_user' => $userId]));
|
|
|
|
$childrenByName = [];
|
|
// pred smazanim si deti ulozim, abych mohl prenest custom data
|
|
foreach ($qb->execute() as $item) {
|
|
$childrenByName[trim(mb_strtolower($item['name']))] = $item;
|
|
}
|
|
|
|
// smazu vsechny deti, abych je mohl znovu naimportovat - tim se provede update
|
|
sqlQueryBuilder()
|
|
->delete('users_family')
|
|
->where(Operator::equals(['id_user' => $userId]))
|
|
->execute();
|
|
|
|
// poukladam deti k nam do tabulky
|
|
foreach ($children as $child) {
|
|
sqlQueryBuilder()
|
|
->insert('users_family')
|
|
->directValues(
|
|
[
|
|
'id_user' => $userId,
|
|
'name' => $child['ChildName'],
|
|
'gender' => !empty($child['ChildSex']) ? $child['ChildSex'] == 1 ? 'M' : 'F' : null,
|
|
'date_birth' => $child['ChildBirthDay'] ?? null,
|
|
'data' => $childrenByName[trim(mb_strtolower($child['ChildName']))]['data'] ?? null,
|
|
]
|
|
)->execute();
|
|
}
|
|
});
|
|
}
|
|
|
|
private function createUserCard(int $userId, string $cardNumber, bool $deep = false): void
|
|
{
|
|
try {
|
|
sqlQueryBuilder()
|
|
->insert('user_cards')
|
|
->directValues(
|
|
[
|
|
'id_user' => $userId,
|
|
'code' => $cardNumber,
|
|
]
|
|
)->execute();
|
|
} catch (UniqueConstraintViolationException $e) {
|
|
if ($deep) {
|
|
throw $e;
|
|
}
|
|
|
|
sqlQueryBuilder()
|
|
->delete('user_cards')
|
|
->where(Operator::equals(['code' => $cardNumber]))
|
|
->execute();
|
|
|
|
$this->createUserCard($userId, $cardNumber, true);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Vraci data, ktere se posilaji pro aktualizaci uzivatele v DRSu.
|
|
*/
|
|
public function getUserData(\User $user, ?int $customerId): array
|
|
{
|
|
$user->fetchAddresses();
|
|
|
|
$userBonusPoints = $this->getUserBonusPoints((int) $user->id);
|
|
|
|
$extendedValue = array_filter([
|
|
// datum vytvoreni zakaznika
|
|
$this->getExtendedValueItem('CreatedDate', $user->date_reg ?? ''),
|
|
// odkud byl zakaznik vytvoren - pokud uz existuje, tak CreatedBranch nechci aktualizovat
|
|
$customerId ? null : $this->getExtendedValueItem('CreatedBranch', $this->getCreatedBranch($user)),
|
|
// ico
|
|
$this->getExtendedValueItem('ITIN', $user->invoice['ico'] ?? ''),
|
|
// dic
|
|
$this->getExtendedValueItem('VATID', $user->invoice['dic'] ?? ''),
|
|
]);
|
|
|
|
return [
|
|
'contact' => [
|
|
'@attributes' => [
|
|
'contactNumber' => $customerId ?: '',
|
|
'loginId' => $user->email,
|
|
'email' => $user->email,
|
|
'branch' => $this->getCreatedBranch($user),
|
|
'firstName' => $user->invoice['name'] ?? '',
|
|
'lastName' => $user->invoice['surname'] ?? '',
|
|
'bonusPoints' => $userBonusPoints,
|
|
'wantsEmails' => ($user->get_news ?? null) === 'Y' ? 'true' : 'false',
|
|
'telephone' => $user->invoice['phone'] ?? '',
|
|
'street' => $this->substr($user->invoice['street'] ?? ''),
|
|
'city' => $this->substr($user->invoice['city'] ?? ''),
|
|
'zipCode' => $this->substr($user->invoice['zip'] ?? '', 10),
|
|
'state' => $user->invoice['country'] ?: 'CZ',
|
|
'country' => $user->invoice['country'] ?: 'CZ',
|
|
'sexId' => $this->getUserSexId($user, true),
|
|
'discount' => '',
|
|
],
|
|
'mainVariables' => [],
|
|
// adresy uzivatele
|
|
'address' => $this->getUserAddresses($user),
|
|
// karty uzivatele
|
|
'customercard' => $this->getUserCards($user),
|
|
'extendedvalue' => array_merge($extendedValue, $this->getUserChildren($user)),
|
|
],
|
|
];
|
|
}
|
|
|
|
protected function processItem(array $item): void
|
|
{
|
|
// TODO: Implement processItem() method.
|
|
}
|
|
|
|
/**
|
|
* Zalozeni / aktualizace uzivatele v DRSu.
|
|
*/
|
|
private function saveUser(array $data): ?int
|
|
{
|
|
try {
|
|
return $this->drsApi->saveUser($data);
|
|
} catch (\SoapFault $e) {
|
|
if (isDevelopment()) {
|
|
throw $e;
|
|
}
|
|
|
|
// ignoruju SoapFault, protoze ten jejich SOAP strasne casto failuje
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Vytvoreni uzivatele v e-shopu.
|
|
*/
|
|
private function createDRSUser(array $item): ?int
|
|
{
|
|
// safe-check
|
|
if (!($customerId = ($item['@attributes']['contactNumber'] ?? null))) {
|
|
return null;
|
|
}
|
|
|
|
$insert = [
|
|
'email' => $item['@attributes']['email'] ?? null,
|
|
'figure' => 'N',
|
|
'date_reg' => (new \DateTime())->format('Y-m-d H:i:s'),
|
|
'id_language' => null,
|
|
];
|
|
|
|
if (empty($insert['email']) || !filter_var($insert['email'], FILTER_VALIDATE_EMAIL)) {
|
|
$insert['email'] = $customerId.'@pompo.cz';
|
|
}
|
|
|
|
if ($recTimeStamp = ($item['@attributes']['recTimeStamp'] ?? null)) {
|
|
if ($recTimeStamp = \DateTime::createFromFormat('Y-m-d\TH:i:s', $recTimeStamp)) {
|
|
$insert['date_reg'] = $recTimeStamp->format('Y-m-d H:i:s');
|
|
}
|
|
}
|
|
|
|
// Uzivatele uz muzeme mit v e-shopu, protoze si mohl prihlasit newsletter
|
|
$newsletterUser = $this->getNewsletterUser($insert['email']);
|
|
|
|
return sqlGetConnection()->transactional(function () use ($newsletterUser, $customerId, $insert) {
|
|
// Pokud mam uzivatele, kterej se vytvoril pres newsletter
|
|
if ($newsletterUser) {
|
|
// Prenesu nastaveni newsletteru na uzivatele, ktereho budu vytvaret
|
|
$insert['get_news'] = $newsletterUser['get_news'];
|
|
$insert['date_subscribe'] = $newsletterUser['date_subscribe'];
|
|
$insert['date_unsubscribe'] = $newsletterUser['date_unsubscribe'];
|
|
|
|
// Smazu toho newsletter uzivatele, abych mohl zalozit uzivatele podle DRSu
|
|
sqlQueryBuilder()
|
|
->delete('users')
|
|
->andWhere('date_reg IS NULL')
|
|
->andWhere(Operator::equals(['id' => $newsletterUser['id']]))
|
|
->execute();
|
|
}
|
|
|
|
// zalozim uzivatele
|
|
sqlQueryBuilder()
|
|
->insert('users')
|
|
->directValues($insert)
|
|
->execute();
|
|
|
|
$userId = (int) sqlInsertId();
|
|
|
|
// vytvorim mapping
|
|
sqlQueryBuilder()
|
|
->insert('drs_users')
|
|
->directValues(
|
|
[
|
|
'id_drs' => $customerId,
|
|
'id_user' => $userId,
|
|
]
|
|
)->execute();
|
|
|
|
return $userId;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Zaktivuje neaktivni uzivatele, pokud to jsou registrovani uzivatele a maji vyplneny mail.
|
|
*
|
|
* Pokud se uzivatel totiz zaklada z DRSu, tak ma vetsinou automaticky generovany mail {drs_id}@pompo.cz
|
|
* takze kdyz se pak pozdeji zaktualizuje, tak zustava furt neaktivni. Takovyhle uzivatele prave tady zviditelnime.
|
|
*/
|
|
private function updateUsersVisibility(): void
|
|
{
|
|
sqlQueryBuilder()
|
|
->update('users', 'u')
|
|
->join('u', 'drs_users', 'du', 'du.id_user = u.id')
|
|
->directValues(['figure' => 'Y'])
|
|
->andWhere('u.date_reg IS NOT NULL')
|
|
->andWhere(Operator::equals(['u.figure' => 'N']))
|
|
->andWhere('u.email NOT LIKE CONCAT("%", du.id_drs, "%")')
|
|
->execute();
|
|
}
|
|
|
|
/**
|
|
* Pokud se uzivateli v DRSu zmeni e-mail, tak ho prozenu touhle metodou.
|
|
*/
|
|
private function updateUserEmail(int $userId, int $drsId, array $update, string $country, bool $deep = false): void
|
|
{
|
|
$this->debugLogUserChange($userId, 'updateUserEmail::start', [
|
|
'drsId' => $drsId,
|
|
'updateData' => $update,
|
|
'deep' => $deep,
|
|
]);
|
|
|
|
// Nacte si aktualni email u uzivatele
|
|
$currentEmail = sqlQueryBuilder()
|
|
->select('email')
|
|
->from('users')
|
|
->where(Operator::equals(['id' => $userId]))
|
|
->execute()->fetchOne();
|
|
|
|
// aktualizace mailu a newsletteru
|
|
$updated = false;
|
|
try {
|
|
sqlQueryBuilder()
|
|
->update('users')
|
|
->directValues($update)
|
|
->where(Operator::equals(['id' => $userId]))
|
|
->execute();
|
|
|
|
$this->debugLogUserChange($userId, 'updateUserEmail::updated', [
|
|
'updateData' => $update,
|
|
'updatedUserEmail' => true,
|
|
]);
|
|
|
|
$updated = true;
|
|
} catch (UniqueConstraintViolationException $e) {
|
|
if (!$deep) {
|
|
// Pokud se uzivateli snazim zmenit email podle DRSu, ale spadne to, protoze v shopu existuje
|
|
// newsletter uzivatel, tak potrebuju newsletter uzivatele prenest k aktualnimu uzivateli a
|
|
// newsletter uzivatele smazat
|
|
if ($newsletterUser = $this->getNewsletterUser($update['email'])) {
|
|
sqlGetConnection()->transactional(function () use ($userId, $drsId, $country, $update, $newsletterUser) {
|
|
// Prenesu nastaveni newsletteru na uzivatele, ktereho aktualizuji
|
|
$update['get_news'] = $newsletterUser['get_news'];
|
|
$update['date_subscribe'] = $newsletterUser['date_subscribe'];
|
|
$update['date_unsubscribe'] = $newsletterUser['date_unsubscribe'];
|
|
|
|
// Smazu toho newsletter uzivatele, abych mohl zalozit uzivatele podle DRSu
|
|
sqlQueryBuilder()
|
|
->delete('users')
|
|
->andWhere('date_reg IS NULL')
|
|
->andWhere(Operator::equals(['id' => $newsletterUser['id']]))
|
|
->execute();
|
|
|
|
$this->updateUserEmail($userId, $drsId, $update, $country, true);
|
|
});
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
addActivityLog(
|
|
ActivityLog::SEVERITY_ERROR,
|
|
ActivityLog::TYPE_SYNC,
|
|
sprintf('[DRS] Nepodařilo se zaktualizovat e-mail pro uživatele: %s', $userId),
|
|
[
|
|
'userId' => $userId,
|
|
'email' => $update['email'] ?? null,
|
|
'update' => $update,
|
|
]
|
|
);
|
|
}
|
|
|
|
// Pokud se provedla uspesne aktualizace, zmenil se email a uzivatel mel predtim docasny email, tak odesleme mail
|
|
// aby si uzivatel mohl dokoncit registraci
|
|
if ($updated && !empty($update['email']) && preg_match('/^(\d+)@pompo\.cz$/',
|
|
$currentEmail) && $currentEmail !== $update['email'] && filter_var($update['email'], FILTER_VALIDATE_EMAIL)) {
|
|
$this->pompoUserUtil->sendPreregistrationEmail(
|
|
userId: $userId,
|
|
drsId: $drsId,
|
|
email: $update['email'],
|
|
country: !empty($country) ? $country : null,
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Uzivatele uz muzeme mit v e-shopu, protoze si mohl prihlasit newsletter.
|
|
*/
|
|
private function getNewsletterUser(string $email): ?array
|
|
{
|
|
$shopUser = sqlQueryBuilder()
|
|
->select('*')
|
|
->from('users')
|
|
// pokud ma jen newsletter, tak nema datum registrace
|
|
->andWhere('date_reg IS NULL')
|
|
// selectuju podle mailu
|
|
->andWhere(Operator::equals(['email' => $email]))
|
|
->execute()->fetchAssociative();
|
|
|
|
if (!$shopUser) {
|
|
return null;
|
|
}
|
|
|
|
return $shopUser;
|
|
}
|
|
|
|
private function createUserMapping(int $userId, int $drsId, bool $deep = false): void
|
|
{
|
|
try {
|
|
sqlQueryBuilder()
|
|
->insert('drs_users')
|
|
->directValues(
|
|
[
|
|
'id_drs' => $drsId,
|
|
'id_user' => $userId,
|
|
]
|
|
)->execute();
|
|
} catch (UniqueConstraintViolationException $e) {
|
|
if ($deep) {
|
|
throw $e;
|
|
}
|
|
|
|
// mapping uz existuje
|
|
// pokud maping existuje pro neaktivniho uzivatele, tak mapping smazu a vytvorim znovu na novem uzivateli
|
|
$inactiveUser = sqlQueryBuilder()
|
|
->select('u.id')
|
|
->from('users', 'u')
|
|
->join('u', 'drs_users', 'du', 'du.id_user = u.id')
|
|
->where(
|
|
Operator::equals(
|
|
[
|
|
'du.id_drs' => $drsId,
|
|
'u.figure' => 'N',
|
|
]
|
|
)
|
|
)->execute()->fetchOne();
|
|
|
|
if ($inactiveUser) {
|
|
sqlQueryBuilder()
|
|
->delete('drs_users')
|
|
->where(Operator::equals(['id_drs' => $drsId]))
|
|
->execute();
|
|
|
|
$this->createUserMapping($userId, $drsId, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Zkontroluje, zda uzivatele v DRSu "smazali" */
|
|
private function isUserDeletedInDRS(array $item): bool
|
|
{
|
|
// do FirstName davaji prefix DEL_<ContactNumber>, kde ContactNumber je zakaznik, se kterym ho treba sloucili
|
|
if (StringUtil::startsWith($item['FirstName'], 'DEL_')) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/** Overi, zda se jedna o zamestnanecky ucet */
|
|
private function isUserEmployer(?int $customerId): bool
|
|
{
|
|
return $customerId && strlen((string) $customerId) == 5 && StringUtil::startsWith((string) $customerId, '6');
|
|
}
|
|
|
|
/**
|
|
* Aktualizuje adresu uzivatele na e-shopu podle adresy v DRSu.
|
|
*/
|
|
private function updateUserDRSAddress(int $userId, array $address, string $type = 'invoice'): void
|
|
{
|
|
$prefix = '';
|
|
if ($type === 'delivery') {
|
|
$prefix = $type.'_';
|
|
}
|
|
|
|
$update = [
|
|
$prefix.'name' => $address['firstName'] ?? '',
|
|
$prefix.'surname' => $address['lastName'] ?? '',
|
|
$prefix.'firm' => $address['company'] ?? '',
|
|
$prefix.'street' => $address['street'] ?? '',
|
|
$prefix.'city' => $address['city'] ?? '',
|
|
$prefix.'zip' => $address['zipCode'] ?? '',
|
|
$prefix.'country' => $address['country'] ?? 'CZ',
|
|
];
|
|
|
|
if (!empty($address['phone'])) {
|
|
$update[$prefix.'phone'] = $address['phone'];
|
|
}
|
|
|
|
sqlQueryBuilder()
|
|
->update('users')
|
|
->directValues($update)
|
|
->where(Operator::equals(['id' => $userId]))
|
|
->execute();
|
|
}
|
|
|
|
private function getExtendedValueItem(string $name, string $value): array
|
|
{
|
|
return [
|
|
'@attributes' => [
|
|
'name' => $name,
|
|
'value' => $value,
|
|
],
|
|
];
|
|
}
|
|
|
|
private function getUserAddresses(\User $user): array
|
|
{
|
|
$result = [];
|
|
// delivery adressu nezapisujeme, protoze to delalo akorat problemy, takze zapisujeme a pracujeme pouze s
|
|
// fakturacni adresou, viz. https://klient.wpj.cz/service/task/8390/change/, kde se odmazavali adresy
|
|
foreach (['invoice'] as $type) {
|
|
$result[] = $this->getUserAddress($user, $type);
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
private function getUserAddress(\User $user, string $type = 'invoice'): array
|
|
{
|
|
return [
|
|
'@attributes' => [
|
|
'addressType' => '1',
|
|
'email' => $user->email,
|
|
'firstName' => $user->{$type}['name'] ?? '',
|
|
'lastName' => $user->{$type}['surname'] ?? '',
|
|
'company' => $this->substr($user->{$type}['firm'] ?? '', 33),
|
|
'street' => $this->substr($user->{$type}['street'] ?? ''),
|
|
'city' => $this->substr($user->{$type}['city'] ?? ''),
|
|
'zipCode' => $this->substr($user->{$type}['zip'] ?? '', 10),
|
|
'state' => $user->{$type}['country'] ?: 'CZ',
|
|
'country' => $user->{$type}['country'] ?: 'CZ',
|
|
'houseNumber' => '',
|
|
'orientNumber' => '',
|
|
'status' => '1',
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Vraci ID shopu, kde byl uzivatel zalozeny.
|
|
*/
|
|
private function getCreatedBranch(\User $user): string
|
|
{
|
|
if ($this->configuration->isNejhracka()) {
|
|
if ($user->id_language === 'sk') {
|
|
return '404';
|
|
}
|
|
|
|
return '403';
|
|
}
|
|
|
|
if ($user->id_language === 'sk') {
|
|
return '402';
|
|
}
|
|
|
|
return '401';
|
|
}
|
|
|
|
private function getUserCards(\User $user): array
|
|
{
|
|
$result = [];
|
|
|
|
$userCards = QueryHint::withRouteToMaster(fn () => $this->userCardUtil->getUserCards($user->id));
|
|
foreach ($userCards as $item) {
|
|
$result[]['@attributes'] = [
|
|
'number' => $item['code'],
|
|
'status' => $item['active'] == 1 ? '1' : '0',
|
|
];
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
private function getUserChildren(\User $user): array
|
|
{
|
|
$indexMap = $this->getChildrenIndexMap();
|
|
|
|
$qb = sqlQueryBuilder()
|
|
->select('*')
|
|
->from('users_family')
|
|
->where(Operator::equals(['id_user' => $user->id]))
|
|
->orderBy('id', 'ASC')
|
|
->sendToMaster();
|
|
|
|
$result = [];
|
|
foreach ($qb->execute() as $index => $item) {
|
|
$prefix = $indexMap[$index] ?? '';
|
|
|
|
$result[]['@attributes'] = [
|
|
'name' => $prefix.'ChildBirthDay',
|
|
'value' => $item['date_birth'],
|
|
];
|
|
$result[]['@attributes'] = [
|
|
'name' => $prefix.'ChildName',
|
|
'value' => $item['name'],
|
|
];
|
|
$result[]['@attributes'] = [
|
|
'name' => $prefix.'ChildSex',
|
|
'value' => $item['gender'] == 'M' ? '1' : '0',
|
|
];
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
private function getUserBonusPoints(int $userId): int
|
|
{
|
|
return (int) sqlQueryBuilder()
|
|
->select('SUM(points)')
|
|
->from('bonus_points')
|
|
->where(\Query\Operator::equals(['id_user' => $userId, 'status' => 'active']))
|
|
->execute()->fetchOne() ?? 0;
|
|
}
|
|
|
|
private function getUserSexId(\User $user, bool $negativeSex = false): string
|
|
{
|
|
// Co se týče pohlaví, tak v TPOMM je nějaký šotek, kdy v nějakých verzích je to 1 - muž a 2 - žena a někde je to obráceně.
|
|
// bez komentare :)
|
|
if ($user->gender === 'M') {
|
|
return $negativeSex ? '2' : '1';
|
|
}
|
|
|
|
if ($user->gender === 'F') {
|
|
return $negativeSex ? '1' : '2';
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
private function getUserIdByDRSId(string $drsId): ?int
|
|
{
|
|
$id = sqlQueryBuilder()
|
|
->select('id_user')
|
|
->from('drs_users')
|
|
->where(Operator::equals(['id_drs' => $drsId]))
|
|
->execute()->fetchOne();
|
|
|
|
if (!$id) {
|
|
return null;
|
|
}
|
|
|
|
return (int) $id;
|
|
}
|
|
|
|
private function prepareChildrenFromDatabaseUser(array $user): array
|
|
{
|
|
$data = array_filter($user['attributes'], fn ($x) => str_contains($x, 'Child'), ARRAY_FILTER_USE_KEY);
|
|
|
|
$children = [];
|
|
foreach ($this->getChildrenIndexMap() as $index => $value) {
|
|
$children[$index] = [
|
|
'ChildName' => $data["{$value}ChildName"] ?? '',
|
|
'ChildSex' => $data["{$value}ChildSex"] ?? '',
|
|
'ChildBirthDay' => $data["{$value}ChildBirthDay"] ?? '',
|
|
];
|
|
}
|
|
|
|
return array_filter($children, fn ($x) => !empty($x['ChildName']));
|
|
}
|
|
|
|
private function prepareChildrenFromExtendedValue(array $extendedValue): array
|
|
{
|
|
$extendedValue = array_filter($extendedValue, function ($x) {
|
|
return strpos($x['@attributes']['name'] ?? '', 'Child') !== false;
|
|
});
|
|
|
|
$children = [];
|
|
foreach ($extendedValue as $item) {
|
|
$item = $item['@attributes'] ?? [];
|
|
$childIndex = null;
|
|
$prefix = null;
|
|
foreach ($this->getChildrenIndexMap() as $index => $value) {
|
|
if (StringUtil::startsWith($item['name'] ?? '', $value)) {
|
|
$childIndex = $index;
|
|
$prefix = $value;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ($childIndex === null) {
|
|
continue;
|
|
}
|
|
|
|
$childKey = str_replace($prefix, '', $item['name']);
|
|
|
|
$children[$childIndex][$childKey] = $item['value'];
|
|
}
|
|
|
|
return array_filter($children, function ($x) { return !empty($x['ChildName']); });
|
|
}
|
|
|
|
private function getChildrenIndexMap(): array
|
|
{
|
|
return [
|
|
0 => 'First',
|
|
1 => 'Second',
|
|
2 => 'Third',
|
|
3 => 'Fourth',
|
|
];
|
|
}
|
|
|
|
private function getItemsAsIndexedArray(array $items): array
|
|
{
|
|
if (ArrayUtil::isDictionary($items)) {
|
|
$items = [$items];
|
|
}
|
|
|
|
return $items;
|
|
}
|
|
|
|
private function substr(string $string, int $maxLength = 50): string
|
|
{
|
|
return substr($string, 0, $maxLength);
|
|
}
|
|
|
|
private function getUserTimestamp(): ?array
|
|
{
|
|
if ($this->mode === self::MODE_FULL) {
|
|
return null;
|
|
}
|
|
|
|
$dbcfg = \Settings::getDefault();
|
|
|
|
$drs = $dbcfg->loadValue('drs');
|
|
|
|
return $drs[static::getType()]['timestamp'] ?? null;
|
|
}
|
|
|
|
// docasne logovani zmen zakazniku
|
|
private function debugLogUserChange(int $userId, string $method, array $data): void
|
|
{
|
|
$this->logger->log(
|
|
'[pompo] User change - ID: '.$userId.'; METHOD: '.$method,
|
|
array_merge($data, ['method' => $method])
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Updatuje `user` timestampy v DB settingach.
|
|
*/
|
|
private function updateTimestamps(bool $sql = true, bool $local = true): void
|
|
{
|
|
if (!$this->timestampLocal && !$this->timestampSql) {
|
|
return;
|
|
}
|
|
|
|
$dbcfg = \Settings::getDefault();
|
|
|
|
$drs = $dbcfg->loadValue('drs') ?? [];
|
|
|
|
$timestampSQLValue = ($sql && $this->timestampSql) ? $this->timestampSql : ($drs[static::getType()]['timestamp']['sql'] ?? null);
|
|
$timestampLocalValue = ($local && $this->timestampLocal) ? $this->timestampLocal : ($drs[static::getType()]['timestamp']['local'] ?? null);
|
|
|
|
$drs[static::getType()]['timestamp'] = [
|
|
'sql' => $timestampSQLValue,
|
|
'local' => $timestampLocalValue,
|
|
];
|
|
$drs[static::getType()]['updated'] = time();
|
|
|
|
$dbcfg->saveValue('drs', $drs, false);
|
|
}
|
|
}
|