19 Commits

Author SHA1 Message Date
4954947829 Merge branch 'release' 2022-03-25 15:49:12 -03:00
d0d123e12e Merge branch 'develop' into release 2022-03-18 17:50:36 -03:00
2b3b475d91 Show one month at a time, and change month with calendar and buttons 2022-03-18 17:50:21 -03:00
e5cf3cfa07 Update blade view 2022-03-18 17:43:26 -03:00
71975ec6d9 Merge branch 'develop' into release 2022-01-07 01:13:22 -03:00
84a3f8e2e3 Order totals for tipocuenta 2022-01-07 01:12:54 -03:00
f36399c1f4 Formatting and Fixes 2022-01-07 00:24:45 -03:00
5a9dc6602c Bold results 2022-01-07 00:24:08 -03:00
d7dfc2d221 FIX:Unavailable value for tipocambio crashed the loading of values 2022-01-07 00:23:52 -03:00
1d62a0c04e Fixes 2022-01-06 15:20:37 -03:00
c1eeba04a2 Resultado y fix cuentas 2022-01-06 15:20:21 -03:00
3cb2a877de FIX: transacciones, tipocambio and headers 2022-01-06 15:19:36 -03:00
a2a90497ae Python updates 2022-01-05 21:11:16 -03:00
b17225549c Python get cambio 2022-01-05 21:10:51 -03:00
605c905f5d Python 2021-12-22 21:53:30 -03:00
430e29eaec Fixed TipoCategoria select and url importar in menu 2021-12-20 23:31:05 -03:00
960c418848 Python 2021-12-09 21:14:28 -03:00
25f873c453 Python 2021-12-06 22:13:57 -03:00
34b429530f Python 2021-12-06 22:13:06 -03:00
27 changed files with 1379 additions and 169 deletions

View File

