<?php
declare(strict_types=1);
namespace App\Sage\Action\Customer;
use App\Entity\Addressing\Address;
use App\Entity\Addressing\AddressType;
use App\Entity\ClinicalManager\ClinicalManager;
use App\Entity\Customer\Customer;
use App\Entity\Customer\CustomerGroup;
use App\Entity\Customer\CustomerGroupInterface;
use App\Entity\Distributor\Distributor;
use App\Entity\Doctor\Doctor;
use App\Entity\Product\Product;
use App\Entity\Product\ReductionProduct;
use App\Entity\Taxonomy\ReductionTaxon;
use App\Entity\Taxonomy\Taxon;
use App\Entity\User\AdminRoleInterface;
use App\Entity\User\AdminUser;
use App\Entity\User\ShopUser;
use App\Entity\User\ShopUserRoleInterface;
use App\Factory\ApiLoggerFactoryInterface;
use App\Factory\ShopUserFactoryInterface;
use App\Repository\CustomerRepository;
use App\Repository\ProductRepository;
use App\Repository\TaxonRepository;
use App\Sage\Service\CustomerServiceInterface;
use App\Service\Wallet\WalletRevenueSyncServiceInterface;
use BitBag\SyliusAclPlugin\Entity\RoleInterface;
use BitBag\SyliusAclPlugin\Repository\RoleRepositoryInterface;
use DateTimeImmutable;
use Doctrine\Persistence\ObjectManager;
use Safe\DateTime;
use Safe\Exceptions\JsonException;
use Sylius\Bundle\CoreBundle\Doctrine\ORM\AddressRepository;
use Sylius\Component\Resource\Factory\FactoryInterface;
use Sylius\Component\Resource\Repository\RepositoryInterface;
use Sylius\Component\User\Canonicalizer\CanonicalizerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Throwable;
use function array_key_exists;
use function count;
use function is_numeric;
use function is_string;
use function Safe\json_encode;
use function trim;
final readonly class UpsertCustomerAction
{
public function __construct(
private CustomerServiceInterface $customerService,
private CustomerRepository $customerRepository,
private RepositoryInterface $customerGroupRepository,
private FactoryInterface $customerFactory,
private ObjectManager $customerManager,
private ProductRepository $productRepository,
private TaxonRepository $taxonRepository,
private FactoryInterface $reductionProductFactory,
private FactoryInterface $reductionTaxonFactory,
private AddressRepository $addressRepository,
private FactoryInterface $addressFactory,
private FactoryInterface $doctorFactory,
private FactoryInterface $distributorFactory,
private FactoryInterface $clinicalManagerFactory,
private CanonicalizerInterface $canonicalizer,
private ShopUserFactoryInterface $shopUserFactory,
private FactoryInterface $adminUserFactory,
private RoleRepositoryInterface $roleRepository,
private ApiLoggerFactoryInterface $apiLoggerFactory,
private ObjectManager $apiLoggerManager,
private WalletRevenueSyncServiceInterface $walletRevenueSyncService,
) {
}
/** @throws JsonException */
public function __invoke(Request $request): Response
{
$apiLogger = $this->apiLoggerFactory->createNewFromRequest('UpsertCustomerAction', $request);
$params = $request->request->all();
$customersParams = ! isset($params['customers']) ? [$params] : $params['customers'];
$errors = $this->customerService->validate($customersParams);
if (count($errors) > 0) {
$apiLogger->setResponse(json_encode($errors));
$apiLogger->setStatusCode(Response::HTTP_BAD_REQUEST);
$this->apiLoggerManager->persist($apiLogger);
$this->apiLoggerManager->flush();
return new JsonResponse([
'status' => 'error',
'errors' => $errors,
], Response::HTTP_BAD_REQUEST);
}
$response = [];
foreach ($customersParams as $value) {
$id = $this->upsertCustomer($value);
$response[] = ['id' => $id, 'idSage' => $value['idSage']];
}
$apiLogger->setResponse(json_encode($response));
$apiLogger->setStatusCode(Response::HTTP_OK);
$this->apiLoggerManager->persist($apiLogger);
$this->apiLoggerManager->flush();
return new JsonResponse([
'status' => 'success',
'data' => $response,
], Response::HTTP_OK);
}
private function upsertCustomer(array $params): int|null
{
if (! isset($params['group'])) {
$params['group'] = CustomerGroupInterface::DOCTOR; // Default group is doctor
}
/** @var CustomerGroup $customerGroup */
$customerGroup = $this->customerGroupRepository->findOneBy(['code' => $params['group']]);
if (! isset($params['id'])) {
/** @var Customer $customer */
$customer = $this->customerFactory->createNew();
$customer->setIdSage($params['idSage']);
} else {
/** @var Customer $customer */
$customer = $this->customerRepository->find($params['id']);
}
$email = trim($params['email']);
$emailCanonical = $this->canonicalizer->canonicalize($email);
$customer->setGroup($customerGroup);
$customer->setEmail($email);
$customer->setEmailCanonical($emailCanonical);
$customer->setLastName($params['name']);
$customer->setPhoneNumber($params['phoneNumber']);
$customer->setSocialReason($params['socialReason']);
$customer->setInterCommunityTvaNumber($params['interCommunityTvaNumber']);
$customer->setCity($params['city']);
$customer->setCountry($params['country']);
/** @var ?ShopUser $shopUser */
$shopUser = $customer->getUser();
if ($shopUser === null) {
/** @var ShopUser $shopUser */
$shopUser = $this->shopUserFactory->createNew();
$shopUser->setCustomer($customer);
$customer->setUser($shopUser);
$shopUser->addRole(ShopUserRoleInterface::USER);
$shopUser->setEnabled(false);
$shopUser->setLocked(true);
$shopUser->setDisabledAt(new DateTime());
}
$shopUser->setUsername($email);
$shopUser->setUsernameCanonical($emailCanonical);
foreach (CustomerGroupInterface::ALL as $role) {
$shopUser->removeRole($role);
}
switch ($customerGroup->getCode()) {
case CustomerGroupInterface::DOCTOR:
$shopUser->addRole(ShopUserRoleInterface::DOCTOR);
$doctor = $customer->getDoctor();
if ($doctor === null) {
/** @var Doctor $doctor */
$doctor = $this->doctorFactory->createNew();
$customer->setDoctor($doctor);
}
$customer->setClinicalManager(null);
$customer->setDistributor(null);
$customer->setAdminUser(null);
break;
case CustomerGroupInterface::DISTRIBUTOR:
$shopUser->addRole(ShopUserRoleInterface::DISTRIBUTOR);
$distributor = $customer->getDistributor();
if ($distributor === null) {
/** @var Distributor $distributor */
$distributor = $this->distributorFactory->createNew();
$customer->setDistributor($distributor);
}
$customer->setClinicalManager(null);
$customer->setDoctor(null);
$customer->setAdminUser(null);
break;
case CustomerGroupInterface::CLINICAL_MANAGER:
$shopUser->addRole(ShopUserRoleInterface::CLINICAL_MANAGER);
$clinicalManager = $customer->getClinicalManager();
if ($clinicalManager === null) {
/** @var ClinicalManager $clinicalManager */
$clinicalManager = $this->clinicalManagerFactory->createNew();
$customer->setClinicalManager($clinicalManager);
}
$customer->setClinicalManager($clinicalManager);
$customer->setDistributor(null);
$customer->setDoctor(null);
$adminUser = $customer->getAdminUser();
if ($adminUser === null) {
/** @var AdminUser $adminUser */
$adminUser = $this->adminUserFactory->createNew();
$customer->setAdminUser($adminUser);
$adminUser->setEnabled(false);
$adminUser->setLocked(true);
}
$adminUser->setLastName($params['name']);
$adminUser->setEmail($email);
$adminUser->setEmailCanonical($emailCanonical);
$adminUser->setUsername($email);
$adminUser->setUsernameCanonical($emailCanonical);
$adminUser->setLocaleCode('fr');
$adminUser->setEnablePermissionChecker(true);
/** @var RoleInterface $roleResource */
$roleResource = $this->roleRepository->findOneBy(['code' => AdminRoleInterface::CLINICAL_MANAGER]);
$adminUser->addRoleResource($roleResource);
break;
}
if (isset($params['reductionProducts'])) {
$this->insertProductReductions($params, $customer);
}
if (isset($params['reductionTaxons'])) {
$this->insertTaxonReductions($params, $customer);
}
if (isset($params['addresses'])) {
$this->insertAddresses($params, $customer);
}
$this->customerManager->persist($customer);
$this->customerManager->flush();
$this->logRevenueAndGrantPointsFromSync($customer, $params);
return $customer->getId();
}
private function logRevenueAndGrantPointsFromSync(Customer $customer, array $params): void
{
if (! array_key_exists('ca', $params) || ! is_numeric($params['ca']) || $params['ca'] <= 0) {
return;
}
$revenue = (float) $params['ca'];
$currency = isset($params['devise']) && is_string($params['devise']) ? $params['devise'] : 'EUR';
$dateCalcul = null;
if (isset($params['date_calcul']) && $params['date_calcul'] !== null && $params['date_calcul'] !== '') {
try {
$dateCalcul = new DateTimeImmutable($params['date_calcul']);
} catch (Throwable) {
// ignore invalid date
}
}
$this->walletRevenueSyncService->logRevenueAndGrantPoints($customer, $revenue, $currency, $dateCalcul);
}
private function insertProductReductions(array $params, Customer $customer): void
{
$customer->clearReductionProducts();
foreach ($params['reductionProducts'] as $productReduction) {
/** @var Product $product */
$product = $this->productRepository->findOneBy(['code' => $productReduction['productCode']]);
/** @var ReductionProduct $reductionProduct */
$reductionProduct = $this->reductionProductFactory->createNew();
$reductionProduct->addProduct($product);
$reductionProduct->setReductionType($productReduction['type']);
$reductionProduct->setReductionValue((int) $productReduction['value']);
$reductionProduct->setCustomer($customer);
$customer->addReductionProduct($reductionProduct);
}
}
private function insertTaxonReductions(array $params, Customer $customer): void
{
$customer->clearReductionTaxons();
foreach ($params['reductionTaxons'] as $taxonReduction) {
/** @var Taxon $taxon */
$taxon = $this->taxonRepository->findOneBy(['code' => $taxonReduction['taxonCode']]);
/** @var ReductionTaxon $reductionTaxon */
$reductionTaxon = $this->reductionTaxonFactory->createNew();
$reductionTaxon->addTaxon($taxon);
$reductionTaxon->setReductionType($taxonReduction['type']);
$reductionTaxon->setReductionValue((int) $taxonReduction['value']);
$reductionTaxon->setCustomer($customer);
$customer->addReductionTaxon($reductionTaxon);
}
}
private function insertAddresses(array $params, Customer $customer): void
{
foreach ($params['addresses'] as $addressParams) {
$customer->addAddress(
$this->insertAddress($addressParams, $customer, AddressType::BILLING),
);
$customer->setDefaultAddress(
$this->insertAddress($addressParams, $customer, AddressType::SHIPPING),
);
}
}
private function insertAddress(array $params, Customer $customer, string $type): Address
{
/** @var ?Address $address */
$address = $this->addressRepository->findOneBy(['idSage' => $params['idSage'], 'type' => $type]);
if ($address === null) {
/** @var Address $address */
$address = $this->addressFactory->createNew();
$address->setIdSage($params['idSage']);
$address->setType($type);
}
$address->setFirstName($params['name']);
$address->setLastName($params['name']);
$address->setPhoneNumber($params['phoneNumber']);
$address->setStreet($params['street']);
$address->setCity($params['city']);
$address->setPostcode($params['postCode']);
$address->setCountryCode($params['countryCode']);
$address->setCustomer($customer);
return $address;
}
}