14 Commits

27 changed files with 311 additions and 49 deletions

View File

@ -4,6 +4,7 @@ use Incoviba\Controller\API\Contabilidad\Cartolas;
$app->group('/cartolas', function($app) {
$app->post('/procesar[/]', [Cartolas::class, 'procesar']);
$app->post('/importar[/]', [Cartolas::class, 'importar']);
$app->get('/update[/]', [Cartolas::class, 'update']);
});
$app->group('/cartola', function($app) {
$app->group('/diaria', function($app) {

View File

@ -0,0 +1,6 @@
<?php
use Incoviba\Controller\API\Tokens;
$app->group('/tokens', function($app) {
$app->get('/try[/]', Tokens::class . ':try');
});

View File

@ -17,5 +17,13 @@ return [
'images'
]);
return (object) $urls;
}
},
'permittedPaths' => [
'/api',
'/api/',
],
'simplePaths' => [
'/api/login',
'/api/login/',
],
];

View File

@ -14,7 +14,11 @@ return [
Incoviba\Middleware\API::class => function(ContainerInterface $container) {
return new Incoviba\Middleware\API(
$container->get(Psr\Http\Message\ResponseFactoryInterface::class),
$container->get(Psr\Log\LoggerInterface::class),
$container->get(Incoviba\Service\API::class),
$container->get(Incoviba\Service\Login::class),
$container->get('permittedPaths'),
$container->get('simplePaths'),
$container->get('API_KEY')
);
}

View File

@ -139,4 +139,18 @@ class Cartolas extends Controller
}
return $this->withJson($response, $output);
}
public function update(ServerRequestInterface $request, ResponseInterface $response,
Service\Contabilidad\Cartola $cartolaService): ResponseInterface
{
$output = [
'cartolas' => []
];
try {
$cuentas = $cartolaService->check();
if (count($cuentas) > 0) {
$output['cartolas'] = $cartolaService->update($cuentas);
}
} catch (EmptyResult) {}
return $this->withJson($response, $output);
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace Incoviba\Controller\API;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Incoviba\Common\Ideal\Controller;
use Incoviba\Common\Implement\Exception\EmptyResult;
use Incoviba\Exception\MissingAuthorizationHeader;
use Incoviba\Service;
class Tokens extends Controller
{
use withJson;
public function try(ServerRequestInterface $request, ResponseInterface $response,
Service\API $apiService,
Service\Login $loginService): ResponseInterface
{
$output = [
'token' => '',
'valid' => false
];
try {
$token = $apiService->getKey($request);
$output['token'] = $token;
if (!str_contains($token, $loginService->getSeparator())) {
throw new EmptyResult('Token not complex');
}
list($key, $selector, $token) = explode($loginService->getSeparator(), $token, 3);
$output['valid'] = $loginService->isIn($selector, $token);
} catch (MissingAuthorizationHeader | EmptyResult) {}
return $this->withJson($response, $output);
}
}

View File

@ -5,19 +5,24 @@ use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Psr\Log\LoggerInterface;
use Incoviba\Exception\MissingAuthorizationHeader;
use Incoviba\Service;
class API
{
public function __construct(protected ResponseFactoryInterface $responseFactory,
protected LoggerInterface $logger,
protected Service\API $apiService,
protected Service\Login $loginService,
protected array $permittedPaths,
protected array $simplePaths,
protected string $key) {}
public function __invoke(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
try {
$key = $this->getKey($request);
$key = $this->apiService->getKey($request);
} catch (MissingAuthorizationHeader $exception) {
return $this->responseFactory->createResponse(401);
}
@ -29,16 +34,6 @@ class API
}
return $this->responseFactory->createResponse(403);
}
protected function getKey(ServerRequestInterface $request): string
{
$auth_headers = $request->getHeader('Authorization');
foreach ($auth_headers as $header) {
if (str_contains($header, 'Bearer')) {
return substr($header, strlen('Bearer '));
}
}
throw new MissingAuthorizationHeader();
}
protected function validate(ServerRequestInterface $request, $incoming_key): bool
{
$selector = null;
@ -61,19 +56,11 @@ class API
protected function noComplexKeyNeeded(ServerRequestInterface $request): bool
{
$uri = $request->getUri();
$validPaths = [
'/api/login',
'/api/login/',
];
return in_array($uri->getPath(), $validPaths);
return in_array($uri->getPath(), $this->simplePaths);
}
protected function validPermitted(ServerRequestInterface $request): bool
{
$uri = $request->getUri();
$validPaths = [
'/api',
'/api/',
];
return in_array($uri->getPath(), $validPaths);
return in_array($uri->getPath(), $this->permittedPaths);
}
}

View File

@ -57,6 +57,14 @@ class Cartola extends Ideal\Repository
->where('fecha = ?');
return $this->fetchMany($query, [$fecha->format('Y-m-d')]);
}
public function fetchByCuenta(int $cuenta_id): array
{
$query = $this->connection->getQueryBuilder()
->select()
->from($this->getTable())
->where('cuenta_id = ?');
return $this->fetchMany($query, [$cuenta_id]);
}
public function fetchByCuentaAndFecha(int $cuenta_id, DateTimeInterface $fecha): Model\Contabilidad\Cartola
{
$query = $this->connection->getQueryBuilder()

View File

@ -54,6 +54,14 @@ class Movimiento extends Ideal\Repository
return $this->update($model, ['cuenta_id', 'fecha', 'glosa', 'documento', 'cargo', 'abono', 'saldo'], $new_data);
}
public function fetchByCuenta(int $cuenta_id): array
{
$query = $this->connection->getQueryBuilder()
->select()
->from($this->getTable())
->where('cuenta_id = ?');
return $this->fetchMany($query, [$cuenta_id]);
}
public function fetchByCuentaAndFecha(int $cuenta_id, DateTimeInterface $fecha): array
{
$query = $this->connection->getQueryBuilder()

23
app/src/Service/API.php Normal file
View File

@ -0,0 +1,23 @@
<?php
namespace Incoviba\Service;
use Psr\Http\Message\ServerRequestInterface;
use Incoviba\Common\Ideal;
use Incoviba\Exception\MissingAuthorizationHeader;
class API extends Ideal\Service
{
/**
* @throws MissingAuthorizationHeader
*/
public function getKey(ServerRequestInterface $request): string
{
$auth_headers = $request->getHeader('Authorization');
foreach ($auth_headers as $header) {
if (str_contains($header, 'Bearer')) {
return substr($header, strlen('Bearer '));
}
}
throw new MissingAuthorizationHeader();
}
}

View File

@ -57,13 +57,13 @@ class Cartola extends Service
$cartolaData = [
'cargos' => 0,
'abonos' => 0,
'saldo' => end($movimientos)->saldo
'saldo' => last($movimientos)->saldo
];
foreach ($movimientos as $movimiento) {
$cartolaData['cargos'] += $movimiento->cargo;
$cartolaData['abonos'] += $movimiento->abono;
}
$this->buildCartola($cuenta, end($movimientos)->fecha, $cartolaData);
$this->buildCartola($cuenta, last($movimientos)->fecha, $cartolaData);
}
$cartola = $this->cartolaRepository->fetchByCuentaAndFecha($cuenta->id, $fecha);
return compact('cartola', 'movimientos');
@ -109,6 +109,31 @@ class Cartola extends Service
}
$this->movimientoService->add($dataMovimiento);
}
$fechas = array_unique(array_map(function($movimiento) {
return $movimiento['fecha']->format('Y-m-d');
}, $movimientos));
foreach ($fechas as $dia) {
try {
$this->cartolaRepository->fetchByCuentaAndFecha($cuenta->id, new DateTimeImmutable($dia));
continue;
} catch (Exception\EmptyResult) {}
$movs = array_filter($movimientos, function($movimiento) use ($dia) {
return $movimiento['fecha']->format('Y-m-d') === $dia;
});
$cargos = array_sum(array_map(function($movimiento) {
return $movimiento['cargo'];
}, $movs));
$abonos = array_sum(array_map(function($movimiento) {
return $movimiento['abono'];
}, $movs));
$saldo = last($movs)['saldo'];
$cartolaData = [
'cargos' => $cargos,
'abonos' => $abonos,
'saldo' => $saldo
];
$this->buildCartola($cuenta, new DateTimeImmutable($dia), $cartolaData);
}
$inmobiliaria = $cuenta->inmobiliaria;
return array_map(function($movimiento) use ($inmobiliaria) {
@ -116,6 +141,66 @@ class Cartola extends Service
return $movimiento;
}, $movimientos);
}
public function check(): array
{
try {
$cuentas = $this->cuentaRepository->fetchAll();
$fechas = [];
foreach ($cuentas as $cuenta) {
$fechas[$cuenta->id] = $this->checkForCuenta($cuenta);
}
return $fechas;
} catch (Exception\EmptyResult) {
return [];
}
}
public function checkForCuenta(Model\Inmobiliaria\Cuenta $cuenta): array
{
$cartolas = $this->cartolaRepository->fetchByCuenta($cuenta->id);
$movimientos = $this->movimientoRepository->fetchByCuenta($cuenta->id);
$fechasMovimientos = array_unique(array_map(function(Model\Contabilidad\Movimiento $movimiento) {
return $movimiento->fecha->format('Y-m-d');
}, $movimientos));
$fechasCartolas = array_map(function(Model\Contabilidad\Cartola $cartola) {
return $cartola->fecha->format('Y-m-d');
}, $cartolas);
$fechas = array_diff($fechasMovimientos, $fechasCartolas);
return array_values($fechas);
}
public function update(array $cuentas): array
{
$cartolas = [];
foreach ($cuentas as $cuenta_id => $fechas) {
$cuenta = $this->cuentaRepository->fetchById($cuenta_id);
$cartolas[$cuenta_id] = $this->updateForCuenta($cuenta, $fechas);
}
return $cartolas;
}
public function updateForCuenta(Model\Inmobiliaria\Cuenta $cuenta, array $fechas): array
{
$cartolas = [];
foreach ($fechas as $fecha) {
$cartolas []= $this->updateForCuentaAndFecha($cuenta, new DateTimeImmutable($fecha));
}
return $cartolas;
}
public function updateForCuentaAndFecha(Model\Inmobiliaria\Cuenta $cuenta, DateTimeInterface $fecha): Model\Contabilidad\Cartola
{
try {
return $this->cartolaRepository->fetchByCuentaAndFecha($cuenta->id, $fecha);
} catch (Exception\EmptyResult) {}
$movimientos = $this->movimientoRepository->fetchByCuentaAndFecha($cuenta->id, $fecha);
$cartolaData = [
'cargos' => array_sum(array_map(function(Model\Contabilidad\Movimiento $movimiento) {
return $movimiento->cargo;
}, $movimientos)),
'abonos' => array_sum(array_map(function(Model\Contabilidad\Movimiento $movimiento) {
return $movimiento->abono;
}, $movimientos)),
'saldo' => last($movimientos)->saldo
];
return $this->buildCartola($cuenta, $fecha, $cartolaData);
}
protected function getMovimientosDiarios(Model\Contabilidad\Banco $banco, UploadedFileInterface $file): array
{

View File

@ -19,6 +19,10 @@ class OfficeBanking extends Banco
}
$xlsx = $reader->load($filename);
$sheet = $xlsx->getActiveSheet();
$subtitle = $sheet->getCell('A1')->getCalculatedValue();
if ($subtitle === 'Consulta de movimientos de Cuentas Corrientes') {
return true;
}
$subtitle = $sheet->getCell('A2')->getCalculatedValue();
return $subtitle === 'Consulta de movimientos de Cuentas Corrientes';
}

