125 Commits

Author SHA1 Message Date
74b15add36 Cartolas Security 2024-09-03 00:10:46 -04:00
d8d2cba308 Excel functions for Cartola Banco Service 2024-09-03 00:07:52 -04:00
5983a4b20f Parse rut and 0 centro_costo 2024-09-03 00:07:06 -04:00
6e4dd8180e FIX: Cartola::import string date 2024-08-27 18:17:59 -04:00
7447579ef0 Try Token 2024-08-27 14:51:54 -04:00
ccd5b296f3 Descriptions 2024-08-27 14:51:06 -04:00
095c33e8aa Command ContainerLoader 2024-08-27 14:50:45 -04:00
4604a9af48 Update Cartolas 2024-08-27 14:50:11 -04:00
55d879274c Try Tokens rout 2024-08-27 14:49:27 -04:00
85b529c22d Try Token 2024-08-27 14:49:15 -04:00
3ba45c48f5 Update Cartolas 2024-08-27 14:48:47 -04:00
552a4c7aca Update Cartolas 2024-08-27 14:48:21 -04:00
2070987403 Fetch by Cuenta 2024-08-27 14:47:21 -04:00
d9dab9c88d Configuracion de API 2024-08-27 14:46:57 -04:00
f9f133d3a9 Se extra getKey y configuraciones the paths 2024-08-27 14:46:03 -04:00
f85af642d2 API Service para manejo de token 2024-08-27 14:45:21 -04:00
f3d9b58ffb Agregar Cartolas al importar, y chequeo de Cartolas faltantes 2024-08-27 14:44:40 -04:00
8f682f7f13 Se agrega chequeo para cartola diaria 2024-08-27 14:43:17 -04:00
26e626074a Add sub Bancos to BCI and Santander 2024-08-26 15:26:10 -04:00
478b407646 Sub Banco for the Cartola Services 2024-08-26 15:25:45 -04:00
7d210601da Clean up 2024-08-26 15:25:13 -04:00
9563876b36 Process errors 2024-08-26 15:25:02 -04:00
d9e27d5af7 Move Cartola::import into the Service and added errors 2024-08-26 15:24:42 -04:00
936830ae64 Separate Banco::process pipe to be able to intervene in the middle 2024-08-26 15:23:22 -04:00
e93149456a FIX: API not added by git 2024-07-26 23:57:04 -04:00
93a15ef1e4 Add UF Update 2024-07-26 23:33:40 -04:00
6950413eec FIX: logger in API Middleware 2024-07-26 23:31:08 -04:00
9162ed240e FIX: testing 2024-07-26 23:20:17 -04:00
84861b5e57 Cli user 2024-07-26 23:15:48 -04:00
43bb7a83c8 FIX: tesoreria no cargaba datos 2024-07-25 12:52:48 -04:00
081187e3d3 Mensaje de error 2024-07-25 12:51:25 -04:00
b2f1bd5ba0 FIX: Importar cartolas correctamente. 2024-07-18 21:25:10 -04:00
a471a1083e Merge branch 'temp' into develop 2024-07-17 22:34:20 -04:00
8e7f43e487 Subir cartolas procesadas 2024-07-17 22:33:33 -04:00
390e79ad6d 2024-06-18 2024-07-16 16:17:24 -04:00
6169089475 FIX: Cambio fecha factura 2024-07-10 15:40:59 -04:00
a7ce78a3fb FIX: IPC asociado a fecha de UF 2024-07-10 15:32:05 -04:00
6f023928e8 FIX: Actualizacion de IPC al cambiar fecha facturas 2024-07-09 19:52:39 -04:00
a4b7183de1 FIX: UF para Pago actualizado 2024-07-09 11:58:49 -04:00
a428aeebe1 Agregar, editar y borrar comentarios 2024-07-04 18:09:25 -04:00
fc543729d2 FIX: Propietario no tomaba en consideracion comuna para direccion 2024-07-04 16:28:01 -04:00
8cec046d66 FIX 2024-07-04 16:23:39 -04:00
1f730fb7f3 FIX: comentario en redirect y cleanup 2024-07-04 16:19:54 -04:00
964d8d4237 Poder editar propietario de ventas, cambiando el propietario en si 2024-07-04 15:42:10 -04:00
0d5c9efd68 FIX: Mostrar unidad principal en busqueda 2024-07-03 17:20:26 -04:00
d68eba5697 Limpieza de input de valor y filtro de datos a nivel Repo 2024-07-03 15:13:13 -04:00
d5b9be0196 FIX: Division por 0 2024-07-03 12:19:44 -04:00
d0d99eaa81 FIX: IPC terreno hasta 2 meses antes de factura 2024-06-27 12:33:02 -04:00
15e9a54c7e FIX: venta sin departamentos 2024-06-11 18:16:34 -04:00
b7e9a17586 Se cambia el total de acuerdo a informacion de Contador 2024-06-11 18:07:12 -04:00
61533b5a4c Cambio en diseño de inicio 2024-06-11 13:21:50 -04:00
5864e7ee94 Mapper default null 2024-06-11 12:07:57 -04:00
3fc7aa88dc FIX: IPC en factura 2024-06-10 21:41:11 -04:00
1caa62500a FIX: unidadService -> propiedadUnidadService 2024-06-10 15:53:05 -04:00
27e88761e0 Mysql log retain 2024-06-07 17:18:20 -04:00
b27a6fab33 Propiedad con unidad_principal 2024-06-07 17:16:58 -04:00
fa978728ce Optimizacion de monedas 2024-05-16 21:23:24 -04:00
256a3d2459 Fechas 2024-05-14 19:43:14 -04:00
f8f5796a66 FIX: columnas al exportar 2024-05-14 13:09:59 -04:00
0e0b783a80 Sigla sociedad 2024-05-13 16:58:33 -04:00
a5125aff74 Movimientos filtrados y editables 2024-05-13 16:54:59 -04:00
71bbe7f2d7 Cuotas compactas 2024-05-10 10:48:19 -04:00
e2c68a14d1 FIX: removed self in asociados 2024-05-09 21:04:51 -04:00
5e51562aec FIX: proporcion pie pagado 2024-05-09 21:00:49 -04:00
7719a5c614 Una fecha para facturas por venta 2024-04-30 20:27:05 -04:00
05b9a3ad8a FIX: valor vacio 2024-04-30 11:35:04 -04:00
665bed8553 Propiedad principal 2024-04-30 11:28:18 -04:00
9c024d1ef7 Matriz facturacion 2024-04-29 18:35:44 -04:00
94d618b2a1 Factura con fecha de UF 2024-04-29 10:45:19 -04:00
4786ee2552 Factura multiple con cambio de fecha 2024-04-28 23:01:55 -04:00
39d8d4943e FIX: No estaban todas las disponibles 2024-04-25 21:00:14 -04:00
e63a3d0077 Disable submit button 2024-04-25 19:56:35 -04:00
d580cc1a11 Favicon 2024-04-25 19:07:21 -04:00
86034a6349 FIX: objeto ->array 2024-04-23 15:35:51 -04:00
97ab5acf3e Agregar usuario a log 2024-04-23 14:35:04 -04:00
435180ebaf Ids de ventas 2024-04-23 14:22:32 -04:00
d2511901ec Facturacion 2024-04-19 23:19:35 -04:00
9388dc17fc Facturacion y Terreno 2024-04-18 20:30:26 -04:00
a33dd341cd Facturacion 2024-04-15 20:13:15 -04:00
8e4b5eaaf8 Listado de Movimientos 2024-04-10 21:18:33 -04:00
91679b164f DAP y saldo anterior 2024-04-05 20:23:58 -03:00
3c7e4b9ef4 Orden sociedades 2024-04-05 17:13:21 -03:00
20045c9a64 Formatos 2024-04-05 16:35:18 -03:00
47d43f504d Informes anteriores y siguientes 2024-04-05 14:48:17 -03:00
0502b8e735 agentes -> proveedores 2024-04-05 14:45:54 -03:00
905e8263bc Login redirect 307 (temporary) 2024-04-03 13:26:12 -03:00
8710c8a111 Agentes 2024-04-02 23:02:52 -03:00
5d9ac2bc51 Tests results in cache 2024-04-02 20:19:59 -03:00
062e0543e4 Ignore app/bin 2024-04-02 19:58:58 -03:00
b97916cfc4 Menu 2024-04-02 19:04:57 -03:00
7af9cafdb6 Moderno 2024-04-02 19:04:26 -03:00
84a2fbd119 Log DB 2024-04-02 13:50:08 -03:00
bc9c71870c No remove nocache 2024-03-26 17:09:43 -03:00
912e8624b2 Nocache after ready 2024-03-26 16:57:07 -03:00
86406bf027 Fecha cuota 2024-03-26 16:41:33 -03:00
7b8d44e8c8 Reordenar 2024-03-26 13:59:18 -03:00
c10f2e5912 Cuotas con detalle de UF y de Pies asociados 2024-03-26 13:21:47 -03:00
bef0a22758 Bin folder 2024-03-26 13:17:08 -03:00
092eb95f06 Cache phpunit 2024-03-26 09:38:35 -03:00
5f56022109 Restructura contabilidad 2024-03-26 09:38:20 -03:00
4b3397dd63 Estado cuenta 2024-03-21 23:10:18 -03:00
529f9e32a1 Inmobiliarias por razon y cuadratura 2024-03-21 22:09:15 -03:00
939adf126f FIX: multiples cartolas 2024-03-21 22:08:30 -03:00
63400af1db Cartola diaria adds all cartolas 2024-03-21 21:57:32 -03:00
019974614c FIX: Saldos cartola diaria 2024-03-21 21:18:40 -03:00
735c341729 FIX: Login, change in separator defined 2024-03-20 23:29:09 -03:00
444ff687fc Base API, and more solid key and check 2024-03-20 23:07:49 -03:00
f3a5fa2cdc Tests 2024-03-20 23:06:38 -03:00
2ed265dcf1 Merge branch 'testing' into develop 2024-03-20 21:11:32 -03:00
dbae630fdd Bold footer 2024-03-20 21:04:43 -03:00
d69976d015 FIX: Remove cache from login when redirecting 2024-03-20 20:55:34 -03:00
2ccbc31ae0 FIX: Auth had all paths starting with slash as valid 2024-03-20 20:48:05 -03:00
e50d80560c FIX: Auth missing from some routes 2024-03-20 20:43:53 -03:00
7cc0333876 Informe tesoreria busca ultima cartola ingresada 2024-03-20 20:22:54 -03:00
eb38236926 FIX: cartola BCI 2024-03-20 17:52:34 -03:00
85ef4dd60e Ignore testing cache 2024-03-20 15:00:54 -03:00
f1e29e3b0b Ignore testing results 2024-03-20 13:50:04 -03:00
6617a92f5f Tests 2024-03-20 13:49:58 -03:00
ca958b8a88 Testing dependencies 2024-03-20 13:45:53 -03:00
dc2e2c7f71 Cartola BCI 2024-03-18 17:54:27 -03:00
936fd2d1e1 Cuota vacia 2024-03-18 18:06:32 +00:00
7c6a397e31 CORS 2024-03-18 14:58:54 -03:00
7385225a2e FIX: Venta cargada sin formaPago 2024-03-15 13:09:58 -03:00
9c335fd350 FIX: Venta 2024-03-13 22:43:37 -03:00
57f9169cc7 Encriptar clave 2024-03-13 21:24:52 -03:00
296 changed files with 11944 additions and 2199 deletions

2
.gitignore vendored
View File

@ -10,3 +10,5 @@
**/.idea/ **/.idea/
**/upload?/ **/upload?/
**/informe?/ **/informe?/
**/.phpunit.cache/
**/coverage/

View File

@ -9,6 +9,8 @@ RUN pecl install xdebug-3.1.3 \
&& docker-php-ext-enable xdebug && docker-php-ext-enable xdebug
COPY ./php-errors.ini /usr/local/etc/php/conf.d/docker-php-errors.ini COPY ./php-errors.ini /usr/local/etc/php/conf.d/docker-php-errors.ini
COPY ./php-xdebug.ini /usr/local/etc/php/conf.d/docker-php-xdebug.ini
COPY ./php-memory.ini /usr/local/etc/php/conf.d/docker-php-memory.ini
COPY --from=composer /usr/bin/composer /usr/bin/composer COPY --from=composer /usr/bin/composer /usr/bin/composer

9
api.compose.yml Normal file
View File

@ -0,0 +1,9 @@
services:
httpclient:
profiles:
- testing
container_name: incoviba_client
image: flawiddsouza/restfox
restart: unless-stopped
ports:
- "${HTTPCLIENT_PORT:-4004}:4004"

1
app/.gitignore vendored Normal file
View File

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

13
app/.phpunit-watcher.yml Normal file
View File

@ -0,0 +1,13 @@
watch:
directories:
- src
- tests
- common
fileMask: '*.php'
notifications:
passingTests: false
failingTests: false
hideManual: true
phpunit:
arguments: '--log-events-text /logs/output.txt --stop-on-failure'
timeout: 180

View File

@ -5,6 +5,11 @@ use Psr\Http\Message\UploadedFileInterface;
interface Banco interface Banco
{ {
/**
* Process bank movements for database inserts
* @param UploadedFileInterface $file
* @return array
*/
public function process(UploadedFileInterface $file): array; public function process(UploadedFileInterface $file): array;
} }

View File

@ -6,5 +6,5 @@ use Incoviba\Model;
interface Exporter interface Exporter
{ {
public function export(Model\Inmobiliaria $inmobiliaria, Model\Banco $banco, DateTimeInterface $mes, array $movimientos): string; public function export(Model\Inmobiliaria $inmobiliaria, Model\Contabilidad\Banco $banco, DateTimeInterface $mes, array $movimientos): string;
} }

View File

@ -9,9 +9,55 @@ abstract class Banco extends Service implements Define\Cartola\Banco
{ {
public function process(UploadedFileInterface $file): array public function process(UploadedFileInterface $file): array
{ {
$data = $this->parseFile($file); $filename = $this->processUploadedFile($file);
$data = $this->processFile($filename);
return $this->mapColumns($data);
}
/**
* There are banks that need some post-processing
* @param array $movimientos
* @return array
*/
public function processMovimientosDiarios(array $movimientos): array
{
return $movimientos;
}
/**
* Move the UploadedFile into a temp file from getFilename
* @param UploadedFileInterface $uploadedFile
* @return string
*/
protected function processUploadedFile(UploadedFileInterface $uploadedFile): string
{
$filename = $this->getFilename($uploadedFile);
$uploadedFile->moveTo($filename);
return $filename;
}
/**
* Process the temp file from getFilename and remove it
* @param string $filename
* @return array
*/
protected function processFile(string $filename): array
{
$data = $this->parseFile($filename);
unlink($filename);
return $data;
}
/**
* Map columns from uploaded file data to database columns
* @param array $data
* @return array
*/
protected function mapColumns(array $data): array
{
$temp = []; $temp = [];
$columns = $this->columnMap(); $columns = $this->columnMap();
foreach ($data as $row) { foreach ($data as $row) {
$r = []; $r = [];
foreach ($columns as $old => $new) { foreach ($columns as $old => $new) {
@ -24,11 +70,24 @@ abstract class Banco extends Service implements Define\Cartola\Banco
} }
return $temp; return $temp;
} }
public function processMovimientosDiarios(array $movimientos): array
{
return $movimientos;
}
/**
* Get filename where to move UploadedFile
* @param UploadedFileInterface $uploadedFile
* @return string
*/
abstract protected function getFilename(UploadedFileInterface $uploadedFile): string;
/**
* Mapping of uploaded file data columns to database columns
* @return array
*/
abstract protected function columnMap(): array; abstract protected function columnMap(): array;
abstract protected function parseFile(UploadedFileInterface $uploadedFile): array;
/**
* Translate uploaded file data to database data
* @param string $filename
* @return array
*/
abstract protected function parseFile(string $filename): array;
} }

View File

@ -37,6 +37,9 @@ abstract class Repository implements Define\Repository
$this->connection->execute($query, [$model->id]); $this->connection->execute($query, [$model->id]);
} }
/**
* @throws EmptyResult
*/
public function fetchById(int $id): Define\Model public function fetchById(int $id): Define\Model
{ {
$query = $this->connection->getQueryBuilder() $query = $this->connection->getQueryBuilder()
@ -45,6 +48,10 @@ abstract class Repository implements Define\Repository
->where("{$this->getKey()} = ?"); ->where("{$this->getKey()} = ?");
return $this->fetchOne($query, [$id]); return $this->fetchOne($query, [$id]);
} }
/**
* @throws EmptyResult
*/
public function fetchAll(null|string|array $ordering = null): array public function fetchAll(null|string|array $ordering = null): array
{ {
$query = $this->connection->getQueryBuilder() $query = $this->connection->getQueryBuilder()
@ -161,4 +168,9 @@ abstract class Repository implements Define\Repository
} }
return $results; return $results;
} }
public function filterData(array $data): array
{
return $data;
}
} }

View File

