PDF reading with python

This commit is contained in:
2021-11-01 11:00:59 -03:00
parent 9f301e2175
commit 5ee267568a
74 changed files with 1092 additions and 26 deletions

8
.idea/.gitignore generated vendored Normal file
View File

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# Editor-based HTTP Client requests
/httpRequests/

5
.idea/codeStyles/codeStyleConfig.xml generated Normal file
View File

@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
</state>
</component>

81
.idea/contabilidad.iml generated Normal file
View File

@ -0,0 +1,81 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/kint-php/kint" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/zeuxisoo/slim-whoops" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/phar-io/version" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/phar-io/manifest" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/theseer/tokenizer" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/webmozart/assert" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/philo/laravel-blade" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/nesbot/carbon" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/sebastian/type" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/sebastian/diff" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/illuminate/support" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/sebastian/lines-of-code" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/illuminate/filesystem" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/sebastian/object-enumerator" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/illuminate/events" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/sebastian/comparator" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/illuminate/contracts" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/psr/simple-cache" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/sebastian/code-unit" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/illuminate/view" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/psr/http-server-handler" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/doctrine/instantiator" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/sebastian/exporter" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/illuminate/container" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/psr/http-factory" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/doctrine/inflector" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/sebastian/cli-parser" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/psr/container" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/sebastian/version" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/psr/http-message" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/sebastian/complexity" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/psr/http-server-middleware" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/sebastian/recursion-context" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/psr/log" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/sebastian/resource-operations" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/sebastian/global-state" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/composer" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/sebastian/environment" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/phpdocumentor/reflection-docblock" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/sebastian/code-unit-reverse-lookup" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/phpdocumentor/type-resolver" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/symfony/polyfill-mbstring" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/sebastian/object-reflector" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/phpdocumentor/reflection-common" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/symfony/finder" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/symfony/deprecation-contracts" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/symfony/translation-contracts" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/filp/whoops" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/symfony/polyfill-ctype" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/symfony/debug" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/nyholm/psr7-server" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/symfony/translation" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/nyholm/psr7" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/symfony/polyfill-php80" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/nikic/php-parser" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/nikic/fast-route" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/php-di/php-di" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/php-di/phpdoc-reader" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/php-di/slim-bridge" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/php-di/invoker" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/phpunit/php-code-coverage" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/phpunit/phpunit" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/phpunit/php-text-template" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/phpunit/php-invoker" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/phpunit/php-file-iterator" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/phpunit/php-timer" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/myclabs/deep-copy" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/phpspec/prophecy" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/opis/closure" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/php-http/message-factory" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/rubellum/slim-blade-view" />
<excludeFolder url="file://$MODULE_DIR$/ui/vendor/slim/slim" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

8
.idea/modules.xml generated Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/contabilidad.iml" filepath="$PROJECT_DIR$/.idea/contabilidad.iml" />
</modules>
</component>
</project>

87
.idea/php.xml generated Normal file
View File

