80 Commits

Author SHA1 Message Date
459a95bf12 Merge branch 'develop' into release 2022-04-18 22:42:05 -04:00
06071884c7 FIX: Negative numbers in cuenta ui, and update_consolidar command route 2022-04-18 22:41:25 -04:00
ca8229abee Merge branch 'develop' into release 2022-04-18 22:15:15 -04:00
6ff584013f Add update consolidado command and queuing 2022-04-18 22:13:17 -04:00
cf27465d75 Improve readability 2022-04-18 22:11:59 -04:00
560fb356fa Move to today in cuentas.show 2022-04-02 23:32:27 -04:00
6c4e51bfff Merge branch 'develop' into release 2022-04-02 23:59:14 -03:00
10e5383a3e FIX: modalToAdd in cuentas.show 2022-04-02 23:58:57 -03:00
81ff87d355 Merge branch 'develop' into release 2022-04-02 23:47:11 -03:00
bc5338b1f1 FIX: consolidado not changed with tipocambio 2022-04-02 23:40:53 -03:00
4db8161c70 FIX: withJson requires 2nd parameter 2022-03-26 20:55:57 -03:00
d94f076946 Script structure and searchable cuentas 2022-03-25 20:54:02 -03:00
899e9f6070 Merge branch 'develop' into release 2022-03-25 18:04:25 -03:00
8009433958 FIX: update buttons 2022-03-25 17:52:21 -03:00
d7b406ef25 FIX: double transform value 2022-03-25 17:45:58 -03:00
cddf768b1e FIX: show cuentas missing moneda 2022-03-25 17:39:12 -03:00
0bd54fa8a4 Merge branch 'develop' into release 2022-03-25 16:37:09 -03:00
8b86560973 Error catching in UI 2022-03-25 16:36:58 -03:00
f6285d6970 Merge branch 'develop' into release 2022-03-25 16:22:02 -03:00
4ea5c6de3e Env files 2022-03-25 16:21:38 -03:00
5f4f5d1501 Merge branch 'develop' into release 2022-03-25 16:19:08 -03:00
91cc28e681 Merge branch 'feature/commands' into develop 2022-03-25 16:18:38 -03:00
ef01ab3c55 Migrations 2022-03-25 16:18:09 -03:00
e41ffd505b Migrations fixed 2022-03-25 16:16:31 -03:00
0e7d7200b6 Merge branch 'develop' into release 2022-03-25 15:49:42 -03:00
2986806137 Merge branch 'feature/commands' into develop 2022-03-25 15:04:53 -03:00
f9076d3bac Error catching in API 2022-03-25 15:04:42 -03:00
ee3133da72 Consolidar command 2022-03-25 15:04:10 -03:00
dbad283e14 Command queue 2022-03-25 15:03:49 -03:00
e3737aba27 Consolidar 2022-03-25 10:11:02 -03:00
fcc84ac09c Console application 2022-03-25 10:10:43 -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
925388df92 Edit transacciones, refresh, sort cuentas 2022-01-05 16:02:40 -03:00
af78106700 Cleaner code 2022-01-05 16:01:27 -03:00
665f426011 Sort cuentas in home 2022-01-05 15:58:03 -03:00
56b371d20c Refresh and edit transacciones 2022-01-05 15:56:03 -03:00
2126d30ee7 Merge branch 'develop' into release 2021-12-23 01:09:50 -03:00
71b4211fc3 Missing categoria from cuentas 2021-12-23 01:09:35 -03:00
0378a2cf09 FIX: Double tables 2021-12-23 01:09:15 -03:00
73df98dca5 Merge branch 'develop' into release 2021-12-23 00:47:47 -03:00
4abe3448c0 Upgrades to the UI 2021-12-23 00:46:56 -03:00
f9bbbfd920 Fixes for uploaded files 2021-12-23 00:10:06 -03:00
9e29dd09b7 Remove ignored files 2021-12-23 00:09:23 -03:00
52443c2226 FIX: Clear form and table 2021-12-23 00:09:09 -03:00
572a9dc87c FIX: Renaming with correct extension 2021-12-23 00:08:49 -03:00
66882d9f85 FIX: Upload valid file check 2021-12-23 00:08:28 -03:00
aacda4d1e4 Fix 2021-12-22 23:24:19 -03:00
1a0c83fb2b FIX: check if upload subfolder exists when loading files 2021-12-22 23:20:23 -03:00
f99b984f6c Fix 2021-12-22 23:12:25 -03:00
a5428b252e FIX: Ignoring upload resources from ui 2021-12-22 23:11:51 -03:00
605c905f5d Python 2021-12-22 21:53:30 -03:00
4bc1e4ae4d Upload and manage files 2021-12-22 01:39:09 -03:00
93f77bfbb8 Upload files 2021-12-22 01:38:34 -03:00
fddba2fb87 Manage uploaded files 2021-12-22 01:38:23 -03:00
79c7d5ad63 Fixes 2021-12-20 23:30:13 -03:00
e6ebb2c279 FIX: url importar 2021-12-20 23:29:40 -03:00
45952bb3ac FIX: Select cuentas 2021-12-20 23:14:23 -03:00
6b03d62ce0 Merge branch 'develop' into release 2021-12-20 22:54:14 -03:00
894cc26b21 Upload files 2021-12-20 22:51:15 -03:00
64ffb53f0c Composer for UI 2021-12-20 22:45:00 -03:00
42310ef0e4 Formatting 2021-12-20 22:44:47 -03:00
a6362a6770 FIX: Empty results threw errors 2021-12-20 22:44:29 -03:00
e9c63abc3a PHP info 2021-12-20 22:43:43 -03:00
9f47c8a85f Default value for seed 2021-12-20 22:43:32 -03:00
34eedb93d7 FIX: .env not loaded in ui 2021-12-20 21:35:47 -03:00
960c418848 Python 2021-12-09 21:14:28 -03:00
0e5714edc8 FIX: cuentas 2021-12-07 09:13:20 -03:00
f33bddfbea Added Docker profiles 2021-12-06 22:22:54 -03:00
120 changed files with 2931 additions and 957 deletions

1
.console.env.sample Normal file
View File

@ -0,0 +1 @@
API_URL=http://api-proxy

5
.db.env.sample Normal file
View File

@ -0,0 +1,5 @@
MYSQL_HOST=
MYSQL_ROOT_PASSWORD=
MYSQL_DATABASE=
MYSQL_USER=
MYSQL_PASSWORD=

View File

@ -1,5 +1 @@
MYSQL_HOST= COMPOSE_PROFILES=
MYSQL_ROOT_PASSWORD=
MYSQL_DATABASE=
MYSQL_USER=
MYSQL_PASSWORD=

2
.gitignore vendored
View File

@ -13,5 +13,3 @@
# Python # Python
**/.idea/ **/.idea/
**/uploads/

8
.idea/.gitignore generated vendored
View File

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

View File

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

81
.idea/contabilidad.iml generated
View File

@ -1,81 +0,0 @@
<?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
View File

@ -1,8 +0,0 @@
<?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
View File

@ -1,87 +0,0 @@
<?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>

1
api/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
**/uploads/

View File

@ -24,4 +24,11 @@ class Base {
$key = urlencode(base64_encode($signature)); $key = urlencode(base64_encode($signature));
return $this->withJson($response, ['key' => $key]); return $this->withJson($response, ['key' => $key]);
} }
public function info(Request $request, Response $response): Response {
ob_start();
phpinfo();
$data = ob_get_clean();
$response->getBody()->write($data);
return $response;
}
} }

View File

