From 8ef4ab1c7df96fa60b26481c69f8dde533eba115 Mon Sep 17 00:00:00 2001 From: Aldarien Date: Mon, 6 Dec 2021 22:10:41 -0300 Subject: [PATCH] API --- api/common/Alias/DocumentHandler.php | 6 + api/common/Concept/DocumentHandler.php | 11 ++ api/common/Controller/Base.php | 13 ++ api/common/Controller/Categorias.php | 31 +++- api/common/Controller/Cuentas.php | 34 ++++- api/common/Controller/Import.php | 6 +- api/common/Controller/Monedas.php | 71 +++++++++ api/common/Controller/TiposCambios.php | 95 ++++++++++++ api/common/Controller/TiposCategorias.php | 139 ++++++++++++++++++ api/common/Controller/TiposCuentas.php | 71 +++++++++ api/common/Middleware/Auth.php | 29 ++++ api/common/Service/Auth.php | 49 ++++++ api/common/Service/CsvHandler.php | 37 +++++ api/common/Service/DocumentHandler.php | 16 ++ api/common/Service/PdfHandler.php | 53 ++++++- api/common/Service/TiposCambios.php | 76 ++++++++++ api/common/Service/XlsHandler.php | 60 ++++++++ api/composer.json | 4 +- .../migrations/20211029150754_tipo_cuenta.php | 1 + api/db/migrations/20211204205950_moneda.php | 29 ++++ .../20211204210207_cuenta_moneda.php | 26 ++++ .../migrations/20211205002439_tipo_cambio.php | 30 ++++ api/db/seeds/Moneda.php | 41 ++++++ api/resources/routes/base.php | 2 + api/resources/routes/monedas.php | 12 ++ api/resources/routes/tipos.php | 16 ++ api/resources/routes/tipos/cambios.php | 13 ++ api/resources/routes/tipos/categorias.php | 13 ++ api/resources/routes/tipos/cuentas.php | 12 ++ api/setup/app.php | 2 +- api/setup/middlewares/01_auth.php | 4 + api/setup/settings/01_env.php | 5 +- api/setup/settings/02_common.php | 8 + api/setup/setups/02_common.php | 33 ++++- api/src/Categoria.php | 68 +++++++-- api/src/Cuenta.php | 57 +++++-- api/src/Moneda.php | 46 ++++++ api/src/TipoCambio.php | 50 +++++++ api/src/TipoCategoria.php | 32 ++++ api/src/TipoCuenta.php | 5 +- api/src/Transaccion.php | 7 +- 41 files changed, 1256 insertions(+), 57 deletions(-) create mode 100644 api/common/Alias/DocumentHandler.php create mode 100644 api/common/Concept/DocumentHandler.php create mode 100644 api/common/Controller/Monedas.php create mode 100644 api/common/Controller/TiposCambios.php create mode 100644 api/common/Controller/TiposCategorias.php create mode 100644 api/common/Controller/TiposCuentas.php create mode 100644 api/common/Middleware/Auth.php create mode 100644 api/common/Service/Auth.php create mode 100644 api/common/Service/CsvHandler.php create mode 100644 api/common/Service/DocumentHandler.php create mode 100644 api/common/Service/TiposCambios.php create mode 100644 api/common/Service/XlsHandler.php create mode 100644 api/db/migrations/20211204205950_moneda.php create mode 100644 api/db/migrations/20211204210207_cuenta_moneda.php create mode 100644 api/db/migrations/20211205002439_tipo_cambio.php create mode 100644 api/db/seeds/Moneda.php create mode 100644 api/resources/routes/monedas.php create mode 100644 api/resources/routes/tipos.php create mode 100644 api/resources/routes/tipos/cambios.php create mode 100644 api/resources/routes/tipos/categorias.php create mode 100644 api/resources/routes/tipos/cuentas.php create mode 100644 api/setup/middlewares/01_auth.php create mode 100644 api/src/Moneda.php create mode 100644 api/src/TipoCambio.php diff --git a/api/common/Alias/DocumentHandler.php b/api/common/Alias/DocumentHandler.php new file mode 100644 index 0000000..24306e6 --- /dev/null +++ b/api/common/Alias/DocumentHandler.php @@ -0,0 +1,6 @@ +folder = $source_folder; + } +} \ No newline at end of file diff --git a/api/common/Controller/Base.php b/api/common/Controller/Base.php index c2b299d..ea2e716 100644 --- a/api/common/Controller/Base.php +++ b/api/common/Controller/Base.php @@ -11,4 +11,17 @@ class Base { public function __invoke(Request $request, Response $response): Response { return $this->withJson($response, []); } + public function generate_key(Request $request, Response $response): Response { + $server_addr = explode('.', $request->getServerParams()['SERVER_ADDR']); + $remote_addr = explode('.', $request->getServerParams()['REMOTE_ADDR']); + for ($i = 0; $i < 3; $i ++) { + if ($server_addr[$i] != $remote_addr[$i]) { + throw new \InvalidArgumentException('Invalid connection address.'); + } + } + $salt = mt_rand(); + $signature = hash_hmac('sha256', $salt, 'contabilidad', true); + $key = urlencode(base64_encode($signature)); + return $this->withJson($response, ['key' => $key]); + } } diff --git a/api/common/Controller/Categorias.php b/api/common/Controller/Categorias.php index 7809ec7..2da6758 100644 --- a/api/common/Controller/Categorias.php +++ b/api/common/Controller/Categorias.php @@ -5,13 +5,34 @@ use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Message\ResponseInterface as Response; use ProVM\Common\Define\Controller\Json; use ProVM\Common\Factory\Model as Factory; +use Contabilidad\Common\Service\TiposCambios as Service; use Contabilidad\Categoria; class Categorias { use Json; - public function __invoke(Request $request, Response $response, Factory $factory): Response { - $categorias = $factory->find(Categoria::class)->array(); + public function __invoke(Request $request, Response $response, Factory $factory, Service $service): Response { + $categorias = $factory->find(Categoria::class)->many(); + array_walk($categorias, function(&$item) use ($service) { + $arr = $item->toArray(); + $arr['cuentas'] = array_map(function($item) { + return $item->toArray(); + }, $item->cuentas()); + $maps = ['activo', 'pasivo', 'ganancia', 'perdida']; + foreach ($maps as $m) { + $p = $m . 's'; + $t = ucfirst($m); + $cuentas = $item->getCuentasOf($t); + if ($cuentas === false or $cuentas === null) { + $arr[$p] = 0; + continue; + } + $arr[$p] = array_reduce($cuentas, function($sum, $item) use($service) { + return $sum + $item->saldo($service, true); + }); + } + $item = $arr; + }); if ($categorias) { usort($categorias, function($a, $b) { return strcmp($a['nombre'], $b['nombre']); @@ -68,14 +89,14 @@ class Categorias { ]; return $this->withJson($response, $output); } - public function cuentas(Request $request, Response $response, Factory $factory, $categoria_id): Response { + public function cuentas(Request $request, Response $response, Factory $factory, Service $service, $categoria_id): Response { $categoria = $factory->find(Categoria::class)->one($categoria_id); $cuentas = null; if ($categoria !== null) { $cuentas = $categoria->cuentas(); if ($cuentas !== null) { - array_walk($cuentas, function(&$item) { - $item = $item->toArray(); + array_walk($cuentas, function(&$item) use ($service) { + $item = $item->toArray($service); }); } } diff --git a/api/common/Controller/Cuentas.php b/api/common/Controller/Cuentas.php index 1bd9bd2..43d3fea 100644 --- a/api/common/Controller/Cuentas.php +++ b/api/common/Controller/Cuentas.php @@ -5,6 +5,7 @@ use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Message\ResponseInterface as Response; use ProVM\Common\Define\Controller\Json; use ProVM\Common\Factory\Model as Factory; +use Contabilidad\Common\Service\TiposCambios as Service; use Contabilidad\Cuenta; class Cuentas { @@ -14,11 +15,15 @@ class Cuentas { $cuentas = $factory->find(Cuenta::class)->array(); if ($cuentas) { usort($cuentas, function($a, $b) { + $t = strcmp($a['tipo']['descripcion'], $b['tipo']['descripcion']); + if ($t != 0) { + return $t; + } $c = strcmp($a['categoria']['nombre'], $b['categoria']['nombre']); - if ($c == 0) { - return strcmp($a['nombre'], $b['nombre']); + if ($c != 0) { + return $c; } - return $c; + return strcmp($a['nombre'], $b['nombre']); }); } $output = [ @@ -90,15 +95,28 @@ class Cuentas { ]; return $this->withJson($response, $output); } - public function transacciones(Request $request, Response $response, Factory $factory, $cuenta_id, $limit = null, $start = 0): Response { + public function transacciones(Request $request, Response $response, Factory $factory, Service $service, $cuenta_id, $limit = null, $start = 0): Response { $cuenta = $factory->find(Cuenta::class)->one($cuenta_id); $transacciones = null; if ($cuenta !== null) { $transacciones = $cuenta->transacciones($limit, $start); - if (count($transacciones)) { - array_walk($transacciones, function(&$item) { - $item = $item->toArray(); - }); + if (count($transacciones) > 0) { + foreach ($transacciones as &$transaccion) { + $arr = $transaccion->toArray(); + if ($cuenta->moneda()->codigo === 'CLP') { + if ($transaccion->debito()->moneda()->codigo !== 'CLP' or $transaccion->credito()->moneda()->codigo !== 'CLP') { + if ($transaccion->debito()->moneda()->codigo !== 'CLP') { + $c = $transaccion->debito(); + } else { + $c = $transaccion->credito(); + } + $service->get($transaccion->fecha(), $c->moneda()->id); + $arr['valor'] = $c->moneda()->cambiar($transaccion->fecha(), $transaccion->valor); + $arr['valorFormateado'] = $cuenta->moneda()->format($arr['valor']); + } + } + $transaccion = $arr; + } } } $output = [ diff --git a/api/common/Controller/Import.php b/api/common/Controller/Import.php index 2af3056..17dee20 100644 --- a/api/common/Controller/Import.php +++ b/api/common/Controller/Import.php @@ -5,7 +5,7 @@ use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Message\ResponseInterface as Response; use ProVM\Common\Define\Controller\Json; use ProVM\Common\Factory\Model as Factory; -use Contabilidad\Common\Service\PdfHandler; +use Contabilidad\Common\Service\DocumentHandler as Handler; class Import { use Json; @@ -14,8 +14,8 @@ class Import { $post = $request->getParsedBody(); return $this->withJson($response, $post); } - public function uploads(Request $request, Response $response, PdfHandler $handler): Response { - $output = $handler->load(); + public function uploads(Request $request, Response $response, Handler $handler): Response { + $output = $handler->handle(); return $this->withJson($response, $output); } } diff --git a/api/common/Controller/Monedas.php b/api/common/Controller/Monedas.php new file mode 100644 index 0000000..a298fd8 --- /dev/null +++ b/api/common/Controller/Monedas.php @@ -0,0 +1,71 @@ +find(Moneda::class)->array(); + if ($monedas) { + usort($monedas, function($a, $b) { + return strcmp($a['denominacion'], $b['denominacion']); + }); + } + $output = [ + 'monedas' => $monedas + ]; + return $this->withJson($response, $output); + } + public function show(Request $request, Response $response, Factory $factory, $moneda_id): Response { + $moneda = $factory->find(Moneda::class)->one($moneda_id); + $output = [ + 'input' => $moneda_id, + 'moneda' => $moneda?->toArray() + ]; + return $this->withJson($response, $output); + } + public function add(Request $request, Response $response, Factory $factory): Response { + $input = json_decode($request->getBody()); + $results = []; + if (is_array($input)) { + foreach ($input as $in) { + $moneda = Moneda::add($factory, $in); + $results []= ['moneda' => $moneda?->toArray(), 'agregado' => $moneda?->save()]; + } + } else { + $moneda = Moneda::add($factory, $input); + $results []= ['moneda' => $moneda?->toArray(), 'agregado' => $moneda?->save()]; + } + $output = [ + 'input' => $input, + 'monedas' => $results + ]; + return $this->withJson($response, $output); + } + public function edit(Request $request, Response $response, Factory $factory, $moneda_id): Response { + $moneda = $factory->find(Moneda::class)->one($moneda_id); + $output = [ + 'input' => $moneda_id, + 'old' => $moneda->toArray() + ]; + $input = json_decode($request->getBody()); + $moneda->edit($input); + $output['moneda'] = $moneda->toArray(); + return $this->withJson($response, $output); + } + public function delete(Request $request, Response $response, Factory $factory, $moneda_id): Response { + $moneda = $factory->find(Moneda::class)->one($moneda_id); + $output = [ + 'input' => $moneda_id, + 'moneda' => $moneda->toArray(), + 'eliminado' => $moneda->delete() + ]; + return $this->withJson($response, $output); + } +} diff --git a/api/common/Controller/TiposCambios.php b/api/common/Controller/TiposCambios.php new file mode 100644 index 0000000..131cb06 --- /dev/null +++ b/api/common/Controller/TiposCambios.php @@ -0,0 +1,95 @@ +find(TipoCambio::class)->array(); + if ($tipos) { + usort($tipos, function($a, $b) { + return strcmp($a['fecha'], $b['fecha']); + }); + } + $output = [ + 'tipos' => $tipos + ]; + return $this->withJson($response, $output); + } + public function show(Request $request, Response $response, Factory $factory, $tipo_id): Response { + $tipo = $factory->find(TipoCambio::class)->one($tipo_id); + $output = [ + 'input' => $tipo_id, + 'tipo' => $tipo?->toArray() + ]; + return $this->withJson($response, $output); + } + public function add(Request $request, Response $response, Factory $factory): Response { + $input = json_decode($request->getBody()); + $results = []; + if (is_array($input)) { + foreach ($input as $in) { + $tipo = TipoCambio::add($factory, $in); + $results []= ['tipo' => $tipo?->toArray(), 'agregado' => $tipo?->save()]; + } + } else { + $tipo = TipoCambio::add($factory, $input); + $results []= ['tipo' => $tipo?->toArray(), 'agregado' => $tipo?->save()]; + } + $output = [ + 'input' => $input, + 'tipos' => $results + ]; + return $this->withJson($response, $output); + } + public function edit(Request $request, Response $response, Factory $factory, $tipo_id): Response { + $tipo = $factory->find(TipoCambio::class)->one($tipo_id); + $output = [ + 'input' => $tipo_id, + 'old' => $tipo->toArray() + ]; + $input = json_decode($request->getBody()); + $tipo->edit($input); + $output['tipo'] = $tipo->toArray(); + return $this->withJson($response, $output); + } + public function delete(Request $request, Response $response, Factory $factory, $tipo_id): Response { + $tipo = $factory->find(TipoCambio::class)->one($tipo_id); + $output = [ + 'input' => $tipo_id, + 'tipo' => $tipo->toArray(), + 'eliminado' => $tipo->delete() + ]; + return $this->withJson($response, $output); + } + public function obtain(Request $request, Response $response, Factory $factory, Service $service): Response { + $post = $request->getParsedBody(); + $valor = $service->get($post['fecha'], $post['moneda_id']); + if ($valor === null) { + return $this->withJson($response, ['input' => $post, 'tipo' => null, 'error' => 'No se encontró valor']); + } + $data = [ + 'fecha' => $post['fecha'], + 'desde_id' => $post['moneda_id'], + 'hasta_id' => 1, + 'valor' => $valor + ]; + $tipo = TipoCambio::add($factory, $data); + if ($tipo !== false and $tipo->is_new()) { + $tipo->save(); + } + $output = [ + 'input' => $post, + 'tipo' => $tipo?->toArray() + ]; + return $this->withJson($response, $output); + } +} diff --git a/api/common/Controller/TiposCategorias.php b/api/common/Controller/TiposCategorias.php new file mode 100644 index 0000000..fa3b113 --- /dev/null +++ b/api/common/Controller/TiposCategorias.php @@ -0,0 +1,139 @@ +find(TipoCategoria::class)->many(); + array_walk($tipos, function(&$item) use ($service) { + $arr = $item->toArray(); + $arr['categorias'] = array_map(function($item) { + return $item->toArray(); + }, $item->categorias()); + $arr['saldo'] = abs($item->saldo($service)); + $maps = ['activo', 'pasivo', 'ganancia', 'perdida']; + foreach ($maps as $m) { + $p = $m . 's'; + $t = ucfirst($m); + $cuentas = $item->getCuentasOf($t); + if ($cuentas === false or $cuentas === null) { + $arr[$p] = 0; + continue; + } + $arr[$p] = array_reduce($cuentas, function($sum, $item) use($service) { + return $sum + $item->saldo($service, true); + }); + } + $item = $arr; + }); + if ($tipos) { + usort($tipos, function($a, $b) { + return strcmp($a['descripcion'], $b['descripcion']); + }); + } + $output = [ + 'tipos' => $tipos + ]; + return $this->withJson($response, $output); + } + public function add(Request $request, Response $response, Factory $factory): Response { + $input = json_decode($request->getBody()); + $results = []; + if (is_array($input)) { + foreach ($input as $in) { + $tipo = TipoCategoria::add($factory, $in); + $results []= ['tipo' => $tipo?->toArray(), 'agregado' => $tipo?->save()]; + } + } else { + $tipo = TipoCategoria::add($factory, $input); + $results []= ['tipo' => $tipo?->toArray(), 'agregado' => $tipo?->save()]; + } + $output = [ + 'input' => $input, + 'tipos' => $results + ]; + return $this->withJson($response, $output); + } + public function edit(Request $request, Response $response, Factory $factory, $tipo_id): Response { + $tipo = $factory->find(TipoCategoria::class)->one($tipo_id); + $output = [ + 'input' => $tipo_id, + 'old' => $tipo->toArray() + ]; + $input = json_decode($request->getBody()); + $tipo->edit($input); + $output['tipo'] = $tipo->toArray(); + return $this->withJson($response, $output); + } + public function delete(Request $request, Response $response, Factory $factory, $tipo_id): Response { + $tipo = $factory->find(TipoCategoria::class)->one($tipo_id); + $output = [ + 'input' => $tipo_id, + 'tipo' => $tipo->toArray(), + 'eliminado' => $tipo->delete() + ]; + return $this->withJson($response, $output); + } + public function categorias(Request $request, Response $response, Factory $factory, Service $service, $tipo_id): Response { + $tipo = $factory->find(TipoCategoria::class)->one($tipo_id); + $categorias = null; + if ($tipo != null) { + $categorias = $tipo->categorias(); + if ($categorias !== null) { + array_walk($categorias, function(&$item) use ($service) { + $arr = $item->toArray($service); + $maps = ['activo', 'pasivo', 'ganancia', 'perdida']; + foreach ($maps as $m) { + $p = $m . 's'; + $t = ucfirst($m); + $cuentas = $item->getCuentasOf($t); + if ($cuentas === false or $cuentas === null) { + $arr[$p] = 0; + continue; + } + $arr[$p] = array_reduce($cuentas, function($sum, $item) use($service) { + return $sum + $item->saldo($service, true); + }); + } + $item = $arr; + }); + } + } + $output = [ + 'input' => $tipo_id, + 'tipo' => $tipo?->toArray(), + 'categorias' => $categorias + ]; + return $this->withJson($response, $output); + } + public function balance(Request $request, Response $response, Factory $factory, Service $service): Response { + $tipos = $factory->find(TipoCategoria::class)->many(); + $balance = array_reduce($tipos, function($sum, $item) use ($service) { + $maps = ['activo', 'pasivo', 'ganancia', 'perdida']; + foreach ($maps as $m) { + $p = $m . 's'; + $t = ucfirst($m); + if (!isset($sum[$p])) { + $sum[$p] = 0; + } + $cuentas = $item->getCuentasOf($t); + if ($cuentas === false or $cuentas === null) { + continue; + } + $sum[$p] += array_reduce($cuentas, function($sum, $item) use($service) { + return $sum + $item->saldo($service, true); + }); + } + return $sum; + }); + return $this->withJson($response, $balance); + } +} diff --git a/api/common/Controller/TiposCuentas.php b/api/common/Controller/TiposCuentas.php new file mode 100644 index 0000000..a0739c9 --- /dev/null +++ b/api/common/Controller/TiposCuentas.php @@ -0,0 +1,71 @@ +find(TipoCuenta::class)->array(); + if ($tipos) { + usort($tipos, function($a, $b) { + return strcmp($a['descripcion'], $b['descripcion']); + }); + } + $output = [ + 'tipos' => $tipos + ]; + return $this->withJson($response, $output); + } + public function show(Request $request, Response $response, Factory $factory, $tipo_id): Response { + $tipo = $factory->find(TipoCuenta::class)->one($tipo_id); + $output = [ + 'input' => $tipo_id, + 'tipo' => $tipo?->toArray() + ]; + return $this->withJson($response, $output); + } + public function add(Request $request, Response $response, Factory $factory): Response { + $input = json_decode($request->getBody()); + $results = []; + if (is_array($input)) { + foreach ($input as $in) { + $tipo = TipoCuenta::add($factory, $in); + $results []= ['tipo' => $tipo?->toArray(), 'agregado' => $tipo?->save()]; + } + } else { + $tipo = TipoCuenta::add($factory, $input); + $results []= ['tipo' => $tipo?->toArray(), 'agregado' => $tipo?->save()]; + } + $output = [ + 'input' => $input, + 'tipos' => $results + ]; + return $this->withJson($response, $output); + } + public function edit(Request $request, Response $response, Factory $factory, $tipo_id): Response { + $tipo = $factory->find(TipoCuenta::class)->one($tipo_id); + $output = [ + 'input' => $tipo_id, + 'old' => $tipo->toArray() + ]; + $input = json_decode($request->getBody()); + $tipo->edit($input); + $output['tipo'] = $tipo->toArray(); + return $this->withJson($response, $output); + } + public function delete(Request $request, Response $response, Factory $factory, $tipo_id): Response { + $tipo = $factory->find(TipoCuenta::class)->one($tipo_id); + $output = [ + 'input' => $tipo_id, + 'tipo' => $tipo->toArray(), + 'eliminado' => $tipo->delete() + ]; + return $this->withJson($response, $output); + } +} diff --git a/api/common/Middleware/Auth.php b/api/common/Middleware/Auth.php new file mode 100644 index 0000000..b19cd4c --- /dev/null +++ b/api/common/Middleware/Auth.php @@ -0,0 +1,29 @@ +factory = $factory; + $this->service = $service; + } + public function __invoke(Request $request, Handler $handler): Response { + if ($request->getMethod() == 'OPTIONS') { + return $handler->handle($request); + } + if (!$this->service->isValid($request)) { + $response = $this->factory->createResponse(401); + $response->getBody()->write(json_encode(['message' => 'Invalid API KEY.'])); + return $response + ->withHeader('Content-Type', 'application/json'); + } + return $handler->handle($request); + } +} diff --git a/api/common/Service/Auth.php b/api/common/Service/Auth.php new file mode 100644 index 0000000..5a8da22 --- /dev/null +++ b/api/common/Service/Auth.php @@ -0,0 +1,49 @@ +key = $api_key; + } + public function isValid(Request $request): bool { + if ($request->hasHeader('Authorization')) { + $sent_key = $this->getAuthKey($request->getHeader('Authorization')); + return $this->key == $sent_key; + } + if (isset($request->getParsedBody()['api_key'])) { + $sent_key = $request->getParsedBody()['api_key']; + return $this->key == $sent_key; + } + $post = $request->getParsedBody() ?? json_decode($request->getBody()); + $sent_key = $this->getArrayKey($post); + if ($sent_key !== null) { + return $this->key == $sent_key; + } + $sent_key = $this->getArrayKey($request->getQueryParams()); + return $this->key == $sent_key; + } + protected function getAuthKey($auth) { + if (is_array($auth)) { + $auth = $auth[0]; + } + if (str_contains($auth, 'Bearer')) { + $auth = explode(' ', $auth)[1]; + } + return $auth; + } + protected function getArrayKey($array) { + $posible_keys = [ + 'API_KEY', + 'api_key', + ]; + foreach ($posible_keys as $key) { + if (isset($array[$key])) { + return $array[$key]; + } + } + return null; + } +} diff --git a/api/common/Service/CsvHandler.php b/api/common/Service/CsvHandler.php new file mode 100644 index 0000000..490e75a --- /dev/null +++ b/api/common/Service/CsvHandler.php @@ -0,0 +1,37 @@ +folder; + $files = new \DirectoryIterator($folder); + $output = []; + foreach ($files as $file) { + if ($file->isDir() or $file->getExtension() != 'csv') { + continue; + } + $bank = 'unknown'; + $text = trim(file_get_contents($file->getRealPath())); + if (str_contains($text, 'SCOTIABANK')) { + $bank = 'Scotiabank'; + } + if (str_contains($text, 'BICE')) { + $bank = 'BICE'; + } + $data = explode(PHP_EOL, $text); + array_walk($data, function(&$item) { + $item = trim($item, '; '); + if (str_contains($item, ';') !== false) { + $item = explode(';', $item); + } + }); + $output []= ['bank' => $bank, 'filename' => $file->getBasename(), 'data' => $data]; + } + return $this->build($output); + } + protected function build(array $data): ?array { + return $data; + } +} \ No newline at end of file diff --git a/api/common/Service/DocumentHandler.php b/api/common/Service/DocumentHandler.php new file mode 100644 index 0000000..8c0e22c --- /dev/null +++ b/api/common/Service/DocumentHandler.php @@ -0,0 +1,16 @@ +handlers = $handlers; + } + public function handle(): array { + $output = []; + foreach ($this->handlers as $handler) { + $output = array_merge($output, $handler->load()); + } + return $output; + } +} \ No newline at end of file diff --git a/api/common/Service/PdfHandler.php b/api/common/Service/PdfHandler.php index 4f9e1ab..1dbfdf6 100644 --- a/api/common/Service/PdfHandler.php +++ b/api/common/Service/PdfHandler.php @@ -1,15 +1,15 @@ client = $client; - $this->folder = $pdf_folder; $this->url = $url; } @@ -25,6 +25,51 @@ class PdfHandler { } $response = $this->client->post($this->url, ['json' => ['files' => $output]]); $output = json_decode($response->getBody()); - return $output; + return $this->build($output); + } + protected function build(array $data): ?array { + foreach ($data as &$file) { + $i = $this->findStartRow($file->text); + if ($i === -1) { + continue; + } + $e = $this->findEndRow($file->text, $i); + if ($e == $i) { + continue; + } + $file->data = array_filter($file->text, function($key) use ($i, $e) { + return ($key >= $i) and ($key <= $e); + }, ARRAY_FILTER_USE_KEY); + } + return $data; + } + protected function findStartRow(array $data): int { + foreach ($data as $i => $row) { + if (!is_array($row)) { + continue; + } + $maybe = false; + foreach ($row as $cell) { + if (str_contains($cell, '/')) { + $maybe = true; + } + if ($maybe and str_contains($cell, '$')) { + return $i - 1; + } + } + } + return -1; + } + protected function findEndRow(array $data, int $start): int { + $l = count($data[$start]); + for ($i = $start; $i < count($data); $i ++) { + if (!is_array($data[$i])) { + return $i - 1; + } + if (count($data[$i]) != $l) { + return $i - 1; + } + } + return $start; } } \ No newline at end of file diff --git a/api/common/Service/TiposCambios.php b/api/common/Service/TiposCambios.php new file mode 100644 index 0000000..be9f88d --- /dev/null +++ b/api/common/Service/TiposCambios.php @@ -0,0 +1,76 @@ +client = $client; + $this->factory = $factory; + $this->base_url = $api_url; + $this->key = $api_key; + } + public function get(string $fecha, int $moneda_id) { + $fecha = Carbon::parse($fecha); + $moneda = $this->factory->find(Moneda::class)->one($moneda_id); + if ($moneda->codigo == 'USD') { + if ($fecha->weekday() == 0) { + $fecha = $fecha->subWeek()->weekday(5); + } + if ($fecha->weekday() == 6) { + $fecha = $fecha->weekday(5); + } + } + $cambio = $moneda->cambio($fecha); + if ($cambio) { + if ($cambio->desde()->id != $moneda->id) { + return 1 / $cambio->valor; + } + return $cambio->valor; + } + $data = [ + 'fecha' => $fecha->format('Y-m-d'), + 'desde' => $moneda->codigo + ]; + $headers = [ + 'Authorization' => 'Bearer ' . $this->key + ]; + $url = implode('/', [ + $this->base_url, + 'cambio', + 'get' + ]); + try { + $response = $this->client->request('POST', $url, ['json' => $data, 'headers' => $headers]); + } catch (ConnectException | RequestException $e) { + error_log($e); + return null; + } + if ($response->getStatusCode() !== 200) { + return null; + } + $result = json_decode($response->getBody()); + $valor = $result->serie[0]->valor; + $data = [ + 'fecha' => $fecha->format('Y-m-d H:i:s'), + 'desde_id' => $moneda->id, + 'hasta_id' => 1, + 'valor' => $valor + ]; + $tipo = TipoCambio::add($this->factory, $data); + if ($tipo !== false and $tipo->is_new()) { + $tipo->save(); + } + return $valor; + } +} diff --git a/api/common/Service/XlsHandler.php b/api/common/Service/XlsHandler.php new file mode 100644 index 0000000..d86ef77 --- /dev/null +++ b/api/common/Service/XlsHandler.php @@ -0,0 +1,60 @@ +folder; + $files = new \DirectoryIterator($folder); + $output = []; + foreach ($files as $file) { + if ($file->isDir() or $file->getExtension() != 'xls') { + continue; + } + $reader = IOFactory::createReader(ucfirst($file->getExtension())); + $xls = $reader->load($file->getRealPath()); + $data = []; + $bank = 'unknown'; + for ($s = 0; $s < $xls->getSheetCount(); $s ++) { + $sheet = $xls->getSheet($s); + foreach ($sheet->getRowIterator() as $row) { + $r = []; + foreach ($row->getCellIterator() as $cell) { + $r []= $cell->getValue(); + } + $data []= $r; + } + foreach ($sheet->getDrawingCollection() as $drawing) { + if ($drawing instanceof MemoryDrawing) { + ob_start(); + call_user_func( + $drawing->getRenderingFunction(), + $drawing->getImageResource() + ); + $imageContents = ob_get_contents(); + $size = ob_get_length(); + ob_end_clean(); + $ocr = new TesseractOCR(); + $ocr->imageData($imageContents, $size); + $image = $ocr->run(); + if (str_contains($image, 'BICE')) { + $bank = 'BICE'; + } + if (str_contains($image, 'Scotiabank')) { + $bank = 'Scotiabank'; + } + } + } + } + $output []= ['bank' => $bank, 'filename' => $file->getBasename(), 'data' => $data]; + } + return $this->build($output); + } + protected function build(array $data): ?array { + return $data; + } +} \ No newline at end of file diff --git a/api/composer.json b/api/composer.json index 312f622..a655735 100644 --- a/api/composer.json +++ b/api/composer.json @@ -14,7 +14,9 @@ "robmorgan/phinx": "^0.12.9", "odan/phinx-migrations-generator": "^5.4", "martin-mikac/csv-to-phinx-seeder": "^1.6", - "guzzlehttp/guzzle": "^7.4" + "guzzlehttp/guzzle": "^7.4", + "phpoffice/phpspreadsheet": "^1.19", + "thiagoalessio/tesseract_ocr": "^2.12" }, "require-dev": { "phpunit/phpunit": "^9.5", diff --git a/api/db/migrations/20211029150754_tipo_cuenta.php b/api/db/migrations/20211029150754_tipo_cuenta.php index 814ccd9..d72ff70 100644 --- a/api/db/migrations/20211029150754_tipo_cuenta.php +++ b/api/db/migrations/20211029150754_tipo_cuenta.php @@ -20,6 +20,7 @@ final class TipoCuenta extends AbstractMigration { $this->table('tipos_cuenta') ->addColumn('descripcion', 'string') + ->addColumn('color', 'string', ['length' => 6]) ->create(); } } diff --git a/api/db/migrations/20211204205950_moneda.php b/api/db/migrations/20211204205950_moneda.php new file mode 100644 index 0000000..71804ba --- /dev/null +++ b/api/db/migrations/20211204205950_moneda.php @@ -0,0 +1,29 @@ +table('monedas') + ->addColumn('denominacion', 'string') + ->addColumn('codigo', 'string', ['length' => 3]) + ->addColumn('prefijo', 'string', ['default' => '']) + ->addColumn('sufijo', 'string', ['default' => '']) + ->addColumn('decimales', 'integer', ['default' => 0]) + ->create(); + } +} diff --git a/api/db/migrations/20211204210207_cuenta_moneda.php b/api/db/migrations/20211204210207_cuenta_moneda.php new file mode 100644 index 0000000..c37e44b --- /dev/null +++ b/api/db/migrations/20211204210207_cuenta_moneda.php @@ -0,0 +1,26 @@ +table('cuentas') + ->addColumn('moneda_id', 'integer') + ->addForeignKey('moneda_id', 'monedas') + ->update(); + } +} diff --git a/api/db/migrations/20211205002439_tipo_cambio.php b/api/db/migrations/20211205002439_tipo_cambio.php new file mode 100644 index 0000000..e5b6efe --- /dev/null +++ b/api/db/migrations/20211205002439_tipo_cambio.php @@ -0,0 +1,30 @@ +table('tipos_cambio') + ->addColumn('fecha', 'datetime') + ->addColumn('desde_id', 'integer') + ->addForeignKey('desde_id', 'monedas') + ->addColumn('hasta_id', 'integer') + ->addForeignKey('hasta_id', 'monedas') + ->addColumn('valor', 'double') + ->create(); + } +} diff --git a/api/db/seeds/Moneda.php b/api/db/seeds/Moneda.php new file mode 100644 index 0000000..4731c53 --- /dev/null +++ b/api/db/seeds/Moneda.php @@ -0,0 +1,41 @@ + 'Pesos Chilenos', + 'codigo' => 'CLP', + 'prefijo' => '$ ' + ], + [ + 'denominacion' => 'Dólar', + 'codigo' => 'USD', + 'prefijo' => 'US$ ', + 'decimales' => 2 + ], + [ + 'denominacion' => 'Unidad de Fomento', + 'codigo' => 'CLF', + 'sufijo' => ' UF', + 'decimales' => 2 + ] + ]; + $this->table('monedas') + ->insert($data) + ->saveData(); + } +} diff --git a/api/resources/routes/base.php b/api/resources/routes/base.php index c1d1538..c818f29 100644 --- a/api/resources/routes/base.php +++ b/api/resources/routes/base.php @@ -1,4 +1,6 @@ get('/key/generate[/]', [Base::class, 'generate_key']); +$app->get('/balance[/]', [Contabilidad\Common\Controller\TiposCategorias::class, 'balance']); $app->get('/', Base::class); diff --git a/api/resources/routes/monedas.php b/api/resources/routes/monedas.php new file mode 100644 index 0000000..a7ef909 --- /dev/null +++ b/api/resources/routes/monedas.php @@ -0,0 +1,12 @@ +group('/monedas', function($app) { + $app->post('/add[/]', [Monedas::class, 'add']); + $app->get('[/]', Monedas::class); +}); +$app->group('/moneda/{moneda_id}', function($app) { + $app->put('/edit', [Monedas::class, 'edit']); + $app->delete('/delete', [Monedas::class, 'delete']); + $app->get('[/]', [Monedas::class, 'show']); +}); diff --git a/api/resources/routes/tipos.php b/api/resources/routes/tipos.php new file mode 100644 index 0000000..8f636c4 --- /dev/null +++ b/api/resources/routes/tipos.php @@ -0,0 +1,16 @@ +group('/tipos', function($app) use ($folder) { + $files = new DirectoryIterator($folder); + foreach ($files as $file) { + if ($file->isDir() or $file->getExtension() != 'php') { + continue; + } + include_once $file->getRealPath(); + } + }); +} \ No newline at end of file diff --git a/api/resources/routes/tipos/cambios.php b/api/resources/routes/tipos/cambios.php new file mode 100644 index 0000000..edf5cf7 --- /dev/null +++ b/api/resources/routes/tipos/cambios.php @@ -0,0 +1,13 @@ +group('/cambios', function($app) { + $app->post('/obtener[/]', [TiposCambios::class, 'obtain']); + $app->post('/add[/]', [TiposCambios::class, 'add']); + $app->get('[/]', TiposCambios::class); +}); +$app->group('/cambio/{tipo_id}', function($app) { + $app->put('/edit[/]', [TiposCambios::class, 'edit']); + $app->delete('/delete[/]', [TiposCambios::class, 'delete']); + $app->get('[/]', [TiposCambios::class, 'show']); +}); diff --git a/api/resources/routes/tipos/categorias.php b/api/resources/routes/tipos/categorias.php new file mode 100644 index 0000000..38f9272 --- /dev/null +++ b/api/resources/routes/tipos/categorias.php @@ -0,0 +1,13 @@ +group('/categorias', function($app) { + $app->post('/add[/]', [TiposCategorias::class, 'add']); + $app->get('[/]', TiposCategorias::class); +}); +$app->group('/categoria/{tipo_id}', function($app) { + $app->get('/categorias', [TiposCategorias::class, 'categorias']); + $app->put('/edit', [TiposCategorias::class, 'edit']); + $app->delete('/delete', [TiposCategorias::class, 'delete']); + $app->get('[/]', [TiposCategorias::class, 'show']); +}); diff --git a/api/resources/routes/tipos/cuentas.php b/api/resources/routes/tipos/cuentas.php new file mode 100644 index 0000000..4b6f4ba --- /dev/null +++ b/api/resources/routes/tipos/cuentas.php @@ -0,0 +1,12 @@ +group('/cuentas', function($app) { + $app->post('/add[/]', [TiposCuentas::class, 'add']); + $app->get('[/]', TiposCuentas::class); +}); +$app->group('/cuenta/{tipo_id}', function($app) { + $app->put('/edit', [TiposCuentas::class, 'edit']); + $app->delete('/delete', [TiposCuentas::class, 'delete']); + $app->get('[/]', [TiposCuentas::class, 'show']); +}); diff --git a/api/setup/app.php b/api/setup/app.php index ba8e133..2090af8 100644 --- a/api/setup/app.php +++ b/api/setup/app.php @@ -31,7 +31,7 @@ $app->addRoutingMiddleware(); $app->add(new WhoopsMiddleware()); -$folder = 'middlewares'; +$folder = implode(DIRECTORY_SEPARATOR, [__DIR__, 'middlewares']); if (file_exists($folder)) { $files = new DirectoryIterator($folder); foreach ($files as $file) { diff --git a/api/setup/middlewares/01_auth.php b/api/setup/middlewares/01_auth.php new file mode 100644 index 0000000..eb7f3a6 --- /dev/null +++ b/api/setup/middlewares/01_auth.php @@ -0,0 +1,4 @@ +add($app->getContainer()->get(Auth::class)); diff --git a/api/setup/settings/01_env.php b/api/setup/settings/01_env.php index 161b2d4..db1b1d3 100644 --- a/api/setup/settings/01_env.php +++ b/api/setup/settings/01_env.php @@ -1,4 +1,7 @@ $_ENV['DEBUG'] ?? false + 'debug' => $_ENV['DEBUG'] ?? false, + 'api_key' => $_ENV['API_KEY'], + 'python_api' => $_ENV['PYTHON_API'] ?? 'http://python:5000', + 'python_key' => $_ENV['PYTHON_KEY'] ]; diff --git a/api/setup/settings/02_common.php b/api/setup/settings/02_common.php index 1687cc3..1185ad3 100644 --- a/api/setup/settings/02_common.php +++ b/api/setup/settings/02_common.php @@ -26,6 +26,14 @@ return [ $arr['uploads'], 'pdfs' ]); + $arr['csvs'] = implode(DIRECTORY_SEPARATOR, [ + $arr['uploads'], + 'csvs' + ]); + $arr['xlss'] = implode(DIRECTORY_SEPARATOR, [ + $arr['uploads'], + 'xlss' + ]); return (object) $arr; }, 'urls' => function(Container $c) { diff --git a/api/setup/setups/02_common.php b/api/setup/setups/02_common.php index 5e7229e..08015fb 100644 --- a/api/setup/setups/02_common.php +++ b/api/setup/setups/02_common.php @@ -5,11 +5,42 @@ return [ GuzzleHttp\Client::class => function(Container $c) { return new GuzzleHttp\Client(); }, + Contabilidad\Common\Service\Auth::class => function(Container $c) { + return new Contabilidad\Common\Service\Auth($c->get('api_key')); + }, + Contabilidad\Common\Middleware\Auth::class => function(Container $c) { + return new Contabilidad\Common\Middleware\Auth( + $c->get(Nyholm\Psr7\Factory\Psr17Factory::class), + $c->get(Contabilidad\Common\Service\Auth::class) + ); + }, Contabilidad\Common\Service\PdfHandler::class => function(Container $c) { return new Contabilidad\Common\Service\PdfHandler($c->get(GuzzleHttp\Client::class), $c->get('folders')->pdfs, implode('/', [ $c->get('urls')->python, 'pdf', 'parse' ])); - } + }, + Contabilidad\Common\Service\CsvHandler::class => function(Container $c) { + return new Contabilidad\Common\Service\CsvHandler($c->get('folders')->csvs); + }, + Contabilidad\Common\Service\XlsHandler::class => function(Container $c) { + return new Contabilidad\Common\Service\XlsHandler($c->get('folders')->xlss); + }, + Contabilidad\Common\Service\DocumentHandler::class => function(Container $c) { + $handlers = [ + $c->get(Contabilidad\Common\Service\XlsHandler::class), + $c->get(Contabilidad\Common\Service\CsvHandler::class), + $c->get(Contabilidad\Common\Service\PdfHandler::class) + ]; + return new Contabilidad\Common\Service\DocumentHandler($handlers); + }, + Contabilidad\Common\Service\TiposCambios::class => function(Container $c) { + return new Contabilidad\Common\Service\TiposCambios( + $c->get(GuzzleHttp\Client::class), + $c->get(ProVM\Common\Factory\Model::class), + $c->get('python_api'), + $c->get('python_key') + ); + } ]; diff --git a/api/src/Categoria.php b/api/src/Categoria.php index d2da085..6d9d80c 100644 --- a/api/src/Categoria.php +++ b/api/src/Categoria.php @@ -1,7 +1,9 @@ tipo; } + public function getCuentasOf($tipo) { + return $this->factory->find(Cuenta::class) + ->select([['cuentas', '*']]) + ->join([ + ['tipos_cuenta', 'tipos_cuenta.id', 'cuentas.tipo_id'] + ]) + ->where([ + ['tipos_cuenta.descripcion', $tipo], + ['cuentas.categoria_id', $this->id] + ]) + ->many(); + } + protected $activos; + public function activos() { + if ($this->activos === null) { + $this->activos = $this->getCuentasOf('Activo'); + } + return $this->activos(); + } + protected $pasivos; + public function pasivos() { + if ($this->pasivos === null) { + $this->activos = $this->getCuentasOf('Pasivo'); + } + return $this->pasivos; + } + protected $ganancias; + public function ganancias() { + if ($this->ganancias === null) { + $this->ganancias = $this->getCuentasOf('Ganancia'); + } + return $this->ganancias; + } + protected $perdidas; + public function perdidas() { + if ($this->perdidas === null) { + $this->perdidas = $this->getCuentasOf('Perdida'); + } + return $this->perdidas; + } + protected $saldo; - public function saldo() { + public function saldo(Service $service = null) { if ($this->saldo === null) { $this->saldo = 0; if ($this->cuentas() !== null) { - $this->saldo = array_reduce($this->cuentas(), function($sum, $item) { - return $sum + $item->saldo(); - }); + $sum = 0; + $debitos = ['Activo', 'Perdida']; + foreach ($this->cuentas() as $cuenta) { + if (array_search($cuenta->tipo()->descripcion, $debitos) !== false) { + $sum -= $cuenta->saldo($service, true); + continue; + } + $sum += $cuenta->saldo($service, true); + } + $this->saldo = $sum; } } return $this->saldo; } - - public function toArray(): array { - $arr = parent::toArray(); - $arr['tipo'] = $this->tipo()->toArray(); - $arr['saldo'] = $this->saldo(); - $arr['saldoFormateado'] = '$' . number_format($this->saldo(), 0, ',', '.'); - return $arr; - } } diff --git a/api/src/Cuenta.php b/api/src/Cuenta.php index 2d98485..4caa29b 100644 --- a/api/src/Cuenta.php +++ b/api/src/Cuenta.php @@ -1,17 +1,20 @@ categoria; } - protected $cuenta; - public function cuenta() { - if ($this->cuenta === null) { - $this->cuenta = $this->childOf(TipoCuenta::class, [Model::SELF_KEY => 'tipo_id']); + protected $tipo; + public function tipo() { + if ($this->tipo === null) { + $this->tipo = $this->childOf(TipoCuenta::class, [Model::SELF_KEY => 'tipo_id']); } - return $this->cuenta; + return $this->tipo; + } + protected $moneda; + public function moneda() { + if ($this->moneda === null) { + $this->moneda = $this->childOf(Moneda::class, [Model::SELF_KEY => 'moneda_id']); + } + return $this->moneda; } protected $cargos; public function cargos() { if ($this->cargos === null) { - $this->cargos = $this->parentOf(Transaccion::class, [Model::CHILD_KEY => 'hasta_id']); + $this->cargos = $this->parentOf(Transaccion::class, [Model::CHILD_KEY => 'credito_id']); } return $this->cargos; } protected $abonos; public function abonos() { if ($this->abonos === null) { - $this->abonos = $this->parentOf(Transaccion::class, [Model::CHILD_KEY => 'desde_id']); + $this->abonos = $this->parentOf(Transaccion::class, [Model::CHILD_KEY => 'debito_id']); } return $this->abonos; } @@ -46,7 +56,7 @@ class Cuenta extends Model { public function transacciones($limit = null, $start = 0) { if ($this->transacciones === null) { $transacciones = Model::factory(Transaccion::class) - ->join('cuentas', 'cuentas.id = transacciones.desde_id OR cuentas.id = transacciones.hasta_id') + ->join('cuentas', 'cuentas.id = transacciones.debito_id OR cuentas.id = transacciones.credito_id') ->whereEqual('cuentas.id', $this->id) ->orderByAsc('transacciones.fecha'); if ($limit !== null) { @@ -64,23 +74,40 @@ class Cuenta extends Model { return $this->transacciones; } protected $saldo; - public function saldo() { + public function saldo(Service $service = null, $in_clp = false) { if ($this->saldo === null) { $this->saldo = 0; if (count($this->transacciones()) > 0) { $this->saldo = array_reduce($this->transacciones(), function($sum, $item) { - return $sum + $item->valor; + return $sum + $item->valor; }); } } + if ($in_clp and $this->moneda()->codigo !== 'CLP') { + $fecha = Carbon::today(); + if ($this->moneda()->codigo == 'USD') { + $fecha = match ($fecha->weekday()) { + 0 => $fecha->subWeek()->weekday(5), + 6 => $fecha->weekday(5), + default => $fecha + }; + } + $service->get($fecha->format('Y-m-d'), $this->moneda()->id); + return $this->moneda()->cambiar($fecha, $this->saldo); + } return $this->saldo; } - public function toArray(): array { + public function format($valor) { + return $this->moneda()->format($valor); + } + + public function toArray(Service $service = null, $in_clp = false): array { $arr = parent::toArray(); - $arr['categoria'] = $this->categoria()->toArray(); - $arr['saldo'] = $this->saldo(); - $arr['saldoFormateado'] = '$' . number_format($this->saldo(), 0, ',', '.'); + $arr['tipo'] = $this->tipo()->toArray(); + $arr['moneda'] = $this->moneda()->toArray(); + $arr['saldo'] = $this->saldo($service, $in_clp); + $arr['saldoFormateado'] = $this->format($this->saldo($service, $in_clp)); return $arr; } } diff --git a/api/src/Moneda.php b/api/src/Moneda.php new file mode 100644 index 0000000..97602a2 --- /dev/null +++ b/api/src/Moneda.php @@ -0,0 +1,46 @@ +prefijo, + number_format($valor, $this->decimales, ',', '.'), + $this->sufijo + ]); + } + public function cambio(\DateTime $fecha) { + $cambio = $this->factory->find(TipoCambio::class) + ->where([['desde_id', $this->id], ['hasta_id', 1], ['fecha', $fecha->format('Y-m-d H:i:s')]]) + ->one(); + if (!$cambio) { + $cambio = $this->factory->find(TipoCambio::class) + ->where([['hasta_id', $this->id], ['desde_id', 1], ['fecha', $fecha->format('Y-m-d H:i:s')]]) + ->one(); + } + return $cambio; + } + public function cambiar(\DateTime $fecha, float $valor) { + $cambio = $this->cambio($fecha); + if (!$cambio) { + return $valor; + } + if ($cambio->desde()->id != $this->id) { + return $cambio->transform($valor, TipoCambio::DESDE); + } + return $cambio->transform($valor); + } +} diff --git a/api/src/TipoCambio.php b/api/src/TipoCambio.php new file mode 100644 index 0000000..0689194 --- /dev/null +++ b/api/src/TipoCambio.php @@ -0,0 +1,50 @@ +desde === null) { + $this->desde = $this->childOf(Moneda::class, [Model::SELF_KEY => 'desde_id']); + } + return $this->desde; + } + protected $hasta; + public function hasta() { + if ($this->hasta === null) { + $this->hasta = $this->childOf(Moneda::class, [Model::SELF_KEY => 'hasta_id']); + } + return $this->hasta; + } + public function fecha(DateTime $fecha = null) { + if ($fecha === null) { + return Carbon::parse($this->fecha); + } + $this->fecha = $fecha->format('Y-m-d H:i:s'); + return $this; + } + + public function transform(float $valor, int $direction = TipoCambio::HASTA): float { + if ($direction == TipoCambio::HASTA) { + return $valor * $this->valor; + } + return $valor / $this->valor; + } +} diff --git a/api/src/TipoCategoria.php b/api/src/TipoCategoria.php index a226d90..c21b187 100644 --- a/api/src/TipoCategoria.php +++ b/api/src/TipoCategoria.php @@ -2,6 +2,7 @@ namespace Contabilidad; use ProVM\Common\Alias\Model; +use Contabilidad\Common\Service\TiposCambios as Service; /** * @property int $id @@ -11,4 +12,35 @@ use ProVM\Common\Alias\Model; class TipoCategoria extends Model { public static $_table = 'tipos_categoria'; protected static $fields = ['descripcion', 'activo']; + + protected $categorias; + public function categorias() { + if ($this->categorias === null) { + $this->categorias = $this->parentOf(Categoria::class, [Model::CHILD_KEY => 'tipo_id']); + } + return $this->categorias; + } + + public function getCuentasOf($tipo) { + return $this->factory->find(Cuenta::class) + ->select([['cuentas', '*']]) + ->join([ + ['tipos_cuenta', 'tipos_cuenta.id', 'cuentas.tipo_id'], + ['categorias', 'categorias.id', 'cuentas.categoria_id'] + ]) + ->where([ + ['tipos_cuenta.descripcion', $tipo], + ['categorias.tipo_id', $this->id] + ])->many(); + } + + protected $saldo; + public function saldo(Service $service = null) { + if ($this->saldo === null) { + $this->saldo = array_reduce($this->categorias(), function($sum, $item) use ($service) { + return $sum + $item->saldo($service); + }); + } + return $this->saldo; + } } diff --git a/api/src/TipoCuenta.php b/api/src/TipoCuenta.php index 4a1354d..5ff4961 100644 --- a/api/src/TipoCuenta.php +++ b/api/src/TipoCuenta.php @@ -6,8 +6,9 @@ use ProVM\Common\Alias\Model; /** * @property int $id * @property string $descripcion + * @property string $color */ class TipoCuenta extends Model { public static $_table = 'tipos_cuenta'; - protected static $fields = ['descripcion']; -} \ No newline at end of file + protected static $fields = ['descripcion', 'color']; +} diff --git a/api/src/Transaccion.php b/api/src/Transaccion.php index 3865109..5fca6e8 100644 --- a/api/src/Transaccion.php +++ b/api/src/Transaccion.php @@ -1,6 +1,7 @@ credito; } - public function fecha(\DateTime $fecha = null) { + public function fecha(DateTime $fecha = null) { if ($fecha === null) { return Carbon::parse($this->fecha); } @@ -43,7 +44,7 @@ class Transaccion extends Model { $arr['debito'] = $this->debito()->toArray(); $arr['credito'] = $this->credito()->toArray(); $arr['fechaFormateada'] = $this->fecha()->format('d-m-Y'); - $arr['valorFormateado'] = '$' . number_format($this->valor, 0, ',', '.'); + $arr['valorFormateado'] = $this->debito()->moneda()->format($this->valor); return $arr; } }