@ -6,7 +6,7 @@ use Throwable;
class EmptyResult extends Exception class EmptyResult extends Exception
{ {
public function __construct(string $query, ?Throwable $previous = null) public function __construct(public string $query, ?Throwable $previous = null)
{ {
$message = "Empty results for {$query}"; $message = "Empty results for {$query}";
$code = 700; $code = 700;

View File

@ -0,0 +1,19 @@
<?php
namespace Incoviba\Common\Implement\Exception;
use Throwable;
use Exception;
class HttpResponse extends Exception
{
public function __construct($reason = "", $message = "", $code = 0, Throwable $previous = null)
{
$this->reason = "HTTP Reason: {$reason}";
parent::__construct($message, $code, $previous);
}
protected string $reason;
public function getReason(): string
{
return $this->reason;
}
}

View File

@ -0,0 +1,60 @@
<?php
namespace Incoviba\Common\Implement\Log;
use PDOStatement;
use Monolog\Handler\AbstractProcessingHandler;
use Monolog\LogRecord;
use Monolog\Level;
use Incoviba\Common\Define\Connection;
class MySQLHandler extends AbstractProcessingHandler
{
private bool $initialized = false;
private PDOStatement $statement;
public function __construct(protected Connection $connection, protected int $retainDays = 90, int|string|Level $level = Level::Debug, bool $bubble = true)
{
parent::__construct($level, $bubble);
}
public function write(LogRecord $record): void
{
if (!$this->initialized) {
$this->initialized();
}
$this->cleanup();
$this->statement->execute([
'channel' => $record->channel,
'level' => $record->level->getName(),
'message' => $record->formatted,
'time' => $record->datetime->format('Y-m-d H:i:s.u'),
'context' => (count($record->context) > 0) ? json_encode($record->context, JSON_UNESCAPED_SLASHES) : '',
'extra' => (count($record->extra) > 0) ? json_encode($record->extra, JSON_UNESCAPED_SLASHES) : ''
]);
}
private function initialized(): void
{
$query = <<<QUERY
CREATE TABLE IF NOT EXISTS monolog (
channel VARCHAR(255),
level VARCHAR(100),
message LONGTEXT,
time DATETIME,
context LONGTEXT,
extra LONGTEXT
)
QUERY;
$this->connection->getPDO()->exec($query);
$query = <<<QUERY
INSERT INTO monolog (channel, level, message, time, context, extra)
VALUES (:channel, :level, :message, :time, :context, :extra)
QUERY;
$this->statement = $this->connection->getPDO()->prepare($query);
$this->initialized = true;
}
private function cleanup(): void
{
$query = "DELETE FROM monolog WHERE time < DATE_SUB(CURDATE(), INTERVAL {$this->retainDays} DAY)";
$this->connection->query($query);
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace Incoviba\Common\Implement\Log;
use Monolog\Formatter\JsonFormatter;
use Monolog\LogRecord;
class PDOFormatter extends JsonFormatter
{
public function __construct(int $batchMode = self::BATCH_MODE_JSON, bool $appendNewline = false, bool $ignoreEmptyContextAndExtra = false, bool $includeStacktraces = true)
{
parent::__construct($batchMode, $appendNewline, $ignoreEmptyContextAndExtra, $includeStacktraces);
}
public function format(LogRecord $record): string
{
$normalized = $this->normalize($record);
return $normalized['message'];
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace Incoviba\Common\Implement\Log;
use Monolog\LogRecord;
use Monolog\Processor\ProcessorInterface;
use Incoviba\Service;
class UserProcessor implements ProcessorInterface
{
public function __construct(protected Service\Login $loginService) {}
public function __invoke(LogRecord $record): LogRecord
{
if ($this->loginService->isIn()) {
$record->extra['user'] = $this->loginService->getUser()->name;
}
return $record;
}
}

View File

@ -1,8 +1,10 @@
<?php <?php
namespace Incoviba\Common\Implement\Repository; namespace Incoviba\Common\Implement\Repository;
use Error;
use Closure; use Closure;
use Incoviba\Common\Define; use Incoviba\Common\Define;
use Incoviba\Common\Implement\Exception\EmptyResult;
class Mapper implements Define\Repository\Mapper class Mapper implements Define\Repository\Mapper
{ {
@ -46,7 +48,11 @@ class Mapper implements Define\Repository\Mapper
} }
public function hasDefault(): bool public function hasDefault(): bool
{ {
return isset($this->default); try {
return isset($this->default) or $this->default === null;
} catch (Error) {
return false;
}
} }
public function parse(Define\Model &$model, string $column, ?array $data): bool public function parse(Define\Model &$model, string $column, ?array $data): bool
@ -63,7 +69,15 @@ class Mapper implements Define\Repository\Mapper
$value = $data[$column]; $value = $data[$column];
if ($this->hasFunction()) { if ($this->hasFunction()) {
if ($value !== null) { if ($value !== null) {
$value = ($this->function)($data); try {
$value = ($this->function)($data);
} catch (EmptyResult $exception) {
if ($this->hasDefault()) {
$value = $this->default;
} else {
throw $exception;
}
}
} elseif ($this->hasDefault()) { } elseif ($this->hasDefault()) {
$value = $this->default; $value = $this->default;
} }

View File

@ -1,8 +1,12 @@
{ {
"name": "incoviba/web", "name": "incoviba/web",
"version": "2.0.0",
"type": "project", "type": "project",
"require": { "require": {
"berrnd/slim-blade-view": "^1.0", "berrnd/slim-blade-view": "^1.0",
"ext-gd": "*",
"ext-openssl": "*",
"ext-pdo": "*",
"guzzlehttp/guzzle": "^7.8", "guzzlehttp/guzzle": "^7.8",
"monolog/monolog": "^3.4", "monolog/monolog": "^3.4",
"nyholm/psr7": "^1.8", "nyholm/psr7": "^1.8",
@ -14,8 +18,10 @@
"slim/slim": "^4.11" "slim/slim": "^4.11"
}, },
"require-dev": { "require-dev": {
"fakerphp/faker": "^1.23",
"kint-php/kint": "^5.1", "kint-php/kint": "^5.1",
"phpunit/phpunit": "^10.2" "phpunit/phpunit": "^10.2",
"spatie/phpunit-watcher": "^1.23"
}, },
"authors": [ "authors": [
{ {
@ -30,6 +36,8 @@
} }
}, },
"config": { "config": {
"sort-packages": true "sort-packages": true,
"process-timeout": 0,
"bin-dir": "bin"
} }
} }

43
app/phpunit.xml Normal file
View File

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd"
bootstrap="vendor/autoload.php"
cacheDirectory="/code/cache/tests"
executionOrder="depends,defects"
requireCoverageMetadata="false"
beStrictAboutCoverageMetadata="false"
beStrictAboutOutputDuringTests="true"
colors="true"
failOnRisky="false"
failOnWarning="false">
<testsuites>
<testsuite name="unit">
<directory>tests/units</directory>
</testsuite>
<testsuite name="acceptance">
<directory>tests/integration</directory>
</testsuite>
<testsuite name="performance">
<directory>tests/performance</directory>
</testsuite>
</testsuites>
<source restrictDeprecations="true" restrictNotices="true" restrictWarnings="true">
<include>
<directory>src</directory>
<directory>common</directory>
</include>
</source>
<coverage includeUncoveredFiles="true" pathCoverage="false" ignoreDeprecatedCodeUnits="true" disableCodeCoverageIgnore="true">
<report>
<html outputDirectory="/code/public/coverage/html" />
<php outputFile="/code/public/coverage/coverage.php" />
</report>
</coverage>
<logging>
<junit outputFile="/code/cache/tests/junit.xml" />
<teamcity outputFile="/code/cache/tests/teamcity.txt" />
<testdoxHtml outputFile="/code/cache/tests/testdox.html" />
<testdoxText outputFile="/code/cache/tests/testdox.txt" />
</logging>
</phpunit>

BIN
app/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 894 B

View File

@ -1,4 +1,6 @@
<?php <?php
use Incoviba\Controller\API\Base;
$app->group('/api', function($app) { $app->group('/api', function($app) {
$folder = implode(DIRECTORY_SEPARATOR, [__DIR__, 'api']); $folder = implode(DIRECTORY_SEPARATOR, [__DIR__, 'api']);
if (file_exists($folder)) { if (file_exists($folder)) {
@ -10,4 +12,5 @@ $app->group('/api', function($app) {
include_once $file->getRealPath(); include_once $file->getRealPath();
} }
} }
$app->get('[/]', Base::class);
})->add($app->getContainer()->get(Incoviba\Middleware\API::class)); })->add($app->getContainer()->get(Incoviba\Middleware\API::class));

View File

@ -2,5 +2,15 @@
use Incoviba\Controller\Inmobiliarias; use Incoviba\Controller\Inmobiliarias;
$app->group('/inmobiliarias', function($app) { $app->group('/inmobiliarias', function($app) {
$folder = implode(DIRECTORY_SEPARATOR, [__DIR__, 'inmobiliarias']);
if (file_exists($folder)) {
$files = new FilesystemIterator($folder);
foreach ($files as $file) {
if ($file->isDir()) {
continue;
}
include_once $file->getRealPath();
}
}
$app->get('[/]', Inmobiliarias::class); $app->get('[/]', Inmobiliarias::class);
})->add($app->getContainer()->get(Incoviba\Middleware\Authentication::class)); })->add($app->getContainer()->get(Incoviba\Middleware\Authentication::class));

View File

@ -0,0 +1,10 @@
<?php
$app->group('/admin', function($app) {
$files = new FilesystemIterator(implode(DIRECTORY_SEPARATOR, [__DIR__, 'admin']));
foreach ($files as $file) {
if ($file->isDir()) {
continue;
}
include_once $file->getRealPath();
}
});

View File

@ -0,0 +1,6 @@
<?php
use Incoviba\Controller\Admin\Users;
$app->group('/users', function($app) {
$app->get('[/]', Users::class);
});

View File

@ -0,0 +1,13 @@
<?php
$app->group('/admin', function($app) {
$folder = implode(DIRECTORY_SEPARATOR, [__DIR__, 'admin']);
if (file_exists($folder)) {
$files = new FilesystemIterator($folder);
foreach ($files as $file) {
if ($file->isDir()) {
continue;
}
include_once $file->getRealPath();
}
}
});

View File

@ -0,0 +1,6 @@
<?php
use Incoviba\Controller\API\Admin\Users;
$app->group('/users', function($app) {
$app->post('/add[/]', Users::class . ':add');
});

View File

@ -3,6 +3,8 @@ use Incoviba\Controller\API\Contabilidad\Cartolas;
$app->group('/cartolas', function($app) { $app->group('/cartolas', function($app) {
$app->post('/procesar[/]', [Cartolas::class, 'procesar']); $app->post('/procesar[/]', [Cartolas::class, 'procesar']);
$app->post('/importar[/]', [Cartolas::class, 'importar']);
$app->get('/update[/]', [Cartolas::class, 'update']);
}); });
$app->group('/cartola', function($app) { $app->group('/cartola', function($app) {
$app->group('/diaria', function($app) { $app->group('/diaria', function($app) {

View File

@ -1,5 +1,6 @@
<?php <?php
use Incoviba\Controller\API\CentrosCostos;
use Incoviba\Controller\API\Contabilidad\CentrosCostos;
$app->group('/centros_costos', function($app) { $app->group('/centros_costos', function($app) {
$app->post('/add[/]', [CentrosCostos::class, 'add']); $app->post('/add[/]', [CentrosCostos::class, 'add']);

View File

@ -1,6 +1,11 @@
<?php <?php
use Incoviba\Controller\API\Contabilidad\Movimientos; use Incoviba\Controller\API\Contabilidad\Movimientos;
$app->group('/movimientos', function($app) {
$app->post('/sociedad/mes', [Movimientos::class, 'sociedad']);
$app->post('/segment', [Movimientos::class, 'segment']);
$app->get('[/]', Movimientos::class);
});
$app->group('/movimiento/{movimiento_id}', function($app) { $app->group('/movimiento/{movimiento_id}', function($app) {
$app->post('/detalles', [Movimientos::class, 'detalles']); $app->post('/detalles', [Movimientos::class, 'detalles']);
}); });

View File

@ -1,11 +1,14 @@
<?php <?php
use Incoviba\Controller\API\Nubox; use Incoviba\Controller\API\Contabilidad\Nubox;
$app->group('/nubox/{inmobiliaria_rut}', function($app) { $app->group('/nubox/{inmobiliaria_rut}', function($app) {
$app->get('/token[/]', [Nubox::class, 'token']); $app->get('/token[/]', [Nubox::class, 'token']);
$app->get('/sistemas[/]', [Nubox::class, 'sistemas']); $app->get('/sistemas[/]', [Nubox::class, 'sistemas']);
$app->get('/cuentas[/]', [Nubox::class, 'cuentas']);
$app->group('/libro', function($app) { $app->group('/libro', function($app) {
$app->post('/mayor[/]', [Nubox::class, 'libroMayor']); $app->post('/mayor[/]', [Nubox::class, 'libroMayor']);
$app->post('/diario[/]', [Nubox::class, 'libroDiario']); $app->post('/diario[/]', [Nubox::class, 'libroDiario']);
}); });
$app->get('/facturas/{dia}[/]', [Nubox::class, 'facturas']);
$app->post('/cuenta', [Nubox::class, 'cuenta']);
}); });

View File

@ -0,0 +1,6 @@
<?php
use Incoviba\Controller\API\Contabilidad\Tesoreria;
$app->group('/tesoreria', function($app) {
$app->post('/import[/]', [Tesoreria::class, 'import']);
});

View File

@ -2,9 +2,20 @@
use Incoviba\Controller\API\Inmobiliarias; use Incoviba\Controller\API\Inmobiliarias;
$app->group('/inmobiliarias', function($app) { $app->group('/inmobiliarias', function($app) {
$folder = implode(DIRECTORY_SEPARATOR, [__DIR__, 'inmobiliarias']);
if (file_exists($folder)) {
$files = new FilesystemIterator($folder);
foreach ($files as $file) {
if ($file->isDir()) {
continue;
}
include_once $file->getRealPath();
}
}
$app->get('[/]', Inmobiliarias::class); $app->get('[/]', Inmobiliarias::class);
}); });
$app->group('/inmobiliaria/{inmobiliaria_rut}', function($app) { $app->group('/inmobiliaria/{inmobiliaria_rut}', function($app) {
$app->post('/proveedores', [Inmobiliarias::class, 'proveedores']);
$app->get('/cuentas[/]', [Inmobiliarias::class, 'cuentas']); $app->get('/cuentas[/]', [Inmobiliarias::class, 'cuentas']);
$app->get('/proyectos[/]', [Inmobiliarias::class, 'proyectos']); $app->get('/proyectos[/]', [Inmobiliarias::class, 'proyectos']);
}); });

View File

@ -0,0 +1,11 @@
<?php
use Incoviba\Controller\API\Inmobiliarias\Agentes;
$app->group('/proveedores', function($app) {
$app->post('/add[/]', [Agentes::class, 'add']);
$app->post('/register[/]', [Agentes::class, 'register']);
$app->get('[/]', Agentes::class);
});
$app->group('/proveedor/{agente_id}', function($app) {
$app->post('/edit[/]', [Agentes::class, 'edit']);
});

View File

@ -0,0 +1,7 @@
<?php
use Incoviba\Controller\API\Inmobiliarias\Sociedades;
$app->group('/sociedades', function($app) {
$app->post('/add[/]', [Sociedades::class, 'add']);
$app->get('[/]', Sociedades::class);
});

View File

@ -0,0 +1,4 @@
<?php
use Incoviba\Controller\API\Login;
$app->post('/login[/]', Login::class);

View File

@ -4,5 +4,10 @@ use Incoviba\Controller\API\Money;
$app->group('/money', function($app) { $app->group('/money', function($app) {
$app->post('/ipc[/]', [Money::class, 'ipc']); $app->post('/ipc[/]', [Money::class, 'ipc']);
$app->post('/uf[/]', [Money::class, 'uf']); $app->post('/uf[/]', [Money::class, 'uf']);
$app->group('/ufs', function($app) {
$app->post('[/]', [Money::class, 'updateUfs']);
$app->get('[/]', [Money::class, 'ufs']);
});
$app->post('/many[/]', [Money::class, 'getMany']);
$app->post('[/]', [Money::class, 'get']); $app->post('[/]', [Money::class, 'get']);
}); });

View File

@ -0,0 +1,17 @@
<?php
use Incoviba\Controller\API\Sociedades;
$app->group('/sociedades', function($app) {
$app->group('/add', function($app) {
$app->post('/one[/]', Sociedades::class . ':add');
$app->post('[/]', Sociedades::class . ':addMany');
});
$app->post('/get[/]', [Sociedades::class, 'getMany']);
$app->post('/edit[/]', [Sociedades::class, 'edit']);
$app->post('/delete[/]', [Sociedades::class, 'delete']);
$app->post('/asisgnar[/]', [Sociedades::class, 'asignar']);
$app->get('[/]', Sociedades::class);
});
$app->group('/sociedad/{sociedad_rut}', function($app) {
$app->get('[/]', Sociedades::class . ':get');
});

View File

@ -0,0 +1,6 @@
<?php
use Incoviba\Controller\API\Tokens;
$app->group('/tokens', function($app) {
$app->get('/try[/]', Tokens::class . ':try');
});

View File

@ -27,7 +27,10 @@ $app->group('/ventas', function($app) {
}); });
$app->group('/venta/{venta_id}', function($app) { $app->group('/venta/{venta_id}', function($app) {
$app->get('/unidades[/]', [Ventas::class, 'unidades']); $app->get('/unidades[/]', [Ventas::class, 'unidades']);
$app->get('/comentarios[/]', [Ventas::class, 'comentarios']); $app->group('/comentarios', function($app) {
$app->post('/add[/]', [Ventas::class, 'addComentario']);
$app->get('[/]', [Ventas::class, 'comentarios']);
});
$app->group('/escritura', function($app) { $app->group('/escritura', function($app) {
$app->post('/add[/]', [Ventas\Escrituras::class, 'add']); $app->post('/add[/]', [Ventas\Escrituras::class, 'add']);
}); });
@ -39,6 +42,9 @@ $app->group('/venta/{venta_id}', function($app) {
$app->get('/eliminar[/]', [Ventas::class, 'insistir']); $app->get('/eliminar[/]', [Ventas::class, 'insistir']);
$app->post('[/]', [Ventas::class, 'desistir']); $app->post('[/]', [Ventas::class, 'desistir']);
}); });
$app->group('/propietario', function($app) {
$app->put('/edit[/]', [Ventas::class, 'propietario']);
});
$app->post('[/]', [Ventas::class, 'edit']); $app->post('[/]', [Ventas::class, 'edit']);
$app->get('[/]', [Ventas::class, 'get']); $app->get('[/]', [Ventas::class, 'get']);
}); });

View File

@ -0,0 +1,8 @@
<?php
use Incoviba\Controller\API\Ventas\Comentarios;
$app->group('/comentario/{comentario_id}', function($app) {
$app->post('/edit[/]', [Comentarios::class, 'edit']);
$app->delete('/remove[/]', [Comentarios::class, 'remove']);
//$app->get('[/]', [Comentarios::class, 'get']);
});

View File

@ -3,4 +3,5 @@ use Incoviba\Controller\API\Ventas\Facturacion;
$app->group('/facturacion', function($app) { $app->group('/facturacion', function($app) {
$app->get('/proyecto/{proyecto_id}[/]', [Facturacion::class, 'proyecto']); $app->get('/proyecto/{proyecto_id}[/]', [Facturacion::class, 'proyecto']);
$app->post('/get[/]', [Facturacion::class, 'ventas']);
}); });

View File

@ -0,0 +1,6 @@
<?php
use Incoviba\Controller\API\Ventas\Facturas;
$app->group('/facturas', function($app) {
$app->post('/add[/]', [Facturas::class, 'add']);
});

View File

@ -3,4 +3,5 @@ use Incoviba\Controller\Contabilidad;
$app->group('/cartolas', function($app) { $app->group('/cartolas', function($app) {
$app->get('/diaria[/]', [Contabilidad::class, 'diaria']); $app->get('/diaria[/]', [Contabilidad::class, 'diaria']);
$app->get('/importar[/]', [Contabilidad::class, 'importar']);
}); });

View File

@ -1,5 +1,6 @@
<?php <?php
use Incoviba\Controller\CentrosCostos;
use Incoviba\Controller\Contabilidad\CentrosCostos;
$app->group('/centros_costos', function($app) { $app->group('/centros_costos', function($app) {
$app->get('/asignar[/]', [CentrosCostos::class, 'asignar']); $app->get('/asignar[/]', [CentrosCostos::class, 'asignar']);

View File

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

View File

@ -1,6 +1,8 @@
<?php <?php
use Incoviba\Controller\Contabilidad; use Incoviba\Controller\Contabilidad;
use Incoviba\Controller\Contabilidad\Tesoreria;
$app->group('/tesoreria', function($app) { $app->group('/tesoreria', function($app) {
$app->get('/import[/]', [Tesoreria::class, 'import']);
$app->get('[/[{fecha}[/]]]', [Contabilidad::class, 'tesoreria']); $app->get('[/[{fecha}[/]]]', [Contabilidad::class, 'tesoreria']);
}); });

View File

@ -0,0 +1,4 @@
<?php
use Incoviba\Controller\Contabilidad\Movimientos;
$app->get('/movimientos', Movimientos::class);

View File

@ -0,0 +1,6 @@
<?php
use Incoviba\Controller\Inmobiliarias\Proveedores;
$app->group('/proveedores', function($app) {
$app->get('[/]', Proveedores::class);
});

View File

@ -0,0 +1,100 @@
@extends('layout.base')
@section('page_content')
<div class="ui container">
<h2 class="ui header">Usuarios</h2>
<table class="ui table">
<thead>
<tr>
<th>Nombre</th>
<th class="right aligned">
<button class="ui mini green icon button" id="create-user-button">
<i class="plus icon"></i>
</button>
</th>
</tr>
</thead>
<tbody>
@foreach($users as $user)
<tr>
<td>{{ $user->name }}</td>
<td class="right aligned">
<button class="ui mini blue icon button">
<i class="edit icon"></i>
</button>
<button class="ui mini red icon button">
<i class="trash icon"></i>
</button>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
<div class="ui modal" id="create-user-modal">
<i class="close icon"></i>
<div class="header">
Crear usuario
</div>
<div class="content">
<form class="ui form">
<div class="field">
<label>Nombre</label>
<input type="text" name="name" placeholder="Nombre">
</div>
<div class="field">
<label>Correo</label>
<input type="email" name="email" placeholder="Correo">
</div>
<div class="field">
<label>Contraseña</label>
<input type="password" name="password" placeholder="Contraseña">
</div>
</form>
</div>
<div class="actions">
<div class="ui black deny button">
Cancelar
</div>
<div class="ui positive right labeled icon button">
Crear
<i class="checkmark icon"></i>
</div>
</div>
</div>
@endsection
@include('layout.body.scripts.cryptojs')
@push('page_scripts')
<script>
function encryptPassword(password) {
const passphrase = Math.floor(Math.random() * Date.now()).toString()
const encrypted = CryptoJS.AES.encrypt(password, passphrase)
return [passphrase, encrypted.toString()].join('')
}
$(document).ready(function () {
$('#create-user-modal').modal({
onApprove: function() {
const url = '{{$urls->api}}/admin/users/add'
const method = 'post'
const body = new FormData(document.querySelector('#create-user-modal form'))
body.set('password', encryptPassword(body.get('password')))
fetchAPI(url, {method, body}).then(response => {
if (!response) {
return;
}
response.json().then(result => {
if (result.success) {
location.reload()
}
})
})
}
})
$('#create-user-button').on('click', function () {
$('#create-user-modal').modal('show')
})
});
</script>
@endpush

View File

@ -10,5 +10,23 @@
<i class="hammer icon"></i> <i class="hammer icon"></i>
Esta parte del sitio está en construcción. Esta parte del sitio está en construcción.
</div> </div>
@if (isset($exception))
<div class="ui warning message">
<i class="exclamation triangle icon"></i>
<div class="content">
<div class="header">Error</div>
<p>{{$message}}</p>
</div>
</div>
@endif
@if (isset($error))
<div class="ui error message">
<i class="exclamation triangle icon"></i>
<div class="content">
<div class="header">Error</div>
<p>{{$message}}</p>
</div>
</div>
@endif
</div> </div>
@endsection @endsection

View File

@ -52,8 +52,7 @@
<th class="right aligned">Cargo</th> <th class="right aligned">Cargo</th>
<th class="right aligned">Abono</th> <th class="right aligned">Abono</th>
<th class="right aligned">Saldo</th> <th class="right aligned">Saldo</th>
<th>Centro de Costo</th> <th class="right aligned"></th>
<th>Detalle</th>
<th>Orden</th> <th>Orden</th>
</tr> </tr>
</thead> </thead>
@ -115,10 +114,85 @@
</div> </div>
</div> </div>
</div> </div>
<div class="ui modal" id="edit_modal" data-cartola="">
<div class="header">
Movimiento
<div class="meta">
<span id="edit_inmobiliaria"></span> |
<span id="edit_cuenta"></span> |
<span id="edit_fecha"></span> |
<span id="edit_glosa"></span> |
<span id="edit_cargo"></span> |
<span id="edit_abono"></span> |
<span id="edit_saldo"></span>
</div>
</div>
<div class="content">
<form class="ui form" id="edit_form">
<input type="hidden" name="movimiento_id" value="" />
<div class="equal width fields">
<div class="field">
<label>Centro de Costo</label>
<div class="ui search selection dropdown">
<input type="hidden" name="centro_costo_id" />
<i class="dropdown icon"></i>
<div class="default text">Centro de Costo</div>
<div class="menu">
@foreach($centrosCostos as $centro)
<div class="item" data-value="{{$centro->id}}">
{{$centro->id}} - {{$centro->descripcion}}
</div>
@endforeach
</div>
</div>
</div>
<div class="field">
<label>Categoría</label>
<div class="ui input">
<input type="text" name="categoria" />
</div>
</div>
<div class="field">
<label>Detalle</label>
<div class="ui input">
<input type="text" name="detalle" />
</div>
</div>
</div>
<div class="equal width fields">
<div class="field">
<label>RUT</label>
<div class="ui right labeled input">
<input type="text" name="rut" />
<div class="ui basic label">-<span id="digito"></span></div>
</div>
</div>
<div class="field">
<label>Nombres</label>
<div class="ui input">
<input type="text" name="nombres" />
</div>
</div>
<div class="field">
<label>Identificador</label>
<div class="ui input">
<input type="text" name="identificador" />
</div>
</div>
</div>
</form>
</div>
<div class="actions">
<button class="ui approve button">
Editar
</button>
</div>
</div>
@endsection @endsection
@include('layout.head.styles.datatables') @include('layout.head.styles.datatables')
@include('layout.body.scripts.datatables') @include('layout.body.scripts.datatables')
@include('layout.body.scripts.rut')
@push('page_scripts') @push('page_scripts')
<script> <script>
@ -126,7 +200,8 @@
idx idx
inmobiliaria = { inmobiliaria = {
rut: 0, rut: 0,
razon: '' razon: '',
sigla: ''
} }
cuenta = { cuenta = {
id: 0, id: 0,
@ -187,46 +262,6 @@
} }
} }
} }
update() {
return {
centro: (idx, centro_id) => {
const id = this.movimientos[idx].id
const url = '{{$urls->api}}/contabilidad/movimiento/' + id + '/detalles'
const body = new FormData()
body.set('centro_id', centro_id)
return fetchAPI(url, {method: 'post', body}).then(response => {
if (!response) {
return
}
response.json().then(json => {
if (!json.status) {
return
}
const dropdown = $(".dropdown[data-idx='" + idx + "']")
dropdown.dropdown('set selected', json.centro.id, true)
})
})
},
detalle: (idx, detalle) => {
const id = this.movimientos[idx].id
const url = '{{$urls->api}}/contabilidad/movimiento/' + id + '/detalles'
const body = new FormData()
body.set('detalle', detalle)
return fetchAPI(url, {method: 'post', body}).then(response => {
if (!response) {
return
}
response.json().then(json => {
if (!json.status) {
return
}
const input = $("input[data-idx='" + idx + "']")
input.val(json.detalle)
})
})
}
}
}
draw() { draw() {
return { return {
form: ($form, inmobiliarias, first = false) => { form: ($form, inmobiliarias, first = false) => {
@ -326,6 +361,10 @@
onChange: (value, text, $choice) => { onChange: (value, text, $choice) => {
this.inmobiliaria.rut = value this.inmobiliaria.rut = value
this.inmobiliaria.razon = text this.inmobiliaria.razon = text
const inmobiliaria = diaria.data.inmobiliarias.find(inmobiliaria => inmobiliaria.rut === parseInt(value))
if (typeof inmobiliaria !== 'undefined') {
this.inmobiliaria.sigla = inmobiliaria.sigla
}
this.get().bancos(value).then(() => { this.get().bancos(value).then(() => {
$cuentas_dropdown.dropdown('change values', this.bancos) $cuentas_dropdown.dropdown('change values', this.bancos)
}) })
@ -380,74 +419,42 @@
cartola: ($tbody, dateFormatter, numberFormatter) => { cartola: ($tbody, dateFormatter, numberFormatter) => {
this.movimientos.forEach((row, idx) => { this.movimientos.forEach((row, idx) => {
$tbody.append( $tbody.append(
$('<tr></tr>').append( [
'<td>' + this.inmobiliaria.razon + '</td>' + "\n" '<tr>',
+ '<td>' + this.cuenta.descripcion + '</td>' + "\n" `<td>${this.inmobiliaria.sigla ?? this.inmobiliaria.razon}</td>`,
+ '<td>' + dateFormatter.format(row.fecha) + '</td>' + "\n" `<td>${this.cuenta.descripcion}</td>`,
+ '<td>' + row.glosa + '</td>' + "\n" `<td>${dateFormatter.format(row.fecha)}</td>`,
+ '<td class="right aligned">' + (row.cargo === 0 ? '' : numberFormatter.format(row.cargo)) + '</td>' + "\n" `<td>${row.glosa}</td>`,
+ '<td class="right aligned">' + (row.abono === 0 ? '' : numberFormatter.format(row.abono)) + '</td>' + "\n" `<td class="right aligned">${row.cargo === 0 ? '' : numberFormatter.format(row.cargo)}</td>`,
+ '<td class="right aligned">' + (row.saldo === 0 ? '' : numberFormatter.format(row.saldo)) + '</td>' + "\n" `<td class="right aligned">${row.abono === 0 ? '' : numberFormatter.format(row.abono)}</td>`,
).append( `<td class="right aligned">${row.saldo === 0 ? '' : numberFormatter.format(row.saldo)}</td>`,
$('<td></td>').append( '<td class="center aligned">',
this.draw().centroCosto(idx) `<button class="ui icon button edit_movimiento" data-movimiento="${idx}">`,
) '<i class="edit icon"></i>',
).append( '</button>',
$('<td></td>').append( '</td>',
this.draw().detalle(idx) `<td>${(idx + 1).toString()}</td>`,
) ].join("\n")
).append(
$('<td></td>').html(idx + 1)
)
) )
}) })
Array.from(document.getElementsByClassName('edit_movimiento')).forEach(button => {
button.addEventListener('click', function(clickEvent) {
const idx = clickEvent.currentTarget.dataset['movimiento']
const movimiento = this.movimientos[idx]
const $modal = $('#edit_modal')
$modal.attr('data-cartola', this.idx)
$modal.find('#edit_inmobiliaria').html(this.inmobiliaria.razon)
$modal.find('#edit_cuenta').html(this.cuenta.descripcion)
$modal.find('#edit_fecha').html(dateFormatter.format(movimiento.fecha))
$modal.find('#edit_glosa').html(movimiento.glosa)
$modal.find('#edit_cargo').html(numberFormatter.format(movimiento.cargo))
$modal.find('#edit_abono').html(numberFormatter.format(movimiento.abono))
$modal.find('#edit_saldo').html(numberFormatter.format(movimiento.saldo))
$modal.find('input[name="movimiento_id"]').val(movimiento.id)
$modal.modal('show')
}.bind(this))
})
}, },
centroCosto: idx => {
const centros = JSON.parse('{!! json_encode($centrosCostos) !!}')
const menu = $('<div></div>').addClass('menu')
centros.forEach(centro => {
menu.append(
'<div class="item" data-value="' + centro.id + '">'
+ centro.id + ' - ' + centro.descripcion
+ '</div>'
)
})
const dropdown = $('<div></div>').addClass('ui search selection dropdown').attr('data-idx', idx).html(
'<input type="hidden" name="centro" />' + "\n" +
'<i class="dropdown icon"></i>' + "\n" +
'<div class="default text">Centro de Costo</div>' + "\n"
).append(menu)
dropdown.dropdown({
onChange: (value, text, $element) => {
const idx = $element.parent().parent().data('idx')
this.update().centro(idx, value)
}
})
if (this.movimientos[idx].centro !== '') {
const cid = centros.findIndex(centro => centro.descripcion === this.movimientos[idx].centro)
dropdown.dropdown('set selected', centros[cid].id, true)
}
return dropdown
},
detalle: idx => {
const detalle = document.createElement('input')
detalle.type = 'text'
detalle.name = 'detalle' + idx
detalle.placeholder = 'Detalle'
detalle.setAttribute('data-idx', idx)
const input = document.createElement('div')
input.className = 'ui input'
input.appendChild(detalle)
if (this.movimientos[idx].detalle !== '') {
detalle.value = this.movimientos[idx].detalle
}
detalle.addEventListener('blur', event => {
const idx = event.currentTarget.dataset['idx']
this.update().detalle(idx, event.currentTarget.value)
})
return $(input)
}
} }
} }
} }
@ -460,8 +467,7 @@
'cargo', 'cargo',
'abono', 'abono',
'saldo', 'saldo',
'centro', 'edit',
'detalle',
'orden' 'orden'
]; ];
@endphp @endphp
@ -471,17 +477,18 @@
inmobiliarias: JSON.parse('{!! json_encode($inmobiliarias) !!}'), inmobiliarias: JSON.parse('{!! json_encode($inmobiliarias) !!}'),
cartolasIdx: [], cartolasIdx: [],
cartolas: [], cartolas: [],
movimientos: [],
}, },
dataTableConfig: { dataTableConfig: {
order: [[{{array_search('orden', $columns)}}, 'asc']], order: [[{{array_search('orden', $columns)}}, 'asc']],
columnDefs: [ columnDefs: [
{ {
targets: [{{implode(',', array_keys(array_filter($columns, function($column) {return in_array($column, ['fecha', 'cargo', 'abono', 'saldo']);})))}}], targets: [{{implode(',', array_keys(array_filter($columns, function($column) {return in_array($column, ['fecha', 'cargo', 'abono', 'saldo', 'edit']);})))}}],
width: '{{round((1/(count($columns) + 3 - 1)) * 100,2)}}%' width: '{{round((1/(count($columns) + 2)) * 100, 2)}}%'
}, },
{ {
targets: [{{implode(',', array_keys(array_filter($columns, function($column) {return in_array($column, ['sociedad', 'cuenta', 'glosa', 'centro', 'detalle']);})))}}], targets: [{{implode(',', array_keys(array_filter($columns, function($column) {return in_array($column, ['sociedad', 'cuenta', 'glosa']);})))}}],
width: '{{round((1/(count($columns) + 3 - 1)) * 2 * 100, 2)}}%' width: '{{round((1/(count($columns) + 2)) * 2 * 100, 2)}}%'
}, },
{ {
targets: [{{array_search('orden', $columns)}}], targets: [{{array_search('orden', $columns)}}],
@ -605,8 +612,12 @@
cargo: row.cargo, cargo: row.cargo,
abono: row.abono, abono: row.abono,
saldo: row.saldo, saldo: row.saldo,
centro: row.detalles?.centro_costo.descripcion ?? '', centro: row.detalles?.centro_costo?.id ?? '',
detalle: row.detalles?.detalle ?? '' detalle: row.detalles?.detalle ?? '',
categoria: row.detalles?.categoria ?? '',
rut: row.detalles?.rut ?? '',
nombres: row.detalles?.nombres ?? '',
identificador: row.detalles?.identificador ?? ''
} }
}) })
const ayer = new Date(this.data.cartolas[cartolaIdx].fecha.getTime()) const ayer = new Date(this.data.cartolas[cartolaIdx].fecha.getTime())
@ -638,6 +649,51 @@
$(this.ids.table.base).DataTable(this.dataTableConfig) $(this.ids.table.base).DataTable(this.dataTableConfig)
this.cartolas().add() this.cartolas().add()
$(this.ids.editModal).modal({
onApprove: $element => {
const idx = $(this.ids.editModal).data('cartola')
const id = $(this.ids.editModal).find('input[name="movimiento_id"]').val()
const body = new FormData($(this.ids.editModal).find('form')[0])
body.set('idx', idx)
if (body.has('rut')) {
body.set('rut', body.get('rut').replace(/\D/g, ''))
body.set('digito', Rut.digitoVerificador(body.get('rut')))
}
const url = '{{$urls->api}}/contabilidad/movimiento/' + id + '/detalles'
return fetchAPI(url, {method: 'post', body}).then(response => {
if (!response) {
return
}
response.json().then(json => {
if (!json.status || json.movimiento === null || json.movimiento.detalles === null) {
return
}
const idx = diaria.data.cartolas.findIndex(cartola => cartola.idx = json.input.idx)
const movimiento = diaria.data.cartolas[idx].movimientos.find(movimiento => movimiento.id === id)
movimiento.centro = json.movimiento.detalles.centro_costo.id
movimiento.categoria = json.movimiento.detalles.categoria
movimiento.detalle = json.movimiento.detalles.detalle
movimiento.rut = json.movimiento.detalles.rut
movimiento.digito = json.movimiento.detalles.digito
movimiento.nombres = json.movimiento.detalles.nombres
movimiento.identificador = json.movimiento.detalles.identificador
})
})
}
})
$(this.ids.editModal).find('.dropdown').dropdown()
$(this.ids.editModal).find('[name="rut"]').on('input', function(event) {
const rut = event.currentTarget.value
const rut_clean = rut.replace(/\D/g, '')
event.currentTarget.value = Rut.format(rut_clean)
document.getElementById('digito').innerHTML = Rut.digitoVerificador(rut_clean)
}.bind(this))
const rut = $(this.ids.editModal).find('[name="rut"]')
const rut_clean = rut.val().replace(/\D/g, '')
rut.val(Rut.format(rut_clean))
document.getElementById('digito').innerHTML = Rut.digitoVerificador(rut_clean)
} }
} }
const manual = { const manual = {
@ -807,7 +863,8 @@
add: '#add_button', add: '#add_button',
process: '#process_button' process: '#process_button'
}, },
loader: '#loader' loader: '#loader',
editModal: '#edit_modal'
}) })
manual.setup({ manual.setup({
modal: '#manual_modal', modal: '#manual_modal',

View File

@ -0,0 +1,345 @@
@extends('layout.base')
@section('page_content')
<div class="ui container">
<h1 class="ui header">
Importar Cartola Diaria
</h1>
<div class="ui grid">
<div class="right aligned sixteen wide column">
<button class="ui green icon button" id="add_button">
Agregar
<i class="plus icon"></i>
</button>
</div>
</div>
<form class="ui form" id="cartola_form">
</form>
<div class="ui two columns grid">
<div class="column">
<button class="ui icon button" id="process_button">
<i class="file excel icon"></i>
Procesar
</button>
</div>
<div class="right aligned column">
<div class="ui inline active loader" id="loader"></div>
</div>
</div>
</div>
<table class="ui celled table" id="movimientos" style="display: none;">
<thead>
<tr>
<th>Sigla</th>
<th>Fecha</th>
<th>Glosa</th>
<th class="right aligned">Cargo</th>
<th class="right aligned">Abono</th>
<th class="right aligned">Saldo</th>
<th class="center aligned">Centro de Costo</th>
<th>Categoría</th>
<th>Detalle</th>
<th>RUT</th>
<th>Nombres</th>
<th class="center aligned">Identificador</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
@endsection
@push('page_scripts')
<script>
class Cartola {
props
ids
constructor({index, ids}) {
this.props = {
index
}
this.ids = ids
}
get sociedad() {
return $(`#${this.ids.sociedad}${this.props.index}`).dropdown('get value')
}
get banco() {
return $(`#${this.ids.banco}${this.props.index}`).dropdown('get value')
}
get mes() {
return $(`#${this.ids.mes}${this.props.index}`).calendar('get date')
}
get file() {
return $(`#archivo${this.props.index}`)[0].files[0]
}
get data() {
return {
sociedad_rut: this.sociedad,
cuenta_id: this.banco,
mes: [this.mes.getFullYear(), this.mes.getMonth() + 1, 1].join('-'),
file: this.file,
index: this.props.index
}
}
draw({sociedades}) {
const output = [
`<div class="fields" data-idx="${this.props.index}">`,
'<div class="five wide field">',
'<label>Sociedad</label>',
`<div class="ui search selection dropdown" id="${this.ids.sociedad}${this.props.index}">`,
`<input type="hidden" name="sociedad_rut${this.props.index}" />`,
'<i class="dropdown icon"></i>',
'<div class="default text">Sociedad</div>',
'<div class="menu">',
...sociedades.map(sociedad => {
return [
`<div class="item" data-value="${sociedad.rut}">${sociedad.razon}</div>`
].join("\n")
}),
'</div>',
'</div>',
'</div>',
'<div class="four wide field">',
'<label>Banco</label>',
`<div class="ui search selection dropdown" id="${this.ids.banco}${this.props.index}">`,
`<input type="hidden" name="banco_id${this.props.index}" />`,
'<i class="dropdown icon"></i>',
'<div class="default text">Banco</div>',
'<div class="menu"></div>',
'</div>',
'</div>',
'<div class="field">',
'<label>Mes</label>',
`<div class="ui calendar" id="${this.ids.mes}${this.props.index}">`,
'<div class="ui input left icon">',
'<i class="calendar icon"></i>',
`<input type="text" name="mes${this.props.index}" placeholder="Mes" />`,
'</div>',
'</div>',
'</div>',
'<div class="field">',
'<label>Archivo</label>',
`<input type="file" class="ui invisible file input" name="archivo${this.props.index}" id="archivo${this.props.index}" />`,
`<label for="archivo${this.props.index}" class="ui icon button" id="archivo_button">`,
'<i class="file icon"></i>',
'</label>',
'</div>',
]
if (this.props.index > 1) {
output.push(...[
'<div class="field">',
'<label>&nbsp;</label>',
`<button class="ui red icon button remove" data-idx="${this.props.index}">`,
'<i class="trash icon"></i>',
'</button>',
'</div>',
])
}
output.push('</div>')
return output.join("\n")
}
activate() {
$(`#${this.ids.sociedad}${this.props.index}`).dropdown({
onChange: (value, text, $choice) => {
cartolas.fetch().bancos(value).then(response => {
if (!response) {
return
}
return response.json().then(data => {
const dropdown = $(`#${this.ids.banco}${this.props.index}`)
dropdown.dropdown('clear')
dropdown.dropdown('change values', data.cuentas.map(cuenta => {
const desc = [cuenta.banco.nombre, cuenta.cuenta].join(' - ')
return {
name: desc,
value: cuenta.id,
text: desc
}
}))
})
})
}
})
$(`#${this.ids.banco}${this.props.index}`).dropdown()
const cdo = structuredClone(calendar_date_options)
cdo.type = 'month'
$(`#${this.ids.mes}${this.props.index}`).calendar(cdo)
if (this.props.index > 1) {
$(`.${this.ids.buttons.remove}[data-idx="${this.props.index}"]`).click(clickEvent => {
const index = $(clickEvent.currentTarget).data('idx')
this.remove(index)
})
}
}
remove(idx) {
$(`.fields[data-idx=${idx}]`).remove()
cartolas.data.cartolas = cartolas.data.cartolas.filter(cartola => cartola.props.index !== idx)
cartolas.draw().form()
}
}
class Movimiento {
props
constructor({sociedad, fecha, glosa, cargo, abono, saldo, categoria, detalle, centro_costo, rut, nombres, identificador}) {
this.props = {
sociedad,
fecha,
glosa,
cargo,
abono,
saldo,
categoria,
detalle,
centro_costo,
rut,
nombres,
identificador
}
}
draw({formatters}) {
const fecha = new Date(this.props.fecha)
return [
'<tr>',
`<td>${this.props.sociedad.sigla}</td>`,
`<td>${formatters.date.format(fecha)}</td>`,
`<td>${this.props.glosa}</td>`,
`<td class="right aligned">${formatters.number.format(this.props.cargo ?? 0)}</td>`,
`<td class="right aligned">${formatters.number.format(this.props.abono ?? 0)}</td>`,
`<td class="right aligned">${formatters.number.format(this.props.saldo)}</td>`,
`<td class="center aligned">${this.props.centro_costo ?? ''}</td>`,
`<td>${this.props.categoria ?? ''}</td>`,
`<td>${this.props.detalle ?? ''}</td>`,
`<td>${this.props.rut ?? ''}</td>`,
`<td>${this.props.nombres ?? ''}</td>`,
`<td class="center aligned">${this.props.identificador ?? ''}</td>`,
'</tr>'
].join("\n")
}
}
const cartolas = {
ids: {},
data: {
sociedades: {!! json_encode($inmobiliarias) !!},
bancos: {!! json_encode($bancos) !!},
cartolas: [],
movimientos: [],
},
formatters: {
number: new Intl.NumberFormat('es-CL', {minimumFractionDigits: 0, maximumFractionDigits: 0}),
date: new Intl.DateTimeFormat('es-CL', {dateStyle: 'short', timeStyle: 'short'})
},
add() {
return {
cartola: () => {
const idx = cartolas.data.cartolas.length + 1
const cartola = new Cartola({index: idx, ids: cartolas.ids.cartolas})
cartolas.data.cartolas.push(cartola)
cartolas.draw().form()
}
}
},
draw() {
return {
form: () => {
const form = $(this.ids.form)
form.empty()
form.append(`<input type="hidden" name="cartolas" value="${this.data.cartolas.length}" />`)
this.data.cartolas.forEach(cartola => {
form.append(cartola.draw({sociedades: this.data.sociedades, bancos: this.data.bancos}))
cartola.activate()
})
},
movimientos: () => {
const table = $(this.ids.movimientos)
const tbody = table.find('tbody')
tbody.empty()
this.data.movimientos.forEach(movimiento => {
tbody.append(movimiento.draw({formatters: this.formatters}))
})
table.show()
}
}
},
fetch() {
return {
bancos: sociedad_rut => {
const url = `{{$urls->api}}/inmobiliaria/${sociedad_rut}/cuentas`
return fetchAPI(url)
}
}
},
import() {
return {
cartolas: () => {
const url = '{{$urls->api}}/contabilidad/cartolas/importar'
const method = 'post'
const body = new FormData()
this.data.cartolas.forEach(cartola => {
Object.entries(cartola.data).forEach(([key, value]) => {
const name = `${key}[${cartola.props.index - 1}]`
body.append(name, value)
})
})
fetchAPI(url, {method, body}).then(response => {
if (!response) {
return
}
return response.json().then(data => {
if (data.errors.length > 0) {
data.errors.forEach(errorData => {
console.error(errorData)
})
}
this.data.movimientos = data.movimientos.map(movimiento => new Movimiento(movimiento))
this.draw().movimientos()
})
}).catch(error => {
const table = $(this.ids.movimientos)
const tbody = table.find('tbody')
tbody.empty()
table.hide()
}).finally(() => {
$(this.ids.loader).hide()
})
}
}
},
setup(ids) {
this.ids = ids
this.add().cartola()
$(this.ids.buttons.add).click(() => {
this.add().cartola()
})
$(this.ids.loader).hide()
$(this.ids.form).submit(submitEvent => {
submitEvent.preventDefault()
$(this.ids.loader).show()
this.import().cartolas()
return false
})
$(this.ids.buttons.process).click(() => {
$(this.ids.form).submit()
})
}
}
$(document).ready(() => {
cartolas.setup({
form: '#cartola_form',
buttons: {
add: '#add_button',
process: '#process_button'
},
movimientos: '#movimientos',
loader: '#loader',
cartolas: {
sociedad: 'sociedad',
banco: 'banco',
mes: 'mes',
buttons: {
remove: 'remove'
}
}
})
})
</script>
@endpush

View File

@ -14,9 +14,10 @@
<i class="dropdown icon"></i> <i class="dropdown icon"></i>
<div class="default text">Inmobiliaria</div> <div class="default text">Inmobiliaria</div>
<div class="menu"> <div class="menu">
@foreach ($inmobiliarias as $inmobiliaria) @foreach ($inmobiliarias as $inmobiliaria)
<div class="item" data-value="{{$inmobiliaria->rut}}">{{$inmobiliaria->razon}}</div> <div class="item"
@endforeach data-value="{{$inmobiliaria->rut}}">{{$inmobiliaria->razon}}</div>
@endforeach
</div> </div>
</div> </div>
</div> </div>
@ -40,7 +41,7 @@
</div> </div>
<div class="field"> <div class="field">
<label for="file">Cartola</label> <label for="file">Cartola</label>
<input type="file" name="file" id="file" class="ui invisible file input" /> <input type="file" name="file" id="file" class="ui invisible file input"/>
<label for="file" class="ui icon button"> <label for="file" class="ui icon button">
<i class="file icon"></i> <i class="file icon"></i>
Cargar Cargar
@ -119,20 +120,20 @@
mes: '', mes: '',
movimientos: [], movimientos: [],
centrosCostos: { centrosCostos: {
ingresos: JSON.parse('{!! json_encode(array_values(array_map(function(\Incoviba\Model\CentroCosto $centroCosto) { ingresos: JSON.parse('{!! json_encode(array_values(array_map(function(\Incoviba\Model\Contabilidad\CentroCosto $centroCosto) {
return [ return [
'id' => $centroCosto->id, 'id' => $centroCosto->id,
'descripcion' => $centroCosto->descripcion 'descripcion' => $centroCosto->descripcion
]; ];
}, array_filter($centrosCostos, function(\Incoviba\Model\CentroCosto $centroCosto) { }, array_filter($centrosCostos, function(\Incoviba\Model\Contabilidad\CentroCosto $centroCosto) {
return $centroCosto->tipoCentro->descripcion === 'Ingreso'; return $centroCosto->tipoCentro->descripcion === 'Ingreso';
})))) !!}'), })))) !!}'),
egresos: JSON.parse('{!! json_encode(array_values(array_map(function(\Incoviba\Model\CentroCosto $centroCosto) { egresos: JSON.parse('{!! json_encode(array_values(array_map(function(\Incoviba\Model\Contabilidad\CentroCosto $centroCosto) {
return [ return [
'id' => $centroCosto->id, 'id' => $centroCosto->id,
'descripcion' => $centroCosto->descripcion 'descripcion' => $centroCosto->descripcion
]; ];
}, array_filter($centrosCostos, function(\Incoviba\Model\CentroCosto $centroCosto) { }, array_filter($centrosCostos, function(\Incoviba\Model\Contabilidad\CentroCosto $centroCosto) {
return $centroCosto->tipoCentro->descripcion === 'Egreso'; return $centroCosto->tipoCentro->descripcion === 'Egreso';
})))) !!}'), })))) !!}'),
} }
@ -170,7 +171,11 @@
return return
} }
$(this.ids.form.banco).dropdown('change values', json.cuentas.map(cuenta => { $(this.ids.form.banco).dropdown('change values', json.cuentas.map(cuenta => {
return {value: cuenta.banco.id, text: cuenta.banco.nombre, name: cuenta.banco.nombre} return {
value: cuenta.banco.id,
text: cuenta.banco.nombre,
name: cuenta.banco.nombre
}
})).dropdown('refresh') })).dropdown('refresh')
}) })
}) })
@ -247,12 +252,12 @@
const movimientos = this.data.movimientos.map((movimiento, idx) => { const movimientos = this.data.movimientos.map((movimiento, idx) => {
const temp = structuredClone(movimiento) const temp = structuredClone(movimiento)
temp.fecha = movimiento.fecha.toISOString() temp.fecha = movimiento.fecha.toISOString()
let centro = $(".centro[data-index='" + (idx+1) + "']").dropdown('get value') let centro = $(".centro[data-index='" + (idx + 1) + "']").dropdown('get value')
if (centro.length === 0) { if (centro.length === 0) {
centro = '' centro = ''
} }
temp.centro_costo = centro temp.centro_costo = centro
let detalle = $("[name='detalle" + (idx+1) + "']").val() let detalle = $("[name='detalle" + (idx + 1) + "']").val()
if (typeof detalle === 'undefined') { if (typeof detalle === 'undefined') {
detalle = '' detalle = ''
} }
@ -294,7 +299,10 @@
month: 'numeric', month: 'numeric',
day: 'numeric' day: 'numeric'
}) })
const numberFormatter = new Intl.NumberFormat('es-CL', {minimumFractionDigits: 0, maximumFractionDigits: 0}) const numberFormatter = new Intl.NumberFormat('es-CL', {
minimumFractionDigits: 0,
maximumFractionDigits: 0
})
this.data.movimientos.forEach((row, idx) => { this.data.movimientos.forEach((row, idx) => {
tbody.append( tbody.append(
$('<tr></tr>').append( $('<tr></tr>').append(
@ -322,7 +330,7 @@
}) })
table.DataTable(this.dataTableConfig) table.DataTable(this.dataTableConfig)
}, },
centrosDropdown: (idx, ingreso=true) => { centrosDropdown: (idx, ingreso = true) => {
const menu = $('<div></div>').addClass('menu') const menu = $('<div></div>').addClass('menu')
let centros = this.data.centrosCostos.ingresos let centros = this.data.centrosCostos.ingresos
if (!ingreso) { if (!ingreso) {

View File

@ -0,0 +1,225 @@
@extends('layout.base')
@section('page_content')
<div class="ui container">
<h1 class="ui header">
Cuadratura de Cartola Mensual
</h1>
<form class="ui form" id="cuadratura_form">
<div class="ui grid">
<div class="fourteen wide column">
<div class="fields">
<div class="five wide field">
<label>Inmobiliaria</label>
<div class="ui selection search dropdown" id="inmobiliaria">
<input type="hidden" name="inmobiliaria"/>
<i class="dropdown icon"></i>
<div class="default text">Inmobiliaria</div>
<div class="menu">
@foreach ($inmobiliarias as $inmobiliaria)
<div class="item" data-value="{{$inmobiliaria->rut}}">{{$inmobiliaria->razon}}</div>
@endforeach
</div>
</div>
</div>
<div class="two wide field">
<label>Banco</label>
<div class="ui selection search dropdown" id="banco">
<input type="hidden" name="banco"/>
<i class="dropdown icon"></i>
<div class="default text">Banco</div>
<div class="menu"></div>
</div>
</div>
<div class="field">
<label>Mes</label>
<div class="ui calendar" id="mes">
<div class="ui icon input">
<i class="calendar icon"></i>
<input type="text" name="mes"/>
</div>
</div>
</div>
<div class="field">
<label for="file">Cartola</label>
<input type="file" name="file" id="file" class="ui invisible file input" />
<label for="file" class="ui icon button">
<i class="file icon"></i>
Cargar
</label>
</div>
</div>
</div>
<div class="two wide middle aligned column">
<button class="ui icon button">
Procesar
<i class="sync icon"></i>
</button>
</div>
</div>
</form>
<div class="ui two columns grid">
<div class="column"></div>
<div class="right aligned column">
<div class="ui inline active loader" id="loader"></div>
</div>
</div>
</div>
<div class="ui fluid container" id="movimientos"></div>
@endsection
@include('layout.head.styles.datatables')
@include('layout.body.scripts.datatables')
@push('page_scripts')
<script>
class LibroMayor {
props
constructor(props) {
this.props = props
}
}
class Cartola {
}
const movimientos = {
ids: {},
data: {
inmobiliaria: {
rut: '',
razon: ''
},
banco: {
id: '',
nombre: ''
}
},
get() {
return {
bancos: inmobiliaria_rut => {
const url = '{{$urls->api}}/inmobiliaria/' + inmobiliaria_rut + '/cuentas'
$(this.ids.loader).show()
return fetchAPI(url).then(response => {
$(this.ids.loader).hide()
if (!response) {
return
}
return response.json().then(json => {
if (json.cuentas.length === 0) {
return
}
$(this.ids.inputs.banco).dropdown('change values', json.cuentas.map(cuenta => {
return {value: cuenta.banco.id, text: cuenta.banco.nombre, name: cuenta.banco.nombre}
})).dropdown('refresh')
})
})
},
firstDate: inmobiliaria_rut => {
const url = '{{$urls->api}}/inmobiliaria/' + inmobiliaria_rut + '/proyectos'
$(this.ids.loader).show()
return fetchAPI(url).then(response => {
$(this.ids.loader).hide()
if (!response) {
return
}
return response.json().then(json => {
if (json.proyectos.length === 0) {
return
}
const min = json.proyectos.reduce((min, proyecto) => {
const date = new Date(proyecto.current_estado.fecha.date)
if (min > date.getTime()) {
return date.getTime()
}
return min
}, (new Date()).getTime())
$(this.ids.inputs.mes).calendar('set minDate', new Date(min))
})
})
}
}
},
parse() {
return {
cartola: submitEvent => {
submitEvent.preventDefault()
const body = new FormData(document.getElementById('asignar_form'))
body.set('mes', $(this.ids.inputs.mes).calendar('get date').toISOString())
const url = '{{$urls->api}}/contabilidad/cartolas/procesar'
$(this.ids.loader).show()
fetchAPI(url, {method: 'post', body}).then(response => {
$(this.ids.loader).hide()
if (!response) {
return
}
return response.json().then(json => {
if (json.movimientos.length === 0) {
return
}
this.data.movimientos = []
json.movimientos.forEach((row, idx) => {
const fecha = new Date(row.fecha)
fecha.setDate(fecha.getDate() + 1)
this.data.movimientos[idx] = {
fecha: fecha,
glosa: row.glosa,
documento: row.documento,
cargo: row.cargo,
abono: row.abono,
}
})
this.draw().cartola()
})
})
return false
}
}
},
setup(ids) {
this.ids = ids
$(this.ids.inputs.inmobiliaria).dropdown({
fireOnInit: true,
onChange: (value, text, $choice) => {
this.data.inmobiliaria.rut = value
this.data.inmobiliaria.razon = text
this.get().bancos(value)
this.get().firstDate(value)
},
})
$(this.ids.inputs.banco).dropdown({
fireOnInit: true,
onChange: (value, text, $choice) => {
this.data.banco.id = value
this.data.banco.nombre = text
}
})
$(this.ids.loader).hide()
calendar_date_options['type'] = 'month'
const lastMonth = new Date()
lastMonth.setDate(0)
calendar_date_options['maxDate'] = lastMonth
calendar_date_options['onChange'] = (date, text, mode) => {
this.data.mes = text
}
$(this.ids.inputs.mes).calendar(calendar_date_options)
$(this.ids.form).submit(this.parse().cartola)
//$(this.ids.button).click(this.export().cartola)
}
}
$(document).ready(() => {
movimientos.setup({
movimientos: '#movimientos',
form: '#cuadratura_form',
loader: '#loader',
inputs: {
inmobiliaria: '#inmobiliaria',
banco: '#banco',
mes: '#mes',
cartola: '#file'
}
})
})
</script>
@endpush

View File

@ -1,14 +1,23 @@
@extends('layout.base') @extends('layout.base')
@push('page_styles')
<style>
tr.bold > th {
font-weight: bold !important;
}
</style>
@endpush
@section('page_content') @section('page_content')
<div class="ui container"> <div class="ui container">
<h1 class="ui centered header">Informe de Tesorería</h1> <h1 class="ui centered header">Informe de Tesorería</h1>
<h4 class="ui centered sub header">{{$fecha->format('d M Y')}}</h4> <h4 class="ui centered sub header">{{$fecha->format('d M Y')}}</h4>
<div class="ui grid"> <div class="ui grid">
<div class="three wide column"> <div class="three wide column">
<a href="/contabilidad/informes/xlsx/tesoreria/{{$fecha->format('Y-m-d')}}" target="_blank" style="color: inherit;"> <a href="/contabilidad/informes/xlsx/tesoreria/{{$fecha->format('Y-m-d')}}" target="_blank"
style="color: inherit;">
<div class="ui inverted green center aligned segment"> <div class="ui inverted green center aligned segment">
Descargar en Excel <i class="file excel icon"></i> Descargar en Excel <i class="file excel icon"></i>
</div> </div>
</a> </a>
</div> </div>
@ -19,7 +28,11 @@
<table class="ui collapsing simple table"> <table class="ui collapsing simple table">
<tr> <tr>
<td>Informe anterior</td> <td>Informe anterior</td>
<td>{{$anterior->format('d/m/Y')}}</td> <td>
<a href="{{$urls->base}}/contabilidad/informes/tesoreria/{{$anterior->format('Y-m-d')}}">
{{$anterior->format('d/m/Y')}}
</a>
</td>
</tr> </tr>
<tr> <tr>
<td>Informe actual</td> <td>Informe actual</td>
@ -40,6 +53,17 @@
</tr> </tr>
</table> </table>
</div> </div>
@if ($siguiente !== null)
<div class="five wide column"></div>
<div class="right aligned two wide column">
<div class="ui segment">
Siguiente
<a href="{{$urls->base}}/contabilidad/informes/tesoreria/{{$siguiente->format('Y-m-d')}}">
{{$siguiente->format('d/m/Y')}} >>
</a>
</div>
</div>
@endif
</div> </div>
<table class="ui striped table"> <table class="ui striped table">
<thead> <thead>
@ -60,27 +84,31 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@foreach ($informes['inmobiliarias'] as $inmobiliaria_rut => $informe) @foreach ($informes['sociedades'] as $sociedad_rut => $informe)
@foreach ($informe->cuentas as $i => $cuenta) @foreach ($informe->cuentas as $i => $cuenta)
<tr> <tr>
@if ($i === 0) @if ($i === 0)
<td rowspan="{{count($informe->cuentas)}}">{{$informe->inmobiliaria->razon}}</td> <td rowspan="{{count($informe->cuentas)}}">{{$informe->sociedad->razon}}</td>
@endif @endif
<td>{{$cuenta->banco}}</td> <td>{{$cuenta->banco}}</td>
<td>{{$cuenta->numero}}</td> <td>{{$cuenta->numero}}</td>
<td class="right aligned">{{$format->pesos($cuenta->anterior)}}</td> <td class="right aligned">{{$format->pesos($cuenta->anterior)}}</td>
<td class="right aligned">{{$format->pesos($cuenta->actual)}}</td> <td class="right aligned">{{$format->pesos($cuenta->actual)}}</td>
<td class="right aligned">{{$format->pesos($cuenta->diferencia())}}</td> <td class="right aligned">{{$format->pesos($cuenta->diferencia())}}</td>
<td class="right aligned">{{$format->pesos($cuenta->ffmm)}}</td> <td class="right aligned">{{$format->pesos($cuenta->ffmm)}}</td>
<td class="right aligned">{{$format->pesos($cuenta->deposito)}}</td> <td class="right aligned">{{$format->pesos($cuenta->deposito)}}</td>
<td class="right aligned">{{$format->pesos($cuenta->saldo())}}</td> <td class="right aligned">{{$format->pesos($cuenta->saldo())}}</td>
@if ($i === 0) @if ($i === 0)
<td class="right aligned" rowspan="{{count($informe->cuentas)}}">{{$format->pesos($informe->total())}}</td> <td class="right aligned"
<td class="right aligned" rowspan="{{count($informe->cuentas)}}">{{$format->pesos($informe->ffmm())}}</td> rowspan="{{count($informe->cuentas)}}">{{$format->pesos($informe->total())}}</td>
<td class="right aligned" rowspan="{{count($informe->cuentas)}}">{{$format->pesos($informe->deposito())}}</td> <td class="right aligned"
<td class="right aligned" rowspan="{{count($informe->cuentas)}}">{{$format->pesos($informe->caja())}}</td> rowspan="{{count($informe->cuentas)}}">{{$format->pesos($informe->ffmm())}}</td>
@endif <td class="right aligned"
</tr> rowspan="{{count($informe->cuentas)}}">{{$format->pesos($informe->deposito())}}</td>
<td class="right aligned"
rowspan="{{count($informe->cuentas)}}">{{$format->pesos($informe->caja())}}</td>
@endif
</tr>
@endforeach @endforeach
@endforeach @endforeach
</tbody> </tbody>
@ -123,7 +151,7 @@
@foreach ($movimientos as $ms) @foreach ($movimientos as $ms)
@foreach ($ms as $movimiento) @foreach ($ms as $movimiento)
<tr> <tr>
<td >{{$movimiento->cuenta->inmobiliaria->razon}}</td> <td>{{$movimiento->cuenta->inmobiliaria->razon}}</td>
<td class="right aligned">{{$format->pesos($movimiento->abono)}}</td> <td class="right aligned">{{$format->pesos($movimiento->abono)}}</td>
<td class="right aligned">{{$format->pesos($movimiento->cargo)}}</td> <td class="right aligned">{{$format->pesos($movimiento->cargo)}}</td>
<td>{{$movimiento->fecha->format('d/m/Y')}}</td> <td>{{$movimiento->fecha->format('d/m/Y')}}</td>
@ -142,7 +170,7 @@
</tr> </tr>
@foreach ($movimientos as $movimiento) @foreach ($movimientos as $movimiento)
<tr> <tr>
<td >{{$movimiento->cuenta->inmobiliaria->razon}}</td> <td>{{$movimiento->cuenta->inmobiliaria->razon}}</td>
<td class="right aligned">{{$format->pesos($movimiento->abono)}}</td> <td class="right aligned">{{$format->pesos($movimiento->abono)}}</td>
<td class="red right aligned">{{$format->pesos($movimiento->cargo)}}</td> <td class="red right aligned">{{$format->pesos($movimiento->cargo)}}</td>
<td>{{$movimiento->fecha->format('d/m/Y')}}</td> <td>{{$movimiento->fecha->format('d/m/Y')}}</td>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,53 @@
@extends('layout.base')
@section('page_content')
<div class="ui container">
<h1 class="ui header">Importar Informe de Tesorería</h1>
<form class="ui form" id="import_form" enctype="multipart/form-data">
<div class="ten wide field">
<label>Informe</label>
<div class="ui file action input">
<input type="file" name="file[]" id="file" accept=".xlsx" multiple placeholder="Informe" />
<label for="file" class="ui icon button">
<i class="file icon"></i>
</label>
</div>
</div>
<button class="ui icon button">
<i class="arrow up icon"></i>
Subir
</button>
</form>
</div>
@endsection
@push('page_scripts')
<script>
$(document).ready(() => {
$('#import_form').submit(submitEvent => {
submitEvent.preventDefault()
let formData = new FormData()
formData.append('file', $('#file')[0].files[0])
const url = '{{$urls->api}}/contabilidad/tesoreria/import'
const method = 'post'
const body = new FormData(submitEvent.currentTarget)
fetchAPI(url, {method, body}).then(response => {
if (!response) {
return
}
return response.json().then(data => {
console.log(data)
})
}).catch(error => {
console.error(error)
})
return false
})
})
</script>
@endpush

View File

@ -10,13 +10,11 @@
<div class="ui two column grid"> <div class="ui two column grid">
<div class="column"> <div class="column">
@include('home.cuotas_por_vencer') @include('home.cuotas_por_vencer')
@include('home.cierres_vigentes')
</div> </div>
<div class="column"> <div class="column">
@include('home.alertas') @include('home.alertas')
</div> </div>
<div class="column">
@include('home.cierres_vigentes')
</div>
</div> </div>
</div> </div>
@endsection @endsection

View File

@ -68,21 +68,6 @@
const index = this.data.proyectos.findIndex(proyecto => proyecto.id === data.proyecto_id) const index = this.data.proyectos.findIndex(proyecto => proyecto.id === data.proyecto_id)
this.data.proyectos[index].escrituras = data.escrituras this.data.proyectos[index].escrituras = data.escrituras
}) })
/*const index = this.data.proyectos.findIndex(proyecto => proyecto.id === proyecto_id)
if (proyecto_id === 3) {
this.data.proyectos[index].escrituras = {
firmar: 20,
pagar: 70,
abonar: 3
}
}
if (proyecto_id === 4) {
this.data.proyectos[index].escrituras = {
firmar: 0,
pagar: 0,
abonar: 2
}
}*/
} }
} }
}, },
@ -132,7 +117,7 @@
$('<div></div>').addClass('event').append( $('<div></div>').addClass('event').append(
$('<div></div>').addClass('content').html(tipo) $('<div></div>').addClass('content').html(tipo)
).append( ).append(
$('<div></div>').addClass('meta').html(total + '/' + full + ' ' + formatter.format(Math.round(total / full * 10000) / 100).padStart(2, ' ') + '%') $('<div></div>').addClass('meta').html(total + '/' + full + ' [' + formatter.format(Math.round(total / full * 10000) / 100).padStart(2, ' ') + '%]')
) )
) )
}) })

