Files
kupshop/bundles/External/HannahBundle/SAP/Synchronizer/UserSynchronizer.php
2025-08-02 16:30:27 +02:00

301 lines
10 KiB
PHP

<?php
declare(strict_types=1);
namespace External\HannahBundle\SAP\Synchronizer;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use External\HannahBundle\SAP\Exception\SAPException;
use External\HannahBundle\SAP\Exception\SAPManyException;
use External\HannahBundle\SAP\MappingType;
use External\HannahBundle\SAP\Util\DataFactory\UserDataFactory;
use External\HannahBundle\SAP\Util\SAPClient;
use KupShop\AdminBundle\Util\ActivityLog;
use Query\Operator;
class UserSynchronizer extends BaseSynchronizer
{
private const RETRY_COUNT_MAX = 4;
protected static $type = 'user';
/** @var SAPClient */
private $sapClient;
/** @var UserDataFactory */
private $userDataFactory;
/**
* @required
*/
public function setUserDataFactory(UserDataFactory $userDataFactory): void
{
$this->userDataFactory = $userDataFactory;
}
/**
* @required
*/
public function setSAPClient(SAPClient $sapClient): void
{
$this->sapClient = $sapClient;
}
public function processToSAP(): void
{
if (isLocalDevelopment()) {
return;
}
$lastSyncTime = $this->getLastSyncTime();
$orX = ['su.id_sap IS NULL'];
if ($lastSyncTime) {
$orX[] = 'u.date_updated IS NOT NULL AND u.date_updated >= :lastSync';
} else {
$orX[] = 'u.date_updated IS NOT NULL';
}
$qb = sqlQueryBuilder()
->select('u.id, su.id_sap')
->from('users', 'u')
->leftJoin('u', 'sap_users', 'su', 'u.id = su.id_user')
->andWhere(Operator::equals(['u.figure' => 'Y']))
// pokud neni vyplnene jmeno a prijmeni, tak SAP hazi chybu a nechce ho zapsat
->andWhere('u.name != "" AND u.surname != ""')
->andWhere(Operator::orX($orX));
if ($lastSyncTime) {
$qb->addParameters(
[
'lastSync' => date('Y-m-d H:i:s', $lastSyncTime),
]
);
}
$newlyAddedUsers = [];
foreach ($qb->execute() as $item) {
if ($result = $this->updateUserToSAP($item)) {
[$user, $isNewlyAddedUser] = $result;
if ($isNewlyAddedUser) {
$newlyAddedUsers[] = $user;
}
}
}
$this->updateLastSyncTime();
if (!empty($newlyAddedUsers)) {
sleep(3);
// update data of newly added users to SAP (bcs of price level definition)
foreach ($newlyAddedUsers as $user) {
$this->updateNewlyAddedUserFromSAP($user);
}
}
}
/**
* Zalozi nebo aktualizuje uzivatele v SAPu.
*/
public function updateUserToSAP(array $item, bool $force = false): ?array
{
$user = \User::createFromId($item['id']);
if ($force === false && $this->getUserTryCount($user) > self::RETRY_COUNT_MAX) {
return null;
}
$isNewlyAddedUser = false;
try {
$passwordInSap = $user->getCustomData()['passwordInSap'] ?? null;
if ($item['id_sap']) {
$this->logUserUpdate($user, 'updateUserToSAP::customerUpdate');
// obecny update uzivatele
$this->sapClient->customerUpdate(
$item['id_sap'],
$this->userDataFactory->getData(['user' => $user, 'type' => UserDataFactory::DATA_UPDATE]),
$this->configuration->getProcessByUser($user)
);
// aktualizovat heslo uzivatele, protoze si ho na shopu zmenil
if (!empty($user->passw) && $user->passw !== $passwordInSap) {
$this->logUserUpdate($user, 'updateUserToSAP::customerChangePassword');
$this->sapClient->customerChangePassword($item['id_sap'], $user->passw);
$user->setCustomData('passwordInSap', $user->passw);
}
// resetovat counter pro sapUpdateTry
$user->setCustomData('sapUpdateTry', null);
} else {
// zalozeni uzivatele
try {
$this->logUserUpdate($user, 'updateUserToSAP::customerCreate');
$result = $this->sapClient->customerCreate(
$this->userDataFactory->getData(['user' => $user]),
$this->configuration->getProcessByUser($user)
);
// resetovat counter pro sapUpdateTry
$user->setCustomData('sapUpdateTry', null);
} catch (SAPManyException $e) {
if ($e->hasExceptionWithCode(['477'])) {
$sapUserData = $user->getCustomData()['sap'] ?? [];
$sapUserData['cards'] = [];
$user->setCustomData('sap', $sapUserData);
}
// zalogovat chybu do Activity logu
if (!$e->hasExceptionWithCode(['020', '095'])) {
$this->handleActivityLogError(
$user,
sprintf('[SAP] Chyba během zakládání uživatele "%s" v SAPu: %s', $user->email, $e->getMessage())
);
return null;
}
// pokusit se nacist uz existujiciho uzivatele
$this->logUserUpdate($user, 'updateUserToSAP::customerValidate');
if ($result = $this->sapClient->customerValidate($user->email, $user->passw, $this->configuration->getProcessByUser($user))) {
$result = $result->CustomerData;
}
}
if (!empty($result->CustomerId)) {
$this->createUserMapping($user, $result);
$isNewlyAddedUser = true;
}
$user->setCustomData('passwordInSap', $user->passw);
}
} catch (SAPException $e) {
if (isLocalDevelopment()) {
throw $e;
}
if ($e instanceof SAPManyException) {
// autofix duplicated sap_users
if ($item['id_sap'] && $e->hasExceptionWithCode(['020'])) {
$sapUsersCount = sqlQueryBuilder()
->select('id_sap')
->from('sap_users')
->where(Operator::equals(['id_user' => $user->id]))
->execute()->rowCount();
if ($sapUsersCount > 1) {
sqlQueryBuilder()
->delete('sap_users')
->where(Operator::equals(['id_user' => $user->id, 'id_sap' => $item['id_sap']]))
->execute();
}
}
}
$this->handleActivityLogError(
$user,
sprintf('[SAP] Nepodařilo se synchronizovat uživatele "%s": %s', $user->email, $e->getMessage())
);
} catch (\Throwable $e) {
$this->sentryLogger->captureException($e);
}
return [$user, $isNewlyAddedUser];
}
public function updateNewlyAddedUserFromSAP(\User $user): void
{
try {
$this->logUserUpdate($user, 'updateNewlyAddedUserFromSAP::customerValidate');
$result = $this->sapClient->customerValidate(
mb_strtolower($user->email),
$user->passw,
$this->configuration->getProcessByUser($user)
);
} catch (SAPManyException $e) {
$this->sentryLogger->captureException($e);
return;
} finally {
$this->logger->notice('updateNewlyAddedUserFromSAP result', ['UserId' => $user->id, 'UserData' => $result ?? null]);
}
$customer = $result->CustomerData;
$this->sapUtil->updateUser($user, $customer);
}
protected function getHandledFields(): array
{
return [];
}
private function createUserMapping(\User $user, object $sapCustomer): void
{
try {
$this->sapUtil->createMapping(MappingType::USERS, $sapCustomer->CustomerId, (int) $user->id);
} catch (UniqueConstraintViolationException $e) {
// pokud SAP ID pro daneho uzivatele uz ma nastavene nekdo jiny
$duplicatedUser = sqlQueryBuilder()
->select('u.id, u.email')
->from('users', 'u')
->join('u', 'sap_users', 'su', 'su.id_user = u.id')
->andWhere(Operator::equals(['su.id_sap' => $sapCustomer->CustomerId]))
->execute()->fetchAssociative();
if (!$duplicatedUser) {
throw $e;
}
if ($duplicatedUser['email'] !== $sapCustomer->PersonalData->Email) {
sqlGetConnection()->transactional(function () use ($duplicatedUser, $user, $sapCustomer) {
$this->sapUtil->deleteMapping(MappingType::USERS, $duplicatedUser['id']);
$this->createUserMapping($user, $sapCustomer);
});
}
}
}
private function handleActivityLogError(\User $user, string $message): void
{
$tryCount = $this->getUserTryCount($user);
// prestavam zapisivat activity log, dokud se nepodari zapis, aby to nespamovalo silene moc
if ($tryCount > self::RETRY_COUNT_MAX) {
return;
}
// zapisu do activity logu
$this->activityLog->addActivityLog(
ActivityLog::SEVERITY_ERROR,
ActivityLog::TYPE_SYNC,
$message
);
// updatuju counter na uzivateli
$user->setCustomData('sapUpdateTry', ++$tryCount);
}
private function getUserTryCount(\User $user): int
{
return (int) ($user->getCustomData(true)['sapUpdateTry'] ?? 1);
}
private function logUserUpdate(\User $user, string $source): void
{
if (isLocalDevelopment()) {
return;
}
$this->logger->notice(
message: 'SAP: User update to SAP; ID: '.$user->id,
context: [
'userId' => $user->id,
'email' => $user->email,
'source' => $source,
]
);
}
}