Cambio en queue para que no quede pegado esperando respuesta en cli.

Chequeo de servicios externos para agregar elementos pendientes.
This commit is contained in:
Juan Pablo Vial
2025-05-15 19:32:25 -04:00
parent 8d32aecd09
commit 8965354528
21 changed files with 687 additions and 65 deletions

View File

@ -1,4 +1,6 @@
<?php <?php
use Incoviba\Controller\API\External;
$app->group('/external', function($app) { $app->group('/external', function($app) {
$files = new FilesystemIterator(implode(DIRECTORY_SEPARATOR, [__DIR__, 'external'])); $files = new FilesystemIterator(implode(DIRECTORY_SEPARATOR, [__DIR__, 'external']));
foreach ($files as $file) { foreach ($files as $file) {
@ -7,4 +9,7 @@ $app->group('/external', function($app) {
} }
include_once $file->getRealPath(); include_once $file->getRealPath();
} }
$app->group('/services', function($app) {
$app->get('/check[/]', [External::class, 'check']);
});
}); });

View File

@ -2,5 +2,9 @@
use Incoviba\Controller\API\Queues; use Incoviba\Controller\API\Queues;
$app->group('/queue', function($app) { $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);
});
}); });

View File

@ -135,7 +135,8 @@ return [
Incoviba\Service\Venta\MediosPago\Toku\Subscription::class => function(ContainerInterface $container) { Incoviba\Service\Venta\MediosPago\Toku\Subscription::class => function(ContainerInterface $container) {
$service = new Incoviba\Service\Venta\MediosPago\Toku\Subscription( $service = new Incoviba\Service\Venta\MediosPago\Toku\Subscription(
$container->get('TokuClient'), $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)); $service->setLogger($container->get(Psr\Log\LoggerInterface::class));
return $service; return $service;
@ -165,7 +166,8 @@ return [
$container->get(Incoviba\Service\Worker\Request::class) $container->get(Incoviba\Service\Worker\Request::class)
)) ))
->register('request', $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) { Incoviba\Service\Worker\Request::class => function(ContainerInterface $container) {
$apiKey = md5($container->get('API_KEY')); $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));
} }
]; ];

View File

@ -0,0 +1,20 @@
<?php
namespace Incoviba\Controller\API;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Incoviba\Common\Ideal;
use Incoviba\Service;
class External extends Ideal\Controller
{
use withJson;
public function check(ServerRequestInterface $request, ResponseInterface $response, Service\External $externalService): ResponseInterface
{
if ($externalService->check()) {
return $response->withStatus(204);
}
return $response->withStatus(409);
}
}

View File

@ -4,13 +4,15 @@ namespace Incoviba\Controller\API;
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Incoviba\Common\Ideal; use Incoviba\Common\Ideal;
use Incoviba\Exception\ServiceAction\Read;
use Incoviba\Service; use Incoviba\Service;
class Queues extends Ideal\Controller class Queues extends Ideal\Controller
{ {
use withJson; 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 = [ $output = [
'success' => false 'success' => false
@ -20,4 +22,20 @@ class Queues extends Ideal\Controller
} }
return $this->withJson($response, $output); 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);
}
} }

View File