@ -13,11 +13,14 @@ class Categorias {
public function __invoke(Request $request, Response $response, Factory $factory, Service $service): Response { public function __invoke(Request $request, Response $response, Factory $factory, Service $service): Response {
$categorias = $factory->find(Categoria::class)->many(); $categorias = $factory->find(Categoria::class)->many();
if ($categorias !== null) {
array_walk($categorias, function(&$item) use ($service) { array_walk($categorias, function(&$item) use ($service) {
$arr = $item->toArray(); $arr = $item->toArray();
if ($item->cuentas()) {
$arr['cuentas'] = array_map(function($item) { $arr['cuentas'] = array_map(function($item) {
return $item->toArray(); return $item->toArray();
}, $item->cuentas()); }, $item->cuentas());
}
$maps = ['activo', 'pasivo', 'ganancia', 'perdida']; $maps = ['activo', 'pasivo', 'ganancia', 'perdida'];
foreach ($maps as $m) { foreach ($maps as $m) {
$p = $m . 's'; $p = $m . 's';
@ -33,7 +36,6 @@ class Categorias {
} }
$item = $arr; $item = $arr;
}); });
if ($categorias) {
usort($categorias, function($a, $b) { usort($categorias, function($a, $b) {
return strcmp($a['nombre'], $b['nombre']); return strcmp($a['nombre'], $b['nombre']);
}); });

View File

@ -0,0 +1,66 @@
<?php
namespace Contabilidad\Common\Controller;
use Carbon\Carbon;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
use ProVM\Common\Factory\Model as Factory;
use ProVM\Common\Define\Controller\Json;
use Contabilidad\Common\Service\Consolidar as Service;
use Contabilidad\Cuenta;
use Contabilidad\Consolidado;
class Consolidados {
use Json;
public function __invoke(Request $request, Response $response, Factory $factory, $cuenta_id): Response {
$consolidados = $factory->find(Consolidados::class)->where([['cuenta_id' => $cuenta_id]])->many();
$output = [
'consolidados' => array_map(function($item) {
return $item->toArray();
}, $consolidados)
];
return $this->withJson($response, $output);
}
public function add(Request $request, Response $response, Factory $factory): Response {
$input = json_decode($request->getBody()->getContents());
$output = [
'input' => $input,
'consolidados' => []
];
if (!is_array($input)) {
$input = [$input];
}
foreach ($input as $data) {
$consolidado = Consolidado::add($factory, $data);
$status = $consolidado->save();
$output['consolidados'] []= [
'consolidado' => $consolidado->toArray(),
'added' => $status
];
}
return $this->withJson($response, $output);
}
public function cli(Request $request, Response $response, Service $service): Response {
try {
if (!$service->isConsolidado()) {
$service->consolidar();
}
} catch (\Error | \Exception $e) {
error_log($e);
throw $e;
}
return $this->withJson($response, []);
}
public function update(Request $request, Response $response, Factory $factory, Service $service, $mes, $cuenta_id): Response {
try {
$cuenta = $factory->find(Cuenta::class)->one($cuenta_id);
$mes = Carbon::parse($mes);
$service->consolidarCuenta($cuenta, $mes);
} catch (\Error | \Exception $e) {
error_log($e);
throw $e;
}
return $this->withJson($response, []);
}
}

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;
@ -12,8 +14,13 @@ class Cuentas {
use Json; use Json;
public function __invoke(Request $request, Response $response, Factory $factory): Response { public function __invoke(Request $request, Response $response, Factory $factory): Response {
$cuentas = $factory->find(Cuenta::class)->array(); $cuentas = $factory->find(Cuenta::class)->many();
if ($cuentas) { if ($cuentas) {
array_walk($cuentas, function (&$item) {
$arr = $item->toArray();
$arr['categoria'] = $item->categoria()->toArray();
$item = $arr;
});
usort($cuentas, function($a, $b) { usort($cuentas, function($a, $b) {
$t = strcmp($a['tipo']['descripcion'], $b['tipo']['descripcion']); $t = strcmp($a['tipo']['descripcion'], $b['tipo']['descripcion']);
if ($t != 0) { if ($t != 0) {
@ -77,6 +84,15 @@ class Cuentas {
]; ];
return $this->withJson($response, $output); return $this->withJson($response, $output);
} }
public function categoria(Request $request, Response $response, Factory $factory, $cuenta_id): Response {
$cuenta = $factory->find(Cuenta::class)->one($cuenta_id);
$output = [
'input' => $cuenta_id,
'cuenta' => $cuenta?->toArray(),
'categoria' => $cuenta?->categoria()->toArray()
];
return $this->withJson($response, $output);
}
public function entradas(Request $request, Response $response, Factory $factory, $cuenta_id): Response { public function entradas(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);
$entradas = null; $entradas = null;
@ -95,13 +111,7 @@ class Cuentas {
]; ];
return $this->withJson($response, $output); return $this->withJson($response, $output);
} }
public function transacciones(Request $request, Response $response, Factory $factory, Service $service, $cuenta_id, $limit = null, $start = 0): Response { protected function transaccionToArray(Service $service, Cuenta $cuenta, Transaccion $transaccion): array {
$cuenta = $factory->find(Cuenta::class)->one($cuenta_id);
$transacciones = null;
if ($cuenta !== null) {
$transacciones = $cuenta->transacciones($limit, $start);
if (count($transacciones) > 0) {
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') {
@ -111,11 +121,22 @@ class Cuentas {
$c = $transaccion->credito(); $c = $transaccion->credito();
} }
$service->get($transaccion->fecha(), $c->moneda()->id); $service->get($transaccion->fecha(), $c->moneda()->id);
$arr['valor'] = $c->moneda()->cambiar($transaccion->fecha(), $transaccion->valor); $arr['valor'] = $transaccion->valor;
$arr['valorFormateado'] = $cuenta->moneda()->format($arr['valor']); $arr['valorFormateado'] = $cuenta->moneda()->format($arr['valor']);
} }
} }
$transaccion = $arr; $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 {
$cuenta = $factory->find(Cuenta::class)->one($cuenta_id);
$transacciones = null;
if ($cuenta !== null) {
$transacciones = $cuenta->transacciones($limit, $start);
if (count($transacciones) > 0) {
foreach ($transacciones as &$transaccion) {
$transaccion = $this->transaccionToArray($service, $cuenta, $transaccion);
} }
} }
} }
@ -126,6 +147,40 @@ 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) {
$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

@ -0,0 +1,76 @@
<?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 Contabilidad\Common\Service\FileHandler as Handler;
use ProVM\Common\Factory\Model as Factory;
use Contabilidad\Cuenta;
class Files {
use Json;
public function __invoke(Request $request, Response $response, Handler $handler): Response {
$files = $handler->listFiles();
usort($files, function($a, $b) {
$f = strcmp($a->folder, $b->folder);
if ($f == 0) {
return strcmp($a->filename, $b->filename);
}
return $f;
});
return $this->withJson($response, compact('files'));
}
public function upload(Request $request, Response $response, Handler $handler, Factory $factory): Response {
$post = $request->getParsedBody();
$cuenta = $factory->find(Cuenta::class)->one($post['cuenta']);
$file = $request->getUploadedFiles()['archivo'];
$new_name = implode(' - ', [$cuenta->nombre, $cuenta->categoria()->nombre, $post['fecha']]);
$output = [
'input' => [
'name' => $file->getClientFilename(),
'type' => $file->getClientMediaType(),
'size' => $file->getSize(),
'error' => $file->getError()
],
'new_name' => $new_name,
'uploaded' => $handler->uploadFile($file, $new_name)
];
return $this->withJson($response, $output);
}
public function get(Request $request, Response $response, Handler $handler, $folder, $filename): Response {
$file = $handler->getFile($folder, $filename);
return $response
->withHeader('Content-Type', $handler->getType($folder))
->withHeader('Content-Disposition', 'attachment; filename=' . $filename)
->withAddedHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
->withHeader('Cache-Control', 'post-check=0, pre-check=0')
->withHeader('Pragma', 'no-cache')
->withBody($file);
}
public function edit(Request $request, Response $response, Handler $handler, Factory $factory, $folder, $filename): Response {
$post = json_decode($request->getBody());
$cuenta = $factory->find(Cuenta::class)->one($post->cuenta);
$new_name = implode(' - ', [$cuenta->nombre, $cuenta->categoria()->nombre, $post->fecha]);
$output = [
'input' => [
'folder' => $folder,
'filename' => $filename,
'post' => $post
],
'edited' => $handler->editFilename($folder, $filename, $new_name)
];
return $this->withJson($response, $output);
}
public function delete(Request $request, Response $response, Handler $handler, $folder, $filename): Response {
$output = [
'input' => [
'folder' => $folder,
'filename' => $filename
],
'deleted' => $handler->deleteFile($folder, $filename)
];
return $this->withJson($response, $output);
}
}

View File

@ -3,16 +3,44 @@ namespace Contabilidad\Common\Controller;
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 Psr\Container\ContainerInterface as Container;
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\DocumentHandler as Handler; use Contabilidad\Common\Service\DocumentHandler as Handler;
use Contabilidad\Cuenta;
class Import { class Import {
use Json; use Json;
public function __invoke(Request $request, Response $response, Factory $factory): Response { public function __invoke(Request $request, Response $response, Factory $factory, Container $container): Response {
$post = $request->getParsedBody(); $post =$request->getParsedBody();
return $this->withJson($response, $post); $cuenta = $factory->find(Cuenta::class)->one($post['cuenta']);
$file = $request->getUploadedFiles()['archivo'];
$valid_media = [
'text/csv' => 'csvs',
'application/pdf' => 'pdfs',
'application/vnd.ms-excel' => 'xlss',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'xlss',
'application/json' => 'jsons'
];
if ($file->getError() === 0 and in_array($file->getClientMediaType(), array_keys($valid_media))) {
$filenfo = new \SplFileInfo($file->getClientFilename());
$new_name = implode('.', [implode(' - ', [$cuenta->nombre, $cuenta->categoria()->nombre, $post['fecha']]), $filenfo->getExtension()]);
$to = implode(DIRECTORY_SEPARATOR, [$container->get('folders')->uploads, $valid_media[$file->getClientMediaType()], $new_name]);
$file->moveTo($to);
$status = file_exists($to);
}
$output = [
'input' => [
'name' => $file->getClientFilename(),
'type' => $file->getClientMediaType(),
'size' => $file->getSize(),
'error' => $file->getError()
],
'new_name' => $new_name,
'uploaded' => $status
];
return $this->withJson($response, $output);
} }
public function uploads(Request $request, Response $response, Handler $handler): Response { public function uploads(Request $request, Response $response, Handler $handler): Response {
$output = $handler->handle(); $output = $handler->handle();

View File

@ -0,0 +1,47 @@
<?php
namespace Contabilidad\Common\Controller;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
use ProVM\Common\Factory\Model as Factory;
use ProVM\Common\Define\Controller\Json;
use Contabilidad\Queue;
class Queues {
use Json;
public function __invoke(Request $request, Response $response, Factory $factory): Response {
$queues = $factory->find(Queue::class)->many();
$output = [
'queues' => array_map(function($item) {return $item->toArray();}, $queues)
];
return $this->withJson($response, $output);
}
public function pending(Request $request, Response $response, Factory $factory): Response {
$pending = $factory->find(Queue::class)->where([['processed', 0]])->many();
$output = [
'pending' => array_map(function($item) {return $item->toArray();}, $pending)
];
return $this->withJson($response, $output);
}
public function processed(Request $request, Response $response, Factory $factory): Response {
$input = json_decode($request->getBody()->getContents());
$output = [
'input' => $input,
'queues' => []
];
if (!is_array($input->processed)) {
$input->processed = [$input->processed];
}
foreach ($input->processed as $id) {
$queue = $factory->find(Queue::class)->one($id);
$queue->setProcessed(true);
$status = $queue->save();
$output['queues'] []= [
'queue' => $queue->toArray(),
'processed' => $status
];
}
return $this->withJson($response, $output);
}
}

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;
@ -13,28 +14,19 @@ class TiposCategorias {
public function __invoke(Request $request, Response $response, Factory $factory, Service $service): Response { public function __invoke(Request $request, Response $response, Factory $factory, Service $service): Response {
$tipos = $factory->find(TipoCategoria::class)->many(); $tipos = $factory->find(TipoCategoria::class)->many();
if ($tipos !== null) {
array_walk($tipos, function(&$item) use ($service) { array_walk($tipos, function(&$item) use ($service) {
$arr = $item->toArray(); $arr = $item->toArray();
$arr['categorias'] = $item->categorias();
if ($arr['categorias'] !== null) {
$arr['categorias'] = array_map(function($item) { $arr['categorias'] = array_map(function($item) {
return $item->toArray(); return $item->toArray();
}, $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;
}); });
if ($tipos) {
usort($tipos, function($a, $b) { usort($tipos, function($a, $b) {
return strcmp($a['descripcion'], $b['descripcion']); return strcmp($a['descripcion'], $b['descripcion']);
}); });
@ -90,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;
}); });
} }
@ -117,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';
@ -133,7 +126,7 @@ class TiposCategorias {
}); });
} }
return $sum; return $sum;
}); });*/
return $this->withJson($response, $balance); return $this->withJson($response, $balance);
} }
} }

View File

@ -5,13 +5,13 @@ 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;
use ProVM\Common\Factory\Model as Factory; use ProVM\Common\Factory\Model as Factory;
use Contabilidad\Common\Service\Queuer as Service;
use Contabilidad\Transaccion; use Contabilidad\Transaccion;
class Transacciones { class Transacciones {
use Json; use Json;
public function __invoke(Request $request, Response $response, Factory $factory): Response { protected function parseTransacciones(?array $transacciones): ?array {
$transacciones = $factory->find(Transaccion::class)->array();
if ($transacciones !== null) { if ($transacciones !== null) {
usort($transacciones, function($a, $b) { usort($transacciones, function($a, $b) {
$d = $a['fecha'] - $b['fecha']; $d = $a['fecha'] - $b['fecha'];
@ -21,8 +21,12 @@ class Transacciones {
return $d; return $d;
}); });
} }
return $transacciones;
}
public function __invoke(Request $request, Response $response, Factory $factory): Response {
$transacciones = $factory->find(Transaccion::class)->array();
$output = [ $output = [
'transacciones' => $transacciones 'transacciones' => $this->parseTransacciones($transacciones)
]; ];
return $this->withJson($response, $output); return $this->withJson($response, $output);
} }
@ -52,17 +56,33 @@ class Transacciones {
]; ];
return $this->withJson($response, $output); return $this->withJson($response, $output);
} }
public function edit(Request $request, Response $response, Factory $factory, $transaccion_id): Response { public function edit(Request $request, Response $response, Factory $factory, Service $queuer, $transaccion_id): Response {
$transaccion = $factory->find(Transaccion::class)->one($transaccion_id); $transaccion = $factory->find(Transaccion::class)->one($transaccion_id);
$output = [ $output = [
'input' => $transaccion_id, 'input' => $transaccion_id,
'old' => $transaccion->toArray() 'old' => $transaccion->toArray()
]; ];
$old_cuentas = ['credito' => $transaccion->credito_id, 'debito_id' => $transaccion->debito_id];
$input = json_decode($request->getBody()); $input = json_decode($request->getBody());
$transaccion->edit($input); $transaccion->edit($input);
$new_cuentas = ['credito' => $transaccion->credito_id, 'debito_id' => $transaccion->debito_id];
$cuentas = [];
foreach ($new_cuentas as $tipo => $id) {
if ($old_cuentas[$tipo] != $id) {
$cuentas []= $old_cuentas[$tipo];
$cuentas []= $id;
}
}
$this->updateConsolidar($queuer, $transaccion->fecha(), $cuentas);
$output['transaccion'] = $transaccion->toArray(); $output['transaccion'] = $transaccion->toArray();
return $this->withJson($response, $output); return $this->withJson($response, $output);
} }
protected function updateConsolidar(Service $queuer, \DateTimeInterface $mes, $cuentas) {
foreach ($cuentas as $cuenta_id) {
$queuer->queue('update_consolidar', ['mes' => $mes->format('Y-m-1'), 'cuenta' => $cuenta_id]);
}
}
public function delete(Request $request, Response $response, Factory $factory, $transaccion_id): Response { public function delete(Request $request, Response $response, Factory $factory, $transaccion_id): Response {
$transaccion = $factory->find(Transaccion::class)->one($transaccion_id); $transaccion = $factory->find(Transaccion::class)->one($transaccion_id);
$output = [ $output = [

View File

@ -0,0 +1,20 @@
<?php
namespace Contabilidad\Common\Middleware;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as Handler;
use Psr\Http\Message\ResponseInterface as Response;
use Contabilidad\Common\Service\Consolidar as ConsolidarService;
class Consolidar {
protected $service;
public function __construct(ConsolidarService $service) {
$this->service = $service;
}
public function __invoke(Request $request, Handler $handler): Response {
if (!$this->service->isConsolidado()) {
$this->service->queue();
}
return $handler->handle($request);
}
}

View File

@ -0,0 +1,174 @@
<?php
namespace Contabilidad\Common\Service;
use Carbon\Carbon;
use \Model;
use ProVM\Common\Factory\Model as ModelFactory;
use Contabilidad\Consolidado;
use Contabilidad\Cuenta;
use Contabilidad\Transaccion;
class Consolidar {
public function __construct(ModelFactory $factory, Queuer $queuer) {
$this->setFactory($factory);
$this->setQueuer($queuer);
}
protected ModelFactory $factory;
public function setFactory(ModelFactory $factory): Consolidar {
$this->factory = $factory;
return $this;
}
protected Queuer $queuer;
public function setQueuer(Queuer $queuer): Consolidar {
$this->queuer = $queuer;
return $this;
}
protected $cuentas;
public function getCuentas() {
if ($this->cuentas === null) {
$this->cuentas = $this->factory->find(Cuenta::class)->many();
}
return $this->cuentas;
}
public function isConsolidado(): bool {
$cuentas = $this->getCuentas();
if ($cuentas === null) {
return true;
}
foreach ($cuentas as $cuenta) {
$transacciones = $cuenta->hasTransacciones();
if (!$transacciones) {
continue;
}
$pendientes = $cuenta->hasConsolidadosPending();
if ($pendientes) {
return false;
}
}
return true;
}
public function queue() {
$this->queuer->queue('consolidar');
}
public function consolidar() {
ini_set('max_execution_time', 60*5);
foreach ($this->getCuentas() as $cuenta) {
if (!$cuenta->hasTransacciones()) {
continue;
}
$first = $this->getFirst($cuenta);
$last = $this->getLast($cuenta);
for ($current = $first->copy()->startOfMonth(); $current < $last->copy()->addMonthWithoutOverflow()->endOfMonth(); $current = $current->copy()->addMonthWithoutOverflow()) {
$this->consolidarCuenta($cuenta, $current);
}
}
}
public function consolidarCuenta(Cuenta $cuenta, Carbon $mes) {
if (!$cuenta->hasTransacciones($mes)) {
return;
}
$transacciones = $this->getTransacciones($cuenta, $mes);
if (count($transacciones) == 0) {
return;
}
array_walk($transacciones, function(&$item) use ($cuenta) {
$item->valor = $item->transformar($cuenta->moneda());
});
$saldo = array_reduce($transacciones, function($sum, $item) {
return $sum + $item->valor;
});
if ($cuenta->tipo()->cargo()) {
$saldo += -1;
}
$consolidado = $this->factory->find(Consolidado::class)->where([['cuenta_id', $cuenta->id], ['fecha', $mes->format('Y-m-1')]])->one();
if ($consolidado === null) {
$data = [
'cuenta_id' => $cuenta->id,
'fecha' => $mes->format('Y-m-1'),
'periodo' => 'P1M',
'saldo' => $saldo
];
$consolidado = $this->factory->create(Consolidado::class, $data);
}
$consolidado->saldo = $saldo;
$consolidado->save();
}
public function getFirst(Cuenta $cuenta): ?Carbon {
$first = [
Model::factory(Transaccion::class)
->select('fecha')
->whereEqual('debito_id', $cuenta->id)
->orderByAsc('fecha')
->findOne(),
Model::factory(Transaccion::class)
->select('fecha')
->whereEqual('credito_id', $cuenta->id)
->orderByAsc('fecha')
->findOne()
];
if (!$first[0]) {
if (!$first[1]) {
return null;
}
return $first[1]->fecha();
}
if (!$first[1]) {
return $first[0]->fecha();
}
if ($first[0]->fecha() < $first[1]->fecha()) {
return $first[0]->fecha();
}
return $first[1]->fecha();
}
public function getLast(Cuenta $cuenta): ?Carbon {
$fechas = [
Model::factory(Transaccion::class)
->select('fecha')
->whereEqual('debito_id', $cuenta->id)
->orderByDesc('fecha')
->findOne(),
Model::factory(Transaccion::class)
->select('fecha')
->whereEqual('credito_id', $cuenta->id)
->orderByDesc('fecha')
->findOne()
];
if (!$fechas[0]) {
if (!$fechas[1]) {
return null;
}
return $fechas[1]->fecha();
}
if (!$fechas[1]) {
return $fechas[0]->fecha();
}
if ($fechas[0]->fecha() > $fechas[1]->fecha()) {
return $fechas[0]->fecha();
}
return $fechas[1]->fecha();
}
public function getTransacciones(Cuenta $cuenta, Carbon $fecha) {
$start = $fecha->copy()->startOfMonth();
$end = $fecha->copy()->endOfMonth();
$debitos = Model::factory(Transaccion::class)
->whereEqual('debito_id', $cuenta->id)
->whereRaw("fecha BETWEEN '{$start->format('Y-m-d')}' AND '{$end->format('Y-m-d')}'")
->findMany();
if ($debitos) {
array_walk($debitos, function(&$item) {
$item->valor *= -1;
});
}
$creditos = Model::factory(Transaccion::class)
->whereEqual('credito_id', $cuenta->id)
->whereRaw("fecha BETWEEN '{$start->format('Y-m-d')}' AND '{$end->format('Y-m-d')}'")
->findMany();
$transacciones = array_merge($debitos, $creditos);
foreach ($transacciones as &$transaccion) {
$transaccion->setFactory($this->factory);
}
return $transacciones;
}
}

View File

@ -0,0 +1,112 @@
<?php
namespace Contabilidad\Common\Service;
use Psr\Http\Message\UploadedFileInterface;
use Psr\Http\Message\StreamInterface;
use Nyholm\Psr7\Stream;
class FileHandler {
protected $base_folder;
protected $valid_types;
protected $folders;
public function __construct(object $params) {
$this->base_folder = $params->folder;
$this->addValidTypes(array_keys($params->types));
$this->addFolders($params->types);
}
public function addFolders(array $folders): FileHandler {
foreach ($folders as $type => $folder) {
$this->addFolder($type, $folder);
}
return $this;
}
public function addFolder(string $type, string $folder): FileHandler {
$this->folders[$type] = $folder;
return $this;
}
public function addValidTypes(array $valid_types): FileHandler {
foreach ($valid_types as $type) {
$this->addValidType($type);
}
return $this;
}
public function addValidType(string $type): FileHandler {
$this->valid_types []= $type;
return $this;
}
public function getType(string $folder): string {
return array_search($folder, $this->folders);
}
public function uploadFile(UploadedFileInterface $file, string $new_name = null): bool {
if ($file->getError() !== UPLOAD_ERR_OK) {
return false;
}
if (!in_array($file->getClientMediaType(), $this->valid_types)) {
return false;
}
if ($new_name === null) {
$new_name = $file->getClientFilename();
}
$filenfo = new \SplFileInfo($file->getClientFilename());
if (!str_contains($new_name, $filenfo->getExtension())) {
$new_name .= '.' . $filenfo->getExtension();
}
$to = implode(DIRECTORY_SEPARATOR, [$this->base_folder, $this->folders[$file->getClientMediaType()], $new_name]);
$file->moveTo($to);
return file_exists($to);
}
public function listFiles(): array {
$output = [];
foreach ($this->folders as $f) {
$folder = implode(DIRECTORY_SEPARATOR, [$this->base_folder, $f]);
if (!file_exists($folder)) {
continue;
}
$files = new \DirectoryIterator($folder);
foreach ($files as $file) {
if ($file->isDir()) {
continue;
}
$output []= (object) ['folder' => $f, 'filename' => $file->getBasename()];
}
}
return $output;
}
protected function validateFilename(string $folder, string $filename): bool|string {
if (!in_array($folder, $this->folders)) {
return false;
}
$f = implode(DIRECTORY_SEPARATOR, [$this->base_folder, $folder, $filename]);
if (!file_exists($f)) {
return false;
}
return $f;
}
public function getInfo(string $folder, string $filename): \SplFileInfo|bool {
if (!$f = $this->validateFilename($folder, $filename)) {
return false;
}
return new \SplFileInfo($f);
}
public function getFile(string $folder, string $filename): StreamInterface|bool {
if (!$f = $this->validateFilename($folder, $filename)) {
return false;
}
return Stream::create(file_get_contents($f));
}
public function editFilename(string $folder, string $filename, string $new_name): bool {
if (!$f = $this->validateFilename($folder, $filename)) {
return false;
}
$info = new \SplFileInfo($f);
$new = implode(DIRECTORY_SEPARATOR, [$this->base_folder, $folder, $new_name . '.' . $info->getExtension()]);
return rename($f, $new);
}
public function deleteFile(string $folder, string $filename): bool {
if (!$f = $this->validateFilename($folder, $filename)) {
return false;
}
return unlink($f);
}
}

View File

@ -0,0 +1,47 @@
<?php
namespace Contabilidad\Common\Service;
use ProVM\Common\Factory\Model as Factory;
use Contabilidad\Queue;
use Contabilidad\QueueArgument;
class Queuer {
protected Factory $factory;
public function __construct(Factory $factory) {
$this->setFactory($factory);
}
public function setFactory(Factory $factory): Queuer {
$this->factory = $factory;
return $this;
}
public function queue(string $command, array $arguments = []) {
if ($this->isProcessed($command, $arguments)) {
return;
}
$queue = $this->factory->create(Queue::class, ['command' => $command, 'created' => (new \DateTime('now'))->format('Y-m-d H:i:s')]);
$queue->save();
if (count($arguments) > 0) {
foreach ($arguments as $argument => $value) {
$arg = $this->factory->create(QueueArgument::class, ['queue_id' => $queue->id, 'argument' => $argument, 'value' => $value]);
$arg->save();
}
}
}
public function isProcessed(string $command, array $arguments = []): bool {
$queues = $this->find($command, $arguments);
if ($queues == null or count($queues) === 0) {
return false;
}
return true;
}
public function find(string $command, array $arguments = []): ?array {
$queues = $this->factory->find(Queue::class)->where([['command', $command], ['processed', 0]])->many();
if ($queues === null) {
return null;
}
if (count($arguments) > 0) {
$queues = array_filter($queues, function($item) use ($arguments) {return count($arguments) === count($item->matchArguments($arguments));});
}
return $queues;
}
}

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;
@ -20,30 +21,22 @@ class TiposCambios {
$this->base_url = $api_url; $this->base_url = $api_url;
$this->key = $api_key; $this->key = $api_key;
} }
public function get(string $fecha, int $moneda_id) { protected function getWeekday(\DateTimeInterface $fecha) {
$fecha = Carbon::parse($fecha);
$moneda = $this->factory->find(Moneda::class)->one($moneda_id);
if ($moneda->codigo == 'USD') {
if ($fecha->weekday() == 0) { if ($fecha->weekday() == 0) {
$fecha = $fecha->subWeek()->weekday(5); return $fecha->subWeek()->weekday(5);
} }
if ($fecha->weekday() == 6) { if ($fecha->weekday() == 6) {
$fecha = $fecha->weekday(5); return $fecha->weekday(5);
} }
return $fecha;
} }
$cambio = $moneda->cambio($fecha); protected function getValor(\DateTimeInterface $fecha, string $moneda_codigo) {
if ($cambio) {
if ($cambio->desde()->id != $moneda->id) {
return 1 / $cambio->valor;
}
return $cambio->valor;
}
$data = [ $data = [
'fecha' => $fecha->format('Y-m-d'), 'fecha' => $fecha->format('Y-m-d'),
'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,
@ -52,15 +45,39 @@ 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());
$valor = $result->serie[0]->valor; 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;
}
public function get(string $fecha, int $moneda_id) {
$fecha = Carbon::parse($fecha);
$moneda = $this->factory->find(Moneda::class)->one($moneda_id);
if ($moneda->codigo == 'USD') {
$fecha = $this->getWeekday($fecha);
}
// If a value exists in the database
$cambio = $moneda->cambio($fecha);
if ($cambio !== null) {
if ($cambio->desde()->id != $moneda->id) {
return 1 / $cambio->valor;
}
return $cambio->valor;
}
$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

@ -20,7 +20,7 @@ final class TipoCuenta extends AbstractMigration
{ {
$this->table('tipos_cuenta') $this->table('tipos_cuenta')
->addColumn('descripcion', 'string') ->addColumn('descripcion', 'string')
->addColumn('color', 'string', ['length' => 6]) ->addColumn('color', 'string', ['length' => 6, 'default' => 'ffffff'])
->create(); ->create();
} }
} }

View File

@ -21,7 +21,7 @@ final class Categoria extends AbstractMigration
$this->table('categorias') $this->table('categorias')
->addColumn('nombre', 'string') ->addColumn('nombre', 'string')
->addColumn('tipo_id', 'integer') ->addColumn('tipo_id', 'integer')
->addForeignKey('tipo_id', 'tipos_categoria') ->addForeignKey('tipo_id', 'tipos_categoria', ['delete' => 'cascade', 'update' => 'cascade'])
->create(); ->create();
} }
} }

View File

@ -21,9 +21,9 @@ final class Cuenta extends AbstractMigration
$this->table('cuentas') $this->table('cuentas')
->addColumn('nombre', 'string') ->addColumn('nombre', 'string')
->addColumn('categoria_id', 'integer') ->addColumn('categoria_id', 'integer')
->addForeignKey('categoria_id', 'categorias') ->addForeignKey('categoria_id', 'categorias', ['delete' => 'cascade', 'update' => 'cascade'])
->addColumn('tipo_id', 'integer') ->addColumn('tipo_id', 'integer')
->addForeignKey('tipo_id', 'tipos_cuenta') ->addForeignKey('tipo_id', 'tipos_cuenta', ['delete' => 'cascade', 'update' => 'cascade'])
->create(); ->create();
} }
} }