@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PhpIncludePathManager">
<include_path>
<path value="$PROJECT_DIR$/ui/vendor/kint-php/kint" />
<path value="$PROJECT_DIR$/ui/vendor/zeuxisoo/slim-whoops" />
<path value="$PROJECT_DIR$/ui/vendor/phar-io/version" />
<path value="$PROJECT_DIR$/ui/vendor/phar-io/manifest" />
<path value="$PROJECT_DIR$/ui/vendor/theseer/tokenizer" />
<path value="$PROJECT_DIR$/ui/vendor/webmozart/assert" />
<path value="$PROJECT_DIR$/ui/vendor/philo/laravel-blade" />
<path value="$PROJECT_DIR$/ui/vendor/nesbot/carbon" />
<path value="$PROJECT_DIR$/ui/vendor/sebastian/type" />
<path value="$PROJECT_DIR$/ui/vendor/sebastian/diff" />
<path value="$PROJECT_DIR$/ui/vendor/illuminate/support" />
<path value="$PROJECT_DIR$/ui/vendor/sebastian/lines-of-code" />
<path value="$PROJECT_DIR$/ui/vendor/illuminate/filesystem" />
<path value="$PROJECT_DIR$/ui/vendor/sebastian/object-enumerator" />
<path value="$PROJECT_DIR$/ui/vendor/illuminate/events" />
<path value="$PROJECT_DIR$/ui/vendor/sebastian/comparator" />
<path value="$PROJECT_DIR$/ui/vendor/illuminate/contracts" />
<path value="$PROJECT_DIR$/ui/vendor/psr/simple-cache" />
<path value="$PROJECT_DIR$/ui/vendor/sebastian/code-unit" />
<path value="$PROJECT_DIR$/ui/vendor/illuminate/view" />
<path value="$PROJECT_DIR$/ui/vendor/psr/http-server-handler" />
<path value="$PROJECT_DIR$/ui/vendor/doctrine/instantiator" />
<path value="$PROJECT_DIR$/ui/vendor/sebastian/exporter" />
<path value="$PROJECT_DIR$/ui/vendor/illuminate/container" />
<path value="$PROJECT_DIR$/ui/vendor/psr/http-factory" />
<path value="$PROJECT_DIR$/ui/vendor/doctrine/inflector" />
<path value="$PROJECT_DIR$/ui/vendor/sebastian/cli-parser" />
<path value="$PROJECT_DIR$/ui/vendor/psr/container" />
<path value="$PROJECT_DIR$/ui/vendor/sebastian/version" />
<path value="$PROJECT_DIR$/ui/vendor/psr/http-message" />
<path value="$PROJECT_DIR$/ui/vendor/sebastian/complexity" />
<path value="$PROJECT_DIR$/ui/vendor/psr/http-server-middleware" />
<path value="$PROJECT_DIR$/ui/vendor/sebastian/recursion-context" />
<path value="$PROJECT_DIR$/ui/vendor/psr/log" />
<path value="$PROJECT_DIR$/ui/vendor/sebastian/resource-operations" />
<path value="$PROJECT_DIR$/ui/vendor/sebastian/global-state" />
<path value="$PROJECT_DIR$/ui/vendor/composer" />
<path value="$PROJECT_DIR$/ui/vendor/sebastian/environment" />
<path value="$PROJECT_DIR$/ui/vendor/phpdocumentor/reflection-docblock" />
<path value="$PROJECT_DIR$/ui/vendor/sebastian/code-unit-reverse-lookup" />
<path value="$PROJECT_DIR$/ui/vendor/phpdocumentor/type-resolver" />
<path value="$PROJECT_DIR$/ui/vendor/symfony/polyfill-mbstring" />
<path value="$PROJECT_DIR$/ui/vendor/sebastian/object-reflector" />
<path value="$PROJECT_DIR$/ui/vendor/phpdocumentor/reflection-common" />
<path value="$PROJECT_DIR$/ui/vendor/symfony/finder" />
<path value="$PROJECT_DIR$/ui/vendor/symfony/deprecation-contracts" />
<path value="$PROJECT_DIR$/ui/vendor/symfony/translation-contracts" />
<path value="$PROJECT_DIR$/ui/vendor/filp/whoops" />
<path value="$PROJECT_DIR$/ui/vendor/symfony/polyfill-ctype" />
<path value="$PROJECT_DIR$/ui/vendor/symfony/debug" />
<path value="$PROJECT_DIR$/ui/vendor/nyholm/psr7-server" />
<path value="$PROJECT_DIR$/ui/vendor/symfony/translation" />
<path value="$PROJECT_DIR$/ui/vendor/nyholm/psr7" />
<path value="$PROJECT_DIR$/ui/vendor/symfony/polyfill-php80" />
<path value="$PROJECT_DIR$/ui/vendor/nikic/php-parser" />
<path value="$PROJECT_DIR$/ui/vendor/nikic/fast-route" />
<path value="$PROJECT_DIR$/ui/vendor/php-di/php-di" />
<path value="$PROJECT_DIR$/ui/vendor/php-di/phpdoc-reader" />
<path value="$PROJECT_DIR$/ui/vendor/php-di/slim-bridge" />
<path value="$PROJECT_DIR$/ui/vendor/php-di/invoker" />
<path value="$PROJECT_DIR$/ui/vendor/phpunit/php-code-coverage" />
<path value="$PROJECT_DIR$/ui/vendor/phpunit/phpunit" />
<path value="$PROJECT_DIR$/ui/vendor/phpunit/php-text-template" />
<path value="$PROJECT_DIR$/ui/vendor/phpunit/php-invoker" />
<path value="$PROJECT_DIR$/ui/vendor/phpunit/php-file-iterator" />
<path value="$PROJECT_DIR$/ui/vendor/phpunit/php-timer" />
<path value="$PROJECT_DIR$/ui/vendor/myclabs/deep-copy" />
<path value="$PROJECT_DIR$/ui/vendor/phpspec/prophecy" />
<path value="$PROJECT_DIR$/ui/vendor/opis/closure" />
<path value="$PROJECT_DIR$/ui/vendor/php-http/message-factory" />
<path value="$PROJECT_DIR$/ui/vendor/rubellum/slim-blade-view" />
<path value="$PROJECT_DIR$/ui/vendor/slim/slim" />
</include_path>
</component>
<component name="PhpProjectSharedConfiguration" php_language_level="8.0">
<option name="suggestChangeDefaultLanguageLevel" value="false" />
</component>
<component name="PhpUnit">
<phpunit_settings>
<PhpUnitSettings custom_loader_path="$PROJECT_DIR$/ui/vendor/autoload.php" />
</phpunit_settings>
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