View File

@ -1,5 +1,5 @@
<?php
function loadCommands(&$app): void {
/*function loadCommands(&$app): void {
$files = new FilesystemIterator($app->getContainer()->get('folders')->commands);
foreach ($files as $file) {
if ($file->isDir()) {
@ -8,4 +8,6 @@ function loadCommands(&$app): void {
include_once $file->getRealPath();
}
}
loadCommands($app);
loadCommands($app);*/
$app->setCommandLoader($app->getContainer()->get(Symfony\Component\Console\CommandLoader\CommandLoaderInterface::class));
$app->setDefaultCommand('run:full');

View File

@ -0,0 +1,18 @@
<?php
return [
'commands' => function() {
return [
'comunas' => Incoviba\Command\Comunas::class,
'contabilidad:cartolas:update' => Incoviba\Command\Contabilidad\Cartolas\Update::class,
'money:ipc' => Incoviba\Command\Money\IPC::class,
'money:uf' => Incoviba\Command\Money\UF::class,
'money:uf:update' => Incoviba\Command\Money\UF\Update::class,
'proyectos:activos' => Incoviba\Command\Proyectos\Activos::class,
'run:full' => Incoviba\Command\Full::class,
'ventas:cierres:vigentes' => Incoviba\Command\Ventas\Cierres\Vigentes::class,
'ventas:cuotas:hoy' => Incoviba\Command\Ventas\Cuotas\Hoy::class,
'ventas:cuotas:pendientes' => Incoviba\Command\Ventas\Cuotas\Pendientes::class,
'ventas:cuotas:vencer' => Incoviba\Command\Ventas\Cuotas\PorVencer::class,
];
}
];

View File

@ -0,0 +1,15 @@
<?php
use Psr\Container\ContainerInterface;
return [
Symfony\Component\Console\CommandLoader\CommandLoaderInterface::class => function(ContainerInterface $container) {
return new Symfony\Component\Console\CommandLoader\ContainerCommandLoader($container, $container->get('commands'));
},
Incoviba\Command\Full::class => function(ContainerInterface $container) {
return new Incoviba\Command\Full(
$container->get(Psr\Http\Client\ClientInterface::class),
$container->get(Psr\Log\LoggerInterface::class),
$container->get('commands')
);
}
];

View File

@ -5,7 +5,8 @@ use Symfony\Component\Console;
use Incoviba\Common\Alias\Command;
#[Console\Attribute\AsCommand(
name: 'comunas'
name: 'comunas',
description: 'Obtiene las comunas de una región'
)]
class Comunas extends Command
{

View File

@ -0,0 +1,22 @@
<?php
namespace Incoviba\Command\Contabilidad\Cartolas;
use Symfony\Component\Console;
use Incoviba\Common\Alias\Command;
#[Console\Attribute\AsCommand(
name: 'contabilidad:cartolas:update',
description: 'Actualiza las cartolas faltante de los bancos de acuerdo a los movimientos ingresados'
)]
class Update extends Command
{
public function execute(Console\Input\InputInterface $input, Console\Output\OutputInterface $output)
{
$this->logger->debug("Running {$this->getName()}");
$uri = '/api/contabilidad/cartolas/update';
$output->writeln("GET {$uri}");
$response = $this->client->get($uri);
$output->writeln("Response Code: {$response->getStatusCode()}");
return Console\Command\Command::SUCCESS;
}
}