View File

@ -20,10 +20,10 @@ final class EstadoConeccion extends AbstractMigration
{ {
$this->table('estados_coneccion') $this->table('estados_coneccion')
->addColumn('coneccion_id', 'integer') ->addColumn('coneccion_id', 'integer')
->addForeignKey('coneccion_id', 'conecciones') ->addForeignKey('coneccion_id', 'conecciones', ['delete' => 'cascade', 'update' => 'cascade'])
->addColumn('fecha', 'date') ->addColumn('fecha', 'date')
->addColumn('tipo_id', 'integer') ->addColumn('tipo_id', 'integer')
->addForeignKey('tipo_id', 'tipos_estado_coneccion') ->addForeignKey('tipo_id', 'tipos_estado_coneccion', ['delete' => 'cascade', 'update' => 'cascade'])
->create(); ->create();
} }
} }

View File

@ -20,9 +20,9 @@ final class Transaccion extends AbstractMigration
{ {
$this->table('transacciones') $this->table('transacciones')
->addColumn('debito_id', 'integer') ->addColumn('debito_id', 'integer')
->addForeignKey('debito_id', 'cuentas') ->addForeignKey('debito_id', 'cuentas', ['delete' => 'cascade', 'update' => 'cascade'])
->addColumn('credito_id', 'integer') ->addColumn('credito_id', 'integer')
->addForeignKey('credito_id', 'cuentas') ->addForeignKey('credito_id', 'cuentas', ['delete' => 'cascade', 'update' => 'cascade'])
->addColumn('fecha', 'datetime') ->addColumn('fecha', 'datetime')
->addColumn('glosa', 'string') ->addColumn('glosa', 'string')
->addColumn('detalle', 'text') ->addColumn('detalle', 'text')

View File

@ -20,7 +20,7 @@ final class CuentaMoneda extends AbstractMigration
{ {
$this->table('cuentas') $this->table('cuentas')
->addColumn('moneda_id', 'integer') ->addColumn('moneda_id', 'integer')
->addForeignKey('moneda_id', 'monedas') ->addForeignKey('moneda_id', 'monedas', ['delete' => 'cascade', 'update' => 'cascade'])
->update(); ->update();
} }
} }

