Compare commits

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->group('/cartolas', function($app) {
$app->post('/procesar[/]', [Cartolas::class, 'procesar']); $app->post('/procesar[/]', [Cartolas::class, 'procesar']);
$app->post('/importar[/]', [Cartolas::class, 'importar']); $app->post('/importar[/]', [Cartolas::class, 'importar']);
$app->get('/update[/]', [Cartolas::class, 'update']);
}); });
$app->group('/cartola', function($app) { $app->group('/cartola', function($app) {
$app->group('/diaria', 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' 'images'
]); ]);
return (object) $urls; 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) { Incoviba\Middleware\API::class => function(ContainerInterface $container) {
return new Incoviba\Middleware\API( return new Incoviba\Middleware\API(
$container->get(Psr\Http\Message\ResponseFactoryInterface::class), $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(Incoviba\Service\Login::class),
$container->get('permittedPaths'),
$container->get('simplePaths'),
$container->get('API_KEY') $container->get('API_KEY')
); );
} }

View File

@ -139,4 +139,18 @@ class Cartolas extends Controller
} }
return $this->withJson($response, $output); 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\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface; use Psr\Http\Server\RequestHandlerInterface;
use Psr\Log\LoggerInterface;
use Incoviba\Exception\MissingAuthorizationHeader; use Incoviba\Exception\MissingAuthorizationHeader;
use Incoviba\Service; use Incoviba\Service;
class API class API
{ {
public function __construct(protected ResponseFactoryInterface $responseFactory, public function __construct(protected ResponseFactoryInterface $responseFactory,
protected LoggerInterface $logger,
protected Service\API $apiService,
protected Service\Login $loginService, protected Service\Login $loginService,
protected array $permittedPaths,
protected array $simplePaths,
protected string $key) {} protected string $key) {}
public function __invoke(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface public function __invoke(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{ {
try { try {
$key = $this->getKey($request); $key = $this->apiService->getKey($request);
} catch (MissingAuthorizationHeader $exception) { } catch (MissingAuthorizationHeader $exception) {
return $this->responseFactory->createResponse(401); return $this->responseFactory->createResponse(401);
} }
@ -29,16 +34,6 @@ class API
} }
return $this->responseFactory->createResponse(403); 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 protected function validate(ServerRequestInterface $request, $incoming_key): bool
{ {
$selector = null; $selector = null;
@ -61,19 +56,11 @@ class API
protected function noComplexKeyNeeded(ServerRequestInterface $request): bool protected function noComplexKeyNeeded(ServerRequestInterface $request): bool
{ {
$uri = $request->getUri(); $uri = $request->getUri();
$validPaths = [ return in_array($uri->getPath(), $this->simplePaths);
'/api/login',
'/api/login/',
];
return in_array($uri->getPath(), $validPaths);
} }
protected function validPermitted(ServerRequestInterface $request): bool protected function validPermitted(ServerRequestInterface $request): bool
{ {
$uri = $request->getUri(); $uri = $request->getUri();
$validPaths = [ return in_array($uri->getPath(), $this->permittedPaths);
'/api',
'/api/',
];
return in_array($uri->getPath(), $validPaths);
} }
} }

View File

@ -57,6 +57,14 @@ class Cartola extends Ideal\Repository
->where('fecha = ?'); ->where('fecha = ?');
return $this->fetchMany($query, [$fecha->format('Y-m-d')]); 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 public function fetchByCuentaAndFecha(int $cuenta_id, DateTimeInterface $fecha): Model\Contabilidad\Cartola
{ {
$query = $this->connection->getQueryBuilder() $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); 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 public function fetchByCuentaAndFecha(int $cuenta_id, DateTimeInterface $fecha): array
{ {
$query = $this->connection->getQueryBuilder() $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 = [ $cartolaData = [
'cargos' => 0, 'cargos' => 0,
'abonos' => 0, 'abonos' => 0,
'saldo' => end($movimientos)->saldo 'saldo' => last($movimientos)->saldo
]; ];
foreach ($movimientos as $movimiento) { foreach ($movimientos as $movimiento) {
$cartolaData['cargos'] += $movimiento->cargo; $cartolaData['cargos'] += $movimiento->cargo;
$cartolaData['abonos'] += $movimiento->abono; $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); $cartola = $this->cartolaRepository->fetchByCuentaAndFecha($cuenta->id, $fecha);
return compact('cartola', 'movimientos'); return compact('cartola', 'movimientos');
@ -109,6 +109,31 @@ class Cartola extends Service
} }
$this->movimientoService->add($dataMovimiento); $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; $inmobiliaria = $cuenta->inmobiliaria;
return array_map(function($movimiento) use ($inmobiliaria) { return array_map(function($movimiento) use ($inmobiliaria) {
@ -116,6 +141,66 @@ class Cartola extends Service
return $movimiento; return $movimiento;
}, $movimientos); }, $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 protected function getMovimientosDiarios(Model\Contabilidad\Banco $banco, UploadedFileInterface $file): array
{ {

View File

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

View File

@ -1,5 +1,5 @@
<?php <?php
function loadCommands(&$app): void { /*function loadCommands(&$app): void {
$files = new FilesystemIterator($app->getContainer()->get('folders')->commands); $files = new FilesystemIterator($app->getContainer()->get('folders')->commands);
foreach ($files as $file) { foreach ($files as $file) {
if ($file->isDir()) { if ($file->isDir()) {
@ -8,4 +8,6 @@ function loadCommands(&$app): void {
include_once $file->getRealPath(); 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; use Incoviba\Common\Alias\Command;
#[Console\Attribute\AsCommand( #[Console\Attribute\AsCommand(
name: 'comunas' name: 'comunas',
description: 'Obtiene las comunas de una región'
)] )]
class Comunas extends Command 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 <?php
namespace Incoviba\Command; namespace Incoviba\Command;
use Psr\Http\Client\ClientInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console; use Symfony\Component\Console;
use Incoviba\Common\Alias\Command; use Incoviba\Common\Alias\Command;
#[Console\Attribute\AsCommand( #[Console\Attribute\AsCommand(
name: 'run:full' name: 'run:full',
description: 'Ejecuta todos los comandos de la aplicación'
)] )]
class Full extends Command 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) public function execute(Console\Input\InputInterface $input, Console\Output\OutputInterface $output)
{ {
$commands = [ foreach ($this->commandsList as $command) {
'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) {
$cmd = new Console\Input\ArrayInput([ $cmd = new Console\Input\ArrayInput([
'command' => $command 'command' => $command
]); ]);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -47,11 +47,28 @@ class Login
} }
return file_get_contents($this->tokenFilename); 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 public function getKey(string $apiKey, string $separator = 'g'): string
{ {
try { try {
$token = $this->retrieveToken(); $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(); $token = $this->login();
} }
return implode('', [md5($apiKey), $separator, $token]); return implode('', [md5($apiKey), $separator, $token]);