11
TODO.md Normal file
View File

@ -0,0 +1,11 @@
# Contabilidad
1. Obtener pdf de cartolas desde email.
1. Conectar a Email por IMAP.
1. Buscar emails con cartolas.
1. Descargar cartolas.
1. Guardar de forma ordenada.
1. Extraer información e ingresar a base de datos a traves de API.
1. Abrir archivos y leer.
1. Formatear datos.
1. Mandar a API.

View File

@ -1,5 +1,9 @@
FROM php:8-fpm
RUN apt-get update -y && apt-get install -y git
RUN docker-php-ext-install pdo pdo_mysql
COPY --from=composer /usr/bin/composer /usr/bin/composer
WORKDIR /app

View File

@ -0,0 +1,21 @@
<?php
namespace Contabilidad\Common\Controller;
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;
class Import {
use Json;
public function __invoke(Request $request, Response $response, Factory $factory): Response {
$post = $request->getParsedBody();
return $this->withJson($response, $post);
}
public function uploads(Request $request, Response $response, PdfHandler $handler): Response {
$output = $handler->load();
return $this->withJson($response, $output);
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace Contabilidad\Common\Service;
use GuzzleHttp\Client;
class PdfHandler {
protected Client $client;
protected string $folder;
protected string $url;
public function __construct(Client $client, string $pdf_folder, string $url) {
$this->client = $client;
$this->folder = $pdf_folder;
$this->url = $url;
}
public function load(): ?array {
$folder = $this->folder;
$files = new \DirectoryIterator($folder);
$output = [];
foreach ($files as $file) {
if ($file->isDir() or $file->getExtension() != 'pdf') {
continue;
}
$output []= ['filename' => $file->getBasename()];
}
$response = $this->client->post($this->url, ['json' => ['files' => $output]]);
!d(json_decode($response->getBody()));
return $output;
}
}

View File

@ -10,7 +10,11 @@
"zeuxisoo/slim-whoops": "^0.7.3",
"provm/controller": "^1.0",
"provm/models": "^1.0.0-rc3",
"nesbot/carbon": "^2.50"
"nesbot/carbon": "^2.50",
"robmorgan/phinx": "^0.12.9",
"odan/phinx-migrations-generator": "^5.4",
"martin-mikac/csv-to-phinx-seeder": "^1.6",
"guzzlehttp/guzzle": "^7.4"
},
"require-dev": {
"phpunit/phpunit": "^9.5",

View File

@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
use Phinx\Migration\AbstractMigration;
final class TipoCategoria extends AbstractMigration
{
/**
* Change Method.
*
* Write your reversible migrations using this method.
*
* More information on writing migrations is available here:
* https://book.cakephp.org/phinx/0/en/migrations.html#the-change-method
*
* Remember to call "create()" or "update()" and NOT "save()" when working
* with the Table class.
*/
public function change(): void
{
$this->table('tipos_categoria')
->addColumn('descripcion', 'string')
->addColumn('activo', 'boolean')
->create();
}
}

View File

@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
use Phinx\Migration\AbstractMigration;
final class TipoEstadoConeccion extends AbstractMigration
{
/**
* Change Method.
*
* Write your reversible migrations using this method.
*
* More information on writing migrations is available here:
* https://book.cakephp.org/phinx/0/en/migrations.html#the-change-method
*
* Remember to call "create()" or "update()" and NOT "save()" when working
* with the Table class.
*/
public function change(): void
{
$this->table('tipos_estado_coneccion')
->addColumn('descripcion', 'string')
->create();
}
}

View File

@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
use Phinx\Migration\AbstractMigration;
final class TipoCuenta extends AbstractMigration
{
/**
* Change Method.
*
* Write your reversible migrations using this method.
*
* More information on writing migrations is available here:
* https://book.cakephp.org/phinx/0/en/migrations.html#the-change-method
*
* Remember to call "create()" or "update()" and NOT "save()" when working
* with the Table class.
*/
public function change(): void
{
$this->table('tipos_cuenta')
->addColumn('descripcion', 'string')
->create();
}
}

View File

@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
use Phinx\Migration\AbstractMigration;
final class Categoria extends AbstractMigration
{
/**
* Change Method.
*
* Write your reversible migrations using this method.
*
* More information on writing migrations is available here:
* https://book.cakephp.org/phinx/0/en/migrations.html#the-change-method
*
* Remember to call "create()" or "update()" and NOT "save()" when working
* with the Table class.
*/
public function change(): void
{
$this->table('categorias')
->addColumn('nombre', 'string')
->addColumn('tipo_id', 'integer')
->addForeignKey('tipo_id', 'tipos_categoria')
->create();
}
}

View File

@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
use Phinx\Migration\AbstractMigration;
final class Coneccion extends AbstractMigration
{
/**
* Change Method.
*
* Write your reversible migrations using this method.
*
* More information on writing migrations is available here:
* https://book.cakephp.org/phinx/0/en/migrations.html#the-change-method
*
* Remember to call "create()" or "update()" and NOT "save()" when working
* with the Table class.
*/
public function change(): void
{
$this->table('conecciones')
->addColumn('key', 'string')
->create();
}
}

View File

@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
use Phinx\Migration\AbstractMigration;
final class Cuenta extends AbstractMigration
{
/**
* Change Method.
*
* Write your reversible migrations using this method.
*
* More information on writing migrations is available here:
* https://book.cakephp.org/phinx/0/en/migrations.html#the-change-method
*
* Remember to call "create()" or "update()" and NOT "save()" when working
* with the Table class.
*/
public function change(): void
{
$this->table('cuentas')
->addColumn('nombre', 'string')
->addColumn('categoria_id', 'integer')
->addForeignKey('categoria_id', 'categorias')
->addColumn('tipo_id', 'integer')
->addForeignKey('tipo_id', 'tipos_cuenta')
->create();
}
}

View File

@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
use Phinx\Migration\AbstractMigration;
final class EstadoConeccion extends AbstractMigration
{
/**
* Change Method.
*
* Write your reversible migrations using this method.
*
* More information on writing migrations is available here:
* https://book.cakephp.org/phinx/0/en/migrations.html#the-change-method
*
* Remember to call "create()" or "update()" and NOT "save()" when working
* with the Table class.
*/
public function change(): void
{
$this->table('estados_coneccion')
->addColumn('coneccion_id', 'integer')
->addForeignKey('coneccion_id', 'conecciones')
->addColumn('fecha', 'date')
->addColumn('tipo_id', 'integer')
->addForeignKey('tipo_id', 'tipos_estado_coneccion')
->create();
}
}

View File

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
use Phinx\Migration\AbstractMigration;
final class Transaccion extends AbstractMigration
{
/**
* Change Method.
*
* Write your reversible migrations using this method.
*
* More information on writing migrations is available here:
* https://book.cakephp.org/phinx/0/en/migrations.html#the-change-method
*
* Remember to call "create()" or "update()" and NOT "save()" when working
* with the Table class.
*/
public function change(): void
{
$this->table('transacciones')
->addColumn('debito_id', 'integer')
->addForeignKey('debito_id', 'cuentas')
->addColumn('credito_id', 'integer')
->addForeignKey('credito_id', 'cuentas')
->addColumn('fecha', 'datetime')
->addColumn('glosa', 'string')
->addColumn('detalle', 'text')
->addColumn('valor', 'double')
->create();
}
}

View File

@ -0,0 +1,36 @@
<?php
use Phinx\Seed\AbstractSeed;
class TipoCuenta extends AbstractSeed
{
/**
* Run Method.
*
* Write your database seeder using this method.
*
* More information on writing seeders is available here:
* https://book.cakephp.org/phinx/0/en/seeding.html
*/
public function run()
{
$data = [
[
'descripcion' => 'Ganancia'
],
[
'descripcion' => 'Activo'
],
[
'descripcion' => 'Pasivo'
],
[
'descripcion' => 'Perdida'
]
];
$this->table('tipos_cuenta')
->insert($data)
->saveData();
}
}

View File

@ -0,0 +1,30 @@
<?php
use Phinx\Seed\AbstractSeed;
class TipoEstadoConeccion extends AbstractSeed
{
/**
* Run Method.
*
* Write your database seeder using this method.
*
* More information on writing seeders is available here:
* https://book.cakephp.org/phinx/0/en/seeding.html
*/
public function run()
{
$data = [
[
'descripcion' => 'Activa'
],
[
'descripcion' => 'Inactiva'
]
];
$this->table('tipos_estado_coneccion')
->insert($data)
->saveData();
}
}

41
api/phinx.php Normal file
View File

@ -0,0 +1,41 @@
<?php
return
[
'paths' => [
'migrations' => '%%PHINX_CONFIG_DIR%%/db/migrations',
'seeds' => '%%PHINX_CONFIG_DIR%%/db/seeds'
],
'environments' => [
'default_migration_table' => 'phinxlog',
'default_environment' => 'development',
'production' => [
'adapter' => 'mysql',
'host' => 'db',
'name' => $_ENV['MYSQL_DATABASE'],
'user' => $_ENV['MYSQL_USER'],
'pass' => $_ENV['MYSQL_PASSWORD'],
'port' => '3306',
'charset' => 'utf8',
],
'development' => [
'adapter' => 'mysql',
'host' => 'db',
'name' => $_ENV['MYSQL_DATABASE'],
'user' => $_ENV['MYSQL_USER'],
'pass' => $_ENV['MYSQL_PASSWORD'],
'port' => '3306',
'charset' => 'utf8',
],
'testing' => [
'adapter' => 'mysql',
'host' => 'db',
'name' => $_ENV['MYSQL_DATABASE'],
'user' => $_ENV['MYSQL_USER'],
'pass' => $_ENV['MYSQL_PASSWORD'],
'port' => '3306',
'charset' => 'utf8',
]
],
'version_order' => 'creation'
];

2
api/php.ini Normal file
View File

@ -0,0 +1,2 @@
log_errors = true
error_log = /var/log/php/error.log

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,5 @@
<?php
use Contabilidad\Common\Controller\Import;
$app->post('/import', Import::class);
$app->get('/import/uploads', [Import::class, 'uploads']);

View File

@ -14,6 +14,24 @@ return [
$arr['resources'],
'routes'
]);
$arr['public'] = implode(DIRECTORY_SEPARATOR, [
$arr['base'],
'public'
]);
$arr['uploads'] = implode(DIRECTORY_SEPARATOR, [
$arr['public'],
'uploads'
]);
$arr['pdfs'] = implode(DIRECTORY_SEPARATOR, [
$arr['uploads'],
'pdfs'
]);
return (object) $arr;
},
'urls' => function(Container $c) {
$arr = [
'python' => 'http://python:5000'
];
return (object) $arr;
}
];