View File

@ -21,9 +21,9 @@ final class TipoCambio extends AbstractMigration
$this->table('tipos_cambio') $this->table('tipos_cambio')
->addColumn('fecha', 'datetime') ->addColumn('fecha', 'datetime')
->addColumn('desde_id', 'integer') ->addColumn('desde_id', 'integer')
->addForeignKey('desde_id', 'monedas') ->addForeignKey('desde_id', 'monedas', ['delete' => 'cascade', 'update' => 'cascade'])
->addColumn('hasta_id', 'integer') ->addColumn('hasta_id', 'integer')
->addForeignKey('hasta_id', 'monedas') ->addForeignKey('hasta_id', 'monedas', ['delete' => 'cascade', 'update' => 'cascade'])
->addColumn('valor', 'double') ->addColumn('valor', 'double')
->create(); ->create();
} }

View File

@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
use Phinx\Migration\AbstractMigration;
final class Queue 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('queue')
->addColumn('command', 'string')
->addColumn('created', 'datetime')
->addColumn('processed', 'boolean', ['default' => 0])
->create();
}
}

View File

@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
use Phinx\Migration\AbstractMigration;
final class QueueArguments 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('queue_arguments')
->addColumn('queue_id', 'integer')
->addForeignKey('queue_id', 'queue', ['delete' => 'cascade', 'update' => 'cascade'])
->addColumn('argument', 'string', ['length' => 100])
->addColumn('value', 'string')
->create();
}
}

View File

@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
use Phinx\Migration\AbstractMigration;
final class Consolidados 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('consolidados')
->addColumn('cuenta_id', 'integer')
->addForeignKey('cuenta_id', 'cuentas', ['delete' => 'cascade', 'update' => 'cascade'])
->addColumn('fecha', 'date')
->addColumn('periodo', 'string', ['length' => 50])
->addColumn('saldo', 'double')
->create();
}
}

View File

@ -5,25 +5,26 @@ server {
access_log /var/log/nginx/access.log; access_log /var/log/nginx/access.log;
root /app/public; root /app/public;
client_max_body_size 50M;
location / { location / {
try_files $uri $uri/ /index.php?$query_string; try_files $uri $uri/ /index.php?$query_string;
} }
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, 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';
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;
} }
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, 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';
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';
try_files $uri =404; try_files $uri =404;

View File

@ -1,2 +1,4 @@
log_errors = true log_errors = true
error_log = /var/log/php/error.log error_log = /var/log/php/error.log
upload_max_filesize = 50M
max_input_vars = 5000

View File

@ -1,7 +1,13 @@
<?php <?php
require_once implode(DIRECTORY_SEPARATOR, [ try {
ini_set('error_reporting', E_ALL & ~E_NOTICE & ~E_DEPRECATED);
$app = require_once implode(DIRECTORY_SEPARATOR, [
dirname(__DIR__), dirname(__DIR__),
'setup', 'setup',
'app.php' 'app.php'
]); ]);
$app->run(); $app->run();
} catch (Error | Exception $e) {
error_log($e);
throw $e;
}

View File

@ -3,4 +3,5 @@ use Contabilidad\Common\Controller\Base;
$app->get('/key/generate[/]', [Base::class, 'generate_key']); $app->get('/key/generate[/]', [Base::class, 'generate_key']);
$app->get('/balance[/]', [Contabilidad\Common\Controller\TiposCategorias::class, 'balance']); $app->get('/balance[/]', [Contabilidad\Common\Controller\TiposCategorias::class, 'balance']);
$app->get('/info', [Base::class, 'info']);
$app->get('/', Base::class); $app->get('/', Base::class);

View File

@ -0,0 +1,7 @@
<?php
use Contabilidad\Common\Controller\Consolidados;
$app->group('/consolidar', function($app) {
$app->get('/update/{mes}/{cuenta_id}', [Consolidados::class, 'update']);
$app->get('[/]', [Consolidados::class, 'cli']);
});

View File

@ -9,8 +9,11 @@ $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->put('/edit', [Cuentas::class, 'edit']); $app->put('/edit', [Cuentas::class, 'edit']);
$app->delete('/delete', [Cuentas::class, 'delete']); $app->delete('/delete', [Cuentas::class, 'delete']);
$app->get('[/]', [Cuentas::class, 'show']); $app->get('[/]', [Cuentas::class, 'show']);

View File

@ -0,0 +1,8 @@
<?php
use Contabilidad\Common\Controller\Queues;
$app->group('/queues', function($app) {
$app->get('/pending', [Queues::class, 'pending']);
$app->post('/processed', [Queues::class, 'processed']);
$app->get('[/]', Queues::class);
});

View File

@ -0,0 +1,12 @@
<?php
use Contabilidad\Common\Controller\Files;
$app->group('/uploads', function($app) {
$app->post('/add[/]', [Files::class, 'upload']);
$app->get('[/]', Files::class);
});
$app->group('/upload/{folder}/{filename}', function($app) {
$app->put('[/]', [Files::class, 'edit']);
$app->delete('[/]', [Files::class, 'delete']);
$app->get('[/]', [Files::class, 'get']);
});

View File

@ -44,3 +44,5 @@ if (file_exists($folder)) {
include_once 'databases.php'; include_once 'databases.php';
include_once 'router.php'; include_once 'router.php';
return $app;

View File

@ -0,0 +1,4 @@
<?php
use Contabilidad\Common\Middleware\Consolidar;
$app->add(new Consolidar($app->getContainer()->get(\Contabilidad\Common\Service\Consolidar::class)));

View File

@ -19,7 +19,7 @@ return [
'public' 'public'
]); ]);
$arr['uploads'] = implode(DIRECTORY_SEPARATOR, [ $arr['uploads'] = implode(DIRECTORY_SEPARATOR, [
$arr['public'], $arr['base'],
'uploads' 'uploads'
]); ]);
$arr['pdfs'] = implode(DIRECTORY_SEPARATOR, [ $arr['pdfs'] = implode(DIRECTORY_SEPARATOR, [

View File

@ -42,5 +42,17 @@ return [
$c->get('python_api'), $c->get('python_api'),
$c->get('python_key') $c->get('python_key')
); );
},
Contabilidad\Common\Service\FileHandler::class => function(Container $c) {
return new Contabilidad\Common\Service\FileHandler((object) [
'folder' => $c->get('folders')->uploads,
'types' => [
'text/csv' => 'csvs',
'application/pdf' => 'pdfs',
'application/vnd.ms-excel' => 'xlss',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'xlss',
'application/json' => 'jsons'
]
]);
} }
]; ];

View File

@ -0,0 +1,11 @@
<?php
use Psr\Container\ContainerInterface as Container;
return [
\Contabilidad\Common\Service\Queuer::class => function(Container $container) {
return new \Contabilidad\Common\Service\Queuer($container->get(\ProVM\Common\Factory\Model::class));
},
\Contabilidad\Common\Service\Consolidar::class => function(Container $container) {
return new \Contabilidad\Common\Service\Consolidar($container->get(\ProVM\Common\Factory\Model::class), $container->get(\Contabilidad\Common\Service\Queuer::class));
}
];

View File

@ -18,6 +18,11 @@ class Categoria extends Model {
public function cuentas() { public function cuentas() {
if ($this->cuentas === null) { if ($this->cuentas === null) {
$this->cuentas = $this->parentOf(Cuenta::class, [Model::CHILD_KEY => 'categoria_id']); $this->cuentas = $this->parentOf(Cuenta::class, [Model::CHILD_KEY => 'categoria_id']);
if ($this->cuentas !== null) {
usort($this->cuentas, function($a, $b) {
return strcmp($a->nombre, $b->nombre);
});
}
} }
return $this->cuentas; return $this->cuentas;
} }
@ -41,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;
} }
return $this->activos(); $cos[$p] = $cuentas;
} }
protected $pasivos; $this->cuentas_of = $cos;
public function pasivos() {
if ($this->pasivos === null) {
$this->activos = $this->getCuentasOf('Pasivo');
} }
return $this->pasivos; return $this->cuentas_of;
} }
protected $ganancias; protected $totales;
public function ganancias() { public function getTotales(Service $service) {
if ($this->ganancias === null) { if ($this->totales === null) {
$this->ganancias = $this->getCuentasOf('Ganancia'); $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;
} }
return $this->ganancias; $totals[$p] = array_reduce($cuentas, function($sum, $item) use ($service) {
return $sum + $item->saldo($service, true);
});
} }
protected $perdidas; $this->totales = $totals;
public function perdidas() {
if ($this->perdidas === null) {
$this->perdidas = $this->getCuentasOf('Perdida');
} }
return $this->perdidas; return $this->totales;
} }
protected $saldo; protected $saldo;
@ -89,4 +104,10 @@ class Categoria extends Model {
} }
return $this->saldo; return $this->saldo;
} }
public function toArray(): array {
$arr = parent::toArray();
$arr['tipo'] = $this->tipo()->toArray();
return $arr;
}
} }

48
api/src/Consolidado.php Normal file
View File

@ -0,0 +1,48 @@
<?php
namespace Contabilidad;
use DateTimeInterface;
use DateInterval;
use Carbon\Carbon;
use Carbon\CarbonInterval;
use ProVM\Common\Alias\Model;
/**
* @property int $id
* @property Cuenta $cuenta_id
* @property DateTimeInterface $fecha
* @property DateInterval $periodo
* @property float $saldo
*/
class Consolidado extends Model {
public static $_table = 'consolidados';
protected $cuenta;
public function cuenta() {
if ($this->cuenta === null) {
$this->cuenta = $this->childOf(Cuenta::class, [Model::SELF_KEY => 'cuenta_id']);
}
return $this->cuenta;
}
public function fecha(DateTimeInterface $fecha = null) {
if ($fecha === null) {
return Carbon::parse($this->fecha);
}
if ($this->periodo()->days > 31) {
$this->fecha = $fecha->format('Y-1-1');
} else {
$this->fecha = $fecha->format('Y-m-1');
}
return $this;
}
public function periodo(DateInterval $periodo = null) {
if ($periodo === null) {
return new CarbonInterval($this->periodo);
}
$this->periodo = CarbonInterval::getDateIntervalSpec($periodo);
return $this;
}
public function saldo() {
return $this->saldo;
}
}

View File

@ -52,10 +52,64 @@ class Cuenta extends Model {
} }
return $this->abonos; return $this->abonos;
} }
protected $consolidados;
public function consolidados() {
if ($this->consolidados === null) {
$this->consolidados = $this->parentOf(Consolidado::class, [Model::CHILD_KEY => 'cuenta_id']);
}
return $this->consolidados;
}
public function hasConsolidados(\DateTimeInterface $mes = null): bool {
$t = Carbon::now();
$q = Model::factory(Consolidado::class)
->whereEqual('cuenta_id', $this->id);
if ($mes !== null) {
//$q = $q->whereEqual('fecha', $mes->format('Y-m-1'));
$q = $q->whereRaw("fecha BETWEEN '{$mes->format('Y-m-1')}' AND '{$mes->format('Y-m-t')}'");
}
$q = $q->count('id');
return (bool) $q;
}
public function hasConsolidadosPending(\DateTimeInterface $mes = null): bool {
$t = Carbon::now();
$q = Model::factory(Consolidado::class)
->whereEqual('cuenta_id', $this->id)
->whereGte('fecha', $t->copy()->subMonthNoOverflow()->startOfMonth()->format('Y-m-d'));
if ($mes !== null) {
$q = $q->whereRaw("fecha BETWEEN '{$mes->format('Y-m-1')}' AND '{$mes->format('Y-m-t')}'");
}
return !(bool) $q
->orderByDesc('fecha')
->count('id');
}
public function hasTransacciones(\DateTimeInterface $mes = null): bool {
$q = Model::factory(Transaccion::class)
->select('transacciones.*')
->join('cuentas', 'cuentas.id = transacciones.debito_id OR cuentas.id = transacciones.credito_id')
->whereEqual('cuentas.id', $this->id);
if ($mes !== null) {
$q = $q->whereRaw("fecha BETWEEN '{$mes->format('Y-m-1')}' AND '{$mes->format('Y-m-t')}'");
}
return (bool) $q->count('transacciones.id');
}
protected $transacciones; protected $transacciones;
protected function parseTransaccion(Transaccion $transaccion) {
$transaccion->setFactory($this->factory);
if ($this->tipo()->cargo()) {
if ($transaccion->credito_id == $this->id) {
$transaccion->valor = -$transaccion->valor;
}
} else {
if ($transaccion->debito_id == $this->id) {
$transaccion->valor = -$transaccion->valor;
}
}
$transaccion->valor = $transaccion->transformar($this->moneda());
return $transaccion;
}
public function transacciones($limit = null, $start = 0) { 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 +117,43 @@ 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 = $this->parseTransaccion($transaccion);
if ($transaccion->desde_id === $this->id) {
$transaccion->valor = - $transaccion->valor;
} }
return $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 = $this->parseTransaccion($transaccion);
} }
return $this->transacciones;
return $transacciones;
}
public function acumulacion(Carbon $date) {
$consolidados = $this->consolidados();
if ($consolidados === null) {
return 0;
}
$saldo = 0;
foreach ($consolidados as $consolidado) {
if ($consolidado->fecha() >= $date) {
continue;
}
$saldo += $consolidado->saldo();
}
return $saldo;
} }
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,25 +16,28 @@ 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, Moneda $moneda = null) {
if ($moneda === null) {
$moneda = $this->factory->find(Moneda::class)->one(1);
} }
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', $moneda->id], ['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', $moneda->id], ['fecha', $fecha->format('Y-m-d H:i:s')]])
->one(); ->one();
} }
return $cambio; return $cambio;
} }
public function cambiar(\DateTime $fecha, float $valor) { public function cambiar(\DateTime $fecha, float $valor, Moneda $moneda = null) {
$cambio = $this->cambio($fecha); $cambio = $this->cambio($fecha, $moneda);
if (!$cambio) { if (!$cambio) {
return $valor; return $valor;
} }
@ -43,4 +46,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;
}
} }