@ -1,6 +1,8 @@
<?php <?php
namespace Incoviba\Repository; namespace Incoviba\Repository;
use PDO;
use PDOException;
use Incoviba\Common\Define; use Incoviba\Common\Define;
use Incoviba\Common\Ideal; use Incoviba\Common\Ideal;
use Incoviba\Common\Implement; use Incoviba\Common\Implement;
@ -45,6 +47,67 @@ class Persona extends Ideal\Repository
return array_intersect_key($data, array_flip(['rut', 'digito', 'nombres', 'apellido_paterno', 'apellido_materno'])); return array_intersect_key($data, array_flip(['rut', 'digito', 'nombres', 'apellido_paterno', 'apellido_materno']));
} }
/**
* @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);
}
/**
* @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 protected function getKey(): string
{ {
return 'rut'; return 'rut';

View File

@ -1,6 +1,7 @@
<?php <?php
namespace Incoviba\Repository\Persona; namespace Incoviba\Repository\Persona;
use PDOException;
use Incoviba\Common\Define; use Incoviba\Common\Define;
use Incoviba\Common\Ideal; use Incoviba\Common\Ideal;
use Incoviba\Common\Implement; use Incoviba\Common\Implement;
@ -85,4 +86,31 @@ class Datos extends Ideal\Repository
->where('persona_rut = ?'); ->where('persona_rut = ?');
return $this->fetchOne($query, [$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);
}
} }

View File

@ -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')])); 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 public function fetchByVenta(int $venta_id): Model\Venta\MediosPago\Toku\Subscription
{ {
$query = $this->connection->getQueryBuilder() $query = $this->connection->getQueryBuilder()
@ -51,6 +56,12 @@ class Subscription extends Ideal\Repository
->where('venta_id = :venta_id'); ->where('venta_id = :venta_id');
return $this->fetchOne($query, compact('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 public function fetchByTokuId(string $toku_id): Model\Venta\MediosPago\Toku\Subscription
{ {
$query = $this->connection->getQueryBuilder() $query = $this->connection->getQueryBuilder()
@ -59,4 +70,19 @@ class Subscription extends Ideal\Repository
->where('toku_id = :toku_id'); ->where('toku_id = :toku_id');
return $this->fetchOne($query, compact('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);
}
} }

View File

@ -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'])); 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);
}
} }

View File

@ -0,0 +1,49 @@
<?php
namespace Incoviba\Service;
use Psr\Log\LoggerInterface;
use Incoviba\Common\Ideal;
class External extends Ideal\Service
{
public function __construct(LoggerInterface $logger, protected Queue $queueService)
{
parent::__construct($logger);
}
protected array $externalServices;
public function register($service): self
{
if (!isset($this->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;
}
}

View File

@ -1,9 +1,13 @@
<?php <?php
namespace Incoviba\Service; namespace Incoviba\Service;
use DateTimeImmutable;
use InvalidArgumentException;
use OutOfRangeException;
use PDOException; use PDOException;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Incoviba\Common\Ideal; use Incoviba\Common\Ideal;
use Incoviba\Common\Implement\Exception\EmptyRedis;
use Incoviba\Common\Implement\Exception\EmptyResult; use Incoviba\Common\Implement\Exception\EmptyResult;
use Incoviba\Exception\ServiceAction\{Create, Read, Update}; use Incoviba\Exception\ServiceAction\{Create, Read, Update};
use Incoviba\Repository; use Incoviba\Repository;
@ -11,59 +15,132 @@ use Incoviba\Model;
class Job extends Ideal\Service class Job extends Ideal\Service
{ {
public function __construct(LoggerInterface $logger, protected Repository\Job $jobRepository) public function __construct(LoggerInterface $logger, protected Redis $redisService,
protected Repository\Job $jobRepository)
{ {
parent::__construct($logger); parent::__construct($logger);
} }
protected string $redisKey = 'jobs';
public function getPending(null|string|array $orderBy = null): array
{
try {
$jobs = $this->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 * @param array $configuration
* @return Model\Job * @return Model\Job
* @throws Create * @throws Read
*/ */
public function add(array $configuration): Model\Job 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 { try {
$data = [ $jobs = $this->redisService->get($this->redisKey);
'configuration' => json_encode($configuration) if ($jobs !== null) {
]; $jobs = json_decode($jobs, true);
$job = $this->jobRepository->create($data); }
return $this->process($this->jobRepository->save($job)); } catch (EmptyRedis) {}
} catch (PDOException $exception) { $jobs []= $data;
throw new Create(__CLASS__, $exception); $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 * @return array
* @throws Read * @throws Read
*/ */
public function getPending(): array protected function getJobs(): array
{ {
try { try {
return array_map([$this, 'process'],$this->jobRepository->fetchPending()); $jobs = $this->redisService->get($this->redisKey);
} catch (EmptyResult $exception) { } catch (EmptyRedis $exception) {
throw new Read(__CLASS__, $exception); throw new Read(__CLASS__, $exception);
} }
} if ($jobs === null) {
$exception = new InvalidArgumentException("Redis Key {$this->redisKey} not found");
/** 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);
} }
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; return $job;
} }
} }

View File

@ -20,6 +20,18 @@ class Persona extends Ideal\Service
parent::__construct($logger); 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 * @param int $rut
* @return Model\Persona * @return Model\Persona
@ -29,7 +41,7 @@ class Persona extends Ideal\Service
{ {
try { try {
return $this->process($this->personaRepository->fetchById($rut)); return $this->process($this->personaRepository->fetchById($rut));
} catch (Implement\Exception\EmptyResult $exception) { } catch (Implement\Exception\EmptyResult) {
try { try {
$this->propietarioRepository->fetchById($rut); $this->propietarioRepository->fetchById($rut);
return $this->add(compact('rut')); return $this->add(compact('rut'));
@ -91,6 +103,60 @@ class Persona extends Ideal\Service
} }
return $this->process($persona); 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 public function edit(Model\Persona $persona, array $data): Model\Persona
{ {
$filteredData = $this->personaRepository->filterData($data); $filteredData = $this->personaRepository->filterData($data);

View File

@ -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 { try {
$jobs = $this->jobService->getPending(); $jobs = $this->jobService->getPending();
@ -47,33 +92,11 @@ class Queue extends Ideal\Service
$errors = []; $errors = [];
foreach ($jobs as $job) { 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 { try {
if (!$worker->execute($job)) { $this->runJob($job->id, $request);
$errors []= $job->id; } catch (Exception) {
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);
$errors []= $job->id; $errors []= $job->id;
} }
break;
} }
return count($errors) === 0; return count($errors) === 0;
} }

View File

@ -28,7 +28,12 @@ class Redis
public function set(string $name, mixed $value, int $expirationTTL = 60 * 60 * 24): void public function set(string $name, mixed $value, int $expirationTTL = 60 * 60 * 24): void
{ {
try { 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) { } catch (ConnectionException) {
return; return;
} }

View File

@ -4,6 +4,7 @@ namespace Incoviba\Service\Venta\MediosPago;
use Incoviba\Common\Ideal; use Incoviba\Common\Ideal;
use Incoviba\Common\Implement\Exception\EmptyResponse; use Incoviba\Common\Implement\Exception\EmptyResponse;
use Incoviba\Exception\InvalidResult; use Incoviba\Exception\InvalidResult;
use Incoviba\Exception\ServiceAction\Read;
use Incoviba\Model; use Incoviba\Model;
use Incoviba\Model\Persona; use Incoviba\Model\Persona;
use Incoviba\Model\Venta\Propietario; use Incoviba\Model\Venta\Propietario;
@ -149,6 +150,80 @@ class Toku extends Ideal\Service
return $this->updatePago($paymentData); 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 * @param array $request

View File

@ -1,9 +1,9 @@
<?php <?php
namespace Incoviba\Service\Venta\MediosPago\Toku; namespace Incoviba\Service\Venta\MediosPago\Toku;
use Psr\Http\Client\ClientInterface;
use Incoviba\Repository; use Incoviba\Repository;
use Incoviba\Service\Venta\MediosPago\AbstractEndPoint; use Incoviba\Service\Venta\MediosPago\AbstractEndPoint;
use Psr\Http\Client\ClientInterface;
class Customer extends AbstractEndPoint class Customer extends AbstractEndPoint
{ {

View File

@ -1,14 +1,19 @@
<?php <?php
namespace Incoviba\Service\Venta\MediosPago\Toku; namespace Incoviba\Service\Venta\MediosPago\Toku;
use Psr\Http\Client\ClientInterface;
use Incoviba\Common\Implement\Exception\EmptyResult;
use Incoviba\Exception\ServiceAction\Read;
use Incoviba\Model\Venta; use Incoviba\Model\Venta;
use Incoviba\Repository; use Incoviba\Repository;
use Incoviba\Service;
use Incoviba\Service\Venta\MediosPago\AbstractEndPoint; use Incoviba\Service\Venta\MediosPago\AbstractEndPoint;
use Psr\Http\Client\ClientInterface;
class Subscription extends AbstractEndPoint class Subscription extends AbstractEndPoint
{ {
public function __construct(ClientInterface $client, protected Repository\Venta\MediosPago\Toku\Subscription $subscriptionRepsitory) public function __construct(ClientInterface $client,
protected Repository\Venta\MediosPago\Toku\Subscription $subscriptionRepsitory,
protected Service\Venta $ventaService)
{ {
parent::__construct($client); parent::__construct($client);
} }
@ -43,6 +48,28 @@ class Subscription extends AbstractEndPoint
$this->sendDelete($request_uri, [204], [404, 409]); $this->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 protected function save(array $data): bool
{ {
return $this->doSave($this->subscriptionRepsitory, $data); return $this->doSave($this->subscriptionRepsitory, $data);

View File

@ -0,0 +1,46 @@
<?php
namespace Incoviba\Service\Worker;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\ContainerInterface;
use Psr\Container\NotFoundExceptionInterface;
use Psr\Log\LoggerInterface;
use Incoviba\Common\Ideal;
use Incoviba\Model;
use Incoviba\Service;
class CheckExternal extends Ideal\Service implements Service\Worker
{
public function __construct(LoggerInterface $logger, protected ContainerInterface $container)
{
parent::__construct($logger);
}
protected Service\Queue $queueService;
public function execute(Model\Job $job): bool
{
$configuration = $job->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;
}
}

View File

@ -14,7 +14,8 @@ return [
'ventas:cuotas:pendientes' => Incoviba\Command\Ventas\Cuotas\Pendientes::class, 'ventas:cuotas:pendientes' => Incoviba\Command\Ventas\Cuotas\Pendientes::class,
'ventas:cuotas:vencer' => Incoviba\Command\Ventas\Cuotas\PorVencer::class, 'ventas:cuotas:vencer' => Incoviba\Command\Ventas\Cuotas\PorVencer::class,
'queue' => Incoviba\Command\Queue::class, 'queue' => Incoviba\Command\Queue::class,
'loop' => Incoviba\Command\BaseLoop::class 'loop' => Incoviba\Command\BaseLoop::class,
'external:services' => Incoviba\Command\ExternalServices::class
]; ];
} }
]; ];

View File

@ -0,0 +1,22 @@
<?php
namespace Incoviba\Command;
use Symfony\Component\Console;
use Incoviba\Common\Alias;
#[Console\Attribute\AsCommand(
name: 'external:services',
description: 'Check external services',
)]
class ExternalServices extends Alias\Command
{
public function execute(Console\Input\InputInterface $input, Console\Output\OutputInterface $output): int
{
$url = '/api/external/services/check';
$output->writeln("GET {$url}");
$response = $this->client->get($url);
$output->writeln("Response Code: {$response->getStatusCode()}");
return Console\Command\Command::SUCCESS;
}
}

View File

@ -18,11 +18,48 @@ class Queue extends Command
$now = new DateTimeImmutable(); $now = new DateTimeImmutable();
$io->title("[{$now->format('Y-m-d H:i:s e')}] Running Queue..."); $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}"); $output->writeln("GET {$uri}");
$response = $this->client->get($uri); $response = $this->client->get($uri);
$output->writeln("Response Code: {$response->getStatusCode()}"); $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;
} }
} }