View File

@ -0,0 +1,15 @@
<?php
use Psr\Container\ContainerInterface as Container;
return [
GuzzleHttp\Client::class => function(Container $c) {
return new GuzzleHttp\Client();
},
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'
]));
}
];

View File

@ -6,6 +6,7 @@ use ProVM\Common\Alias\Model;
/**
* @property int $id
* @property string $nombre
* @property TipoCategoria $tipo_id
*/
class Categoria extends Model {
public static $_table = 'categorias';
@ -18,6 +19,13 @@ class Categoria extends Model {
}
return $this->cuentas;
}
protected $tipo;
public function tipo() {
if ($this->tipo === null) {
$this->tipo = $this->childOf(TipoCategoria::class, [Model::SELF_KEY => 'tipo_id']);
}
return $this->tipo;
}
protected $saldo;
public function saldo() {
@ -34,6 +42,7 @@ class Categoria extends Model {
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;

21
api/src/Coneccion.php Normal file
View File

@ -0,0 +1,21 @@
<?php
namespace Contabilidad;
use ProVM\Common\Alias\Model;
/**
* @property int $id
* @property string $key
*/
class Coneccion extends Model {
public static $_table = 'conecciones';
protected static $fields = ['key'];
protected $estados;
public function estados() {
if ($this->estados === null) {
$this->estados = $this->parentOf(TipoEstadoConeccion::class, [Model::CHILD_KEY => 'coneccion_id']);
}
return $this->estados;
}
}

View File

@ -7,10 +7,11 @@ use ProVM\Common\Alias\Model;
* @property int $id
* @property string $nombre
* @property Categoria $categoria_id
* @property TipoCuenta $tipo_id
*/
class Cuenta extends Model {
public static $_table = 'cuentas';
protected static $fields = ['nombre', 'categoria_id'];
protected static $fields = ['nombre', 'categoria_id', 'tipo_id'];
protected $categoria;
public function categoria() {
@ -19,13 +20,12 @@ class Cuenta extends Model {
}
return $this->categoria;
}
protected $entradas;
public function entradas() {
if ($this->entradas === null) {
$this->entradas = $this->parentOf(Entrada::class, [Model::CHILD_KEY => 'cuenta_id']);
protected $cuenta;
public function cuenta() {
if ($this->cuenta === null) {
$this->cuenta = $this->childOf(TipoCuenta::class, [Model::SELF_KEY => 'tipo_id']);
}
return $this->entradas;
return $this->cuenta;
}
protected $cargos;

View File

@ -0,0 +1,30 @@
<?php
namespace Contabilidad;
use ProVM\Common\Alias\Model;
/**
* @property int $id
* @property Coneccion $coneccion_id
* @property DateTime $fecha
* @property TipoEstadoConeccion $tipo_id
*/
class EstadoConeccion extends Model {
public static $_table = 'estados_coneccion';
protected static $fields = ['coneccion_id', 'fecha', 'tipo_id'];
protected $coneccion;
public function coneccion() {
if ($this->coneccion === null) {
$this->coneccion = $this->childOf(Coneccion::class, [Model::SELF_KEY => 'coneccion_id']);
}
return $this->coneccion;
}
protected $tipo;
public function tipo() {
if ($this->tipo === null) {
$this->tipo = $this->childOf(TipoEstadoConeccion::class, [Model::SELF_KEY => 'tipo_id']);
}
return $this->tipo;
}
}

14
api/src/TipoCategoria.php Normal file
View File

@ -0,0 +1,14 @@
<?php
namespace Contabilidad;
use ProVM\Common\Alias\Model;
/**
* @property int $id
* @property string $descripcion
* @property int $activo
*/
class TipoCategoria extends Model {
public static $_table = 'tipos_categoria';
protected static $fields = ['descripcion', 'activo'];
}

13
api/src/TipoCuenta.php Normal file
View File

@ -0,0 +1,13 @@
<?php
namespace Contabilidad;
use ProVM\Common\Alias\Model;
/**
* @property int $id
* @property string $descripcion
*/
class TipoCuenta extends Model {
public static $_table = 'tipos_cuenta';
protected static $fields = ['descripcion'];
}

View File

@ -0,0 +1,13 @@
<?php
namespace Contabilidad;
use ProVM\Common\Alias\Model;
/**
* @property int $id
* @property string $descripcion
*/
class TipoEstadoConeccion extends Model {
public static $_table = 'tipos_estado_coneccion';
protected static $fields = ['descripcion'];
}

View File

@ -6,8 +6,8 @@ use ProVM\Common\Alias\Model;
/**
* @property int $id
* @property Cuenta $desde_id
* @property Cuenta $hasta_id
* @property Cuenta $debito_id
* @property Cuenta $credito_id
* @property \DateTime $fecha
* @property string $glosa
* @property string $detalle
@ -15,21 +15,21 @@ use ProVM\Common\Alias\Model;
*/
class Transaccion extends Model {
public static $_table = 'transacciones';
protected static $fields = ['desde_id', 'hasta_id', 'fecha', 'glosa', 'detalle', 'valor'];
protected static $fields = ['debito_id', 'credito_id', 'fecha', 'glosa', 'detalle', 'valor'];
protected $desde;
public function desde() {
if ($this->desde === null) {
$this->desde = $this->childOf(Cuenta::class, [Model::SELF_KEY => 'desde_id']);
protected $debito;
public function debito() {
if ($this->debito === null) {
$this->debito = $this->childOf(Cuenta::class, [Model::SELF_KEY => 'debito_id']);
}
return $this->desde;
return $this->debito;
}
protected $hasta;
public function hasta() {
if ($this->hasta === null) {
$this->hasta = $this->childOf(Cuenta::class, [Model::SELF_KEY => 'hasta_id']);
protected $credito;
public function credito() {
if ($this->credito === null) {
$this->credito = $this->childOf(Cuenta::class, [Model::SELF_KEY => 'credito_id']);
}
return $this->hasta;
return $this->credito;
}
public function fecha(\DateTime $fecha = null) {
if ($fecha === null) {
@ -40,8 +40,8 @@ class Transaccion extends Model {
public function toArray(): array {
$arr = parent::toArray();
$arr['desde'] = $this->desde()->toArray();
$arr['hasta'] = $this->hasta()->toArray();
$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, ',', '.');
return $arr;

View File

@ -2,44 +2,62 @@ version: '3'
services:
api:
restart: unless-stopped
image: php
build:
context: api
env_file: .env
volumes:
- ./api/:/app/
- ./api/php.ini:/usr/local/etc/php/conf.d/php.ini
- ./logs/api/php/:/var/log/php/
api-proxy:
restart: unless-stopped
image: nginx
ports:
- 9001:80
- "9001:80"
volumes:
- ./api/nginx.conf:/etc/nginx/conf.d/default.conf
- ./logs/api/:/var/log/nginx/
- ./api/:/app/
db:
restart: unless-stopped
image: mariadb
env_file: .env
volumes:
- contabilidad_data:/var/lib/mysql
adminer:
restart: unless-stopped
image: adminer
ports:
- 9002:8080
- "9002:8080"
ui:
restart: unless-stopped
image: php-ui
build:
context: ui
volumes:
- ./ui/:/app/
ui-proxy:
restart: unless-stopped
image: nginx
ports:
- 9000:80
- "9000:80"
volumes:
- ./ui/nginx.conf:/etc/nginx/conf.d/default.conf
- ./logs/ui/:/var/log/nginx/
- ./ui/:/app/
python:
restart: unless-stopped
build:
context: ./python
volumes:
- ./python/src/:/app/src/
- ./python/config/:/app/config/
- ./api/public/uploads/pdfs/:/app/data/
- ./logs/python/:/var/log/python/
volumes:
contabilidad_data:

3
python/.idea/.gitignore generated vendored Normal file
View File

@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

View File

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

7
python/.idea/misc.xml generated Normal file
View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.9 (contabilidad)" project-jdk-type="Python SDK" />
<component name="PyCharmProfessionalAdvertiser">
<option name="shown" value="true" />
</component>
</project>

8
python/.idea/modules.xml generated Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/python.iml" filepath="$PROJECT_DIR$/.idea/python.iml" />
</modules>
</component>
</project>

8
python/.idea/python.iml generated Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="jdk" jdkName="Python 3.9 (contabilidad)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

6
python/.idea/vcs.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
</component>
</project>

18
python/Dockerfile Normal file
View File

@ -0,0 +1,18 @@
FROM python
RUN apt-get update -y && apt-get install -y default-jre
RUN pip install flask tabula-py pyyaml pypdf4 gunicorn
WORKDIR /app
COPY ./src/ /app/src/
#ENTRYPOINT ["/bin/bash"]
EXPOSE 5000
WORKDIR /app/src
CMD ["python", "app.py"]
#CMD ["gunicorn", "-b 0.0.0.0:5000", "app:app"]

View File

@ -0,0 +1,3 @@
passwords:
- 0839
- 159608395

Binary file not shown.

3
python/src/.idea/.gitignore generated vendored Normal file
View File

@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

View File

@ -0,0 +1,12 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="PyUnresolvedReferencesInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredIdentifiers">
<list>
<option value="property.tolist" />
</list>
</option>
</inspection_tool>
</profile>
</component>

View File

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

4
python/src/.idea/misc.xml generated Normal file
View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.9 (python)" project-jdk-type="Python SDK" />
</project>

8
python/src/.idea/modules.xml generated Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/src.iml" filepath="$PROJECT_DIR$/.idea/src.iml" />
</modules>
</component>
</project>

8
python/src/.idea/src.iml generated Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="jdk" jdkName="Python 3.9 (python)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

6
python/src/.idea/vcs.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/../.." vcs="Git" />
</component>
</project>

Binary file not shown.

34
python/src/app.py Normal file
View File

@ -0,0 +1,34 @@
import json
import os
from flask import Flask, request
import contabilidad.pdf as pdf
import contabilidad.passwords as passwords
import contabilidad.log as log
app = Flask(__name__)
log.logging['filename'] = '/var/log/python/contabilidad.log'
@app.route('/pdf/parse', methods=['POST'])
def pdf_parse():
data = request.get_json()
if not isinstance(data['files'], list):
data['files'] = [data['files']]
password_file = '/app/config/.passwords.yml'
pwds = passwords.get_passwords(password_file)
texts = []
for file in data['files']:
filename = os.path.realpath(os.path.join('/app/data', file['filename']))
for p in pwds:
obj = pdf.get_text(filename, p)
if obj is None:
continue
print(obj)
texts.append(json.dumps(obj))
return json.dumps(texts)
if __name__ == '__main__':
app.run(host='0.0.0.0')

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,19 @@
import time
logging = {
'filename': '/var/log/python/error.log'
}
class LOG_LEVEL:
INFO = 'INFO'
WARNING = 'WARNING'
DEBUG = 'DEBUG'
ERROR = 'ERROR'
def log(message, level=LOG_LEVEL.INFO):
filename = logging['filename']
with open(filename, 'a') as f:
f.write(time.strftime('[%Y-%m-%d %H:%M:%S] ') + ' - ' + level + ': ' + message)

View File

@ -0,0 +1,6 @@
import yaml
def get_passwords(filename):
with open(filename, 'r') as f:
return yaml.load(f, Loader=yaml.Loader)['passwords']

View File

@ -0,0 +1,31 @@
import PyPDF4
import tabula
def get_pdf(file, password=''):
reader = PyPDF4.PdfFileReader(file)
if reader.getIsEncrypted() and password != '':
status = reader.decrypt(password=password)
if status == 0:
return None
return reader
def get_text(filename, password=''):
with open(filename, 'rb') as f:
reader = get_pdf(f, password)
if reader is None:
return None
print(reader.getPage(0).extractText())
texts = []
for p in range(0, reader.getNumPages()):
print(p)
texts.append(reader.getPage(p).extractText())
return "\n".join(texts)
def get_data(filename, password=''):
if password == '':
return tabula.read_pdf(filename, pages='all', output_format='json')
else:
return tabula.read_pdf(filename, password=password, pages='all', output_format='json')

View File

@ -0,0 +1,54 @@
import argparse
import yaml
import PyPDF4
import httpx
def get_pdf(file, password=''):
reader = PyPDF4.PdfFileReader(file)
if password != '':
status = reader.decrypt(password=password)
if status == 0:
print('Not decrypted')
return reader
def send_to_parser(url, text):
res = httpx.post(url, data={'to_parse': text})
return {'status': res.status_code, 'text': res.json()}
def get_text(filename, password=''):
with open(filename, 'rb') as f:
reader = get_pdf(f, password)
texts = []
for p in range(0, reader.getNumPages()):
texts.append(reader.getPage(p).extractText())
return "\n".join(texts)
def get_config(filename):
with open(filename, 'r') as f:
return yaml.load(f, Loader=yaml.Loader)
def main(args):
password = ''
if args.config_file is not None:
config = get_config(args.config_file)
password = config['password']
if args.password is not None:
password = args.password
text = get_text(args.filename, password)
res = send_to_parser(args.url, text)
print(res)
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('-f', '--filename', type=str)
parser.add_argument('-p', '--password', type=str)
parser.add_argument('-c', '--config_file', type=str)
parser.add_argument('-u', '--url', type=str)
_args = parser.parse_args()
main(_args)

View File

@ -0,0 +1,3 @@
def text_cleanup(text):
lines = text.split("\n")
print(lines)

18
python/src/main.py Normal file
View File

@ -0,0 +1,18 @@
import argparse
import os
import contabilidad.pdf as pdf
def main(args):
filename = os.path.realpath(os.path.join(os.path.dirname(__file__), '..', 'data', args.filename))
obj = pdf.get_text(filename, args.password)
print(obj)
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('-f', '--filename', type=str)
parser.add_argument('-p', '--password', type=str, default='')
_args = parser.parse_args()
main(_args)

View File

@ -0,0 +1,35 @@
# Tests
### 1. Conductor
+ Set start event
+ Get ready events
+ Set first step event
+ Get first step ready
+ Set second step event
+ Get second step ready
### 2. Email
+ Connect to IMAP
+ Wrong data
+ Wrong configuration
+ Get mailboxes
+ Get mail ids with search
+ Get mails by id
+ Get mail by id
+ Get attachment
+ Close connection
### 3. API Sender
+ Get attachments
+ Process
+ Send to API
## Steps
1. Start
+ Connect
+ Standby
2. Find emails, get attachments
3. Process attachments
4. Send to API
5. Close

12
worker/main.py Normal file
View File

@ -0,0 +1,12 @@
from threading import Thread
import httpx
class Worker(Thread):
def __init__(self, settings):
self.settings = settings
def run():
while True:
if self.stop_event.isSet():
break