98
api/src/Queue.php Normal file
View File

@ -0,0 +1,98 @@
<?php
namespace Contabilidad;
use DateTimeInterface;
use Carbon\Carbon;
use ProVM\Common\Alias\Model;
use ProVM\Common\Factory\Model as Factory;
/**
* @property int $id
* @property string $command
* @property DateTimeInterface $created
* @property bool $processed
*/
class Queue extends Model {
public static $_table = 'queue';
protected static $fields = ['command', 'created', 'processed'];
public function created(DateTimeInterface $fecha = null) {
if ($fecha === null) {
return Carbon::parse($this->created);
}
$this->created = $fecha->format('Y-m-d H:i:s');
return $this;
}
public function hasArguments(): bool {
return Model::factory(QueueArgument::class)
->whereEqual('queue_id', $this->id)
->groupBy('queue_id')
->count('id') > 0;
}
protected $arguments;
public function arguments() {
if ($this->arguments === null) {
$this->arguments = $this->parentOf(QueueArgument::class, [Model::CHILD_KEY => 'queue_id']);
}
return $this->arguments;
}
public function matchArguments(array $arguments): array {
$args = $this->arguments();
if ($args === null) {
return [];
}
$matched = [];
foreach ($arguments as $argument => $value) {
foreach ($args as $arg) {
if ($arg->argument == $argument and $arg->value == $value) {
$matched []= $arg;
}
}
}
return $matched;
}
public function isProcessed(): bool {
return $this->processed > 0;
}
public function setProcessed(bool $processed): Queue {
$this->processed = $processed ? 1 : 0;
return $this;
}
public static function find(Factory $factory, $data) {
$where = [
'command' => $data['command'],
'processed' => $data['processed'] ?? 0
];
array_walk($where, function(&$item, $key) {
$item = [$key, $item];
});
$where = array_values($where);
return $factory->find(Queue::class)->where($where)->one();
}
public function toArray(): array
{
$arr = parent::toArray();
$cmd = [(string) $this];
$arr['arguments'] = [];
if ($this->hasArguments()) {
$arr['arguments'] = array_map(function($item) use (&$cmd) {
$cmd []= (string) $item;
return $item->toArray();
}, $this->arguments());
}
$arr['cmd'] = implode(' ', $cmd);
return $arr;
}
public function __toString(): string {
$str = "{$this->command}";
$arguments = $this->arguments();
if ($arguments !== null and count($arguments) > 0) {
$arguments = implode(' ', array_map(function($item) {return (string) $item;}, $arguments));
$str .= " {$arguments}";
}
return $str;
}
}

26
api/src/QueueArgument.php Normal file
View File

@ -0,0 +1,26 @@
<?php
namespace Contabilidad;
use ProVM\Common\Alias\Model;
/**
* @property int $id
* @property Queue $queue_id
* @property string $argument
* @property string $value
*/
class QueueArgument extends Model {
public static $_table = 'queue_arguments';
protected $queue;
public function queue() {
if ($this->queue === null) {
$this->queue = $this->childOf(Queue::class, [Model::SELF_KEY => 'queue_id']);
}
return $this->queue;
}
public function __toString(): string {
return "--{$this->argument} '{$this->value}'";
}
}

View File

