5 Commits

17 changed files with 212 additions and 30 deletions

0
app/bin/console Normal file → Executable file
View File

0
app/bin/integration_tests Normal file → Executable file
View File

0
app/bin/performance_tests Normal file → Executable file
View File

0
app/bin/unit_tests Normal file → Executable file
View File

View File

@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
use Phinx\Migration\AbstractMigration;
final class ChangeTelefonoSizeInPropietario extends AbstractMigration
{
/**
* Change Method.
*
* Write your reversible migrations using this method.
*
* More information on writing migrations is available here:
* https://book.cakephp.org/phinx/0/en/migrations.html#the-change-method
*
* Remember to call "create()" or "update()" and NOT "save()" when working
* with the Table class.
*/
public function change(): void
{
$this->table('propietario')
->changeColumn('telefono', 'biginteger', ['null' => true, 'signed' => false, 'default' => null])
->update();
}
}

View File

@ -156,7 +156,7 @@
<script>
const regiones = [
@foreach ($regiones as $region)
'<div class="item" data-value="{{$region->id}}">{{$region->descripcion}}</div>',
'<div class="item" data-value="{{$region->id}}">{{$region->numeral}} - {{$region->descripcion}}</div>',
@endforeach
]

View File

@ -54,22 +54,23 @@
}
$(document).ready(() => {
const url = '{{$urls->api}}/ventas/pago/{{$venta->resciliacion()->id}}'
let old = new Date({{$venta->resciliacion()?->fecha->format('Y') ?? date('Y')}},
{{$venta->resciliacion()?->fecha->format('n') ?? date('n')}}-1, {{$venta->resciliacion()?->fecha->format('j') ?? date('j')}})
let old = new Date(Date.parse('{{$venta->resciliacion()?->fecha->format('Y-m-d') ?? $venta->currentEstado()->fecha->format('Y-m-d') ?? $venta->fecha->format('Y-m-d')}}') + 24 * 60 * 60 * 1000)
calendar_date_options['initialDate'] = old
calendar_date_options['onChange'] = function(date, text, mode) {
if (date.getTime() === old.getTime()) {
return
}
const body = new FormData()
body.set('fecha', date.toISOString())
const fecha = new Date(date.getTime())
fecha.setDate(fecha.getDate() - 1)
body.set('fecha', fecha.toISOString())
$('#loading-spinner-fecha').show()
APIClient.fetch(url, {method: 'post', body}).then(response => {
$('#loading-spinner-fecha').hide()
if (!response) {
return
}
old = date
old = new Date(date.getTime())
alertResponse('Fecha cambiada correctamente.')
})
}

View File

@ -6,15 +6,26 @@
@section('venta_content')
<div class="ui list">
<div class="item">
<div class="header">Valor Pagado</div>
<div class="content">
{{$format->pesos($venta->formaPago()->pie->pagado('pesos'))}}
<div class="ui left pointing small label">
{{$format->number($venta->formaPago()->pie->pagado() / $venta->valor * 100)}}% de la venta
@if (isset($venta->formaPago()->pie))
<div class="item">
<div class="header">Valor Pagado</div>
<div class="content">
{{$format->pesos($venta->formaPago()->pie->pagado('pesos'))}}
<div class="ui left pointing small label">
{{$format->number($venta->formaPago()->pie->pagado() / $venta->valor * 100)}}% de la venta
</div>
</div>
</div>
</div>
@else
<div class="item">
<div class="ui compact warning message">
<div class="content">
<i class="exclamation triangle icon"></i>
No tiene valor pagado
</div>
</div>
</div>
@endif
<div class="item">
<div class="header">
Multa Estandar

View File

@ -116,7 +116,18 @@ return [
->registerSub($container->get(Incoviba\Service\Contabilidad\Cartola\BCI\Mes::class));
},
'TokuClient' => function(ContainerInterface $container) {
$logger = $container->get('externalLogger');
$stack = GuzzleHttp\HandlerStack::create();
$stack->push(GuzzleHttp\Middleware::mapRequest(function(Psr\Http\Message\RequestInterface $request) use ($logger) {
$logger->info('Toku Request', [
'method' => $request->getMethod(),
'uri' => (string) $request->getUri(),
'headers' => $request->getHeaders(),
'body' => $request->getBody()->getContents(),
]);
}));
return new GuzzleHttp\Client([
'handler' => $stack,
'base_uri' => $container->get('TOKU_URL'),
'headers' => [
'x-api-key' => $container->get('TOKU_TOKEN'),

View File

@ -4,6 +4,9 @@ namespace Incoviba\Controller;
use Incoviba\Common\Alias\View;
use Incoviba\Common\Implement\Exception\EmptyRedis;
use Incoviba\Common\Implement\Exception\EmptyResult;
use Incoviba\Exception\ServiceAction\Create;
use Incoviba\Exception\ServiceAction\Read;
use Incoviba\Exception\ServiceAction\Update;
use Incoviba\Model;
use Incoviba\Repository;
use Incoviba\Service;
@ -142,9 +145,24 @@ class Ventas
return $view->render($response, 'ventas.desistir', compact('venta'));
}
public function desistida(ServerRequestInterface $request, ResponseInterface $response, Service\Venta $ventaService,
Service\Venta\Pago $pagoService,
View $view, int $venta_id): ResponseInterface
{
$venta = $ventaService->getById($venta_id);
try {
$venta = $ventaService->getById($venta_id);
} catch (Read) {
return $view->render($response->withStatus(404), 'not_found');
}
if ($venta->resciliacion() === null) {
$pagoData = [
'fecha' => $venta->currentEstado()->fecha->format('Y-m-d'),
'valor' => 0
];
try {
$pago = $pagoService->add($pagoData);
$venta = $ventaService->edit($venta, ['resciliacion' => $pago->id]);
} catch (Create | Update) {}
}
return $view->render($response, 'ventas.desistida', compact('venta'));
}
}

View File

@ -59,6 +59,8 @@ class Queue extends Ideal\Service
try {
if (!$worker->execute($job)) {
$this->logger->debug("Could not execute job {$job->id}");
$job->retries++;
$this->jobService->update($job);
return false;
}
if (!$this->jobService->execute($job)) {
@ -67,6 +69,12 @@ class Queue extends Ideal\Service
}
} catch (Exception $exception) {
$this->logger->warning("Could not run job {$job->id}", ['exception' => $exception]);
$job->retries++;
try {
$this->jobService->update($job);
} catch (Update $exception) {
$this->logger->error($exception->getMessage(), ['job' => $job, 'exception' => $exception]);
}
return false;
}
return true;

View File

@ -4,7 +4,7 @@ namespace Incoviba\Service;
use DateTimeInterface;
use DateTimeImmutable;
use DateMalformedStringException;
use function PHPUnit\Framework\countOf;
use Incoviba\Service\Valor\Phone;
class Valor
{
@ -40,6 +40,14 @@ class Valor
}
return $value / $this->ufService->get($date);
}
public function phone(): Phone
{
return new Phone();
}
public function telefono(): Phone
{
return $this->phone();
}
protected function getDateTime(null|string|DateTimeInterface $date): DateTimeInterface
{

View File

@ -0,0 +1,28 @@
<?php
namespace Incoviba\Service\Valor;
class Phone
{
public function toDatabase(?string $phone): ?int
{
if ($phone === null) {
return null;
}
return (int) str_replace([' ', '+'], '', $phone) ?? null;
}
public function toDisplay(?int $phone): ?string
{
if ($phone === null) {
return null;
}
$parts = preg_split('/(?=<country>\d{2})?(?=<area>\d)(?=<first>\d{4})(?=<last>\d{4})/', $phone);
$output = [];
if (array_key_exists('country', $parts)) {
$output [] = "+{$parts[0]}";
}
$output [] = $parts[1] ?? '';
$output [] = $parts[2] ?? '';
$output [] = $parts[3] ?? '';
return implode(' ', $output);
}
}

View File

@ -1,22 +1,24 @@
<?php
namespace Incoviba\Service\Venta;
use PDOException;
use Psr\Log\LoggerInterface;
use Incoviba\Common\Ideal\Service;
use Incoviba\Common\Implement\Exception\EmptyResult;
use Incoviba\Exception\ServiceAction\Create;
use Incoviba\Exception\ServiceAction\Read;
use Incoviba\Exception\ServiceAction\Update;
use Incoviba\Repository;
use Incoviba\Model;
use PDOException;
use Psr\Log\LoggerInterface;
use Incoviba\Repository;
use Incoviba\Service\Valor;
class Propietario extends Service
{
public function __construct(
LoggerInterface $logger,
protected Repository\Venta\Propietario $propietarioRepository,
protected Repository\Direccion $direccionRepository
protected Repository\Direccion $direccionRepository,
protected Valor $valorService
) {
parent::__construct($logger);
}
@ -49,6 +51,9 @@ class Propietario extends Service
$data['direccion'] = $direccion->id;
}
$filteredData = $this->propietarioRepository->filterData($data);
if (array_key_exists('telefono', $filteredData)) {
$filteredData['telefono'] = $this->valorService->telefono()->toDatabase($filteredData['telefono']);
}
try {
return $this->propietarioRepository->edit($propietario, $filteredData);
} catch (PDOException | EmptyResult $exception) {
@ -85,6 +90,10 @@ class Propietario extends Service
]);
$filtered_data = array_intersect_key($data, $fields);
if (array_key_exists('telefono', $filtered_data)) {
$filtered_data['telefono'] = $this->valorService->telefono()->toDatabase($filtered_data['telefono']);
}
try {
$propietario = $this->propietarioRepository->fetchById($data['rut']);
$edits = [];
@ -95,6 +104,7 @@ class Propietario extends Service
} catch (EmptyResult) {
try {
$propietario = $this->propietarioRepository->create($filtered_data);
$this->logger->info('Propietario', ['propietario' => $propietario]);
$propietario = $this->propietarioRepository->save($propietario);
} catch (PDOException $exception) {
throw new Create(__CLASS__, $exception);

View File

@ -42,7 +42,14 @@ class Queue extends Command
}
$io->title("[{$now->format('Y-m-d H:i:s e')}] Running Queue...");
return $this->runJob();
$results = [];
for ($i = 0; $i < $this->batchSize; $i++) {
if ($this->jobService->getPending() === 0) {
break;
}
$results []= $this->runJob();
}
return count(array_filter($results, fn ($result) => $result === self::FAILURE)) === 0 ? self::SUCCESS : self::FAILURE;
}
protected array $sections;

View File

@ -2,13 +2,14 @@
namespace Incoviba\Command\Queue;
use Throwable;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console;
use Incoviba\Service;
#[Console\Attribute\AsCommand(name: 'queue:push', description: 'Push a job to the queue')]
class Push extends Console\Command\Command
{
public function __construct(protected Service\Job $jobService, ?string $name = null)
public function __construct(protected LoggerInterface $logger, protected Service\Job $jobService, ?string $name = null)
{
parent::__construct($name);
}
@ -16,7 +17,7 @@ class Push extends Console\Command\Command
protected function configure(): void
{
$this->addOption('configurations', 'c', Console\Input\InputOption::VALUE_REQUIRED | Console\Input\InputOption::VALUE_IS_ARRAY, 'Job configuration options array, each job configuration must be in valid JSON format');
$this->addOption('file', 'f', Console\Input\InputOption::VALUE_REQUIRED, 'Path to jobs configuration file with JSON array');
$this->addOption('files', 'f', Console\Input\InputOption::VALUE_REQUIRED | Console\Input\InputOption::VALUE_IS_ARRAY, 'Paths to jobs configurations files with JSON array content');
}
protected function execute(Console\Input\InputInterface $input, Console\Output\OutputInterface $output): int
@ -49,19 +50,72 @@ class Push extends Console\Command\Command
}
protected function getConfigurations(Console\Input\InputInterface $input): array
{
return [
...$this->getFilesConfigurations($input),
...$this->getOptionConfigurations($input),
];
}
protected function getFilesConfigurations(Console\Input\InputInterface $input): array
{
$configurations = [];
$filePath = $input->getOption('file');
if ($filePath !== null and file_exists($filePath)) {
$json = file_get_contents($filePath);
if (json_validate($json)) {
$configurations = array_map(fn($configArray) => json_encode($configArray), json_decode($json, true));
}
$files = $input->getOption('files');
if ($files === null) {
return $configurations;
}
$configOptions = $input->getOption('configurations');
if ($configOptions !== null) {
$configurations = array_merge($configurations, array_filter($configOptions, fn($config) => json_validate($config)));
foreach ($files as $filePath) {
if (!file_exists($filePath)) {
continue;
}
$configurations = array_merge($configurations, $this->getFileConfigurations($filePath));
}
return $configurations;
}
protected function getFileConfigurations(string $filePath): array
{
$configurations = [];
if (!file_exists($filePath)) {
return $configurations;
}
$json = file_get_contents($filePath);
if (!json_validate($json)) {
return $configurations;
}
$tmp = json_decode($json, true);
foreach ($tmp as $config) {
try {
$configurations []= $this->processConfiguration(json_encode($config));
} catch (Throwable $exception) {
$this->logger->warning($exception->getMessage(), ['exception' => $exception, 'config' => $config]);
}
}
return $configurations;
}
protected function getOptionConfigurations(Console\Input\InputInterface $input): array
{
$configurations = [];
$configOptions = $input->getOption('configurations');
if ($configOptions === null) {
return $configurations;
}
foreach ($configOptions as $config) {
try {
$configurations []= $this->processConfiguration($config);
} catch (Throwable $exception) {
$this->logger->warning($exception->getMessage(), ['exception' => $exception, 'config' => $config]);
}
}
return $configurations;
}
protected function processConfiguration(string $configuration): string
{
$json = json_decode($configuration, true);
if (!array_key_exists('type', $json) and !array_key_exists('configuration', $json)) {
throw new Console\Exception\InvalidArgumentException('Missing type or configuration key in JSON');
}
if (array_key_exists('type', $json)) {
return json_encode($json);
}
return json_encode($json['configuration']);
}
}

0
cli/start_command Normal file → Executable file
View File