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_, 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); } }