@ -23,7 +23,7 @@ class TipoCategoria extends Model {
public function getCuentasOf($tipo) { public function getCuentasOf($tipo) {
return $this->factory->find(Cuenta::class) return $this->factory->find(Cuenta::class)
->select([['cuentas', '*']]) ->select('cuentas.*')
->join([ ->join([
['tipos_cuenta', 'tipos_cuenta.id', 'cuentas.tipo_id'], ['tipos_cuenta', 'tipos_cuenta.id', 'cuentas.tipo_id'],
['categorias', 'categorias.id', 'cuentas.categoria_id'] ['categorias', 'categorias.id', 'cuentas.categoria_id']
@ -33,11 +33,31 @@ 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) {
if ($this->saldo === null) { if ($this->saldo === null) {
$this->saldo = array_reduce($this->categorias(), function($sum, $item) use ($service) { $this->saldo = array_reduce($this->categorias() ?? [], function($sum, $item) use ($service) {
return $sum + $item->saldo($service); return $sum + $item->saldo($service);
}); });
} }

View File

@ -11,4 +11,17 @@ use ProVM\Common\Alias\Model;
class TipoCuenta extends Model { class TipoCuenta extends Model {
public static $_table = 'tipos_cuenta'; public static $_table = 'tipos_cuenta';
protected static $fields = ['descripcion', 'color']; protected static $fields = ['descripcion', 'color'];
public function cargo()
{
$tipos = [
'activo',
'perdida',
'banco'
];
if (in_array(strtolower($this->descripcion), $tipos)) {
return false;
}
return true;
}
} }

View File

@ -13,10 +13,11 @@ use ProVM\Common\Alias\Model;
* @property string $glosa * @property string $glosa
* @property string $detalle * @property string $detalle
* @property double $valor * @property double $valor
* @property Moneda $moneda_id
*/ */
class Transaccion extends Model { class Transaccion extends Model {
public static $_table = 'transacciones'; public static $_table = 'transacciones';
protected static $fields = ['debito_id', 'credito_id', 'fecha', 'glosa', 'detalle', 'valor']; protected static $fields = ['debito_id', 'credito_id', 'fecha', 'glosa', 'detalle', 'valor', 'moneda_id'];
protected $debito; protected $debito;
public function debito() { public function debito() {
@ -37,6 +38,21 @@ class Transaccion extends Model {
return Carbon::parse($this->fecha); return Carbon::parse($this->fecha);
} }
$this->fecha = $fecha->format('Y-m-d'); $this->fecha = $fecha->format('Y-m-d');
return $this;
}
protected $moneda;
public function moneda() {
if ($this->moneda === null) {
$this->moneda = $this->childOf(Moneda::class, [Model::SELF_KEY => 'moneda_id']);
}
return $this->moneda;
}
public function transformar(Moneda $moneda = null) {
if (($moneda !== null and $this->moneda()->id === $moneda->id) or ($moneda === null and $this->moneda()->id === 1)) {
return $this->valor;
}
return $this->moneda()->cambiar($this->fecha(), $this->valor, $moneda);
} }
public function toArray(): array { public function toArray(): array {

12
console/Dockerfile Normal file
View File

@ -0,0 +1,12 @@
FROM php:8-cli
RUN apt-get update -y && apt-get install -y cron git libzip-dev zip
RUN docker-php-ext-install zip
COPY --from=composer /usr/bin/composer /usr/bin/composer
WORKDIR /app
CMD ["cron", "-f", "-l", "2"]
ENTRYPOINT ["cron", "-f", "-l", "2"]

7
console/bin/console Normal file
View File

@ -0,0 +1,7 @@
<?php
$app = require_once implode(DIRECTORY_SEPARATOR, [
dirname(__FILE__, 2),
'setup',
'app.php'
]);
$app->run();

View File

@ -0,0 +1,37 @@
<?php
namespace Contabilidad\Common\Command;
use Psr\Http\Client\ClientInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class Consolidar extends Command {
protected $client;
public function __construct(ClientInterface $client = null, string $name = null) {
parent::__construct($name);
$this->setClient($client);
}
public function setClient(ClientInterface $client) {
$this->client = $client;
return $this;
}
public function getClient(): ClientInterface {
return $this->client;
}
public function execute(InputInterface $input, OutputInterface $output) {
try {
$response = $this->getClient()->get('/consolidar');
if ($response->getStatusCode() === 200) {
return Command::SUCCESS;
}
error_log($response->getReasonPhrase());
return Command::FAILURE;
} catch (\Exception $e) {
error_log($e);
return Command::FAILURE;
}
}
}

View File

@ -0,0 +1,53 @@
<?php
namespace Contabilidad\Common\Command;
use Psr\Http\Client\ClientInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class Queue extends Command {
protected $client;
public function __construct(ClientInterface $client = null, string $name = null) {
parent::__construct($name);
$this->setClient($client);
}
public function setClient(ClientInterface $client) {
$this->client = $client;
return $this;
}
public function getClient(): ClientInterface {
return $this->client;
}
public function execute(InputInterface $input, OutputInterface $output)
{
$response = $this->getClient()->get('/queues/pending');
if ($response->getStatusCode() !== 200) {
return Command::FAILURE;
}
$data = json_decode($response->getBody()->getContents());
$output = [
'input' => $data,
'processed' => []
];
foreach ($data->pending as $queue) {
$log = "Running {$queue->command} from queue. Created in {$queue->created}.";
error_log($log);
$cmd = '/usr/local/bin/php /app/bin/console ' . $queue->cmd;
exec($cmd, $result, $code);
if ($code != Command::SUCCESS) {
error_log(var_export($queue, true));
error_log(var_export($result, true));
continue;
}
$output['processed'] []= $queue->id;
}
$response = $this->getClient()->post('/queues/processed', ['json' => $output]);
if ($response->getStatusCode() !== 200) {
return Command::FAILURE;
}
return Command::SUCCESS;
}
}

View File

@ -0,0 +1,38 @@
<?php
namespace Contabilidad\Common\Command;
use Psr\Http\Client\ClientInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class UpdateConsolidar extends Command {
protected $client;
public function __construct(ClientInterface $client = null, string $name = null) {
parent::__construct($name);
$this->setClient($client);
}
public function setClient(ClientInterface $client) {
$this->client = $client;
return $this;
}
public function getClient(): ClientInterface {
return $this->client;
}
public function execute(InputInterface $input, OutputInterface $output) {
try {
$mes = $input->getArgument('mes');
$cuenta = $input->getArgument('cuenta');
$response = $this->getClient()->get("/consolidar/update/{$mes}/{$cuenta}/");
if ($response->getStatusCode() === 200) {
return Command::SUCCESS;
}
error_log($response->getReasonPhrase());
return Command::FAILURE;
} catch (\Exception $e) {
error_log($e);
return Command::FAILURE;
}
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace Contabilidad\Common\Define;
use Psr\Container\ContainerInterface as Container;
use Symfony\Component\Console\Application as Base;
class Application extends Base {
public function __construct(Container $container = null, string $name = 'UNKNOWN', string $version = 'UNKNOWN')
{
parent::__construct($name, $version);
$this->setContainer($container);
}
protected $container;
public function setContainer(Container $container) {
$this->container = $container;
return $this;
}
public function getContainer() {
return $this->container;
}
}

25
console/composer.json Normal file
View File

@ -0,0 +1,25 @@
{
"name": "provm/contabilidad-console",
"type": "project",
"require": {
"symfony/console": "^6.0",
"php-di/php-di": "^6.3",
"nesbot/carbon": "^2.57",
"guzzlehttp/guzzle": "^7.4"
},
"require-dev": {
"phpunit/phpunit": "^9.5",
"kint-php/kint": "^4.1"
},
"authors": [
{
"name": "Aldarien",
"email": "aldarien85@gmail.com"
}
],
"autoload": {
"psr-4": {
"Contabilidad\\Common\\": "common/"
}
}
}

24
console/crontab Normal file
View File

@ -0,0 +1,24 @@
# Edit this file to introduce tasks to be run by cron.
#
# Each task to run has to be defined through a single line
# indicating with different fields when the task will be run
# and what command to run for the task
#
# To define the time you can provide concrete values for
# minute (m), hour (h), day of month (dom), month (mon),
# and day of week (dow) or use '*' in these fields (for 'any').
#
# Notice that tasks will be started based on the cron's system
# daemon's notion of time and timezones.
#
# Output of the crontab jobs (including errors) is sent through
# email to the user the crontab file belongs to (unless redirected).
#
# For example, you can run a backup of all your user accounts
# at 5 a.m every week with:
# 0 5 * * 1 tar -zcf /var/backups/home.tgz /home/
#
# For more information see the manual pages of crontab(5) and cron(8)
#
# m h dom mon dow command
0 2 * * * /usr/local/bin/php /app/bin/console queue

4
console/php.ini Normal file
View File

@ -0,0 +1,4 @@
[PHP]
display_errors = E_ALL
log_errors = true
error_log = /var/log/php/error.log

39
console/setup/app.php Normal file
View File

@ -0,0 +1,39 @@
<?php
use DI\ContainerBuilder as Builder;
use Contabilidad\Common\Define\Application;
require_once 'composer.php';
$builder = new Builder();
$folders = [
'settings',
'setups'
];
foreach ($folders as $f) {
$folder = implode(DIRECTORY_SEPARATOR, [__DIR__, $f]);
if (!file_exists($folder)) {
continue;
}
$files = new DirectoryIterator($folder);
foreach ($files as $file) {
if ($file->isDir()) {
continue;
}
$builder->addDefinitions($file->getRealPath());
}
}
$container = $builder->build();
$app = new Application($container);
$folder = implode(DIRECTORY_SEPARATOR, [__DIR__, 'commands']);
if (file_exists($folder)) {
$files = new DirectoryIterator($folder);
foreach ($files as $file) {
if ($file->isDir() or $file->getExtension() != 'php') {
continue;
}
include_once $file->getRealPath();
}
}
return $app;

View File

@ -0,0 +1,4 @@
<?php
use Contabilidad\Common\Command\Queue;
$app->add(new Queue($app->getContainer()->get(\Psr\Http\Client\ClientInterface::class), 'queue'));

View File

@ -0,0 +1,4 @@
<?php
use Contabilidad\Common\Command\Consolidar;
$app->add(new Consolidar($app->getContainer()->get(\Psr\Http\Client\ClientInterface::class), 'consolidar'));

View File

@ -0,0 +1,4 @@
<?php
use Contabilidad\Common\Command\UpdateConsolidar;
$app->add(new UpdateConsolidar($app->getContainer()->get(\Psr\Http\Client\ClientInterface::class), 'update_consolidar'));

View File

@ -0,0 +1,6 @@
<?php
require_once implode(DIRECTORY_SEPARATOR, [
dirname(__DIR__),
'vendor',
'autoload.php'
]);

View File

@ -0,0 +1,5 @@
<?php
return [
'api_url' => $_ENV['API_URL'],
'api_key' => $_ENV['API_KEY']
];

View File

@ -0,0 +1,13 @@
<?php
use Psr\Container\ContainerInterface as Container;
return [
\Psr\Http\Client\ClientInterface::class => function(Container $container) {
return new \GuzzleHttp\Client([
'base_uri' => $container->get('api_url'),
'headers' => [
'Authorization' => "Bearer {$container->get('api_key')}"
]
]);
}
];

View File

@ -1,15 +1,18 @@
version: '3' version: '3'
x-restart: &restart
restart: unless-stopped
services: services:
api: api:
profiles: profiles:
- api - api
restart: unless-stopped <<: *restart
image: php image: php
build: build:
context: api context: api
env_file: env_file:
- .env - .db.env
- .api.env - .api.env
- .python.env - .python.env
volumes: volumes:
@ -19,26 +22,26 @@ services:
api-proxy: api-proxy:
profiles: profiles:
- api - api
restart: unless-stopped <<: *restart
image: nginx image: nginx
ports: ports:
- "9001:80" - "9001:80"
volumes: volumes:
- ./api/nginx.conf:/etc/nginx/conf.d/default.conf - ./api/nginx.conf:/etc/nginx/conf.d/default.conf
- ./logs/api/:/var/log/nginx/ - ./logs/api/proxy/:/var/log/nginx/
- ./api/:/app/ - ./api/:/app/
db: db:
profiles: profiles:
- api - api
restart: unless-stopped <<: *restart
image: mariadb image: mariadb
env_file: .env env_file: .db.env
volumes: volumes:
- contabilidad_data:/var/lib/mysql - contabilidad_data:/var/lib/mysql
adminer: adminer:
profiles: profiles:
- api - api
restart: unless-stopped <<: *restart
image: adminer image: adminer
ports: ports:
- "9002:8080" - "9002:8080"
@ -46,10 +49,11 @@ services:
ui: ui:
profiles: profiles:
- ui - ui
restart: unless-stopped <<: *restart
image: php-ui image: php-ui
env_file: env_file:
- .api.env - .api.env
- .env
build: build:
context: ui context: ui
volumes: volumes:
@ -59,19 +63,19 @@ services:
ui-proxy: ui-proxy:
profiles: profiles:
- ui - ui
restart: unless-stopped <<: *restart
image: nginx image: nginx
ports: ports:
- "9000:80" - "9000:80"
volumes: volumes:
- ./ui/nginx.conf:/etc/nginx/conf.d/default.conf - ./ui/nginx.conf:/etc/nginx/conf.d/default.conf
- ./logs/ui/:/var/log/nginx/ - ./logs/ui/proxy/:/var/log/nginx/
- ./ui/:/app/ - ./ui/:/app/
python: python:
profiles: profiles:
- python - python
restart: unless-stopped <<: *restart
build: build:
context: ./python context: ./python
env_file: env_file:
@ -84,5 +88,21 @@ services:
- ./api/public/uploads/pdfs/:/app/data/ - ./api/public/uploads/pdfs/:/app/data/
- ./logs/python/:/var/log/python/ - ./logs/python/:/var/log/python/
console:
profiles:
- console
<<: *restart
build:
context: ./console
env_file:
- .api.env
- .console.env
- .db.env
volumes:
- ./console/:/app/
- ./console/php.ini:/usr/local/etc/php/conf.d/php.ini
- ./logs/console/:/var/log/php/
- ./console/crontab:/var/spool/cron/crontabs/root
volumes: volumes:
contabilidad_data: contabilidad_data:

View File

@ -1,5 +1,8 @@
import json import json
import os import os
import time
import timeit
import tensorflow as tf import tensorflow as tf
import sklearn import sklearn
import numpy as np import numpy as np
@ -83,6 +86,7 @@ class AI:
self.__dict = None self.__dict = None
self.__network = None self.__network = None
self.__sources = None self.__sources = None
self._phrases = None
self.filename = '' self.filename = ''
def add_source(self, text): def add_source(self, text):
@ -108,7 +112,6 @@ class AI:
os.remove(temp) os.remove(temp)
word_list = th.split_words(obj) word_list = th.split_words(obj)
fits = encoder.fit_transform(word_list) fits = encoder.fit_transform(word_list)
print(fits)
phrases = [] phrases = []
for length in range(1, len(word_list) + 1): for length in range(1, len(word_list) + 1):
for start in range(0, len(word_list)): for start in range(0, len(word_list)):
@ -117,7 +120,7 @@ class AI:
np.zeros([len(word_list) - len(phrase)])) np.zeros([len(word_list) - len(phrase)]))
phrases.append(phrase) phrases.append(phrase)
phrases = np.array(phrases) phrases = np.array(phrases)
print(phrases.shape) self._phrases = phrases
def active_train(self): def active_train(self):
pass pass

View File

@ -1,102 +0,0 @@
import json
from src.ai.word import Word, WordType
class PhraseType:
TEXT = 0
TITLE = 1
HEADER = 2
MOVEMENT = 3
INVALID = 99
def __init__(self):
self.__id = 0
self.__description = 'text'
def get_id(self):
return self.__id
def get_desc(self):
return self.__description
def to_json(self):
return self.__id
def load(self, phrase_id: int):
self.__id = phrase_id
if phrase_id == self.TITLE:
self.__description = 'title'
elif phrase_id == self.HEADER:
self.__description = 'header'
elif phrase_id == self.MOVEMENT:
self.__description = 'movement'
elif phrase_id == self.INVALID:
self.__description = 'invalid'
return self
def phrase_factory(phrase: list, phrase_type: int = None, frec: int = 1):
pt = PhraseType()
if phrase_type is not None:
pt.load(phrase_type)
ph = Phrase()
ph.set_phrase(phrase).set_type(pt).add_frec(frec - 1)
return ph
class Phrase:
def __init__(self):
self.__phrase = None
self.__type = None
self.__frec = 1
def to_json(self):
return {
'phrase': [w.to_json() for w in self.__phrase],
'type': self.__type.to_json(),
'frec': self.__frec
}
def set_phrase(self, phrase: list):
[self.add_word(w) for w in phrase]
return self
def get_phrase(self):
return self.__phrase
def set_type(self, phrase_type: PhraseType):
self.__type = phrase_type
return self
def get_type(self):
return self.__type
def add_word(self, word: Word, pos: int = None):
if self.__phrase is None:
self.__phrase = []
if pos is None:
self.__phrase.append(word)
return self
self.__phrase = self.__phrase[:pos] + [word] + self.__phrase[pos:]
return self
def add_frec(self, amount: int = 1):
self.__frec += amount
def match(self, words: list):
if len(words) != len(self.__phrase):
return False
for w in self.__phrase:
if w not in words:
return False
return True
def __repr__(self):
print(self.__phrase)
return json.dumps({
'phrase': [w.to_json() for w in self.get_phrase()],
'type': self.get_type().get_desc()
})
def __len__(self):
return len(self.__phrase)

View File

@ -1,84 +0,0 @@
import json
class WordType:
STRING = 0
NUMERIC = 1
CURRENCY = 2
DATE = 3
def __init__(self):
self.__id = 0
self.__description = 'string'
def to_json(self):
return self.__id
def load(self, word_id: int):
self.__id = word_id
if word_id == self.NUMERIC:
self.__description = 'numeric'
elif word_id == self.CURRENCY:
self.__description = 'currency'
elif word_id == self.DATE:
self.__description = 'data'
return self
def get_id(self):
return self.__id
def get_desc(self):
return self.__description
def __repr__(self):
return {
'id': self.get_id(),
'description': self.get_desc()
}
def word_factory(word: str, word_type: int = None, frec: int = 1):
wt = WordType()
if word_type is not None:
wt.load(word_type)
w = Word()
w.set_word(word).set_type(wt).add_frec(frec - 1)
return w
class Word:
def __init__(self):
self.__word = None
self.__type = None
self.__frec = 1
def to_json(self):
return {
'word': self.__word,
'type': self.__type.to_json(),
'frec': self.__frec
}
def set_word(self, word: str):
self.__word = word
return self
def get_word(self):
return self.__word
def set_type(self, word_type: WordType):
self.__type = word_type
return self
def get_type(self):
return self.__type
def add_frec(self, amount: int = 1):
self.__frec += amount
return self
def __repr__(self):
return json.dumps({
'word': self.get_word(),
'type': self.get_type().get_desc()
})

View File

@ -22,7 +22,10 @@ def validate_key(request_obj):
if isinstance(auth, list): if isinstance(auth, list):
auth = auth[0] auth = auth[0]
if 'Bearer' in auth: if 'Bearer' in auth:
try:
auth = auth.split(' ')[1] auth = auth.split(' ')[1]
except:
return False
return auth == api_key return auth == api_key
if 'API_KEY' in request_obj.values: if 'API_KEY' in request_obj.values:
return request_obj.values.get('API_KEY') == api_key return request_obj.values.get('API_KEY') == api_key

View File

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

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

@ -0,0 +1,12 @@
<?php
namespace Contabilidad\Common\Controller;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
use Slim\Views\Blade as View;
class Importar {
public function __invoke(Request $request, Response $response, View $view): Response {
return $view->render($response, 'importar');
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace Contabilidad\Common\Controller;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Views\Blade as View;
class Queues
{
public function __invoke(Request $request, Response $response, View $view): Response {
return $view->render($response, 'queues.list');
}
}

View File

@ -0,0 +1,24 @@
<?php
namespace Contabilidad\Common\Controller;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
use Slim\Views\Blade as View;
use GuzzleHttp\Client;
class Uploads {
public function __invoke(Request $request, Response $response, View $view): Response {
return $view->render($response, 'uploads.list');
}
public function get(Request $request, Response $response, Client $client, $folder, $filename): Response {
$resp = $client->get(implode('/', ['upload', $folder, $filename]));
$file = $resp->getBody();
return $response
->withHeader('Content-Type', $resp->getHeader('Content-Type'))
->withHeader('Content-Disposition', 'attachment; filename=' . $filename)
->withAddedHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
->withHeader('Cache-Control', 'post-check=0, pre-check=0')
->withHeader('Pragma', 'no-cache')
->withBody($file);
}
}

View File

@ -5,10 +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",
"berrnd/slim-blade-view": "^1.0"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "^9.5", "phpunit/phpunit": "^9.5",
@ -24,11 +25,5 @@
"psr-4": { "psr-4": {
"Contabilidad\\Common\\": "common" "Contabilidad\\Common\\": "common"
} }
},
"repositories": [
{
"type": "git",
"url": "http://git.provm.cl/ProVM/controller.git"
} }
]
} }

View File

@ -90,9 +90,7 @@ const tipos_categorias = {
this.draw() this.draw()
}) })
}, },
getParent: function() { buildParent: function(segment) {
let parent = $(this.id).find('tbody')
if (parent.length === 0) {
const table = $('<table></table>').attr('class', 'ui table').append( const table = $('<table></table>').attr('class', 'ui table').append(
$('<thead></thead>').append( $('<thead></thead>').append(
$('<tr></tr>').append( $('<tr></tr>').append(
@ -115,7 +113,14 @@ const tipos_categorias = {
}) })
parent = $('<tbody></tbody>') parent = $('<tbody></tbody>')
table.append(parent) table.append(parent)
$(this.id).append(table) segment.append(table)
return parent
},
getParent: function() {
const segment = $(this.id)
let parent = segment.find('tbody')
if (parent.length === 0) {
parent = this.buildParent(segment)
} }
return parent return parent
}, },

View File

@ -1,231 +0,0 @@
class Transaccion {
constructor({id, debito_id, credito_id, fecha, glosa, detalle, valor, debito, credito, fechaFormateada, valorFormateado}) {
this.id = id
this.debito_id = debito_id
this.credito_id = credito_id
this.fecha = {
fecha,
formateada: fechaFormateada
}
this.glosa = glosa
this.detalle = detalle
this.valor = {
valor,
formateado: valorFormateado
}
this.debito = debito
this.credito = credito
this.modal = null
}
setCuenta(cuenta) {
this.cuenta = cuenta
}
setModal(modal) {
this.modal = modal
}
isDebito() {
return this.debito.id === this.cuenta.id;
}
isIncrement() {
const debits = ['Activo', 'Perdida']
if (debits.indexOf(this.cuenta.tipo.descripcion)) {
return this.isDebito()
}
return !this.isDebito()
}
draw({saldo, format}) {
const fuente = (this.isDebito()) ? this.credito : this.debito
return $('<tr></tr>').append(
$('<td></td>').html(this.fecha.formateada)
).append(
$('<td></td>').append(
$('<a></a>').attr('href', _urls.base + 'cuenta/' + fuente.id).html(fuente.nombre + ' (' + fuente.categoria.nombre + ')')
)
).append(
$('<td></td>').html(this.glosa + '<br />' + this.detalle)
).append(
$('<td></td>').attr('class', 'right aligned').html((this.isIncrement()) ? '' : format.format(this.valor.valor))
).append(
$('<td></td>').attr('class', 'right aligned').html((this.isIncrement()) ? format.format(this.valor.valor) : '')
).append(
$('<td></td>').attr('class', 'right aligned').html(format.format(saldo))
).append(
$('<td></td>').attr('class', 'right aligned')/*.append(
$('<button></button>').attr('class', 'ui tiny circular icon button').append(
$('<i></i>').attr('class', 'edit icon')
).click((e) => {
e.preventDefault()
this.edit()
return false
})
)*/.append(
$('<button></button>').attr('class', 'ui tiny circular red icon button').append(
$('<i></i>').attr('class', 'remove icon')
).click((e) => {
e.preventDefault()
this.remove()
return false
})
)
)
}
edit() {
const form = this.modal.find('form')
form.find("[name='fecha']")
}
remove() {
sendDelete(_urls.api + '/transaccion/' + this.id + '/delete').then(() => {
transacciones.get().transacciones()
})
}
}
const transacciones = {
id: '#transacciones',
cuenta_id: 0,
cuenta: null,
transacciones: [],
cuentas: [],
saldo: 0,
get: function() {
return {
transacciones: () => {
let promises = []
sendGet(_urls.api + '/cuenta/' + this.cuenta_id + '/transacciones/amount').then((data) => {
if (data.cuenta === null) {
return
}
this.cuenta = data.cuenta
this.saldo = this.cuenta.saldo
$('#cuenta').html(this.cuenta.nombre + ' (' + this.cuenta.categoria.nombre + ')').append(
$('<i></i>').attr('class', 'square full icon').css('color', '#' + this.cuenta.tipo.color)
)
const amount = data.transacciones
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) {
Promise.all(promises).then((data_arr) => {
this.transacciones = []
data_arr.forEach(data => {
if (data.transacciones === null || data.transacciones.length === 0) {
return
}
$.each(data.transacciones, (i, el) => {
const tr = new Transaccion(el)
tr.setCuenta(this.cuenta)
tr.setModal(this.modal)
this.transacciones.push(tr)
})
})
this.transacciones.sort((a, b) => {
return (new Date(b.fecha)) - (new Date(a.fecha))
})
}).then(() => {
this.draw()
})
} else {
this.draw()
}
})
},
cuentas: () => {
return sendGet(_urls.api + '/cuentas').then((data) => {
if (data.cuentas === null || data.cuentas.length === 0) {
return
}
this.cuentas = data.cuentas
}).then(() => {
const select = this.modal.find("[name='cuenta']")
$.each(this.cuentas, (i, el) => {
select.append(
$('<option></option>').attr('value', el.id).html(el.nombre + ' (' + el.categoria.nombre + ')')
)
})
})
}
}
},
draw: function() {
const format = Intl.NumberFormat('es-CL', {style: 'currency', currency: this.cuenta.moneda.codigo})
const parent = $(this.id)
parent.html('')
$.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)
})
},
add: function() {
return {
show: () => {
this.modal.find('form').trigger('reset')
this.modal.modal('show')
},
exec: () => {
const fecha = $("[name='fecha']").val()
const data1 = JSON.stringify({
desde_id: $("[name='moneda']").val(),
hasta_id: 1,
fecha: fecha,
valor: $("[name='cambio']").val()
})
sendPut(_urls.api + '/tipos/cambios/add', data1)
const valor = $("[name='valor']").val()
const cuenta = $("[name='cuenta']").val()
const data = JSON.stringify({
debito_id: (valor < 0) ? this.cuenta_id : cuenta,
credito_id: (valor < 0) ? cuenta : this.cuenta_id,
fecha: fecha,
glosa: $("[name='glosa']").val(),
detalle: $("[name='detalle']").val(),
valor: (valor < 0) ? -valor : valor
})
return sendPost(_urls.api + '/transacciones/add', data).then(() => {
this.modal.modal('hide')
this.get().transacciones()
})
}
}
},
build: function() {
return {
modal: () => {
this.modal = $('.ui.modal')
this.modal.modal()
this.modal.find('.close.icon').click(() => {
this.modal.modal('hide')
})
this.modal.find('form').submit((e) => {
e.preventDefault()
this.add().exec()
return false
})
this.modal.find('.ui.calendar').calendar({
type: 'date',
formatter: {
date: function(date, settings) {
if (!date) return ''
let day = ('00' + date.getDate()).slice(-2)
let month = ('00' + (date.getMonth() + 1)).slice(-2)
let year = date.getFullYear()
return year + '/' + month + '/' + day
}
},
maxDate: new Date()
})
this.get().cuentas()
}
}
},
setup: function() {
this.build().modal()
$(this.id).parent().find('.ui.button').click(() => {
this.add().show()
})
this.get().transacciones()
}
}

View File

@ -0,0 +1,465 @@
class Transaccion {
constructor({id, debito_id, credito_id, fecha, glosa, detalle, valor, debito, credito, fechaFormateada, valorFormateado, format, moneda_id}) {
this.id = id
this.debito_id = debito_id
this.credito_id = credito_id
this.fecha = {
fecha,
formateada: fechaFormateada
}
this.glosa = glosa
this.detalle = detalle
this.valor = {
valor,
formateado: valorFormateado
}
this.format_array = format
this.debito = debito
this.credito = credito
this.modal = null
}
setCuenta(cuenta) {
this.cuenta = cuenta
}
setModal(modal) {
this.modal = modal
}
isDebito() {
return this.debito.id === this.cuenta.id;
}
isIncrement() {
const debits = ['Activo', 'Perdida']
if (debits.indexOf(this.cuenta.tipo.descripcion)) {
return this.isDebito()
}
return !this.isDebito()
}
draw({saldo, format, format_array, format_call}) {
const fuente = (this.isDebito()) ? this.credito : this.debito
return $('<tr></tr>').append(
$('<td></td>').html(this.fecha.formateada)
).append(
$('<td></td>').append(
$('<a></a>').attr('href', _urls.base + 'cuenta/' + fuente.id).html(fuente.nombre + ' (' + fuente.categoria.nombre + ')')
)
).append(
$('<td></td>').html(this.glosa + '<br />' + this.detalle)
).append(
$('<td></td>').attr('class', 'right aligned').html((this.isIncrement()) ? '' : format_call({value: this.valor.valor, format, format_array}))
).append(
$('<td></td>').attr('class', 'right aligned').html((this.isIncrement()) ? format_call({value: -this.valor.valor, format, format_array}) : '')
).append(
$('<td></td>').attr('class', 'right aligned').html(format_call({value: saldo, format_array, format}))
).append(
$('<td></td>').attr('class', 'right aligned').append(
$('<button></button>').attr('class', 'ui tiny circular icon button').append(
$('<i></i>').attr('class', 'edit icon')
).click((e) => {
e.preventDefault()
this.edit()
return false
})
).append(
$('<button></button>').attr('class', 'ui tiny circular red icon button').append(
$('<i></i>').attr('class', 'remove icon')
).click((e) => {
e.preventDefault()
this.remove()
return false
})
)
)
}
edit() {
const form = this.modal.find('form')
form.trigger('reset')
form.find("[name='id']").val(this.id)
form.find(".ui.calendar").calendar('set date', new Date(this.fecha.fecha))
form.find("[name='cuenta']").dropdown('set selected', (this.isDebito()) ? this.credito_id : this.credito_id)
form.find("[name='glosa']").val(this.glosa)
form.find("[name='detalle']").val(this.detalle)
form.find("[name='valor']").val(((this.isDebito()) ? -1 : 1) * this.valor.valor)
modalToEdit(this.modal)
this.modal.modal('show')
}
remove() {
sendDelete(_urls.api + '/transaccion/' + this.id + '/delete').then(() => {
transacciones.get().transacciones()
})
}
}
const transacciones = {
id: '#transacciones',
mes: '#mes',
buttons: {
prev: '#prev_button',
left: '#left_button',
today: '#today_button',
right: '#right_button',
next: '#next_button'
},
cuenta_id: 0,
cuenta: null,
transacciones: [],
cuentas: [],
monedas: [],
date: new Date(),
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() {
return {
transacciones: () => {
this.draw().loading()
let promises = []
sendGet(_urls.api + '/cuenta/' + this.cuenta_id + '/transacciones/amount').then((data) => {
if (data.cuenta === null) {
return
}
this.cuenta = data.cuenta
this.intl_format = Intl.NumberFormat('es-CL')
sendGet(_urls.api + '/cuenta/' + this.cuenta_id + '/categoria').then((resp) => {
this.cuenta.categoria = resp.categoria
}).then(() => {
$('#cuenta').html(this.cuenta.nombre + ' (' + this.cuenta.categoria.nombre + ')').append(
$('<i></i>').attr('class', 'square full icon').css('color', '#' + this.cuenta.tipo.color)
)
promises = this.get().transaccionesMes(this.date)
if (promises.length > 0) {
Promise.all(promises).then((data_arr) => {
this.transacciones = []
data_arr.forEach(data => {
if (data.transacciones === null || data.transacciones.length === 0) {
return
}
$.each(data.transacciones, (i, el) => {
const tr = new Transaccion(el)
tr.setCuenta(this.cuenta)
tr.setModal(this.modal)
this.transacciones.push(tr)
})
})
this.transacciones.sort((a, b) => {
return (new Date(b.fecha)) - (new Date(a.fecha))
})
}).then(() => {
this.get().transaccionesAcumulacion(this.date).then((response) => {
this.acumulation = response.acumulation
this.saldo = response.acumulation
}).then(() => {
this.draw().table()
this.draw().acumulation()
})
})
} else {
this.draw().table()
}
})
})
},
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: () => {
return sendGet(_urls.api + '/cuentas').then((data) => {
if (data.cuentas === null || data.cuentas.length === 0) {
return
}
this.cuentas = data.cuentas
}).then(() => {
const select = this.modal.find("[name='cuenta']")
$.each(this.cuentas, (i, el) => {
this.get().categoria(i).then(() => {
select.append(
$('<option></option>').attr('value', el.id).html(el.nombre + ' (' + el.categoria.nombre + ')')
)
select.dropdown()
})
})
this.get().monedas().then(() => {
const select = this.modal.find("[name='moneda']")
$.each(this.monedas, (i, el) => {
const option = $('<option></option>').attr('value', el.id).html(el.denominacion)
if (el.id === '1') {
option.attr('selected', 'selected')
}
select.append(option)
})
})
})
},
categoria: (idx) => {
return sendGet(_urls.api + '/cuenta/' + this.cuentas[idx].id + '/categoria').then((data) => {
this.cuentas[idx].categoria = data.categoria
})
},
monedas: () => {
return sendGet(_urls.api + '/monedas').then((response) => {
this.monedas = response.monedas
})
}
}
},
draw: function() {
return {
loading: () => {
const parent = $(this.id)
parent.html('')
parent.append(
$('<tr></tr>').append(
$('<td></td>').attr('colspan', 7).append(
$('<div></div>').attr('class', 'ui active dimmer').append(
$('<div></div>').attr('class', 'ui indeterminate elastic text loader').html('Buscando los datos')
)
)
)
)
},
table: () => {
const parent = $(this.id)
parent.html('')
$.each(this.transacciones, (i, el) => {
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('')
)
)
}
}
},
add: function() {
return {
show: () => {
this.modal.find('form').trigger('reset')
modalToAdd(this.modal)
this.modal.modal('show')
},
exec: () => {
const fecha = $("[name='fecha']").val()
const valor = $("[name='valor']").val()
const cuenta = $("[name='cuenta']").val()
const data = JSON.stringify({
debito_id: (valor < 0) ? this.cuenta_id : cuenta,
credito_id: (valor < 0) ? cuenta : this.cuenta_id,
fecha: fecha,
glosa: $("[name='glosa']").val(),
detalle: $("[name='detalle']").val(),
valor: (valor < 0) ? -valor : valor,
moneda_id: $("[name='moneda']").val()
})
return sendPost(_urls.api + '/transacciones/add', data).then(() => {
this.modal.modal('hide')
this.get().transacciones()
})
}
}
},
edit: function() {
const id = $("[name='id']").val()
const fecha = $("[name='fecha']").val()
const cuenta = $("[name='cuenta']").val()
const glosa = $("[name='glosa']").val()
const detalle = $("[name='detalle']").val()
const valor = $("[name='valor']").val()
const moneda_id = $("[name='moneda']").val()
const data = JSON.stringify({
debito_id: (valor < 0) ? this.cuenta_id : cuenta,
credito_id: (valor < 0) ? cuenta : this.cuenta_id,
fecha,
glosa,
detalle,
valor: (valor < 0) ? -valor : valor,
moneda_id
})
return sendPut(_urls.api + '/transaccion/' + id + '/edit', data).then(() => {
this.modal.modal('hide')
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)
},
today: function() {
this.date = new Date()
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.toDateString() === f.toDateString()) {
$(this.buttons.today).addClass('disabled')
} else {
$(this.buttons.today).removeClass('disabled')
}
if (this.date.getFullYear() === f.getFullYear()) {
$(this.buttons.next).addClass('disabled')
} else {
$(this.buttons.next).removeClass('disabled')
}
},
refresh: function () {
this.get().transacciones()
this.checkButtons()
},
build: function() {
return {
modal: () => {
this.modal = $('.ui.modal')
this.modal.modal()
this.modal.find('.close.icon').click(() => {
this.modal.modal('hide')
})
this.modal.find('form').submit((e) => {
e.preventDefault()
const add = $(e.currentTarget).find('.plus.icon')
if (add.length > 0) {
this.add().exec()
} else {
this.edit()
}
return false
})
this.modal.find('.ui.calendar').calendar({
type: 'date',
formatter: {
date: function(date, settings) {
if (!date) return ''
let day = ('00' + date.getDate()).slice(-2)
let month = ('00' + (date.getMonth() + 1)).slice(-2)
let year = date.getFullYear()
return year + '/' + month + '/' + day
}
},
maxDate: new Date()
})
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.today).click((e) => {
this.today()
})
$(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() {
this.build().modal()
this.build().mes()
this.build().buttons()
$(this.id).parent().find('#refresh').click(() => {
this.refresh()
})
$(this.id).parent().find('#add').click(() => {
this.add().show()
})
this.get().transacciones()
}
}

View File

@ -55,6 +55,7 @@ class TipoCuenta {
const tipos_cuentas = { const tipos_cuentas = {
id: '#tipos_cuentas', id: '#tipos_cuentas',
tipos: [], tipos: [],
modal: null,
getTipos: function() { getTipos: function() {
this.tipos = [] this.tipos = []
return sendGet(_urls.api + '/tipos/cuentas').then((data) => { return sendGet(_urls.api + '/tipos/cuentas').then((data) => {

View File

@ -39,22 +39,32 @@ class Cuenta {
} }
tr.append(td) tr.append(td)
}) })
$("[data-id='" + this.categoria_id + "'][data-class='categoria']").after(tr) const prev = this.prev()
prev.after(tr)
}
prev() {
let prev = $("[data-id='" + this.categoria_id + "'][data-class='categoria']")
let n = 0
while (prev.next().attr('data-class') === 'cuenta') {
prev = prev.next()
n ++;
if (n >= 100) {
return prev
}
}
return prev
} }
remove() { remove() {
$("[data-id='" + this.id + "'][data-class='cuenta']").remove() $("[data-id='" + this.id + "'][data-class='cuenta']").remove()
} }
} }
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 = []
} }
@ -62,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')
@ -85,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)
@ -143,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')
@ -175,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
} }
@ -210,15 +217,13 @@ class TipoCategoria {
} }
} }
const cuentas = { const cuentas = {
id: 'cuentas', id: '#cuentas',
balance: 0, balance: 0,
tipos: [], tipos: [],
tipos_categorias: [], tipos_categorias: [],
get: function() { build: function() {
return { return {
parent: () => { parent: (segment) => {
let parent = $('#' + this.id)
if (parent.length === 0) {
const tr = $('<tr></tr>').append( const tr = $('<tr></tr>').append(
$('<th></th>').attr('colspan', 3).html('Cuenta') $('<th></th>').attr('colspan', 3).html('Cuenta')
) )
@ -230,9 +235,43 @@ const cuentas = {
const table = $('<table></table>').attr('class', 'ui striped table').append( const table = $('<table></table>').attr('class', 'ui striped table').append(
$('<thead></thead>').append(tr) $('<thead></thead>').append(tr)
) )
parent = $('<tbody></tbody>').attr('id', this.id) const parent = $('<tbody></tbody>')
table.append(parent) table.append(parent)
$('h1.header').after(table) segment.append(table)
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')
)
)
)
}
}
},
get: function() {
return {
parent: () => {
const segment = $(this.id)
let parent = segment.find('tbody')
if (parent.length === 0) {
parent = this.build().parent(segment)
} }
return parent return parent
}, },
@ -250,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)
}) })
@ -263,6 +302,8 @@ const cuentas = {
this.balance = data this.balance = data
}).then(() => { }).then(() => {
this.draw().balance() this.draw().balance()
}).then(() => {
this.draw().resultado()
}) })
} }
} }
@ -298,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