@ -1,8 +1,10 @@
<?php <?php
namespace Contabilidad\Common\Controller; namespace Contabilidad\Common\Controller;
use Contabilidad\Transaccion;
use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ResponseInterface as Response;
use Carbon\Carbon;
use ProVM\Common\Define\Controller\Json; use ProVM\Common\Define\Controller\Json;
use ProVM\Common\Factory\Model as Factory; use ProVM\Common\Factory\Model as Factory;
use Contabilidad\Common\Service\TiposCambios as Service; use Contabilidad\Common\Service\TiposCambios as Service;
@ -109,6 +111,24 @@ class Cuentas {
]; ];
return $this->withJson($response, $output); return $this->withJson($response, $output);
} }
protected function transaccionToArray(Service $service, Cuenta $cuenta, Transaccion $transaccion): array {
$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']);
}
}
$arr['debito']['categoria'] = $transaccion->debito()->categoria()->toArray();
$arr['credito']['categoria'] = $transaccion->credito()->categoria()->toArray();
return $arr;
}
public function transacciones(Request $request, Response $response, Factory $factory, Service $service, $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); $cuenta = $factory->find(Cuenta::class)->one($cuenta_id);
$transacciones = null; $transacciones = null;
@ -116,7 +136,7 @@ class Cuentas {
$transacciones = $cuenta->transacciones($limit, $start); $transacciones = $cuenta->transacciones($limit, $start);
if (count($transacciones) > 0) { if (count($transacciones) > 0) {
foreach ($transacciones as &$transaccion) { foreach ($transacciones as &$transaccion) {
$arr = $transaccion->toArray(); /*$arr = $transaccion->toArray();
if ($cuenta->moneda()->codigo === 'CLP') { if ($cuenta->moneda()->codigo === 'CLP') {
if ($transaccion->debito()->moneda()->codigo !== 'CLP' or $transaccion->credito()->moneda()->codigo !== 'CLP') { if ($transaccion->debito()->moneda()->codigo !== 'CLP' or $transaccion->credito()->moneda()->codigo !== 'CLP') {
if ($transaccion->debito()->moneda()->codigo !== 'CLP') { if ($transaccion->debito()->moneda()->codigo !== 'CLP') {
@ -130,8 +150,8 @@ class Cuentas {
} }
} }
$arr['debito']['categoria'] = $transaccion->debito()->categoria()->toArray(); $arr['debito']['categoria'] = $transaccion->debito()->categoria()->toArray();
$arr['credito']['categoria'] = $transaccion->credito()->categoria()->toArray(); $arr['credito']['categoria'] = $transaccion->credito()->categoria()->toArray();*/
$transaccion = $arr; $transaccion = $this->transaccionToArray($service, $cuenta, $transaccion);
} }
} }
} }
@ -142,6 +162,55 @@ class Cuentas {
]; ];
return $this->withJson($response, $output); return $this->withJson($response, $output);
} }
public function transaccionesMonth(Request $request, Response $response, Factory $factory, Service $service, $cuenta_id, $month): Response {
$cuenta = $factory->find(Cuenta::class)->one($cuenta_id);
$month = Carbon::parse($month);
$transacciones = null;
if ($cuenta !== null) {
$transacciones = $cuenta->transaccionesMonth($month);
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']);
}
}
$arr['debito']['categoria'] = $transaccion->debito()->categoria()->toArray();
$arr['credito']['categoria'] = $transaccion->credito()->categoria()->toArray();*/
$transaccion = $this->transaccionToArray($service, $cuenta, $transaccion);
}
}
}
$output = [
'input' => compact('cuenta_id', 'month'),
'cuenta' => $cuenta?->toArray(),
'transacciones' => $transacciones
];
return $this->withJson($response, $output);
}
public function transaccionesAcumulation(Request $request, Response $response, Factory $factory, $cuenta_id, $date): Response {
$cuenta = $factory->find(Cuenta::class)->one($cuenta_id);
$f = Carbon::parse($date);
$acum = 0;
if ($cuenta !== null) {
$acum = $cuenta->acumulacion($f);
}
$output = [
'input' => compact('cuenta_id', 'date'),
'cuenta' => $cuenta?->toArray(),
'format' => $cuenta->moneda()->toArray(),
'acumulation' => $acum
];
return $this->withJson($response, $output);
}
public function transaccionesAmount(Request $request, Response $response, Factory $factory, $cuenta_id): Response { public function transaccionesAmount(Request $request, Response $response, Factory $factory, $cuenta_id): Response {
$cuenta = $factory->find(Cuenta::class)->one($cuenta_id); $cuenta = $factory->find(Cuenta::class)->one($cuenta_id);
$transacciones = 0; $transacciones = 0;

View File

@ -1,6 +1,7 @@
<?php <?php
namespace Contabilidad\Common\Controller; namespace Contabilidad\Common\Controller;
use Contabilidad\TipoCuenta;
use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ResponseInterface as Response;
use ProVM\Common\Define\Controller\Json; use ProVM\Common\Define\Controller\Json;
@ -23,19 +24,7 @@ class TiposCategorias {
}, $item->categorias()); }, $item->categorias());
} }
$arr['saldo'] = abs($item->saldo($service)); $arr['saldo'] = abs($item->saldo($service));
$maps = ['activo', 'pasivo', 'ganancia', 'perdida']; $arr['totales'] = $item->getTotales($service);
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; $item = $arr;
}); });
usort($tipos, function($a, $b) { usort($tipos, function($a, $b) {
@ -93,19 +82,7 @@ class TiposCategorias {
if ($categorias !== null) { if ($categorias !== null) {
array_walk($categorias, function(&$item) use ($service) { array_walk($categorias, function(&$item) use ($service) {
$arr = $item->toArray($service); $arr = $item->toArray($service);
$maps = ['activo', 'pasivo', 'ganancia', 'perdida']; $arr['totales'] = $item->getTotales($service);
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; $item = $arr;
}); });
} }
@ -120,6 +97,19 @@ class TiposCategorias {
public function balance(Request $request, Response $response, Factory $factory, Service $service): Response { public function balance(Request $request, Response $response, Factory $factory, Service $service): Response {
$tipos = $factory->find(TipoCategoria::class)->many(); $tipos = $factory->find(TipoCategoria::class)->many();
$balance = array_reduce($tipos, function($sum, $item) use ($service) { $balance = array_reduce($tipos, function($sum, $item) use ($service) {
$totales = $item->getTotales($service);
if (!is_array($sum)) {
$sum = [];
}
foreach ($totales as $p => $total) {
if (!isset($sum[$p])) {
$sum[$p] = 0;
}
$sum[$p] += $total;
}
return $sum;
});
/*$balance = array_reduce($tipos, function($sum, $item) use ($service) {
$maps = ['activo', 'pasivo', 'ganancia', 'perdida']; $maps = ['activo', 'pasivo', 'ganancia', 'perdida'];
foreach ($maps as $m) { foreach ($maps as $m) {
$p = $m . 's'; $p = $m . 's';
@ -136,7 +126,7 @@ class TiposCategorias {
}); });
} }
return $sum; return $sum;
}); });*/
return $this->withJson($response, $balance); return $this->withJson($response, $balance);
} }
} }

View File

@ -5,6 +5,7 @@ use Carbon\Carbon;
use GuzzleHttp\Client; use GuzzleHttp\Client;
use GuzzleHttp\Exception\ConnectException; use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\RequestException; use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Exception\ServerException;
use ProVM\Common\Factory\Model as Factory; use ProVM\Common\Factory\Model as Factory;
use Contabilidad\Moneda; use Contabilidad\Moneda;
use Contabilidad\TipoCambio; use Contabilidad\TipoCambio;
@ -35,7 +36,7 @@ class TiposCambios {
'desde' => $moneda_codigo 'desde' => $moneda_codigo
]; ];
$headers = [ $headers = [
'Authorization' => 'Bearer ' . $this->key 'Authorization' => "Bearer {$this->key}"
]; ];
$url = implode('/', [ $url = implode('/', [
$this->base_url, $this->base_url,
@ -44,14 +45,19 @@ class TiposCambios {
]); ]);
try { try {
$response = $this->client->request('POST', $url, ['json' => $data, 'headers' => $headers]); $response = $this->client->request('POST', $url, ['json' => $data, 'headers' => $headers]);
} catch (ConnectException | RequestException $e) { } catch (ConnectException | RequestException | ServerException $e) {
error_log($e); error_log($e);
return null; return null;
} }
if ($response->getStatusCode() !== 200) { if ($response->getStatusCode() !== 200) {
error_log('Could not connect to python API.');
return null; return null;
} }
$result = json_decode($response->getBody()); $result = json_decode($response->getBody());
if (isset($result->message) and $result->message === 'Not Authorized') {
error_log('Not authorized for connecting to python API.');
return null;
}
return $result->serie[0]->valor; return $result->serie[0]->valor;
} }
public function get(string $fecha, int $moneda_id) { public function get(string $fecha, int $moneda_id) {
@ -62,13 +68,16 @@ class TiposCambios {
} }
// If a value exists in the database // If a value exists in the database
$cambio = $moneda->cambio($fecha); $cambio = $moneda->cambio($fecha);
if ($cambio) { if ($cambio !== null) {
if ($cambio->desde()->id != $moneda->id) { if ($cambio->desde()->id != $moneda->id) {
return 1 / $cambio->valor; return 1 / $cambio->valor;
} }
return $cambio->valor; return $cambio->valor;
} }
$valor = $this->getValor($fecha, $moneda->codigo); $valor = $this->getValor($fecha, $moneda->codigo);
if ($valor === null) {
return 1;
}
$data = [ $data = [
'fecha' => $fecha->format('Y-m-d H:i:s'), 'fecha' => $fecha->format('Y-m-d H:i:s'),
'desde_id' => $moneda->id, 'desde_id' => $moneda->id,

View File

@ -12,11 +12,12 @@ server {
} }
location ~ \.php$ { location ~ \.php$ {
if ($request_method = 'OPTIONS') { if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Access-Control-Allow-Origin' '*'; add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Headers' 'Authorization,Accept,Origin,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range'; add_header 'Access-Control-Allow-Headers' 'Authorization,Accept,Origin,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range';
add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS,PUT,DELETE,PATCH'; add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS,PUT,DELETE,PATCH';
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'application/json'; add_header 'Content-Type' 'application/json';
add_header 'Content-Length' 0; add_header 'Content-Length' 0;
return 204; return 204;

View File

@ -9,6 +9,8 @@ $app->group('/cuenta/{cuenta_id}', function($app) {
$app->get('/entradas', [Cuentas::class, 'entradas']); $app->get('/entradas', [Cuentas::class, 'entradas']);
$app->group('/transacciones', function($app) { $app->group('/transacciones', function($app) {
$app->get('/amount', [Cuentas::class, 'transaccionesAmount']); $app->get('/amount', [Cuentas::class, 'transaccionesAmount']);
$app->get('/month/{month}', [Cuentas::class, 'transaccionesMonth']);
$app->get('/acum/{date}', [Cuentas::class, 'transaccionesAcumulation']);
$app->get('[/{limit:[0-9]+}[/{start:[0-9]+}]]', [Cuentas::class, 'transacciones']); $app->get('[/{limit:[0-9]+}[/{start:[0-9]+}]]', [Cuentas::class, 'transacciones']);
}); });
$app->get('/categoria', [Cuentas::class, 'categoria']); $app->get('/categoria', [Cuentas::class, 'categoria']);

View File

@ -46,33 +46,43 @@ class Categoria extends Model {
]) ])
->many(); ->many();
} }
protected $activos; protected $cuentas_of;
public function activos() { public function getCuentas() {
if ($this->activos === null) { if ($this->cuentas_of === null) {
$this->activos = $this->getCuentasOf('Activo'); $tipos = $this->factory->find(TipoCuenta::class)->many();
$cos = [];
foreach ($tipos as $tipo) {
$p = strtolower($tipo->descripcion) . 's';
$cos[$p] = [];
$cuentas = $this->getCuentasOf($tipos->descripcion);
if ($cuentas === null) {
continue;
}
$cos[$p] = $cuentas;
}
$this->cuentas_of = $cos;
} }
return $this->activos(); return $this->cuentas_of;
} }
protected $pasivos; protected $totales;
public function pasivos() { public function getTotales(Service $service) {
if ($this->pasivos === null) { if ($this->totales === null) {
$this->activos = $this->getCuentasOf('Pasivo'); $tipos = $this->factory->find(TipoCuenta::class)->many();
$totals = [];
foreach ($tipos as $tipo) {
$p = strtolower($tipo->descripcion) . 's';
$totals[$p] = 0;
$cuentas = $this->getCuentasOf($tipo->descripcion);
if ($cuentas === null) {
continue;
}
$totals[$p] = array_reduce($cuentas, function($sum, $item) use ($service) {
return $sum + $item->saldo($service, true);
});
}
$this->totales = $totals;
} }
return $this->pasivos; return $this->totales;
}
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; protected $saldo;

View File

@ -1,7 +1,9 @@
<?php <?php
namespace Contabilidad; namespace Contabilidad;
use DateTimeInterface;
use Carbon\Carbon; use Carbon\Carbon;
use PhpParser\Node\Expr\AssignOp\Mod;
use ProVM\Common\Alias\Model; use ProVM\Common\Alias\Model;
use Contabilidad\Common\Service\TiposCambios as Service; use Contabilidad\Common\Service\TiposCambios as Service;
@ -54,8 +56,8 @@ class Cuenta extends Model {
} }
protected $transacciones; protected $transacciones;
public function transacciones($limit = null, $start = 0) { public function transacciones($limit = null, $start = 0) {
if ($this->transacciones === null) {
$transacciones = Model::factory(Transaccion::class) $transacciones = Model::factory(Transaccion::class)
->select('transacciones.*')
->join('cuentas', 'cuentas.id = transacciones.debito_id OR cuentas.id = transacciones.credito_id') ->join('cuentas', 'cuentas.id = transacciones.debito_id OR cuentas.id = transacciones.credito_id')
->whereEqual('cuentas.id', $this->id) ->whereEqual('cuentas.id', $this->id)
->orderByAsc('transacciones.fecha'); ->orderByAsc('transacciones.fecha');
@ -63,15 +65,52 @@ class Cuenta extends Model {
$transacciones = $transacciones->limit($limit) $transacciones = $transacciones->limit($limit)
->offset($start); ->offset($start);
} }
$this->transacciones = $transacciones->findMany(); $transacciones = $transacciones->findMany();
foreach ($this->transacciones as &$transaccion) { foreach ($transacciones as &$transaccion) {
$transaccion->setFactory($this->factory); $transaccion->setFactory($this->factory);
if ($transaccion->desde_id === $this->id) { if ($transaccion->debito_id === $this->id) {
$transaccion->valor = - $transaccion->valor; $transaccion->valor = - $transaccion->valor;
} }
} }
} return $transacciones;
return $this->transacciones; }
public function transaccionesMonth(Carbon $month) {
$start = $month->copy()->startOfMonth();
$end = $month->copy()->endOfMonth();
$transacciones = Model::factory(Transaccion::class)
->select('transacciones.*')
->join('cuentas', 'cuentas.id = transacciones.debito_id OR cuentas.id = transacciones.credito_id')
->whereEqual('cuentas.id', $this->id)
->whereRaw("transacciones.fecha BETWEEN '{$start->format('Y-m-d')}' AND '{$end->format('Y-m-d')}'")
->orderByAsc('transacciones.fecha');
$transacciones = $transacciones->findMany();
foreach ($transacciones as &$transaccion) {
$transaccion->setFactory($this->factory);
if ($transaccion->desde_id === $this->id) {
$transaccion->valor = - $transaccion->valor;
}
}
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;
}
return $cargos - $abonos;
} }
protected $saldo; protected $saldo;
public function saldo(Service $service = null, $in_clp = false) { public function saldo(Service $service = null, $in_clp = false) {

View File

@ -16,17 +16,17 @@ class Moneda extends Model {
protected static $fields = ['denominacion', 'codigo']; protected static $fields = ['denominacion', 'codigo'];
public function format($valor) { public function format($valor) {
return implode('', [ return trim(implode('', [
$this->prefijo, $this->prefijo,
number_format($valor, $this->decimales, ',', '.'), number_format($valor, $this->decimales, ',', '.'),
$this->sufijo $this->sufijo
]); ]));
} }
public function cambio(\DateTime $fecha) { public function cambio(\DateTime $fecha) {
$cambio = $this->factory->find(TipoCambio::class) $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', 1], ['fecha', $fecha->format('Y-m-d H:i:s')]])
->one(); ->one();
if (!$cambio) { if ($cambio === null) {
$cambio = $this->factory->find(TipoCambio::class) $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', 1], ['fecha', $fecha->format('Y-m-d H:i:s')]])
->one(); ->one();
@ -43,4 +43,14 @@ class Moneda extends Model {
} }
return $cambio->transform($valor); return $cambio->transform($valor);
} }
public function toArray(): array {
$arr = parent::toArray();
$arr['format'] = [
'prefijo' => $this->prefijo,
'sufijo' => $this->sufijo,
'decimales' => $this->decimales
];
return $arr;
}
} }

View File

@ -33,6 +33,26 @@ class TipoCategoria extends Model {
['categorias.tipo_id', $this->id] ['categorias.tipo_id', $this->id]
])->many(); ])->many();
} }
protected $totales;
public function getTotales(Service $service) {
if ($this->totales === null) {
$tipos = $this->factory->find(TipoCuenta::class)->many();
$totals = [];
foreach ($tipos as $tipo) {
$p = strtolower($tipo->descripcion) . 's';
$totals[$p] = 0;
$cuentas = $this->getCuentasOf($tipo->descripcion);
if ($cuentas === null) {
continue;
}
$totals[$p] = array_reduce($cuentas, function($sum, $item) use ($service) {
return $sum + $item->saldo($service, true);
});
}
$this->totales = $totals;
}
return $this->totales;
}
protected $saldo; protected $saldo;
public function saldo(Service $service = null) { public function saldo(Service $service = null) {

View File

@ -2,7 +2,7 @@ FROM python
RUN apt-get update -y && apt-get install -y ghostscript python3-tk libgl-dev RUN apt-get update -y && apt-get install -y ghostscript python3-tk libgl-dev
RUN pip install flask pyyaml pypdf4 gunicorn camelot-py[cv] pikepdf RUN pip install flask pyyaml pypdf4 gunicorn camelot-py[cv] pikepdf httpx
WORKDIR /app WORKDIR /app
@ -12,4 +12,5 @@ EXPOSE 5000
WORKDIR /app/src WORKDIR /app/src
CMD ["gunicorn", "-b 0.0.0.0:5000", "app:app"] CMD ["python", "app.py"]
#CMD ["gunicorn", "-b 0.0.0.0:5000", "app:app"]

View File

@ -1,3 +1,4 @@
passwords: passwords:
- 0839 - 0839
- 159608395 - 159608395
- 15960839

Binary file not shown.

285
python/src/ai/dictionary.py Normal file
View File

@ -0,0 +1,285 @@
import json
import os
import numpy as np
import sklearn
import enlighten
from sklearn.preprocessing import LabelEncoder
import src.contabilidad.pdf as pdf
import src.contabilidad.text_handler as th
from src.ai.models import Phrase, phrase_factory, Word, word_factory
from src.contabilidad.log import LOG_LEVEL
class Dictionary:
def __init__(self, filename, logger):
self.filename = filename
self._logger = logger
self.__processed = []
self.__phrases = None
self.__words = None
self.load()
def load(self):
if not os.path.isfile(self.filename):
return
with open(self.filename, 'r') as file:
data = json.load(file)
if 'words' in data.keys():
self.__words = []
[self.__words.append(word_factory(w)) for w in data['words']]
if 'phrases' in data.keys():
self.__phrases = []
[self.__phrases.append(phrase_factory(ph)) for ph in data['phrases']]
if 'processed' in data.keys():
self.__processed = []
self.__processed = data['processed']
def save(self):
self.sort_words()
self.sort_phrases()
with open(self.filename, 'w') as file:
json.dump(self.to_json(), file, indent=2)
def to_data(self):
encoder = LabelEncoder()
data = encoder.fit_transform([w.get_word() for w in self.get_words()])
[self.__words[i].set_fit(f) for i, f in enumerate(data)]
print(data)
# return [ph.to_data() for ph in self.get_phrases()]
def to_json(self):
output = {
'processed': [],
'words': [],
'phrases': []
}
if self.__processed is not None and len(self.__processed) > 0:
output['processed'] = self.__processed
if self.__words is not None and len(self.__words) > 0:
output['words'] = [w.to_json() for w in self.__words]
if self.__phrases is not None and len(self.__phrases) > 0:
output['phrases'] = [p.to_json() for p in self.__phrases]
return output
def find_phrase(self, phrase: Phrase = None, phrase_dict: dict = None, phrase_list: list = None):
if not self.__phrases:
return -1
if phrase is not None:
phrase_list = [w.get_word() for w in phrase.get_words()]
elif phrase_dict is not None:
phrase_list = phrase_dict['words']
elif phrase_list is not None:
pass
else:
return -1
return find_phrase(self.__phrases, phrase_list)
def add_phrase(self, phrase: Phrase = None, phrase_dict: dict = None, phrase_list: list = None):
if self.__phrases is None:
self.__phrases = []
if phrase is not None:
pass
elif phrase_dict is not None:
phrase = phrase_factory(phrase_dict)
elif phrase_list is not None:
phrase = phrase_factory({'words': phrase_list})
else:
return self
i = self.find_phrase(phrase)
if i > -1:
self.__phrases[i].add_freq()
return self
self.__phrases.append(phrase)
return self
def add_phrases(self, phrase_list: list):
if self.__phrases is None:
self.__phrases = []
phs = [sorted(w.get_word() for w in p) for p in self.__phrases]
with enlighten.get_manager() as manager:
with manager.counter(total=len(phrase_list), desc='Phrases', unit='phrases', color='green') as bar1:
for i, phrase in enumerate(phrase_list):
# print(f'Adding phrase {i}.')
p2 = sorted([w.get_word() for w in phrase])
if p2 in phs:
k = phs.index(p2)
self.__phrases[k].add_freq()
continue
ph = phrase_factory({'words': phrase})
self.__phrases.append(ph)
phs.append(p2)
bar1.update()
def get_phrases(self):
return self.__phrases
def sort_phrases(self):
if self.__phrases is None:
return
try:
def sort_phrase(p):
if p is None:
return 0
if isinstance(p, Phrase):
return p.get_freq(), p.get_type().get_desc(), len(p.get_words())
return p['frequency'], p['type']['description'], len(p['words'])
self.__phrases = sorted(self.__phrases,
key=sort_phrase)
except Exception as e:
self._logger.log(repr(self.__phrases), LOG_LEVEL.ERROR)
self._logger.log(e)
return self
def sort_words(self):
if self.__words is None:
return
try:
def sort_word(w):
if w is None:
return 0
if isinstance(w, Word):
return w.get_freq(), w.get_type().get_desc(), w.get_word()
return w['frequency'], w['type']['description'], w['word']
self.__words = sorted(self.__words, key=sort_word, reverse=True)
except Exception as e:
self._logger.log(repr(self.__words))
self._logger.log(e)
return self
def find_word(self, word: Word = None, word_dict: dict = None, word_str: str = None):
if not self.__words:
return -1
if word is not None:
word_str = word.get_word()
elif word_dict is not None:
word_str = word_dict['word']
elif word_str is not None:
pass
else:
return -1
return find_word(self.__words, word_str)
def add_word(self, word: Word = None, word_dict: dict = None, word_str: str = None):
if self.__words is None:
self.__words = []
if word is not None:
pass
elif word_dict is not None:
word = word_factory(word_dict)
elif word_str is not None:
word = word_factory({'word': word_str})
else:
return self
i = self.find_word(word)
if i > -1:
self.__words[i].add_freq()
return self
self.__words.append(word)
return self
def add_words(self, words: list):
[self.add_word(word=w) for w in words if isinstance(w, Word)]
[self.add_word(word_dict=w) for w in words if isinstance(w, dict)]
[self.add_word(word_str=w) for w in words if isinstance(w, str)]
return self
def get_words(self):
return filter_unique_words(self.__words)
def match_words(self, word_list: list):
new_list = []
for w in word_list:
wi = self.find_word(word_str=w)
new_list.append(self.__words[wi])
return new_list
def append_to_phrase(self, seed: list = None, length: int = 1):
if seed is None:
return [self.__words[0]]
max_index = max(seed) + length
if max_index > len(self.__words):
if length == 1:
return False
return self.append_to_phrase(seed, length - 1)
return seed + self.__words[max_index]
def get_possible_phrases(self, word_list):
print('Adding words.')
self.add_words(word_list)
print('Creating phrases.')
with enlighten.get_manager() as manager:
with manager.counter(total=len(word_list)**2, desc='Phrases', unit='words', color='red') as bar1:
phrases = []
for length in range(1, len(word_list) + 1):
bar2 = bar1.add_subcounter(color='green')
for start in range(0, len(word_list)):
phrase = build_phrase(word_list, start, start + length)
phrase = self.match_words(phrase)
phrases.append(phrase)
start += length
bar2.update()
bar1.update()
print(f'Created {len(phrases)} phrases.')
phrases = sorted(phrases, key=lambda e: len(e))
print('Adding phrases.')
# Really slow (~115000 phrases in one pdf)
self.add_phrases(phrases)
return self.__phrases
def is_processed(self, filename: str):
return os.path.basename(filename) in self.__processed
def process(self, filename: str, password: str = None):
if self.is_processed(filename):
print('Already processed.')
return
t = filename.split('.')
temp = os.path.realpath(os.path.join(os.path.dirname(filename), t[0] + '-temp.pdf'))
print('Removing PDF encryption.')
pdf.remove_encryption(filename, password, temp)
print('Getting text')
obj = pdf.get_text(temp)
os.remove(temp)
print('Getting possible phrases.')
phrases = self.get_possible_phrases(th.split_words(obj))
self.__processed.append(os.path.basename(filename))
return phrases
def build_phrase(word_list, start: int, end: int = None):
if end is None:
return word_list[start:]
return word_list[start:end]
def filter_unique_words(words):
new_list = []
for w in words:
if w not in new_list:
new_list.append(w)
return new_list
def validate_phrase(phrase):
return True
def find_phrase(phrases: list, phrase: list):
phrase_list = [sorted([w.get_word() for w in p.get_words()]) for p in phrases]
sphrase = sorted(phrase)
if sphrase in phrase_list:
return phrase_list.index(sphrase)
return -1
def find_word(words: list, word: str):
word_list = [w.get_word() for w in words]
if word in word_list:
return word_list.index(word)
return -1

243
python/src/ai/models.py Normal file
View File

@ -0,0 +1,243 @@
import json
class Type:
def __init__(self, _id, _description):
self.__id = _id
self.__description = _description
def get_id(self):
return self.__id
def get_desc(self):
return self.__description
def to_json(self):
return self.get_id()
def __repr__(self):
return json.dumps({
'id': self.get_id(),
'description': self.get_desc()
})
def type_factory(_type: str, _id: int):
if _type == 'Word' or _type == 'WordType':
t = WordType()
elif _type == 'Phrase' or _type == 'PhraseType':
t = PhraseType()
else:
return None
t.load(_id)
return t
class WordType(Type):
STRING = 0
NUMERIC = 1
CURRENCY = 2
DATE = 4
def __init__(self):
super().__init__(0, 'string')
def load(self, word_type: int):
if word_type == self.STRING:
self.__description = 'string'
elif word_type == self.NUMERIC:
self.__description = 'numeric'
elif word_type == self.CURRENCY:
self.__description = 'currency'
elif word_type == self.DATE:
self.__description = 'date'
return self
class PhraseType(Type):
TEXT = 0
TITLE = 1
HEADER = 2
MOVEMENT = 4
INVALID = 99
def __init__(self):
super(PhraseType, self).__init__(0, 'text')
def load(self, phrase_type: int):
if phrase_type == self.TEXT:
self.__description = 'text'
elif phrase_type == self.TITLE:
self.__description = 'title'
elif phrase_type == self.HEADER:
self.__description = 'header'
class Word:
def __init__(self):
self.__id = 0
self.__word = None
self.__type_id = 0
self.__type = None
self.__frequency = 1
def set_id(self, idx: int):
self.__id = idx
return self
def set_word(self, word: str):
self.__word = word
return self
def set_type(self, word_type):
if isinstance(word_type, WordType):
self.__type_id = word_type.get_id()
# self.__type = word_type
if isinstance(word_type, int):
self.__type_id = word_type
# self.__type = type_factory('Word', word_type)
return self
def add_freq(self, amount: int = 1):
self.__frequency += amount
return self
def get_id(self) -> int:
return self.__id
def get_word(self) -> str:
return self.__word
def get_type_id(self) -> int:
return self.__type_id
def get_type(self) -> WordType:
if self.__type is None:
self.__type = type_factory('Word', self.__type_id)
return self.__type
def get_freq(self) -> int:
return self.__frequency
def to_json(self) -> dict:
output = {
'id': self.get_id(),
'word': self.get_word(),
'type': self.get_type_id(),
'freq': self.get_freq()
}
return output
def __repr__(self):
return json.dumps(self.to_json())
def word_factory(word: dict) -> Word:
w = Word()
w.set_id(word['id'])
w.set_word(word['word'])
if 'type' in word:
w.set_type(word['type'])
if 'freq' in word:
w.add_freq(word['freq'] - 1)
return w
class Phrase:
def __init__(self):
self.__id = 0
self.__words = None
self.__type_id = 0
self.__type = None
self.__frequency = 1
def set_id(self, idx: int):
self.__id = idx
return self
def add_word(self, word):
if isinstance(word, Word):
self.__words.append(word.get_id())
if isinstance(word, dict):
if 'id' in word:
self.__words.append(word['id'])
if isinstance(word, int):
self.__words.append(word)
return self
def set_words(self, words: list):
if self.__words is None:
self.__words = []
for w in words:
if isinstance(w, Word):
self.add_word(w)
if isinstance(w, dict):
self.add_word(w)
if isinstance(w, int):
self.add_word(w)
return self
def set_type(self, phrase_type):
if isinstance(phrase_type, PhraseType):
self.__type_id = phrase_type.get_id()
# self.__type = phrase_type
if isinstance(phrase_type, int):
self.__type_id = phrase_type
# self.__type = type_factory('Phrase', phrase_type)
return self
def add_freq(self, amount: int = 1):
self.__frequency += amount
return self
def get_id(self) -> int:
return self.__id
def get_words(self) -> list:
return self.__words
def get_type_id(self) -> int:
return self.__type_id
def get_type(self) -> PhraseType:
if self.__type is None:
self.__type = type_factory('Phrase', self.__type_id)
return self.__type
def get_freq(self) -> int:
return self.__frequency
def match(self, word_list: list):
if len(word_list) != len(self.__words):
return False
new_words = sorted(self.__words)
new_list = sorted(word_list)
if new_words == new_list:
return True
return False
def to_json(self):
output = {
'id': self.get_id(),
'words': self.get_words(),
'type': self.get_type_id(),
'freq': self.get_freq()
}
return output
def __repr__(self):
return json.dumps(self.to_json())
def __len__(self):
return len(self.get_words())
def phrase_factory(phrase: dict) -> Phrase:
ph = Phrase()
ph.set_id(phrase['id'])
ph.set_words(phrase['words'])
if 'type' in phrase:
ph.set_type(phrase['type'])
if 'freq' in phrase:
ph.add_freq(phrase['freq'] - 1)
return ph

126
python/src/ai/network.py Normal file
View File

@ -0,0 +1,126 @@
import json
import os
import time
import timeit
import tensorflow as tf
import sklearn
import numpy as np
from sklearn.preprocessing import LabelEncoder
import src.contabilidad.pdf as pdf
import src.contabilidad.text_handler as th
class Layer:
def __init__(self):
self.__weights = None
self.__bias = None
def set_size(self, inputs: int, size: int):
self.__weights = [[0 for j in range(0, inputs)] for i in range(0, size)]
self.__bias = [0 for i in range(0, size)]
def add_weight(self, vector: list, idx: int = None):
if idx is None:
self.__weights.append(vector)
return self
self.__weights = self.__weights[:idx] + [vector] + self.__weights[idx:]
return self
def set_weight(self, value: float, weight_index: int, input_index: int):
self.__weights[weight_index][input_index] = value
def set_bias(self, value: list):
self.__bias = value
def train(self, input_values: list, output_values: list):
output = self.get_output(input_values)
errors = []
for i, v in enumerate(output):
error = (output_values[i] - v) / output_values[i]
new_value = v * error
def to_json(self):
return {
'bias': self.__bias,
'weights': self.__weights
}
def get_output(self, vector: list):
output = []
for i, weight in enumerate(self.__weights):
val = 0
for j, v in enumerate(weight):
val += v * vector[j]
output[i] = val + self.__bias[i]
return output
def layer_factory(layer_dict: dict):
layer = Layer()
layer.set_bias(layer_dict['bias'])
[layer.add_weight(w) for w in layer_dict['weights']]
return layer
class Network:
def __init__(self, filename: str):
self._filename = filename
self.__layers = None
def load(self):
with open(self._filename) as f:
data = json.load(f)
if 'layers' in data.keys():
self.add_layers(data['layers'])
def add_layers(self, layers: list):
for lr in layers:
layer = layer_factory(lr)
self.__layers.append(layer)
class AI:
def __init__(self, dictionary_filename, logger):
self.__dict = None
self.__network = None
self.__sources = None
self._phrases = None
self.filename = ''
def add_source(self, text):
if self.__sources is None:
self.__sources = []
self.__sources.append(text)
return self
def set_filename(self, filename: str):
self.filename = filename
return self
def process_sources(self):
for source in self.__sources:
self.process(**source)
def process(self, filename, password):
encoder = LabelEncoder()
t = filename.split('.')
temp = os.path.realpath(os.path.join(os.path.dirname(filename), t[0] + '-temp.pdf'))
pdf.remove_encryption(filename, password, temp)
obj = pdf.get_text(temp)
os.remove(temp)
word_list = th.split_words(obj)
fits = encoder.fit_transform(word_list)
phrases = []
for length in range(1, len(word_list) + 1):
for start in range(0, len(word_list)):
phrase = word_list[start:(start + length)]
phrase = np.append(np.array([fits[word_list.index(w)] for w in phrase]),
np.zeros([len(word_list) - len(phrase)]))
phrases.append(phrase)
phrases = np.array(phrases)
self._phrases = phrases
def active_train(self):
pass

View File

@ -1,22 +1,43 @@
import io
import json import json
import os import os
import sys import sys
from flask import Flask, request import httpx
from flask import Flask, request, jsonify
import contabilidad.pdf as pdf import contabilidad.pdf as pdf
import contabilidad.passwords as passwords import contabilidad.passwords as passwords
import contabilidad.log as log
import contabilidad.text_handler as th import contabilidad.text_handler as th
from contabilidad.log import Log
app = Flask(__name__) app = Flask(__name__)
log.logging['filename'] = '/var/log/python/contabilidad.log' log = Log('/var/log/python/contabilidad.log')
api_key = os.environ.get('PYTHON_KEY')
def validate_key(request_obj):
if 'Authorization' in request_obj.headers:
auth = request_obj.headers.get('Authorization')
if isinstance(auth, list):
auth = auth[0]
if 'Bearer' in auth:
try:
auth = auth.split(' ')[1]
except:
return False
return auth == api_key
if 'API_KEY' in request_obj.values:
return request_obj.values.get('API_KEY') == api_key
if 'api_key' in request_obj.values:
return request_obj.values.get('api_key') == api_key
return False
@app.route('/pdf/parse', methods=['POST']) @app.route('/pdf/parse', methods=['POST'])
def pdf_parse(): def pdf_parse():
if not validate_key(request):
return jsonify({'message': 'Not Authorized'})
data = request.get_json() data = request.get_json()
if not isinstance(data['files'], list): if not isinstance(data['files'], list):
data['files'] = [data['files']] data['files'] = [data['files']]
@ -32,6 +53,11 @@ def pdf_parse():
continue continue
pdf.remove_encryption(filename, p, temp) pdf.remove_encryption(filename, p, temp)
obj = pdf.get_data(temp) obj = pdf.get_data(temp)
try:
text = th.text_cleanup(pdf.get_text(temp))
except IndexError as ie:
print(ie, file=sys.stderr)
continue
outputs = [] outputs = []
for o in obj: for o in obj:
out = json.loads(o.df.to_json(orient='records')) out = json.loads(o.df.to_json(orient='records'))
@ -48,8 +74,35 @@ def pdf_parse():
out[i] = line out[i] = line
outputs.append(out) outputs.append(out)
os.remove(temp) os.remove(temp)
output.append({'filename': file['filename'], 'text': outputs}) output.append({'bank': text['bank'], 'filename': file['filename'], 'tables': outputs, 'text': text['text']})
return json.dumps(output) return jsonify(output)
@app.route('/cambio/get', methods=['POST'])
def cambios():
if not validate_key(request):
return jsonify({'message': 'Not Authorized'})
data = request.get_json()
valid = {
"CLF": "uf",
"IVP": "ivp",
"USD": "dolar",
"USDo": "dolar_intercambio",
"EUR": "euro",
"IPC": "ipc",
"UTM": "utm",
"IMACEC": "imacec",
"TPM": "tpm",
"CUP": "libra_cobre",
"TZD": "tasa_desempleo",
"BTC": "bitcoin"
}
base_url = 'https://mindicador.cl/api/'
url = f"{base_url}{valid[data['desde']]}/{'-'.join(list(reversed(data['fecha'].split('-'))))}"
res = httpx.get(url)
if res.status_code != httpx.codes.OK:
return jsonify({'error': 'Valor no encontrado.'})
return jsonify(res.json())
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -1,19 +1,65 @@
import os.path
import time import time
import traceback
logging = {
'filename': '/var/log/python/error.log'
}
class LOG_LEVEL: class LOG_LEVEL:
INFO = 'INFO' INFO = 0
WARNING = 'WARNING' WARNING = 1
DEBUG = 'DEBUG' DEBUG = 2
ERROR = 'ERROR' ERROR = 4
@staticmethod
def desc(level):
mapping = {
LOG_LEVEL.INFO: 'INFO',
LOG_LEVEL.WARNING: 'WARNING',
LOG_LEVEL.DEBUG: 'DEBUG',
LOG_LEVEL.ERROR: 'ERROR'
}
return mapping[level]
def log(message, level=LOG_LEVEL.INFO): class Logger:
filename = logging['filename'] def __init__(self):
with open(filename, 'a') as f: self._logs = []
f.write(time.strftime('[%Y-%m-%d %H:%M:%S] ') + ' - ' + level + ': ' + message)
def add_log(self, filename: str, min_level: int = LOG_LEVEL.INFO):
self._logs.append({'log': Log(filename), 'level': min_level})
self._logs.sort(key=lambda e: e['level'])
return self
def log(self, message, level: int = LOG_LEVEL.INFO):
for log in self._logs:
if log['level'] >= level:
log['log'].log(message, level)
class Log:
MAX_SIZE = 10 * 1024 * 1024
def __init__(self, filename: str = '/var/log/python/error.log'):
self._filename = filename
def log(self, message, level: int = LOG_LEVEL.INFO):
if isinstance(message, Exception):
message = traceback.format_exc()
if level < LOG_LEVEL.ERROR:
level = LOG_LEVEL.ERROR
self.rotate_file()
with open(self._filename, 'a') as f:
f.write(time.strftime('[%Y-%m-%d %H:%M:%S] ') + ' - ' + LOG_LEVEL.desc(level=level) + ': ' + message + "\n")
def rotate_file(self):
if not os.path.isfile(self._filename):
return
file_size = os.path.getsize(self._filename)
if file_size > self.MAX_SIZE:
self.next_file()
def next_file(self):
name = self._filename.split('.')
n = 1
if name[-2].isnumeric():
n = int(name[-2]) + 1
self._filename = '.'.join([name[0], str(n), name[-1]])

View File

@ -1,48 +1,112 @@
def text_cleanup(text, filename: str = None): def text_cleanup(text: str):
if isinstance(text, list): if isinstance(text, list):
output = [] text = "\n\n\n".join(text)
for t in text: if 'bice' in text.lower():
output.append(text_cleanup(t, filename=filename)) return {'bank': 'BICE', 'text': bice(text)}
return output if 'scotiabank' in text.lower():
if filename is None: return {'bank': 'Scotiabank', 'text': scotiabank(text)}
return text if 'TARJETA' in text:
if 'bice' in filename.lower(): return {'bank': 'Scotiabank', 'text': tarjeta(text)}
return bice(text) return {'bank': 'unknown', 'text': basic(text)}
if 'scotiabank' in filename.lower():
return scotiabank(text)
return text
def bice(text): def bice(text):
lines = text.split("\n\n\n") lines = [t2.strip() for t in text.split("\n\n\n")
print(lines) for t1 in t.split("\n\n") for t2 in t1.split("\n") if t2.strip() != '']
return text output = []
output += extract_from_to(lines, 'NOMBRE DEL CLIENTE', end='LAS CONDES', line_length=3)
ti = [t for t in lines if 'MOVIMIENTOS DE LA CUENTA CORRIENTE' in t][0]
output += extract_from_to(lines, 'LAS CONDES', end=ti, line_length=3)
output += [ti]
ti = [i for i, t in enumerate(lines) if 'FECHA' in t]
output += extract_from_to(lines, ti[0], end=ti[1], line_length=4)
output += extract_from_to(lines, 'RESUMEN DEL PERIODO', end='SALDO INICIAL', line_length=1)
output += extract_from_to(lines, 'SALDO INICIAL', end='LINEA SOBREGIRO AUTORIZADA', line_length=4)
output += extract_from_to(lines, 'LINEA SOBREGIRO AUTORIZADA', end='OBSERVACIONES', line_length=3)
output += extract_from_to(lines, 'OBSERVACIONES', line_length=1)
return output
def scotiabank(text): def scotiabank(text):
words = text.split("\n") words = split_words(text)
output = [words[0]] output = [words[0]]
output = output + extract_from_to(words, 'No. CTA.', end='VENCIMIENTO LINEA DE CREDITO', line_length=3) output += extract_from_to(words, 'No. CTA.', end='VENCIMIENTO LINEA DE CREDITO', line_length=3)
output = output + extract_from_to(words, 'VENCIMIENTO LINEA DE CREDITO', output += extract_from_to(words, 'VENCIMIENTO LINEA DE CREDITO',
end='NOMBRE EJECUTIVO: LILIAN AVILA MANRIQUEZ', line_length=2) end='NOMBRE EJECUTIVO: LILIAN AVILA MANRIQUEZ', line_length=2)
output = output + extract_from_to(words, 'NOMBRE EJECUTIVO: LILIAN AVILA MANRIQUEZ', end='SALDO ANTERIOR', output += extract_from_to(words, 'NOMBRE EJECUTIVO: LILIAN AVILA MANRIQUEZ', end='SALDO ANTERIOR',
line_length=1) line_length=1)
output = output + extract_from_to(words, 'SALDO ANTERIOR', end='FECHA', line_length=4) output += extract_from_to(words, 'SALDO ANTERIOR', end='FECHA', line_length=4)
output = output + extract_data(words, 'FECHA', end='ACTUALICE SIEMPRE ANTECEDENTES LEGALES, ', line_length=6, output += extract_data(words, 'FECHA', end='ACTUALICE SIEMPRE ANTECEDENTES LEGALES, ', line_length=6,
merge_list=[['DOCTO', 'No.'], ['SALDO', 'DIARIO']]) merge_list=[['DOCTO', 'No.'], ['SALDO', 'DIARIO']])
[print(li) for li in output] output += extract_from_to(words, 'ACTUALICE SIEMPRE ANTECEDENTES LEGALES, ', 1)
return text return output
def extract_from_to(word_list, start, line_length, end: str = None, merge_list=None): def tarjeta(text):
words = split_words(text)
output = ['ESTADO DE CUENTA NACIONAL DE TARJETA DE CRÉDITO']
i = [i for i, w in enumerate(words) if 'FECHA ESTADO DE CUENTA' in w][0] + 2
output += extract_from_to(words, 'NOMBRE DEL TITULAR', end=i, line_length=2)
output += ['I. INFORMACIóN GENERAL']
i = [i for i, w in enumerate(words) if 'CUPO TOTAL' in w][1]
output += extract_from_to(words, 'CUPO TOTAL', end=i, line_length=3)
output += extract_from_to(words, i, end='ROTATIVO', line_length=4)
output += extract_from_to(words, 'ROTATIVO', end='TASA INTERÉS VIGENTE', line_length=3)
output += extract_from_to(words, 'TASA INTERÉS VIGENTE',
end='CAE se calcula sobre un supuesto de gasto mensual de UF 20 y pagadero en 12 cuotas.',
line_length=4)
output += extract_from_to(words, 'DESDE', end='PERÍODO FACTURADO', line_length=2)
output += extract_from_to(words, 'PERÍODO FACTURADO', end='II.', line_length=3)
output += ['II. DETALLE']
output += extract_from_to(words, '1. PERÍODO ANTERIOR', end='SALDO ADEUDADO INICIO PERÍODO ANTERIOR', line_length=3)
i = words.index('2. PERÍODO ACTUAL')
output += extract_from_to(words, 'SALDO ADEUDADO INICIO PERÍODO ANTERIOR', end=i - 1, line_length=2,
merge_list=[['MONTO FACTURADO A PAGAR (PERÍODO ANTERIOR)', '(A)']], merge_character=" ")
output += ['2. PERÍODO ACTUAL']
output += extract_from_to(words, 'LUGAR DE', end='1.TOTAL OPERACIONES', line_length=7,
merge_list=[['OPERACIÓN', 'O COBRO'], ['TOTAL A', 'PAGAR'], ['VALOR CUOTA', 'MENSUAL']])
i = words.index('1.TOTAL OPERACIONES') + 3
output += extract_from_to(words, '1.TOTAL OPERACIONES', end=i, line_length=3)
output += extract_from_to(words, i, end='TOTAL PAGOS A LA CUENTA', line_length=7)
i = words.index('TOTAL PAGOS A LA CUENTA') + 2
output += extract_from_to(words, 'TOTAL PAGOS A LA CUENTA', end=i, line_length=2)
output += extract_from_to(words, i, end='TOTAL PAT A LA CUENTA', line_length=8)
i = words.index('TOTAL PAT A LA CUENTA') + 2
output += extract_from_to(words, 'TOTAL PAT A LA CUENTA', end=i, line_length=2)
output += extract_from_to(words, i, end=i + 3, line_length=2,
merge_list=[
['2.PRODUCTOS O SERVICIOS VOLUNTARIAMENTE CONTRATADOS SIN MOVIMIENTOS', '(C)']],
merge_character=" ")
if '3.CARGOS, COMISIONES, IMPUESTOS Y ABONOS' in words:
i = words.index('3.CARGOS, COMISIONES, IMPUESTOS Y ABONOS') + 3
output += extract_from_to(words, '3.CARGOS, COMISIONES, IMPUESTOS Y ABONOS', end=i, line_length=3)
return output
def basic(text):
return split_words(text)
def split_words(text):
if isinstance(text, list):
text = "\n\n\n".join(text)
words = [t.strip() for t in text.split("\n") if t.strip() != '']
return words
def extract_from_to(word_list, start, line_length, end=None, merge_list=None, merge_character="\n"):
if not isinstance(start, int):
start = word_list.index(start)
if end is not None: if end is not None:
return extract_by_line(word_list[word_list.index(start):word_list.index(end)], line_length, merge_list) if not isinstance(end, int):
return extract_by_line(word_list[word_list.index(start):], line_length, merge_list) end = word_list.index(end)
return extract_by_line(word_list[start:end], line_length, merge_list, merge_character)
return extract_by_line(word_list[start:], line_length, merge_list, merge_character)
def extract_by_line(word_list, line_length, merge_list=None): def extract_by_line(word_list, line_length, merge_list=None, merge_character="\n"):
if merge_list is not None: if merge_list is not None:
word_list = merge_words(word_list, merge_list) word_list = merge_words(word_list, merge_list, merge_character)
output = [] output = []
line = [] line = []
for k, w in enumerate(word_list): for k, w in enumerate(word_list):
@ -54,22 +118,39 @@ def extract_by_line(word_list, line_length, merge_list=None):
return output return output
def merge_words(word_list, merge_list): def merge_words(word_list, merge_list, merge_character):
for m in merge_list: for m in merge_list:
i = word_list.index(m[0]) ixs = find_words(word_list, m)
word_list = word_list[:i] + ["\n".join(m)] + word_list[i+len(m):] if ixs is None:
continue
for i in ixs:
word_list = word_list[:i] + [merge_character.join(m)] + word_list[i + len(m):]
return word_list return word_list
def extract_data(word_list, start, line_length, end=None, merge_list=None, date_sep='/'): def find_words(word_list, find_list):
ixs = [i for i, w in enumerate(word_list) if find_list[0] == w]
output = []
for i in ixs:
mistake = False
for k, m in enumerate(find_list):
if m != word_list[i + k]:
mistake = True
break
if mistake:
continue
output.append(i)
return output
def extract_data(word_list, start, line_length, end=None, merge_list=None, merge_character="\n", date_sep='/'):
word_list = word_list[word_list.index(start):] word_list = word_list[word_list.index(start):]
if end is not None: if end is not None:
word_list = word_list[:word_list.index(end)] word_list = word_list[:word_list.index(end)]
if merge_list is not None: if merge_list is not None:
word_list = merge_words(word_list, merge_list) word_list = merge_words(word_list, merge_list, merge_character)
output = [] output = []
line = [] line = []
line_num = 0
col = 0 col = 0
for k, w in enumerate(word_list): for k, w in enumerate(word_list):
if col > 0 and col % line_length == 0: if col > 0 and col % line_length == 0:
@ -87,4 +168,5 @@ def extract_data(word_list, start, line_length, end=None, merge_list=None, date_
continue continue
line.append(w) line.append(w)
col += 1 col += 1
output.append(line)
return output return output

View File

@ -3,22 +3,51 @@ import os
import contabilidad.pdf as pdf import contabilidad.pdf as pdf
import contabilidad.text_handler as th import contabilidad.text_handler as th
from contabilidad.log import Logger, LOG_LEVEL
import ai.dictionary as dictionary
from ai.network import AI
def parse_settings(args):
output = {'filename': args.filename}
if not os.path.isfile(output['filename']):
output['filename'] = os.path.realpath(os.path.join(os.path.dirname(__file__), '..', 'data', args.filename))
t = args.filename.split('.')
output['temp'] = os.path.realpath(os.path.join(os.path.dirname(output['filename']), t[0] + '-temp.pdf'))
output['dictionary'] = os.path.join(os.path.dirname(output['filename']), 'dictionary.json')
output['network'] = os.path.join(os.path.dirname(output['filename']), 'network.json')
output['log_file'] = args.log_file
if not os.path.isfile(output['log_file']):
output['log_file'] = os.path.join(os.path.dirname(os.path.dirname(output['filename'])), output['log_file'])
output['error_log_file'] = os.path.join(os.path.dirname(output['log_file']), 'error.log')
output['logger'] = Logger()
output['logger'].add_log(output['log_file']).add_log(output['error_log_file'], LOG_LEVEL.ERROR)
return output
def main(args): def main(args):
filename = os.path.realpath(os.path.join(os.path.dirname(__file__), '..', 'data', args.filename)) settings = parse_settings(args)
temp = os.path.realpath(os.path.join(os.path.dirname(__file__), '..', 'data', args.temp_filename))
pdf.remove_encryption(filename, args.password, temp) print('Loading AI')
obj = pdf.get_data(temp) network = AI(settings['dictionary'], settings['logger'])
obj = pdf.get_text(filename, args.password) network.set_filename(settings['network'])
text = th.text_cleanup(obj, filename=str(args.filename)) network.add_source({'filename': settings['filename'], 'password': args.password})
os.remove(temp) network.process_sources()
exit()
print('Loading dictionary.')
dictio = dictionary.Dictionary(settings['dictionary'], settings['logger'])
print('Getting possible phrases.')
dictio.process(settings['filename'], args.password)
dictio.to_data()
# print('Saving dictionary.')
# dictio.save()
if __name__ == '__main__': if __name__ == '__main__':
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument('-f', '--filename', type=str) parser.add_argument('-f', '--filename', type=str)
parser.add_argument('-p', '--password', type=str, default='') parser.add_argument('-p', '--password', type=str, default='')
parser.add_argument('-t', '--temp_filename', type=str) parser.add_argument('-l', '--log_file', type=str, default=None)
_args = parser.parse_args() _args = parser.parse_args()
main(_args) main(_args)

View File

@ -10,7 +10,8 @@ class Cuentas {
return $view->render($response, 'cuentas.list'); return $view->render($response, 'cuentas.list');
} }
public function show(Request $request, Response $response, View $view, $cuenta_id): Response { public function show(Request $request, Response $response, View $view, $cuenta_id): Response {
return $view->render($response, 'cuentas.show', compact('cuenta_id')); $max_transacciones = 100;
return $view->render($response, 'cuentas.show', compact('cuenta_id', 'max_transacciones'));
} }
public function add(Request $request, Response $response, View $view): Response { public function add(Request $request, Response $response, View $view): Response {
return $view->render($response, 'cuentas.add'); return $view->render($response, 'cuentas.add');

View File

@ -5,11 +5,11 @@
"require": { "require": {
"php-di/php-di": "^6.3", "php-di/php-di": "^6.3",
"php-di/slim-bridge": "^3.1", "php-di/slim-bridge": "^3.1",
"rubellum/slim-blade-view": "^0.1.1",
"nyholm/psr7-server": "^1.0", "nyholm/psr7-server": "^1.0",
"zeuxisoo/slim-whoops": "^0.7.3", "zeuxisoo/slim-whoops": "^0.7.3",
"nyholm/psr7": "^1.4", "nyholm/psr7": "^1.4",
"guzzlehttp/guzzle": "^7.4" "guzzlehttp/guzzle": "^7.4",
"berrnd/slim-blade-view": "^1.0"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "^9.5", "phpunit/phpunit": "^9.5",

View File

@ -1,5 +1,5 @@
class Transaccion { class Transaccion {
constructor({id, debito_id, credito_id, fecha, glosa, detalle, valor, debito, credito, fechaFormateada, valorFormateado}) { constructor({id, debito_id, credito_id, fecha, glosa, detalle, valor, debito, credito, fechaFormateada, valorFormateado, format}) {
this.id = id this.id = id
this.debito_id = debito_id this.debito_id = debito_id
this.credito_id = credito_id this.credito_id = credito_id
@ -13,6 +13,7 @@ class Transaccion {
valor, valor,
formateado: valorFormateado formateado: valorFormateado
} }
this.format_array = format
this.debito = debito this.debito = debito
this.credito = credito this.credito = credito
this.modal = null this.modal = null
@ -33,7 +34,7 @@ class Transaccion {
} }
return !this.isDebito() return !this.isDebito()
} }
draw({saldo, format}) { draw({saldo, format, format_array, format_call}) {
const fuente = (this.isDebito()) ? this.credito : this.debito const fuente = (this.isDebito()) ? this.credito : this.debito
return $('<tr></tr>').append( return $('<tr></tr>').append(
$('<td></td>').html(this.fecha.formateada) $('<td></td>').html(this.fecha.formateada)
@ -44,11 +45,11 @@ class Transaccion {
).append( ).append(
$('<td></td>').html(this.glosa + '<br />' + this.detalle) $('<td></td>').html(this.glosa + '<br />' + this.detalle)
).append( ).append(
$('<td></td>').attr('class', 'right aligned').html((this.isIncrement()) ? '' : format.format(this.valor.valor)) $('<td></td>').attr('class', 'right aligned').html((this.isIncrement()) ? '' : format_call({value: this.valor.valor, format, format_array}))
).append( ).append(
$('<td></td>').attr('class', 'right aligned').html((this.isIncrement()) ? format.format(this.valor.valor) : '') $('<td></td>').attr('class', 'right aligned').html((this.isIncrement()) ? format_call({value: this.valor.valor, format, format_array}) : '')
).append( ).append(
$('<td></td>').attr('class', 'right aligned').html(format.format(saldo)) $('<td></td>').attr('class', 'right aligned').html(format_call({value: saldo, format_array, format}))
).append( ).append(
$('<td></td>').attr('class', 'right aligned').append( $('<td></td>').attr('class', 'right aligned').append(
$('<button></button>').attr('class', 'ui tiny circular icon button').append( $('<button></button>').attr('class', 'ui tiny circular icon button').append(
@ -90,11 +91,33 @@ class Transaccion {
const transacciones = { const transacciones = {
id: '#transacciones', id: '#transacciones',
mes: '#mes',
buttons: {
prev: '#prev_button',
left: '#left_button',
right: '#right_button',
next: '#next_button'
},
cuenta_id: 0, cuenta_id: 0,
cuenta: null, cuenta: null,
transacciones: [], transacciones: [],
cuentas: [], cuentas: [],
date: new Date(),
saldo: 0, saldo: 0,
acumulation: 0,
intl_format: null,
max: null,
format: ({format_array, format, value}) => {
let output = []
if (format_array.prefijo !== '') {
output.push(format_array.prefijo)
}
output.push(format.format(Math.round(value * Math.pow(10, format_array.decimales)) / Math.pow(10, format_array.decimales)))
if (format_array.sufijo !== '') {
output.push(format_array.sufijo)
}
return output.join('')
},
get: function() { get: function() {
return { return {
transacciones: () => { transacciones: () => {
@ -105,20 +128,15 @@ const transacciones = {
return return
} }
this.cuenta = data.cuenta this.cuenta = data.cuenta
this.intl_format = Intl.NumberFormat('es-CL')
sendGet(_urls.api + '/cuenta/' + this.cuenta_id + '/categoria').then((resp) => { sendGet(_urls.api + '/cuenta/' + this.cuenta_id + '/categoria').then((resp) => {
this.cuenta.categoria = resp.categoria this.cuenta.categoria = resp.categoria
}).then(() => { }).then(() => {
this.saldo = this.cuenta.saldo //this.saldo = this.cuenta.saldo
$('#cuenta').html(this.cuenta.nombre + ' (' + this.cuenta.categoria.nombre + ')').append( $('#cuenta').html(this.cuenta.nombre + ' (' + this.cuenta.categoria.nombre + ')').append(
$('<i></i>').attr('class', 'square full icon').css('color', '#' + this.cuenta.tipo.color) $('<i></i>').attr('class', 'square full icon').css('color', '#' + this.cuenta.tipo.color)
) )
const amount = data.transacciones promises = this.get().transaccionesMes(this.date)
const step = 50
for (let i = 0; i < amount; i += step) {
promises.push(
sendGet(_urls.api + '/cuenta/' + this.cuenta_id + '/transacciones/' + step + '/' + i)
)
}
if (promises.length > 0) { if (promises.length > 0) {
Promise.all(promises).then((data_arr) => { Promise.all(promises).then((data_arr) => {
this.transacciones = [] this.transacciones = []
@ -137,7 +155,13 @@ const transacciones = {
return (new Date(b.fecha)) - (new Date(a.fecha)) return (new Date(b.fecha)) - (new Date(a.fecha))
}) })
}).then(() => { }).then(() => {
this.draw().table() this.get().transaccionesAcumulacion(this.date).then((response) => {
this.acumulation = response.acumulation
this.saldo = response.acumulation
}).then(() => {
this.draw().table()
this.draw().acumulation()
})
}) })
} else { } else {
this.draw().table() this.draw().table()
@ -145,6 +169,32 @@ const transacciones = {
}) })
}) })
}, },
transaccionesLimit: () => {
let promises = []
const amount = data.transacciones
let step = 50
let start = 0
if (this.max !== null) {
step = Math.min(50, this.max)
start = Math.max(0, amount - this.max)
}
for (let i = start; i <= amount; i += step) {
promises.push(
sendGet(_urls.api + '/cuenta/' + this.cuenta_id + '/transacciones/' + step + '/' + i)
)
}
return promises
},
transaccionesMes: (mes) => {
let promises = []
promises.push(
sendGet(_urls.api + '/cuenta/' + this.cuenta_id + '/transacciones/month/' + [mes.getFullYear(), mes.getMonth() + 1, '1'].join('-'))
)
return promises
},
transaccionesAcumulacion: (mes) => {
return sendGet(_urls.api + '/cuenta/' + this.cuenta_id + '/transacciones/acum/' + [mes.getFullYear(), mes.getMonth() + 1, '1'].join('-'))
},
cuentas: () => { cuentas: () => {
return sendGet(_urls.api + '/cuentas').then((data) => { return sendGet(_urls.api + '/cuentas').then((data) => {
if (data.cuentas === null || data.cuentas.length === 0) { if (data.cuentas === null || data.cuentas.length === 0) {
@ -185,13 +235,36 @@ const transacciones = {
) )
}, },
table: () => { table: () => {
const format = Intl.NumberFormat('es-CL', {style: 'currency', currency: this.cuenta.moneda.codigo})
const parent = $(this.id) const parent = $(this.id)
parent.html('') parent.html('')
$.each(this.transacciones, (i, el) => { $.each(this.transacciones, (i, el) => {
parent.append(el.draw({saldo: this.saldo, format: format}))
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}))
}) })
},
acumulation: () => {
const parent = $(this.id)
parent.prepend(
$('<tr></tr>').append(
$('<td></td>').html('')
).append(
$('<td></td>').html('')
).append(
$('<td></td>').html('Acumulacion Anterior')
).append(
$('<td></td>').attr('class', 'right aligned').html('')
).append(
$('<td></td>').attr('class', 'right aligned').html('')
).append(
$('<td></td>').attr('class', 'right aligned').html(this.format({
format_array: this.cuenta.moneda.format,
format: this.intl_format,
value: this.acumulation
}))
).append(
$('<td></td>').attr('class', 'right aligned').html('')
)
)
} }
} }
}, },
@ -231,8 +304,10 @@ const transacciones = {
edit: function() { edit: function() {
const id = $("[name='id']").val() const id = $("[name='id']").val()
const fecha = $("[name='fecha']").val() const fecha = $("[name='fecha']").val()
const valor = $("[name='valor']").val()
const cuenta = $("[name='cuenta']").val() const cuenta = $("[name='cuenta']").val()
const glosa = $("[name='glosa']").val()
const detalle = $("[name='detalle']").val()
const valor = $("[name='valor']").val()
const data = JSON.stringify({ const data = JSON.stringify({
debito_id: (valor < 0) ? this.cuenta_id : cuenta, debito_id: (valor < 0) ? this.cuenta_id : cuenta,
credito_id: (valor < 0) ? cuenta : this.cuenta_id, credito_id: (valor < 0) ? cuenta : this.cuenta_id,
@ -246,6 +321,33 @@ const transacciones = {
this.get().transacciones() this.get().transacciones()
}) })
}, },
changeMonth: function(dif) {
let d = this.date
d.setMonth(this.date.getMonth() + dif)
this.date = d
this.checkButtons()
$(this.mes).calendar('set date', this.date)
},
changeYear: function(dif) {
let d = this.date
d.setFullYear(this.date.getFullYear() + dif)
this.date = d
this.checkButtons()
$(this.mes).calendar('set date', this.date)
},
checkButtons: function() {
let f = new Date()
if (this.date.getMonth() === f.getMonth() && this.date.getFullYear() === f.getFullYear()) {
$(this.buttons.right).addClass('disabled')
} else {
$(this.buttons.right).removeClass('disabled')
}
if (this.date.getFullYear() === f.getFullYear()) {
$(this.buttons.next).addClass('disabled')
} else {
$(this.buttons.next).removeClass('disabled')
}
},
refresh: function () { refresh: function () {
this.get().transacciones() this.get().transacciones()
}, },
@ -281,11 +383,49 @@ const transacciones = {
maxDate: new Date() maxDate: new Date()
}) })
this.get().cuentas() this.get().cuentas()
},
mes: () => {
const meses = ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Dicembre']
$(this.mes).calendar({
type: 'month',
initialDate: this.date,
maxDate: new Date(),
text: {
months: meses,
monthsShort: meses.map((item) => {item.slice(0, 3)})
},
formatter: {
date: function (date, settings) {
if (!date) return '';
return meses[date.getMonth()] + ', ' + date.getFullYear()
}
},
onChange: (date) => {
this.date = date
this.refresh()
}
})
},
buttons: () => {
$(this.buttons.right).click((e) => {
this.changeMonth(1)
})
$(this.buttons.left).click((e) => {
this.changeMonth(-1)
})
$(this.buttons.next).click(() => {
this.changeYear(1)
})
$(this.buttons.prev).click(() => {
this.changeYear(-1)
})
} }
} }
}, },
setup: function() { setup: function() {
this.build().modal() this.build().modal()
this.build().mes()
this.build().buttons()
$(this.id).parent().find('#refresh').click(() => { $(this.id).parent().find('#refresh').click(() => {
this.refresh() this.refresh()
}) })

View File

@ -59,15 +59,12 @@ class Cuenta {
} }
} }
class Categoria { class Categoria {
constructor({id, nombre, tipo_id, tipo, activos, pasivos, ganancias, perdidas}) { constructor({id, nombre, tipo_id, tipo, totales}) {
this.id = id this.id = id
this.nombre = nombre this.nombre = nombre
this.tipo_id = tipo_id this.tipo_id = tipo_id
this.tipo = tipo this.tipo = tipo
this.activos = activos this.totales = totales
this.pasivos = pasivos
this.ganancias = ganancias
this.perdidas = perdidas
this.is_open = false this.is_open = false
this.cuentas = [] this.cuentas = []
} }
@ -75,7 +72,7 @@ class Categoria {
this.tipos = tipos this.tipos = tipos
} }
draw({format}) { draw({format}) {
const button = $('<button></button>').attr('class', 'ui mini compact icon button').append( const button = $('<button></button>').attr('class', 'ui mini compact circular icon button').append(
$('<i></i>').attr('class', down_icon + ' icon') $('<i></i>').attr('class', down_icon + ' icon')
).click((e) => { ).click((e) => {
const plus = button.find('.' + down_icon.replace(' ', '.') + '.icon') const plus = button.find('.' + down_icon.replace(' ', '.') + '.icon')
@ -98,7 +95,7 @@ class Categoria {
) )
$.each(this.tipos, (i, el) => { $.each(this.tipos, (i, el) => {
tr.append( tr.append(
$('<td></td>').attr('class', 'right aligned').html(format.format(this[el.descripcion.toLowerCase() + 's'])) $('<td></td>').attr('class', 'right aligned').html(format.format(this.totales[el.descripcion.toLowerCase() + 's']))
) )
}) })
$("[data-id='" + this.tipo_id + "'][data-class='tipo_categoria']").after(tr) $("[data-id='" + this.tipo_id + "'][data-class='tipo_categoria']").after(tr)
@ -156,21 +153,18 @@ class Categoria {
} }
} }
class TipoCategoria { class TipoCategoria {
constructor({id, descripcion, activo, activos, pasivos, ganancias, perdidas}) { constructor({id, descripcion, activo, totales}) {
this.id = id this.id = id
this.descripcion = descripcion this.descripcion = descripcion
this.activo = activo this.activo = activo
this.activos = activos this.totales = totales
this.pasivos = pasivos
this.ganancias = ganancias
this.perdidas = perdidas
this.categorias = [] this.categorias = []
} }
setTipos(tipos) { setTipos(tipos) {
this.tipos = tipos this.tipos = tipos
} }
draw({format}) { draw({format}) {
const button = $('<button></button>').attr('class', 'ui mini compact icon button').append( const button = $('<button></button>').attr('class', 'ui mini compact circular icon button').append(
$('<i></i>').attr('class', down_icon + ' icon') $('<i></i>').attr('class', down_icon + ' icon')
).click((e) => { ).click((e) => {
const plus = button.find('.' + down_icon.replace(' ', '.') + '.icon') const plus = button.find('.' + down_icon.replace(' ', '.') + '.icon')
@ -188,7 +182,7 @@ class TipoCategoria {
) )
) )
$.each(this.tipos, (i, el) => { $.each(this.tipos, (i, el) => {
tr.append($('<td></td>').attr('class', 'right aligned').html(format.format(this[el.descripcion.toLowerCase() + 's']))) tr.append($('<td></td>').attr('class', 'right aligned').html(format.format(this.totales[el.descripcion.toLowerCase() + 's'])))
}) })
return tr return tr
} }
@ -245,6 +239,29 @@ const cuentas = {
table.append(parent) table.append(parent)
segment.append(table) segment.append(table)
return parent return parent
},
resultado: (segment) => {
segment.append(
$('<table></table>').attr('class', 'ui collapsing table').append(
$('<tr></tr>').append(
$('<td></td>').html('Ganancias')
).append(
$('<td></td>').attr('data-tipo', 'ganancias')
)
).append(
$('<tr></tr>').append(
$('<td></td>').html('Perdidas')
).append(
$('<td></td>').attr('data-tipo', 'perdidas')
)
).append(
$('<tr></tr>').append(
$('<td></td>').html('<b>Resultado</b>')
).append(
$('<td></td>').attr('data-tipo', 'resultado')
)
)
)
} }
} }
}, },
@ -272,7 +289,7 @@ const cuentas = {
return return
} }
$.each(data.tipos, (i, el) => { $.each(data.tipos, (i, el) => {
tipo = new TipoCategoria(el) const tipo = new TipoCategoria(el)
tipo.setTipos(this.tipos) tipo.setTipos(this.tipos)
this.tipos_categorias.push(tipo) this.tipos_categorias.push(tipo)
}) })
@ -285,6 +302,8 @@ const cuentas = {
this.balance = data this.balance = data
}).then(() => { }).then(() => {
this.draw().balance() this.draw().balance()
}).then(() => {
this.draw().resultado()
}) })
} }
} }
@ -320,6 +339,17 @@ const cuentas = {
) )
}) })
foot.append(tr) foot.append(tr)
},
resultado: () => {
const div = $('#resultado')
if (div.find("[data-tipo='resultado']").length === 0) {
div.html('')
this.build().resultado(div)
}
const format = Intl.NumberFormat('es-CL', {style: 'currency', currency: 'CLP'})
div.find("[data-tipo='ganancias']").html(format.format(this.balance['ganancias']))
div.find("[data-tipo='perdidas']").html(format.format(this.balance['perdidas']))
div.find("[data-tipo='resultado']").html('<b>' + format.format(this.balance['ganancias'] - this.balance['perdidas']) + '</b>')
} }
} }
}, },