View File

@ -1,28 +1,25 @@
<?php
namespace Incoviba\Command;
use Psr\Http\Client\ClientInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console;
use Incoviba\Common\Alias\Command;
#[Console\Attribute\AsCommand(
name: 'run:full'
name: 'run:full',
description: 'Ejecuta todos los comandos de la aplicación'
)]
class Full extends Command
{
public function __construct(ClientInterface $client, LoggerInterface $logger, protected array $commandsList, string $name = null)
{
parent::__construct($client, $logger, $name);
}
public function execute(Console\Input\InputInterface $input, Console\Output\OutputInterface $output)
{
$commands = [
'comunas',
'money:ipc',
'money:uf',
'money:uf:update',
'proyectos:activos',
'ventas:cierres:vigentes',
'ventas:cuotas:hoy',
'ventas:cuotas:pendientes',
'ventas:cuotas:vencer'
];
foreach ($commands as $command) {
foreach ($this->commandsList as $command) {
$cmd = new Console\Input\ArrayInput([
'command' => $command
]);

View File

@ -7,7 +7,8 @@ use Symfony\Component\Console;
use Incoviba\Common\Alias\Command;
#[Console\Attribute\AsCommand(
name: 'money:ipc'
name: 'money:ipc',
description: 'Fetch IPC data from the API'
)]
class IPC extends Command
{

View File

@ -6,7 +6,8 @@ use Symfony\Component\Console;
use Incoviba\Common\Alias\Command;
#[Console\Attribute\AsCommand(
name: 'money:uf'
name: 'money:uf',
description: 'Get the UF value for today'
)]
class UF extends Command
{

View File

@ -6,7 +6,8 @@ use Symfony\Component\Console;
use Incoviba\Common\Alias\Command;
#[Console\Attribute\AsCommand(
name: 'money:uf:update'
name: 'money:uf:update',
description: 'Update UF value'
)]
class Update extends Command
{

View File

@ -5,7 +5,8 @@ use Symfony\Component\Console;
use Incoviba\Common\Alias\Command;
#[Console\Attribute\AsCommand(
name: 'proyectos:activos'
name: 'proyectos:activos',
description: 'Obtiene los proyectos activos'
)]
class Activos extends Command
{

View File

@ -5,7 +5,8 @@ use Symfony\Component\Console;
use Incoviba\Common\Alias\Command;
#[Console\Attribute\AsCommand(
name: 'ventas:cierres:vigentes'
name: 'ventas:cierres:vigentes',
description: 'Obtiene los cierres de ventas vigentes'
)]
class Vigentes extends Command
{

View File

@ -5,7 +5,8 @@ use Symfony\Component\Console;
use Incoviba\Common\Alias\Command;
#[Console\Attribute\AsCommand(
name: 'ventas:cuotas:hoy'
name: 'ventas:cuotas:hoy',
description: 'Obtiene las cuotas de ventas que vencen hoy'
)]
class Hoy extends Command
{

View File

@ -5,7 +5,8 @@ use Symfony\Component\Console;
use Incoviba\Common\Alias\Command;
#[Console\Attribute\AsCommand(
name: 'ventas:cuotas:pendientes'
name: 'ventas:cuotas:pendientes',
description: 'Obtiene las cuotas pendientes de pago'
)]
class Pendientes extends Command
{

View File

@ -5,7 +5,8 @@ use Symfony\Component\Console;
use Incoviba\Common\Alias\Command;
#[Console\Attribute\AsCommand(
name: 'ventas:cuotas:vencer'
name: 'ventas:cuotas:vencer',
description: 'Obtiene las cuotas por vencer'
)]
class PorVencer extends Command
{

View File

@ -47,11 +47,28 @@ class Login
}
return file_get_contents($this->tokenFilename);
}
public function tryToken(string $token): bool
{
$url = '/api/tokens/try';
try {
$response = $this->client->request('GET', $url, [
'headers' => ['Authorization' => "Bearer {$token}"]
]);
} catch (ClientExceptionInterface $exception) {
$this->logger->error($exception);
return false;
}
return $response->getStatusCode() === 200;
}
public function getKey(string $apiKey, string $separator = 'g'): string
{
try {
$token = $this->retrieveToken();
} catch (Exception) {
if (!$this->tryToken(implode('', [md5($apiKey), $separator, $token]))) {
throw new Exception('Token not valid');
}
} catch (Exception $exception) {
$this->logger->error($exception);
$token = $this->login();
}
return implode('', [md5($apiKey), $separator, $token]);