From 89653545287fd69fce913956e3377204ab9e85f6 Mon Sep 17 00:00:00 2001 From: Juan Pablo Vial Date: Thu, 15 May 2025 19:32:25 -0400 Subject: [PATCH] Cambio en queue para que no quede pegado esperando respuesta en cli. Chequeo de servicios externos para agregar elementos pendientes. --- app/resources/routes/api/external.php | 5 + app/resources/routes/api/queue.php | 6 +- app/setup/setups/services.php | 19 ++- app/src/Controller/API/External.php | 20 +++ app/src/Controller/API/Queues.php | 20 ++- app/src/Repository/Persona.php | 63 +++++++++ app/src/Repository/Persona/Datos.php | 28 ++++ .../Venta/MediosPago/Toku/Subscription.php | 26 ++++ app/src/Repository/Venta/Propietario.php | 15 ++ app/src/Service/External.php | 49 +++++++ app/src/Service/Job.php | 133 ++++++++++++++---- app/src/Service/Persona.php | 68 ++++++++- app/src/Service/Queue.php | 73 ++++++---- app/src/Service/Redis.php | 7 +- app/src/Service/Venta/MediosPago/Toku.php | 75 ++++++++++ .../Venta/MediosPago/Toku/Customer.php | 2 +- .../Venta/MediosPago/Toku/Subscription.php | 31 +++- app/src/Service/Worker/CheckExternal.php | 46 ++++++ cli/setup/settings/commands.php | 3 +- cli/src/Command/ExternalServices.php | 22 +++ cli/src/Command/Queue.php | 41 +++++- 21 files changed, 687 insertions(+), 65 deletions(-) create mode 100644 app/src/Controller/API/External.php create mode 100644 app/src/Service/External.php create mode 100644 app/src/Service/Worker/CheckExternal.php create mode 100644 cli/src/Command/ExternalServices.php diff --git a/app/resources/routes/api/external.php b/app/resources/routes/api/external.php index 37b597e..b9a35ca 100644 --- a/app/resources/routes/api/external.php +++ b/app/resources/routes/api/external.php @@ -1,4 +1,6 @@ group('/external', function($app) { $files = new FilesystemIterator(implode(DIRECTORY_SEPARATOR, [__DIR__, 'external'])); foreach ($files as $file) { @@ -7,4 +9,7 @@ $app->group('/external', function($app) { } include_once $file->getRealPath(); } + $app->group('/services', function($app) { + $app->get('/check[/]', [External::class, 'check']); + }); }); diff --git a/app/resources/routes/api/queue.php b/app/resources/routes/api/queue.php index 000c6e3..72f4f95 100644 --- a/app/resources/routes/api/queue.php +++ b/app/resources/routes/api/queue.php @@ -2,5 +2,9 @@ use Incoviba\Controller\API\Queues; $app->group('/queue', function($app) { - $app->get('/run[/]', Queues::class); + $app->get('/jobs[/]', [Queues::class, 'jobs']); + $app->group('/run', function($app) { + $app->get('/{job_id:[0-9]+}[/]', [Queues::class, 'run']); + $app->get('[/]', Queues::class); + }); }); diff --git a/app/setup/setups/services.php b/app/setup/setups/services.php index 2e1323a..4f01215 100644 --- a/app/setup/setups/services.php +++ b/app/setup/setups/services.php @@ -135,7 +135,8 @@ return [ Incoviba\Service\Venta\MediosPago\Toku\Subscription::class => function(ContainerInterface $container) { $service = new Incoviba\Service\Venta\MediosPago\Toku\Subscription( $container->get('TokuClient'), - $container->get(Incoviba\Repository\Venta\MediosPago\Toku\Subscription::class) + $container->get(Incoviba\Repository\Venta\MediosPago\Toku\Subscription::class), + $container->get(Incoviba\Service\Venta::class) ); $service->setLogger($container->get(Psr\Log\LoggerInterface::class)); return $service; @@ -165,7 +166,8 @@ return [ $container->get(Incoviba\Service\Worker\Request::class) )) ->register('request', $container->get(Incoviba\Service\Worker\Request::class)) - ->register('dummy', $container->get(Incoviba\Service\Worker\Dummy::class)); + ->register('dummy', $container->get(Incoviba\Service\Worker\Dummy::class)) + ->register('checkExternal', $container->get(Incoviba\Service\Worker\CheckExternal::class)); }, Incoviba\Service\Worker\Request::class => function(ContainerInterface $container) { $apiKey = md5($container->get('API_KEY')); @@ -184,5 +186,18 @@ return [ ] ]) ); + }, + Incoviba\Service\Worker\CheckExternal::class => function(ContainerInterface $container) { + return new Incoviba\Service\Worker\CheckExternal( + $container->get(Psr\Log\LoggerInterface::class), + $container + ); + }, + Incoviba\Service\External::class => function(ContainerInterface $container) { + return (new Incoviba\Service\External( + $container->get(Psr\Log\LoggerInterface::class), + $container->get(Incoviba\Service\Queue::class) + )) + ->register($container->get(Incoviba\Service\Venta\MediosPago\Toku::class)); } ]; diff --git a/app/src/Controller/API/External.php b/app/src/Controller/API/External.php new file mode 100644 index 0000000..7aa426a --- /dev/null +++ b/app/src/Controller/API/External.php @@ -0,0 +1,20 @@ +check()) { + return $response->withStatus(204); + } + return $response->withStatus(409); + } +} diff --git a/app/src/Controller/API/Queues.php b/app/src/Controller/API/Queues.php index 6ff9eba..8c2720c 100644 --- a/app/src/Controller/API/Queues.php +++ b/app/src/Controller/API/Queues.php @@ -4,13 +4,15 @@ namespace Incoviba\Controller\API; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ResponseInterface; use Incoviba\Common\Ideal; +use Incoviba\Exception\ServiceAction\Read; use Incoviba\Service; class Queues extends Ideal\Controller { use withJson; - public function __invoke(ServerRequestInterface $request, ResponseInterface $response, Service\Queue $queueService): ResponseInterface + public function __invoke(ServerRequestInterface $request, ResponseInterface $response, + Service\Queue $queueService): ResponseInterface { $output = [ 'success' => false @@ -20,4 +22,20 @@ class Queues extends Ideal\Controller } return $this->withJson($response, $output); } + public function jobs(ServerRequestInterface $request, ResponseInterface $response, + Service\Queue $queueService): ResponseInterface + { + $output = [ + 'jobs' => array_column($queueService->getPendingJobs(), 'id') + ]; + return $this->withJson($response, $output); + } + public function run(ServerRequestInterface $request, ResponseInterface $response, Service\Queue $queueService, + int $job_id): ResponseInterface + { + if ($queueService->runJob($job_id, $request)) { + return $response->withStatus(200); + } + return $response->withStatus(422); + } } diff --git a/app/src/Repository/Persona.php b/app/src/Repository/Persona.php index c49ec27..ee70138 100644 --- a/app/src/Repository/Persona.php +++ b/app/src/Repository/Persona.php @@ -1,6 +1,8 @@ connection->getQueryBuilder() + ->select() + ->from($this->getTable()) + ->where("rut IN ({$rutsQuery})"); + return $this->fetchMany($query, $ruts); + } + /** + * @return array + * @throws Implement\Exception\EmptyResult + */ + public function fetchMissing(): array + { + $query = $this->connection->getQueryBuilder() + ->select('a.rut') + ->from("propietario a") + ->joined("LEFT OUTER JOIN {$this->getTable()} b WHERE a.rut = b.rut") + ->where('b.rut IS NULL'); + try { + $statement = $this->connection->query($query); + } catch (PDOException $exception) { + throw new Implement\Exception\EmptyResult($query, $exception); + } + try { + $results = $statement->fetchAll(PDO::FETCH_COLUMN, 0); + } catch (PDOException $exception) { + throw new Implement\Exception\EmptyResult($query, $exception); + } + if (!$results or empty($results)) { + throw new Implement\Exception\EmptyResult($query); + } + return $results; + } + + /** + * @param array $data + * @return array + * @throws Implement\Exception\EmptyResult + * @throws PDOException + */ + public function saveMissing(array $data): array + { + $valueArray = array_fill(0, count($data), ['?', '?', '?', '?', '?']); + $query = $this->connection->getQueryBuilder() + ->insert() + ->into($this->getTable()) + ->columns(['rut', 'digito', 'nombres', 'apellido_paterno', 'apellido_materno']) + ->values($valueArray); + $flattened = array_merge(...$data); + $this->connection->execute($query, $flattened); + + return $this->fetchByRuts(array_column($data, 'rut')); + } + protected function getKey(): string { return 'rut'; diff --git a/app/src/Repository/Persona/Datos.php b/app/src/Repository/Persona/Datos.php index c227c06..bfec12b 100644 --- a/app/src/Repository/Persona/Datos.php +++ b/app/src/Repository/Persona/Datos.php @@ -1,6 +1,7 @@ where('persona_rut = ?'); return $this->fetchOne($query, [$persona_rut]); } + + /** + * @param array $data + * @return void + * @throws PDOException + */ + public function saveMissing(array $data): void + { + $dataQuery = array_fill(0, count($data), ['?', '?', '?', '?', '?', '?', '?', '?', '?']); + $query = $this->connection->getQueryBuilder() + ->insert() + ->into($this->getTable()) + ->columns([ + 'direccion_id', 'telefono', 'email', 'fecha_nacimiento', 'sexo', 'estado_civil', + 'nacionalidad', 'ocupacion' + ]) + ->values($dataQuery); + $flattened = []; + foreach ($data as $col => $value) { + $row = [ + $value['direccion_id'] ?? null, $value['telefono'] ?? null, $value['email'] ?? null, $value['fecha_nacimiento'] ?? null, + $value['sexo'] ?? null, $value['estado_civil'] ?? null, $value['nacionalidad'] ?? null, $value['ocupacion'] ?? null + ]; + $flattened = array_merge($flattened, $row); + } + $this->connection->execute($query, $flattened); + } } diff --git a/app/src/Repository/Venta/MediosPago/Toku/Subscription.php b/app/src/Repository/Venta/MediosPago/Toku/Subscription.php index 80202e9..b2d5d25 100644 --- a/app/src/Repository/Venta/MediosPago/Toku/Subscription.php +++ b/app/src/Repository/Venta/MediosPago/Toku/Subscription.php @@ -43,6 +43,11 @@ class Subscription extends Ideal\Repository return $this->update($model, ['venta_id', 'toku_id', 'updated_at'], array_merge($new_data, ['updated_at' => (new DateTimeImmutable())->format('Y-m-d H:i:s')])); } + /** + * @param int $venta_id + * @return Model\Venta\MediosPago\Toku\Subscription + * @throws Implement\Exception\EmptyResult + */ public function fetchByVenta(int $venta_id): Model\Venta\MediosPago\Toku\Subscription { $query = $this->connection->getQueryBuilder() @@ -51,6 +56,12 @@ class Subscription extends Ideal\Repository ->where('venta_id = :venta_id'); return $this->fetchOne($query, compact('venta_id')); } + + /** + * @param string $toku_id + * @return Model\Venta\MediosPago\Toku\Subscription + * @throws Implement\Exception\EmptyResult + */ public function fetchByTokuId(string $toku_id): Model\Venta\MediosPago\Toku\Subscription { $query = $this->connection->getQueryBuilder() @@ -59,4 +70,19 @@ class Subscription extends Ideal\Repository ->where('toku_id = :toku_id'); return $this->fetchOne($query, compact('toku_id')); } + + /** + * @param array $ventas_ids + * @return array + * @throws Implement\Exception\EmptyResult + */ + public function fetchByVentas(array $ventas_ids): array + { + $idsQuery = implode(', ', array_fill(0, count($ventas_ids), '?')); + $query = $this->connection->getQueryBuilder() + ->select() + ->from($this->getTable()) + ->where("venta_id IN ({$idsQuery})"); + return $this->fetchMany($query, $ventas_ids); + } } diff --git a/app/src/Repository/Venta/Propietario.php b/app/src/Repository/Venta/Propietario.php index 92a289e..ac20aaf 100644 --- a/app/src/Repository/Venta/Propietario.php +++ b/app/src/Repository/Venta/Propietario.php @@ -90,4 +90,19 @@ class Propietario extends Ideal\Repository { return array_intersect_key($data, array_flip(['rut', 'dv', 'nombres', 'apellido_paterno', 'apellido_materno', 'direccion', 'email', 'telefono', 'otro', 'representante'])); } + + /** + * @param array $ruts + * @return array + * @throws Implement\Exception\EmptyResult + */ + public function fetchByRuts(array $ruts): array + { + $rutsQuery = implode(', ', array_fill(0, count($ruts), '?')); + $query = $this->connection->getQueryBuilder() + ->select() + ->from($this->getTable()) + ->where("rut IN ({$rutsQuery})"); + return $this->fetchMany($query, $ruts); + } } diff --git a/app/src/Service/External.php b/app/src/Service/External.php new file mode 100644 index 0000000..cba9773 --- /dev/null +++ b/app/src/Service/External.php @@ -0,0 +1,49 @@ +externalServices)) { + $this->externalServices = [$service]; + return $this; + } + if (!in_array($service, $this->externalServices)) { + $this->externalServices []= $service; + } + return $this; + } + + public function check(): bool + { + $errors = []; + foreach ($this->externalServices as $externalService) { + if (!method_exists($externalService, 'check')) { + continue; + } + $queueData = [ + 'type' => 'checkExternal', + 'service' => get_class($externalService), + 'action' => 'check', + ]; + if (!$this->queueService->enqueue($queueData)) { + $errors []= get_class($externalService); + } + } + if (count($errors) > 0) { + $this->logger->error('Could not enqueue check of external services', ['services' => $errors]); + return false; + } + return true; + } +} diff --git a/app/src/Service/Job.php b/app/src/Service/Job.php index ef65354..7d74947 100644 --- a/app/src/Service/Job.php +++ b/app/src/Service/Job.php @@ -1,9 +1,13 @@ redisService->get($this->redisKey); + if ($jobs === null) { + return []; + } + $jobs = json_decode($jobs, true); + if ($orderBy !== null) { + uksort($jobs, function($a, $b) use ($orderBy) { + return $a[$orderBy] <=> $b[$orderBy]; + }); + } + return array_map([$this, 'load'], $jobs); + } catch (EmptyRedis) { + return []; + } + } + + /** + * @param int $id + * @return Model\Job + * @throws Read + */ + public function getPendingById(int $id): Model\Job + { + $jobs = $this->getJobs(); + try { + $idx = $this->findJob($jobs, $id); + } catch (EmptyResult $exception) { + $exception = new OutOfRangeException('Job not found', count($jobs), $exception); + throw new Read(__CLASS__, $exception); + } + return $this->load($jobs[$idx]); + } /** * @param array $configuration * @return Model\Job - * @throws Create + * @throws Read */ public function add(array $configuration): Model\Job { + $now = (new DateTimeImmutable()); + $data = [ + 'id' => $now->getTimestamp(), + 'configuration' => $configuration, + 'executed' => false, + 'created_at' => $now->format('Y-m-d H:i:s'), + 'updated_at' => null + ]; + $jobs = []; try { - $data = [ - 'configuration' => json_encode($configuration) - ]; - $job = $this->jobRepository->create($data); - return $this->process($this->jobRepository->save($job)); - } catch (PDOException $exception) { - throw new Create(__CLASS__, $exception); + $jobs = $this->redisService->get($this->redisKey); + if ($jobs !== null) { + $jobs = json_decode($jobs, true); + } + } catch (EmptyRedis) {} + $jobs []= $data; + $this->redisService->set($this->redisKey, json_encode($jobs), -1); + return $this->load($data); + } + + /** + * @param Model\Job $job + * @return bool + * @throws Read + */ + public function execute(Model\Job $job): bool + { + $jobs = $this->getJobs(); + try { + $idx = $this->findJob($jobs, $job->id); + } catch (EmptyResult $exception) { + $exception = new OutOfRangeException('Job not found', count($jobs), $exception); + throw new Read(__CLASS__, $exception); } + unset($jobs[$idx]); + $this->redisService->set($this->redisKey, json_encode(array_values($jobs)), -1); + return true; } /** * @return array * @throws Read */ - public function getPending(): array + protected function getJobs(): array { try { - return array_map([$this, 'process'],$this->jobRepository->fetchPending()); - } catch (EmptyResult $exception) { + $jobs = $this->redisService->get($this->redisKey); + } catch (EmptyRedis $exception) { throw new Read(__CLASS__, $exception); } - } - - /** - * @param Model\Job $job - * @return bool - * @throws Update - */ - public function execute(Model\Job $job): bool - { - try { - $this->jobRepository->edit($job, ['executed' => true]); - return true; - } catch (EmptyResult | PDOException $exception) { - throw new Update(__CLASS__, $exception); + if ($jobs === null) { + $exception = new InvalidArgumentException("Redis Key {$this->redisKey} not found"); + throw new Read(__CLASS__, $exception); } + return json_decode($jobs, true); } - - protected function process(Model\Job $job): Model\Job + /** + * @param array $jobs + * @param int $id + * @return int + * @throws EmptyResult + */ + protected function findJob(array $jobs, int $id): int { + $idx = array_find_key($jobs, fn($job) => $job['id'] === $id); + if ($idx === false) { + throw new EmptyResult("SELECT * FROM jobs WHERE id = ?"); + } + return $idx; + } + protected function load(array $data, ?int $id = null): Model\Job + { + $job = new Model\Job(); + $job->id = $id ?? $data['id'] ?? null; + $job->configuration = $data['configuration'] ?? []; + $job->executed = $data['executed'] ?? false; return $job; } } diff --git a/app/src/Service/Persona.php b/app/src/Service/Persona.php index 417e115..3e285ce 100644 --- a/app/src/Service/Persona.php +++ b/app/src/Service/Persona.php @@ -20,6 +20,18 @@ class Persona extends Ideal\Service parent::__construct($logger); } + public function getAll(null|string|array $orderBy = null): array + { + try { + try { + $ruts = $this->personaRepository->fetchMissing(); + $this->addMissingPropietarios($ruts); + } catch (Implement\Exception\EmptyResult | Read | Create) {} + return array_map([$this, 'process'], $this->personaRepository->fetchAll($orderBy)); + } catch (Implement\Exception\EmptyResult) { + return []; + } + } /** * @param int $rut * @return Model\Persona @@ -29,7 +41,7 @@ class Persona extends Ideal\Service { try { return $this->process($this->personaRepository->fetchById($rut)); - } catch (Implement\Exception\EmptyResult $exception) { + } catch (Implement\Exception\EmptyResult) { try { $this->propietarioRepository->fetchById($rut); return $this->add(compact('rut')); @@ -91,6 +103,60 @@ class Persona extends Ideal\Service } return $this->process($persona); } + + /** + * @param array $ruts + * @param bool $load + * @return array + * @throws Create + * @throws Read + */ + public function addMissingPropietarios(array $ruts, bool $load = false): ?array + { + try { + $propietarios = $this->propietarioRepository->fetchByRuts($ruts); + } catch (Implement\Exception\EmptyResult $exception) { + throw new Read(__CLASS__, $exception); + } + $data = []; + $datos = []; + foreach ($propietarios as $i => $propietario) { + $data []= [ + 'rut' => $propietario->rut, + 'digito' => $propietario->dv, + 'nombres' => $propietario->nombres, + 'apellido_paterno' => $propietario->apellidos['paterno'], + 'apellido_materno' => $propietario->apellidos['materno'], + ]; + $datos []= [ + 'persona_rut' => $propietario->rut, + 'direccion_id' => $propietario->datos?->direccion_id, + 'email' => $propietario->datos?->email, + 'telefono' => $propietario->datos?->telefono, + ]; + } + + try { + $personas = $this->personaRepository->saveMissing($data); + } catch (PDOException|Implement\Exception\EmptyResult $exception) { + throw new Create(__CLASS__, $exception); + } + $personasRuts = array_map(function(Model\Persona $persona) { + return $persona->rut; + }, $personas); + $datos = array_filter($datos, function($row) use ($personasRuts) { + return in_array($row['persona_rut'], $personasRuts); + }); + try { + $this->datosPersonaRepository->saveMissing($datos); + } catch (PDOException $exception) { + throw new Create(__CLASS__, $exception); + } + if ($load) { + return array_map([$this, 'process'], $personas); + } + return null; + } public function edit(Model\Persona $persona, array $data): Model\Persona { $filteredData = $this->personaRepository->filterData($data); diff --git a/app/src/Service/Queue.php b/app/src/Service/Queue.php index b704b9c..09b6a76 100644 --- a/app/src/Service/Queue.php +++ b/app/src/Service/Queue.php @@ -35,7 +35,52 @@ class Queue extends Ideal\Service } } - public function run(?RequestInterface $request): bool + /** + * @return array + */ + public function getPendingJobs(): array + { + return $this->jobService->getPending(); + } + public function runJob(int $job_id, ?RequestInterface $request = null): bool + { + try { + $job = $this->jobService->getPendingById($job_id); + } catch (Read $exception) { + $this->logger->debug($exception); + return false; + } + + $type = 'default'; + if (isset($job->configuration['type'])) { + $type = strtolower($job->configuration['type']); + } + if (!isset($this->workers[$type])) { + $type = 'default'; + } + + $worker = $this->workers[$type]; + if (is_a($worker, Service\Worker\Request::class) and $request !== null) { + $worker->setRequest($request); + } + + try { + if (!$worker->execute($job)) { + $this->logger->debug("Could not execute job {$job_id}"); + return false; + } + if (!$this->jobService->execute($job)) { + $this->logger->debug("Could not remove job {$job_id}"); + return false; + } + } catch (Exception $exception) { + $final = new Exception("Could not run job", 0, $exception); + $this->logger->warning($final); + return false; + } + return true; + } + public function run(?RequestInterface $request = null): bool { try { $jobs = $this->jobService->getPending(); @@ -47,33 +92,11 @@ class Queue extends Ideal\Service $errors = []; foreach ($jobs as $job) { - $type = 'default'; - if (isset($job->configuration['type'])) { - $type = strtolower($job->configuration['type']); - } - if (!isset($this->workers[$type])) { - $type = 'default'; - } - - $worker = $this->workers[$type]; - if (is_a($worker, Service\Worker\Request::class)) { - $worker->setRequest($request); - } - try { - if (!$worker->execute($job)) { - $errors []= $job->id; - continue; - } - if (!$this->jobService->execute($job)) { - $errors []= $job->id; - } - } catch (Exception $exception) { - $final = new Exception("Could not run job", 0, $exception); - $this->logger->warning($final); + $this->runJob($job->id, $request); + } catch (Exception) { $errors []= $job->id; } - break; } return count($errors) === 0; } diff --git a/app/src/Service/Redis.php b/app/src/Service/Redis.php index e399bf3..66e06c9 100644 --- a/app/src/Service/Redis.php +++ b/app/src/Service/Redis.php @@ -28,7 +28,12 @@ class Redis public function set(string $name, mixed $value, int $expirationTTL = 60 * 60 * 24): void { try { - $this->client->set($name, $value, 'EX', $expirationTTL); + $resolution = 'EX'; + if ($expirationTTL === -1) { + $resolution = null; + $expirationTTL = null; + } + $this->client->set($name, $value, $resolution, $expirationTTL); } catch (ConnectionException) { return; } diff --git a/app/src/Service/Venta/MediosPago/Toku.php b/app/src/Service/Venta/MediosPago/Toku.php index a295148..81eb16a 100644 --- a/app/src/Service/Venta/MediosPago/Toku.php +++ b/app/src/Service/Venta/MediosPago/Toku.php @@ -4,6 +4,7 @@ namespace Incoviba\Service\Venta\MediosPago; use Incoviba\Common\Ideal; use Incoviba\Common\Implement\Exception\EmptyResponse; use Incoviba\Exception\InvalidResult; +use Incoviba\Exception\ServiceAction\Read; use Incoviba\Model; use Incoviba\Model\Persona; use Incoviba\Model\Venta\Propietario; @@ -149,6 +150,80 @@ class Toku extends Ideal\Service return $this->updatePago($paymentData); } } + public function check(bool $update = false): array + { + try { + list('existingSubscriptions' => $existingSubscriptions, 'missingVentas' => $missingVentas) = $this->subscription->check(); + } catch (Read) { + return []; + } + + $queues = []; + if (count($missingVentas) > 0) { + foreach ($missingVentas as $venta) { + $cuotas = $venta->formaPago()->pie->cuotas(); + if (count($cuotas) === 0) { + continue; + } + $queueData = [ + 'type' => 'request', + 'url' => "/api/external/toku/cuotas/{$venta->id}", + 'method' => 'post', + 'body' => ['cuotas' => array_map(function(Model\Venta\Cuota $cuota) {return $cuota->id;}, $cuotas)] + ]; + $queues []= $queueData; + } + } + if ($update and count($existingSubscriptions) > 0) { + foreach ($existingSubscriptions as $subscription) { + $cuotas = $subscription->venta->formaPago()->pie->cuotas(); + if (count($cuotas) === 0) { + continue; + } + + $propietario = $subscription->venta->propietario(); + try { + $customer = $this->customer->getById($propietario->rut()); + } catch (InvalidResult) { + continue; + } + try { + $editData = [ + 'rut' => $customer['rut'], + 'nombreCompleto' => $propietario->nombreCompleto(), + 'email' => $propietario->datos?->email ?? '', + 'telefono' => $propietario->datos?->telefono ?? '' + ]; + $this->customer->edit($customer['toku_id'], $editData); + } catch (EmptyResponse) {} + foreach ($cuotas as $cuota) { + try { + $invoice = $this->invoice->getById($cuota->id); + $editData = [ + 'customer' => $customer['toku_id'], + 'product_id' => $subscription->venta->id, + 'subscription' => $subscription->toku_id, + 'cuota' => $cuota + ]; + try { + $this->invoice->edit($invoice['toku_id'], $editData); + } catch (EmptyResponse) {} + } catch (InvalidResult) { + $invoiceData = [ + 'customer' => $customer['toku_id'], + 'product_id' => $subscription->venta->id, + 'subscription' => $subscription->toku_id, + 'cuota' => $cuota + ]; + try { + $this->invoice->add($invoiceData); + } catch (EmptyResponse) {} + } + } + } + } + return $queues; + } /** * @param array $request diff --git a/app/src/Service/Venta/MediosPago/Toku/Customer.php b/app/src/Service/Venta/MediosPago/Toku/Customer.php index 75c1a0b..2af755b 100644 --- a/app/src/Service/Venta/MediosPago/Toku/Customer.php +++ b/app/src/Service/Venta/MediosPago/Toku/Customer.php @@ -1,9 +1,9 @@ sendDelete($request_uri, [204], [404, 409]); } + /** + * @return array + * @throws Read + */ + public function check(): array + { + $ventas = $this->ventaService->getAllWithCuotaPending(); + $ids = array_column($ventas, 'id'); + $existingSubscriptions = []; + try { + $existingSubscriptions = $this->subscriptionRepsitory->fetchByVentas($ids); + } catch (EmptyResult) {} + if (count($existingSubscriptions) === 0) { + $missingVentas = $ventas; + } else { + $missingVentas = array_filter($ventas, function($venta) use ($existingSubscriptions) { + return !array_any($existingSubscriptions, fn($subscription) => $subscription->venta->id === $venta->id); + }); + } + return compact('existingSubscriptions', 'missingVentas'); + } + protected function save(array $data): bool { return $this->doSave($this->subscriptionRepsitory, $data); diff --git a/app/src/Service/Worker/CheckExternal.php b/app/src/Service/Worker/CheckExternal.php new file mode 100644 index 0000000..23c1cb3 --- /dev/null +++ b/app/src/Service/Worker/CheckExternal.php @@ -0,0 +1,46 @@ +configuration; + $serviceClass = $configuration['service']; + if (!$this->container->has($serviceClass)) { + return false; + } + try { + $service = $this->container->get($serviceClass); + } catch (NotFoundExceptionInterface | ContainerExceptionInterface) { + return false; + } + $method = $configuration['method'] ?? 'check'; + + if (!method_exists($service, $method)) { + return false; + } + if (!isset($this->queueService)) { + $this->queueService = $this->container->get(Service\Queue::class); + } + $queues = $service->{$method}(); + foreach ($queues as $queue) { + $this->queueService->enqueue($queue); + } + return true; + } +} diff --git a/cli/setup/settings/commands.php b/cli/setup/settings/commands.php index 1cd1449..0345604 100644 --- a/cli/setup/settings/commands.php +++ b/cli/setup/settings/commands.php @@ -14,7 +14,8 @@ return [ 'ventas:cuotas:pendientes' => Incoviba\Command\Ventas\Cuotas\Pendientes::class, 'ventas:cuotas:vencer' => Incoviba\Command\Ventas\Cuotas\PorVencer::class, 'queue' => Incoviba\Command\Queue::class, - 'loop' => Incoviba\Command\BaseLoop::class + 'loop' => Incoviba\Command\BaseLoop::class, + 'external:services' => Incoviba\Command\ExternalServices::class ]; } ]; diff --git a/cli/src/Command/ExternalServices.php b/cli/src/Command/ExternalServices.php new file mode 100644 index 0000000..30bcec2 --- /dev/null +++ b/cli/src/Command/ExternalServices.php @@ -0,0 +1,22 @@ +writeln("GET {$url}"); + $response = $this->client->get($url); + $output->writeln("Response Code: {$response->getStatusCode()}"); + + return Console\Command\Command::SUCCESS; + } +} diff --git a/cli/src/Command/Queue.php b/cli/src/Command/Queue.php index 372b642..e23cb38 100644 --- a/cli/src/Command/Queue.php +++ b/cli/src/Command/Queue.php @@ -18,11 +18,48 @@ class Queue extends Command $now = new DateTimeImmutable(); $io->title("[{$now->format('Y-m-d H:i:s e')}] Running Queue..."); - $uri = '/api/queue/run'; + $jobs = $this->getJobs($output); + if (is_int($jobs)) { + return $jobs; + } + + return $this->runJobs($output, $jobs); + } + + protected function getJobs(Console\Output\OutputInterface $output): int|array + { + $uri = '/api/queue/jobs'; + $output->writeln("GET {$uri}"); + $response = $this->client->get($uri); + $output->writeln("Response Code: {$response->getStatusCode()}"); + if ($response->getStatusCode() !== 200) { + return Console\Command\Command::FAILURE; + } + + $contents = $response->getBody()->getContents(); + if (empty($contents)) { + return []; + } + + return json_decode($contents, true)['jobs']; + } + protected function runJobs(Console\Output\OutputInterface $output, array $jobs): int + { + $errors = 0; + foreach ($jobs as $job) { + if ($this->runJob($output, $job) === Console\Command\Command::FAILURE) { + $errors ++; + } + } + return $errors === 0 ? Console\Command\Command::SUCCESS : Console\Command\Command::FAILURE; + } + protected function runJob(Console\Output\OutputInterface $output, int $job_id): int + { + $uri = "/api/queue/run/{$job_id}"; $output->writeln("GET {$uri}"); $response = $this->client->get($uri); $output->writeln("Response Code: {$response->getStatusCode()}"); - return Console\Command\Command::SUCCESS; + return ((int) floor($response->getStatusCode() / 100) === 2) ? Console\Command\Command::SUCCESS : Console\Command\Command::FAILURE; } }