View File

@ -5,6 +5,26 @@
@endsection @endsection
@section('cuentas_content') @section('cuentas_content')
<div class="ui right aligned header">
<div class="ui calendar" id="mes">
<div class="ui input right icon">
<input type="text" placeholder="Mes" />
<i class="calendar icon"></i>
</div>
</div>
<div class="ui icon button" id="prev_button">
<i class="double left angle icon"></i>
</div>
<div class="ui icon button" id="left_button">
<i class="left angle icon"></i>
</div>
<div class="ui icon disabled button" id="right_button">
<i class="right angle icon"></i>
</div>
<div class="ui icon disabled button" id="next_button">
<i class="double right angle icon"></i>
</div>
</div>
<table class="ui striped table"> <table class="ui striped table">
<thead> <thead>
<tr> <tr>
@ -82,6 +102,7 @@
<script type="text/javascript"> <script type="text/javascript">
$(document).ready(() => { $(document).ready(() => {
transacciones.cuenta_id = {{$cuenta_id}} transacciones.cuenta_id = {{$cuenta_id}}
transacciones.max = {{$max_transacciones ?? 100}}
transacciones.setup() transacciones.setup()
}) })
</script> </script>

View File

@ -5,6 +5,7 @@
Contabilidad Contabilidad
</h1> </h1>
<div id="cuentas" class="ui basic fitted segment"></div> <div id="cuentas" class="ui basic fitted segment"></div>
<div id="resultado" class="ui basic fitted segment"></div>
@endsection @endsection
@push('scripts') @push('scripts')

View File

@ -22,5 +22,6 @@ return [
]); ]);
$arr['api'] = $_ENV['API_URL'] ?? 'http://localhost:9001'; $arr['api'] = $_ENV['API_URL'] ?? 'http://localhost:9001';
return (object) $arr; return (object) $arr;
} },
'max_transacciones' => 100
]; ];