View File

@ -10,11 +10,6 @@
<div class="column"> <div class="column">
Inmobiliarias Inmobiliarias
</div> </div>
{{--<div class="right aligned column">
<button class="ui icon button" type="button">
<i class="plus icon"></i>
</button>
</div>--}}
</h2> </h2>
<div class="ui divider"></div> <div class="ui divider"></div>
<div class="ui cards"> <div class="ui cards">
@ -22,9 +17,7 @@
<div class="ui card"> <div class="ui card">
<div class="content"> <div class="content">
<div class="header"> <div class="header">
{{$inmobiliaria->abreviacion}} {{$inmobiliaria->abreviacion}}
{{--<a href="{{$urls->base}}/inmobiliaria/{{$inmobiliaria->rut}}">
</a>--}}
</div> </div>
<div class="description">{{$inmobiliaria->razon}} {{$inmobiliaria->tipoSociedad->descripcion}}</div> <div class="description">{{$inmobiliaria->razon}} {{$inmobiliaria->tipoSociedad->descripcion}}</div>
<div class="meta">{{$inmobiliaria->rut()}}</div> <div class="meta">{{$inmobiliaria->rut()}}</div>

View File

@ -0,0 +1,330 @@
@extends('layout.base')
@section('page_content')
<div class="ui container">
<table class="ui table">
<thead>
<tr>
<th>Nombre</th>
<th>Contacto</th>
<th class="right aligned">
<button class="ui green icon button" id="add_button">
<i class="plus icon"></i>
</button>
</th>
</tr>
</thead>
<tbody id="proveedores">
@foreach ($sociedades as $sociedad)
<tr>
<td>{{$sociedad->nombre}}</td>
<td>{{$sociedad->contacto->nombreCompleto()}}</td>
<td class="right aligned">
<button class="ui icon button" data-sociedad="{{$sociedad->rut}}">
<i class="edit icon"></i>
</button>
<button class="ui red icon button" data-sociedad="{{$sociedad->rut}}">
<i class="remove icon"></i>
</button>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
<div class="ui modal" id="add_modal">
<div class="content">
<form class="ui form">
<div class="three wide field">
<label for="rut">RUT</label>
<div class="ui right labeled input">
<input class="right aligned" type="text" id="rut" name="rut" placeholder="RUT" maxlength="10" required />
<div class="ui basic label">-<span id="dv"></span></div>
</div>
</div>
<div class="five wide field">
<label for="nombre">Nombre</label>
<input type="text" id="nombre" name="nombre" placeholder="Nombre" required />
</div>
<div class="field">
<label for="razon">Razón Social</label>
<input type="text" id="razon" name="razon" placeholder="Razón Social" required />
</div>
<div class="four wide field">
<label for="tipo">Tipo</label>
<div class="ui selection dropdown" id="tipo">
<input type="hidden" name="tipo" required />
<i class="dropdown icon"></i>
<div class="default text">Tipo</div>
<div class="menu">
@foreach ($tiposSociedades as $tipo)
<div class="item" data-value="{{$tipo->id}}">{{$tipo->descripcion}}</div>
@endforeach
</div>
</div>
</div>
<div class="ui divider">Contacto</div>
<div class="three wide field">
<label for="rut_contacto">RUT</label>
<div class="ui right labeled input">
<input type="text" id="rut_contacto" name="rut_contacto" placeholder="RUT" maxlength="10" required />
<div class="ui basic label">-<span id="dv_contacto"></span></div>
</div>
</div>
<div class="fields">
<div class="five wide field">
<label for="nombre_contacto">Nombre</label>
<input type="text" id="nombre_contacto" name="nombre_contacto" placeholder="Nombre" required />
</div>
<div class="field">
<label for="apellido_paterno_contacto">Apellido Paterno</label>
<input type="text" id="apellido_paterno_contacto" name="apellido_paterno_contacto" placeholder="Apellido Paterno" required />
</div>
<div class="field">
<label for="apellido_materno_contacto">Apellido Materno</label>
<input type="text" id="apellido_materno_contacto" name="apellido_materno_contacto" placeholder="Apellido Materno" />
</div>
</div>
<div class="field">
<label for="email_contacto">Email</label>
<input type="email" id="email_contacto" name="email_contacto" placeholder="Email" />
</div>
<div class="field">
<label for="telefono_contacto">Teléfono</label>
<div class="ui left labeled input">
<div class="ui basic label">+56</div>
<input type="text" id="telefono_contacto" name="telefono_contacto" placeholder="Teléfono" />
</div>
</div>
</form>
</div>
<div class="actions">
<button class="ui green approve button">Guardar</button>
</div>
</div>
@endsection
@push('page_scripts')
<script>
const proveedores = {
ids: {
modal: '',
buttons: {
add: '',
edit: '',
remove: ''
},
add: {
form: '',
rut: '',
dv: '',
nombre: '',
razon: '',
tipo: '',
contacto: {
rut: '',
dv: '',
nombre: '',
apellido_paterno: '',
apellido_materno: '',
email: '',
telefono: ''
}
},
proveedores: ''
},
data: JSON.parse('{!! json_encode($sociedades) !!}'),
add() {
return {
sociedad: () => {
const data = {
rut: $(this.ids.add.rut).val().replace(/\D/g, ''),
digito: $(this.ids.add.dv).text(),
nombre: $(this.ids.add.nombre).val(),
razon: $(this.ids.add.razon).val(),
tipo_sociedad_id: $(this.ids.add.tipo).dropdown('get value'),
contacto: {
rut: $(this.ids.add.contacto.rut).val().replace(/\D/g, ''),
digito: $(this.ids.add.contacto.dv).text(),
nombres: $(this.ids.add.contacto.nombre).val(),
apellido_paterno: $(this.ids.add.contacto.apellido_paterno).val(),
apellido_materno: $(this.ids.add.contacto.apellido_materno).val(),
email: $(this.ids.add.contacto.email).val(),
telefono: $(this.ids.add.contacto.telefono).val().replace(/\D/g, ''),
}
}
const body = new FormData()
body.append('sociedades[]', JSON.stringify(data))
const url = '{{$urls->api}}/sociedades/add'
const method = 'post'
fetchAPI(url, {method, body})
.then(response => (response) ? response.json() : null)
.then(data => {
if (data.sociedades !== null) {
data.sociedades.forEach(sociedad => {
const exists = this.data.find(s => s.rut === sociedad.rut)
if (typeof exists !== 'undefined') {
return
}
this.data.push(sociedad)
})
this.draw().sociedades()
$(this.ids.modal).find('form.form').trigger('reset')
}
})
}
}
},
edit() {},
remove() {
return {
sociedad: rut => {
const body = new FormData()
body.append('sociedades_ruts[]', rut)
const url = '{{$urls->api}}/sociedades/delete'
const method = 'post'
fetchAPI(url, {method, body})
.then(response => (response) ? response.json() : null)
.then(data => {
if (data.sociedades !== null) {
data.sociedades.forEach(sociedad => {
if (sociedad.sociedad.rut !== rut) {
return
}
if (!sociedad.deleted) {
return
}
$(this.ids.proveedores).find(`button[data-sociedad="${rut}"]`).closest('tr').remove()
})
}
})
}
}
},
draw() {
return {
sociedades: () => {
$(this.ids.proveedores).empty()
this.data.forEach(sociedad => {
$(this.ids.proveedores).append(`
<tr>
<td>${sociedad.nombre}</td>
<td>${sociedad.contacto.nombreCompleto}</td>
<td class="right aligned">
<button class="ui icon button" data-sociedad="${sociedad.rut}">
<i class="edit icon"></i>
</button>
<button class="ui red icon button" data-sociedad="${sociedad.rut}">
<i class="remove icon"></i>
</button>
</td>
</tr>
`)
})
$(this.ids.buttons.remove).click((e) => {
this.remove().sociedad($(e.target).data('sociedad'))
})
}
}
},
formatters() {
return {
rut: value => {
const rut = value.replace(/[^0-9]/g, '')
if (rut.length <= 1) {
return rut
}
return rut.replace(/\B(?=(\d{3})+(?!\d))/g, '.')
},
telefono: value => {
const phone = value.replace(/[^0-9]/g, '')
if (phone.length <= 1) {
return phone
}
return phone.replace(/(\d{2})(\d{3})(\d{4})/, '$1 $2 $3')
}
}
},
digitoVerificador(value) {
let rut = value.replace(/[^0-9kK]/g, '')
if (rut.length < 1) {
return ''
}
let suma = 0
let mul = 2
for (let i = rut.length-1; i >= 0; i--) {
suma += parseInt(rut[i]) * mul
mul = (mul + 1) % 8 || 2
}
const dv = 11 - suma % 11
return dv === 10 ? 'K' : (dv === 11 ? '0' : dv.toString())
},
setup(ids) {
this.ids = ids
$(this.ids.buttons.add).click(() => {
$(this.ids.modal).modal('show')
})
$(this.ids.add.rut).on('input', (e) => {
e.target.value = this.formatters().rut(e.target.value)
$(this.ids.add.dv).text(this.digitoVerificador(e.target.value))
})
if ($(this.ids.add.rut).val().length > 0) {
$(this.ids.add.rut).val(this.formatters().rut($(this.ids.add.rut).val()))
$(this.ids.add.dv).text(this.digitoVerificador($(this.ids.add.rut).val()))
}
$(this.ids.tipo).dropdown()
$(this.ids.add.contacto.rut).on('input', (e) => {
e.target.value = this.formatters().rut(e.target.value)
$(this.ids.add.contacto.dv).text(this.digitoVerificador(e.target.value))
})
if ($(this.ids.add.contacto.rut).val().length > 0) {
$(this.ids.add.contacto.rut).val(this.formatters().rut($(this.ids.add.contacto.rut).val()))
$(this.ids.add.contacto.dv).text(this.digitoVerificador($(this.ids.add.contacto.rut).val()))
}
$(this.ids.add.contacto.telefono).on('input', (e) => {
e.target.value = this.formatters().telefono(e.target.value)
})
if ($(this.ids.add.contacto.telefono).val().length > 0) {
$(this.ids.add.contacto.telefono).val(this.formatters().telefono($(this.ids.add.contacto.telefono).val()))
}
$(this.ids.modal).modal({
onApprove: () => {
this.add().sociedad()
}
})
$(this.ids.buttons.remove).click((e) => {
this.remove().sociedad($(e.target).data('sociedad'))
})
}
}
$(document).ready(() => {
proveedores.setup({
modal: '#add_modal',
buttons: {
add: '#add_button',
edit: '.edit',
remove: '.remove'
},
add: {
form: '#add_modal form.form',
rut: '#rut',
dv: '#dv',
nombre: '#nombre',
razon: '#razon',
tipo: '#tipo',
contacto: {
rut: '#rut_contacto',
dv: '#dv_contacto',
nombre: '#nombre_contacto',
apellido_paterno: '#apellido_paterno_contacto',
apellido_materno: '#apellido_materno_contacto',
email: '#email_contacto',
telefono: '#telefono_contacto'
}
},
proveedores: '#proveedores'
})
})
</script>
@endpush

