From e3737aba27b7b5caf3133fec371d997e1908ff0c Mon Sep 17 00:00:00 2001 From: Aldarien Date: Fri, 25 Mar 2022 10:11:02 -0300 Subject: [PATCH] Consolidar --- api/common/Controller/Consolidados.php | 53 ++++++++ api/common/Middleware/Consolidar.php | 20 +++ api/common/Service/Consolidar.php | 148 +++++++++++++++++++++++ api/resources/routes/consolidar.php | 4 + api/setup/setups/03_consolidar.php | 8 ++ api/src/Consolidado.php | 51 ++++++++ api/src/Cuenta.php | 61 ++++++---- api/src/Moneda.php | 13 +- api/src/Transaccion.php | 16 +++ console/common/Command/Consolidar.php | 37 ++++++ console/crontab | 24 ++++ console/setup/commands/consolidar.php | 4 + ui/public/assets/scripts/cuentas.show.js | 2 +- 13 files changed, 411 insertions(+), 30 deletions(-) create mode 100644 api/common/Controller/Consolidados.php create mode 100644 api/common/Middleware/Consolidar.php create mode 100644 api/common/Service/Consolidar.php create mode 100644 api/resources/routes/consolidar.php create mode 100644 api/setup/setups/03_consolidar.php create mode 100644 api/src/Consolidado.php create mode 100644 console/common/Command/Consolidar.php create mode 100644 console/crontab create mode 100644 console/setup/commands/consolidar.php diff --git a/api/common/Controller/Consolidados.php b/api/common/Controller/Consolidados.php new file mode 100644 index 0000000..dfff5c8 --- /dev/null +++ b/api/common/Controller/Consolidados.php @@ -0,0 +1,53 @@ +find(Consolidados::class)->where([['cuenta_id' => $cuenta_id]])->many(); + $output = [ + 'consolidados' => array_map(function($item) { + return $item->toArray(); + }, $consolidados) + ]; + return $this->withJson($response, $output); + } + public function add(Request $request, Response $response, Factory $factory): Response { + $input = json_decode($request->getBody()->getContents()); + $output = [ + 'input' => $input, + 'consolidados' => [] + ]; + if (!is_array($input)) { + $input = [$input]; + } + foreach ($input as $data) { + $consolidado = Consolidado::add($factory, $data); + $status = $consolidado->save(); + $output['consolidados'] []= [ + 'consolidado' => $consolidado->toArray(), + 'added' => $status + ]; + } + return $this->withJson($response, $output); + } + public function cli(Request $request, Response $response, Service $service): Response { + try { + if (!$service->isConsolidado()) { + $service->consolidar(); + } + } catch (\Error | \Exception $e) { + error_log($e); + throw $e; + } + return $this->withJson($response); + } +} diff --git a/api/common/Middleware/Consolidar.php b/api/common/Middleware/Consolidar.php new file mode 100644 index 0000000..d3e82b3 --- /dev/null +++ b/api/common/Middleware/Consolidar.php @@ -0,0 +1,20 @@ +service = $service; + } + public function __invoke(Request $request, Handler $handler): Response { + if (!$this->service->isConsolidado()) { + $this->service->consolidar(); + } + return $handler->handle($request); + } +} diff --git a/api/common/Service/Consolidar.php b/api/common/Service/Consolidar.php new file mode 100644 index 0000000..5be69f1 --- /dev/null +++ b/api/common/Service/Consolidar.php @@ -0,0 +1,148 @@ +factory = $factory; + } + protected $cuentas; + public function getCuentas() { + if ($this->cuentas === null) { + $this->cuentas = $this->factory->find(Cuenta::class)->many(); + } + return $this->cuentas; + } + + public function isConsolidado() { + $consolidado = true; + $cuentas = $this->getCuentas(); + if ($cuentas === null) { + return false; + } + foreach ($cuentas as $cuenta) { + $transacciones = $cuenta->hasTransacciones(); + if (!$transacciones) { + continue; + } + $consolidados = $cuenta->hasConsolidados(); + if (!$consolidados and $transacciones) { + $consolidado = false; + break; + } + } + return $consolidado; + } + public function consolidar() { + ini_set('max_execution_time', 60*5); + + foreach ($this->getCuentas() as $cuenta) { + if (!$cuenta->hasTransacciones()) { + continue; + } + $first = $this->getFirst($cuenta); + $last = $this->getLast($cuenta); + for ($current = $first->copy()->startOfMonth(); $current < $last->copy()->addMonthWithoutOverflow()->endOfMonth(); $current = $current->copy()->addMonthWithoutOverflow()) { + $transacciones = $this->getTransacciones($cuenta, $current); + if (count($transacciones) == 0) { + continue; + } + $f = $this->factory; + array_walk($transacciones, function(&$item) use ($cuenta, $f) { + $item->setFactory($f); + $item->valor = $item->transformar($cuenta->moneda()); + }); + $saldo = array_reduce($transacciones, function($sum, $item) { + return $sum + $item->valor; + }); + $data = [ + 'cuenta_id' => $cuenta->id, + 'fecha' => $current->format('Y-m-1'), + 'periodo' => 'P1M', + 'saldo' => $saldo + ]; + $consolidado = $this->factory->create(Consolidado::class, $data); + $consolidado->save(); + } + } + } + public function getFirst(Cuenta $cuenta): ?Carbon { + $first = [ + Model::factory(Transaccion::class) + ->select('fecha') + ->whereEqual('debito_id', $cuenta->id) + ->orderByAsc('fecha') + ->findOne(), + Model::factory(Transaccion::class) + ->select('fecha') + ->whereEqual('credito_id', $cuenta->id) + ->orderByAsc('fecha') + ->findOne() + ]; + if (!$first[0]) { + if (!$first[1]) { + return null; + } + return $first[1]->fecha(); + } + if (!$first[1]) { + return $first[0]->fecha(); + } + if ($first[0]->fecha() < $first[1]->fecha()) { + return $first[0]->fecha(); + } + return $first[1]->fecha(); + } + public function getLast(Cuenta $cuenta): ?Carbon { + $fechas = [ + Model::factory(Transaccion::class) + ->select('fecha') + ->whereEqual('debito_id', $cuenta->id) + ->orderByDesc('fecha') + ->findOne(), + Model::factory(Transaccion::class) + ->select('fecha') + ->whereEqual('credito_id', $cuenta->id) + ->orderByDesc('fecha') + ->findOne() + ]; + if (!$fechas[0]) { + if (!$fechas[1]) { + return null; + } + return $fechas[1]->fecha(); + } + if (!$fechas[1]) { + return $fechas[0]->fecha(); + } + if ($fechas[0]->fecha() > $fechas[1]->fecha()) { + return $fechas[0]->fecha(); + } + return $fechas[1]->fecha(); + } + public function getTransacciones(Cuenta $cuenta, Carbon $fecha) { + $start = $fecha->copy()->startOfMonth(); + $end = $fecha->copy()->endOfMonth(); + $debitos = Model::factory(Transaccion::class) + ->whereEqual('debito_id', $cuenta->id) + ->whereRaw("fecha BETWEEN '{$start->format('Y-m-d')}' AND '{$end->format('Y-m-d')}'") + ->findMany(); + if ($debitos) { + array_walk($debitos, function(&$item) { + $item->valor *= -1; + }); + } + $creditos = Model::factory(Transaccion::class) + ->whereEqual('credito_id', $cuenta->id) + ->whereRaw("fecha BETWEEN '{$start->format('Y-m-d')}' AND '{$end->format('Y-m-d')}'") + ->findMany(); + return array_merge($debitos, $creditos); + } +} diff --git a/api/resources/routes/consolidar.php b/api/resources/routes/consolidar.php new file mode 100644 index 0000000..e49c0ba --- /dev/null +++ b/api/resources/routes/consolidar.php @@ -0,0 +1,4 @@ +get('/consolidar', [Consolidados::class, 'cli']); diff --git a/api/setup/setups/03_consolidar.php b/api/setup/setups/03_consolidar.php new file mode 100644 index 0000000..b7c7bb8 --- /dev/null +++ b/api/setup/setups/03_consolidar.php @@ -0,0 +1,8 @@ + function(Container $container) { + return new \Contabilidad\Common\Service\Consolidar($container->get(\ProVM\Common\Factory\Model::class)); + } +]; diff --git a/api/src/Consolidado.php b/api/src/Consolidado.php new file mode 100644 index 0000000..2c20a72 --- /dev/null +++ b/api/src/Consolidado.php @@ -0,0 +1,51 @@ +cuenta === null) { + $this->cuenta = $this->childOf(Cuenta::class, [Model::SELF_KEY => 'cuenta_id']); + } + return $this->cuenta; + } + public function fecha(DateTimeInterface $fecha = null) { + if ($fecha === null) { + return Carbon::parse($this->fecha); + } + if ($this->periodo()->days > 31) { + $this->fecha = $fecha->format('Y-1-1'); + } else { + $this->fecha = $fecha->format('Y-m-1'); + } + return $this; + } + public function periodo(DateInterval $periodo = null) { + if ($periodo === null) { + return new CarbonInterval($this->periodo); + } + $this->periodo = CarbonInterval::getDateIntervalSpec($periodo); + return $this; + } + public function saldo(DateTimeInterface $fecha = null) { + if ($fecha === null) { + $fecha = $this->fecha(); + } + return $this->cuenta()->moneda()->cambiar($fecha, $this->saldo); + } +} diff --git a/api/src/Cuenta.php b/api/src/Cuenta.php index 46f6d18..7fa3538 100644 --- a/api/src/Cuenta.php +++ b/api/src/Cuenta.php @@ -1,9 +1,7 @@ abonos; } + protected $consolidados; + public function consolidados() { + if ($this->consolidados === null) { + $this->consolidados = $this->parentOf(Consolidado::class, [Model::CHILD_KEY => 'cuenta_id']); + } + return $this->consolidados; + } + public function hasConsolidados(): bool { + return (bool) Model::factory(Consolidado::class) + ->whereEqual('cuenta_id', $this->id) + ->count('id'); + } + public function hasTransacciones(): bool { + return (bool) Model::factory(Transaccion::class) + ->select('transacciones.*') + ->join('cuentas', 'cuentas.id = transacciones.debito_id OR cuentas.id = transacciones.credito_id') + ->whereEqual('cuentas.id', $this->id) + ->count('transacciones.id'); + } protected $transacciones; + protected function parseTransaccion(Transaccion $transaccion) { + $transaccion->setFactory($this->factory); + if ($transaccion->debito_id === $this->id) { + $transaccion->valor = - $transaccion->valor; + } + $transaccion->valor = $transaccion->transformar($this->moneda()); + return $transaccion; + } public function transacciones($limit = null, $start = 0) { $transacciones = Model::factory(Transaccion::class) ->select('transacciones.*') @@ -67,10 +92,7 @@ class Cuenta extends Model { } $transacciones = $transacciones->findMany(); foreach ($transacciones as &$transaccion) { - $transaccion->setFactory($this->factory); - if ($transaccion->debito_id === $this->id) { - $transaccion->valor = - $transaccion->valor; - } + $transaccion = $this->parseTransaccion($transaccion); } return $transacciones; } @@ -87,30 +109,21 @@ class Cuenta extends Model { $transacciones = $transacciones->findMany(); foreach ($transacciones as &$transaccion) { - $transaccion->setFactory($this->factory); - if ($transaccion->desde_id === $this->id) { - $transaccion->valor = - $transaccion->valor; - } + $transaccion = $this->parseTransaccion($transaccion); } return $transacciones; } public function acumulacion(Carbon $date) { - $abonos = Model::factory(Transaccion::class) - ->whereEqual('credito_id', $this->id) - ->whereLt('fecha', $date->format('Y-m-d')) - ->groupBy('credito_id') - ->sum('valor'); - $cargos = Model::factory(Transaccion::class) - ->whereEqual('debito_id', $this->id) - ->whereLt('fecha', $date->format('Y-m-d')) - ->groupBy('debito_id') - ->sum('valor'); - - if (in_array($this->tipo()->descripcion, ['activo', 'banco', 'perdida'])) { - return $abonos - $cargos; + $consolidados = $this->consolidados(); + $saldo = 0; + foreach ($consolidados as $consolidado) { + if ($consolidado->fecha() >= $date) { + continue; + } + $saldo += $consolidado->saldo($date); } - return $cargos - $abonos; + return $saldo; } protected $saldo; public function saldo(Service $service = null, $in_clp = false) { diff --git a/api/src/Moneda.php b/api/src/Moneda.php index 9405f41..f643019 100644 --- a/api/src/Moneda.php +++ b/api/src/Moneda.php @@ -22,19 +22,22 @@ class Moneda extends Model { $this->sufijo ])); } - public function cambio(\DateTime $fecha) { + public function cambio(\DateTime $fecha, Moneda $moneda = null) { + if ($moneda === null) { + $moneda = $this->factory->find(Moneda::class)->one(1); + } $cambio = $this->factory->find(TipoCambio::class) - ->where([['desde_id', $this->id], ['hasta_id', 1], ['fecha', $fecha->format('Y-m-d H:i:s')]]) + ->where([['desde_id', $this->id], ['hasta_id', $moneda->id], ['fecha', $fecha->format('Y-m-d H:i:s')]]) ->one(); if ($cambio === null) { $cambio = $this->factory->find(TipoCambio::class) - ->where([['hasta_id', $this->id], ['desde_id', 1], ['fecha', $fecha->format('Y-m-d H:i:s')]]) + ->where([['hasta_id', $this->id], ['desde_id', $moneda->id], ['fecha', $fecha->format('Y-m-d H:i:s')]]) ->one(); } return $cambio; } - public function cambiar(\DateTime $fecha, float $valor) { - $cambio = $this->cambio($fecha); + public function cambiar(\DateTime $fecha, float $valor, Moneda $moneda = null) { + $cambio = $this->cambio($fecha, $moneda); if (!$cambio) { return $valor; } diff --git a/api/src/Transaccion.php b/api/src/Transaccion.php index 5fca6e8..fb7f71c 100644 --- a/api/src/Transaccion.php +++ b/api/src/Transaccion.php @@ -13,6 +13,7 @@ use ProVM\Common\Alias\Model; * @property string $glosa * @property string $detalle * @property double $valor + * @property Moneda $moneda_id */ class Transaccion extends Model { public static $_table = 'transacciones'; @@ -37,6 +38,21 @@ class Transaccion extends Model { return Carbon::parse($this->fecha); } $this->fecha = $fecha->format('Y-m-d'); + return $this; + } + protected $moneda; + public function moneda() { + if ($this->moneda === null) { + $this->moneda = $this->childOf(Moneda::class, [Model::SELF_KEY => 'moneda_id']); + } + return $this->moneda; + } + + public function transformar(Moneda $moneda = null) { + if (($moneda !== null and $this->moneda()->id === $moneda->id) or ($moneda === null and $this->moneda()->id === 1)) { + return $this->valor; + } + return $this->moneda()->cambiar($this->fecha(), $this->valor, $moneda); } public function toArray(): array { diff --git a/console/common/Command/Consolidar.php b/console/common/Command/Consolidar.php new file mode 100644 index 0000000..d9cedb4 --- /dev/null +++ b/console/common/Command/Consolidar.php @@ -0,0 +1,37 @@ +setClient($client); + } + + public function setClient(ClientInterface $client) { + $this->client = $client; + return $this; + } + public function getClient(): ClientInterface { + return $this->client; + } + + public function execute(InputInterface $input, OutputInterface $output) { + error_log('Starting Consolidar'); + try { + $response = $this->getClient()->get('/consolidar'); + if ($response->getStatusCode() === 200) { + return 0; + } + return $response->getStatusCode(); + } catch (\Exception $e) { + error_log($e); + return $e->getCode(); + } + } +} diff --git a/console/crontab b/console/crontab new file mode 100644 index 0000000..24803bf --- /dev/null +++ b/console/crontab @@ -0,0 +1,24 @@ +# Edit this file to introduce tasks to be run by cron. +# +# Each task to run has to be defined through a single line +# indicating with different fields when the task will be run +# and what command to run for the task +# +# To define the time you can provide concrete values for +# minute (m), hour (h), day of month (dom), month (mon), +# and day of week (dow) or use '*' in these fields (for 'any'). +# +# Notice that tasks will be started based on the cron's system +# daemon's notion of time and timezones. +# +# Output of the crontab jobs (including errors) is sent through +# email to the user the crontab file belongs to (unless redirected). +# +# For example, you can run a backup of all your user accounts +# at 5 a.m every week with: +# 0 5 * * 1 tar -zcf /var/backups/home.tgz /home/ +# +# For more information see the manual pages of crontab(5) and cron(8) +# +# m h dom mon dow command +0 2 1 * * /usr/local/bin/php /app/bin/console consolidar diff --git a/console/setup/commands/consolidar.php b/console/setup/commands/consolidar.php new file mode 100644 index 0000000..87da64b --- /dev/null +++ b/console/setup/commands/consolidar.php @@ -0,0 +1,4 @@ +add(new Consolidar($app->getContainer()->get(\Psr\Http\Client\ClientInterface::class), 'consolidar')); diff --git a/ui/public/assets/scripts/cuentas.show.js b/ui/public/assets/scripts/cuentas.show.js index 5f863ce..adaa54d 100644 --- a/ui/public/assets/scripts/cuentas.show.js +++ b/ui/public/assets/scripts/cuentas.show.js @@ -238,7 +238,7 @@ const transacciones = { const parent = $(this.id) parent.html('') $.each(this.transacciones, (i, el) => { - this.saldo = this.saldo + parseInt(el.valor.valor) * ((el.isIncrement()) ? 1 : -1) + this.saldo = this.saldo + parseInt(el.valor.valor)// * ((el.isIncrement()) ? 1 : -1) parent.append(el.draw({saldo: this.saldo, format: this.intl_format, format_array: this.cuenta.moneda.format, format_call: this.format})) }) },