@ -0,0 +1,221 @@
class Archivo {
constructor({folder, filename}) {
this.folder = folder
this.filename = filename
this.modal = null
}
setModal(modal) {
this.modal = modal
return this
}
draw() {
return $('<tr></tr>').append(
$('<td></td>').append(
$('<a></a>').attr('class', 'item').attr('href', _urls.base + ['upload', this.folder, this.filename].join('/')).html(this.filename)
)
).append(
$('<td></td>').attr('class', 'right aligned').append(
$('<button></button>').attr('class', 'ui mini circular icon button').append(
$('<i></i>').attr('class', 'edit icon')
).click((e) => {
e.preventDefault()
const t = e.currentTarget
this.edit()
return false
})
).append(
$('<button></button>').attr('class', 'ui mini red circular icon button').append(
$('<i></i>').attr('class', 'remove icon')
).click((e) => {
e.preventDefault()
const t = e.currentTarget
this.remove()
return false
})
)
)
}
edit() {
this.modal.find('form').trigger('reset')
this.modal.find('form').find("[name='folder']").val(this.folder)
this.modal.find('form').find("[name='old_filename']").val(this.filename)
this.modal.find('form').find("[name='filename']").val(this.filename)
this.modal.modal('show')
}
remove() {
return sendDelete([_urls.api, 'upload', this.folder, this.filename].join('/')).then((data) => {
if (data.deleted) {
archivos.get()
}
})
}
}
const archivos = {
id: '#archivos',
archivos: [],
modals: {
add: null,
edit: null
},
build: function() {
return {
parent: (segment) => {
const table = $('<table></table>').attr('class', 'ui striped table').append(
$('<thead></thead>').append(
$('<tr></tr>').append(
$('<th></th>').html('Archivo')
).append(
$('<th></th>').attr('class', 'right aligned').append(
$('<button></button>').attr('class', 'ui tiny green circular icon button').append(
$('<i></i>').attr('class', 'plus icon')
).click((e) => {
e.preventDefault()
this.add()
return false
})
)
)
)
)
const parent = $('<tbody></tbody>')
table.append(parent)
segment.append(table)
return parent
}
}
},
get: function() {
return {
parent: () => {
const segment = $(this.id)
let parent = segment.find('tbody')
if (parent.length === 0) {
parent = this.build().parent(segment)
}
return parent
},
archivos: () => {
return sendGet(_urls.api + '/uploads').then((data) => {
if (data.files === null || data.files.length === 0) {
return
}
this.archivos = []
$.each(data.files, (i, el) => {
const arch = new Archivo(el)
arch.setModal(this.modals.edit)
this.archivos.push(arch)
})
}).then(() => {
this.draw()
})
},
cuentas: () => {
return sendGet(_urls.api + '/cuentas')
}
}
},
draw: function() {
const tbody = this.get().parent()
tbody.empty()
$.each(this.archivos, (i, el) => {
tbody.append(el.draw())
})
},
add: function() {
this.modals.add.find('form').trigger('reset')
this.modals.add.find("[name='cuenta']").dropdown('clear')
this.modals.add.modal('show')
},
doAdd: function() {
const data = new FormData(this.modals.add.find('form')[0])
return sendPost(_urls.api + '/uploads/add', data, true).then((resp) => {
this.modals.add.modal('hide')
this.get().archivos()
})
},
doEdit: function() {
const folder = this.modals.edit.find("[name='folder']").val()
const filename = this.modals.edit.find("[name='old_filename']").val()
const data = JSON.stringify({
cuenta: this.modals.edit.find("[name='cuenta']").val(),
fecha: this.modals.edit.find("[name='fecha']").val()
})
sendPut([_urls.api, 'upload', folder, filename].join('/'), data).then((resp) => {
this.modals.edit.modal('hide')
if (resp.edited) {
this.get().archivos()
}
})
},
updateCalendar: function(modal) {
const today = new Date()
const start = new Date(today.getFullYear(), today.getMonth() - 1)
modal.find('.ui.calendar').calendar({
type: 'month',
initialDate: start,
maxDate: start,
months: ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'],
monthsShort: ['Ene', 'Feb', 'Mar', 'Abr', 'May', 'Jun', 'Jul', 'Ago', 'Sep', 'Oct', 'Nov', 'Dic'],
formatter: {
date: function(date, settings) {
if (!date) return ''
const year = date.getFullYear()
const month = (date.getMonth() + 1).toString().padStart(2, '0')
return [year, month].join('-')
}
}
})
},
updateCuentas: function(modal, data) {
if (data.cuentas === null || data.cuentas.length === 0) {
return
}
const select = modal.find("select[name='cuenta']")
let values = []
$.each(data.cuentas, (i, el) => {
const nombre = [el.nombre, el.categoria.nombre].join(' - ')
values.push({
name: nombre,
value: el.id,
text: nombre
})
})
select.dropdown({values})
},
setupModal: function() {
this.modals.add = $('#add_modal')
this.modals.edit = $('#edit_modal')
$.each(this.modals, (i, el) => {
el.modal().find('.close.icon').click(() => {
el.modal('hide')
})
this.updateCalendar(el)
})
this.modals.add.find('form').submit((e) => {
e.preventDefault()
this.doAdd()
return false
})
this.modals.add.find('#archivo_btn').css('cursor', 'pointer').click(() => {
this.modals.add.find("[name='archivo']").trigger('click')
})
this.modals.add.find("[name='archivo']").change((e) => {
const arch = $(e.currentTarget)
const filename = arch[0].files[0].name
this.modals.add.find('#archivo_btn').find('input').val(filename)
})
this.modals.edit.find('form').submit((e) => {
e.preventDefault()
this.doEdit()
return false
})
this.get().cuentas().then((data) => {
this.updateCuentas(this.modals.add, data)
this.updateCuentas(this.modals.edit, data)
})
},
setup: function() {
this.setupModal()
this.get().archivos()
}
}