View File

@ -16,7 +16,14 @@
<a class="item" href="{{$urls->base}}/contabilidad/centros_costos/asignar">Asignar en Cartola</a> <a class="item" href="{{$urls->base}}/contabilidad/centros_costos/asignar">Asignar en Cartola</a>
</div> </div>
</div> </div>
<a class="item" href="{{$urls->base}}/contabilidad/cartolas/diaria">Cartola Diaria</a> <div class="item">
<i class="dropdown icon"></i>
<a class="text" href="{{$urls->base}}/contabilidad/cartolas/diaria">Cartola Diaria</a>
<div class="menu">
<a class="item" href="{{$urls->base}}/contabilidad/cartolas/importar">Importar</a>
</div>
</div>
<a class="item" href="{{$urls->base}}/contabilidad/depositos">Depósitos a Plazo</a> <a class="item" href="{{$urls->base}}/contabilidad/depositos">Depósitos a Plazo</a>
<a class="item" href="{{$urls->base}}/contabilidad/movimientos">Movimientos</a>
</div> </div>
</div> </div>

View File

@ -33,6 +33,7 @@
</div>--}} </div>--}}
{{--<a class="item" href="{{$urls->base}}/ventas/precios/importar">Importar Precios</a>--}} {{--<a class="item" href="{{$urls->base}}/ventas/precios/importar">Importar Precios</a>--}}
{{--<a class="item" href="{{$urls->base}}/ventas/cierres/evaluar">Evaluar Cierre</a>--}} {{--<a class="item" href="{{$urls->base}}/ventas/cierres/evaluar">Evaluar Cierre</a>--}}
<a class="item" href="{{$urls->base}}/ventas/facturacion">Facturación</a>
<a class="item" href="{{$urls->base}}/ventas/add"> <a class="item" href="{{$urls->base}}/ventas/add">
Nueva Venta Nueva Venta
<i class="plus icon"></i> <i class="plus icon"></i>

View File