View File

@ -1,7 +1,12 @@
<?php <?php
require_once implode(DIRECTORY_SEPARATOR, [ try {
require_once implode(DIRECTORY_SEPARATOR, [
dirname(__DIR__), dirname(__DIR__),
'setup', 'setup',
'app.php' 'app.php'
]); ]);
$app->run(); $app->run();
} catch (Error | Exception $e) {
error_log($e);
throw $e;
}

View File

@ -0,0 +1,4 @@
<?php
use Contabilidad\Common\Controller\Importar;
$app->get('/importar[/]', Importar::class);

View File

@ -0,0 +1,6 @@
<?php
use Contabilidad\Common\Controller\Queues;
$app->group('/queues', function($app) {
$app->get('[/]', Queues::class);
});

View File

@ -0,0 +1,10 @@
<?php
use Contabilidad\Common\Controller\Uploads;
$app->group('/uploads', function($app) {
$app->get('/add', [Uploads::class, 'upload']);
$app->get('[/]', Uploads::class);
});
$app->group('/upload/{folder}/{filename}', function($app) {
$app->get('[/]', [Uploads::class, 'get']);
});

View File

@ -8,7 +8,7 @@
Categorías Categorías
@endif @endif
</h1> </h1>
<div class="ui segment"> <div class="ui basic fitted segment">
@yield('categorias_content') @yield('categorias_content')
</div> </div>
@endsection @endsection

View File

@ -28,7 +28,7 @@
@endsection @endsection
@push('scripts') @push('scripts')
<script type="text/javascript" src="{{$urls->scripts}}/categorias.list.js"></script> <script type="text/javascript" src="{{$urls->scripts}}/categorias/list.js"></script>
<script type="text/javascript"> <script type="text/javascript">
$(document).ready(() => { $(document).ready(() => {
categorias.setup() categorias.setup()

Some files were not shown because too many files have changed in this diff Show More