@ -2,6 +2,11 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/fomantic-ui/2.9.3/semantic.min.js" integrity="sha512-gnoBksrDbaMnlE0rhhkcx3iwzvgBGz6mOEj4/Y5ZY09n55dYddx6+WYc72A55qEesV8VX2iMomteIwobeGK1BQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/fomantic-ui/2.9.3/semantic.min.js" integrity="sha512-gnoBksrDbaMnlE0rhhkcx3iwzvgBGz6mOEj4/Y5ZY09n55dYddx6+WYc72A55qEesV8VX2iMomteIwobeGK1BQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script type="text/javascript"> <script type="text/javascript">
class APIClient {
static fetch(url, options=null) {
return fetchAPI(url, options)
}
}
function fetchAPI(url, options=null) { function fetchAPI(url, options=null) {
if (options === null) { if (options === null) {
options = {} options = {}
@ -10,7 +15,7 @@
options['headers'] = {} options['headers'] = {}
} }
if (!Object.hasOwn(options['headers'], 'Authorization')) { if (!Object.hasOwn(options['headers'], 'Authorization')) {
options['headers']['Authorization'] = 'Bearer {{md5($API_KEY)}}' options['headers']['Authorization'] = 'Bearer {{md5($API_KEY)}}{{($login->isIn()) ? $login->getSeparator() . $login->getToken() : ''}}'
} }
return fetch(url, options).then(response => { return fetch(url, options).then(response => {
if (response.ok) { if (response.ok) {
@ -21,6 +26,28 @@
console.error(error) console.error(error)
}) })
} }
const datatables_defaults = {
language: {
emptyTable: 'No hay datos disponibles',
info: 'Mostrando _START_ a _END_ de _TOTAL_ registros',
infoEmpty: 'Mostrando desde 0 a 0 de 0 registros',
infoFiltered: '(filtrado de _MAX_ registros totales)',
lengthMenu: 'Mostrar _MENU_ registros',
loadingRecords: 'Cargando...',
search: 'Buscar:',
zeroRecords: 'No se encontraron registros',
paginate: {
first: 'Primero',
last: 'Último',
next: 'Siguiente',
previous: 'Anterior',
},
aria: {
orderable: 'Ordenar por esta columna',
orderableReverse: 'Ordenar por esta columna en orden inverso',
}
}
}
const calendar_date_options = { const calendar_date_options = {
type: 'date', type: 'date',
firstDayOfWeek: 1, firstDayOfWeek: 1,
@ -28,9 +55,12 @@
monthFirst: false, monthFirst: false,
text: { text: {
days: ['Do', 'Lu', 'Ma', 'Mi', 'Ju', 'Vi', 'Sa'], days: ['Do', 'Lu', 'Ma', 'Mi', 'Ju', 'Vi', 'Sa'],
dayNamesShort: ['Dom', 'Lun', 'Mar', 'Mie', 'Jue', 'Vie', 'Sab'],
dayNames: ['Domingo', 'Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado'],
months: ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'], 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'], monthsShort: ['Ene', 'Feb', 'Mar', 'Abr', 'May', 'Jun', 'Jul', 'Ago', 'Sep', 'Oct', 'Nov', 'Dic'],
today: 'Hoy' today: 'Hoy',
now: 'Ahora',
}, },
formatter: { formatter: {
date: 'DD-MM-YYYY' date: 'DD-MM-YYYY'

View File

@ -0,0 +1,3 @@
@push('page_scripts')
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.2.0/crypto-js.min.js" integrity="sha512-a+SUDuwNzXDvz4XrIcXHuCf089/iJAoN4lmrXJg18XnduKK6YlDHNRalv4yd1N40OKI80tFidF+rqTFKGPoWFQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
@endpush

View File

@ -1,5 +1,4 @@
@push('page_scripts') @push('page_scripts')
{{--<script type="text/javascript" src="https://cdn.datatables.net/2.0.1/js/jquery.dataTables.min.js"></script>--}} <script type="text/javascript" src="https://cdn.datatables.net/2.0.3/js/dataTables.min.js"></script>
<script type="text/javascript" src="https://cdn.datatables.net/2.0.1/js/dataTables.min.js"></script> <script src="https://cdn.datatables.net/2.0.3/js/dataTables.semanticui.min.js"></script>
<script src="https://cdn.datatables.net/2.0.1/js/dataTables.semanticui.min.js"></script>
@endpush @endpush

View File

@ -1,9 +1,9 @@
@push('page_scripts') @push('page_scripts')
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js" crossorigin="anonymous" referrerpolicy="no-referrer"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.9/pdfmake.min.js" crossorigin="anonymous" referrerpolicy="no-referrer"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.9/pdfmake.min.js" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdn.datatables.net/buttons/3.0.0/js/dataTables.buttons.min.js"></script> <script src="https://cdn.datatables.net/buttons/3.0.1/js/dataTables.buttons.min.js"></script>
<script src="https://cdn.datatables.net/buttons/3.0.0/js/buttons.semanticui.min.js"></script> <script src="https://cdn.datatables.net/buttons/3.0.1/js/buttons.semanticui.min.js"></script>
<script src="https://cdn.datatables.net/buttons/3.0.0/js/buttons.colVis.min.js"></script> <script src="https://cdn.datatables.net/buttons/3.0.1/js/buttons.colVis.min.js"></script>
<script src="https://cdn.datatables.net/buttons/3.0.0/js/buttons.html5.min.js"></script> <script src="https://cdn.datatables.net/buttons/3.0.1/js/buttons.html5.min.js"></script>
<script src="https://cdn.datatables.net/buttons/3.0.0/js/buttons.print.min.js"></script> <script src="https://cdn.datatables.net/buttons/3.0.1/js/buttons.print.min.js"></script>
@endpush @endpush

View File

@ -0,0 +1,5 @@
@push('page_scripts')
<script src="https://cdn.datatables.net/datetime/1.5.2/js/dataTables.dateTime.min.js"></script>
<script src="https://cdn.datatables.net/searchbuilder/1.7.0/js/dataTables.searchBuilder.min.js"></script>
<script src="https://cdn.datatables.net/searchbuilder/1.7.0/js/searchBuilder.semanticui.js"></script>
@endpush

View File

@ -0,0 +1,3 @@
@push('page_scripts')
<script src="https://cdn.jsdelivr.net/npm/luxon@3.4.4/build/global/luxon.min.js" integrity="sha256-7NQm0bhvDJKosL8d+6ZgSi2LxZCIcA/TD087GLEBO9M=" crossorigin="anonymous"></script>
@endpush

View File

@ -0,0 +1,26 @@
@push('page_scripts')
<script>
class Rut {
static digitoVerificador(rut) {
if (rut.length === 0) {
return ''
}
let M = 0, S = 1
for (; rut; rut = Math.floor(rut / 10)) {
S = (S + rut % 10 * (9 - M++ % 6)) % 11
}
return S ? S - 1 : 'K'
}
static format(rut) {
if (rut.length === 0) {
return ''
}
rut.replace(/\D/g, '')
return rut.replace(/^(\d{1,2})(\d{3})(\d{3})$/, '$1.$2.$3')
}
static validar(rut, digito) {
return Rut.digitoVerificador(rut) === digito
}
}
</script>
@endpush

View File

@ -1,3 +1,3 @@
@push('page_styles') @push('page_styles')
<link rel="stylesheet" href="https://cdn.datatables.net/2.0.1/css/dataTables.semanticui.min.css" /> <link rel="stylesheet" href="https://cdn.datatables.net/2.0.3/css/dataTables.semanticui.min.css" />
@endpush @endpush

View File

@ -0,0 +1,4 @@
@push('page_styles')
<link rel="stylesheet" href="https://cdn.datatables.net/datetime/1.5.2/css/dataTables.dateTime.min.css" />
<link rel="stylesheet" href="https://cdn.datatables.net/searchbuilder/1.7.0/css/searchBuilder.dataTables.min.css" />
@endpush

View File

@ -16,29 +16,30 @@
</div> </div>
@endsection @endsection
@include('layout.body.scripts.cryptojs')
@push('page_scripts') @push('page_scripts')
<script type="text/javascript"> <script type="text/javascript">
function encryptPassword(password) {
const passphrase = Math.floor(Math.random() * Date.now()).toString()
const encrypted = CryptoJS.AES.encrypt(password, passphrase)
return [passphrase, encrypted.toString()].join('')
}
function sendLogin(name, password) { function sendLogin(name, password) {
const data = new FormData() const method = 'post'
data.append('name', name) const headers = {
data.append('password', password) Accept: 'json'
return fetch('{{$urls->base}}/login', { }
method: 'post', const body = new FormData()
headers: { body.append('name', name)
Accept: 'json' body.append('password', encryptPassword(password))
}, return fetch('{{$urls->base}}/login', {method, headers, body}).then(response => {
body: data
}).then(response => {
if (response.ok) { if (response.ok) {
return response.json() return response.json()
} }
}).then(data => { }).then(data => {
if (data.login === true) { if (data.login === true) {
@if(isset($redirect_uri)) window.location = '{{(isset($redirect_uri)) ? $redirect_uri : $urls->base}}'
window.location = '{{$redirect_uri}}'
@else
window.location = '{{$urls->base}}'
@endif
} }
}) })
} }

View File

@ -789,6 +789,9 @@
$('#add_form').submit(event => { $('#add_form').submit(event => {
event.preventDefault() event.preventDefault()
const button = $(event.currentTarget).find(".ui.button[type='submit']")
button.prop('disabled', true)
button.css('cursor', 'wait')
const body = new FormData(event.currentTarget) const body = new FormData(event.currentTarget)
const uri = '{{$urls->api}}/ventas/add' const uri = '{{$urls->api}}/ventas/add'
return fetchAPI(uri, {method: 'post', body}).then(response => { return fetchAPI(uri, {method: 'post', body}).then(response => {
@ -800,6 +803,8 @@
window.location = '{{$urls->base}}/venta/' + data.venta_id window.location = '{{$urls->base}}/venta/' + data.venta_id
return true return true
} }
button.prop('disabled', false)
button.css('cursor', 'pointer')
showErrors(data.errors) showErrors(data.errors)
return false return false
}) })

View File

@ -1,5 +1,14 @@
@extends('layout.base') @extends('layout.base')
@push('page_styles')
<style>
#data {
margin-left: 1rem;
margin-right: 1rem;
}
</style>
@endpush
@section('page_content') @section('page_content')
<div class="ui container"> <div class="ui container">
<h2 class="ui header">Matriz Facturación</h2> <h2 class="ui header">Matriz Facturación</h2>
@ -24,7 +33,7 @@
@endforeach @endforeach
</div> </div>
</div> </div>
<table class="ui table" id="data"></table> <div id="data"></div>
@endsection @endsection
@include('layout.head.styles.datatables') @include('layout.head.styles.datatables')
@ -33,53 +42,150 @@
@push('page_scripts') @push('page_scripts')
<script> <script>
const money = { const money = {
data: {
ufs: {},
ipcs: {}
},
ufs: {}, ufs: {},
ipcs: {}, ipcs: {},
sent: { sent: {
uf: {}, uf: {},
ipc: {} ipc: {}
}, },
queue: {
size: 0,
array: () => {
const array = []
Object.entries(money.queue).forEach(([type, dates]) => {
if (['uf', 'ipc'].includes(type) === false) {
return
}
if (type === 'uf') {
Object.entries(dates).forEach(([dateString, ventas]) => {
const temp = []
ventas.forEach(venta => {
temp.push(venta)
})
array.push({
type,
date: dateString,
ventas: temp
})
})
return
}
Object.entries(dates).forEach(([dateString, data]) => {
const temp = []
data.ventas.forEach(venta => {
temp.push(venta)
})
array.push({
type,
start: data.start,
end: data.end,
ventas: temp
})
})
})
return array
}
},
enqueue() {
return {
uf: ({date, venta_id}) => {
const type = 'uf'
const data = {
date: [date.getFullYear(), date.getMonth()+1, date.getDate()].join('-'),
}
if (!(type in this.queue)) {
this.queue[type] = {}
}
if (!(data.date in this.queue[type])) {
this.queue[type][data.date] = []
}
if (this.queue[type][data.date].includes(data)) {
return
}
this.queue[type][data.date].push(venta_id)
this.queue['size'] ++
},
ipc: ({start, end, venta_id}) => {
const type = 'ipc'
let mult = 1
if (start > end) {
mult = -1
let temp = end
end = start
start = temp
}
const data = {
start: [start.getFullYear(), start.getMonth()+1, start.getDate()].join('-'),
end: [end.getFullYear(), end.getMonth()+1, end.getDate()].join('-'),
}
if (!(type in this.queue)) {
this.queue[type] = {}
}
if (!([data.start, data.end].join('|') in this.queue[type])) {
this.queue[type][[data.start, data.end].join('|')] = {
start: data.start,
end: data.end,
ventas: []
}
}
if (this.queue[type][[data.start, data.end].join('|')].ventas.includes(data)) {
return
}
this.queue[type][[data.start, data.end].join('|')].ventas.push({venta_id, mult})
this.queue['size'] ++
},
}
},
get() { get() {
return { return {
uf: date => { many: () => {
if (typeof this.sent.uf[date.toISOString()] !== 'undefined') { const url = '{{$urls->api}}/money/many'
return this.sent.uf[date.toISOString()] const method = 'post'
} const chunkSize = 100
const url = '{{$urls->api}}/money/uf' const promises = []
const data = new FormData() for (let i = 0; i < this.queue.size; i += chunkSize) {
data.set('fecha', date.toISOString()) const chunk = this.queue.array().slice(i, i + chunkSize)
const options = { const missing = chunk.filter(data => {
method: 'post', if (data.type === 'uf') {
body: data return !(data.date in this.data.ufs)
} }
return this.sent.uf[date.toISOString()] = fetchAPI(url, options).then(response => { if (data.type === 'ipc') {
if (response.ok) { const dateKey = [data.start, data.end].join('|')
return response.json() return !(dateKey in this.data.ipcs)
}
})
if (missing.length === 0) {
continue
} }
}).then(json => { const body = new FormData()
return this.ufs[json.input.fecha] = json.uf body.set('dates', JSON.stringify(missing))
}) const options = {
}, method,
ipc: (end, start) => { body
const dateKey = [start.getFullYear(), (start.getMonth()+1), end.getFullYear(), (end.getMonth()+1)].join('-')
if (typeof this.sent.ipc[dateKey] !== 'undefined') {
return this.sent.ipc[dateKey]
}
const url = '{{$urls->api}}/money/ipc'
const data = new FormData()
data.set('start', start.toISOString())
data.set('end', end.toISOString())
const options = {
method: 'post',
body: data
}
return this.sent.ipc[dateKey] = fetchAPI(url, options).then(response => {
if (response.ok) {
return response.json()
} }
}).then(json => { promises.push(fetchAPI(url, options).then(response => {
return this.ipcs[json.date_string] = json.ipc if (!response) {
}) return
}
return response.json().then(json => {
json.values.forEach(data => {
if (data.type === 'uf') {
this.data.ufs[data.date] = data.value
}
if (data.type === 'ipc') {
const dateKey = [data.start, data.end].join('|')
this.data.ipcs[dateKey] = data.value
}
})
return json.values
})
}))
}
return Promise.all(promises)
} }
} }
} }
@ -92,8 +198,7 @@
prorrateo prorrateo
precio precio
constructor({id, tipo, descripcion, prorrateo, precio}) constructor({id, tipo, descripcion, prorrateo, precio}) {
{
this.id = id this.id = id
this.tipo = tipo this.tipo = tipo
this.descripcion = descripcion this.descripcion = descripcion
@ -111,44 +216,13 @@
unidades unidades
principal principal
constructor({id, precio, fecha, escritura}) { constructor({id, precio, fecha, escritura, unidades, principal}) {
this.id = id this.id = id
this.precio = precio this.precio = precio
this.fecha = fecha this.fecha = new Date(fecha)
this.escritura = escritura this.escritura = new Date(escritura)
this.uf = 1 this.unidades = unidades
this.ipc = 1 this.principal = principal
this.unidades = []
}
get() {
return {
unidades: () => {
const url = '{{$urls->api}}/venta/' + this.id + '/unidades'
return fetchAPI(url).then(response => {
if (response.ok) {
return response.json()
}
}).then(json => {
json.unidades.forEach(unidad => {
const tipo = unidad.proyecto_tipo_unidad.tipo_unidad.descripcion
const data = {
id: unidad.id,
tipo: tipo.charAt(0).toUpperCase() + tipo.slice(1),
descripcion: unidad.descripcion,
prorrateo: unidad.prorrateo,
precio: 0
}
if (unidad.current_precio !== null) {
data.precio = unidad.current_precio.valor
}
const u = new Unidad(data)
this.unidades.push(u)
})
this.principal = this.unidades.filter(unidad => unidad.tipo === 'Departamento')[0]
})
}
}
} }
draw({tbody, valor_terreno}) { draw({tbody, valor_terreno}) {
@ -173,16 +247,17 @@
const dateFormatter = new Intl.DateTimeFormat('es-CL', {year: 'numeric', month: 'numeric', day: 'numeric'}) const dateFormatter = new Intl.DateTimeFormat('es-CL', {year: 'numeric', month: 'numeric', day: 'numeric'})
const pesosFormatter = new Intl.NumberFormat('es-CL', {style: 'currency', currency: 'CLP'}) const pesosFormatter = new Intl.NumberFormat('es-CL', {style: 'currency', currency: 'CLP'})
const ufFormatter = new Intl.NumberFormat('es-CL', {minimumFractionDigits: 2, maximumFractionDigits: 2}) const ufFormatter = new Intl.NumberFormat('es-CL', {minimumFractionDigits: 2, maximumFractionDigits: 2})
const venta = this.unidades[0].descripcion const venta = this.principal.descripcion
const cantidad = this.unidades.length const cantidad = this.unidades.length
this.unidades.forEach(unidad => { this.unidades.forEach(unidad => {
const valor = (unidad.valor > 0) ? unidad.valor : unidad.precio
const values = [ const values = [
venta, '<a href="{{$urls->base}}/ventas/factura/' + this.id + '">' + venta + '</a>',
cantidad, cantidad,
unidad.tipo, unidad.tipo,
unidad.descripcion, unidad.descripcion,
dateFormatter.format(this.fecha), dateFormatter.format(this.fecha),
ufFormatter.format(unidad.precio) + ' UF', ufFormatter.format(valor) + ' UF',
unidad.prorrateo, unidad.prorrateo,
'', '',
'', '',
@ -195,22 +270,23 @@
'' ''
] ]
if (this.escritura !== null) { if (this.escritura !== null) {
const descuento = valor_terreno * (1 + this.ipc / 100) * unidad.prorrateo const ipc = (this.ipc >= 0) ? (1 + this.ipc) : 1 / (1 + this.ipc)
const precio_venta = unidad.precio * this.uf const descuento = valor_terreno * ipc * unidad.prorrateo
const precio_venta = valor * this.uf
const precio_bruto = precio_venta - descuento const precio_bruto = precio_venta - descuento
const precio_neto = precio_bruto / 1.19 const precio_neto = precio_bruto / 1.19
const iva = precio_bruto - precio_neto const iva = precio_bruto - precio_neto
let i = 7 let i = 7
values[i++] = pesosFormatter.format(descuento) values[i++] = (!isNaN(descuento)) ? pesosFormatter.format(descuento) : '<div class="ui active inline loader"></div>'
values[i++] = pesosFormatter.format(precio_venta) values[i++] = (!isNaN(precio_venta)) ? pesosFormatter.format(precio_venta) : ''
values[i++] = pesosFormatter.format(precio_bruto) values[i++] = (!isNaN(precio_bruto)) ? pesosFormatter.format(precio_bruto) : ''
values[i++] = pesosFormatter.format(iva) values[i++] = (!isNaN(iva)) ? pesosFormatter.format(iva) : ''
values[i++] = pesosFormatter.format(precio_neto) values[i++] = (!isNaN(precio_neto)) ? pesosFormatter.format(precio_neto) : ''
values[i++] = (iva / precio_venta * 100).toFixed(2) + '%' values[i++] = (!isNaN(iva) && !isNaN(precio_venta)) ? (iva / precio_venta * 100).toFixed(2) + '%' : ''
values[i++] = dateFormatter.format(this.escritura) values[i++] = dateFormatter.format(this.escritura)
values[i++] = ufFormatter.format(this.uf) values[i++] = (!isNaN(this.uf)) ? ufFormatter.format(this.uf) : ''
values[i++] = (this.ipc).toFixed(2) + '%' values[i++] = (!isNaN(this.ipc)) ? (ufFormatter.format(this.ipc >= 0 ? this.ipc * 100 : -this.ipc * 100) + '%') : ''
} }
const row = $('<tr></tr>') const row = $('<tr></tr>')
values.forEach(value => { values.forEach(value => {
@ -226,74 +302,144 @@
ids: {}, ids: {},
selected: 0, selected: 0,
data: JSON.parse('{!! json_encode($proyectos) !!}'), data: JSON.parse('{!! json_encode($proyectos) !!}'),
sent: false,
queues: {
uf: {},
ipc: {}
},
ufs: {},
ipcs: {},
table: null, table: null,
get: function() { get: function() {
return { return {
ventas: () => { ventas: () => {
if (this.sent) {
return
}
this.sent = true
const url = '{{$urls->api}}/ventas/facturacion/proyecto/' + this.selected const url = '{{$urls->api}}/ventas/facturacion/proyecto/' + this.selected
this.draw().loading()
return fetchAPI(url).then(response => { return fetchAPI(url).then(response => {
if (response.ok) { if (!response) {
return response.json() return
} }
}).then(json => { return response.json().then(json => {
const idx = this.data.findIndex(proyecto => proyecto.id === json.proyecto_id) const idx = this.data.findIndex(proyecto => proyecto.id === json.proyecto_id)
const fecha_terreno = new Date(this.data[idx].terreno.date) this.data[idx]['ventas'] = []
this.data[idx]['ventas'] = [] const ventas = []
const ventas = [] const chunkSize = 100
json.ventas.forEach(venta => { for (let i = 0; i < json.ventas.length; i += chunkSize) {
const data = { const chunk = json.ventas.slice(i, i + chunkSize).map(venta => venta.id)
id: venta.id, ventas.push(this.get().chunk({idx, chunk}))
precio: venta.valor,
fecha: new Date(venta.fecha),
escritura: null
} }
if (['escriturando'].includes(venta.current_estado.tipo_estado_venta.descripcion)) { Promise.all(ventas).then(() => {
data.escritura = new Date(venta.current_estado.fecha) this.draw().ventas(idx)
} this.sent = false
const v = new Venta(data) money.get().many().then(response => {
const promises = [] response.forEach(dates => {
if (v.escritura !== null) { dates.forEach(values => {
promises.push(money.get().uf(v.escritura).then(uf => { values.ventas.forEach(venta => {
v.uf = uf let vidx = 0
})) if (values.type === 'uf') {
promises.push(money.get().ipc(v.escritura, fecha_terreno).then(ipc => { vidx = this.data[idx].ventas.findIndex(v => v.id === venta)
v.ipc = ipc this.data[idx].ventas[vidx].uf = values.value
})) return
} }
promises.push(v.get().unidades()) if (values.type === 'ipc') {
const promise = Promise.all(promises).then(() => { vidx = this.data[idx].ventas.findIndex(v => v.id === venta.venta_id)
this.data[idx].ventas.push(v) this.data[idx].ventas[vidx].ipc = values.value * venta.mult
}
})
})
})
this.draw().ventas(idx)
})
}) })
ventas.push(promise)
})
Promise.all(ventas).then(() => {
this.data[idx].ventas.sort((a, b) => parseInt(a.principal.descripcion) - parseInt(b.principal.descripcion))
this.draw().ventas(idx)
}) })
}) })
},
chunk: ({idx, chunk}) => {
const url = '{{$urls->api}}/ventas/facturacion/get'
const method = 'post'
const body = new FormData()
body.set('ventas', chunk)
return fetchAPI(url, {method, body}).then(response => {
if (!response) {
return response
}
return response.json().then(json => {
json.ventas.forEach(venta => {
this.add().venta({proyecto_idx: idx, venta})
})
});
})
},
}
},
add() {
return {
venta: ({proyecto_idx, venta}) => {
const v = new Venta(venta)
this.data[proyecto_idx].ventas.push(v)
const fecha_terreno = (typeof this.data[proyecto_idx].terreno.fecha === 'undefined') ? new Date() : new Date(this.data[proyecto_idx].terreno.fecha)
if (v.escritura !== null) {
money.enqueue().uf({date: v.escritura, venta_id: v.id})
money.enqueue().ipc({start: v.escritura, end: fecha_terreno, venta_id: v.id})
}
},
}
},
register() {
return {
uf: ({dateString, venta_id}) => {
if (!Object.hasOwn(this.queues.uf, dateString)) {
this.queues.uf[dateString] = []
}
this.queues.uf[dateString].push(venta_id)
},
ipc: ({dateString, venta_id}) => {
if (!Object.hasOwn(this.queues.ipc, dateString)) {
this.queues.ipc[dateString] = []
}
this.queues.ipc[dateString].push(venta_id)
} }
} }
}, },
draw: function() { draw: function() {
return { return {
proyectos: () => { proyectos: () => {
$(this.ids.title).html('Proyectos')
if (this.table !== null) { if (this.table !== null) {
this.table.clear() this.table.clear()
this.table.destroy()
$(this.ids.data).html('')
} }
$(this.ids.data).hide() $(this.ids.data).hide()
$(this.ids.proyectos).find('.item').css('cursor', 'pointer')
$(this.ids.proyectos).show() $(this.ids.proyectos).show()
}, },
ventas: proyecto_idx => { ventas: proyecto_idx => {
const table = $(this.ids.data) $(this.ids.title).html(this.data[proyecto_idx].descripcion)
const parent = $(this.ids.data)
if (this.table !== null) { if (this.table !== null) {
this.table.clear().destroy() this.table.clear()
this.table.destroy()
this.table = null
parent.html('')
} }
const table = $('<table></table>').addClass('ui compact table')
table.append(this.draw().thead()) table.append(this.draw().thead())
table.append(this.draw().tbody(proyecto_idx)) table.append(this.draw().tbody(proyecto_idx))
table.show() parent.show()
parent.html(table)
$(this.ids.proyectos).hide() $(this.ids.proyectos).hide()
if (this.table === null) { if (this.table === null) {
this.table = table.DataTable() const dtD = structuredClone(datatables_defaults)
dtD['pageLength'] = 100
dtD['order'] = [[0, 'asc']]
this.table = table.DataTable(dtD)
} }
}, },
thead: () => { thead: () => {
@ -331,6 +477,9 @@
venta.draw({tbody, valor_terreno: this.data[proyecto_idx].terreno.valor}) venta.draw({tbody, valor_terreno: this.data[proyecto_idx].terreno.valor})
}) })
return tbody return tbody
},
loading: () => {
$(this.ids.proyectos).find('.item').css('cursor', 'wait')
} }
} }
}, },
@ -369,6 +518,7 @@
} }
$(document).ready(() => { $(document).ready(() => {
proyectos.setup({ids: { proyectos.setup({ids: {
title: '#list_title',
proyectos: '#proyectos', proyectos: '#proyectos',
data: '#data', data: '#data',
buttons: { buttons: {

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,30 @@
@endsection @endsection
@section('venta_content') @section('venta_content')
<table class="ui table" id="cuotas"> @if (count($asociadas) > 0)
<div class="ui grid">
<div class="two wide column">Asociados</div>
<div class="six wide column">
{!! implode(' - ', array_map(function(Incoviba\Model\Venta $venta) use ($urls) {
return "<a href=\"{$urls->base}/venta/{$venta->id}\">{$venta->propiedad()->departamentos()[0]->descripcion}</a>";
}, $asociadas)) !!}
</div>
</div>
@endif
<div class="ui grid">
<div class="column">Valor</div>
<div class="four wide column">
{{$format->ufs($venta->formaPago()->pie->valor)}}
@if (count($asociadas) > 0)
[{{$format->ufs($venta->formaPago()->pie->valor + array_reduce($asociadas, function(float $sum, Incoviba\Model\Venta $venta) {
return $sum + $venta->formaPago()->pie->valor;
}, 0.0))}}]
@endif
</div>
<div class="column">Cuotas</div>
<div class="column">{{$venta->formaPago()->pie->cuotas}}</div>
</div>
<table class="ui compact table" id="cuotas">
<thead> <thead>
<tr> <tr>
<th>#</th> <th>#</th>
@ -37,32 +60,37 @@
<td>{{$cuota->pago->identificador}}</td> <td>{{$cuota->pago->identificador}}</td>
<td class="right aligned">{{$format->pesos($cuota->pago->valor)}}</td> <td class="right aligned">{{$format->pesos($cuota->pago->valor)}}</td>
<td class="right aligned"> <td class="right aligned">
@if ($cuota->pago->currentEstado->tipoEstadoPago->descripcion === 'abonado' and $cuota->pago->currentEstado->fecha <= $now) @if (in_array($cuota->pago->currentEstado->tipoEstadoPago->descripcion, ['depositado', 'abonado'])
and $cuota->pago->currentEstado->fecha <= $now)
{{$format->ufs($cuota->pago->valor())}} {{$format->ufs($cuota->pago->valor())}}
@else
~{{$format->ufs($cuota->pago->valor / $uf_venta)}}
@endif @endif
</td> </td>
<td <td{!! ($cuota->pago->currentEstado->tipoEstadoPago->descripcion === 'abonado' ? ' class="green"' :
@if ($cuota->pago->currentEstado->tipoEstadoPago->descripcion === 'abonado') ($cuota->pago->currentEstado->tipoEstadoPago->descripcion === 'depositado' ? ' class="yellow"' :
class="green" ($cuota->pago->currentEstado->tipoEstadoPago->activo !== 1 ? ' class="red"' : ''))) !!}>
@elseif ($cuota->pago->currentEstado->tipoEstadoPago->descripcion === 'depositado') {{ucwords($cuota->pago->currentEstado->tipoEstadoPago->descripcion)}}
class="yellow" </td>
@elseif ($cuota->pago->currentEstado->tipoEstadoPago->activo !== 1)
class="red"
@endif
>{{ucwords($cuota->pago->currentEstado->tipoEstadoPago->descripcion)}}</td>
<td> <td>
@if (in_array($cuota->pago->currentEstado->tipoEstadoPago->descripcion, ['abonado', 'anulado', 'reemplazado'])) @if (in_array($cuota->pago->currentEstado->tipoEstadoPago->descripcion, ['abonado', 'anulado', 'reemplazado']))
{{$cuota->pago->currentEstado->fecha->format('d-m-Y')}} {{$cuota->pago->currentEstado->fecha->format('d-m-Y')}}
@elseif (!in_array($cuota->pago->currentEstado->tipoEstadoPago->descripcion, ['anulado', 'reemplazado'])) @elseif (!in_array($cuota->pago->currentEstado->tipoEstadoPago->descripcion, ['anulado', 'reemplazado']))
@if ($cuota->pago->currentEstado->tipoEstadoPago->descripcion === 'depositado')
{{$cuota->pago->currentEstado->fecha->format('d-m-Y')}}
@endif
<div class="ui calendar fecha_estado" data-date="{{$cuota->pago->currentEstado->fecha->format('Y-m-d')}}"> <div class="ui calendar fecha_estado" data-date="{{$cuota->pago->currentEstado->fecha->format('Y-m-d')}}">
<div class="ui action left icon input"> <div class="ui action left icon input">
<i class="calendar icon"></i> <i class="calendar icon"></i>
<input type="text" name="fecha_estado" /> <input type="text" name="fecha_estado" />
<button class="ui green basic icon button accept_estado" data-pago="{{$cuota->pago->id}}" data-estado="{{$cuota->pago->currentEstado->tipoEstadoPago->descripcion}}"> <button class="ui green basic icon button accept_estado"
data-pago="{{$cuota->pago->id}}"
data-estado="{{$cuota->pago->currentEstado->tipoEstadoPago->descripcion}}">
<i class="check icon"></i> <i class="check icon"></i>
</button> </button>
@if ($cuota->pago->currentEstado->tipoEstadoPago->descripcion === 'depositado') @if ($cuota->pago->currentEstado->tipoEstadoPago->descripcion === 'depositado')
<button class="ui red basic icon button reject_estado" data-pago="{{$cuota->pago->id}}"> <button class="ui red basic icon button reject_estado"
data-pago="{{$cuota->pago->id}}">
<i class="remove icon"></i> <i class="remove icon"></i>
</button> </button>
@endif @endif
@ -115,7 +143,7 @@
}, 0.0))}} }, 0.0))}}
</th> </th>
<th class="right aligned"> <th class="right aligned">
{{$format->number($pagado / $total * 100, 2)}}% {{$format->number(($total > 0) ? $pagado / $total * 100 : 0, 2)}}%
</th> </th>
<th colspan="3"></th> <th colspan="3"></th>
</tr> </tr>
@ -128,7 +156,7 @@
{{$format->ufs($total - $pagado)}} {{$format->ufs($total - $pagado)}}
</th> </th>
<th class="right aligned"> <th class="right aligned">
{{$format->number(($total - $pagado) / $total * 100, 2)}}% {{$format->number(($total > 0) ? ($total - $pagado) / $total * 100 : 0, 2)}}%
</th> </th>
<th colspan="3"></th> <th colspan="3"></th>
</tr> </tr>
@ -144,9 +172,10 @@
@push('page_scripts') @push('page_scripts')
<script type="text/javascript"> <script type="text/javascript">
$(document).ready(() => { $(document).ready(() => {
function updateRow({pago_id, fecha, color, estado, remove_fecha=false, add_reject=false, disable=false}) { function updateRow({pago_id, valor, fecha, color, estado, remove_fecha=false, add_reject=false, disable=false}) {
const tr = $("tr[data-pago='" + pago_id + "']") const tr = $("tr[data-pago='" + pago_id + "']")
tr.find(':nth-child(6)').html(valor)
tr.find(':nth-child(7)').attr('class', color).html(estado) tr.find(':nth-child(7)').attr('class', color).html(estado)
if (remove_fecha) { if (remove_fecha) {
tr.find(':nth-child(8)').html(fecha) tr.find(':nth-child(8)').html(fecha)
@ -181,7 +210,7 @@
if (!json.depositado) { if (!json.depositado) {
return return
} }
updateRow({pago_id: json.pago_id, fecha: json.input.fecha, estado: 'Depositado', color: 'yellow', add_reject: true}) updateRow({pago_id: json.pago_id, valor: json.pago.valor_uf, fecha: json.input.fecha, estado: 'Depositado', color: 'yellow', add_reject: true})
button.attr('data-estado', 'depositado') button.attr('data-estado', 'depositado')
}) })
}) })
@ -189,7 +218,7 @@
function abonar(pago_id, fecha) { function abonar(pago_id, fecha) {
const url = '{{$urls->api}}/ventas/pago/' + pago_id + '/abonar' const url = '{{$urls->api}}/ventas/pago/' + pago_id + '/abonar'
const body = new FormData() const body = new FormData()
body.set('fecha', fecha.toISOString()) body.set('fecha', [fecha.getFullYear(), fecha.getMonth() + 1, fecha.getDate()].join('-'))
return fetchAPI(url, {method: 'post', body}).then(response => { return fetchAPI(url, {method: 'post', body}).then(response => {
if (!response) { if (!response) {
return return
@ -270,7 +299,7 @@
zeroRecords: 'No se encotró cuotas con ese criterio', zeroRecords: 'No se encotró cuotas con ese criterio',
search: 'Buscar: ' search: 'Buscar: '
}, },
pageLength: "25", pageLength: "100",
columnDefs: [ columnDefs: [
{ {
target: 1, target: 1,

View File

@ -81,7 +81,7 @@
tipo_id: {{$unidad->proyectoTipoUnidad->tipoUnidad->id}}, tipo_id: {{$unidad->proyectoTipoUnidad->tipoUnidad->id}},
descripcion: '{{$unidad->descripcion}}', descripcion: '{{$unidad->descripcion}}',
pid: {{$unidad->pu_id}}, pid: {{$unidad->pu_id}},
valor: {{($unidad->valor > 0) ? $unidad->valor : $unidad->precio($venta->fecha)->valor}} valor: {{($unidad->valor > 0) ? $unidad->valor : ($unidad->precio($venta->fecha)->valor ?? 0)}}
}, },
@endforeach @endforeach
], ],
@ -108,9 +108,12 @@
}, },
doAddUnidad: function() { doAddUnidad: function() {
const url = '{{$urls->api}}/ventas/propiedades/unidades/add' const url = '{{$urls->api}}/ventas/propiedades/unidades/add'
const data = new FormData(document.getElementById('add_form')) const body = new FormData(document.getElementById('add_form'))
data.set('propiedad', {{$propiedad->id}}) body.set('propiedad', {{$propiedad->id}})
return fetchAPI(url, {method: 'post', body: data}).then(response => { if (body.get('valor') === '') {
body.set('valor', '0')
}
return fetchAPI(url, {method: 'post', body}).then(response => {
if (response.ok) { if (response.ok) {
return response.json() return response.json()
} }

View File

@ -9,9 +9,12 @@ Editar Propietario
<h2 class="ui header">Editar Propietario</h2> <h2 class="ui header">Editar Propietario</h2>
<form class="ui form" id="edit_form"> <form class="ui form" id="edit_form">
<input type="hidden" name="venta_id" value="{{$venta_id}}" /> <input type="hidden" name="venta_id" value="{{$venta_id}}" />
<div class="field"> <div class="two wide field">
<label for="rut">RUT</label> <label for="rut">RUT</label>
{{$propietario->rut()}} <div class="ui right labeled input" id="rut">
<input type="text" name="rut" value="{{number_format($propietario->rut, 0, ',', '.')}}" />
<div class="ui basic label">-<span id="digito">{{$propietario->dv}}</span></div>
</div>
</div> </div>
<div class="fields"> <div class="fields">
<div class="field"> <div class="field">
@ -62,165 +65,486 @@ Editar Propietario
@push('page_scripts') @push('page_scripts')
<script type="text/javascript"> <script type="text/javascript">
function drawComunas({parent, comunas}) { class Propietario {
parent.html('') props
comunas.forEach(comuna => { constructor({rut, digito, nombres, apellidos: {paterno, materno}, direccion: {calle, numero, extra, comuna, region}}) {
const option = $('<option></option>') this.props = {
option.attr('value', comuna.id).html(comuna.descripcion) rut,
if (comuna.id === {{$propietario->datos->direccion->comuna->id}}) { digito,
option.prop('selected', true) nombres,
apellidos: {
paterno,
materno
},
direccion: {
calle,
numero,
extra,
comuna,
region
},
} }
parent.append(option) }
})
parent.show() update() {
parent.dropdown() return {
} rut: rut => {
function findComunas(direccion) { this.props.rut = rut
const original_id = $("[name='comuna']").val() this.update().digito(Rut.digito(this.props.rut))
const uri = '{{$urls->api}}/direcciones/comunas/find' },
const data = {direccion} digito: digito => {
return fetchAPI(uri, this.props.digito = digito
{method: 'post', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(data)} },
).then(response => { nombres: nombres => {
if (response.ok) { this.props.nombres = nombres
return response.json() },
apellidos: () => {
return {
paterno: paterno => {
this.props.apellidos.paterno = paterno
},
materno: materno => {
this.props.apellidos.materno = materno
}
}
},
direccion: () => {
return {
calle: calle => {
this.props.direccion.calle = calle
},
numero: numero => {
this.props.direccion.numero = numero
},
extra: extra => {
this.props.direccion.extra = extra
},
comuna: comuna => {
this.props.direccion.comuna = comuna
},
region: region => {
this.props.direccion.region = region
}
}
}
} }
}).then(data => { }
if (data.total === 0) { edit() {
const uri = '{{$urls->api}}/venta/{{$venta_id}}/propietario/edit'
const originales = {
rut: '{{trim($propietario->rut)}}',
nombres: '{{trim($propietario->nombres)}}',
apellidos: {
paterno: '{{trim($propietario->apellidos['paterno'])}}',
materno: '{{trim($propietario->apellidos['materno'])}}'
},
direccion: {
calle: '{{trim($propietario->datos->direccion->calle)}}',
numero: '{{$propietario->datos->direccion->numero}}',
extra: '{{trim($propietario->datos->direccion->extra)}}',
comuna: '{{$propietario->datos->direccion->comuna->id}}'
}
}
const data = {}
const collator = new Intl.Collator('es')
Object.entries(originales).forEach(([key, value]) => {
if (key === 'apellidos') {
Object.entries(value).forEach(([k, v]) => {
const val = this.props[key][k]
if (collator.compare(val, v) !== 0) {
data[`apellido_${k}`] = val
}
})
return
}
if (key === 'direccion') {
let changed = false
Object.entries(value).forEach(([k, v]) => {
const val = this.props[key][k]
if (collator.compare(val, v) !== 0) {
changed = true
data[k] = val
}
})
if (changed) {
Object.entries(value).forEach(([k, v]) => {
if (k in data) {
return
}
data[k] = this.props[key][k]
})
}
return
}
const val = this.props[key]
if (collator.compare(val, value) !== 0) {
data[key] = val
}
})
if (Object.keys(data).length === 0) {
// No changes
propietario.redirect()
return return
} }
const comuna_id = data.comunas[0].id if ('rut' in data) {
if (comuna_id === original_id) { data['dv'] = Rut.digito(data['rut'])
return
} }
const parent = $('#comunas') return fetchAPI(uri,
parent.dropdown('set selected', comuna_id) {method: 'put', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(data)}
}) ).then(response => {
} if (!response) {
function getComunas(region_id) { return
const parent = $('#comunas') }
parent.hide() return response.json().then(data => {
const uri = '{{$urls->api}}/direcciones/region/' + region_id + '/comunas' if (data.edited) {
return fetchAPI(uri).then(response => { propietario.redirect()
if (response.ok) { }
return response.json() })
} })
}).then(data => {
if (data.total === 0) {
return
}
drawComunas({parent, comunas: data.comunas})
})
}
function redirect() {
window.location = '{{$urls->base}}/venta/{{$venta_id}}'
}
function changeDireccion() {
const names = [
'calle',
'numero',
'extra'
]
const originals = [
'{{trim($propietario->datos->direccion->calle)}}',
'{{trim($propietario->datos->direccion->numero)}}',
'{{trim($propietario->datos->direccion->extra)}}'
]
const values = []
names.forEach(name => {
const val = $("[name='" + name + "']").val()
values.push(val)
})
const collator = new Intl.Collator('es')
if (collator.compare(originals.join(' '), values.join(' ')) !== 0) {
findComunas(values.join(' ').trim())
} }
} }
function watchChangeDireccion() { class Region {
const watched = [ props
'calle', comunas
'numero',
'extra' constructor({id, descripcion, selected = false}) {
] this.props = {
watched.forEach(name => { id,
$("[name='" + name + "']").change(event => { descripcion,
changeDireccion() selected
}
this.comunas = []
}
get() {
return {
comunas: () => {
if (this.comunas.length > 0) {
return new Promise(resolve => resolve())
}
const uri = '{{$urls->api}}/direcciones/region/' + this.props.id + '/comunas'
return fetchAPI(uri).then(response => {
if (!response) {
return
}
return response.json().then(data => {
if (data.total === 0) {
return
}
this.comunas = data.comunas.map(comuna => {
let selected = false
if (comuna.id === {{$propietario->datos->direccion->comuna->id}}) {
selected = true
}
return {
id: comuna.id,
descripcion: comuna.descripcion,
selected
}
})
})
})
}
}
}
}
class Form {
static submit(event) {
event.preventDefault()
propietario.props.data.edit()
return false
}
}
class Watcher {
static rut() {
const rut = $("[name='rut']")
rut.on('input', EventHandler.rut().update)
rut.on('blur', EventHandler.rut().change)
}
static nombres() {
$("[name='nombres']").change(event => {
propietario.props.data.update().nombres($(event.currentTarget).val())
})
}
static apellidos() {
const watched = [
'apellido_paterno',
'apellido_materno'
]
watched.forEach(name => {
$("[name='" + name + "']").change(event => {
const value = $(event.currentTarget).val()
const key = name.split('_')[1]
propietario.props.data.update().apellidos()[key](value)
})
})
}
static direccion() {
const watched = [
'calle',
'numero',
'extra'
]
watched.forEach(name => {
$("[name='" + name + "']").change(EventHandler.direccion)
})
}
static region() {
$('#region').change(EventHandler.region)
}
static comuna() {
$('#comunas').change(event => {
const comuna_id = $(event.currentTarget).val()
propietario.props.data.update().direccion().comuna(comuna_id)
})
}
static run() {
Watcher.rut()
Watcher.nombres()
Watcher.apellidos()
Watcher.direccion()
Watcher.region()
Watcher.comuna()
}
}
class EventHandler {
static region(event) {
const region_id = $(event.currentTarget).val()
propietario.props.regiones.forEach(region => {
region.props.selected = false
})
const region = propietario.props.regiones.find(region => region.props.id === region_id)
region.props.selected = true
region.get().comunas().then(() => {
propietario.draw().comunas()
})
}
static direccion(event) {
const names = [
'calle',
'numero',
'extra'
]
const originals = [
'{{trim($propietario->datos->direccion->calle)}}',
'{{trim($propietario->datos->direccion->numero)}}',
'{{trim($propietario->datos->direccion->extra)}}'
]
const values = []
names.forEach(name => {
const val = $("[name='" + name + "']").val()
propietario.props.data.update().direccion()[name](val)
values.push(val)
})
const collator = new Intl.Collator('es')
if (collator.compare(originals.join(' '), values.join(' ')) !== 0) {
propietario.find().comuna(values.join(' ').trim())
}
}
static rut() {
return {
change: event => {
const rut = $("[name='rut']").val().replace(/\D/g, '')
if (rut.length < 7) {
return
}
propietario.find().propietario(rut)
},
update: event => {
const input = $("[name='rut']")
const rut = input.val().replace(/\D/g, '')
propietario.props.data.update().rut(rut)
input.val(Rut.format(rut))
$('#digito').html(propietario.props.data.props.digito)
}
}
}
}
class Rut {
static format(rut) {
return Intl.NumberFormat('es-CL', {maximumFractionDigits: 0}).format(rut)
}
static digito(rut) {
const cleanRut = rut.replace(/\D/g, ''); // Removes non-digit characters more efficiently
let sum = 0;
const factors = [2, 3, 4, 5, 6, 7, 2, 3, 4, 5];
for (let i = 0; i < cleanRut.length; i++) {
const digit = parseInt(cleanRut[cleanRut.length - 1 - i], 10);
sum += digit * factors[i % factors.length];
}
const dv = 11 - (sum % 11);
return dv === 10 ? 'K' : dv === 11 ? '0' : dv.toString();
}
}
const propietario = {
props: {
ids: {},
data: {},
regiones: [],
},
update() {
return {
propietario: () => {
$("[name='rut']").val(Rut.format(this.props.data.props.rut))
$('#digito').html(this.props.data.props.digito)
$("[name='nombres']").val(this.props.data.props.nombres)
$("[name='apellido_paterno']").val(this.props.data.props.apellidos.paterno)
$("[name='apellido_materno']").val(this.props.data.props.apellidos.materno)
$("[name='calle']").val(this.props.data.props.direccion.calle)
$("[name='numero']").val(this.props.data.props.direccion.numero)
$("[name='extra']").val(this.props.data.props.direccion.extra)
$('#region').val(this.props.data.props.direccion.region)
this.update().region()
},
region: () => {
this.props.regiones.forEach(region => {
region.props.selected = false
})
this.props.regiones.filter(region => region.props.id === this.props.data.props.direccion.region).forEach(region => {
region.props.selected = true
})
const promises = []
this.props.regiones.filter(region => region.props.selected).forEach(region => {
promises.push(region.get().comunas())
})
Promise.all(promises).then(() => {
this.draw().comunas()
})
}
}
},
find() {
return {
propietario: rut => {
const uri = '{{$urls->api}}/ventas/propietario/' + rut
return fetchAPI(uri).then(response => {
if (!response) {
this.draw().reset()
return
}
return response.json().then(data => {
if (data.propietario === null) {
this.draw().reset()
return
}
this.props.data.update().nombres(data.propietario.nombres)
this.props.data.update().apellidos().paterno(data.propietario.apellidos.paterno)
this.props.data.update().apellidos().materno(data.propietario.apellidos.materno)
this.props.data.update().direccion().calle(data.propietario.direccion.calle)
this.props.data.update().direccion().numero(data.propietario.direccion.numero)
this.props.data.update().direccion().extra(data.propietario.direccion.extra)
this.props.data.update().direccion().comuna(data.propietario.direccion.comuna.id)
this.props.data.update().direccion().region(data.propietario.direccion.comuna.provincia.region.id)
this.update().propietario()
})
})
},
comuna: direccion => {
const original_id = $("[name='comuna']").val()
const uri = '{{$urls->api}}/direcciones/comunas/find'
const data = {direccion}
return fetchAPI(uri,
{method: 'post', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(data)}
).then(response => {
if (!response) {
return
}
return response.json().then(data => {
if (data.total === 0) {
return
}
const comuna_id = data.comunas[0].id
if (comuna_id === original_id) {
return
}
const parent = $('#comunas')
parent.dropdown('set selected', comuna_id)
})
})
}
}
},
draw() {
return {
comunas: () => {
const parent = $(this.props.ids.comuna)
parent.html('')
const region_id = $(this.props.ids.region).val()
const comunas = this.props.regiones.find(region => region.props.id === region_id).comunas
comunas.forEach(comuna => {
const option = $('<option></option>')
option.attr('value', comuna.id).html(comuna.descripcion)
if (parseInt(comuna.id) === parseInt(this.props.data.props.direccion.comuna)) {
option.prop('selected', true)
}
parent.append(option)
})
parent.show()
parent.dropdown()
},
reset: () => {
$("[name='nombres']").val('')
$("[name='apellido_paterno']").val('')
$("[name='apellido_materno']").val('')
$("[name='calle']").val('')
$("[name='numero']").val('')
$("[name='extra']").val('')
}
}
},
redirect() {
window.location = '{{$urls->base}}/venta/{{$venta_id}}'
},
setup({ids, propietario}) {
this.props.ids = ids
this.props.data = propietario
$(this.props.ids.region).dropdown()
$(this.props.ids.region).change(EventHandler.region)
$(this.props.ids.comuna).hide()
$(this.props.ids.region).find('option').each((index, element) => {
const id = $(element).val()
const descripcion = $(element).text()
const selected = $(element).prop('selected')
this.props.regiones.push(new Region({id, descripcion, selected}))
})
this.update().region()
$(this.props.ids.forms.edit).submit(Form.submit)
Watcher.run()
}
}
$(document).ready(() => {
propietario.setup({
ids: {
region: '#region',
comuna: '#comunas',
forms: {
edit: '#edit_form'
},
buttons: {
guardar: '#guardar_button'
}
},
propietario: new Propietario({
rut: '{{$propietario->rut}}',
digito: '{{$propietario->dv}}',
nombres: '{{$propietario->nombres}}',
apellidos: {
paterno: '{{$propietario->apellidos['paterno']}}',
materno: '{{$propietario->apellidos['materno']}}'
},
direccion: {
calle: '{{$propietario->datos->direccion->calle}}',
numero: '{{$propietario->datos->direccion->numero}}',
extra: '{{$propietario->datos->direccion->extra}}',
comuna: '{{$propietario->datos->direccion->comuna->id}}',
region: '{{$propietario->datos->direccion->comuna->provincia->region->id}}',
}
}) })
}) })
}
function editPropietario() {
const uri = '{{$urls->api}}/ventas/propietario/{{$propietario->rut}}'
const names = [
'nombres',
'apellido_paterno',
'apellido_materno'
]
const values = [
'{{trim($propietario->nombres)}}',
'{{trim($propietario->apellidos['paterno'])}}',
'{{trim($propietario->apellidos['materno'])}}'
]
const direccion_names = [
'calle',
'numero',
'extra',
'comuna'
]
const direccion_values = [
'{{trim($propietario->datos->direccion->calle)}}',
'{{$propietario->datos->direccion->numero}}',
'{{trim($propietario->datos->direccion->extra)}}',
'{{$propietario->datos->direccion->comuna->id}}'
]
const data = {}
const collator = new Intl.Collator('es')
names.forEach((name, index) => {
const val = $("[name='" + name + "']").val()
if (collator.compare(val, values[index]) !== 0) {
console.debug(name, val, values[index], collator.compare(val, values[index]))
data[name] = val
}
})
direccion_names.forEach((name, index) => {
const val = $("[name='" + name + "']").val()
if (collator.compare(val, direccion_values[index]) !== 0) {
if (typeof data['direccion'] === 'undefined') {
data['direccion'] = {}
}
console.debug(name, val, direccion_values[index])
data['direccion'][name] = val
}
})
if (Object.keys(data).length === 0) {
redirect()
return
}
return fetchAPI(uri,
{method: 'put', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(data)}
).then(response => {
if (response.ok) {
redirect()
}
})
}
$(document).ready(() => {
const regiones = $("select[name='region']")
regiones.dropdown()
regiones.change(event => {
const region_id = $(event.currentTarget).val()
getComunas(region_id)
})
$('#comunas').hide()
getComunas({{$propietario->datos->direccion->comuna->provincia->region->id}})
$('#edit_form').submit(event => {
event.preventDefault()
editPropietario({{$propietario->rut}})
return false
})
$('#guardar_button').click(event => {
editPropietario({{$propietario->rut}})
})
watchChangeDireccion()
}) })
</script> </script>
@endpush @endpush

View File

@ -3,9 +3,9 @@
COMENTARIOS COMENTARIOS
</div> </div>
<div class="right aligned column"> <div class="right aligned column">
<a href="javascript: addComment()" style="color: inherit;"> <button class="ui icon button" style="background: none; color: inherit; padding: 0;" id="add_comentario_button">
<i class="plus icon"></i> <i class="plus icon"></i>
</a> </button>
</div> </div>
</div> </div>
<div class="ui segment"> <div class="ui segment">
@ -13,37 +13,199 @@
<tbody id="comentarios"></tbody> <tbody id="comentarios"></tbody>
</table> </table>
</div> </div>
<div class="ui modal" id="addComment">
<div class="header">
Agregar Comentario
</div>
<div class="content">
<form class="ui form">
<div class="three wide field">
<label>Fecha</label>
<div class="ui calendar" id="fecha">
<div class="ui icon input">
<input type="text" placeholder="Fecha">
<i class="calendar icon"></i>
</div>
</div>
</div>
<div class="field">
<label>Comentario</label>
<textarea id="comentario" rows="2"></textarea>
</div>
</form>
</div>
<div class="actions">
<div class="ui black deny button">
Cancelar
</div>
<div class="ui positive right labeled icon button">
Agregar
<i class="checkmark icon"></i>
</div>
</div>
</div>
<div class="ui modal" id="editComment">
<div class="header">
Edit Comentario
</div>
<div class="content">
<form class="ui form">
<input type="hidden" name="id" />
<div class="three wide field">
<label>Fecha</label>
<div class="ui calendar" id="fechaEdit">
<div class="ui icon input">
<input type="text" placeholder="Fecha">
<i class="calendar icon"></i>
</div>
</div>
</div>
<div class="field">
<label>Comentario</label>
<textarea id="comentarioEdit" rows="2"></textarea>
</div>
</form>
</div>
<div class="actions">
<div class="ui black deny button">
Cancelar
</div>
<div class="ui positive right labeled icon button">
Editar
<i class="checkmark icon"></i>
</div>
</div>
</div>
@push('page_scripts') @push('page_scripts')
<script type="text/javascript"> <script type="text/javascript">
class Comentario class Comentario {
{ id
fecha fecha
texto texto
constructor({fecha, texto}) constructor({id, fecha, texto})
{ {
this.id = id
this.fecha = new Date(fecha + 'T00:00:00') this.fecha = new Date(fecha + 'T00:00:00')
this.texto = texto this.texto = texto
} }
draw(dateFormatter) draw(dateFormatter)
{ {
return $('<tr></tr>').append( return [
$('<td></td>').html(dateFormatter.format(this.fecha)) '<tr>',
).append( `<td class="collapsing">${dateFormatter.format(this.fecha)}</td>`,
$('<td></td>').html(this.texto) `<td>${this.texto}</td>`,
).append( '<td class="right aligned">',
$('<td></td>').addClass('right aligned').append( `<button class="ui tiny tertiary icon button editComentario" data-id="${this.id}">`,
$('<a></a>').attr('href', 'javascript: removeComment();').append( '<i class="edit icon"></i>',
$('<i></i>').addClass('minus icon') '</button>',
) `<button class="ui tiny tertiary red icon button removeComentario" data-id="${this.id}">`,
) '<i class="minus icon"></i>',
) '</button>',
'</td>',
'</tr>'
].join("\n")
}
}
class AddModal {
props
constructor({id}) {
this.props = {
id
}
$(this.props.id).modal({
onApprove: () => {
this.approve()
}
})
const cdo = structuredClone(calendar_date_options)
$(this.props.id).find('.ui.calendar').calendar(cdo)
this.hide()
}
approve() {
const fecha = $(this.props.id).find('#fecha').calendar('get date')
const fechaString = [fecha.getFullYear(), fecha.getMonth()+1, fecha.getDate()].join('-')
const comentario = $(this.props.id).find('#comentario').val()
const uri = '{{$urls->api}}/venta/{{$venta->id}}/comentarios/add'
const body = new FormData()
body.append('fecha', fechaString)
body.append('texto', comentario)
fetchAPI(uri, {method: 'post', body}).then(response => {
if (!response) {
return
}
return response.json().then(data => {
if (data.added) {
comentarios.comentarios.push(new Comentario(data.comentario))
comentarios.draw().comentarios()
}
})
})
}
show() {
const modal = $(this.props.id)
modal.find('form').trigger('reset')
modal.modal('show')
}
hide() {
const modal = $(this.props.id)
modal.modal('hide')
}
}
class EditModal {
props
constructor({id}) {
this.props = {
id
}
$(this.props.id).modal({
onApprove: () => {
this.approve()
}
})
const cdo = structuredClone(calendar_date_options)
$(this.props.id).find('.ui.calendar').calendar(cdo)
this.hide()
}
approve() {
const id = $(this.props.id).find("[name='id']").val()
const fecha = $(this.props.id).find('#fechaEdit').calendar('get date')
const fechaString = [fecha.getFullYear(), fecha.getMonth()+1, fecha.getDate()].join('-')
const comentario = $(this.props.id).find('#comentarioEdit').val()
const uri = `{{$urls->api}}/ventas/comentario/${id}/edit`
const body = new FormData()
body.append('fecha', fechaString)
body.append('texto', comentario)
fetchAPI(uri, {method: 'post', body}).then(response => {
if (!response) {
return
}
return response.json().then(data => {
if (data.edited) {
const idx = comentarios.comentarios.findIndex(comentario => comentario.id === data.comentario_id)
comentarios.comentarios[idx] = new Comentario(data.comentario)
comentarios.draw().comentarios()
}
})
})
}
show() {
const modal = $(this.props.id)
modal.modal('show')
}
hide() {
const modal = $(this.props.id)
modal.modal('hide')
} }
} }
const comentarios = { const comentarios = {
comentarios: [], comentarios: [],
id: '', id: '',
modals: {
add: null,
edit: null
},
fetch: function() { fetch: function() {
return { return {
comentarios: () => { comentarios: () => {
@ -60,6 +222,7 @@
this.comentarios.push(new Comentario(settings)) this.comentarios.push(new Comentario(settings))
}) })
this.draw().comentarios() this.draw().comentarios()
}) })
} }
} }
@ -73,11 +236,41 @@
this.comentarios.forEach(comentario => { this.comentarios.forEach(comentario => {
body.append(comentario.draw(dateFormatter)) body.append(comentario.draw(dateFormatter))
}) })
$('.editComentario').on('click', event => {
const id = $(event.currentTarget).data('id')
const comentario = this.comentarios.find(comentario => comentario.id === id)
const modal = this.modals.edit
const fecha = new Date(comentario.fecha)
$(modal.props.id).find("[name='id']").val(id)
$(modal.props.id).find('#fechaEdit').calendar('set date', fecha)
$(modal.props.id).find('#comentarioEdit').val(comentario.texto)
modal.show()
})
$('.removeComentario').click(event => {
const id = $(event.currentTarget).data('id')
const uri = `{{$urls->api}}/ventas/comentario/${id}/remove`
fetchAPI(uri, {method: 'delete'}).then(response => {
if (!response) {
return
}
return response.json().then(data => {
if (data.removed) {
this.comentarios = this.comentarios.filter(comentario => comentario.id !== id)
this.draw().comentarios()
}
})
})
})
} }
} }
}, },
setup: function(id) { setup: function(id) {
this.id = id this.id = id
this.modals.add = new AddModal({id: '#addComment'})
this.modals.edit = new EditModal({id: '#editComment'})
$('#add_comentario_button').click(() => {
this.modals.add.show()
})
this.fetch().comentarios() this.fetch().comentarios()
} }
} }

View File

@ -26,7 +26,7 @@
<tr> <tr>
<td>{{$format->ufs($venta->valor)}}</td> <td>{{$format->ufs($venta->valor)}}</td>
<td>{{$format->ufs($venta->util())}}</td> <td>{{$format->ufs($venta->util())}}</td>
<td>{{$format->number($venta->util() / $venta->propiedad()->vendible(), 2)}} UF/</td> <td>{{($venta->propiedad()->vendible() > 0) ? $format->number($venta->util() / $venta->propiedad()->vendible(), 2) : 0}} UF/</td>
<td>0,00 UF (0,00%)</td> <td>0,00 UF (0,00%)</td>
<td> <td>
{{$venta->fecha->format('d-m-Y')}}<br/> {{$venta->fecha->format('d-m-Y')}}<br/>

View File

@ -50,7 +50,7 @@
</td> </td>
<td class="right aligned"> <td class="right aligned">
@if ($unidad->proyectoTipoUnidad->tipoUnidad->descripcion === 'departamento') @if ($unidad->proyectoTipoUnidad->tipoUnidad->descripcion === 'departamento')
{{$format->number(($unidad->valor ?? $precio) / $unidad->proyectoTipoUnidad->vendible(), 2)}} UF/ {{$format->number((($unidad->valor === null or $unidad->valor === 0.0) ? $precio : $unidad->valor) / $unidad->proyectoTipoUnidad->vendible(), 2)}} UF/
@endif @endif
</td> </td>
<td class="center aligned"> <td class="center aligned">

View File

@ -1,2 +1,2 @@
<?php <?php
//$app->add($app->getContainer()->get(Incoviba\Middleware\Authentication::class)); $app->add($app->getContainer()->get(Incoviba\Middleware\Authentication::class));

View File

@ -0,0 +1,2 @@
<?php
$app->add($app->getContainer()->get(Incoviba\Middleware\CORS::class));

View File

@ -17,5 +17,13 @@ return [
'images' 'images'
]); ]);
return (object) $urls; return (object) $urls;
} },
'permittedPaths' => [
'/api',
'/api/',
],
'simplePaths' => [
'/api/login',
'/api/login/',
],
]; ];

View File

@ -4,18 +4,6 @@ use Psr\Container\ContainerInterface;
return [ return [
Psr\Log\LoggerInterface::class => function(ContainerInterface $container) { Psr\Log\LoggerInterface::class => function(ContainerInterface $container) {
return new Monolog\Logger('incoviba', [ return new Monolog\Logger('incoviba', [
new Monolog\Handler\FilterHandler(
(new Monolog\Handler\RotatingFileHandler('/logs/debug.log', 10))
->setFormatter(new Monolog\Formatter\LineFormatter(null, null, false, false, true)),
Monolog\Level::Debug,
Monolog\Level::Debug
),
new Monolog\Handler\FilterHandler(
(new Monolog\Handler\RotatingFileHandler('/logs/info.log', 10))
->setFormatter(new Monolog\Formatter\LineFormatter(null, null, false, false, true)),
Monolog\Level::Info,
Monolog\Level::Warning,
),
new Monolog\Handler\FilterHandler( new Monolog\Handler\FilterHandler(
(new Monolog\Handler\RotatingFileHandler('/logs/error.log', 10)) (new Monolog\Handler\RotatingFileHandler('/logs/error.log', 10))
->setFormatter(new Monolog\Formatter\LineFormatter(null, null, false, false, true)), ->setFormatter(new Monolog\Formatter\LineFormatter(null, null, false, false, true)),
@ -26,13 +14,25 @@ return [
(new Monolog\Handler\RotatingFileHandler('/logs/critical.log', 10)) (new Monolog\Handler\RotatingFileHandler('/logs/critical.log', 10))
->setFormatter(new Monolog\Formatter\LineFormatter(null, null, false, false, true)), ->setFormatter(new Monolog\Formatter\LineFormatter(null, null, false, false, true)),
Monolog\Level::Critical Monolog\Level::Critical
),
new Monolog\Handler\FilterHandler(
new Monolog\Handler\RedisHandler($container->get(Predis\ClientInterface::class), 'logs:notices'),
Monolog\Level::Notice,
Monolog\Level::Warning
),
new Monolog\Handler\FilterHandler(
(new Incoviba\Common\Implement\Log\MySQLHandler($container->get(Incoviba\Common\Define\Connection::class)))
->setFormatter(new Incoviba\Common\Implement\Log\PDOFormatter()),
Monolog\Level::Debug,
Monolog\Level::Warning
) )
], [ ], [
$container->get(Monolog\Processor\PsrLogMessageProcessor::class), $container->get(Incoviba\Common\Implement\Log\UserProcessor::class),
$container->get(Monolog\Processor\WebProcessor::class),
$container->get(Monolog\Processor\IntrospectionProcessor::class), $container->get(Monolog\Processor\IntrospectionProcessor::class),
$container->get(Monolog\Processor\WebProcessor::class),
$container->get(Monolog\Processor\MemoryUsageProcessor::class), $container->get(Monolog\Processor\MemoryUsageProcessor::class),
$container->get(Monolog\Processor\MemoryPeakUsageProcessor::class) $container->get(Monolog\Processor\MemoryPeakUsageProcessor::class),
$container->get(Monolog\Processor\PsrLogMessageProcessor::class),
]); ]);
} }
]; ];

View File

@ -14,6 +14,11 @@ return [
Incoviba\Middleware\API::class => function(ContainerInterface $container) { Incoviba\Middleware\API::class => function(ContainerInterface $container) {
return new Incoviba\Middleware\API( return new Incoviba\Middleware\API(
$container->get(Psr\Http\Message\ResponseFactoryInterface::class), $container->get(Psr\Http\Message\ResponseFactoryInterface::class),
$container->get(Psr\Log\LoggerInterface::class),
$container->get(Incoviba\Service\API::class),
$container->get(Incoviba\Service\Login::class),
$container->get('permittedPaths'),
$container->get('simplePaths'),
$container->get('API_KEY') $container->get('API_KEY')
); );
} }

View File

@ -6,10 +6,12 @@ return [
Incoviba\Service\Login::class => function(ContainerInterface $container) { Incoviba\Service\Login::class => function(ContainerInterface $container) {
return new Incoviba\Service\Login( return new Incoviba\Service\Login(
$container->get(Incoviba\Repository\Login::class), $container->get(Incoviba\Repository\Login::class),
$container->get(Incoviba\Repository\User::class),
$container->get('COOKIE_NAME'), $container->get('COOKIE_NAME'),
$container->get('MAX_LOGIN_HOURS'), $container->get('MAX_LOGIN_HOURS'),
$container->has('COOKIE_DOMAIN') ? $container->get('COOKIE_DOMAIN') : '', $container->has('COOKIE_DOMAIN') ? $container->get('COOKIE_DOMAIN') : '',
$container->has('COOKIE_PATH') ? $container->get('COOKIE_PATH') : '' $container->has('COOKIE_PATH') ? $container->get('COOKIE_PATH') : '',
$container->has('COOKIE_SEPARATOR') ? $container->get('COOKIE_SEPARATOR') : 'g'
); );
}, },
Incoviba\Service\Money::class => function(ContainerInterface $container) { Incoviba\Service\Money::class => function(ContainerInterface $container) {
@ -25,39 +27,40 @@ return [
->register('usd', $mindicador) ->register('usd', $mindicador)
->register('ipc', $ine); ->register('ipc', $ine);
}, },
Predis\Client::class => function(ContainerInterface $container) { Predis\ClientInterface::class => function(ContainerInterface $container) {
return new Predis\Client([ return new Predis\Client([
'scheme' => 'tcp', 'scheme' => 'tcp',
'host' => $container->get('REDIS_HOST'), 'host' => $container->get('REDIS_HOST'),
'port' => $container->get('REDIS_PORT') 'port' => $container->get('REDIS_PORT')
]); ]);
}, },
Incoviba\Service\Cartola::class => function(ContainerInterface $container) { Incoviba\Service\Contabilidad\Cartola::class => function(ContainerInterface $container) {
return (new Incoviba\Service\Cartola( return (new Incoviba\Service\Contabilidad\Cartola(
$container->get(Psr\Log\LoggerInterface::class), $container->get(Psr\Log\LoggerInterface::class),
$container->get(Psr\Http\Message\StreamFactoryInterface::class), $container->get(Psr\Http\Message\StreamFactoryInterface::class),
$container->get(Incoviba\Common\Define\Contabilidad\Exporter::class), $container->get(Incoviba\Common\Define\Contabilidad\Exporter::class),
$container->get(Incoviba\Repository\Inmobiliaria::class), $container->get(Incoviba\Repository\Inmobiliaria::class),
$container->get(Incoviba\Repository\Inmobiliaria\Cuenta::class), $container->get(Incoviba\Repository\Inmobiliaria\Cuenta::class),
$container->get(Incoviba\Repository\Movimiento::class), $container->get(Incoviba\Repository\Contabilidad\Movimiento::class),
$container->get(Incoviba\Service\Movimiento::class), $container->get(Incoviba\Service\Contabilidad\Movimiento::class),
$container->get(Incoviba\Repository\Cartola::class) $container->get(Incoviba\Repository\Contabilidad\Cartola::class)
)) ))
->register('security', $container->get(Incoviba\Service\Cartola\Security::class)) ->register('security', $container->get(Incoviba\Service\Contabilidad\Cartola\Security::class))
->register('itau', $container->get(Incoviba\Service\Cartola\Itau::class)) ->register('itau', $container->get(Incoviba\Service\Contabilidad\Cartola\Itau::class))
->register('santander', $container->get(Incoviba\Service\Cartola\Santander::class)); ->register('santander', $container->get(Incoviba\Service\Contabilidad\Cartola\Santander::class))
->register('bci', $container->get(Incoviba\Service\Contabilidad\Cartola\BCI::class));
}, },
Incoviba\Common\Define\Contabilidad\Exporter::class => function(ContainerInterface $container) { Incoviba\Common\Define\Contabilidad\Exporter::class => function(ContainerInterface $container) {
return $container->get(Incoviba\Service\Contabilidad\Exporter\Nubox::class); return $container->get(Incoviba\Service\Contabilidad\Exporter\Nubox::class);
}, },
Incoviba\Service\Contabilidad\Exporter\Nubox::class => function(ContainerInterface $container) { Incoviba\Service\Contabilidad\Exporter\Nubox::class => function(ContainerInterface $container) {
return new Incoviba\Service\Contabilidad\Exporter\Nubox($container->get(Incoviba\Repository\CentroCosto::class), return new Incoviba\Service\Contabilidad\Exporter\Nubox($container->get(Incoviba\Repository\Contabilidad\CentroCosto::class),
$container->get('folders')->get('uploads')); $container->get('folders')->get('uploads'));
}, },
Incoviba\Service\Contabilidad\Nubox::class => function(ContainerInterface $container) { Incoviba\Service\Contabilidad\Nubox::class => function(ContainerInterface $container) {
return new Incoviba\Service\Contabilidad\Nubox( return new Incoviba\Service\Contabilidad\Nubox(
$container->get(Psr\Log\LoggerInterface::class), $container->get(Psr\Log\LoggerInterface::class),
$container->get(Incoviba\Repository\Nubox::class), $container->get(Incoviba\Repository\Contabilidad\Nubox::class),
$container->get(Incoviba\Service\Redis::class), $container->get(Incoviba\Service\Redis::class),
new GuzzleHttp\Client(), new GuzzleHttp\Client(),
$container->get(Psr\Http\Message\RequestFactoryInterface::class), $container->get(Psr\Http\Message\RequestFactoryInterface::class),
@ -70,12 +73,24 @@ return [
) )
->register('xlsx', Incoviba\Service\Informe\Excel::class); ->register('xlsx', Incoviba\Service\Informe\Excel::class);
}, },
Incoviba\Service\Contabilidad\Informe\Tesoreria\Excel::class => function(ContainerInterface $container) { Incoviba\Service\Contabilidad\Informe\Tesoreria\Output\Excel::class => function(ContainerInterface $container) {
return new Incoviba\Service\Contabilidad\Informe\Tesoreria\Excel( return new Incoviba\Service\Contabilidad\Informe\Tesoreria\Output\Excel(
$container->get(Psr\Log\LoggerInterface::class), $container->get(Psr\Log\LoggerInterface::class),
$container->get('folders')->get('informes'), $container->get('folders')->get('informes'),
$container->get(Incoviba\Service\UF::class), $container->get(Incoviba\Service\UF::class),
$container->get(Incoviba\Service\USD::class) $container->get(Incoviba\Service\USD::class)
); );
},
Incoviba\Service\Contabilidad\Cartola\Santander::class => function(ContainerInterface $container) {
return (new Incoviba\Service\Contabilidad\Cartola\Santander(
$container->get(Psr\Log\LoggerInterface::class),
))
->registerSub($container->get(Incoviba\Service\Contabilidad\Cartola\Santander\OfficeBanking::class));
},
Incoviba\Service\Contabilidad\Cartola\BCI::class => function(ContainerInterface $container) {
return (new Incoviba\Service\Contabilidad\Cartola\BCI(
$container->get(Psr\Log\LoggerInterface::class),
))
->registerSub($container->get(Incoviba\Service\Contabilidad\Cartola\BCI\Mes::class));
} }
]; ];

View File

@ -0,0 +1,30 @@
<?php
namespace Incoviba\Controller\API\Admin;
use Exception;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Incoviba\Common\Implement\Exception\EmptyResult;
use Incoviba\Controller\API\withJson;
use Incoviba\Service;
class Users
{
use withJson;
public function add(ServerRequestInterface $request, ResponseInterface $response, Service\Login $loginService): ResponseInterface
{
$body = $request->getParsedBody();
$output = [
'input' => array_filter($body, fn($key) => $key !== 'password', ARRAY_FILTER_USE_KEY),
'success' => false,
'user' => null
];
try {
$user = $loginService->addUser($body);
$output['success'] = true;
$output['user'] = $user;
} catch (EmptyResult) {}
return $this->withJson($response, $output);
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace Incoviba\Controller\API;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Incoviba\Common\Ideal\Controller;
class Base extends Controller
{
use withJson;
public function __invoke(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
{
$output = [
'version' => '2.0.0',
'organization' => 'Ingenieria y Construccion Vial Balmaceda Sociedad Anonima'
];
return $this->withJson($response, $output);
}
}

View File

@ -1,10 +1,9 @@
<?php <?php
namespace Incoviba\Controller\API\Contabilidad; namespace Incoviba\Controller\API\Contabilidad;
use DateTimeInterface;
use DateTimeImmutable; use DateTimeImmutable;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Incoviba\Common\Ideal\Controller; use Incoviba\Common\Ideal\Controller;
use Incoviba\Common\Implement\Exception\EmptyResult; use Incoviba\Common\Implement\Exception\EmptyResult;
use Incoviba\Controller\API\withJson; use Incoviba\Controller\API\withJson;
@ -15,10 +14,10 @@ class Cartolas extends Controller
{ {
use withJson; use withJson;
public function procesar(ServerRequestInterface $request, ResponseInterface $response, public function procesar(ServerRequestInterface $request, ResponseInterface $response,
Repository\Inmobiliaria $inmobiliariaRepository, Repository\Inmobiliaria $inmobiliariaRepository,
Repository\Banco $bancoRepository, Repository\Contabilidad\Banco $bancoRepository,
Service\Cartola $cartolaService): ResponseInterface Service\Contabilidad\Cartola $cartolaService): ResponseInterface
{ {
$body = $request->getParsedBody(); $body = $request->getParsedBody();
$output = [ $output = [
@ -30,14 +29,14 @@ class Cartolas extends Controller
$banco = $bancoRepository->fetchById($body['banco']); $banco = $bancoRepository->fetchById($body['banco']);
$mes = new DateTimeImmutable($body['mes']); $mes = new DateTimeImmutable($body['mes']);
$file = $request->getUploadedFiles()['file']; $file = $request->getUploadedFiles()['file'];
$output['movimientos'] = $cartolaService->process($inmobiliaria, $banco, $mes, $file); $output['movimientos'] = $cartolaService->process($banco, $file);
} catch (EmptyResult) {} } catch (EmptyResult) {}
return $this->withJson($response, $output); return $this->withJson($response, $output);
} }
public function exportar(ServerRequestInterface $request, ResponseInterface $response, public function exportar(ServerRequestInterface $request, ResponseInterface $response,
Repository\Inmobiliaria $inmobiliariaRepository, Repository\Inmobiliaria $inmobiliariaRepository,
Repository\Banco $bancoRepository, Repository\Contabilidad\Banco $bancoRepository,
Service\Cartola $cartolaService): ResponseInterface Service\Contabilidad\Cartola $cartolaService): ResponseInterface
{ {
$body = $request->getParsedBody(); $body = $request->getParsedBody();
$output = [ $output = [
@ -52,9 +51,9 @@ class Cartolas extends Controller
} catch (EmptyResult) {} } catch (EmptyResult) {}
return $this->withJson($response, $output); return $this->withJson($response, $output);
} }
public function diaria(ServerRequestInterface $request, ResponseInterface $response, public function diaria(ServerRequestInterface $request, ResponseInterface $response,
Repository\Inmobiliaria\Cuenta $cuentaRepository, Repository\Inmobiliaria\Cuenta $cuentaRepository,
Service\Cartola $cartolaService): ResponseInterface Service\Contabilidad\Cartola $cartolaService): ResponseInterface
{ {
$body = $request->getParsedBody(); $body = $request->getParsedBody();
$output = [ $output = [
@ -80,9 +79,9 @@ class Cartolas extends Controller
} }
return $this->withJson($response, $output); return $this->withJson($response, $output);
} }
public function ayer(ServerRequestInterface $request, ResponseInterface $response, public function ayer(ServerRequestInterface $request, ResponseInterface $response,
Repository\Inmobiliaria\Cuenta $cuentaRepository, Repository\Inmobiliaria\Cuenta $cuentaRepository,
Repository\Cartola $cartolaRepository): ResponseInterface Repository\Contabilidad\Cartola $cartolaRepository): ResponseInterface
{ {
$body = $request->getParsedBody(); $body = $request->getParsedBody();
$output = [ $output = [
@ -96,4 +95,62 @@ class Cartolas extends Controller
} catch (EmptyResult) {} } catch (EmptyResult) {}
return $this->withJson($response, $output); return $this->withJson($response, $output);
} }
public function importar(ServerRequestInterface $request, ResponseInterface $response,
Service\Contabilidad\Cartola $cartolaService): ResponseInterface
{
$body = $request->getParsedBody();
$files = $request->getUploadedFiles();
$output = [
'input' => $body,
'movimientos' => [],
'errors' => []
];
$errors = [
UPLOAD_ERR_OK => 'Ok',
UPLOAD_ERR_INI_SIZE => 'El archivo excede el tamaño máximo permitido',
UPLOAD_ERR_FORM_SIZE => 'El archivo excede el tamaño máximo permitido',
UPLOAD_ERR_PARTIAL => 'El archivo fue subido parcialmente',
UPLOAD_ERR_NO_FILE => 'No se subió ningún archivo',
UPLOAD_ERR_NO_TMP_DIR => 'Falta la carpeta temporal',
UPLOAD_ERR_CANT_WRITE => 'No se pudo escribir el archivo',
UPLOAD_ERR_EXTENSION => 'Una extensión de PHP detuvo la subida del archivo'
];
if (is_array($files['file'])) {
foreach ($files['file'] as $i => $file) {
if ($file->getError() !== UPLOAD_ERR_OK) {
$output['errors'] []= ['filename' => $file->getClientFilename(), 'error' => $errors[$file->getError()]];
continue;
}
try {
$output['movimientos'] = array_merge($output['movimientos'], $cartolaService->import($body['cuenta_id'][$i], $file));
} catch (EmptyResult) {}
}
} else {
$file = $files['file'];
if ($file->getError() !== UPLOAD_ERR_OK) {
$output['errors'][] = ['filename' => $file->getClientFilename(), 'error' => $errors[$file->getError()]];
} else {
try {
$output['movimientos'] = $cartolaService->import($body['cuenta_id'], $file);
} catch (EmptyResult) {}
}
}
return $this->withJson($response, $output);
}
public function update(ServerRequestInterface $request, ResponseInterface $response,
Service\Contabilidad\Cartola $cartolaService): ResponseInterface
{
$output = [
'cartolas' => []
];
try {
$cuentas = $cartolaService->check();
if (count($cuentas) > 0) {
$output['cartolas'] = $cartolaService->update($cuentas);
}
} catch (EmptyResult) {}
return $this->withJson($response, $output);
}
} }

View File

@ -0,0 +1,63 @@
<?php
namespace Incoviba\Controller\API\Contabilidad;
use Incoviba\Common\Implement\Exception\EmptyResult;
use Incoviba\Controller\API\withJson;
use Incoviba\Repository;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
class CentrosCostos
{
use withJson;
public function add(ServerRequestInterface $request, ResponseInterface $response,
Repository\Contabilidad\CentroCosto $centroCostoRepository): ResponseInterface
{
$body = $request->getParsedBody();
$output = [
'input' => $body,
'added' => false
];
try {
$centroCosto = $centroCostoRepository->create($body);
$centroCosto->id = $body['id'];
$centroCostoRepository->save($centroCosto);
$output['added'] = true;
} catch (EmptyResult) {}
return $this->withJson($response, $output);
}
public function edit(ServerRequestInterface $request, ResponseInterface $response,
Repository\Contabilidad\CentroCosto $centroCostoRepository, int $centro_costo_id): ResponseInterface
{
$body = $request->getParsedBody();
$output = [
'centro_costo_id' => $centro_costo_id,
'input' => $body,
'edited' => false
];
try {
$centroCosto = $centroCostoRepository->fetchById($centro_costo_id);
if ($body['tipo_cuenta_id'] === '') {
$body['tipo_cuenta_id'] = null;
}
$centroCostoRepository->edit($centroCosto, $body);
$output['edited'] = true;
} catch (EmptyResult) {}
return $this->withJson($response, $output);
}
public function remove(ServerRequestInterface $request, ResponseInterface $response,
Repository\Contabilidad\CentroCosto $centroCostoRepository, int $centro_costo_id): ResponseInterface
{
$output = [
'centro_costo_id' => $centro_costo_id,
'removed' => false
];
try {
$centroCosto = $centroCostoRepository->fetchById($centro_costo_id);
$centroCostoRepository->remove($centroCosto);
$output['removed'] = true;
} catch (EmptyResult) {}
return $this->withJson($response, $output);
}
}

View File

@ -2,23 +2,23 @@
namespace Incoviba\Controller\API\Contabilidad; namespace Incoviba\Controller\API\Contabilidad;
use DateTimeImmutable; use DateTimeImmutable;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;
use Incoviba\Common\Ideal; use Incoviba\Common\Ideal;
use Incoviba\Common\Implement; use Incoviba\Common\Implement;
use Incoviba\Controller\API\withJson; use Incoviba\Controller\API\withJson;
use Incoviba\Repository; use Incoviba\Repository;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
class Depositos extends Ideal\Controller class Depositos extends Ideal\Controller
{ {
use withJson; use withJson;
public function inmobiliaria(ServerRequestInterface $request, ResponseInterface $response, public function inmobiliaria(ServerRequestInterface $request, ResponseInterface $response,
Repository\Inmobiliaria $inmobiliariaRepository, Repository\Inmobiliaria $inmobiliariaRepository,
Repository\Banco $bancoRepository, Repository\Contabilidad\Banco $bancoRepository,
Repository\Inmobiliaria\Cuenta $cuentaRepository, Repository\Inmobiliaria\Cuenta $cuentaRepository,
Repository\Deposito $dapRepository, Repository\Contabilidad\Deposito $dapRepository,
int $inmobiliaria_rut): ResponseInterface int $inmobiliaria_rut): ResponseInterface
{ {
$output = [ $output = [
'inmobiliaria_rut' => $inmobiliaria_rut, 'inmobiliaria_rut' => $inmobiliaria_rut,
@ -33,10 +33,10 @@ class Depositos extends Ideal\Controller
} catch (Implement\Exception\EmptyResult) {} } catch (Implement\Exception\EmptyResult) {}
return $this->withJson($response, $output); return $this->withJson($response, $output);
} }
public function add(ServerRequestInterface $request, ResponseInterface $response, public function add(ServerRequestInterface $request, ResponseInterface $response,
Repository\Inmobiliaria $inmobiliariaRepository, Repository\Banco $bancoRepository, Repository\Inmobiliaria $inmobiliariaRepository, Repository\Contabilidad\Banco $bancoRepository,
Repository\Inmobiliaria\Cuenta $cuentaRepository, Repository\Inmobiliaria\Cuenta $cuentaRepository,
Repository\Deposito $dapRepository): ResponseInterface Repository\Contabilidad\Deposito $dapRepository): ResponseInterface
{ {
$body = $request->getParsedBody(); $body = $request->getParsedBody();
$output = [ $output = [

View File

@ -1,11 +1,12 @@
<?php <?php
namespace Incoviba\Controller\API\Contabilidad; namespace Incoviba\Controller\API\Contabilidad;
use DateTimeImmutable;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ServerRequestInterface;
use Incoviba\Common\Ideal;
use Incoviba\Common\Implement\Exception\EmptyResult; use Incoviba\Common\Implement\Exception\EmptyResult;
use Incoviba\Controller\API\withJson; use Incoviba\Controller\API\withJson;
use Incoviba\Common\Ideal;
use Incoviba\Repository; use Incoviba\Repository;
use Incoviba\Service; use Incoviba\Service;
@ -13,38 +14,95 @@ class Movimientos extends Ideal\Controller
{ {
use withJson; use withJson;
public function detalles(ServerRequestInterface $request, ResponseInterface $response, public function detalles(ServerRequestInterface $request, ResponseInterface $response,
Service\Movimiento $movimientoService, Service\Contabilidad\Movimiento $movimientoService,
Repository\CentroCosto $centroCostoRepository, int $movimiento_id): ResponseInterface Repository\Contabilidad\CentroCosto $centroCostoRepository, int $movimiento_id): ResponseInterface
{ {
$body = $request->getParsedBody(); $body = $request->getParsedBody();
$output = [ $output = [
'movimiento_id' => $movimiento_id, 'movimiento_id' => $movimiento_id,
'input' => $body, 'input' => $body,
'status' => false, 'status' => false,
'movimiento' => null, 'movimiento' => null
'centro' => null,
'detalle' => ''
]; ];
try { try {
$movimiento = $movimientoService->getById($movimiento_id); $movimiento = $movimientoService->getById($movimiento_id);
$output['movimiento'] = $movimiento; $output['movimiento'] = $this->movimientosToArray([$movimiento])[0];
$data = []; $data = [];
if (isset($body['centro_id'])) { $fieldMap = [
$centro = $centroCostoRepository->fetchById($body['centro_id']); 'centro_costo_id',
$data['centro_costo_id'] = $centro->id; 'categoria',
} 'detalle',
if (isset($body['detalle'])) { 'rut',
$data['detalle'] = $body['detalle']; 'digito',
'nombres',
'identificador'
];
foreach ($fieldMap as $field) {
if (key_exists($field, $body)) {
$data[$field] = $body[$field];
}
} }
$movimientoService->setDetalles($movimiento, $data); $movimientoService->setDetalles($movimiento, $data);
if (isset($body['centro_id'])) { $output['movimiento'] = $this->movimientosToArray([$movimientoService->getById($movimiento->id)])[0];
$output['centro'] = $centro; $output['status'] = true;
} } catch (EmptyResult) {}
if (isset($body['detalle'])) { return $this->withJson($response, $output);
$output['detalle'] = $body['detalle']; }
public function __invoke(ServerRequestInterface $request, ResponseInterface $response, Service\Contabilidad\Movimiento $movimientoService): ResponseInterface
{
$output = [
'movimientos' => []
];
try {
$movimientos = $movimientoService->getAll();
$output['movimientos'] = $this->movimientosToArray($movimientos);
} catch (EmptyResult) {}
return $this->withJson($response, $output);
}
public function segment(ServerRequestInterface $request, ResponseInterface $response,
Service\Contabilidad\Movimiento $movimientoService): ResponseInterface
{
$input = $request->getParsedBody();
$output = [
'input' => $input,
'movimientos' => []
];
try {
$movimientos = $movimientoService->getAmountStartingFrom($input['from'], $input['amount']);
$output['movimientos'] = $this->movimientosToArray($movimientos);
} catch (EmptyResult) {}
return $this->withJson($response, $output);
}
public function sociedad(ServerRequestInterface $request, ResponseInterface $response,
Service\Contabilidad\Movimiento $movimientoService): ResponseInterface
{
$input = $request->getParsedBody();
$output = [
'input' => $input,
'movimientos' => []
];
try {
$sociedades_ruts = $input['sociedades_ruts'];
foreach ($sociedades_ruts as $sociedadRut) {
$movimientos = $movimientoService->getAmountBySociedadAndMes($sociedadRut, new DateTimeImmutable($input['mes']), $input['amount'] ?? null);
$output['movimientos'] = array_merge($output['movimientos'], $this->movimientosToArray($movimientos));
} }
} catch (EmptyResult) {} } catch (EmptyResult) {}
return $this->withJson($response, $output); return $this->withJson($response, $output);
} }
protected function movimientosToArray(array $movimientos): array
{
$output = json_decode(json_encode($movimientos), JSON_OBJECT_AS_ARRAY);
foreach ($output as $i => &$arrayMovimiento) {
$arrayMovimiento['cuenta'] = json_decode(json_encode($movimientos[$i]->cuenta), JSON_OBJECT_AS_ARRAY);
$arrayMovimiento['cuenta']['inmobiliaria'] = json_decode(json_encode($movimientos[$i]->cuenta->inmobiliaria), JSON_OBJECT_AS_ARRAY);
if ($arrayMovimiento['detalles'] !== null) {
$arrayMovimiento['detalles']['centro_costo'] = $movimientos[$i]->getDetalles()->centroCosto;
}
}
return $output;
}
} }

View File

@ -0,0 +1,141 @@
<?php
namespace Incoviba\Controller\API\Contabilidad;
use DateTimeImmutable;
use Incoviba\Common\Implement\Exception\EmptyRedis;
use Incoviba\Common\Implement\Exception\EmptyResult;
use Incoviba\Common\Implement\Exception\HttpResponse;
use Incoviba\Controller\API\withJson;
use Incoviba\Service;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
class Nubox
{
use withJson;
public function token(ServerRequestInterface $request, ResponseInterface $response,
Service\Contabilidad\Nubox $nuboxService, int $inmobiliaria_rut): ResponseInterface
{
$output = [
'inmobiliaria_rut' => $inmobiliaria_rut,
'token' => ''
];
try {
$output['token'] = $nuboxService->getToken($inmobiliaria_rut);
} catch (HttpResponse $exception) {
$output['error'] = [
'code' => $exception->getCode(),
'message' => $exception->getMessage()
];
}
return $this->withJson($response, $output);
}
public function sistemas(ServerRequestInterface $request, ResponseInterface $response,
Service\Contabilidad\Nubox $nuboxService, int $inmobiliaria_rut): ResponseInterface
{
$output = [
'inmobiliaria_rut' => $inmobiliaria_rut,
'sistemas' => []
];
try {
$output['sistemas'] = $nuboxService->getSistemas($inmobiliaria_rut);
} catch (HttpResponse $exception) {
$output['error'] = [
'code' => $exception->getCode(),
'message' => $exception->getMessage()
];
}
return $this->withJson($response, $output);
}
public function libroMayor(ServerRequestInterface $request, ResponseInterface $response,
Service\Contabilidad\Nubox $nuboxService, int $inmobiliaria_rut): ResponseInterface
{
$body = $request->getParsedBody();
$output = [
'inmobiliaria_rut' => $inmobiliaria_rut,
'input' => $body,
'libro_mayor' => []
];
try {
$from = new DateTimeImmutable($body['inicio']);
$to = new DateTimeImmutable($body['termino']);
$output['libro_mayor'] = $nuboxService->getLibroMayor($inmobiliaria_rut, $from, $to);
} catch (HttpResponse $exception) {
$output['error'] = [
'code' => $exception->getCode(),
'message' => $exception->getMessage()
];
}
return $this->withJson($response, $output);
}
public function libroDiario(ServerRequestInterface $request, ResponseInterface $response,
Service\Contabilidad\Nubox $nuboxService, int $inmobiliaria_rut): ResponseInterface
{
$body = $request->getParsedBody();
$output = [
'inmobiliaria_rut' => $inmobiliaria_rut,
'input' => $body,
'libro_diario' => []
];
try {
$from = new DateTimeImmutable($body['inicio']);
$to = new DateTimeImmutable($body['termino']);
$output['libro_diario'] = $nuboxService->getLibroDiario($inmobiliaria_rut, $from, $to);
} catch (HttpResponse $exception) {
$output['error'] = [
'code' => $exception->getCode(),
'message' => $exception->getMessage()
];
}
return $this->withJson($response, $output);
}
public function cuentas(ServerRequestInterface $request, ResponseInterface $response, Service\Contabilidad\Nubox $nuboxService, int $inmobiliaria_rut): ResponseInterface
{
$body = $request->getParsedBody();
$output = [
'inmobiliaria_rut' => $inmobiliaria_rut,
'input' => $body,
'cuentas' => $nuboxService->getCuentas($inmobiliaria_rut)
];
return $this->withJson($response, $output);
}
public function cuenta(ServerRequestInterface $request, ResponseInterface $response, Service\Contabilidad\Nubox $nuboxService, int $inmobiliaria_rut): ResponseInterface
{
$body = $request->getParsedBody();
$output = [
'inmobiliaria_rut' => $inmobiliaria_rut,
'input' => $body,
'movimientos' => []
];
try {
$mes = new DateTimeImmutable($body['mes']);
$cuenta = $body['cuenta'];
$output['movimientos'] = $nuboxService->getMesCuenta($inmobiliaria_rut, $cuenta, $mes);
} catch (HttpResponse $exception) {
$output['error'] = [
'code' => $exception->getCode(),
'message' => $exception->getMessage()
];
}
return $this->withJson($response, $output);
}
public function facturas(ServerRequestInterface $request, ResponseInterface $response, Service\Contabilidad\Nubox $nuboxService, int $inmobiliaria_rut, string $dia): ResponseInterface
{
$output = [
'inmobiliaria_rut' => $inmobiliaria_rut,
'dia' => $dia,
'facturas' => []
];
try {
$output['facturas'] = $nuboxService->getFacturas($inmobiliaria_rut, new DateTimeImmutable($dia));
} catch (HttpResponse $exception) {
$output['error'] = [
'code' => $exception->getCode(),
'reason' => $exception->getReason(),
'message' => $exception->getMessage(),
];
}
return $this->withJson($response, $output);
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace Incoviba\Controller\API\Contabilidad;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Log\LoggerInterface;
use Incoviba\Controller\API\withJson;
use Incoviba\Service;
class Tesoreria
{
use withJson;
public function import(ServerRequestInterface $request, ResponseInterface $response, LoggerInterface $logger,
Service\Contabilidad\Informe\Tesoreria $tesoreriaService): ResponseInterface
{
$files = $request->getUploadedFiles();
$output = [];
foreach ($files['file'] as $file) {
if ($file->getError() !== UPLOAD_ERR_OK) {
continue;
}
$data = $tesoreriaService->getInput()->loadFromExcel($file);
$tesoreriaService->getInput()->load($data);
$output['informes'] []= $data;
}
return $this->withJson($response, $output);
}
}

View File

@ -15,15 +15,30 @@ class Direcciones
use withRedis, withJson; use withRedis, withJson;
public function comunas(ServerRequestInterface $request, ResponseInterface $response, Service\Redis $redisService, public function comunas(ServerRequestInterface $request, ResponseInterface $response, Service\Redis $redisService,
Repository\Provincia $provinciaRepository, Repository\Comuna $comunaRepository, int $region_id) : ResponseInterface Repository\Region $regionRepository, Repository\Comuna $comunaRepository,
int $region_id) : ResponseInterface
{ {
$output = ['total' => 0, 'comunas' => []]; $output = ['total' => 0, 'comunas' => []];
$redisKey = 'comunas'; $redisKey = "comunas:region:{$region_id}";
try { try {
$output['comunas'] = $this->fetchRedis($redisService, $redisKey); $output['comunas'] = $this->fetchRedis($redisService, $redisKey);
$output['total'] = count($output['comunas']); $output['total'] = count($output['comunas']);
} catch (EmptyRedis) { } catch (EmptyRedis) {
$provinciaKey = 'provincias'; $regionKey = "regiones:{$region_id}";
try {
$region = $this->fetchRedis($redisService, $regionKey);
} catch (EmptyRedis) {
$region = $regionRepository->fetchById($region_id);
$this->saveRedis($redisService, $regionKey, $region, 60 * 60 * 24 * 30);
}
$comunas = $comunaRepository->fetchByRegion($region->id);
usort($comunas, function(Model\Comuna $a, Model\Comuna $b) {
return strcoll($a->descripcion, $b->descripcion);
});
$output = ['comunas' => $comunas, 'total' => count($comunas)];
$this->saveRedis($redisService, $redisKey, $comunas, 60 * 60 * 24 * 30);
/*$provinciaKey = 'provincias';
try { try {
$temp_provincias = $this->fetchRedis($redisService, $provinciaKey); $temp_provincias = $this->fetchRedis($redisService, $provinciaKey);
} catch (EmptyRedis) { } catch (EmptyRedis) {
@ -39,7 +54,7 @@ class Direcciones
return strcoll($a->descripcion, $b->descripcion); return strcoll($a->descripcion, $b->descripcion);
}); });
$output = ['comunas' => $comunas, 'total' => count($comunas)]; $output = ['comunas' => $comunas, 'total' => count($comunas)];
$this->saveRedis($redisService, $redisKey, $comunas, 60 * 60 * 24 * 30); $this->saveRedis($redisService, $redisKey, $comunas, 60 * 60 * 24 * 30);*/
} }
return $this->withJson($response, $output); return $this->withJson($response, $output);
} }

View File

@ -12,6 +12,16 @@ class Inmobiliarias
{ {
use withJson; use withJson;
public function __invoke(ServerRequestInterface $request, ResponseInterface $response, Repository\Inmobiliaria $inmobiliariaRepository): ResponseInterface
{
$output = [
'inmobiliarias' => []
];
try {
$output['inmobiliarias'] = $inmobiliariaRepository->fetchAll('razon');
} catch (EmptyResult) {}
return $this->withJson($response, $output);
}
public function cuentas(ServerRequestInterface $request, ResponseInterface $response, public function cuentas(ServerRequestInterface $request, ResponseInterface $response,
Repository\Inmobiliaria $inmobiliariaRepository, Repository\Inmobiliaria $inmobiliariaRepository,
Repository\Inmobiliaria\Cuenta $cuentaRepository, int $inmobiliaria_rut): ResponseInterface Repository\Inmobiliaria\Cuenta $cuentaRepository, int $inmobiliaria_rut): ResponseInterface
@ -45,4 +55,35 @@ class Inmobiliarias
} catch (EmptyResult) {} } catch (EmptyResult) {}
return $this->withJson($response, $output); return $this->withJson($response, $output);
} }
public function proveedores(ServerRequestInterface $request, ResponseInterface $response,
Repository\Inmobiliaria $inmobiliariaRepository,
Repository\Inmobiliaria\SociedadAgente $sociedadAgenteRepository,
Repository\Inmobiliaria\TipoAgente $tipoAgenteRepository,
int $inmobiliaria_rut): ResponseInterface
{
$input = $request->getParsedBody();
$output = [
'sociedad_rut' => $inmobiliaria_rut,
'input' => $input,
'sociedad' => null,
'proveedores' => []
];
try {
$inmobiliaria = $inmobiliariaRepository->fetchById($inmobiliaria_rut);
$output['sociedad'] = $inmobiliaria;
if (isset($input['tipo_agente_id'])) {
$tipo = $tipoAgenteRepository->fetchById($input['tipo_agente_id']);
$proveedores = $sociedadAgenteRepository->fetchBySociedadAndTipo($inmobiliaria->rut, $tipo->id);
} else {
$proveedores = $sociedadAgenteRepository->fetchBySociedad($inmobiliaria->rut);
}
$output['proveedores'] = json_decode(json_encode($proveedores), JSON_OBJECT_AS_ARRAY);
foreach ($proveedores as $i => $proveedor) {
$output['proveedores'][$i]['sociedad'] = $proveedor->sociedad;
$output['proveedores'][$i]['proveedor'] = $proveedor->agenteTipo->agente;
$output['proveedores'][$i]['tipo'] = $proveedor->agenteTipo->tipoAgente;
}
} catch (EmptyResult) {}
return $this->withJson($response, $output);
}
} }

View File

@ -0,0 +1,90 @@
<?php
namespace Incoviba\Controller\API\Inmobiliarias;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Incoviba\Common\Implement\Exception\EmptyResult;
use Incoviba\Controller\API\withJson;
use Incoviba\Repository;
class Agentes
{
use withJson;
public function __invoke(ServerRequestInterface $request, ResponseInterface $response, Repository\Inmobiliaria\Agente $agenteRepository): ResponseInterface
{
$output = [
'agentes' => []
];
try {
$output['agentes'] = $agenteRepository->fetchAll('abreviacion');
} catch (EmptyResult) {}
return $this->withJson($response, $output);
}
public function add(ServerRequestInterface $request, ResponseInterface $response, Repository\Inmobiliaria\Agente $agenteRepository): ResponseInterface
{
$input = $request->getParsedBody();
$output = [
'input' => $input,
'agente' => null
];
try {
$agente = $agenteRepository->create($input);
$output['agente'] = $agenteRepository->save($agente);
} catch (EmptyResult) {}
return $this->withJson($response, $output);
}
public function edit(ServerRequestInterface $request, ResponseInterface $response,
Repository\Inmobiliaria\Agente $agenteRepository, int $agente_id): ResponseInterface
{
$input = $request->getParsedBody();
$output = [
'agente_id' => $agente_id,
'input' => $input,
'agente' => null
];
try {
$agente = $agenteRepository->fetchById($agente_id);
$output['agente'] = $agenteRepository->edit($agente, $input);
} catch (EmptyResult) {}
return $this->withJson($response, $output);
}
public function register(ServerRequestInterface $request, ResponseInterface $response,
Repository\Inmobiliaria $inmobiliariaRepository,
Repository\Inmobiliaria\Agente $agenteRepository,
Repository\Inmobiliaria\TipoAgente $tipoAgenteRepository,
Repository\Inmobiliaria\AgenteTipo $agenteTipoRepository,
Repository\Inmobiliaria\SociedadAgente $sociedadAgenteRepository): ResponseInterface
{
$input = $request->getParsedBody();
$output = [
'input' => $input,
'sociedad' => null,
'agente' => null,
'tipo_agente' => null,
'sociedad_agente' => null
];
try {
$sociedad = $inmobiliariaRepository->fetchById($input['sociedad_rut']);
$output['sociedad'] = $sociedad;
$agente = $agenteRepository->fetchById($input['agente_id']);
$output['agente'] = $agente;
$tipo = $tipoAgenteRepository->fetchById($input['tipo_agente_id']);
$output['tipo_agente'] = $tipo;
$agenteTipo = $agenteTipoRepository->fetchByAgenteAndTipo($agente->id, $tipo->id);
try {
$output['sociedad_agente'] = $sociedadAgenteRepository->fetchBySociedadAndAgenteAndTipo($sociedad->rut, $agente->id, $tipo->id);
} catch (EmptyResult) {
$data = [
'sociedad_rut' => $sociedad->rut,
'agente_tipo_id' => $agenteTipo->id
];
$sociedadAgente = $sociedadAgenteRepository->create($data);
$output['sociedad_agente'] = $sociedadAgenteRepository->save($sociedadAgente);
}
} catch (EmptyResult) {
}
return $this->withJson($response, $output);
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace Incoviba\Controller\API\Inmobiliarias;
use Incoviba\Controller\API\withJson;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;
use Incoviba\Common\Implement\Exception\EmptyResult;
use Incoviba\Common\Ideal;
use Incoviba\Repository;
use Incoviba\Service;
class Sociedades extends Ideal\Service
{
use withJson;
public function add(ServerRequestInterface $request, ResponseInterface $response,
Service\Sociedad $sociedadService, Service\Persona $personaService): ResponseInterface
{
$input = $request->getParsedBody();
$output = [
'input' => $input,
'sociedad' => null
];
try {
$data = json_decode($input, true);
$contacto = $personaService->add($data['contacto']);
$data['contacto_rut'] = $contacto->rut;
unset($data['contacto']);
$output['sociedad'] = $sociedadService->add($data);
} catch (EmptyResult) {
$output['error'] = 'No se pudo agregar la sociedad';
}
return $this->withJson($response, $output);
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace Incoviba\Controller\API;
use Incoviba\Common\Implement\Exception\EmptyResult;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Incoviba\Repository;
use Incoviba\Service;
use Psr\Log\LoggerInterface;
class Login
{
use withJson;
public function __invoke(ServerRequestInterface $request, ResponseInterface $response,
Repository\User $userRepository,
Repository\Login $loginRepository,
Service\Login $loginService): ResponseInterface
{
$body = $request->getParsedBody();
$output = [
'username' => $body['username'],
];
try {
$user = $userRepository->fetchByName($body['username']);
if ($user->validate($body['password'])) {
$loginService->login($user);
$output['token'] = $loginService->getToken();
}
} catch (EmptyResult) {}
return $this->withJson($response, $output);
}
}

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