113 Commits

Author SHA1 Message Date
7bd946976e Merge branch 'develop' into feature/cierres 2025-08-12 19:24:48 -04:00
4e4c0b7648 Merge pull request 'Se registra todo envio a Toku' (#28) from feature/mas-registro-en-toku into develop
Reviewed-on: #28
2025-08-12 19:20:27 -04:00
47679cd4e4 Se registra todo envio a Toku 2025-08-12 19:18:18 -04:00
27f7d1d579 Mostrar reservas 2025-08-12 19:17:32 -04:00
977b5f76f4 Mostrar reservations 2025-08-08 17:06:16 -04:00
42336133cd Reservations 2025-08-08 17:04:50 -04:00
62aa6a08a0 Listado de cierres 2025-08-05 18:18:05 -04:00
28b272bc55 Merge branch 'develop' into feature/cierres 2025-08-05 15:46:21 -04:00
0f8db5a3f8 Merge pull request 'FIX: Telefono sobre rango maximo de integer en MySQL' (#26) from hotfix/nueva-venta-fallando into develop
Reviewed-on: #26
2025-08-05 15:44:54 -04:00
c38e89d3f1 FIX: Telefono sobre rango maximo de integer en MySQL 2025-08-05 15:41:23 -04:00
909bb2b879 Mostrar reservas 2025-07-22 18:21:05 -04:00
39bc190775 Cambios a modelo Reservation 2025-07-22 18:20:48 -04:00
413709e443 Cambio a migraciones de reservation 2025-07-22 18:19:49 -04:00
808b55cf29 Merge branch 'develop' into feature/cierres 2025-07-22 18:15:55 -04:00
307f2ac7d7 feature/cierres (#25)
Varios cambios

Co-authored-by: Juan Pablo Vial <jpvialb@incoviba.cl>
Reviewed-on: #25
2025-07-22 13:18:00 +00:00
6c25c5f6d5 FIX: Venta no se actualizaba 2025-07-18 16:20:23 -04:00
3ce41914e5 FIX: Venta desistida pero sin resciliacion 2025-07-18 16:15:30 -04:00
56505fe519 Merge 2025-07-18 13:57:31 -04:00
c7aa731261 FIX: Desistir cuando no existe pie 2025-07-18 13:56:07 -04:00
b211af47d0 Permissions 2025-07-15 23:30:53 -04:00
fd134804a2 Queue runs batch of jobs 2025-07-15 23:18:09 -04:00
d3d6940842 Add retries when job execute failed 2025-07-15 23:07:32 -04:00
c512d7a79a FIX: sacar configuracion de json 2025-07-15 22:56:48 -04:00
9310d65d77 Push can now push json file 2025-07-15 22:19:07 -04:00
8b3cf47762 Pending now in queue 2025-07-15 22:18:45 -04:00
48f992dd47 Merge remote-tracking branch 'origin/feature/cierres' into feature/cierres 2025-07-15 19:07:03 -04:00
2f1628887e Permisos entrypoint 2025-07-15 19:06:07 -04:00
79073ebeb7 Beanstalkd -> Pheanstalk 2025-07-15 19:04:19 -04:00
501151a90e Beanstalkd for jobs 2025-07-14 14:29:41 -04:00
e6e7470bb2 Limit children 2025-07-12 11:00:08 -04:00
2efe4e4a85 Don't run all jobs directly 2025-07-12 10:48:50 -04:00
5ab94e7b2d Reset memory 2025-07-12 10:33:50 -04:00
e037c86e6f Run one job at a time. 2025-07-12 10:31:48 -04:00
595a71d7dd System info 2025-07-12 10:30:32 -04:00
8666499626 Merge remote-tracking branch 'origin/feature/cierres' into feature/cierres 2025-07-12 09:43:19 -04:00
f752256f76 Fcgi idle timeout 2025-07-12 09:43:02 -04:00
6dda5aef00 Habilitar sync 2025-07-03 17:30:58 -04:00
18e79b76d6 Better logging 2025-07-03 17:17:55 -04:00
239376fadc Send all 2025-07-03 17:13:22 -04:00
15ba23bf23 Toku update output 2025-07-03 17:06:00 -04:00
73328240cc Update invoices 2025-07-03 17:00:01 -04:00
495581de8f Update subscriptions 2025-07-03 16:59:41 -04:00
cb0731b67d Update subscriptions 2025-07-03 16:37:57 -04:00
4980839568 Merge remote-tracking branch 'origin/feature/cierres' into feature/cierres 2025-07-01 17:16:39 -04:00
7714e25270 Uso de cuenta al agregar y editar para Toku 2025-07-01 17:14:53 -04:00
8a043b21bc Migracion para guardar account-key de toku 2025-07-01 17:14:21 -04:00
925faeb6f4 Migracion para guardar account-key de toku 2025-07-01 15:28:07 -04:00
f11b7dcd9b PHP more request handlers 2025-06-30 17:25:48 -04:00
e35053edce FastCGI Command max requests 2025-06-30 17:09:45 -04:00
829ab86770 FastCGI pool 2025-06-30 16:39:55 -04:00
4b66694166 Batch queue 2025-06-30 15:52:36 -04:00
d4f8804fbb Batch queue 2025-06-30 15:52:28 -04:00
5f4d8a4bc2 FIX: separate details exception 2025-06-30 11:47:57 -04:00
1fa152c07f FIX: new line exception.log 2025-06-30 11:44:05 -04:00
7f8bd607e3 Job retries 2025-06-27 18:42:47 -04:00
1bbee1121b FIX: Authorization en FastCGI 2025-06-27 18:00:45 -04:00
352e33179c More detail 2025-06-27 17:42:09 -04:00
f34d7338f1 FIX: FasCGI Response 2025-06-27 17:35:17 -04:00
4dd83ae63d FIX: Error observado 2025-06-27 17:24:04 -04:00
e265854958 FIX: Error observado 2025-06-27 17:21:25 -04:00
174b29efff FIX: str_starts_with 2025-06-27 17:17:21 -04:00
1c0f4a5ae9 Try to register fatal errors in file 2025-06-27 17:17:06 -04:00
ccee5f9f56 FIX: more details 2025-06-27 16:58:15 -04:00
9677f11aef FIX: seguir cuando existe error. 2025-06-27 16:56:24 -04:00
c21de2848d FIX: saltarse lineas comentadas 2025-06-27 16:51:16 -04:00
348bb18654 LoggerEnabled Fix 2025-06-25 18:14:43 -04:00
a2a9f4bbb4 LoggerEnabled Fix 2025-06-25 18:11:28 -04:00
ab7328b40b FastCGI 2025-06-25 18:07:08 -04:00
7f97862324 Revisiones 2025-06-25 12:41:28 -04:00
9edf0d9120 Revisiones 2025-06-25 12:22:01 -04:00
ca1ed3f870 Pruebas de integracion con seeds 2025-06-24 21:55:02 -04:00
360537c638 Prueba de integracion para service worker 2025-06-24 14:24:47 -04:00
a6e6b8acc0 Prueba de integracion para service worker 2025-06-24 12:56:03 -04:00
a4e2b4fc7a Seed TipoEstadoPago 2025-06-24 12:55:46 -04:00
479047cd6a Dependencias para testeo 2025-06-24 12:55:02 -04:00
29d9ea8e4a Correr seeds y botar tablas al final 2025-06-24 12:54:48 -04:00
a7cd661938 Registro de worker 2025-06-24 12:54:32 -04:00
7bdcc7168a Container separado a archivo a parte para poder cargarlo en otros lados 2025-06-24 12:54:19 -04:00
d2dff57531 FIX: id de migracion 2025-06-24 12:53:56 -04:00
39198bbe7c get Uf asincronico 2025-06-24 11:20:53 -04:00
200510d60a Prueba Service Worker 2025-06-24 11:20:21 -04:00
ecc67a43c8 Service Worker, corre metodos de servicios asincronicamente, requiere parametros escalares y no retornar mas que bool o void 2025-06-24 11:19:58 -04:00
64791d1fc5 Alias enqueue 2025-06-24 11:16:29 -04:00
4053854410 Prueba UF 2025-06-24 11:16:19 -04:00
a687743762 FIX: No buscar UF para fechas mayores a hoy o día nueve del mes 2025-06-24 11:04:33 -04:00
38fb6f3bcc Pausa a external 2025-06-19 16:47:24 -04:00
1e6bac61b5 FIX: Limpiar archivos 2025-06-19 16:47:06 -04:00
a611ae247d FIX: prorrateo ingresado en valor % 2025-06-12 11:28:04 -04:00
c02c6eb15c FIX: Add custom HttpException 2025-06-10 19:36:56 -04:00
b7e0217cf3 FIX: remove ext-http 2025-06-10 19:35:17 -04:00
2fd1a44984 FIX: install ext-http with external script 2025-06-10 19:04:32 -04:00
fe912db62b Add ext-http 2025-06-10 18:20:05 -04:00
da90160a2a Add ext-http 2025-06-10 18:02:05 -04:00
64047ef577 TOKU: FIX: exceptions 2025-06-10 17:48:36 -04:00
feef90afd6 TOKU: payments loop through pages 2025-06-10 17:46:16 -04:00
32e1e49a97 FIX: cuerpo para borrar payments 2025-06-10 17:34:04 -04:00
11c64cb10f FIX: url para borrar 2025-06-10 17:29:39 -04:00
823f97ce5f FIX: log levels 2025-06-10 17:24:38 -04:00
4ae72199fc Better external logging 2025-06-10 17:00:20 -04:00
d841b1aeed FIX: url de payments en Toku 2025-06-10 16:57:34 -04:00
666cac1958 FIX: url de payments en Toku 2025-06-10 16:54:46 -04:00
71c189e236 FIX: circular dependency 2025-06-10 16:49:34 -04:00
5b6a1c42e3 Toku: reorden 2025-06-10 16:28:46 -04:00
81bce6fe7f Cleanup 2025-06-10 16:28:13 -04:00
e1072ea252 Toku: Reset payments 2025-06-10 16:26:33 -04:00
21473fe52c Valor cuota en CLP 2025-06-10 15:27:09 -04:00
7d589e0e87 Valor cuota en CLF 2025-06-10 15:24:51 -04:00
c48a0d2381 Valor cuota en CLP 2025-06-10 15:14:25 -04:00
abb1ce7299 Mas logging 2025-06-09 12:44:42 -04:00
f1ed9668fc email -> mail 2025-06-06 18:25:12 -04:00
f9ae809fc4 Data too large 2025-06-06 18:10:59 -04:00
06e5292af1 Log add response 2025-06-06 18:10:06 -04:00
47ba664142 Log customer params 2025-06-06 18:04:57 -04:00
179 changed files with 7478 additions and 652 deletions

View File

@ -2,15 +2,15 @@ FROM php:8.4-cli
ENV TZ "${TZ}"
ENV APP_NAME "${APP_NAME}"
ENV API_URL "${API_URL}"
RUN apt-get update && apt-get install -y --no-install-recommends cron rsyslog nano && rm -r /var/lib/apt/lists/*
RUN apt-get update && apt-get install -y --no-install-recommends cron rsyslog nano beanstalkd \
&& rm -r /var/lib/apt/lists/*
RUN pecl install xdebug-3.4.2 \
&& docker-php-ext-enable xdebug \
&& echo $TZ > /etc/timezone
COPY --chmod=550 ./cli/entrypoint /root/entrypoint
COPY --chmod=550 ./cli/start_command /root/start_command
COPY ./php-errors.ini /usr/local/etc/php/conf.d/docker-php-errors.ini
COPY ./php-timezone.ini /usr/local/etc/php/conf.d/docker-php-timezone.ini
@ -19,4 +19,4 @@ WORKDIR /code/bin
COPY --chmod=644 ./cli/crontab /var/spool/cron/crontabs/root
CMD [ "/root/entrypoint" ]
CMD [ "/root/start_command" ]

View File

@ -3,7 +3,7 @@ FROM php:8.4-fpm
ENV TZ=America/Santiago
RUN apt-get update && apt-get install -y --no-install-recommends libzip-dev libicu-dev git \
libpng-dev unzip tzdata libxml2-dev \
libpng-dev unzip tzdata libxml2-dev beanstalkd \
&& rm -r /var/lib/apt/lists/* \
&& docker-php-ext-install pdo pdo_mysql zip intl gd bcmath dom \
&& pecl install xdebug-3.4.2 \

0
app/bin/console Normal file → Executable file
View File

0
app/bin/integration_tests Normal file → Executable file
View File

0
app/bin/performance_tests Normal file → Executable file
View File

0
app/bin/unit_tests Normal file → Executable file
View File

View File

@ -6,7 +6,7 @@ use Psr\Log\LoggerInterface;
abstract class LoggerEnabled implements LoggerAwareInterface
{
protected LoggerInterface $logger;
public LoggerInterface $logger;
public function setLogger(LoggerInterface $logger): void
{
$this->logger = $logger;

View File

@ -0,0 +1,10 @@
<?php
namespace Incoviba\Common\Ideal\Service;
use Incoviba\Common\Define;
use Incoviba\Common\Ideal;
abstract class Repository extends Ideal\Service
{
abstract public function getRepository(): Define\Repository;
}

View File

@ -64,10 +64,10 @@ class Select extends Ideal\Query implements Define\Query\Select
public function having(array|string $conditions): Select
{
if (is_string($conditions)) {
return $this->addCondition($conditions);
return $this->addHaving($conditions);
}
foreach ($conditions as $condition) {
$this->addCondition($condition);
$this->addHaving($condition);
}
return $this;
}

View File

@ -0,0 +1,13 @@
<?php
namespace Incoviba\Common\Implement\Exception;
use Throwable;
use Exception;
class HttpException extends Exception
{
public function __construct(string $message = "", int $code = 0, ?Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
}
}

View File

@ -0,0 +1,139 @@
<?php
namespace Incoviba\Common\Implement\Log\Processor;
use DateInvalidTimeZoneException;
use DateMalformedStringException;
use DateTimeImmutable;
use DateTimeZone;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\ContainerInterface;
use Psr\Container\NotFoundExceptionInterface;
use Monolog\Formatter;
use Monolog\Handler;
use Monolog\Level;
use Predis;
use Incoviba;
use Throwable;
class ArrayBuilder
{
public function __construct(protected ContainerInterface $container) {}
public function build(array $data): array
{
$handlers = [];
foreach ($data as $handlerData) {
if (in_array($handlerData['handler'], [Handler\StreamHandler::class, Handler\RotatingFileHandler::class,])) {
$params = [
"/logs/{$handlerData['filename']}",
];
if ($handlerData['handler'] === Handler\RotatingFileHandler::class) {
$params []= 10;
}
try {
$formatter = Formatter\LineFormatter::class;
if (array_key_exists('formatter', $handlerData)) {
$formatter = $handlerData['formatter'];
}
$handler = new $handlerData['handler'](...$params)
->setFormatter($this->container->get($formatter));
} catch (NotFoundExceptionInterface | ContainerExceptionInterface $exception) {
$this->log($exception, ['handlerData' => $handlerData]);
continue;
}
} elseif ($handlerData['handler'] === Incoviba\Common\Implement\Log\Handler\MySQL::class) {
try {
$params = [
$this->container->get(Incoviba\Common\Define\Connection::class)
];
$formatter = Incoviba\Common\Implement\Log\Formatter\PDO::class;
if (array_key_exists('formatter', $handlerData)) {
$formatter = $handlerData['formatter'];
}
$handler = new $handlerData['handler'](...$params)
->setFormatter($this->container->get($formatter));
} catch (NotFoundExceptionInterface | ContainerExceptionInterface $exception) {
$this->log($exception, ['handlerData' => $handlerData]);
continue;
}
} elseif ($handlerData['handler'] === Handler\RedisHandler::class) {
try {
$params = [
$this->container->get(Predis\ClientInterface::class),
"logs:{$handlerData['name']}"
];
} catch (NotFoundExceptionInterface | ContainerExceptionInterface $exception) {
$this->log($exception, ['handlerData' => $handlerData]);
continue;
}
$handler = new $handlerData['handler'](...$params);
}
if (!isset($handler)) {
$this->log("Invalid handler", ['handlerData' => $handlerData]);
continue;
}
$params = [
$handler,
];
if (is_array($handlerData['levels'])) {
foreach ($handlerData['levels'] as $level) {
$params []= $level;
}
} else {
$params []= $handlerData['levels'];
$params []= Level::Emergency;
}
$params []= false;
$handlers []= new Handler\FilterHandler(...$params);
}
return $handlers;
}
protected function log(string|Throwable $message, array $context = []): void
{
try {
$dateTime = new DateTimeImmutable('now', new DateTimeZone($_ENV['TZ'] ?? 'America/Santiago'));
} catch (DateMalformedStringException | DateInvalidTimeZoneException $exception) {
$dateTime = new DateTimeImmutable();
}
if (is_a($message, Throwable::class)) {
$exception = $message;
$message = $exception->getMessage();
}
$context = json_encode($context, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
if ($context === false) {
$context = '[]';
}
$extra = [];
$extra['from'] = __FILE__;
if (isset($exception)) {
$extra['file'] = $exception->getFile();
$extra['line'] = $exception->getLine();
$extra['trace'] = $exception->getTrace();
}
$extra = json_encode($extra, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
$code = 0;
if (isset($exception)) {
$code = $exception->getCode();
}
if ($extra === false) {
$extra = '[]';
}
$output = "[{$dateTime->format('Y-m-d H:i:s P')}] [{$code}] {$message} {$context} {$extra}";
$filename = '/logs/error.json';
$fileContents = [];
if (file_exists($filename)) {
$fileContents = file_get_contents($filename);
$fileContents = json_decode($fileContents, true);
if ($fileContents === false) {
$fileContents = [];
}
}
$fileContents[$dateTime->getTimestamp()] = $output;
$fileContents = json_encode($fileContents, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
if ($fileContents === false) {
$fileContents = '[]';
}
file_put_contents($filename, $fileContents);
}
}

View File

@ -2,7 +2,9 @@
namespace Incoviba\Common\Implement\Repository;
use Closure;
use Exception;
use Incoviba\Common\Define;
use Incoviba\Common\Implement\Exception\EmptyResult;
class Factory implements Define\Repository\Factory
{
@ -20,8 +22,16 @@ class Factory implements Define\Repository\Factory
return $this;
}
/**
* @return mixed
* @throws EmptyResult
*/
public function run(): mixed
{
return call_user_func_array($this->callable, $this->args);
try {
return call_user_func_array($this->callable, $this->args);
} catch (Exception $exception) {
throw new EmptyResult($exception->getMessage(), $exception);
}
}
}

View File

@ -8,11 +8,13 @@
"ext-gd": "*",
"ext-openssl": "*",
"ext-pdo": "*",
"ext-sockets": "*",
"berrnd/slim-blade-view": "^1",
"guzzlehttp/guzzle": "^7",
"monolog/monolog": "^3",
"nyholm/psr7": "^1",
"nyholm/psr7-server": "^1",
"pda/pheanstalk": "^7.0",
"php-di/php-di": "^7",
"php-di/slim-bridge": "^3",
"phpoffice/phpspreadsheet": "^3",

496
app/fcgi.conf Normal file
View File

@ -0,0 +1,496 @@
; Start a new pool named 'www'.
; the variable $pool can be used in any directive and will be replaced by the
; pool name ('www' here)
[fcgi]
; Per pool prefix
; It only applies on the following directives:
; - 'access.log'
; - 'slowlog'
; - 'listen' (unixsocket)
; - 'chroot'
; - 'chdir'
; - 'php_values'
; - 'php_admin_values'
; When not set, the global prefix (or NONE) applies instead.
; Note: This directive can also be relative to the global prefix.
; Default Value: none
;prefix = /path/to/pools/$pool
; Unix user/group of the child processes. This can be used only if the master
; process running user is root. It is set after the child process is created.
; The user and group can be specified either by their name or by their numeric
; IDs.
; Note: If the user is root, the executable needs to be started with
; --allow-to-run-as-root option to work.
; Default Values: The user is set to master process running user by default.
; If the group is not set, the user's group is used.
user = www-data
group = www-data
; The address on which to accept FastCGI requests.
; Valid syntaxes are:
; 'ip.add.re.ss:port' - to listen on a TCP socket to a specific IPv4 address on
; a specific port;
; '[ip:6:addr:ess]:port' - to listen on a TCP socket to a specific IPv6 address on
; a specific port;
; 'port' - to listen on a TCP socket to all addresses
; (IPv6 and IPv4-mapped) on a specific port;
; '/path/to/unix/socket' - to listen on a unix socket.
; Note: This value is mandatory.
;listen = 127.0.0.1:9000
listen = 9090
; Set listen(2) backlog.
; Default Value: 511 (-1 on Linux, FreeBSD and OpenBSD)
;listen.backlog = 511
; Set permissions for unix socket, if one is used. In Linux, read/write
; permissions must be set in order to allow connections from a web server. Many
; BSD-derived systems allow connections regardless of permissions. The owner
; and group can be specified either by name or by their numeric IDs.
; Default Values: Owner is set to the master process running user. If the group
; is not set, the owner's group is used. Mode is set to 0660.
;listen.owner = www-data
;listen.group = www-data
;listen.mode = 0660
; When POSIX Access Control Lists are supported you can set them using
; these options, value is a comma separated list of user/group names.
; When set, listen.owner and listen.group are ignored
;listen.acl_users =
;listen.acl_groups =
; List of addresses (IPv4/IPv6) of FastCGI clients which are allowed to connect.
; Equivalent to the FCGI_WEB_SERVER_ADDRS environment variable in the original
; PHP FCGI (5.2.2+). Makes sense only with a tcp listening socket. Each address
; must be separated by a comma. If this value is left blank, connections will be
; accepted from any ip address.
; Default Value: any
;listen.allowed_clients = 127.0.0.1
; Set the associated the route table (FIB). FreeBSD only
; Default Value: -1
;listen.setfib = 1
; Specify the nice(2) priority to apply to the pool processes (only if set)
; The value can vary from -19 (highest priority) to 20 (lower priority)
; Note: - It will only work if the FPM master process is launched as root
; - The pool processes will inherit the master process priority
; unless it specified otherwise
; Default Value: no set
; process.priority = -19
; Set the process dumpable flag (PR_SET_DUMPABLE prctl for Linux or
; PROC_TRACE_CTL procctl for FreeBSD) even if the process user
; or group is different than the master process user. It allows to create process
; core dump and ptrace the process for the pool user.
; Default Value: no
; process.dumpable = yes
; Choose how the process manager will control the number of child processes.
; Possible Values:
; static - a fixed number (pm.max_children) of child processes;
; dynamic - the number of child processes are set dynamically based on the
; following directives. With this process management, there will be
; always at least 1 children.
; pm.max_children - the maximum number of children that can
; be alive at the same time.
; pm.start_servers - the number of children created on startup.
; pm.min_spare_servers - the minimum number of children in 'idle'
; state (waiting to process). If the number
; of 'idle' processes is less than this
; number then some children will be created.
; pm.max_spare_servers - the maximum number of children in 'idle'
; state (waiting to process). If the number
; of 'idle' processes is greater than this
; number then some children will be killed.
; pm.max_spawn_rate - the maximum number of rate to spawn child
; processes at once.
; ondemand - no children are created at startup. Children will be forked when
; new requests will connect. The following parameter are used:
; pm.max_children - the maximum number of children that
; can be alive at the same time.
; pm.process_idle_timeout - The number of seconds after which
; an idle process will be killed.
; Note: This value is mandatory.
pm = ondemand
; The number of child processes to be created when pm is set to 'static' and the
; maximum number of child processes when pm is set to 'dynamic' or 'ondemand'.
; This value sets the limit on the number of simultaneous requests that will be
; served. Equivalent to the ApacheMaxClients directive with mpm_prefork.
; Equivalent to the PHP_FCGI_CHILDREN environment variable in the original PHP
; CGI. The below defaults are based on a server without much resources. Don't
; forget to tweak pm.* to fit your needs.
; Note: Used when pm is set to 'static', 'dynamic' or 'ondemand'
; Note: This value is mandatory.
pm.max_children = 2
; The number of child processes created on startup.
; Note: Used only when pm is set to 'dynamic'
; Default Value: (min_spare_servers + max_spare_servers) / 2
;pm.start_servers = 2
; The desired minimum number of idle server processes.
; Note: Used only when pm is set to 'dynamic'
; Note: Mandatory when pm is set to 'dynamic'
;pm.min_spare_servers = 1
; The desired maximum number of idle server processes.
; Note: Used only when pm is set to 'dynamic'
; Note: Mandatory when pm is set to 'dynamic'
;pm.max_spare_servers = 3
; The number of rate to spawn child processes at once.
; Note: Used only when pm is set to 'dynamic'
; Note: Mandatory when pm is set to 'dynamic'
; Default Value: 32
;pm.max_spawn_rate = 32
; The number of seconds after which an idle process will be killed.
; Note: Used only when pm is set to 'ondemand'
; Default Value: 10s
;pm.process_idle_timeout = 10s;
pm.process_idle_timeout = 10s
; The number of requests each child process should execute before respawning.
; This can be useful to work around memory leaks in 3rd party libraries. For
; endless request processing specify '0'. Equivalent to PHP_FCGI_MAX_REQUESTS.
; Default Value: 0
;pm.max_requests = 500
; The URI to view the FPM status page. If this value is not set, no URI will be
; recognized as a status page. It shows the following information:
; pool - the name of the pool;
; process manager - static, dynamic or ondemand;
; start time - the date and time FPM has started;
; start since - number of seconds since FPM has started;
; accepted conn - the number of request accepted by the pool;
; listen queue - the number of request in the queue of pending
; connections (see backlog in listen(2));
; max listen queue - the maximum number of requests in the queue
; of pending connections since FPM has started;
; listen queue len - the size of the socket queue of pending connections;
; idle processes - the number of idle processes;
; active processes - the number of active processes;
; total processes - the number of idle + active processes;
; max active processes - the maximum number of active processes since FPM
; has started;
; max children reached - number of times, the process limit has been reached,
; when pm tries to start more children (works only for
; pm 'dynamic' and 'ondemand');
; Value are updated in real time.
; Example output:
; pool: www
; process manager: static
; start time: 01/Jul/2011:17:53:49 +0200
; start since: 62636
; accepted conn: 190460
; listen queue: 0
; max listen queue: 1
; listen queue len: 42
; idle processes: 4
; active processes: 11
; total processes: 15
; max active processes: 12
; max children reached: 0
;
; By default the status page output is formatted as text/plain. Passing either
; 'html', 'xml' or 'json' in the query string will return the corresponding
; output syntax. Example:
; http://www.foo.bar/status
; http://www.foo.bar/status?json
; http://www.foo.bar/status?html
; http://www.foo.bar/status?xml
;
; By default the status page only outputs short status. Passing 'full' in the
; query string will also return status for each pool process.
; Example:
; http://www.foo.bar/status?full
; http://www.foo.bar/status?json&full
; http://www.foo.bar/status?html&full
; http://www.foo.bar/status?xml&full
; The Full status returns for each process:
; pid - the PID of the process;
; state - the state of the process (Idle, Running, ...);
; start time - the date and time the process has started;
; start since - the number of seconds since the process has started;
; requests - the number of requests the process has served;
; request duration - the duration in µs of the requests;
; request method - the request method (GET, POST, ...);
; request URI - the request URI with the query string;
; content length - the content length of the request (only with POST);
; user - the user (PHP_AUTH_USER) (or '-' if not set);
; script - the main script called (or '-' if not set);
; last request cpu - the %cpu the last request consumed
; it's always 0 if the process is not in Idle state
; because CPU calculation is done when the request
; processing has terminated;
; last request memory - the max amount of memory the last request consumed
; it's always 0 if the process is not in Idle state
; because memory calculation is done when the request
; processing has terminated;
; If the process is in Idle state, then information is related to the
; last request the process has served. Otherwise information is related to
; the current request being served.
; Example output:
; ************************
; pid: 31330
; state: Running
; start time: 01/Jul/2011:17:53:49 +0200
; start since: 63087
; requests: 12808
; request duration: 1250261
; request method: GET
; request URI: /test_mem.php?N=10000
; content length: 0
; user: -
; script: /home/fat/web/docs/php/test_mem.php
; last request cpu: 0.00
; last request memory: 0
;
; Note: There is a real-time FPM status monitoring sample web page available
; It's available in: /usr/local/share/php/fpm/status.html
;
; Note: The value must start with a leading slash (/). The value can be
; anything, but it may not be a good idea to use the .php extension or it
; may conflict with a real PHP file.
; Default Value: not set
;pm.status_path = /status
; The address on which to accept FastCGI status request. This creates a new
; invisible pool that can handle requests independently. This is useful
; if the main pool is busy with long running requests because it is still possible
; to get the status before finishing the long running requests.
;
; Valid syntaxes are:
; 'ip.add.re.ss:port' - to listen on a TCP socket to a specific IPv4 address on
; a specific port;
; '[ip:6:addr:ess]:port' - to listen on a TCP socket to a specific IPv6 address on
; a specific port;
; 'port' - to listen on a TCP socket to all addresses
; (IPv6 and IPv4-mapped) on a specific port;
; '/path/to/unix/socket' - to listen on a unix socket.
; Default Value: value of the listen option
;pm.status_listen = 127.0.0.1:9001
; The ping URI to call the monitoring page of FPM. If this value is not set, no
; URI will be recognized as a ping page. This could be used to test from outside
; that FPM is alive and responding, or to
; - create a graph of FPM availability (rrd or such);
; - remove a server from a group if it is not responding (load balancing);
; - trigger alerts for the operating team (24/7).
; Note: The value must start with a leading slash (/). The value can be
; anything, but it may not be a good idea to use the .php extension or it
; may conflict with a real PHP file.
; Default Value: not set
;ping.path = /ping
; This directive may be used to customize the response of a ping request. The
; response is formatted as text/plain with a 200 response code.
; Default Value: pong
;ping.response = pong
; The access log file
; Default: not set
;access.log = log/$pool.access.log
access.log = /proc/self/fd/2
; The access log format.
; The following syntax is allowed
; %%: the '%' character
; %C: %CPU used by the request
; it can accept the following format:
; - %{user}C for user CPU only
; - %{system}C for system CPU only
; - %{total}C for user + system CPU (default)
; %d: time taken to serve the request
; it can accept the following format:
; - %{seconds}d (default)
; - %{milliseconds}d
; - %{milli}d
; - %{microseconds}d
; - %{micro}d
; %e: an environment variable (same as $_ENV or $_SERVER)
; it must be associated with embraces to specify the name of the env
; variable. Some examples:
; - server specifics like: %{REQUEST_METHOD}e or %{SERVER_PROTOCOL}e
; - HTTP headers like: %{HTTP_HOST}e or %{HTTP_USER_AGENT}e
; %f: script filename
; %l: content-length of the request (for POST request only)
; %m: request method
; %M: peak of memory allocated by PHP
; it can accept the following format:
; - %{bytes}M (default)
; - %{kilobytes}M
; - %{kilo}M
; - %{megabytes}M
; - %{mega}M
; %n: pool name
; %o: output header
; it must be associated with embraces to specify the name of the header:
; - %{Content-Type}o
; - %{X-Powered-By}o
; - %{Transfert-Encoding}o
; - ....
; %p: PID of the child that serviced the request
; %P: PID of the parent of the child that serviced the request
; %q: the query string
; %Q: the '?' character if query string exists
; %r: the request URI (without the query string, see %q and %Q)
; %R: remote IP address
; %s: status (response code)
; %t: server time the request was received
; it can accept a strftime(3) format:
; %d/%b/%Y:%H:%M:%S %z (default)
; The strftime(3) format must be encapsulated in a %{<strftime_format>}t tag
; e.g. for a ISO8601 formatted timestring, use: %{%Y-%m-%dT%H:%M:%S%z}t
; %T: time the log has been written (the request has finished)
; it can accept a strftime(3) format:
; %d/%b/%Y:%H:%M:%S %z (default)
; The strftime(3) format must be encapsulated in a %{<strftime_format>}t tag
; e.g. for a ISO8601 formatted timestring, use: %{%Y-%m-%dT%H:%M:%S%z}t
; %u: basic auth user if specified in Authorization header
;
; Default: "%R - %u %t \"%m %r\" %s"
;access.format = "%R - %u %t \"%m %r%Q%q\" %s %f %{milli}d %{kilo}M %C%%"
; A list of request_uri values which should be filtered from the access log.
;
; As a security precaution, this setting will be ignored if:
; - the request method is not GET or HEAD; or
; - there is a request body; or
; - there are query parameters; or
; - the response code is outwith the successful range of 200 to 299
;
; Note: The paths are matched against the output of the access.format tag "%r".
; On common configurations, this may look more like SCRIPT_NAME than the
; expected pre-rewrite URI.
;
; Default Value: not set
;access.suppress_path[] = /ping
;access.suppress_path[] = /health_check.php
; The log file for slow requests
; Default Value: not set
; Note: slowlog is mandatory if request_slowlog_timeout is set
;slowlog = log/$pool.log.slow
; The timeout for serving a single request after which a PHP backtrace will be
; dumped to the 'slowlog' file. A value of '0s' means 'off'.
; Available units: s(econds)(default), m(inutes), h(ours), or d(ays)
; Default Value: 0
;request_slowlog_timeout = 0
; Depth of slow log stack trace.
; Default Value: 20
;request_slowlog_trace_depth = 20
; The timeout for serving a single request after which the worker process will
; be killed. This option should be used when the 'max_execution_time' ini option
; does not stop script execution for some reason. A value of '0' means 'off'.
; Available units: s(econds)(default), m(inutes), h(ours), or d(ays)
; Default Value: 0
;request_terminate_timeout = 0
; The timeout set by 'request_terminate_timeout' ini option is not engaged after
; application calls 'fastcgi_finish_request' or when application has finished and
; shutdown functions are being called (registered via register_shutdown_function).
; This option will enable timeout limit to be applied unconditionally
; even in such cases.
; Default Value: no
;request_terminate_timeout_track_finished = no
; Set open file descriptor rlimit.
; Default Value: system defined value
;rlimit_files = 1024
; Set max core size rlimit.
; Possible Values: 'unlimited' or an integer greater or equal to 0
; Default Value: system defined value
;rlimit_core = 0
; Chroot to this directory at the start. This value must be defined as an
; absolute path. When this value is not set, chroot is not used.
; Note: you can prefix with '$prefix' to chroot to the pool prefix or one
; of its subdirectories. If the pool prefix is not set, the global prefix
; will be used instead.
; Note: chrooting is a great security feature and should be used whenever
; possible. However, all PHP paths will be relative to the chroot
; (error_log, sessions.save_path, ...).
; Default Value: not set
;chroot =
; Chdir to this directory at the start.
; Note: relative path can be used.
; Default Value: current directory or / when chroot
;chdir = /var/www
; Redirect worker stdout and stderr into main error log. If not set, stdout and
; stderr will be redirected to /dev/null according to FastCGI specs.
; Note: on highloaded environment, this can cause some delay in the page
; process time (several ms).
; Default Value: no
;catch_workers_output = yes
catch_workers_output = yes
; Decorate worker output with prefix and suffix containing information about
; the child that writes to the log and if stdout or stderr is used as well as
; log level and time. This options is used only if catch_workers_output is yes.
; Settings to "no" will output data as written to the stdout or stderr.
; Default value: yes
;decorate_workers_output = no
decorate_workers_output = no
; Clear environment in FPM workers
; Prevents arbitrary environment variables from reaching FPM worker processes
; by clearing the environment in workers before env vars specified in this
; pool configuration are added.
; Setting to "no" will make all environment variables available to PHP code
; via getenv(), $_ENV and $_SERVER.
; Default Value: yes
;clear_env = no
clear_env = no
; Limits the extensions of the main script FPM will allow to parse. This can
; prevent configuration mistakes on the web server side. You should only limit
; FPM to .php extensions to prevent malicious users to use other extensions to
; execute php code.
; Note: set an empty value to allow all extensions.
; Default Value: .php
;security.limit_extensions = .php .php3 .php4 .php5 .php7
; Pass environment variables like LD_LIBRARY_PATH. All $VARIABLEs are taken from
; the current environment.
; Default Value: clean env
;env[HOSTNAME] = $HOSTNAME
;env[PATH] = /usr/local/bin:/usr/bin:/bin
;env[TMP] = /tmp
;env[TMPDIR] = /tmp
;env[TEMP] = /tmp
; Additional php.ini defines, specific to this pool of workers. These settings
; overwrite the values previously defined in the php.ini. The directives are the
; same as the PHP SAPI:
; php_value/php_flag - you can set classic ini defines which can
; be overwritten from PHP call 'ini_set'.
; php_admin_value/php_admin_flag - these directives won't be overwritten by
; PHP call 'ini_set'
; For php_*flag, valid values are on, off, 1, 0, true, false, yes or no.
; Defining 'extension' will load the corresponding shared extension from
; extension_dir. Defining 'disable_functions' or 'disable_classes' will not
; overwrite previously defined php.ini values, but will append the new value
; instead.
; Note: path INI options can be relative and will be expanded with the prefix
; (pool, global or /usr/local)
; Default Value: nothing is defined by default except the values in php.ini and
; specified at startup with the -d argument
;php_admin_value[sendmail_path] = /usr/sbin/sendmail -t -i -f www@my.domain.com
;php_flag[display_errors] = off
;php_admin_value[error_log] = /var/log/fpm-php.www.log
;php_admin_flag[log_errors] = on
;php_admin_value[memory_limit] = 32M

View File

@ -10,7 +10,8 @@ class CreateTipoEstadoPago extends Phinx\Migration\AbstractMigration
$this->execute("ALTER DATABASE CHARACTER SET 'utf8mb4';");
$this->execute("ALTER DATABASE COLLATE='utf8mb4_general_ci';");
$this->table('tipo_estado_pago')
$this->table('tipo_estado_pago', ['id' => false, 'primary_key' => 'id'])
->addColumn('id', 'integer', ['signed' => true])
->addColumn('descripcion', 'string', ['length' => 20, 'default' => null, 'null' => true])
->addColumn('active', 'integer', ['length' => 1, 'default' => 0])
->create();

View File

@ -4,7 +4,7 @@ declare(strict_types=1);
use Phinx\Migration\AbstractMigration;
final class CreateReservation extends AbstractMigration
final class CreateReservations extends AbstractMigration
{
/**
* Change Method.
@ -23,9 +23,11 @@ final class CreateReservation extends AbstractMigration
$this->execute("ALTER DATABASE CHARACTER SET 'utf8mb4';");
$this->execute("ALTER DATABASE COLLATE='utf8mb4_general_ci';");
$this->table('reservation')
$this->table('reservations')
->addColumn('project_id', 'integer', ['signed' => false, 'null' => false])
->addColumn('buyer_rut', 'integer', ['signed' => false, 'null' => false])
->addColumn('date', 'date', ['null' => false])
->addForeignKey('project_id', 'proyecto', 'id', ['delete' => 'cascade', 'update' => 'cascade'])
->addForeignKey('buyer_rut', 'personas', 'rut', ['delete' => 'cascade', 'update' => 'cascade'])
->create();

View File

@ -4,7 +4,7 @@ declare(strict_types=1);
use Phinx\Migration\AbstractMigration;
final class CreateReservationDatas extends AbstractMigration
final class CreateReservationDetails extends AbstractMigration
{
/**
* Change Method.
@ -23,11 +23,13 @@ final class CreateReservationDatas extends AbstractMigration
$this->execute("ALTER DATABASE CHARACTER SET 'utf8mb4';");
$this->execute("ALTER DATABASE COLLATE='utf8mb4_general_ci';");
$this->table('reservation_data')
$this->table('reservation_details')
->addColumn('reservation_id', 'integer', ['signed' => false, 'null' => false])
->addColumn('type', 'integer', ['length' => 1, 'signed' => false, 'null' => false])
->addColumn('reference_id', 'integer', ['signed' => false, 'null' => false])
->addColumn('value', 'decimal', ['precision' => 10, 'scale' => 2, 'signed' => false, 'default' => 0.00, 'null' => true])
->addColumn('value', 'decimal', ['precision' => 10, 'scale' => 2, 'signed' => false, 'default' => null, 'null' => true])
->addForeignKey('reservation_id', 'reservations', 'id', ['delete' => 'cascade', 'update' => 'cascade'])
->addIndex(['reservation_id', 'type', 'reference_id'], ['unique' => true, 'name' => 'idx_reservation_details'])
->create();
$this->execute('SET unique_checks=1; SET foreign_key_checks=1;');

View File

@ -27,7 +27,7 @@ final class CreateReservationStates extends AbstractMigration
->addColumn('reservation_id', 'integer', ['signed' => false, 'null' => false])
->addColumn('date', 'date', ['null' => false])
->addColumn('type', 'integer', ['length' => 3, 'null' => false, 'default' => 0])
->addForeignKey('reservation_id', 'reservation', 'id', ['delete' => 'cascade', 'update' => 'cascade'])
->addForeignKey('reservation_id', 'reservations', 'id', ['delete' => 'cascade', 'update' => 'cascade'])
->create();
$this->execute('SET unique_checks=1; SET foreign_key_checks=1;');

View File

@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
use Phinx\Migration\AbstractMigration;
final class CreateTokuAccounts extends AbstractMigration
{
/**
* Change Method.
*
* Write your reversible migrations using this method.
*
* More information on writing migrations is available here:
* https://book.cakephp.org/phinx/0/en/migrations.html#the-change-method
*
* Remember to call "create()" or "update()" and NOT "save()" when working
* with the Table class.
*/
public function change(): void
{
$this->execute('SET unique_checks=0; SET foreign_key_checks=0;');
$this->table('toku_accounts')
->addColumn('sociedad_rut', 'integer', ['limit' => 8, 'signed' => false, 'null' => false])
->addColumn('toku_id', 'string', ['length' => 255, 'null' => false])
->addColumn('account_key', 'string', ['length' => 255, 'null' => false])
->addColumn('enabled', 'boolean', ['default' => true])
->addTimestamps()
#->addForeignKey('sociedad_rut', 'inmobiliaria', 'rut', ['delete' => 'CASCADE', 'update' => 'CASCADE'])
->addIndex(['toku_id'], ['unique' => true])
->create();
$this->execute('SET unique_checks=1; SET foreign_key_checks=1;');
}
}

View File

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

View File

@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
use Phinx\Migration\AbstractMigration;
final class AddCommentsToEstadoCierre extends AbstractMigration
{
/**
* Change Method.
*
* Write your reversible migrations using this method.
*
* More information on writing migrations is available here:
* https://book.cakephp.org/phinx/0/en/migrations.html#the-change-method
*
* Remember to call "create()" or "update()" and NOT "save()" when working
* with the Table class.
*/
public function change(): void
{
$this->table('estado_cierre_comentarios')
->addColumn('estado_cierre_id', 'integer', ['signed' => false])
->addColumn('fecha', 'datetime', ['default' => 'CURRENT_TIMESTAMP'])
->addColumn('comments', 'text')
->addForeignKey('estado_cierre_id', 'estado_cierre', 'id', ['delete' => 'cascade', 'update' => 'cascade'])
->create();
}
}

View File

@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
use Phinx\Seed\AbstractSeed;
class Comuna extends AbstractSeed
{
public function getDependencies(): array
{
return [
'Provincia',
];
}
/**
* Run Method.
*
* Write your database seeder using this method.
*
* More information on writing seeders is available here:
* https://book.cakephp.org/phinx/0/en/seeding.html
*/
public function run(): void
{
$this->execute('SET unique_checks=0; SET foreign_key_checks=0;');
$table = $this->table('comuna');
$table->truncate();
$filename = implode(DIRECTORY_SEPARATOR, [getcwd(), 'resources', 'database', 'seeds', 'comuna.csv']);
$dataRows = explode(PHP_EOL, trim(file_get_contents($filename)));
$data = array_map(fn($row) => explode(';', $row), $dataRows);
$columns = array_shift($data);
$columns = array_map(function($column) {
if (str_contains($column, 'id')) {
return 'id';
}
if (str_contains($column, 'provincia')) {
return 'provincia';
}
return $column;
}, $columns);
$data = array_map(function ($row) use ($columns) {
return array_combine($columns, $row);
}, $data);
$table->insert($data)->save();
$this->execute('SET unique_checks=1; SET foreign_key_checks=1;');
}
}

View File

@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
use Phinx\Seed\AbstractSeed;
class Provincia extends AbstractSeed
{
public function getDependencies(): array
{
return [
'Region',
];
}
/**
* Run Method.
*
* Write your database seeder using this method.
*
* More information on writing seeders is available here:
* https://book.cakephp.org/phinx/0/en/seeding.html
*/
public function run(): void
{
$this->execute('SET unique_checks=0; SET foreign_key_checks=0;');
$table = $this->table('provincia');
$table->truncate();
$filename = implode(DIRECTORY_SEPARATOR, [getcwd(), 'resources', 'database', 'seeds', 'provincia.csv']);
$dataRows = explode(PHP_EOL, trim(file_get_contents($filename)));
$data = array_map(fn($row) => explode(';', $row), $dataRows);
$columns = array_shift($data);
$columns = array_map(function($column) {
if (str_contains($column, 'id')) {
return 'id';
}
if (str_contains($column, 'region')) {
return 'region';
}
return $column;
}, $columns);
$data = array_map(function ($row) use ($columns) {
return array_combine($columns, $row);
}, $data);
$table->insert($data)->save();
$this->execute('SET unique_checks=1; SET foreign_key_checks=1;');
}
}

View File

@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
use Phinx\Seed\AbstractSeed;
class Region extends AbstractSeed
{
/**
* Run Method.
*
* Write your database seeder using this method.
*
* More information on writing seeders is available here:
* https://book.cakephp.org/phinx/0/en/seeding.html
*/
public function run(): void
{
$this->execute('SET unique_checks=0; SET foreign_key_checks=0;');
$table = $this->table('region');
$table->truncate();
$filename = implode(DIRECTORY_SEPARATOR, [getcwd(), 'resources', 'database', 'seeds', 'region.csv']);
$dataRows = explode(PHP_EOL, trim(file_get_contents($filename)));
$data = array_map(fn($row) => explode(';', $row), $dataRows);
$columns = array_shift($data);
$columns = array_map(function($column) {
if (str_contains($column, 'id')) {
return 'id';
}
if (str_contains($column, 'numeracion')) {
return 'numeracion';
}
return $column;
}, $columns);
$data = array_map(function ($row) use ($columns) {
return array_combine($columns, $row);
}, $data);
$table->insert($data)->save();
$this->execute('SET unique_checks=1; SET foreign_key_checks=1;');
}
}

View File

@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
use Phinx\Seed\AbstractSeed;
class TipoEstadoPago extends AbstractSeed
{
/**
* Run Method.
*
* Write your database seeder using this method.
*
* More information on writing seeders is available here:
* https://book.cakephp.org/phinx/0/en/seeding.html
*/
public function run(): void
{
$this->execute('SET unique_checks=0; SET foreign_key_checks=0;');
$table = $this->table('tipo_estado_pago');
$table->truncate();
$data = [
['id' => -3, 'descripcion' => 'anulado'],
['id' => -2, 'descripcion' => 'reemplazado'],
['id' => -1, 'descripcion' => 'devuelto'],
['id' => 0, 'descripcion' => 'no pagado', 'active' => 1],
['id' => 1, 'descripcion' => 'depositado', 'active' => 1],
['id' => 2, 'descripcion' => 'abonado', 'active' => 1],
];
$table->insert($data)->saveData();
$this->execute('SET unique_checks=1; SET foreign_key_checks=1;');
}
}

View File

@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
use Phinx\Seed\AbstractSeed;
class TipoPago extends AbstractSeed
{
/**
* Run Method.
*
* Write your database seeder using this method.
*
* More information on writing seeders is available here:
* https://book.cakephp.org/phinx/0/en/seeding.html
*/
public function run(): void
{
$table = $this->table('tipo_pago');
$data = [
[
'descripcion' => 'cheque',
],
[
'descripcion' => 'carta de resguardo',
],
[
'descripcion' => 'vale vista'
],
[
'descripcion' => 'deposito',
],
[
'descripcion' => 'efectivo',
],
[
'descripcion' => 'tarjeta de credito',
],
[
'descripcion' => 'transferencia electronica',
],
[
'descripcion' => 'virtual',
]
];
$table->truncate();
$table
->insert($data)
->saveData();
}
}

View File

@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
use Phinx\Seed\AbstractSeed;
class TipoSociedad extends AbstractSeed
{
/**
* Run Method.
*
* Write your database seeder using this method.
*
* More information on writing seeders is available here:
* https://book.cakephp.org/phinx/0/en/seeding.html
*/
public function run(): void
{
$this->execute('SET unique_checks=0; SET foreign_key_checks=0;');
$table = $this->table('tipo_sociedad');
$table->truncate();
$data = [
[
'descripcion' => 'Limitada',
'abreviacion' => 'Ltda',
],
[
'descripcion' => 'Sociedad Anónima',
'abreviacion' => 'SA',
],
[
'descripcion' => 'Sociedad por Acciones',
'abreviacion' => 'SpA',
]
];
$table->insert($data)->saveData();
$this->execute('SET unique_checks=1; SET foreign_key_checks=1;');
}
}

View File

@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
use Phinx\Seed\AbstractSeed;
class TipoUnidad extends AbstractSeed
{
/**
* Run Method.
*
* Write your database seeder using this method.
*
* More information on writing seeders is available here:
* https://book.cakephp.org/phinx/0/en/seeding.html
*/
public function run(): void
{
$this->execute('SET unique_checks=0; SET foreign_key_checks=0;');
$table = $this->table('tipo_unidad');
$table->truncate();
$data = [
[
'descripcion' => 'departamento',
'orden' => 0,
],
[
'descripcion' => 'estacionamiento',
'orden' => 1,
],
[
'descripcion' => 'bodega',
'orden' => 2,
]
];
$table->insert($data)->saveData();
$this->execute('SET unique_checks=1; SET foreign_key_checks=1;');
}
}

View File

@ -0,0 +1,347 @@
id;descripcion;provincia
1101;Iquique;11
1107;Alto Hospicio;11
1401;Pozo Almonte;14
1402;Camiña;14
1403;Colchane;14
1404;Huara;14
1405;Pica;14
2101;Antofagasta;21
2102;Mejillones;21
2103;Sierra Gorda;21
2104;Taltal;21
2201;Calama;22
2202;Ollagüe;22
2203;San Pedro de Atacama;22
2301;Tocopilla;23
2302;María Elena;23
3101;Copiapó;31
3102;Caldera;31
3103;Tierra Amarilla;31
3201;Chañaral;32
3202;Diego de Almagro;32
3301;Vallenar;33
3302;Alto del Carmen;33
3303;Freirina;33
3304;Huasco;33
4101;La Serena;41
4102;Coquimbo;41
4103;Andacollo;41
4104;La Higuera;41
4105;Paiguano;41
4106;Vicuña;41
4201;Illapel;42
4202;Canela;42
4203;Los Vilos;42
4204;Salamanca;42
4301;Ovalle;43
4302;Combarbalá;43
4303;Monte Patria;43
4304;Punitaqui;43
4305;Río Hurtado;43
5101;Valparaíso;51
5102;Casablanca;51
5103;Concón;51
5104;Juan Fernández;51
5105;Puchuncaví;51
5107;Quintero;51
5109;Viña del Mar;51
5201;Isla de Pascua;52
5301;Los Andes;53
5302;Calle Larga;53
5303;Rinconada;53
5304;San Esteban;53
5401;La Ligua;54
5402;Cabildo;54
5403;Papudo;54
5404;Petorca;54
5405;Zapallar;54
5501;Quillota;55
5502;Calera;55
5503;Hijuelas;55
5504;La Cruz;55
5506;Nogales;55
5601;San Antonio;56
5602;Algarrobo;56
5603;Cartagena;56
5604;El Quisco;56
5605;El Tabo;56
5606;Santo Domingo;56
5701;San Felipe;57
5702;Catemu;57
5703;Llaillay;57
5704;Panquehue;57
5705;Putaendo;57
5706;Santa María;57
5801;Quilpué;58
5802;Limache;58
5803;Olmué;58
5804;Villa Alemana;58
6101;Rancagua;61
6102;Codegua;61
6103;Coinco;61
6104;Coltauco;61
6105;Doñihue;61
6106;Graneros;61
6107;Las Cabras;61
6108;Machalí;61
6109;Malloa;61
6110;Mostazal;61
6111;Olivar;61
6112;Peumo;61
6113;Pichidegua;61
6114;Quinta de Tilcoco;61
6115;Rengo;61
6116;Requínoa;61
6117;San Vicente;61
6201;Pichilemu;62
6202;La Estrella;62
6203;Litueche;62
6204;Marchihue;62
6205;Navidad;62
6206;Paredones;62
6301;San Fernando;63
6302;Chépica;63
6303;Chimbarongo;63
6304;Lolol;63
6305;Nancagua;63
6306;Palmilla;63
6307;Peralillo;63
6308;Placilla;63
6309;Pumanque;63
6310;Santa Cruz;63
7101;Talca;71
7102;Constitución;71
7103;Curepto;71
7104;Empedrado;71
7105;Maule;71
7106;Pelarco;71
7107;Pencahue;71
7108;Río Claro;71
7109;San Clemente;71
7110;San Rafael;71
7201;Cauquenes;72
7202;Chanco;72
7203;Pelluhue;72
7301;Curicó;73
7302;Hualañé;73
7303;Licantén;73
7304;Molina;73
7305;Rauco;73
7306;Romeral;73
7307;Sagrada Familia;73
7308;Teno;73
7309;Vichuquén;73
7401;Linares;74
7402;Colbún;74
7403;Longaví;74
7404;Parral;74
7405;Retiro;74
7406;San Javier;74
7407;Villa Alegre;74
7408;Yerbas Buenas;74
8101;Concepción;81
8102;Coronel;81
8103;Chiguayante;81
8104;Florida;81
8105;Hualqui;81
8106;Lota;81
8107;Penco;81
8108;San Pedro de la Paz;81
8109;Santa Juana;81
8110;Talcahuano;81
8111;Tomé;81
8112;Hualpén;81
8201;Lebu;82
8202;Arauco;82
8203;Cañete;82
8204;Contulmo;82
8205;Curanilahue;82
8206;Los Álamos;82
8207;Tirúa;82
8301;Los Ángeles;83
8302;Antuco;83
8303;Cabrero;83
8304;Laja;83
8305;Mulchén;83
8306;Nacimiento;83
8307;Negrete;83
8308;Quilaco;83
8309;Quilleco;83
8310;San Rosendo;83
8311;Santa Bárbara;83
8312;Tucapel;83
8313;Yumbel;83
8314;Alto Biobío;83
8401;Chillán;84
8402;Bulnes;84
8403;Cobquecura;84
8404;Coelemu;84
8405;Coihueco;84
8406;Chillán Viejo;84
8407;El Carmen;84
8408;Ninhue;84
8409;Ñiquén;84
8410;Pemuco;84
8411;Pinto;84
8412;Portezuelo;84
8413;Quillón;84
8414;Quirihue;84
8415;Ránquil;84
8416;San Carlos;84
8417;San Fabián;84
8418;San Ignacio;84
8419;San Nicolás;84
8420;Treguaco;84
8421;Yungay;84
9101;Temuco;91
9102;Carahue;91
9103;Cunco;91
9104;Curarrehue;91
9105;Freire;91
9106;Galvarino;91
9107;Gorbea;91
9108;Lautaro;91
9109;Loncoche;91
9110;Melipeuco;91
9111;Nueva Imperial;91
9112;Padre las Casas;91
9113;Perquenco;91
9114;Pitrufquén;91
9115;Pucón;91
9116;Saavedra;91
9117;Teodoro Schmidt;91
9118;Toltén;91
9119;Vilcún;91
9120;Villarrica;91
9121;Cholchol;91
9201;Angol;92
9202;Collipulli;92
9203;Curacautín;92
9204;Ercilla;92
9205;Lonquimay;92
9206;Los Sauces;92
9207;Lumaco;92
9208;Purén;92
9209;Renaico;92
9210;Traiguén;92
9211;Victoria;92
10101;Puerto Montt;101
10102;Calbuco;101
10103;Cochamó;101
10104;Fresia;101
10105;Frutillar;101
10106;Los Muermos;101
10107;Llanquihue;101
10108;Maullín;101
10109;Puerto Varas;101
10201;Castro;102
10202;Ancud;102
10203;Chonchi;102
10204;Curaco de Vélez;102
10205;Dalcahue;102
10206;Puqueldón;102
10207;Queilén;102
10208;Quellón;102
10209;Quemchi;102
10210;Quinchao;102
10301;Osorno;103
10302;Puerto Octay;103
10303;Purranque;103
10304;Puyehue;103
10305;Río Negro;103
10306;San Juan de la Costa;103
10307;San Pablo;103
10401;Chaitén;104
10402;Futaleufú;104
10403;Hualaihué;104
10404;Palena;104
11101;Coihaique;111
11102;Lago Verde;111
11201;Aisén;112
11202;Cisnes;112
11203;Guaitecas;112
11301;Cochrane;113
11302;OHiggins;113
11303;Tortel;113
11401;Chile Chico;114
11402;Río Ibáñez;114
12101;Punta Arenas;121
12102;Laguna Blanca;121
12103;Río Verde;121
12104;San Gregorio;121
12201;Cabo de Hornos (Ex Navarino);122
12202;Antártica;122
12301;Porvenir;123
12302;Primavera;123
12303;Timaukel;123
12401;Natales;124
12402;Torres del Paine;124
13101;Santiago;131
13102;Cerrillos;131
13103;Cerro Navia;131
13104;Conchalí;131
13105;El Bosque;131
13106;Estación Central;131
13107;Huechuraba;131
13108;Independencia;131
13109;La Cisterna;131
13110;La Florida;131
13111;La Granja;131
13112;La Pintana;131
13113;La Reina;131
13114;Las Condes;131
13115;Lo Barnechea;131
13116;Lo Espejo;131
13117;Lo Prado;131
13118;Macul;131
13119;Maipú;131
13120;Ñuñoa;131
13121;Pedro Aguirre Cerda;131
13122;Peñalolén;131
13123;Providencia;131
13124;Pudahuel;131
13125;Quilicura;131
13126;Quinta Normal;131
13127;Recoleta;131
13128;Renca;131
13129;San Joaquín;131
13130;San Miguel;131
13131;San Ramón;131
13132;Vitacura;131
13201;Puente Alto;132
13202;Pirque;132
13203;San José de Maipo;132
13301;Colina;133
13302;Lampa;133
13303;Tiltil;133
13401;San Bernardo;134
13402;Buin;134
13403;Calera de Tango;134
13404;Paine;134
13501;Melipilla;135
13502;Alhué;135
13503;Curacaví;135
13504;María Pinto;135
13505;San Pedro;135
13601;Talagante;136
13602;El Monte;136
13603;Isla de Maipo;136
13604;Padre Hurtado;136
13605;Peñaflor;136
14101;Valdivia;141
14102;Corral;141
14103;Lanco;141
14104;Los Lagos;141
14105;Máfil;141
14106;Mariquina;141
14107;Paillaco;141
14108;Panguipulli;141
14201;La Unión;142
14202;Futrono;142
14203;Lago Ranco;142
14204;Río Bueno;142
15101;Arica;151
15102;Camarones;151
15201;Putre;152
15202;General Lagos;152
1 id descripcion provincia
2 1101 Iquique 11
3 1107 Alto Hospicio 11
4 1401 Pozo Almonte 14
5 1402 Camiña 14
6 1403 Colchane 14
7 1404 Huara 14
8 1405 Pica 14
9 2101 Antofagasta 21
10 2102 Mejillones 21
11 2103 Sierra Gorda 21
12 2104 Taltal 21
13 2201 Calama 22
14 2202 Ollagüe 22
15 2203 San Pedro de Atacama 22
16 2301 Tocopilla 23
17 2302 María Elena 23
18 3101 Copiapó 31
19 3102 Caldera 31
20 3103 Tierra Amarilla 31
21 3201 Chañaral 32
22 3202 Diego de Almagro 32
23 3301 Vallenar 33
24 3302 Alto del Carmen 33
25 3303 Freirina 33
26 3304 Huasco 33
27 4101 La Serena 41
28 4102 Coquimbo 41
29 4103 Andacollo 41
30 4104 La Higuera 41
31 4105 Paiguano 41
32 4106 Vicuña 41
33 4201 Illapel 42
34 4202 Canela 42
35 4203 Los Vilos 42
36 4204 Salamanca 42
37 4301 Ovalle 43
38 4302 Combarbalá 43
39 4303 Monte Patria 43
40 4304 Punitaqui 43
41 4305 Río Hurtado 43
42 5101 Valparaíso 51
43 5102 Casablanca 51
44 5103 Concón 51
45 5104 Juan Fernández 51
46 5105 Puchuncaví 51
47 5107 Quintero 51
48 5109 Viña del Mar 51
49 5201 Isla de Pascua 52
50 5301 Los Andes 53
51 5302 Calle Larga 53
52 5303 Rinconada 53
53 5304 San Esteban 53
54 5401 La Ligua 54
55 5402 Cabildo 54
56 5403 Papudo 54
57 5404 Petorca 54
58 5405 Zapallar 54
59 5501 Quillota 55
60 5502 Calera 55
61 5503 Hijuelas 55
62 5504 La Cruz 55
63 5506 Nogales 55
64 5601 San Antonio 56
65 5602 Algarrobo 56
66 5603 Cartagena 56
67 5604 El Quisco 56
68 5605 El Tabo 56
69 5606 Santo Domingo 56
70 5701 San Felipe 57
71 5702 Catemu 57
72 5703 Llaillay 57
73 5704 Panquehue 57
74 5705 Putaendo 57
75 5706 Santa María 57
76 5801 Quilpué 58
77 5802 Limache 58
78 5803 Olmué 58
79 5804 Villa Alemana 58
80 6101 Rancagua 61
81 6102 Codegua 61
82 6103 Coinco 61
83 6104 Coltauco 61
84 6105 Doñihue 61
85 6106 Graneros 61
86 6107 Las Cabras 61
87 6108 Machalí 61
88 6109 Malloa 61
89 6110 Mostazal 61
90 6111 Olivar 61
91 6112 Peumo 61
92 6113 Pichidegua 61
93 6114 Quinta de Tilcoco 61
94 6115 Rengo 61
95 6116 Requínoa 61
96 6117 San Vicente 61
97 6201 Pichilemu 62
98 6202 La Estrella 62
99 6203 Litueche 62
100 6204 Marchihue 62
101 6205 Navidad 62
102 6206 Paredones 62
103 6301 San Fernando 63
104 6302 Chépica 63
105 6303 Chimbarongo 63
106 6304 Lolol 63
107 6305 Nancagua 63
108 6306 Palmilla 63
109 6307 Peralillo 63
110 6308 Placilla 63
111 6309 Pumanque 63
112 6310 Santa Cruz 63
113 7101 Talca 71
114 7102 Constitución 71
115 7103 Curepto 71
116 7104 Empedrado 71
117 7105 Maule 71
118 7106 Pelarco 71
119 7107 Pencahue 71
120 7108 Río Claro 71
121 7109 San Clemente 71
122 7110 San Rafael 71
123 7201 Cauquenes 72
124 7202 Chanco 72
125 7203 Pelluhue 72
126 7301 Curicó 73
127 7302 Hualañé 73
128 7303 Licantén 73
129 7304 Molina 73
130 7305 Rauco 73
131 7306 Romeral 73
132 7307 Sagrada Familia 73
133 7308 Teno 73
134 7309 Vichuquén 73
135 7401 Linares 74
136 7402 Colbún 74
137 7403 Longaví 74
138 7404 Parral 74
139 7405 Retiro 74
140 7406 San Javier 74
141 7407 Villa Alegre 74
142 7408 Yerbas Buenas 74
143 8101 Concepción 81
144 8102 Coronel 81
145 8103 Chiguayante 81
146 8104 Florida 81
147 8105 Hualqui 81
148 8106 Lota 81
149 8107 Penco 81
150 8108 San Pedro de la Paz 81
151 8109 Santa Juana 81
152 8110 Talcahuano 81
153 8111 Tomé 81
154 8112 Hualpén 81
155 8201 Lebu 82
156 8202 Arauco 82
157 8203 Cañete 82
158 8204 Contulmo 82
159 8205 Curanilahue 82
160 8206 Los Álamos 82
161 8207 Tirúa 82
162 8301 Los Ángeles 83
163 8302 Antuco 83
164 8303 Cabrero 83
165 8304 Laja 83
166 8305 Mulchén 83
167 8306 Nacimiento 83
168 8307 Negrete 83
169 8308 Quilaco 83
170 8309 Quilleco 83
171 8310 San Rosendo 83
172 8311 Santa Bárbara 83
173 8312 Tucapel 83
174 8313 Yumbel 83
175 8314 Alto Biobío 83
176 8401 Chillán 84
177 8402 Bulnes 84
178 8403 Cobquecura 84
179 8404 Coelemu 84
180 8405 Coihueco 84
181 8406 Chillán Viejo 84
182 8407 El Carmen 84
183 8408 Ninhue 84
184 8409 Ñiquén 84
185 8410 Pemuco 84
186 8411 Pinto 84
187 8412 Portezuelo 84
188 8413 Quillón 84
189 8414 Quirihue 84
190 8415 Ránquil 84
191 8416 San Carlos 84
192 8417 San Fabián 84
193 8418 San Ignacio 84
194 8419 San Nicolás 84
195 8420 Treguaco 84
196 8421 Yungay 84
197 9101 Temuco 91
198 9102 Carahue 91
199 9103 Cunco 91
200 9104 Curarrehue 91
201 9105 Freire 91
202 9106 Galvarino 91
203 9107 Gorbea 91
204 9108 Lautaro 91
205 9109 Loncoche 91
206 9110 Melipeuco 91
207 9111 Nueva Imperial 91
208 9112 Padre las Casas 91
209 9113 Perquenco 91
210 9114 Pitrufquén 91
211 9115 Pucón 91
212 9116 Saavedra 91
213 9117 Teodoro Schmidt 91
214 9118 Toltén 91
215 9119 Vilcún 91
216 9120 Villarrica 91
217 9121 Cholchol 91
218 9201 Angol 92
219 9202 Collipulli 92
220 9203 Curacautín 92
221 9204 Ercilla 92
222 9205 Lonquimay 92
223 9206 Los Sauces 92
224 9207 Lumaco 92
225 9208 Purén 92
226 9209 Renaico 92
227 9210 Traiguén 92
228 9211 Victoria 92
229 10101 Puerto Montt 101
230 10102 Calbuco 101
231 10103 Cochamó 101
232 10104 Fresia 101
233 10105 Frutillar 101
234 10106 Los Muermos 101
235 10107 Llanquihue 101
236 10108 Maullín 101
237 10109 Puerto Varas 101
238 10201 Castro 102
239 10202 Ancud 102
240 10203 Chonchi 102
241 10204 Curaco de Vélez 102
242 10205 Dalcahue 102
243 10206 Puqueldón 102
244 10207 Queilén 102
245 10208 Quellón 102
246 10209 Quemchi 102
247 10210 Quinchao 102
248 10301 Osorno 103
249 10302 Puerto Octay 103
250 10303 Purranque 103
251 10304 Puyehue 103
252 10305 Río Negro 103
253 10306 San Juan de la Costa 103
254 10307 San Pablo 103
255 10401 Chaitén 104
256 10402 Futaleufú 104
257 10403 Hualaihué 104
258 10404 Palena 104
259 11101 Coihaique 111
260 11102 Lago Verde 111
261 11201 Aisén 112
262 11202 Cisnes 112
263 11203 Guaitecas 112
264 11301 Cochrane 113
265 11302 O’Higgins 113
266 11303 Tortel 113
267 11401 Chile Chico 114
268 11402 Río Ibáñez 114
269 12101 Punta Arenas 121
270 12102 Laguna Blanca 121
271 12103 Río Verde 121
272 12104 San Gregorio 121
273 12201 Cabo de Hornos (Ex Navarino) 122
274 12202 Antártica 122
275 12301 Porvenir 123
276 12302 Primavera 123
277 12303 Timaukel 123
278 12401 Natales 124
279 12402 Torres del Paine 124
280 13101 Santiago 131
281 13102 Cerrillos 131
282 13103 Cerro Navia 131
283 13104 Conchalí 131
284 13105 El Bosque 131
285 13106 Estación Central 131
286 13107 Huechuraba 131
287 13108 Independencia 131
288 13109 La Cisterna 131
289 13110 La Florida 131
290 13111 La Granja 131
291 13112 La Pintana 131
292 13113 La Reina 131
293 13114 Las Condes 131
294 13115 Lo Barnechea 131
295 13116 Lo Espejo 131
296 13117 Lo Prado 131
297 13118 Macul 131
298 13119 Maipú 131
299 13120 Ñuñoa 131
300 13121 Pedro Aguirre Cerda 131
301 13122 Peñalolén 131
302 13123 Providencia 131
303 13124 Pudahuel 131
304 13125 Quilicura 131
305 13126 Quinta Normal 131
306 13127 Recoleta 131
307 13128 Renca 131
308 13129 San Joaquín 131
309 13130 San Miguel 131
310 13131 San Ramón 131
311 13132 Vitacura 131
312 13201 Puente Alto 132
313 13202 Pirque 132
314 13203 San José de Maipo 132
315 13301 Colina 133
316 13302 Lampa 133
317 13303 Tiltil 133
318 13401 San Bernardo 134
319 13402 Buin 134
320 13403 Calera de Tango 134
321 13404 Paine 134
322 13501 Melipilla 135
323 13502 Alhué 135
324 13503 Curacaví 135
325 13504 María Pinto 135
326 13505 San Pedro 135
327 13601 Talagante 136
328 13602 El Monte 136
329 13603 Isla de Maipo 136
330 13604 Padre Hurtado 136
331 13605 Peñaflor 136
332 14101 Valdivia 141
333 14102 Corral 141
334 14103 Lanco 141
335 14104 Los Lagos 141
336 14105 Máfil 141
337 14106 Mariquina 141
338 14107 Paillaco 141
339 14108 Panguipulli 141
340 14201 La Unión 142
341 14202 Futrono 142
342 14203 Lago Ranco 142
343 14204 Río Bueno 142
344 15101 Arica 151
345 15102 Camarones 151
346 15201 Putre 152
347 15202 General Lagos 152

View File

@ -0,0 +1,55 @@
id;descripcion;region
11;Iquique;1
14;Tamarugal;1
21;Antofagasta;2
22;El Loa;2
23;Tocopilla;2
31;Copiap;3
32;Chañaral;3
33;Huasco;3
41;Elqui;4
42;Choapa;4
43;Limari;4
51;Valparaíso;5
52;Isla de Pascua;5
53;Los Andes;5
54;Petorca;5
55;Quillota;5
56;San Antonio;5
57;San Felipe;5
58;Marga Marga;5
61;Cachapoal;6
62;Cardenal Caro;6
63;Colchagua;6
71;Talca;7
72;Cauquenes;7
73;Curico;7
74;Linares;7
81;Concepci;8
82;Arauco;8
83;Bío- Bío;8
84;Ñuble;8
91;Cautín;9
92;Malleco;9
101;Llanquihue;10
102;Chiloe;10
103;Osorno;10
104;Palena;10
111;Coihaique;11
112;Aisén;11
113;Capitan Prat;11
114;General Carrera;11
121;Magallanes;12
122;Antártica Chilena;12
123;Tierra del Fuego;12
124;Ultima Esperanza;12
131;Santiago;13
132;Cordillera;13
133;Chacabuco;13
134;Maipo;13
135;Melipilla;13
136;Talagante;13
141;Valdivia;14
142;Ranco;14
151;Arica;15
152;Parinacota;15
1 id descripcion region
2 11 Iquique 1
3 14 Tamarugal 1
4 21 Antofagasta 2
5 22 El Loa 2
6 23 Tocopilla 2
7 31 Copiap 3
8 32 Chañaral 3
9 33 Huasco 3
10 41 Elqui 4
11 42 Choapa 4
12 43 Limari 4
13 51 Valparaíso 5
14 52 Isla de Pascua 5
15 53 Los Andes 5
16 54 Petorca 5
17 55 Quillota 5
18 56 San Antonio 5
19 57 San Felipe 5
20 58 Marga Marga 5
21 61 Cachapoal 6
22 62 Cardenal Caro 6
23 63 Colchagua 6
24 71 Talca 7
25 72 Cauquenes 7
26 73 Curico 7
27 74 Linares 7
28 81 Concepci 8
29 82 Arauco 8
30 83 Bío- Bío 8
31 84 Ñuble 8
32 91 Cautín 9
33 92 Malleco 9
34 101 Llanquihue 10
35 102 Chiloe 10
36 103 Osorno 10
37 104 Palena 10
38 111 Coihaique 11
39 112 Aisén 11
40 113 Capitan Prat 11
41 114 General Carrera 11
42 121 Magallanes 12
43 122 Antártica Chilena 12
44 123 Tierra del Fuego 12
45 124 Ultima Esperanza 12
46 131 Santiago 13
47 132 Cordillera 13
48 133 Chacabuco 13
49 134 Maipo 13
50 135 Melipilla 13
51 136 Talagante 13
52 141 Valdivia 14
53 142 Ranco 14
54 151 Arica 15
55 152 Parinacota 15

View File

@ -0,0 +1,16 @@
id;descripcion;numeral;numeracion
1;Región de Tarapacá;I;1
2;Región de Antofagasta;II;2
3;Región de Atacama;III;3
4;Región de Coquimbo;IV;4
5;Región de Valparaíso;V;5
6;Región del Libertador Gral. Bernardo OHiggins;VI;6
7;Región del Maule;VII;7
8;Región del Biobío;VIII;8
9;Región de la Araucanía;IX;9
10;Región de Los Lagos;X;10
11;Región Aisén del Gral. Carlos Ibáñez del Campo;XI;11
12;Región de Magallanes y de la Antártica Chilena;XII;12
13;Región Metropolitana de Santiago;RM;13
14;Región de Los Ríos;XIV;14
15;Región de Arica y Parinacota;XV;15
1 id descripcion numeral numeracion
2 1 Región de Tarapacá I 1
3 2 Región de Antofagasta II 2
4 3 Región de Atacama III 3
5 4 Región de Coquimbo IV 4
6 5 Región de Valparaíso V 5
7 6 Región del Libertador Gral. Bernardo O’Higgins VI 6
8 7 Región del Maule VII 7
9 8 Región del Biobío VIII 8
10 9 Región de la Araucanía IX 9
11 10 Región de Los Lagos X 10
12 11 Región Aisén del Gral. Carlos Ibáñez del Campo XI 11
13 12 Región de Magallanes y de la Antártica Chilena XII 12
14 13 Región Metropolitana de Santiago RM 13
15 14 Región de Los Ríos XIV 14
16 15 Región de Arica y Parinacota XV 15

View File

@ -7,4 +7,5 @@ $app->group('/toku', function($app) {
$app->get('/test[/]', [Toku::class, 'test']);
$app->delete('/reset[/]', [Toku::class, 'reset']);
$app->post('/enqueue[/]', [Toku::class, 'enqueue']);
$app->post('/update[/{type}[/]]', [Toku::class, 'update']);
});

View File

@ -0,0 +1,7 @@
<?php
use Incoviba\Controller\API\Personas;
//$app->group('/personas', function($app) {});
$app->group('/persona/{rut}', function($app) {
$app->get('[/]', [Personas::class, 'get']);
});

View File

@ -30,4 +30,5 @@ $app->group('/proyecto/{proyecto_id}', function($app) {
$app->post('/edit[/]', [Proyectos::class, 'terreno']);
});
$app->get('/brokers', [Proyectos::class, 'brokers']);
$app->get('/promotions', [Proyectos::class, 'promotions']);
});

View File

@ -2,9 +2,9 @@
use Incoviba\Controller\API\Queues;
$app->group('/queue', function($app) {
$app->get('/jobs[/]', [Queues::class, 'jobs']);
#$app->get('/jobs[/]', [Queues::class, 'jobs']);
$app->group('/run', function($app) {
$app->get('/{job_id:[0-9]+}[/]', [Queues::class, 'run']);
#$app->get('/{job_id:[0-9]+}[/]', [Queues::class, 'run']);
$app->get('[/]', Queues::class);
});
});

View File

@ -3,6 +3,11 @@ use Incoviba\Controller\API\Ventas\Reservations;
$app->group('/reservations', function($app) {
$app->post('/add[/]', [Reservations::class, 'add']);
$app->group('/project/{project_id}', function($app) {
$app->get('/active[/]', [Reservations::class, 'active']);
$app->get('/pending[/]', [Reservations::class, 'pending']);
$app->get('/rejected[/]', [Reservations::class, 'rejected']);
});
$app->get('[/]', Reservations::class);
});
$app->group('/reservation/{reservation_id}', function($app) {

View File

@ -1,5 +1,6 @@
<?php
use Incoviba\Controller\Ventas\Cierres;
//use Incoviba\Controller\Ventas\Cierres;
use Incoviba\Controller\Ventas\Reservations as Cierres;
$app->group('/cierres', function($app) {
$app->get('[/]', Cierres::class);

View File

@ -156,7 +156,7 @@
<script>
const regiones = [
@foreach ($regiones as $region)
'<div class="item" data-value="{{$region->id}}">{{$region->descripcion}}</div>',
'<div class="item" data-value="{{$region->id}}">{{$region->numeral}} - {{$region->descripcion}}</div>',
@endforeach
]

View File

@ -54,22 +54,23 @@
}
$(document).ready(() => {
const url = '{{$urls->api}}/ventas/pago/{{$venta->resciliacion()->id}}'
let old = new Date({{$venta->resciliacion()?->fecha->format('Y') ?? date('Y')}},
{{$venta->resciliacion()?->fecha->format('n') ?? date('n')}}-1, {{$venta->resciliacion()?->fecha->format('j') ?? date('j')}})
let old = new Date(Date.parse('{{$venta->resciliacion()?->fecha->format('Y-m-d') ?? $venta->currentEstado()->fecha->format('Y-m-d') ?? $venta->fecha->format('Y-m-d')}}') + 24 * 60 * 60 * 1000)
calendar_date_options['initialDate'] = old
calendar_date_options['onChange'] = function(date, text, mode) {
if (date.getTime() === old.getTime()) {
return
}
const body = new FormData()
body.set('fecha', date.toISOString())
const fecha = new Date(date.getTime())
fecha.setDate(fecha.getDate() - 1)
body.set('fecha', fecha.toISOString())
$('#loading-spinner-fecha').show()
APIClient.fetch(url, {method: 'post', body}).then(response => {
$('#loading-spinner-fecha').hide()
if (!response) {
return
}
old = date
old = new Date(date.getTime())
alertResponse('Fecha cambiada correctamente.')
})
}

View File

@ -6,15 +6,26 @@
@section('venta_content')
<div class="ui list">
<div class="item">
<div class="header">Valor Pagado</div>
<div class="content">
{{$format->pesos($venta->formaPago()->pie->pagado('pesos'))}}
<div class="ui left pointing small label">
{{$format->number($venta->formaPago()->pie->pagado() / $venta->valor * 100)}}% de la venta
@if (isset($venta->formaPago()->pie))
<div class="item">
<div class="header">Valor Pagado</div>
<div class="content">
{{$format->pesos($venta->formaPago()->pie->pagado('pesos'))}}
<div class="ui left pointing small label">
{{$format->number($venta->formaPago()->pie->pagado() / $venta->valor * 100)}}% de la venta
</div>
</div>
</div>
</div>
@else
<div class="item">
<div class="ui compact warning message">
<div class="content">
<i class="exclamation triangle icon"></i>
No tiene valor pagado
</div>
</div>
</div>
@endif
<div class="item">
<div class="header">
Multa Estandar

View File

@ -40,7 +40,7 @@
const url = '{{$urls->api}}/ventas/unidad/' + this.props.id + '/prorrateo'
const method = 'post'
const body = new FormData()
body.set('prorrateo', newValue)
body.set('prorrateo', (parseFloat(newValue) / 100).toFixed(8))
return fetchAPI(url, {method, body}).then(response => {
if (!response) {
return

View File

@ -0,0 +1,554 @@
@extends('layout.base')
@section('page_title')
Cierres -Reservas
@endsection
@section('page_content')
<div class="ui container">
<h2 class="ui header">Cierres - Reservas</h2>
<div class="ui compact segment" id="projects">
<div class="ui header">Proyectos</div>
@if (count($projects) == 0)
<div class="ui message">
No hay proyectos en venta.
</div>
@else
<div class="ui link list">
@foreach ($projects as $project)
<div class="item link" data-id="{{ $project->id }}">
{{ $project->descripcion }}
</div>
@endforeach
</div>
@endif
</div>
<div class="ui two column grid">
<div class="column">
<h3 class="ui header" id="project"></h3>
</div>
<div class="column">
<div class="ui active inline loader" id="loader"></div>
</div>
</div>
<div id="results">
<div class="ui right aligned top attached basic segment" id="controls">
<div class="ui tiny icon buttons">
<button class="ui button" id="up_button">
<i class="up arrow icon"></i>
</button>
<button class="ui button" id="refresh_button">
<i class="refresh icon"></i>
</button>
<button class="ui green icon button" id="add_button">
<i class="plus icon"></i>
</button>
</div>
</div>
<div class="ui top attached tabular menu" id="tabs">
<div class="yellow active item" data-tab="pending">Pendientes</div>
<div class="green item" data-tab="active">Activas</div>
<div class="red item" data-tab="rejected">Rechazadas</div>
</div>
<div class="ui bottom attached tab fitted segment active" data-tab="pending">
<table class="ui yellow striped table" id="pending_reservations">
<thead>
<tr>
<th>Unidades</th>
<th>Cliente</th>
<th>Fecha</th>
<th>Oferta</th>
<th>¿Valida?</th>
<th>Operador</th>
<th></th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
<div class="ui bottom attached tab fitted segment" data-tab="active">
<table class="ui green striped table" id="active_reservations">
<thead>
<tr>
<th>Unidades</th>
<th>Cliente</th>
<th>Fecha</th>
<th>Oferta</th>
<th>Operador</th>
<th></th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
<div class="ui bottom attached tab fitted segment" data-tab="rejected">
<table class="ui red striped table" id="rejected_reservations">
<thead>
<tr>
<th>Unidades</th>
<th>Cliente</th>
<th>Fecha</th>
<th>Oferta</th>
<th>Estado</th>
<th>Operador</th>
<th>Comentarios</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
</div>
@include('ventas.reservations.add_modal')
@endsection
@push('page_styles')
<style>
.item.link {
cursor: pointer;
text-decoration: underline;
}
</style>
@endpush
@push('page_scripts')
<script>
class Projects {
display = {
projects: '',
project: ''
}
component_id = ''
component = null
current_project = null;
title_id = ''
title_component = null
constructor({component_id, title_id}) {
this.component_id = component_id
this.component = document.getElementById(this.component_id)
this.display.projects = this.component.style.display
this.title_id = title_id
this.title_component = document.getElementById(this.title_id)
this.display.project = this.title_component.style.display
this.show()
this.watch()
}
select(event) {
event.preventDefault()
const project_id = event.currentTarget.dataset.id
reservations.show.results()
if (project_id === this.current_project) {
this.hide()
this.title_component.innerHTML = event.currentTarget.innerHTML
reservations.action.reservations()
return
}
this.current_project = project_id
reservations.get.reservations(project_id)
reservations.components.modals.add.load(project_id)
this.hide()
this.title_component.innerHTML = event.currentTarget.innerHTML
}
watch() {
this.component.querySelectorAll('.item.link').forEach(item => {
item.addEventListener('click', this.select.bind(this))
})
}
show() {
this.component.style.display = this.display.projects
this.title_component.style.display = 'none'
}
hide() {
this.component.style.display = 'none'
this.title_component.style.display = this.display.project
}
}
class Controls {
display = {
up: '',
reset: '',
}
component_id = ''
component = null
buttons = {
up: null,
reset: null,
add: null
}
constructor({component_id}) {
this.component_id = component_id
this.component = document.getElementById(this.component_id)
const buttons = this.component.querySelectorAll('button')
this.buttons.up = buttons[0]
this.buttons.reset = buttons[1]
this.buttons.add = buttons[2]
this.display.up = buttons[0].style.display
this.display.reset = buttons[1].style.display
this.watch()
this.hide()
}
watch() {
Object.entries(this.buttons).forEach(([key, value]) => {
const name = key.replace('_button', '')
value.addEventListener('click', this.action[name].bind(this))
})
}
hide() {
this.buttons.up.style.display = this.display.up
this.buttons.reset.style.display = this.display.reset
}
show() {
this.buttons.up.style.display = 'none'
this.buttons.reset.style.display = 'none'
}
action = {
reset: event => {
event.preventDefault()
reservations.action.reset(event)
return false
},
up: event => {
event.preventDefault()
reservations.action.up(event)
return false
},
add: event => {
event.preventDefault()
reservations.action.add(event)
return false
}
}
}
class Reservations {
display = {
reservations: '',
}
component_id = ''
component = null
formatters = {
date: null,
ufs: null
}
columnNames = []
reservations = []
constructor({component_id, formatters = {date, ufs}}) {
this.component_id = component_id
this.component = document.getElementById(this.component_id)
this.display.reservations = this.component.style.display
this.formatters = formatters
this.set().columnNames()
this.hide()
}
set() {
return {
reservations: reservations => {
this.reservations = reservations
return this
},
columnNames: () => {
const tds = this.component.querySelector('thead tr').querySelectorAll('th')
this.columnNames = []
tds.forEach(td => {
let name = td.innerHTML.toLowerCase()
if (name === '') {
return
}
if (name.includes('?')) {
name = name.replaceAll(/[¿?]/g, '')
}
this.columnNames.push(name)
})
return this
}
}
}
columnsData() {
return this.reservations.map(reservation => {
const date = new Date(Date.parse(reservation.fecha) + 24 * 60 * 60 * 1000)
return {
id: reservation.id,
unidades: reservation.summary,
cliente: reservation.buyer.nombreCompleto,
fecha: this.formatters.date.format(date),
oferta: `${this.formatters.ufs.format(reservation.offer)} UF`,
valida: reservation.valid ? '<span class="ui green text">Si</span>' : '<span class="ui red text">No</span>',
operador: reservation.broker?.name ?? '',
}
})
}
draw() {
const tbody = this.component.querySelector('tbody')
tbody.innerHTML = ''
this.columnsData().forEach(column => {
const tr = document.createElement('tr')
const contents = []
const id = column.id
this.columnNames.forEach(name => {
contents.push(`<td>${column[name]}</td>`)
})
const actions = this.drawActions(id)
if (actions !== '') {
contents.push(actions)
}
tr.innerHTML = contents.join("\n")
tbody.appendChild(tr)
})
this.show()
}
drawActions(id) {
return `
<td class="right aligned">
<button class="ui green mini icon button approve" data-id="${id}" title="Aprobar">
<i class="check icon"></i>
</button>
<button class="ui red mini icon button reject" data-id="${id}" title="Rechazar">
<i class="trash icon"></i>
</button>
</td>`
}
empty() {
const tbody = this.component.querySelector('tbody')
tbody.innerHTML = ''
const col_span = this.columnNames.length + 1
const tr = document.createElement('tr')
tr.innerHTML = `<td colspan="${col_span}">No hay cierres</td>`
tbody.appendChild(tr)
this.show()
}
show() {
this.component.style.display = this.display.reservations
}
hide() {
this.component.style.display = 'none'
}
}
class ActiveReservations extends Reservations {
constructor({component_id, formatters = {date, ufs}}) {
super({component_id, formatters})
}
columnsData() {
const data = super.columnsData();
return data.map(row => {
delete(row['valida'])
return row
})
}
drawActions(id) {
return `
<td class="right aligned">
<button class="ui green mini icon button edit" data-id="${id}" title="Promesar">
<i class="right chevron icon"></i>
</button>
<button class="ui red mini icon button remove" data-id="${id}" title="Abandonar">
<i class="trash icon"></i>
</button>
</td>`
}
}
class PendingReservations extends Reservations {
constructor({component_id, formatters = {date, ufs}}) {
super({component_id, formatters})
}
}
class RejectedReservations extends Reservations {
constructor({component_id, formatters = {date, ufs}}) {
super({component_id, formatters})
}
columnsData() {
const data = super.columnsData()
return this.reservations.map((reservation, idx) => {
data[idx]['estado'] = reservation.state.charAt(0).toUpperCase() + reservation.state.slice(1)
data[idx]['comentarios'] = reservation.comments?.join('<br />\n') ?? ''
return data[idx]
})
}
drawActions(id) {
return ''
}
}
const reservations = {
components: {
projects: null,
loader: null,
results: null,
controls: null,
reservations: {
active: null,
pending: null,
rejected: null
},
modals: {
add: null
}
},
display: {
loader: '',
results: '',
},
get: {
send: (project_id, url_segment, component) => {
const url = `/api/ventas/reservations/project/${project_id}/${url_segment}`
return APIClient.fetch(url).then(response => response.json()).then(json => {
if (json.reservations.length === 0) {
component.empty()
return
}
component.set().reservations(json.reservations).draw()
})
},
active: project_id => {
return reservations.get.send(project_id, 'active', reservations.components.reservations.active)
/*const url = `/ventas/reservations/project/${project_id}/active`
return APIClient.fetch(url).then(json => {
if (json.reservations.length === 0) {
return
}
this.components.reservations.active.set().reservations(json.reservations).draw()
})*/
},
pending: project_id => {
return reservations.get.send(project_id, 'pending', reservations.components.reservations.pending)
/*const url = `/ventas/reservations/project/${project_id}/pending`
return APIClient.fetch(url).then(json => {
if (json.reservations.length === 0) {
return
}
this.components.reservations.pending.set().reservations(json.reservations).draw()
})*/
},
rejected: project_id => {
return reservations.get.send(project_id, 'rejected', reservations.components.reservations.rejected)
/*const url = `/ventas/reservations/project/${project_id}/rejected`
return APIClient.fetch(url).then(json => {
if (json.reservations.length === 0) {
return
}
this.components.reservations.rejected.set().reservations(json.reservations).draw()
})*/
},
reservations: project_id => {
reservations.loading.show()
const promises = []
promises.push(reservations.get.active(project_id))
promises.push(reservations.get.pending(project_id))
promises.push(reservations.get.rejected(project_id))
return Promise.any(promises).then(() => {
reservations.loading.hide()
})
}
},
loading: {
show: () => {
reservations.components.loader.style.display = reservations.display.loader
},
hide: () => {
reservations.components.loader.style.display = 'none'
}
},
action: {
reset: event => {
event.preventDefault()
reservations.components.projects.current_project = null
Object.entries(reservations.components.reservations).forEach(([key, value]) => {
reservations.components.reservations[key].reservations = []
reservations.components.reservations[key].hide()
})
reservations.show.projects()
return false
},
up: event => {
event.preventDefault()
Object.values(reservations.components.reservations).forEach(reservations => reservations.hide())
reservations.show.projects()
return false
},
add: event => {
event.preventDefault()
reservations.components.modals.add.show()
return false
},
reservations: () => {
Object.values(reservations.components.reservations).forEach(reservations => {
reservations.draw()
})
}
},
show: {
projects: () => {
reservations.components.projects.show()
reservations.components.results.style.display = 'none'
},
results: () => {
reservations.components.projects.hide()
reservations.components.results.style.display = reservations.display.results
}
},
setup(configuration) {
const formatters = {
date: new Intl.DateTimeFormat('es-CL', {year: 'numeric', month: 'long', day: 'numeric'}),
ufs: new Intl.NumberFormat('es-CL', {minimumFractionDigits: 2, maximumFractionDigits: 2})
}
this.components.loader = document.getElementById(configuration.ids.loader)
this.components.projects = new Projects({component_id: configuration.ids.projects, title_id: configuration.ids.project})
this.components.controls = new Controls({component_id: configuration.ids.controls})
this.components.reservations.active = new ActiveReservations({component_id: configuration.ids.active, formatters})
this.components.reservations.pending = new PendingReservations({component_id: configuration.ids.pending, formatters})
this.components.reservations.rejected = new RejectedReservations({component_id: configuration.ids.rejected, formatters})
this.display.loader = this.components.loader.style.display
this.loading.hide()
$(`#${configuration.ids.tabs} .item`).tab()
this.components.results = document.getElementById(configuration.ids.results)
this.display.results = this.components.results.style.display
this.show.projects()
this.components.modals.add = new AddReservationModal()
}
}
$(document).ready(() => {
reservations.setup({
ids: {
projects: 'projects',
project: 'project',
results: 'results',
loader: 'loader',
controls: 'controls',
tabs: 'tabs',
active: 'active_reservations',
pending: 'pending_reservations',
rejected: 'rejected_reservations',
}
})
})
</script>
@endpush

View File

@ -0,0 +1,733 @@
<div class="ui modal" id="add_reservation_modal">
<div class="header">
Agregar Cierre
</div>
<div class="content">
<form class="ui form" id="add_reservation_form">
<input type="hidden" name="project_id" />
<div class="three wide required field">
<label>Fecha</label>
<div class="ui calendar" id="add_date">
<div class="ui icon input">
<i class="calendar icon"></i>
<input type="text" name="date" />
</div>
</div>
</div>
<div class="fields">
<div class="three wide required field">
<label>RUT</label>
<div class="ui right labeled input" id="add_rut">
<input type="text" name="rut" placeholder="RUT" />
<div class="ui basic label">-<span id="add_digit"></span></div>
</div>
</div>
<div class="field">
<label></label>
<div class="ui inline loader" id="add_rut_loader"></div>
</div>
</div>
<div class="fields">
<div class="three wide required field">
<label>Nombre</label>
<input type="text" name="name" placeholder="Nombre" />
</div>
<div class="six wide required field">
<label>Apellidos</label>
<input type="text" name="last_name" placeholder="Apellido Paterno" />
</div>
<div class="six wide field">
<label></label>
<input type="text" name="last_name2" placeholder="Apellido Materno" />
</div>
</div>
<div class="fields">
<div class="three wide field">
<label>Dirección</label>
<input type="text" name="calle" placeholder="Calle" />
</div>
<div class="field">
<label></label>
<input type="text" name="numero" placeholder="" />
</div>
<div class="three wide field">
<label></label>
<input type="text" name="extra" placeholder="Otros Detalles" />
</div>
</div>
<div class="fields">
<div class="three wide field">
<label>Comuna</label>
<div class="ui search selection dropdown" id="add_comuna">
<input type="hidden" name="comuna" />
<i class="dropdown icon"></i>
<div class="default text">Comuna</div>
<div class="menu"></div>
</div>
</div>
<div class="seven wide field">
<label>Región</label>
<div class="ui search selection dropdown" id="add_region">
<input type="hidden" name="region" />
<i class="dropdown icon"></i>
<div class="default text">Región</div>
<div class="menu">
@foreach($regions as $region)
<div class="item" data-value="{{$region->id}}">{{$region->numeral}} - {{$region->descripcion}}</div>
@endforeach
</div>
</div>
</div>
</div>
<div class="fields">
<div class="field">
<label>Telefono</label>
<input type="text" name="phone" placeholder="Telefono" />
</div>
<div class="field">
<label>Correo</label>
<div class="ui labeled input">
<input type="text" name="email_name" placeholder="Correo" />
<div class="ui basic label">@</div>
<input type="text" name="email_domain" placeholder="Dominio" />
</div>
</div>
</div>
<div class="fields">
<div class="field">
<label>Estado Civil</label>
<input type="text" name="civil_status" placeholder="Estado Civil" />
</div>
<div class="field">
<label>Profesión</label>
<input type="text" name="profession" placeholder="Profesión" />
</div>
<div class="field">
<label>Fecha de Nacimiento</label>
<div class="ui calendar" id="add_birthdate">
<div class="ui icon input">
<i class="calendar icon"></i>
<input type="text" name="birthdate" />
</div>
</div>
</div>
</div>
<div class="six wide field">
<label>Operador *</label>
<div class="ui clearable search selection dropdown" id="add_broker">
<input type="hidden" name="broker" />
<i class="dropdown icon"></i>
<div class="default text">Operador</div>
<div class="menu"></div>
</div>
</div>
<div class="field">
<label>Agregar Promoción</label>
<button type="button" class="ui icon button" id="add_promotion">
<i class="plus icon"></i>
</button>
</div>
<div id="add_promotions"></div>
<h4 class="ui dividing header">Unidades</h4>
<div class="fields" id="add_unit_buttons"></div>
<div id="add_units"></div>
</form>
</div>
<div class="actions">
<div class="ui cancel button">
Cancelar
</div>
<div class="ui green ok button">
Agregar
</div>
</div>
</div>
@include('layout.body.scripts.rut')
@push('page_scripts')
<script>
class AddModalPromotions {
ids = {
button: '',
elements: ''
}
data = {
promotions: []
}
components = {
button: null,
promotions: null,
}
display = {
button: ''
}
constructor() {
this.ids = {
button: 'add_promotion',
elements: 'add_promotions'
}
this.setup()
}
add() {
const idx = Math.max(this.data.promotions.length, 0, Math.max(...this.data.promotions) + 1)
this.data.promotions.push(idx)
this.draw.promotions()
}
reset() {
this.data.promotions = []
this.draw.promotions()
}
remove(idx) {
this.data.promotions = this.data.promotions.filter(promotion => promotion !== idx)
this.draw.promotions()
}
draw = {
promotion: idx => {
const promotions = this.data.promotions.map(promotion => {
return `<div class="item" data-value="${promotion.id}">${promotion.name}</div>`
})
return [
`<div class="fields promotion" data-id="${idx}">`,
'<div class="three wide field">',
'<label>Promoción</label>',
`<div class="ui search selection dropdown">`,
'<input type="hidden" name="promotions[]" />',
'<i class="dropdown icon"></i>',
'<div class="default text">Promoción</div>',
`<div class="menu">${promotions.join('')}</div>`,
'</div>',
'</div>',
'<div class="two wide field">',
'<label></label>',
`<button class="ui red tiny icon button remove_promotion" type="button" data-id="${idx}"><i class="trash icon"></i></button>`,
'</div>',
'</div>'
].join('')
},
promotions: () => {
if (this.data.promotions.length === 0) {
this.components.button.parentElement.style.display = 'none'
this.components.promotions.innerHTML = ''
return
}
this.components.button.parentElement.style.display = this.display.button
this.components.promotions.innerHTML = this.data.promotions.map((promotion, idx) => {
return this.draw.promotion(idx)
}).join('')
this.components.promotions.querySelectorAll('.dropdown').forEach(dropdown => {
$(dropdown).dropdown()
})
this.components.promotions.querySelectorAll('.remove_promotion').forEach(button => {
button.addEventListener('click', () => {
const idx = Number(button.dataset.id)
this.remove(idx)
})
})
}
}
setup() {
this.components.button = document.getElementById(this.ids.button)
this.components.promotions = document.getElementById(this.ids.elements)
this.components.button.addEventListener('click', () => {
this.add()
})
this.display.button = this.components.button.parentElement.style.display
this.draw.promotions()
}
}
class AddModalUnits {
ids = {
buttons_holder: '',
units: ''
}
data = {
button_map: {},
types: {},
units: [],
}
components = {
buttons_holder: null,
units: null,
}
constructor() {
this.ids = {
buttons_holder: 'add_unit_buttons',
units: 'add_units'
}
this.data.button_map = {
'departamento': 'building',
'estacionamiento': 'car',
'bodega': 'warehouse',
'terraza': 'vector square'
}
this.setup()
}
draw = {
button: type => {
return [
'<div class="field">',
`<button class="ui icon button" type="button" data-type="${type}" title="${type.charAt(0).toUpperCase() + type.slice(1)}">`,
'<i class="plus icon"></i>',
`<i class="${this.data.button_map[type]} icon"></i>`,
'</button>',
'</div>'
].join('')
},
buttons: () => {
this.components.buttons_holder.innerHTML = Object.keys(this.data.types).map(type => {
return this.draw.button(type)
}).join('')
this.components.buttons_holder.querySelectorAll('.button').forEach(button => {
button.addEventListener('click', () => {
const type = button.dataset.type
this.add(type)
})
})
},
units: () => {
if (this.data.units.length === 0) {
this.components.units.innerHTML = ''
return
}
this.components.units.innerHTML = this.data.units.map(unit => {
return [
'<div class="fields">',
'<div class="four wide field">',
`<label>${unit.type.charAt(0).toUpperCase() + unit.type.slice(1)}</label>`,
`<div class="ui search selection dropdown">`,
'<input type="hidden" name="units[]" />',
'<i class="dropdown icon"></i>',
`<div class="default text">${unit.type.charAt(0).toUpperCase() + unit.type.slice(1)}</div>`,
'<div class="menu">',
this.data.types[unit.type].map(unit => {
return `<div class="item" data-value="${unit.value}">${unit.name}</div>`
}).join(''),
'</div>',
'</div>',
'</div>',
'<div class="field">',
'<label></label>',
`<button class="ui red tiny icon button remove_unit" type="button" data-id="${unit.idx}"><i class="trash icon"></i></button>`,
'</div>',
'</div>',
].join('')
}).join('')
this.components.units.querySelectorAll('.dropdown').forEach(dropdown => {
$(dropdown).dropdown()
})
this.components.units.querySelectorAll('.remove_unit').forEach(button => {
button.addEventListener('click', () => {
const idx = Number(button.dataset.id)
this.remove(idx)
})
})
}
}
reset() {
this.data.units = []
this.draw.units()
}
add(type) {
const idx = Math.max(this.data.units.length, 0, Math.max(...this.data.units.map(unit => unit.idx)) + 1)
this.data.units.push({idx, type})
this.draw.units()
}
remove(idx) {
this.data.units = this.data.units.filter(unit => unit.idx !== idx)
this.draw.units()
}
setup() {
this.components.buttons_holder = document.getElementById(this.ids.buttons_holder)
this.components.units = document.getElementById(this.ids.units)
this.draw.buttons()
}
}
class AddReservationModal {
ids = {
modal: '',
form: '',
date: '',
rut: '',
digit: '',
birthdate: '',
comuna: '',
region: '',
broker: '',
promotion_button: '',
promotions: '',
unit_buttons: '',
units: ''
}
components = {
$modal: null,
form: null,
$date: null,
$rut: null,
rut: null,
digit: null,
$birthdate: null,
$comuna: null,
$region: null,
$broker: null,
promotion_button: null,
promotions: null,
unit_buttons: null,
units: null,
$loader: null
}
data = {
current_project: null,
comunas: {},
brokers: {},
promotions: {},
unit_buttons: [],
units: {},
added_units: {},
current_user: null
}
maps = {
unit_types: {
departamento: 'building',
estacionamiento: 'car',
bodega: 'warehouse',
terraza: 'tree',
}
}
constructor() {
this.ids = {
modal: 'add_reservation_modal',
form: 'add_reservation_form',
date: 'add_date',
rut: 'add_rut',
digit: 'add_digit',
birthdate: 'add_birthdate',
comuna: 'add_comuna',
region: 'add_region',
broker: 'add_broker',
promotion_button: 'add_promotion',
promotions: 'add_promotions',
unit_buttons: 'add_unit_buttons',
units: 'add_units',
loader: 'add_rut_loader'
}
this.setup()
}
load(project_id) {
this.reset()
this.data.current_project = project_id
this.components.form.querySelector('input[name="project_id"]').value = project_id
this.get.brokers(project_id)
this.get.promotions(project_id).then(promotions => {
this.components.promotions.data.promotions = promotions
this.components.promotions.draw.promotions()
})
this.get.units(project_id).then(units => {
this.components.units.data.types = units
this.components.units.draw.buttons()
})
}
reset() {
this.components.form.reset()
this.components.promotions.reset()
this.components.units.reset()
}
add() {
const url = '/api/ventas/reservations/add'
const form = document.getElementById(this.ids.form)
const body = new FormData(form)
const date = this.components.$date.calendar('get date')
console.debug(date)
body.set('date', [date.getFullYear(), date.getMonth() + 1, date.getDate()].join('-'))
body.set('comuna', this.components.$comuna.dropdown('get value'))
body.set('region', this.components.$region.dropdown('get value'))
body.set('broker_rut', this.components.$broker.dropdown('get value'))
/*body.set('promotions[]', '')
this.components.promotions.forEach(promotion => {
body.append('promotions[]', promotion.querySelector('promotion').value)
})*/
/*body.set('units[]', '')
this.components.units.forEach(unit => {
body.append('units[]', unit.querySelector('unit').value)
})*/
const method = 'post'
return APIClient.fetch(url, {method, body}).then(response => response.json()).then(json => {
if (json.success) {
window.location.reload()
}
})
}
get = {
comunas: region_id => {
if (region_id in this.data.comunas) {
this.components.$comuna.dropdown('change values', this.data.comunas[region_id])
if (this.data.current_user !== null && this.data.current_user?.direccion?.comuna !== null) {
this.components.$comuna.dropdown('set selected', this.data.current_user.direccion.comuna.id)
}
return
}
const uri = `/api/region/${region_id}/comunas`
return APIClient.fetch(uri).then(response => response.json()).then(json => {
if (json.comunas.length === 0) {
return
}
this.data.comunas[region_id] = json.comunas.map(comuna => {
return {
text: comuna.descripcion,
name: comuna.descripcion,
value: comuna.id
}
})
this.components.$comuna.dropdown('change values', this.data.comunas[region_id])
if (this.data.current_user !== null && this.data.current_user?.direccion?.comuna !== null) {
this.components.$comuna.dropdown('set selected', this.data.current_user.direccion.comuna.id)
}
})
},
brokers: project_id => {
if (project_id in this.data.brokers) {
return new Promise((resolve, reject) => {
resolve(this.data.brokers[project_id])
})
}
const uri = `/api/proyecto/${project_id}/brokers`
return APIClient.fetch(uri).then(response => response.json()).then(json => {
if (json.contracts.length === 0) {
return
}
const formatter = new Intl.NumberFormat('es-CL', { style: 'percent', minimumFractionDigits: 2 })
this.data.brokers[project_id] = json.contracts.map(contract => {
return {
id: contract.id,
broker_rut: contract.broker_rut,
commission: formatter.format(contract.commission),
name: '',
text: '',
}
})
const promises = []
json.contracts.forEach(contract => {
promises.push(this.get.broker(contract.broker_rut))
})
return Promise.all(promises).then(data => {
data.forEach(broker => {
if (!('rut' in broker)) {
return
}
const idx = this.data.brokers[project_id].findIndex(contract => contract.broker_rut === broker.rut)
this.data.brokers[project_id][idx].name = this.data.brokers[project_id][idx].text = `${broker.name} - ${this.data.brokers[project_id][idx].commission}`
})
this.fill.brokers()
})
})
},
broker: (broker_rut) => {
const uri = `/api/proyectos/broker/${broker_rut}`
return APIClient.fetch(uri).then(response => response.json()).then(json => {
if (!('broker' in json)) {
return []
}
return json.broker
})
},
promotions: project_id => {
if (project_id in this.data.promotions) {
return new Promise((resolve, reject) => {
resolve(this.data.promotions[project_id])
})
}
const uri = `/api/proyecto/${project_id}/promotions`
return APIClient.fetch(uri).then(response => response.json()).then(json => {
if (json.promotions.length === 0) {
return this.data.promotions[project_id] = []
}
return this.data.promotions[project_id] = json.promotions.map(promotion => {
return {
text: promotion.name,
name: promotion.name,
value: promotion.id
}
})
})
},
units: project_id => {
if (project_id in this.data.units) {
return new Promise((resolve, reject) => {
resolve(this.data.units[project_id])
})
}
const uri = `/api/proyecto/${project_id}/unidades`
return APIClient.fetch(uri).then(response => response.json()).then(json => {
if (json.total === 0) {
this.data.units[project_id] = {}
return this.data.units[project_id]
}
if (!(project_id in this.data.units)) {
this.data.units[project_id] = {}
}
Object.entries(json.unidades).forEach(([type, units]) => {
if (!(type in this.data.units[project_id])) {
this.data.units[project_id][type] = []
}
units.forEach(unit => {
this.data.units[project_id][type].push({
text: unit.descripcion,
name: unit.descripcion,
value: unit.id
})
})
})
return this.data.units[project_id]
})
},
user: rut => {
if (this.data.current_user !== null && this.data.current_user?.rut === rut) {
return this.data.current_user
}
this.loader.show()
const uri = `/api/persona/${rut}`
return APIClient.fetch(uri).then(response => response.json()).then(json => {
this.loader.hide()
if (!json.success) {
return
}
this.data.current_user = json.persona
return json.persona
})
}
}
fill = {
user: user => {
const form = this.components.form
form.querySelector('input[name="name"]').value = user.nombres || ''
form.querySelector('input[name="last_name"]').value = user.apellidoPaterno || ''
form.querySelector('input[name="last_name2"]').value = user.apellidoMaterno || ''
form.querySelector('input[name="calle"]').value = user.datos?.direccion?.calle || ''
form.querySelector('input[name="numero"]').value = user.datos?.direccion?.numero || ''
form.querySelector('input[name="extra"]').value = user.datos?.direccion?.extra || ''
if (parseInt(this.components.$region.dropdown('get value')) !== user.datos?.direccion?.comuna?.provincia?.region?.id) {
this.components.$region.dropdown('set selected', user.datos?.direccion?.comuna?.provincia?.region?.id)
} else {
this.components.$comuna.dropdown('set selected', user.datos?.direccion?.comuna?.id)
}
form.querySelector('input[name="phone"]').value = user.datos?.telefono
const email_parts = user.datos?.email.split('@')
form.querySelector('input[name="email_name"]').value = email_parts[0]
form.querySelector('input[name="email_domain"]').value = email_parts[1]
if ('estadoCivil' in user.datos) {
form.querySelector('input[name="civil_status"]').value = user.datos?.estadoCivil.charAt(0).toUpperCase() + user.datos?.estadoCivil.slice(1)
} else {
form.querySelector('input[name="civil_status"]').value = ''
}
if ('ocupacion' in user.datos) {
form.querySelector('input[name="profession"]').value = user.datos?.ocupacion
} else {
form.querySelector('input[name="profession"]').value = ''
}
if ('fechaNacimiento' in user.datos) {
this.components.$birthdate.calendar('set date', user.datos?.fechaNacimiento)
}
},
brokers: () => {
this.components.$broker.dropdown('change values', this.data.brokers[this.data.current_project])
},
units: () => {
const buttons = []
Object.entries(this.maps.unit_types).forEach(([type, map]) => {
if (!(type in this.data.units[this.data.current_project])) {
return
}
buttons.push(`<div class="field"><div class="ui icon button" data-type="${type}" title="${type.charAt(0).toUpperCase() + type.slice(1)}"><i class="plus icon"></i><i class="${map} icon"></i></div></div>`)
})
this.components.unit_buttons.innerHTML = buttons.join('')
this.components.unit_buttons.querySelectorAll('.ui.icon.button').forEach(button => {
button.addEventListener('click', () => {
this.units.add(button.dataset.type)
})
})
}
}
watch = {
region: (value, text, $choice) => {
this.get.comunas(value)
},
}
loader = {
show: () => {
this.components.$loader.show()
},
hide: () => {
this.components.$loader.hide()
}
}
show() {
this.reset()
this.components.$modal.modal('show')
}
setup() {
this.components.$modal = $(`#${this.ids.modal}`)
this.components.form = document.getElementById(this.ids.form)
this.components.$date = $(`#${this.ids.date}`)
this.components.rut = document.getElementById(this.ids.rut)
this.components.digit = document.getElementById(this.ids.digit)
this.components.$birthdate = $(`#${this.ids.birthdate}`)
this.components.$comuna = $(`#${this.ids.comuna}`)
this.components.$region = $(`#${this.ids.region}`)
this.components.$broker = $(`#${this.ids.broker}`)
this.components.promotions = new AddModalPromotions()
this.components.units = new AddModalUnits()
this.components.$modal.modal({
onApprove: () => {
this.add()
}
})
this.components.form.addEventListener('submit', event => {
event.preventDefault()
this.add()
return false
})
const cdo = structuredClone(calendar_date_options)
cdo['startDate'] = new Date()
cdo['maxDate'] = new Date()
this.components.$date.calendar(cdo)
const rutInput = this.components.rut.querySelector('input')
rutInput.addEventListener('input', event => {
const value = event.currentTarget.value.replace(/\D/g, '')
if (value.length <= 3) {
return
}
this.components.digit.textContent = Rut.digitoVerificador(value)
})
rutInput.addEventListener('blur', event => {
const value = event.currentTarget.value.replace(/\D/g, '')
if (value.length <= 3) {
return
}
event.currentTarget.value = Rut.format(value)
this.get.user(value).then(user => {
this.fill.user(user)
})
})
const cdo2 = structuredClone(cdo)
cdo2['startDate'].setFullYear(cdo2['startDate'].getFullYear() - 18)
cdo2['maxDate'].setFullYear(cdo2['maxDate'].getFullYear() - 18)
this.components.$birthdate.calendar(cdo2)
this.components.$region.dropdown({
fireOnInit: true,
onChange: this.watch.region
})
this.components.$region.dropdown('set selected', 13)
this.components.$comuna.dropdown()
this.components.$broker.dropdown()
this.components.$loader = $(`#${this.ids.loader}`)
}
}
</script>
@endpush

View File

@ -5,28 +5,8 @@ use DI\Bridge\Slim\Bridge;
require_once 'composer.php';
function buildApp() {
$builder = new ContainerBuilder();
$folders = [
'settings',
'setups'
];
foreach ($folders as $folder_name) {
$folder = implode(DIRECTORY_SEPARATOR, [
__DIR__,
$folder_name
]);
if (!file_exists($folder)) {
continue;
}
$files = new FilesystemIterator($folder);
foreach ($files as $file) {
if ($file->isDir()) {
continue;
}
$builder->addDefinitions($file->getRealPath());
}
}
$app = Bridge::create($builder->build());
require_once 'container.php';
$app = Bridge::create(buildContainer());
$folder = implode(DIRECTORY_SEPARATOR, [
__DIR__,
'middlewares'

33
app/setup/container.php Normal file
View File

@ -0,0 +1,33 @@
<?php
use Psr\Container\ContainerInterface;
use DI\ContainerBuilder;
/**
* @return ContainerInterface
* @throws Exception
*/
function buildContainer(): ContainerInterface
{
$builder = new ContainerBuilder();
$folders = [
'settings',
'setups'
];
foreach ($folders as $folder_name) {
$folder = implode(DIRECTORY_SEPARATOR, [
__DIR__,
$folder_name
]);
if (!file_exists($folder)) {
continue;
}
$files = new FilesystemIterator($folder);
foreach ($files as $file) {
if ($file->isDir()) {
continue;
}
$builder->addDefinitions($file->getRealPath());
}
}
return $builder->build();
}

View File

@ -27,8 +27,8 @@ return [
$container->get(Monolog\Processor\UidProcessor::class),
];
},
'defaultMonologHandlers' => function(ContainerInterface $container) {
$baseHandlers = [
'baseDefaultHandlers' => function(ContainerInterface $container) {
return [
'critical' => [
'handler' => Monolog\Handler\RotatingFileHandler::class,
'filename' => 'critical.log',
@ -49,56 +49,28 @@ return [
'levels' => [Monolog\Level::Debug, Monolog\Level::Info]
],
];
},
'developmentHandlers' => function(ContainerInterface $container) {
$baseHandlers = $container->get('baseDefaultHandlers');
$baseHandlers['critical']['handler'] = Monolog\Handler\StreamHandler::class;
$baseHandlers['error']['handler'] = Monolog\Handler\StreamHandler::class;
$baseHandlers['notices']['handler'] = Monolog\Handler\StreamHandler::class;
$baseHandlers['notices']['filename'] = 'notices.log';
$baseHandlers['debug']['handler'] = Monolog\Handler\StreamHandler::class;
$baseHandlers['debug']['filename'] = 'debug.log';
return $baseHandlers;
},
'defaultMonologHandlers' => function(ContainerInterface $container) {
$key = 'baseDefault';
if ($container->has('ENVIRONMENT') and $container->get('ENVIRONMENT') === 'development') {
$baseHandlers['critical']['handler'] = Monolog\Handler\StreamHandler::class;
$baseHandlers['error']['handler'] = Monolog\Handler\StreamHandler::class;
$baseHandlers['notices']['handler'] = Monolog\Handler\StreamHandler::class;
$baseHandlers['notices']['filename'] = 'notices.log';
$baseHandlers['debug']['handler'] = Monolog\Handler\StreamHandler::class;
$baseHandlers['debug']['filename'] = 'debug.log';
}
$handlers = [];
foreach ($baseHandlers as $handlerData) {
if (in_array($handlerData['handler'], [
Monolog\Handler\StreamHandler::class,
Monolog\Handler\RotatingFileHandler::class,
])) {
$params = [
"/logs/{$handlerData['filename']}",
];
if ($handlerData['handler'] === Monolog\Handler\RotatingFileHandler::class) {
$params []= 10;
}
$handler = new $handlerData['handler'](...$params)
->setFormatter($container->get(Monolog\Formatter\LineFormatter::class));
} elseif ($handlerData['handler'] === Incoviba\Common\Implement\Log\Handler\MySQL::class) {
$params = [
$container->get(Incoviba\Common\Define\Connection::class)
];
$handler = new $handlerData['handler'](...$params)
->setFormatter(new \Incoviba\Common\Implement\Log\Formatter\PDO());
} elseif ($handlerData['handler'] === Monolog\Handler\RedisHandler::class) {
$params = [
$container->get(Predis\ClientInterface::class),
"logs:{$handlerData['name']}"
];
$handler = new $handlerData['handler'](...$params);
$key = 'development';
if (!$container->has("{$key}Handlers")) {
$key = 'baseDefault';
}
$params = [
$handler,
];
if (is_array($handlerData['levels'])) {
foreach ($handlerData['levels'] as $level) {
$params []= $level;
}
} else {
$params []= $handlerData['levels'];
$params []= Monolog\Level::Emergency;
}
$params []= false;
$handlers []= new Monolog\Handler\FilterHandler(...$params);
}
return $handlers;
$baseHandlers = $container->get("{$key}Handlers");
$builder = $container->get(Incoviba\Common\Implement\Log\Processor\ArrayBuilder::class);
return $builder->build($baseHandlers);
},
Psr\Log\LoggerInterface::class => function(ContainerInterface $container) {
return new Monolog\Logger('incoviba',
@ -108,6 +80,26 @@ return [
$container->get(DateTimeZone::class)
);
},
'jsonHandlers' => function(ContainerInterface $container) {
$baseHandlers = $container->get('baseDefaultHandlers');
$baseHandlers['debug']['handler'] = Monolog\Handler\RotatingFileHandler::class;
$baseHandlers['debug']['filename'] = 'info.json';
$baseHandlers['debug']['formatter'] = Monolog\Formatter\JsonFormatter::class;
$baseHandlers['notices']['handler'] = Monolog\Handler\RotatingFileHandler::class;
$baseHandlers['notices']['filename'] = 'notices.json';
$baseHandlers['notices']['formatter'] = Monolog\Formatter\JsonFormatter::class;
return $baseHandlers;
},
'jsonLogger' => function(ContainerInterface $container) {
$builder = $container->get(Incoviba\Common\Implement\Log\Processor\ArrayBuilder::class);
$handlers = $builder->build($container->get('jsonHandlers'));
return new Monolog\Logger('json',
$handlers,
[$container->get(Incoviba\Common\Implement\Log\Processor\User::class)]
+ $container->get('baseMonologProcessors'),
$container->get(DateTimeZone::class)
);
},
'loginLogger' => function(ContainerInterface $container) {
return new Monolog\Logger('login',
[
@ -126,11 +118,35 @@ return [
$container->get(DateTimeZone::class)
);
},
'externalLogger' => function(ContainerInterface $container) {
return new Monolog\Logger('external',
[
new Monolog\Handler\RedisHandler($container->get(Predis\ClientInterface::class), 'logs:external'),
'externalHandlers' => function(ContainerInterface $container) {
return [
'critical' => [
'handler' => Monolog\Handler\RedisHandler::class,
'name' => 'external:critical',
'levels' => Monolog\Level::Critical
],
'error' => [
'handler' => Monolog\Handler\RedisHandler::class,
'name' => 'external:error',
'levels' => [Monolog\Level::Error, Monolog\Level::Error],
],
'notices' => [
'handler' => Monolog\Handler\RedisHandler::class,
'name' => 'external:notices',
'levels' => [Monolog\Level::Notice, Monolog\Level::Warning],
],
'debug' => [
'handler' => Monolog\Handler\RedisHandler::class,
'name' => 'external:debug',
'levels' => [Monolog\Level::Debug, Monolog\Level::Info],
],
];
},
'externalLogger' => function(ContainerInterface $container) {
$builder = $container->get(Incoviba\Common\Implement\Log\Processor\ArrayBuilder::class);
$handlers = $builder->build($container->get('externalHandlers'));
return new Monolog\Logger('external',
$handlers,
$container->get('baseMonologProcessors'),
$container->get(DateTimeZone::class)
);

View File

@ -116,7 +116,18 @@ return [
->registerSub($container->get(Incoviba\Service\Contabilidad\Cartola\BCI\Mes::class));
},
'TokuClient' => function(ContainerInterface $container) {
$logger = $container->get('externalLogger');
$stack = GuzzleHttp\HandlerStack::create();
$stack->push(GuzzleHttp\Middleware::mapRequest(function(Psr\Http\Message\RequestInterface $request) use ($logger) {
$logger->info('Toku Request', [
'method' => $request->getMethod(),
'uri' => (string) $request->getUri(),
'headers' => $request->getHeaders(),
'body' => $request->getBody()->getContents(),
]);
}));
return new GuzzleHttp\Client([
'handler' => $stack,
'base_uri' => $container->get('TOKU_URL'),
'headers' => [
'x-api-key' => $container->get('TOKU_TOKEN'),
@ -149,25 +160,37 @@ return [
$container->get(Incoviba\Service\UF::class)
);
$service->setLogger($container->get('externalLogger'));
$service->setAltLogger($container->get('jsonLogger'));
return $service;
},
Incoviba\Service\Venta\MediosPago\Toku::class => function(ContainerInterface $container) {
return (new Incoviba\Service\Venta\MediosPago\Toku(
return new Incoviba\Service\Venta\MediosPago\Toku(
$container->get('externalLogger'),
$container->get(Incoviba\Common\Define\Connection::class),
$container->get(Incoviba\Service\HMAC::class)
))
)
->register('customer', $container->get(Incoviba\Service\Venta\MediosPago\Toku\Customer::class))
->register('subscription', $container->get(Incoviba\Service\Venta\MediosPago\Toku\Subscription::class))
->register('invoice', $container->get(Incoviba\Service\Venta\MediosPago\Toku\Invoice::class));
},
Pheanstalk\Pheanstalk::class => function(ContainerInterface $container) {
return Pheanstalk\Pheanstalk::create(
$container->get('BEANSTALKD_HOST'),
$container->has('BEANSTALKD_PORT') ? $container->get('BEANSTALKD_PORT') : 11300
);
},
Incoviba\Service\MQTT::class => function(ContainerInterface $container) {
return new Incoviba\Service\MQTT()
->register('default', $container->get(Incoviba\Service\MQTT\Pheanstalk::class));
},
Incoviba\Service\Queue::class => function(ContainerInterface $container) {
return (new Incoviba\Service\Queue(
return new Incoviba\Service\Queue(
$container->get(Psr\Log\LoggerInterface::class),
$container->get(Incoviba\Service\Job::class),
$container->get(Incoviba\Service\Worker\Request::class)
))
)
->register('request', $container->get(Incoviba\Service\Worker\Request::class))
->register('service', $container->get(Incoviba\Service\Worker\Service::class))
->register('dummy', $container->get(Incoviba\Service\Worker\Dummy::class))
->register('checkExternal', $container->get(Incoviba\Service\Worker\CheckExternal::class));
},
@ -196,10 +219,10 @@ return [
);
},
Incoviba\Service\External::class => function(ContainerInterface $container) {
return (new Incoviba\Service\External(
return new Incoviba\Service\External(
$container->get('externalLogger'),
$container->get(Incoviba\Service\Queue::class)
))
)
->register($container->get(Incoviba\Service\Venta\MediosPago\Toku::class));
}
];

View File

@ -0,0 +1,34 @@
<?php
namespace Incoviba\Controller\API;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Incoviba\Common\Ideal;
use Incoviba\Common\Implement;
use Incoviba\Exception\ServiceAction;
use Incoviba\Repository;
use Incoviba\Service;
class Personas extends Ideal\Controller
{
use withJson;
public function get(ServerRequestInterface $request, ResponseInterface $response,
Service\Persona $personaService, int $rut): ResponseInterface
{
$output = [
'rut' => $rut,
'persona' => null,
'success' => false
];
try {
$persona = $personaService->getById($rut);
$output['persona'] = $persona;
$output['success'] = true;
} catch (ServiceAction\Read $exception) {
$this->logger->error($exception->getMessage(), ['exception' => $exception]);
}
return $this->withJson($response, $output);
}
}

View File

@ -187,5 +187,18 @@ class Proyectos
}
return $this->withJson($response, $output);
}
public function promotions(ServerRequestInterface $request, ResponseInterface $response,
Service\Venta\Promotion $promotionService, int $proyecto_id): ResponseInterface
{
$output = [
'project_id' => $proyecto_id,
'promotions' => []
];
try {
$output['promotions'] = $promotionService->getByProject($proyecto_id);
} catch (Read $exception) {
return $this->withError($response, $exception);
}
return $this->withJson($response, $output);
}
}

View File

@ -22,20 +22,4 @@ class Queues extends Ideal\Controller
}
return $this->withJson($response, $output);
}
public function jobs(ServerRequestInterface $request, ResponseInterface $response,
Service\Queue $queueService): ResponseInterface
{
$output = [
'jobs' => array_column($queueService->getPendingJobs(), 'id')
];
return $this->withJson($response, $output);
}
public function run(ServerRequestInterface $request, ResponseInterface $response, Service\Queue $queueService,
int $job_id): ResponseInterface
{
if ($queueService->runJob($job_id, $request)) {
return $response->withStatus(200);
}
return $response->withStatus(422);
}
}

View File

@ -49,8 +49,11 @@ class Toku extends Controller
ResponseFactoryInterface $responseFactory,
Service\Venta\MediosPago\Toku $tokuService): ResponseInterface
{
$body = $request->getBody()->getContents();
$input = json_decode($body, true);
$input = $request->getParsedBody();
if ($input === null) {
$body = $request->getBody()->getContents();
$input = json_decode($body, true);
}
$this->logger->info('Toku payment success', ['input' => $input]);
try {
if ($tokuService->successEvent($input)) {
@ -111,6 +114,7 @@ class Toku extends Controller
if (!$container->has('TOKU_ENV') or strtolower($container->get('TOKU_ENV')) !== 'sandbox') {
return $this->withJson($response, ['success' => false], 409);
}
$this->logger->info('Toku reset');
$input = $request->getParsedBody();
$output = [
'input' => $input,
@ -119,7 +123,9 @@ class Toku extends Controller
try {
$tokuService->reset($input['skips'] ?? []);
$output['success'] = true;
} catch (Exception $exception) {}
} catch (Exception $exception) {
$this->logger->error($exception);
}
return $this->withJson($response, $output);
}
public function enqueue(ServerRequestInterface $request, ResponseInterface $response,
@ -140,4 +146,25 @@ class Toku extends Controller
}
return $this->withJson($response, $output);
}
public function update(ServerRequestInterface $request, ResponseInterface $response,
Service\Venta\MediosPago\Toku $tokuService, ?string $type = null): ResponseInterface
{
$body = $request->getBody()->getContents();
$input = json_decode($body, true);
$output = [
'type' => $type,
'input' => $input,
'output' => [],
'success' => false
];
try {
$output['output'] = $tokuService->update($input, $type);
$output['success'] = true;
} catch (Exception $exception) {
$this->logger->error($exception);
}
return $this->withJson($response, $output);
}
}

View File

@ -11,7 +11,8 @@ class Reservations
{
use withJson;
public function __invoke(ServerRequestInterface $request, ResponseInterface $response, Service\Venta\Reservation $reservationService): ResponseInterface
public function __invoke(ServerRequestInterface $request, ResponseInterface $response,
Service\Venta\Reservation $reservationService): ResponseInterface
{
$reservations = [];
try {
@ -22,6 +23,18 @@ class Reservations
return $this->withJson($response, compact('reservations'));
}
public function getByProject(ServerRequestInterface $request, ResponseInterface $response,
Service\Venta\Reservation $reservationService, int $project_id): ResponseInterface
{
$reservations = [];
try {
$reservations = $reservationService->getByProject($project_id);
} catch (ServiceAction\Read $exception) {
return $this->withError($response, $exception);
}
return $this->withJson($response, compact('reservations'));
}
public function get(ServerRequestInterface $request, ResponseInterface $response, Service\Venta\Reservation $reservationService, int $reservation_id): ResponseInterface
{
$output = [
@ -100,4 +113,47 @@ class Reservations
return $this->withJson($response, $output);
}
public function active(ServerRequestInterface $request, ResponseInterface $response,
Service\Venta\Reservation $reservationService, int $project_id): ResponseInterface
{
$output = [
'project_id' => $project_id,
'reservations' => [],
'success' => false,
];
try {
$output['reservations'] = $reservationService->getActive($project_id);
$output['success'] = true;
} catch (ServiceAction\Read) {}
return $this->withJson($response, $output);
}
public function pending(ServerRequestInterface $request, ResponseInterface $response,
Service\Venta\Reservation $reservationService, int $project_id): ResponseInterface
{
$output = [
'project_id' => $project_id,
'reservations' => [],
'success' => false,
];
try {
$output['reservations'] = $reservationService->getPending($project_id);
$output['success'] = true;
} catch (ServiceAction\Read) {}
return $this->withJson($response, $output);
}
public function rejected(ServerRequestInterface $request, ResponseInterface $response,
Service\Venta\Reservation $reservationService, int $project_id): ResponseInterface
{
$output = [
'project_id' => $project_id,
'reservations' => [],
'success' => false,
];
try {
$output['reservations'] = $reservationService->getRejected($project_id);
$output['success'] = true;
} catch (ServiceAction\Read) {}
return $this->withJson($response, $output);
}
}

View File

@ -4,6 +4,9 @@ namespace Incoviba\Controller;
use Incoviba\Common\Alias\View;
use Incoviba\Common\Implement\Exception\EmptyRedis;
use Incoviba\Common\Implement\Exception\EmptyResult;
use Incoviba\Exception\ServiceAction\Create;
use Incoviba\Exception\ServiceAction\Read;
use Incoviba\Exception\ServiceAction\Update;
use Incoviba\Model;
use Incoviba\Repository;
use Incoviba\Service;
@ -142,9 +145,24 @@ class Ventas
return $view->render($response, 'ventas.desistir', compact('venta'));
}
public function desistida(ServerRequestInterface $request, ResponseInterface $response, Service\Venta $ventaService,
Service\Venta\Pago $pagoService,
View $view, int $venta_id): ResponseInterface
{
$venta = $ventaService->getById($venta_id);
try {
$venta = $ventaService->getById($venta_id);
} catch (Read) {
return $view->render($response->withStatus(404), 'not_found');
}
if ($venta->resciliacion() === null) {
$pagoData = [
'fecha' => $venta->currentEstado()->fecha->format('Y-m-d'),
'valor' => 0
];
try {
$pago = $pagoService->add($pagoData);
$venta = $ventaService->edit($venta, ['resciliacion' => $pago->id]);
} catch (Create | Update) {}
}
return $view->render($response, 'ventas.desistida', compact('venta'));
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace Incoviba\Controller\Ventas;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Incoviba\Common\Alias\View;
use Incoviba\Common\Implement\Exception\EmptyResult;
use Incoviba\Exception\ServiceAction\Read;
use Incoviba\Repository;
use Incoviba\Service;
class Reservations
{
public function __invoke(ServerRequestInterface $request, ResponseInterface $response,
Service\Proyecto $proyectoService, Repository\Region $regionRepository,
View $view): ResponseInterface
{
$projects = [];
try {
$projects = $proyectoService->getVendibles('descripcion');
} catch (Read) {}
$regions = [];
try {
$regions = $regionRepository->fetchAll();
} catch (EmptyResult) {}
return $view->render($response, 'ventas.reservations', compact('projects', 'regions'));
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace Incoviba\Exception;
use Throwable;
use Exception;
abstract class MQTT extends Exception
{
public function __construct($message = "", $code = 0, ?Throwable $previous = null)
{
$baseCode = 700;
$code = $baseCode + $code;
if ($message == "") {
$message = "MQTT Exception";
}
parent::__construct($message, $code, $previous);
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace Incoviba\Exception\MQTT;
use Throwable;
use Incoviba\Exception\MQTT;
class MissingClient extends MQTT
{
public function __construct(string $host = '', ?Throwable $previous = null)
{
$message = 'Missing MQTT client';
if ($host !== '') {
$message = "{$message} for host {$host}";
}
$code = 1;
parent::__construct($message, $code, $previous);
}
}

View File

@ -0,0 +1,15 @@
<?php
namespace Incoviba\Exception\MQTT;
use Throwable;
use Incoviba\Exception\MQTT;
class MissingJob extends MQTT
{
public function __construct(?Throwable $previous = null)
{
$message = 'Missing MQTT job';
$code = 10;
parent::__construct($message, $code, $previous);
}
}

View File

@ -0,0 +1,15 @@
<?php
namespace Incoviba\Exception\MQTT;
use Throwable;
use Incoviba\Exception\MQTT;
class RemoveJob extends MQTT
{
public function __construct(int $jobId, ?Throwable $previous = null)
{
$message = "Could not remove job {$jobId}";
$code = 13;
parent::__construct($message, $code, $previous);
}
}

View File

@ -0,0 +1,15 @@
<?php
namespace Incoviba\Exception\MQTT;
use Throwable;
use Incoviba\Exception\MQTT;
class SetJob extends MQTT
{
public function __construct(string $payload, ?Throwable $previous = null)
{
$message = "Could not set job with {$payload}";
$code = 11;
parent::__construct($message, $code, $previous);
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace Incoviba\Exception\Model;
use Throwable;
use Exception;
class InvalidState extends Exception
{
public function __construct(string $message = "Invalid state", int $code = 505, ?Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
}
}

View File

@ -7,11 +7,11 @@ use Incoviba\Model\Inmobiliaria\TipoSociedad;
class Inmobiliaria extends Model
{
public int $rut;
public ?string $dv;
public ?string $razon;
public ?string $abreviacion;
public ?TipoSociedad $tipoSociedad;
public string $sigla;
public ?string $dv = null;
public ?string $razon = null;
public ?string $abreviacion = null;
public ?TipoSociedad $tipoSociedad = null;
public ?string $sigla = null;
public function rut(): string
{
@ -37,7 +37,7 @@ class Inmobiliaria extends Model
'razon' => $this->razon ?? '',
'abreviacion' => $this->abreviacion ?? '',
'tipo_sociedad' => $this->tipoSociedad ?? '',
'sigla' => $this->sigla,
'sigla' => $this->sigla ?? '',
];
}
}

View File

@ -7,12 +7,14 @@ class Job extends Ideal\Model
{
public array $configuration;
public bool $executed = false;
public int $retries = 0;
protected function jsonComplement(): array
{
return [
'configuration' => $this->configuration,
'executed' => $this->executed
'executed' => $this->executed,
'retries' => $this->retries
];
}
}

View File

@ -1,6 +1,7 @@
<?php
namespace Incoviba\Model;
use Incoviba\Common\Implement\Exception\EmptyResult;
use InvalidArgumentException;
use Incoviba\Common\Ideal;
use Incoviba\Model\Persona\Datos;
@ -27,7 +28,11 @@ class Persona extends Ideal\Model
public function datos(): ?Datos
{
if (!isset($this->datos)) {
$this->datos = $this->runFactory('datos');
try {
$this->datos = $this->runFactory('datos');
} catch (EmptyResult) {
$this->datos = null;
}
}
return $this->datos;
}

View File

@ -1,7 +1,10 @@
<?php
namespace Incoviba\Model\Proyecto;
use DateTimeImmutable;
use DateTimeInterface;
use Incoviba\Common;
use Incoviba\Model\Proyecto;
class Broker extends Common\Ideal\Model
{
@ -26,6 +29,36 @@ class Broker extends Common\Ideal\Model
}
return $this->contracts;
}
public function getContract(Proyecto $proyecto, ?DateTimeInterface $date = null): ?Proyecto\Broker\Contract
{
if ($date === null) {
$date = new DateTimeImmutable();
}
$contracts = $this->contracts();
$valid = array_filter($contracts, fn(Proyecto\Broker\Contract $contract) => $contract->project->id === $proyecto->id);
$valid = array_filter($valid, fn(Proyecto\Broker\Contract $contract) => $contract->wasActive($date));
if (count($valid) === 0) {
return null;
}
return last($valid);
}
public function applyCommission(Proyecto $proyecto, float $price, ?DateTimeInterface $date = null): float
{
$contract = $this->getContract($proyecto, $date);
if ($contract === null) {
return $price;
}
return $contract->activate()->apply($proyecto, $price);
}
public function reverseCommission(Proyecto $proyecto, float $price, ?DateTimeInterface $date = null): float
{
$contract = $this->getContract($proyecto, $date);
if ($contract === null) {
return $price;
}
return $contract->activate()->reverse($proyecto, $price);
}
public function rutFull(): string
{

View File

@ -1,6 +1,7 @@
<?php
namespace Incoviba\Model\Proyecto\Broker;
use DateTimeInterface;
use Incoviba\Common;
use Incoviba\Model;
@ -41,6 +42,51 @@ class Contract extends Common\Ideal\Model
return $this->promotions;
}
public function isActive(): bool
{
$state = $this->current();
return $state !== null and $state->isActive();
}
public function activate(): self
{
$this->current()->activate();
return $this;
}
public function wasActive(DateTimeInterface $date): bool
{
$states = $this->states();
return array_any($states, fn($state) => $state->date <= $date);
}
public function value(float $price): float
{
if (!$this->isActive()) {
return $price;
}
return $price * (1 + $this->commission);
}
public function inverse(float $price): float
{
if (!$this->isActive()) {
return $price;
}
return $price / (1 + $this->commission);
}
public function apply(Model\Proyecto $project, float $price): float
{
if (!$this->isActive() or $this->project->id !== $project->id) {
return $price;
}
return $this->value($price);
}
public function reverse(Model\Proyecto $project, float $price): float
{
if (!$this->isActive() or $this->project->id !== $project->id) {
return $price;
}
return $this->inverse($price);
}
protected function jsonComplement(): array
{
return [

View File

@ -11,6 +11,16 @@ class State extends Common\Ideal\Model
public DateTimeInterface $date;
public State\Type $type;
public function isActive(): bool
{
return $this->type === State\Type::ACTIVE;
}
public function activate(): self
{
$this->type = State\Type::ACTIVE;
return $this;
}
protected function jsonComplement(): array
{
return [

View File

@ -6,12 +6,12 @@ use Incoviba\Model\Direccion;
class Datos
{
public ?string $sexo;
public ?string $estado_civil;
public ?string $profesion;
public ?Direccion $direccion;
public ?int $telefono;
public ?string $email;
public ?string $sexo = null;
public ?string $estado_civil = null;
public ?string $profesion = null;
public ?Direccion $direccion = null;
public ?int $telefono = null;
public ?string $email = null;
public function jsonSerialize(): mixed
{

View File

@ -3,6 +3,7 @@ namespace Incoviba\Model\Venta;
use DateTimeInterface;
use Incoviba\Common;
use Incoviba\Model\Proyecto;
use Incoviba\Model\Proyecto\Broker;
use Incoviba\Model\Venta\Promotion\State;
use Incoviba\Model\Venta\Promotion\Type;
@ -61,13 +62,95 @@ class Promotion extends Common\Ideal\Model
return $this->units;
}
public function isActive(): bool
{
return $this->state === State::ACTIVE;
}
public function activate(): self
{
$this->state = State::ACTIVE;
return $this;
}
public function value(float $price): float
{
if (!$this->isActive()) {
return $price;
}
if ($this->type === Type::FIXED) {
return $price + $this->amount;
}
return $price / (1 - $this->amount);
}
public function inverse(float $price): float
{
if (!$this->isActive()) {
return $price;
}
if ($this->type === Type::FIXED) {
return $price - $this->amount;
}
return $price * (1 - $this->amount);
}
public function apply(Unidad $unit, float $price, ?Broker $broker = null): float
{
if (!$this->isActive()) {
return $price;
}
$projectIds = array_map(fn(Proyecto $proyecto) => $proyecto->id, $this->projects());
if (in_array($unit->proyectoTipoUnidad->proyecto->id, $projectIds)) {
return $this->value($price);
}
if ($broker !== null) {
$brokerIds = array_map(fn(Broker $broker) => $broker->id, $this->brokers());
if (in_array($broker->id, $brokerIds)) {
return $this->value($price);
}
}
$typeIds = array_map(fn(Proyecto\TipoUnidad $type) => $type->id, $this->unitTypes());
if (in_array($unit->proyectoTipoUnidad->tipoUnidad->id, $typeIds)) {
return $this->value($price);
}
$lineIds = array_map(fn(Proyecto\ProyectoTipoUnidad $line) => $line->id, $this->unitLines());
if (in_array($unit->proyectoTipoUnidad->id, $lineIds)) {
return $this->value($price);
}
$unitIds = array_map(fn(Unidad $unit) => $unit->id, $this->units());
if (in_array($unit->id, $unitIds)) {
return $this->value($price);
}
return $price;
}
public function reverse(Unidad $unit, float $price, ?Broker $broker = null): float
{
if (!$this->isActive()) {
return $price;
}
$projectIds = array_map(fn(Proyecto $proyecto) => $proyecto->id, $this->projects());
if (in_array($unit->proyectoTipoUnidad->proyecto->id, $projectIds)) {
return $this->inverse($price);
}
if ($broker !== null) {
$brokerIds = array_map(fn(Broker $broker) => $broker->id, $this->brokers());
if (in_array($broker->id, $brokerIds)) {
return $this->inverse($price);
}
}
$typeIds = array_map(fn(Proyecto\TipoUnidad $type) => $type->id, $this->unitTypes());
if (in_array($unit->proyectoTipoUnidad->tipoUnidad->id, $typeIds)) {
return $this->inverse($price);
}
$lineIds = array_map(fn(Proyecto\ProyectoTipoUnidad $line) => $line->id, $this->unitLines());
if (in_array($unit->proyectoTipoUnidad->id, $lineIds)) {
return $this->inverse($price);
}
$unitIds = array_map(fn(Unidad $unit) => $unit->id, $this->units());
if (in_array($unit->id, $unitIds)) {
return $this->inverse($price);
}
return $price;
}
protected function jsonComplement(): array
{

View File

@ -9,7 +9,7 @@ class Propiedad extends Ideal\Model
public array $unidades = [];
public bool $estado;
public function principal(): Unidad
public function principal(): ?Unidad
{
if (count($this->departamentos()) > 0) {
return $this->departamentos()[0];

View File

@ -11,8 +11,8 @@ class Propietario extends Model
public string $nombres;
public array $apellidos;
public Datos $datos;
public ?Propietario $representante;
public ?bool $otro;
public ?Propietario $representante = null;
public ?bool $otro = null;
public function rut(): string
{

View File

@ -7,11 +7,44 @@ use Incoviba\Model;
class Reservation extends Common\Ideal\Model
{
public Model\Proyecto $project;
public Model\Persona $buyer;
public DateTimeInterface $date;
public array $units = [];
public array $promotions = [];
public ?Model\Proyecto\Broker $broker = null;
public function offer(): float
{
return array_sum(array_column($this->units, 'value'));
}
public function withCommission(): float
{
$base = 0;
foreach ($this->units as $unit) {
foreach ($this->promotions as $promotion) {
$base += $promotion->activate()->reverse($unit['unit'], $unit['value'], $this->broker);
}
}
return $base;
}
public function base(): float
{
$base = $this->withCommission();
if ($this->broker !== null) {
$base = $this->broker->reverseCommission($this->project, $base, $this->date);
}
return $base;
}
public function price(): float
{
$price = 0;
foreach ($this->units as $unit) {
$price += $unit->unit->precio($this->date);
}
return $price;
}
protected array $states = [];
public function states(): array
@ -38,13 +71,12 @@ class Reservation extends Common\Ideal\Model
$this->units[$i]['value'] = $value;
return $this;
}
$this->units[] = [
$this->units[] = (object) [
'unit' => $unit,
'value' => $value,
];
return $this;
}
public function removeUnit(int $unit_id): self
{
if (($i = $this->findUnit($unit_id)) === null) {
@ -54,7 +86,6 @@ class Reservation extends Common\Ideal\Model
$this->units = array_values($this->units);
return $this;
}
public function findUnit(int $unit_id): ?int
{
foreach ($this->units as $idx => $unit) {
@ -64,20 +95,43 @@ class Reservation extends Common\Ideal\Model
}
return null;
}
public function hasUnit(int $unit_id): bool
{
return $this->findUnit($unit_id) !== null;
}
public function summary(): string
{
$unitSummary = array_map(function($unit) {
$type = $unit->unit->proyectoTipoUnidad->tipoUnidad->descripcion;
$cap = strtoupper(strstr($type, 0, 1));
return "{$cap}{$unit->unit->descripcion}";
}, $this->units);
return implode('', $unitSummary);
}
public function valid(): bool
{
$base = $this->base();
$price = $this->price();
return $base >= $price;
}
protected function jsonComplement(): array
{
return [
'buyer_rut' => $this->buyer->rut,
'project_id' => $this->project->id,
'buyer' => $this->buyer,
'date' => $this->date->format('Y-m-d'),
'units' => $this->units,
'promotions' => $this->promotions,
'broker_rut' => $this->broker?->rut,
'broker' => $this->broker,
'offer' => $this->offer(),
'with_commission' => $this->withCommission(),
'base' => $this->base(),
'price' => $this->price(),
'valid' => $this->valid(),
'summary' => $this->summary()
];
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace Incoviba\Model\Venta\Reservation\Detail;
enum Type: int
{
case Unit = 1;
case Promotion = 2;
case Broker = 3;
public function jsonSerialize(): array
{
return [
'id' => $this->value,
'description' => $this->name
];
}
}

View File

@ -9,17 +9,14 @@ class State extends Common\Ideal\Model
{
public Model\Venta\Reservation $reservation;
public DateTimeInterface $date;
public int $type;
public Model\Venta\Reservation\State\Type $type;
protected function jsonComplement(): array
{
return [
'reservation_id' => $this->reservation->id,
'date' => $this->date->format('Y-m-d'),
'type' => [
'id' => $this->type,
'description' => State\Type::name($this->type)
]
'type' => $this->type
];
}
}
}

View File

@ -7,13 +7,15 @@ enum Type: int
case INACTIVE = 0;
case REJECTED = -1;
public static function name(int $type): string
public function jsonSerialize(): array
{
return match ($type) {
self::ACTIVE => 'active',
self::INACTIVE => 'inactive',
self::REJECTED => 'rejected',
default => throw new \InvalidArgumentException('Unexpected match value')
};
return [
'id' => $this->value,
'description' => $this->name
];
}
}
public static function getTypes(): array
{
return [self::ACTIVE->value, self::INACTIVE->value, self::REJECTED->value];
}
}

View File

@ -22,7 +22,7 @@ class Inmobiliaria extends Ideal\Repository
public function create(?array $data = null): Model\Inmobiliaria
{
$map = (new Implement\Repository\MapperParser(['dv', 'razon', 'abreviacion', 'sigla']))
$map = (new Implement\Repository\MapperParser(['rut', 'dv', 'razon', 'abreviacion', 'sigla']))
->register('sociedad', (new Implement\Repository\Mapper())
->setProperty('tipoSociedad')
->setFunction(function($data) {
@ -32,9 +32,9 @@ class Inmobiliaria extends Ideal\Repository
}
public function save(Define\Model $model): Model\Inmobiliaria
{
$model->rut = $this->saveNew(
['dv', 'razon', 'abreviacion', 'cuenta', 'banco', 'sociedad'],
[$model->dv, $model->razon, $model->abreviacion, $model->cuenta, $model->banco->id, $model->tipoSociedad->id]
$this->saveNew(
['rut', 'dv', 'razon', 'abreviacion', 'cuenta', 'banco', 'sociedad'],
[$model->rut, $model?->dv, $model?->razon, $model?->abreviacion, $model?->cuenta, $model?->banco->id, $model?->tipoSociedad->id]
);
return $model;
}

View File

@ -29,6 +29,9 @@ class Datos extends Ideal\Repository
->register('direccion_id', (new Implement\Repository\Mapper())
->setProperty('direccion')
->setFunction(function($data) {
if ($data['direccion_id'] === null) {
return null;
}
return $this->direccionRepository->fetchById($data['direccion_id']);
})->setDefault(null))
->register('telefono', (new Implement\Repository\Mapper())->setFunction(function($data) {

View File

@ -9,7 +9,8 @@ use Incoviba\Repository;
class ProyectoTipoUnidad extends Ideal\Repository
{
public function __construct(Define\Connection $connection, protected Repository\Proyecto $proyectoRepository, protected Repository\Proyecto\TipoUnidad $tipoUnidadRepository)
public function __construct(Define\Connection $connection, protected Repository\Proyecto $proyectoRepository,
protected Repository\Proyecto\TipoUnidad $tipoUnidadRepository)
{
parent::__construct($connection);
$this->setTable('proyecto_tipo_unidad');

View File

@ -2,6 +2,8 @@
namespace Incoviba\Repository\Venta\MediosPago\Toku;
use DateTimeImmutable;
use PDO;
use PDOException;
use Incoviba\Common\Define;
use Incoviba\Common\Ideal;
use Incoviba\Common\Implement;
@ -79,4 +81,32 @@ class Customer extends Ideal\Repository
->where('toku_id = :toku_id');
return $this->fetchOne($query, compact('toku_id'));
}
/**
* @return array
* @throws Implement\Exception\EmptyResult
*/
public function fetchAllTokuIds(): array
{
$query = $this->connection->getQueryBuilder()
->select('toku_id')
->from($this->getTable());
try {
$statement = $this->connection->query($query);
} catch (PDOException $exception) {
throw new Implement\Exception\EmptyResult($query, $exception);
}
if ($statement->rowCount() === 0) {
throw new Implement\Exception\EmptyResult($query);
}
return $statement->fetchAll(PDO::FETCH_COLUMN);
}
public function removeByTokuId(string $toku_id): void
{
$query = $this->connection->getQueryBuilder()
->delete()
->from($this->getTable())
->where('toku_id = :toku_id');
$this->connection->execute($query, compact('toku_id'));
}
}

View File

@ -2,6 +2,8 @@
namespace Incoviba\Repository\Venta\MediosPago\Toku;
use DateTimeImmutable;
use PDO;
use PDOException;
use Incoviba\Common\Define;
use Incoviba\Common\Ideal;
use Incoviba\Common\Implement;
@ -69,4 +71,38 @@ class Invoice extends Ideal\Repository
->where('toku_id = :toku_id');
return $this->fetchOne($query, compact('toku_id'));
}
/**
* @return array
* @throws Implement\Exception\EmptyResult
*/
public function fetchAllTokuIds(): array
{
$query = $this->connection->getQueryBuilder()
->select('toku_id')
->from($this->getTable());
try {
$statement = $this->connection->query($query);
} catch (PDOException $exception) {
throw new Implement\Exception\EmptyResult($query, $exception);
}
if ($statement->rowCount() === 0) {
throw new Implement\Exception\EmptyResult($query);
}
return $statement->fetchAll(PDO::FETCH_COLUMN);
}
/**
* @param string $toku_id
* @return void
* @throws PDOException
*/
public function removeByTokuId(string $toku_id): void
{
$query = $this->connection->getQueryBuilder()
->delete()
->from($this->getTable())
->where('toku_id = :toku_id');
$this->connection->execute($query, compact('toku_id'));
}
}

View File

@ -2,6 +2,8 @@
namespace Incoviba\Repository\Venta\MediosPago\Toku;
use DateTimeImmutable;
use PDO;
use PDOException;
use Incoviba\Common\Define;
use Incoviba\Common\Ideal;
use Incoviba\Common\Implement;
@ -85,4 +87,38 @@ class Subscription extends Ideal\Repository
->where("venta_id IN ({$idsQuery})");
return $this->fetchMany($query, $ventas_ids);
}
/**
* @return array
* @throws Implement\Exception\EmptyResult
*/
public function fetchAllTokuIds(): array
{
$query = $this->connection->getQueryBuilder()
->select('toku_id')
->from($this->getTable());
try {
$statement = $this->connection->query($query);
} catch (PDOException $exception) {
throw new Implement\Exception\EmptyResult($query, $exception);
}
if ($statement->rowCount() === 0) {
throw new Implement\Exception\EmptyResult($query);
}
return $statement->fetchAll(PDO::FETCH_COLUMN);
}
/**
* @param string $toku_id
* @return void
* @throws PDOException
*/
public function removeByTokuId(string $toku_id): void
{
$query = $this->connection->getQueryBuilder()
->delete()
->from($this->getTable())
->where('toku_id = :toku_id');
$this->connection->execute($query, compact('toku_id'));
}
}

View File

@ -3,14 +3,19 @@ namespace Incoviba\Repository\Venta;
use DateTimeInterface;
use DateInterval;
use Incoviba\Common\Define;
use Incoviba\Exception\Model\InvalidState;
use PDO;
use Incoviba\Common;
use Incoviba\Model;
use Incoviba\Repository;
use PDOException;
class Reservation extends Common\Ideal\Repository
{
public function __construct(Common\Define\Connection $connection, protected Repository\Persona $personaRepository,
public function __construct(Common\Define\Connection $connection,
protected Repository\Proyecto $proyectoRepository,
protected Repository\Persona $personaRepository,
protected Repository\Proyecto\Broker $brokerRepository,
protected Unidad $unitRepository, protected Promotion $promotionRepository)
{
@ -25,37 +30,33 @@ class Reservation extends Common\Ideal\Repository
public function create(?array $data = null): Model\Venta\Reservation
{
$map = (new Common\Implement\Repository\MapperParser())
->register('project_id', (new Common\Implement\Repository\Mapper())
->setProperty('project')
->setFunction(function($data) {
return $this->proyectoRepository->fetchById($data['project_id']);
}))
->register('buyer_rut', (new Common\Implement\Repository\Mapper())
->setProperty('buyer')
->setFunction(function($data) use ($data) {
->setFunction(function($data) {
return $this->personaRepository->fetchById($data['buyer_rut']);
}))
->register('date', new Common\Implement\Repository\Mapper\DateTime('date'))
->register('broker_rut', (new Common\Implement\Repository\Mapper())
->setProperty('broker')
->setDefault(null)
->setFunction(function($data) use ($data) {
try {
return $this->brokerRepository->fetchById($data['broker_rut']);
} catch (Common\Implement\Exception\EmptyResult) {
return null;
}
}));
->register('date', new Common\Implement\Repository\Mapper\DateTime('date'));
return $this->parseData(new Model\Venta\Reservation(), $data, $map);
}
public function save(Common\Define\Model $model): Model\Venta\Reservation
{
$model->id = $this->saveNew([
'project_id',
'buyer_rut',
'date',
'broker_rut'
'date'
], [
$model->project->id,
$model->buyer->rut,
$model->date->format('Y-m-d'),
$model->broker?->rut
$model->date->format('Y-m-d')
]);
$this->saveUnits($model);
$this->savePromotions($model);
$this->saveBroker($model);
return $model;
}
@ -67,15 +68,20 @@ class Reservation extends Common\Ideal\Repository
*/
public function edit(Common\Define\Model $model, array $new_data): Model\Venta\Reservation
{
return $this->update($model, ['buyer_rut', 'date', 'broker_rut'], $new_data);
$model = $this->update($model, ['project_id', 'buyer_rut', 'date'], $new_data);
$this->editUnits($model, $new_data);
$this->editPromotions($model, $new_data);
$this->editBroker($model, $new_data);
return $model;
}
public function load(array $data_row): Model\Venta\Reservation
{
$model = parent::load($data_row);
$this->fetchUnits($model);
$this->fetchPromotions($model);
$this->fetchUnits($model, $data_row);
$this->fetchBroker($model, $data_row);
$this->fetchPromotions($model, $data_row);
return $model;
}
@ -93,6 +99,86 @@ class Reservation extends Common\Ideal\Repository
->where('buyer_rut = :buyer_rut AND date >= :date');
return $this->fetchOne($query, ['buyer_rut' => $buyer_rut, 'date' => $date->sub(new DateInterval('P10D'))->format('Y-m-d')]);
}
public function fetchByProject(int $project_id): array
{
$query = $this->connection->getQueryBuilder()
->select()
->from('reservations')
->where('project_id = :project_id');
return $this->fetchMany($query, ['project_id' => $project_id]);
}
/**
* @param int $project_id
* @param int $state
* @return array
* @throws Common\Implement\Exception\EmptyResult
* @throws InvalidState
*/
public function fetchState(int $project_id, int $state): array
{
if (!in_array($state, Model\Venta\Reservation\State\Type::getTypes())) {
throw new InvalidState();
}
$sub1 = $this->connection->getQueryBuilder()
->select('MAX(id) AS id, reservation_id')
->from('reservation_states')
->group('reservation_id');
$sub2 = $this->connection->getQueryBuilder()
->select('er1.*')
->from('reservation_states er1')
->joined("INNER JOIN ({$sub1}) er0 ON er0.id = er1.id");
$query = $this->connection->getQueryBuilder()
->select()
->from('reservations')
->joined("INNER JOIN ({$sub2}) er ON er.reservation_id = reservations.id")
->where('project_id = :project_id AND er.type = :state');
return $this->fetchMany($query, ['project_id' => $project_id,
'state' => $state]);
}
/**
* @param int $project_id
* @return array
* @throws Common\Implement\Exception\EmptyResult
*/
public function fetchActive(int $project_id): array
{
try {
return $this->fetchState($project_id, Model\Venta\Reservation\State\Type::ACTIVE->value);
} catch (InvalidState $exception) {
throw new Common\Implement\Exception\EmptyResult('Select active reservations', $exception);
}
}
/**
* @param int $project_id
* @return array
* @throws Common\Implement\Exception\EmptyResult
*/
public function fetchPending(int $project_id): array
{
try {
return $this->fetchState($project_id, Model\Venta\Reservation\State\Type::INACTIVE->value);
} catch (InvalidState $exception) {
throw new Common\Implement\Exception\EmptyResult('Select pending reservations', $exception);
}
}
/**
* @param int $project_id
* @return array
* @throws Common\Implement\Exception\EmptyResult
*/
public function fetchRejected(int $project_id): array
{
try {
return $this->fetchState($project_id, Model\Venta\Reservation\State\Type::REJECTED->value);
} catch (InvalidState $exception) {
throw new Common\Implement\Exception\EmptyResult('Select rejected reservations', $exception);
}
}
protected function saveUnits(Model\Venta\Reservation $reservation): void
{
@ -101,22 +187,71 @@ class Reservation extends Common\Ideal\Repository
}
$queryCheck = $this->connection->getQueryBuilder()
->select('COUNT(id) AS cnt')
->from('reservation_data')
->where('reservation_id = :id AND type = "Unit" AND reference_id = :unit_id');
->from('reservation_details')
->where('reservation_id = :id AND type = :type AND reference_id = :unit_id');
$statementCheck = $this->connection->prepare($queryCheck);
$queryInsert = $this->connection->getQueryBuilder()
->insert()
->into('reservation_data')
->into('reservation_details')
->columns(['reservation_id', 'type', 'reference_id', 'value'])
->values([':reservation_id', ':type', ':reference_id', ':value']);
$statementInsert = $this->connection->prepare($queryInsert);
foreach ($reservation->units as $unit) {
$statementCheck->execute(['id' => $reservation->id, 'unit_id' => $unit['unit']->id]);
$statementCheck->execute([
'id' => $reservation->id,
'type' => Model\Venta\Reservation\Detail\Type::Unit->value,
'unit_id' => $unit->unit->id
]);
$result = $statementCheck->fetch(PDO::FETCH_ASSOC);
if ($result['cnt'] > 0) {
continue;
}
$statementInsert->execute(['reservation_id' => $reservation->id, 'type' => 'Unit', 'reference_id' => $unit['unit']->id, 'value' => $unit['value']]);
$statementInsert->execute([
'reservation_id' => $reservation->id,
'type' => Model\Venta\Reservation\Detail\Type::Unit->value,
'reference_id' => $unit->unit->id,
'value' => $unit->value
]);
}
$this->cleanUpUnits($reservation);
}
protected function cleanUpUnits(Model\Venta\Reservation $reservation): void
{
$this->cleanUpDetails($reservation,
Model\Venta\Reservation\Detail\Type::Unit->value,
array_map(fn($unit) => $unit->unit->id, $reservation->units));
}
protected function saveBroker(Model\Venta\Reservation &$reservation): void
{
if ($reservation->broker === null) {
$this->removeBroker($reservation);
return;
}
try {
$queryCheck = $this->connection->getQueryBuilder()
->select('id')
->from('reservation_details')
->where('reservation_id = :id AND type = :type');
$statementCheck = $this->connection->execute($queryCheck, [
'id' => $reservation->id,
'type' => Model\Venta\Reservation\Detail\Type::Broker->value
]);
$result = $statementCheck->fetch(PDO::FETCH_ASSOC);
$new_id = $reservation->broker->id;
$reservation->broker = $this->brokerRepository->fetchById($result['id']);
$this->editBroker($reservation, ['broker_id' => $new_id]);
} catch (PDOException) {
$queryInsert = $this->connection->getQueryBuilder()
->insert()
->into('reservation_details')
->columns(['reservation_id', 'type', 'reference_id'])
->values([':reservation_id', ':type', ':reference_id']);
$this->connection->execute($queryInsert, [
'reservation_id' => $reservation->id,
'type' => Model\Venta\Reservation\Detail\Type::Broker->value,
'reference_id' => $reservation->broker->id
]);
}
}
protected function savePromotions(Model\Venta\Reservation $reservation): void
@ -126,60 +261,152 @@ class Reservation extends Common\Ideal\Repository
}
$queryCheck = $this->connection->getQueryBuilder()
->select('COUNT(id) AS cnt')
->from('reservation_data')
->where('reservation_id = :id AND type = "Promotion" AND reference_id = :promotion_id');
->from('reservation_details')
->where('reservation_id = :id AND type = :type AND reference_id = :promotion_id');
$statementCheck = $this->connection->prepare($queryCheck);
$queryInsert = $this->connection->getQueryBuilder()
->insert()
->into('reservation_data')
->into('reservation_details')
->columns(['reservation_id', 'type', 'reference_id'])
->values([':reservation_id', ':type', ':reference_id']);
$statementInsert = $this->connection->prepare($queryInsert);
foreach ($reservation->promotions as $promotion) {
$statementCheck->execute(['id' => $reservation->id, 'promotion_id' => $promotion->id]);
$statementCheck->execute([
'id' => $reservation->id,
'type' => Model\Venta\Reservation\Detail\Type::Promotion->value,
'promotion_id' => $promotion->id
]);
$result = $statementCheck->fetch(PDO::FETCH_ASSOC);
if ($result['cnt'] > 0) {
continue;
}
$statementInsert->execute(['reservation_id' => $reservation->id, 'type' => 'Promotion', 'reference_id' => $promotion->id]);
$statementInsert->execute([
'reservation_id' => $reservation->id,
'type' => Model\Venta\Reservation\Detail\Type::Promotion->value,
'reference_id' => $promotion->id
]);
}
$this->cleanUpPromotions($reservation);
}
protected function editUnits(Model\Venta\Reservation $reservation, array $new_data): void
protected function cleanUpPromotions(Model\Venta\Reservation $reservation): void
{
$this->cleanUpDetails($reservation,
Model\Venta\Reservation\Detail\Type::Promotion->value,
array_map(fn($promotion) => $promotion->id, $reservation->promotions));
}
protected function cleanUpDetails(Model\Venta\Reservation $reservation, int $type_id, array $currentIds): void
{
$queryCheck = $this->connection->getQueryBuilder()
->select('COUNT(id) AS cnt')
->from('reservation_details')
->where('reservation_id = :id AND type = :type');
$statementCheck = $this->connection->prepare($queryCheck);
$deleteParam = implode(', ', array_map(fn($id) => ":id$id", $currentIds));
$queryDelete = $this->connection->getQueryBuilder()
->delete()
->from('reservation_details')
->where("reservation_id = :id AND type = :type AND reference_id NOT IN ({$deleteParam})");
$statementDelete = $this->connection->prepare($queryDelete);
try {
$statementCheck->execute([
'id' => $reservation->id,
'type' => $type_id
]);
$result = $statementCheck->fetch(PDO::FETCH_ASSOC);
if ($result['cnt'] <= count($currentIds)) {
return;
}
$deleteIdValues = array_combine(array_map(fn($id) => "id$id", $currentIds), $currentIds);
$statementDelete->execute(array_merge(
['id' => $reservation->id, 'type' => $type_id],
$deleteIdValues));
} catch (PDOException) {}
}
protected function editUnits(Model\Venta\Reservation &$reservation, array $new_data): void
{
$querySelect = $this->connection->getQueryBuilder()
->select()
->from('reservation_data')
->where('reservation_id = :id AND type = "Unit" AND reference_id = :unit_id');
->from('reservation_details')
->where('reservation_id = :id AND type = :type AND reference_id = :unit_id');
$statementSelect = $this->connection->prepare($querySelect);
$queryUpdate = $this->connection->getQueryBuilder()
->update('reservation_data')
->update('reservation_details')
->set('value = :value')
->where('reservation_id = :id AND type = "Unit" AND reference_id = :unit_id');
->where('reservation_id = :id AND type = :type AND reference_id = :unit_id');
$statementUpdate = $this->connection->prepare($queryUpdate);
foreach ($new_data as $unit_id => $value) {
$idx = $reservation->findUnit($unit_id);
$queryInsert = $this->connection->getQueryBuilder()
->insert()
->into('reservation_details')
->columns(['reservation_id', 'type', 'reference_id', 'value'])
->values([':reservation_id', ':type', ':reference_id', ':value']);
$statementInsert = $this->connection->prepare($queryInsert);
foreach ($new_data['units'] as $unit) {
$idx = $reservation->findUnit($unit['unit_id']);
if ($idx === null) {
$reservation->addUnit($this->unitRepository->fetchById($unit_id), $value);
$reservation->addUnit($this->unitRepository->fetchById($unit['unit_id']), $unit['value']);
$statementInsert->execute([
'reservation_id' => $reservation->id,
'type' => Model\Venta\Reservation\Detail\Type::Unit->value,
'reference_id' => $unit['unit_id'],
'value' => $unit['value']
]);
continue;
}
$statementSelect->execute(['id' => $reservation->id, 'unit_id' => $unit_id]);
$statementSelect->execute([
'id' => $reservation->id,
'type' => Model\Venta\Reservation\Detail\Type::Unit->value,
'unit_id' => $unit['unit_id']
]);
$result = $statementSelect->fetch(PDO::FETCH_ASSOC);
if (!$result) {
$reservation->addUnit($this->unitRepository->fetchById($unit['unit_id']), $unit['value']);
$statementInsert->execute([
'reservation_id' => $reservation->id,
'type' => Model\Venta\Reservation\Detail\Type::Unit->value,
'reference_id' => $unit['unit_id'],
'value' => $unit['value']
]);
continue;
}
$statementUpdate->execute(['id' => $reservation->id, 'unit_id' => $unit_id, 'value' => $value]);
$reservation->units[$idx]['value'] = $value;
$statementUpdate->execute([
'id' => $reservation->id,
'type' => Model\Venta\Reservation\Detail\Type::Unit->value,
'unit_id' => $unit['unit_id'],
'value' => $unit['value']
]);
$reservation->units[$idx]['value'] = $unit['value'];
}
}
protected function editPromotions(Model\Venta\Reservation $reservation, array $new_data): void
protected function editBroker(Model\Venta\Reservation &$reservation, array $new_data): void
{
if (!array_key_exists('broker_id', $new_data) or $new_data['broker_id'] === $reservation->broker->id) {
return;
}
try {
$query = $this->connection->getQueryBuilder()
->update('reservation_details')
->set('reference_id = :broker_id')
->where('reservation_id = :id AND type = :type');
$this->connection->execute($query, [
'id' => $reservation->id,
'type' => Model\Venta\Reservation\Detail\Type::Broker->value,
'broker_id' => $new_data['broker_id']
]);
$reservation->broker = $this->brokerRepository->fetchById($new_data['broker_id']);
} catch (PDOException) {}
}
protected function editPromotions(Model\Venta\Reservation &$reservation, array $new_data): void
{
$querySelect = $this->connection->getQueryBuilder()
->select()
->from('reservation_data')
->from('reservation_details')
->where('reservation_id = :id AND type = "Promotion" AND reference_id = :promotion_id');
$statementSelect = $this->connection->prepare($querySelect);
$queryUpdate = $this->connection->getQueryBuilder()
->update('reservation_data')
->update('reservation_details')
->set('value = :value')
->where('reservation_id = :id AND type = "Promotion" AND reference_id = :promotion_id');
$statementUpdate = $this->connection->prepare($queryUpdate);
@ -198,13 +425,26 @@ class Reservation extends Common\Ideal\Repository
$reservation->promotions[$idx] = $this->promotionRepository->fetchById($promotion_id);
}
}
protected function fetchUnits(Model\Venta\Reservation &$reservation): Model\Venta\Reservation
protected function fetchUnits(Model\Venta\Reservation &$reservation, array $new_data): Model\Venta\Reservation
{
$this->fetchSavedUnits($reservation);
if (array_key_exists('units', $new_data) and count($new_data['units']) > 0) {
$this->fetchUnsavedUnits($reservation, $new_data);
}
return $reservation;
}
protected function fetchSavedUnits(Model\Venta\Reservation &$reservation): Model\Venta\Reservation
{
$query = $this->connection->getQueryBuilder()
->select()
->from('reservation_data')
->where('reservation_id = :id AND type = "Unit"');
$statement = $this->connection->execute($query, ['id' => $reservation->id]);
->from('reservation_details')
->where('reservation_id = :id AND type = ?');
$statement = $this->connection->execute($query, [
'id' => $reservation->id,
Model\Venta\Reservation\Detail\Type::Unit->value
]);
while ($result = $statement->fetch(PDO::FETCH_ASSOC)) {
try {
@ -213,13 +453,69 @@ class Reservation extends Common\Ideal\Repository
}
return $reservation;
}
protected function fetchPromotions(Model\Venta\Reservation $reservation): Model\Venta\Reservation
protected function fetchUnsavedUnits(Model\Venta\Reservation &$reservation, array $new_data): Model\Venta\Reservation
{
if (!array_key_exists('units', $new_data) or count($new_data['units']) > 0) {
return $reservation;
}
$queryCheck = $this->connection->getQueryBuilder()
->select('COUNT(id) AS cnt')
->from('reservation_details')
->where('reservation_id = :id AND type = :type AND reference_id = :unit_id');
$statementCheck = $this->connection->prepare($queryCheck);
foreach ($new_data['units'] as $unit) {
$statementCheck->execute([
'id' => $reservation->id,
'type' => Model\Venta\Reservation\Detail\Type::Unit->value,
'unit_id' => $unit['unit_id']
]);
$result = $statementCheck->fetch(PDO::FETCH_ASSOC);
if ($result['cnt'] > 0) {
continue;
}
try {
$reservation->addUnit($this->unitRepository->fetchById($unit['unit_id']), $unit['value']);
} catch (Common\Implement\Exception\EmptyResult) {}
}
return $reservation;
}
protected function fetchBroker(Model\Venta\Reservation &$reservation, array $new_data): Model\Venta\Reservation
{
if (!array_key_exists('broker_id', $new_data)) {
$query = $this->connection->getQueryBuilder()
->select('reference_id')
->from('reservation_details')
->where('reservation_id = :id AND type = :type');
try {
$statement =$this->connection->execute($query, [
'id' => $reservation->id,
'type' => Model\Venta\Reservation\Detail\Type::Broker->value
]);
$result = $statement->fetch(PDO::FETCH_ASSOC);
$reservation->broker = $this->brokerRepository->fetchById($result['reference_id']);
} catch (PDOException) {}
return $reservation;
}
try {
$reservation->broker = $this->brokerRepository->fetchById($new_data['broker_id']);
} catch (Common\Implement\Exception\EmptyResult) {}
return $reservation;
}
protected function fetchPromotions(Model\Venta\Reservation &$reservation, array $new_data): Model\Venta\Reservation
{
$this->fetchSavedPromotions($reservation);
$this->fetchUnsavedPromotions($reservation, $new_data);
return $reservation;
}
protected function fetchSavedPromotions(Model\Venta\Reservation &$reservation): Model\Venta\Reservation
{
$query = $this->connection->getQueryBuilder()
->select()
->from('reservation_data')
->where('type = "Promotion" AND reservation_id = :id');
$statement = $this->connection->execute($query, ['id' => $reservation->id]);
->from('reservation_details')
->where('type = :type AND reservation_id = :id');
$statement = $this->connection->execute($query,
['id' => $reservation->id, 'type' => Model\Venta\Reservation\Detail\Type::Promotion->value]);
while ($result = $statement->fetch(PDO::FETCH_ASSOC)) {
try {
$reservation->promotions []= $this->promotionRepository->fetchById($result['reference_id']);
@ -227,4 +523,43 @@ class Reservation extends Common\Ideal\Repository
}
return $reservation;
}
protected function fetchUnsavedPromotions(Model\Venta\Reservation &$reservation, array $new_data): Model\Venta\Reservation
{
if (!array_key_exists('promotions', $new_data) or count($new_data['promotions']) > 0) {
return $reservation;
}
$queryCheck = $this->connection->getQueryBuilder()
->select('COUNT(id) AS cnt')
->from('reservation_details')
->where('type = :type AND reservation_id = :id AND reference_id = :promotion_id');
$statementCheck = $this->connection->prepare($queryCheck);
foreach ($new_data['promotions'] as $promotion) {
$statementCheck->execute([
'id' => $reservation->id,
'promotion_id' => $promotion
]);
$result = $statementCheck->fetch(PDO::FETCH_ASSOC);
if ($result['cnt'] > 0) {
continue;
}
try {
$reservation->promotions []= $this->promotionRepository->fetchById($promotion);
} catch (Common\Implement\Exception\EmptyResult) {}
}
return $reservation;
}
protected function removeBroker(Model\Venta\Reservation &$reservation): void
{
try {
$query = $this->connection->getQueryBuilder()
->delete()
->from('reservation_details')
->where('reservation_id = :id AND type = :type');
$this->connection->execute($query, [
'id' => $reservation->id,
'type' => Model\Venta\Reservation\Detail\Type::Broker->value
]);
$reservation->broker = null;
} catch (PDOException) {}
}
}

View File

@ -22,7 +22,7 @@ class State extends Common\Ideal\Repository
$map = (new Common\Implement\Repository\MapperParser(['type']))
->register('reservation_id', (new Common\Implement\Repository\Mapper())
->setProperty('reservation')
->setFunction(function($data) use ($data) {
->setFunction(function($data) {
return $this->reservationRepository->fetchById($data['reservation_id']);
}))
->register('date', new Common\Implement\Repository\Mapper\DateTime('date'));

View File

@ -30,8 +30,9 @@ class Unidad extends Ideal\Repository
public function save(Define\Model $model): Model\Venta\Unidad
{
$model->id = $this->saveNew(
['subtipo', 'piso', 'descripcion', 'orientacion', 'pt'],
[$model->subtipo, $model->piso, $model->descripcion, $model->orientacion, $model->proyectoTipoUnidad->id]
['proyecto', 'tipo', 'subtipo', 'piso', 'descripcion', 'abreviacion', 'orientacion', 'pt'],
[$model->proyectoTipoUnidad->proyecto->id, $model->proyectoTipoUnidad->tipoUnidad->id, $model->subtipo,
$model->piso, $model->descripcion, $model->proyectoTipoUnidad->abreviacion, $model->orientacion, $model->proyectoTipoUnidad->id]
);
return $model;
}

View File

@ -1,149 +1,135 @@
<?php
namespace Incoviba\Service;
use DateInvalidTimeZoneException;
use DateMalformedStringException;
use DateTimeImmutable;
use DateTimeZone;
use InvalidArgumentException;
use OutOfRangeException;
use Psr\Log\LoggerInterface;
use Predis\Connection\ConnectionException;
use Incoviba\Common\Ideal;
use Incoviba\Common\Implement\Exception\EmptyRedis;
use Incoviba\Common\Implement\Exception\EmptyResult;
use Incoviba\Exception\ServiceAction\{Create, Read, Update};
use Incoviba\Repository;
use Incoviba\Exception\MQTT as MQTTException;
use Incoviba\Exception\ServiceAction\{Create, Delete, Read, Update};
use Incoviba\Model;
use Incoviba\Repository;
class Job extends Ideal\Service
{
public function __construct(LoggerInterface $logger, protected Redis $redisService,
public function __construct(LoggerInterface $logger, protected MQTT $mqttService,
protected Repository\Job $jobRepository)
{
parent::__construct($logger);
}
protected string $redisKey = 'jobs';
public function getPending(null|string|array $orderBy = null): array
public function isPending(): bool
{
try {
$jobs = $this->redisService->get($this->redisKey);
if ($jobs === null) {
return [];
}
$jobs = json_decode($jobs, true);
if ($orderBy !== null) {
uksort($jobs, function($a, $b) use ($orderBy) {
return $a[$orderBy] <=> $b[$orderBy];
});
}
return array_map([$this, 'load'], $jobs);
} catch (ConnectionException | EmptyRedis) {
return [];
return $this->mqttService->exists();
} catch (MQTTException $exception) {
$this->logger->error($exception->getMessage(), ['exception' => $exception]);
return false;
}
}
/**
* @param int $id
* @return Model\Job
* @throws Read
*/
public function getPendingById(int $id): Model\Job
public function get(): Model\Job
{
$jobs = $this->getJobs();
try {
$idx = $this->findJob($jobs, $id);
} catch (EmptyResult $exception) {
$exception = new OutOfRangeException('Job not found', count($jobs), $exception);
return $this->load(json_decode($this->mqttService->get(), true));
} catch (MQTTException $exception) {
$this->logger->error($exception->getMessage(), ['exception' => $exception]);
throw new Read(__CLASS__, $exception);
}
return $this->load($jobs[$idx]);
}
/**
* @param array $configuration
* @return Model\Job
* @throws Read
* @throws Create
*/
public function add(array $configuration): Model\Job
{
$now = (new DateTimeImmutable('now', new DateTimeZone($_ENV['TZ'] ?? 'America/Santiago')));
try {
$now = (new DateTimeImmutable('now', new DateTimeZone($_ENV['TZ'] ?? 'America/Santiago')));
} catch (DateMalformedStringException | DateInvalidTimeZoneException $exception) {
$this->logger->warning($exception->getMessage(), ['exception' => $exception]);
$now = new DateTimeImmutable();
}
$data = [
'id' => $now->format('Uu'),
'configuration' => $configuration,
'executed' => false,
'created_at' => $now->format('Y-m-d H:i:s'),
'updated_at' => null
'updated_at' => null,
'retries' => 0
];
$jobs = [];
try {
$jobs = $this->redisService->get($this->redisKey);
if ($jobs !== null) {
$jobs = json_decode($jobs, true);
}
} catch (EmptyRedis) {}
$jobs []= $data;
$this->redisService->set($this->redisKey, json_encode($jobs), -1);
$this->mqttService->set(json_encode($data));
} catch (MQTTException $exception) {
throw new Create(__CLASS__, $exception);
}
return $this->load($data);
}
/**
* @param Model\Job $job
* @return bool
* @throws Read
* @return void
* @throws Update
*/
public function execute(Model\Job $job): bool
public function update(Model\Job $job): void
{
$jobs = $this->getJobs();
try {
$idx = $this->findJob($jobs, $job->id);
} catch (EmptyResult $exception) {
$exception = new OutOfRangeException('Job not found', count($jobs), $exception);
throw new Read(__CLASS__, $exception);
$now = (new DateTimeImmutable('now', new DateTimeZone($_ENV['TZ'] ?? 'America/Santiago')));
} catch (DateMalformedStringException | DateInvalidTimeZoneException) {
$now = new DateTimeImmutable();
}
$data = json_decode(json_encode($job), true);
$data['updated_at'] = $now->format('Y-m-d H:i:s');
try {
$this->mqttService->update(json_encode($data));
} catch (MQTTException $exception) {
throw new Update(__CLASS__, $exception);
}
unset($jobs[$idx]);
$this->redisService->set($this->redisKey, json_encode(array_values($jobs)), -1);
return true;
}
/**
* @return array
* @throws Read
* @param Model\Job $job
* @throws Delete
*/
protected function getJobs(): array
public function remove(Model\Job $job): void
{
try {
$jobs = $this->redisService->get($this->redisKey);
} catch (EmptyRedis $exception) {
throw new Read(__CLASS__, $exception);
$this->mqttService->remove();
} catch (MQTTException $exception) {
throw new Delete(__CLASS__, $exception);
}
if ($jobs === null) {
$exception = new InvalidArgumentException("Redis Key {$this->redisKey} not found");
throw new Read(__CLASS__, $exception);
}
return json_decode($jobs, true);
}
/**
* @param array $jobs
* @param int $id
* @return int
* @throws EmptyResult
* @param Model\Job $job
* @return bool
*/
protected function findJob(array $jobs, int $id): int
public function execute(Model\Job $job): bool
{
$idx = array_find_key($jobs, function($job) use ($id) {
return (int) $job['id'] === $id;
});
if ($idx === null) {
throw new EmptyResult("SELECT * FROM jobs WHERE id = ?");
try {
$this->mqttService->remove();
return true;
} catch (MQTTException $exception) {
$this->logger->error($exception->getMessage(), ['exception' => $exception]);
return false;
}
return $idx;
}
protected function load(array $data, ?int $id = null): Model\Job
{
$job = new Model\Job();
$job->id = $id ?? $data['id'] ?? null;
$job->configuration = $data['configuration'] ?? [];
$job->executed = $data['executed'] ?? false;
$job->retries = $data['retries'] ?? 0;
return $job;
}
}

View File

@ -167,8 +167,13 @@ class Login
try {
$login = $this->repository->fetchActiveByUser($user->id);
$this->logout($login->user);
} catch (PDOException | EmptyResult $exception) {
error_log($exception, 3, '/logs/exception.log');
} catch (EmptyResult $exception) {
$message []= "No logins for user {$user->name}";
$message []= $exception->getMessage();
$message []= $exception->getTraceAsString();
error_log(implode(PHP_EOL, $message).PHP_EOL, 3, '/logs/login-exception.log');
} catch (PDOException $exception) {
error_log($exception.PHP_EOL, 3, '/logs/login-exception.log');
}
try {
@ -185,7 +190,7 @@ class Login
$this->saveCookie($selector, $token, $login->dateTime->add(new DateInterval("PT{$this->max_login_time}H")));
return true;
} catch (PDOException | Exception $exception) {
error_log($exception, 3, '/logs/exception.log');
error_log($exception.PHP_EOL, 3, '/logs/login-exception.log');
return false;
}
}

102
app/src/Service/MQTT.php Normal file
View File

@ -0,0 +1,102 @@
<?php
namespace Incoviba\Service;
use Incoviba\Exception\MQTT as MQTTException;
use Incoviba\Service\MQTT\MQTTInterface;
class MQTT implements MQTTInterface
{
protected array $clients = [];
public function register(string $name, MQTTInterface $client): self
{
$this->clients[$name] = $client;
return $this;
}
public function clientExists(string $name): bool
{
return isset($this->clients[$name]);
}
/**
* @param string|null $name
* @return MQTTInterface
* @throws MQTTException/MissingClient
*/
public function getClient(?string $name = null): MQTTInterface
{
if ($name === null) {
$name = array_keys($this->clients)[0];
}
if (!$this->clientExists($name)) {
throw new MQTTException\MissingClient($name);
}
return $this->clients[$name];
}
/**
* @param string|null $host
* @return bool
* @throws MQTTException/MissingClient
* @throws MQTTException/MissingJob
*/
public function exists(?string $host = null): bool
{
$client = $this->getClient($host);
return $client->exists();
}
/**
* @param string|null $host
* @return string
* @throws MQTTException/MissingClient
* @throws MQTTException/MissingJob
*/
public function get(?string $host = null): string
{
$client = $this->getClient($host);
return $client->get();
}
/**
* @param string $value
* @param int $delay
* @param string|null $host
* @return $this
* @throws MQTTException/MissingClient
* @throws MQTTException/SetJob
*/
public function set(string $value, int $delay = 0, ?string $host = null): self
{
$client = $this->getClient($host);
$client->set($value, $delay);
return $this;
}
/**
* @param int|null $jobId
* @param string|null $host
* @return $this
* @throws MQTTException/MissingJob
* @throws MQTTException/RemoveJob
*/
public function remove(?int $jobId = null, ?string $host = null): self
{
$this->getClient($host)->remove($jobId);
return $this;
}
/**
* @param string $newPayload
* @param int|null $jobId
* @param string|null $host
* @return $this
* @throws MQTTException/MissingJob
* @throws MQTTException/RemoveJob
* @throws MQTTException/SetJob
*/
public function update(string $newPayload, ?int $jobId = null, ?string $host = null): self
{
$this->getClient($host)->update($newPayload, $jobId);
return $this;
}
}

View File

@ -0,0 +1,111 @@
<?php
namespace Incoviba\Service\MQTT;
use Exception;
use Incoviba\Exception\MQTT\MissingJob;
use Incoviba\Exception\MQTT\RemoveJob;
use Incoviba\Exception\MQTT\SetJob;
use Psr\Log\LoggerInterface;
use xobotyi\beansclient;
use xobotyi\beansclient\Exception\ClientException;
use xobotyi\beansclient\Exception\CommandException;
use xobotyi\beansclient\Exception\JobException;
class Beanstalkd implements MQTTInterface
{
/**
* @throws JobException
* @throws ClientException
* @throws CommandException
*/
public function __construct(protected LoggerInterface $logger, protected beansclient\BeansClient $client,
protected string $tube = 'default', protected int $ttr = beansclient\BeansClient::DEFAULT_TTR,
protected int $priority = 1)
{
$this->client->watchTube($this->tube);
}
public function exists(): bool
{
try {
$stats = $this->client->statsTube($this->tube);
return $stats['current-jobs-ready'] > 0;
} catch (Exception $exception) {
$this->logger->error($exception->getMessage(), ['exception' => $exception]);
return false;
}
}
protected ?beansclient\Job $currentJob = null;
public function get(): string
{
if (!$this->exists()) {
throw new MissingJob();
}
try {
$job = $this->client->watchTube($this->tube)->reserve();
$this->currentJob = $job;
return $job->payload;
} catch (Exception $exception) {
$this->logger->error($exception->getMessage(), ['exception' => $exception]);
throw new MissingJob($exception);
}
}
/**
* @param string $value
* @param int $delay
* @return $this
* @throws SetJob
*/
public function set(string $value, int $delay = 0): self
{
try {
$this->client->useTube($this->tube)->put($value, $this->priority, $delay, $this->ttr ?? 0);
} catch (Exception $exception) {
$this->logger->error($exception->getMessage(), ['payload' => $value, 'delay' => $delay, 'exception' => $exception]);
throw new SetJob($value, $exception);
}
return $this;
}
/**
* @param string $newPayload
* @param int|null $jobId
* @return self
* @throws RemoveJob
* @throws SetJob
*/
public function update(string $newPayload, ?int $jobId = null): self
{
if ($jobId === null) {
$jobId = $this->currentJob->id;
}
return $this->remove($jobId)
->set($newPayload);
}
/**
* @param int|null $jobId
* @return $this
* @throws RemoveJob
*/
public function remove(?int $jobId = null): self
{
if ($jobId === null) {
$jobId = $this->currentJob->id;
}
try {
if (!$this->client->useTube($this->tube)->delete($jobId)) {
throw new JobException("Failed to delete job {$jobId}");
}
if ($this->currentJob !== null && $this->currentJob->id === $jobId) {
$this->currentJob = null;
}
} catch (Exception $exception) {
$this->logger->error($exception->getMessage(), ['jobId' => $jobId, 'exception' => $exception]);
throw new RemoveJob($jobId, $exception);
}
return $this;
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace Incoviba\Service\MQTT;
use Incoviba\Exception\MQTT\MissingJob;
interface MQTTInterface {
/**
* @return bool
*/
public function exists(): bool;
/**
* @return string
* @throws MissingJob
*/
public function get(): string;
/**
* @param string $value
* @param int $delay
* @return self
*/
public function set(string $value, int $delay = 0): self;
public function remove(?int $jobId = null): self;
}

View File

@ -0,0 +1,59 @@
<?php
namespace Incoviba\Service\MQTT;
use Incoviba\Common\Ideal\Service;
use Psr\Log\LoggerInterface;
use Pheanstalk as PBA;
class Pheanstalk extends Service implements MQTTInterface
{
const string DEFAULT_TUBE = 'default';
const int DEFAULT_TTR = 60;
const int DEFAULT_PRIORITY = 1_024;
public function __construct(LoggerInterface $logger, protected PBA\Pheanstalk $client, string $tubeName = self::DEFAULT_TUBE)
{
parent::__construct($logger);
$this->tube = new PBA\Values\TubeName($tubeName);
}
protected PBA\Values\TubeName $tube;
public function set(string $value, int $delay = 0): self
{
$this->client->useTube($this->tube);
$this->client->put($value, self::DEFAULT_PRIORITY, $delay, self::DEFAULT_TTR);
return $this;
}
public function exists(): bool
{
$stats = $this->client->statsTube($this->tube);
return $stats->currentJobsReady > 0;
}
protected int $currentJobId;
public function get(): string
{
$this->client->useTube($this->tube);
$job = $this->client->reserve();
$this->currentJobId = $job->getId();
return $job->getData();
}
public function update(string $newPayload, ?int $jobId = null): self
{
if ($jobId === null) {
$jobId = $this->currentJobId;
}
$this->remove($jobId);
$this->set($newPayload);
return $this;
}
public function remove(?int $jobId = null): self
{
if ($jobId === null) {
$jobId = $this->currentJobId;
}
$this->client->useTube($this->tube);
$this->client->delete(new PBA\Values\JobId($jobId));
return $this;
}
}

View File

@ -35,7 +35,7 @@ class Persona extends Ideal\Service
/**
* @param int $rut
* @return Model\Persona
* @throws Read|Create
* @throws Read
*/
public function getById(int $rut): Model\Persona
{
@ -44,7 +44,11 @@ class Persona extends Ideal\Service
} catch (Implement\Exception\EmptyResult) {
try {
$this->propietarioRepository->fetchById($rut);
return $this->add(compact('rut'));
try {
return $this->add(compact('rut'));
} catch (Create $exception) {
throw new Read(__CLASS__, $exception);
}
} catch (Implement\Exception\EmptyResult $exception) {
throw new Read(__CLASS__, $exception);
}

View File

@ -5,12 +5,14 @@ use Exception;
use Psr\Http\Message\RequestInterface;
use Psr\Log\LoggerInterface;
use Incoviba\Common\Ideal;
use Incoviba\Exception\ServiceAction\{Create, Read};
use Incoviba\Exception\ServiceAction\{Create, Delete, Read, Update};
use Incoviba\Service;
use Incoviba\Model;
class Queue extends Ideal\Service
{
public function __construct(LoggerInterface $logger, protected Service\Job $jobService, Worker $defaultWorker)
public function __construct(LoggerInterface $logger, protected Service\Job $jobService, Worker $defaultWorker,
protected int $maxRetries = 5)
{
parent::__construct($logger);
$this->register('default', $defaultWorker);
@ -28,29 +30,19 @@ class Queue extends Ideal\Service
try {
$this->jobService->add($configuration);
return true;
} catch (Read $exception) {
} catch (Create $exception) {
$final = new Exception("Could not enqueue job", 0, $exception);
$this->logger->warning($final);
return false;
}
}
/**
* @return array
*/
public function getPendingJobs(): array
public function push(array $configuration): bool
{
return $this->jobService->getPending();
return $this->enqueue($configuration);
}
public function runJob(int $job_id, ?RequestInterface $request = null): bool
{
try {
$job = $this->jobService->getPendingById($job_id);
} catch (Read $exception) {
$this->logger->debug($exception);
return false;
}
public function runJob(Model\Job $job, ?RequestInterface $request = null): bool
{
$type = 'default';
if (isset($job->configuration['type'])) {
$type = strtolower($job->configuration['type']);
@ -66,36 +58,57 @@ class Queue extends Ideal\Service
try {
if (!$worker->execute($job)) {
$this->logger->debug("Could not execute job {$job_id}");
$this->logger->debug("Could not execute job {$job->id}");
$job->retries++;
$this->jobService->update($job);
return false;
}
if (!$this->jobService->execute($job)) {
$this->logger->debug("Could not remove job {$job_id}");
$this->logger->debug("Could not remove job {$job->id}");
return false;
}
} catch (Exception $exception) {
$final = new Exception("Could not run job", 0, $exception);
$this->logger->warning($final);
$this->logger->warning("Could not run job {$job->id}", ['exception' => $exception]);
$job->retries++;
try {
$this->jobService->update($job);
} catch (Update $exception) {
$this->logger->error($exception->getMessage(), ['job' => $job, 'exception' => $exception]);
}
return false;
}
return true;
}
public function run(?RequestInterface $request = null): bool
{
$jobs = $this->jobService->getPending();
if (count($jobs) === 0) {
$this->logger->debug("No pending jobs");
if (!$this->jobService->isPending()) {
return true;
}
$errors = [];
foreach ($jobs as $job) {
try {
$this->runJob($job->id, $request);
} catch (Exception) {
$errors []= $job->id;
}
try {
$job = $this->jobService->get();
} catch (Read $exception) {
$this->logger->error($exception->getMessage(), ['exception' => $exception]);
return false;
}
return count($errors) === 0;
if ($job->retries >= $this->maxRetries) {
try {
$this->jobService->remove($job);
} catch (Delete $exception) {
$this->logger->error($exception->getMessage(), ['job' => $job, 'exception' => $exception]);
}
return true;
}
try {
$this->runJob($job, $request);
} catch (Exception) {
$job->retries ++;
try {
$this->jobService->update($job);
} catch (Update $exception) {
$this->logger->error($exception->getMessage(), ['job' => $job, 'exception' => $exception]);
}
return false;
}
return true;
}
}

View File

@ -18,9 +18,13 @@ class UF
public function get(?DateTimeInterface $date = null): float
{
$today = new DateTimeImmutable();
if ($date === null) {
$date = new DateTimeImmutable();
}
if ($date->diff($today)->days < 0) {
return 0.0;
}
/**
* 1 - Redis
* 2 - DB

View File

@ -4,7 +4,7 @@ namespace Incoviba\Service;
use DateTimeInterface;
use DateTimeImmutable;
use DateMalformedStringException;
use function PHPUnit\Framework\countOf;
use Incoviba\Service\Valor\Phone;
class Valor
{
@ -40,6 +40,14 @@ class Valor
}
return $value / $this->ufService->get($date);
}
public function phone(): Phone
{
return new Phone();
}
public function telefono(): Phone
{
return $this->phone();
}
protected function getDateTime(null|string|DateTimeInterface $date): DateTimeInterface
{

View File

@ -0,0 +1,28 @@
<?php
namespace Incoviba\Service\Valor;
class Phone
{
public function toDatabase(?string $phone): ?int
{
if ($phone === null) {
return null;
}
return (int) str_replace([' ', '+'], '', $phone) ?? null;
}
public function toDisplay(?int $phone): ?string
{
if ($phone === null) {
return null;
}
$parts = preg_split('/(?=<country>\d{2})?(?=<area>\d)(?=<first>\d{4})(?=<last>\d{4})/', $phone);
$output = [];
if (array_key_exists('country', $parts)) {
$output [] = "+{$parts[0]}";
}
$output [] = $parts[1] ?? '';
$output [] = $parts[2] ?? '';
$output [] = $parts[3] ?? '';
return implode(' ', $output);
}
}

View File

@ -1,10 +1,10 @@
<?php
namespace Incoviba\Service;
use Exception;
use DateTimeImmutable;
use DateMalformedStringException;
use Incoviba\Exception\ServiceAction\{Create, Read, Update};
use Incoviba\Common\Define;
use PDOException;
use Psr\Log\LoggerInterface;
use Incoviba\Common\Ideal\Service;
@ -12,7 +12,7 @@ use Incoviba\Common\Implement;
use Incoviba\Repository;
use Incoviba\Model;
class Venta extends Service
class Venta extends Service\Repository
{
public function __construct(
LoggerInterface $logger,
@ -189,6 +189,11 @@ class Venta extends Service
}
}
public function getRepository(): Define\Repository
{
return $this->ventaRepository;
}
protected function process(Model\Venta $venta): Model\Venta
{
if ($venta->uf === 0.0) {

View File

@ -9,6 +9,7 @@ use Psr\Log\LoggerInterface;
use Incoviba\Common\Ideal;
use Incoviba\Common\Implement\Exception\EmptyResult;
use Incoviba\Exception\ServiceAction\Create;
use Incoviba\Exception\ServiceAction\Read;
use Incoviba\Repository;
use Incoviba\Model;
@ -23,6 +24,20 @@ class Cuota extends Ideal\Service
parent::__construct($logger);
}
/**
* @param int $cuota_id
* @return Model\Venta\Cuota
* @throws Read
*/
public function getById(int $cuota_id): Model\Venta\Cuota
{
try {
return $this->process($this->cuotaRepository->fetchById($cuota_id));
} catch (EmptyResult $exception) {
throw new Read(__CLASS__, $exception);
}
}
public function pendientes(): array
{
$cuotas = $this->cuotaRepository->fetchPendientes();
@ -130,4 +145,9 @@ class Cuota extends Ideal\Service
throw new Create(__CLASS__, $exception);
}
}
protected function process(Model\Venta\Cuota $cuota): Model\Venta\Cuota
{
return $cuota;
}
}

View File

@ -1,14 +1,13 @@
<?php
namespace Incoviba\Service\Venta\MediosPago;
use HttpException;
use PDOException;
use Psr\Http\Client\ClientExceptionInterface;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\ResponseInterface;
use Incoviba\Common\Define\Repository;
use Incoviba\Common\Ideal\LoggerEnabled;
use Incoviba\Common\Implement\Exception\{EmptyResponse, EmptyResult};
use Incoviba\Common\Implement\Exception\{EmptyResponse, EmptyResult, HttpException};
use Incoviba\Exception\InvalidResult;
abstract class AbstractEndPoint extends LoggerEnabled implements EndPoint
@ -77,14 +76,24 @@ abstract class AbstractEndPoint extends LoggerEnabled implements EndPoint
* @param array $data
* @param array $validStatus
* @param array $invalidStatus
* @param string|null $accountKey
* @return bool
* @throws EmptyResponse
*/
protected function sendAdd(string $request_uri, array $data, array $validStatus, array $invalidStatus): bool
protected function sendAdd(string $request_uri, array $data, array $validStatus, array $invalidStatus, ?string $accountKey = null): bool
{
$params = $this->mapParams($data);
$this->logger->info('Send Add', ['uri' => $request_uri, 'params' => $params]);
try {
$response = $this->client->post($request_uri, ['json' => $params]);
$options = [
'json' => $params
];
if ($accountKey !== null) {
$options['headers'] = [
'X-Account-Key' => $accountKey
];
}
$response = $this->client->post($request_uri, $options);
} catch (ClientExceptionInterface $exception) {
throw new EmptyResponse($request_uri, $exception);
}
@ -102,6 +111,7 @@ abstract class AbstractEndPoint extends LoggerEnabled implements EndPoint
throw new EmptyResponse($request_uri);
}
$json = json_decode($contents, true);
$this->logger->info('Add Response', $json);
return $this->save($json);
}
@ -110,14 +120,23 @@ abstract class AbstractEndPoint extends LoggerEnabled implements EndPoint
* @param array $data
* @param array $validStatus
* @param array $invalidStatus
* @param string|null $accountKey
* @return bool
* @throws EmptyResponse
*/
protected function sendEdit(string $request_uri, array $data, array $validStatus, array $invalidStatus): bool
protected function sendEdit(string $request_uri, array $data, array $validStatus, array $invalidStatus, ?string $accountKey = null): bool
{
$params = $this->mapParams($data);
try {
$response = $this->client->put($request_uri, ['json' => $params]);
$options = [
'json' => $params
];
if ($accountKey !== null) {
$options['headers'] = [
'X-Account-Key' => $accountKey
];
}
$response = $this->client->put($request_uri, $options);
} catch (ClientExceptionInterface $exception) {
throw new EmptyResponse($request_uri, $exception);
}
@ -133,18 +152,25 @@ abstract class AbstractEndPoint extends LoggerEnabled implements EndPoint
* @param string $request_uri
* @param array $validStatus
* @param array $invalidStatus
* @param array|null $data
* @return void
* @throws EmptyResponse
*/
protected function sendDelete(string $request_uri, array $validStatus, array $invalidStatus): void
protected function sendDelete(string $request_uri, array $validStatus, array $invalidStatus, ?array $data = null): void
{
$this->logger->info('Send Delete', ['uri' => $request_uri]);
try {
$response = $this->client->delete($request_uri);
$options = [];
if ($data !== null) {
$options = ['json' => $data];
}
$response = $this->client->delete($request_uri, $options);
} catch (ClientExceptionInterface $exception) {
throw new EmptyResponse($request_uri, $exception);
}
$this->validateResponse($response, $request_uri, $validStatus, $invalidStatus);
$this->logger->info('Delete Response', ['request_uri' => $request_uri]);
}
protected function doSave(Repository $repository, array $data): bool
{

View File

@ -28,18 +28,20 @@ interface EndPoint
/**
* @param array $data
* @param string|null $accountKey
* @return bool
* @throws EmptyResponse
*/
public function add(array $data): bool;
public function add(array $data, ?string $accountKey = null): bool;
/**
* @param string $id
* @param array $data
* @param string|null $accountKey
* @return bool
* @throws EmptyResponse
*/
public function edit(string $id, array $data): bool;
public function edit(string $id, array $data, ?string $accountKey = null): bool;
/**
* @param string $id

View File

@ -1,8 +1,10 @@
<?php
namespace Incoviba\Service\Venta\MediosPago;
use Incoviba\Common\Implement\Exception\EmptyResult;
use InvalidArgumentException;
use PDO;
use PDOException;
use Psr\Http\Message\ServerRequestInterface;
use Incoviba\Common\Define\Connection;
use Incoviba\Common\Ideal;
@ -80,13 +82,18 @@ class Toku extends Ideal\Service
try {
return $this->subscription->getById($venta->id);
} catch (InvalidResult $exception) {
$inmobiliaria = $venta->proyecto()->inmobiliaria();
$accountKey = null;
try {
$accountKey = $this->getAccountKey($inmobiliaria->rut);
} catch (EmptyResult) {}
$subscriptionData = [
'customer' => $customer['toku_id'],
'product_id' => $venta->id,
'venta' => $venta
];
try {
if (!$this->subscription->add($subscriptionData)) {
if (!$this->subscription->add($subscriptionData, $accountKey)) {
throw new InvalidResult("Could not save Subscription for Venta {$venta->id}", 409, $exception);
}
} catch (EmptyResponse $exception) {
@ -95,7 +102,6 @@ class Toku extends Ideal\Service
return $this->subscription->getById($venta->id);
}
}
/**
* @param Model\Venta $venta
* @param array $cuotas_ids
@ -115,6 +121,12 @@ class Toku extends Ideal\Service
});
} catch (EmptyResponse) {}
$inmobiliaria = $venta->proyecto()->inmobiliaria();
$accountKey = null;
try {
$accountKey = $this->getAccountKey($inmobiliaria->rut);
} catch (EmptyResult) {}
$invoices = [];
$errors = [];
foreach ($venta->formaPago()->pie->cuotas() as $cuota) {
@ -142,7 +154,7 @@ class Toku extends Ideal\Service
'cuota' => $cuota,
'venta' => $venta
];
if (!$this->invoice->add($invoiceData)) {
if (!$this->invoice->add($invoiceData, $accountKey)) {
throw new EmptyResponse("Could not add Invoice for Cuota {$cuota->id}", $exception);
}
$invoices []= $this->invoice->getById($cuota->id);
@ -266,6 +278,7 @@ class Toku extends Ideal\Service
try {
$output['customer'] = $this->customer->reset($skips['customer'] ?? []);
$output['subscription'] = $this->subscription->reset($skips['subscription'] ?? []);
$output['payments'] = $this->invoice->resetPayments();
$output['invoice'] = $this->invoice->reset($skips['invoice'] ?? []);
} catch (InvalidResult $exception) {
$this->logger->warning($exception);
@ -289,6 +302,95 @@ class Toku extends Ideal\Service
return $queues;
}
public function update(array $ids, ?string $type = null): array
{
if ($type === null) {
$types = [
'customers',
'subscriptions',
'invoices'
];
$results = [];
foreach ($types as $type) {
$results[$type] = $this->update($ids[$type], $type);
}
return $results;
}
$results = [];
switch ($type) {
case 'subscriptions':
try {
$results['subscription'] = $this->subscription->update($ids);
} catch (EmptyResult | EmptyResponse $exception) {
$this->logger->error($exception);
}
break;
case 'invoices':
try {
$results['invoice'] = $this->invoice->updateAll($ids);
} catch (EmptyResult $exception) {
$this->logger->error($exception);
}
break;
}
return $results;
}
/**
* @param ServerRequestInterface $request
* @param array $tokenConfig
* @return bool
*/
public function validateToken(ServerRequestInterface $request, array $tokenConfig): bool
{
if (!$request->hasHeader('User-Agent') or !str_starts_with($request->getHeaderLine('User-Agent'), 'Toku-Webhooks')) {
return false;
}
if (!$request->hasHeader('X-Datadog-Tags') or !$request->hasHeader('Tracestate')) {
return false;
}
if (!$request->hasHeader('Toku-Signature')) {
return false;
}
$tokuSignature = $request->getHeaderLine('Toku-Signature');
try {
list($timestamp, $signature) = array_map(function($elem) {
return explode('=', $elem)[1];
}, explode(',', $tokuSignature));
$body = $request->getBody()->getContents();
$json = json_decode($body, true);
if (!is_array($json)) {
return false;
}
if (!array_key_exists('id', $json)) {
return false;
}
$eventId = $json['id'];
$eventType = $json['event_type'];
$query = $this->connection->getQueryBuilder()
->select('secret')
->from('toku_webhooks')
->where('enabled = ? AND JSON_SEARCH(events, "one", ?) IS NOT NULL');
$params = [true, $eventType];
$statement = $this->connection->prepare($query);
$statement->execute($params);
$results = $statement->fetchAll(PDO::FETCH_COLUMN);
if (count($results) === 0) {
return false;
}
if (array_any($results, fn($secret) => $this->hmac->validate($timestamp, $signature, $eventId, $secret))) {
return true;
}
} catch (Throwable $throwable) {
$this->logger->error($throwable);
}
return false;
}
/**
* @param array $request
* @return bool
@ -405,54 +507,20 @@ class Toku extends Ideal\Service
$data['date'] = $data['transaction_date'];
return $data;
}
public function validateToken(ServerRequestInterface $request, array $tokenConfig): bool
protected function getAccountKey(int $sociedad_rut): string
{
if (!$request->hasHeader('User-Agent') or !str_starts_with($request->getHeaderLine('User-Agent'), 'Toku-Webhooks')) {
return false;
}
if (!$request->hasHeader('X-Datadog-Tags') or !$request->hasHeader('Tracestate')) {
return false;
}
if (!$request->hasHeader('Toku-Signature')) {
return false;
}
$tokuSignature = $request->getHeaderLine('Toku-Signature');
$query = $this->connection->getQueryBuilder()
->select('account_key')
->from('toku_accounts')
->where('enabled = ? AND sociedad_rut = ?');
$params = [true, $sociedad_rut];
try {
list($timestamp, $signature) = array_map(function($elem) {
return explode('=', $elem)[1];
}, explode(',', $tokuSignature));
$body = $request->getBody()->getContents();
$json = json_decode($body, true);
if (!is_array($json)) {
return false;
}
if (!array_key_exists('id', $json)) {
return false;
}
$eventId = $json['id'];
$eventType = $json['event_type'];
$query = $this->connection->getQueryBuilder()
->select('secret')
->from('toku_webhooks')
->where('enabled = ? AND JSON_SEARCH(events, "one", ?) IS NOT NULL');
$params = [true, $eventType];
$statement = $this->connection->prepare($query);
$statement->execute($params);
$results = $statement->fetchAll(PDO::FETCH_COLUMN);
if (count($results) === 0) {
return false;
}
if (array_any($results, fn($secret) => $this->hmac->validate($timestamp, $signature, $eventId, $secret))) {
return true;
}
} catch (Throwable $throwable) {
$this->logger->error($throwable);
return $statement->fetchColumn();
} catch (PDOException $exception) {
$this->logger->error($exception);
throw new EmptyResult($query, $exception);
}
return false;
}
}

View File

@ -1,10 +1,10 @@
<?php
namespace Incoviba\Service\Venta\MediosPago\Toku;
use PDOException;
use Psr\Http\Client\ClientInterface;
use Incoviba\Common\Implement\Exception\EmptyResponse;
use Incoviba\Common\Implement\Exception\EmptyResult;
use Incoviba\Model;
use Incoviba\Repository;
use Incoviba\Service\Venta\MediosPago\AbstractEndPoint;
@ -29,15 +29,15 @@ class Customer extends AbstractEndPoint
$request_uri = "/customers/{$id}";
return $this->sendGet($request_uri, [200], [404, 422]);
}
public function add(array $data): bool
public function add(array $data, ?string $accountKey = null): bool
{
$request_uri = "/customers";
return $this->sendAdd($request_uri, $data, [200, 201], [400, 422]);
return $this->sendAdd($request_uri, $data, [200, 201], [400, 422], $accountKey);
}
public function edit(string $id, array $data): bool
public function edit(string $id, array $data, ?string $accountKey = null): bool
{
$request_uri = "customers/{$id}";
return $this->sendEdit($request_uri, $data, [200], [400, 404, 422]);
return $this->sendEdit($request_uri, $data, [200], [400, 404, 422], $accountKey);
}
public function delete(string $id): void
{
@ -47,24 +47,24 @@ class Customer extends AbstractEndPoint
public function reset(array $skip = []): array
{
try {
$customers = $this->customerRepository->fetchAll();
$customers = array_filter($customers, function (Model\Venta\MediosPago\Toku\Customer $customer) use ($skip) {
return !in_array($customer->toku_id, $skip);
$tokuIds = $this->customerRepository->fetchAllTokuIds();
$tokuIds = array_filter($tokuIds, function (string $tokuId) use ($skip) {
return !in_array($tokuId, $skip);
});
} catch (EmptyResult $exception) {
$this->logger->warning($exception);
return [];
}
$this->logger->info('Resetando ' . count($customers) . ' clientes');
foreach ($customers as $customer) {
$this->logger->info('Resetando ' . count($tokuIds) . ' clientes');
foreach ($tokuIds as $tokuId) {
try {
$this->delete($customer->toku_id);
$this->customerRepository->remove($customer);
} catch (EmptyResponse $exception) {
$this->logger->warning($exception, ['customer' => $customer]);
$this->delete($tokuId);
$this->customerRepository->removeByTokuId($tokuId);
} catch (EmptyResponse | PDOException $exception) {
$this->logger->warning($exception, ['customer->toku_id' => $tokuId]);
}
}
return $customers;
return $tokuIds;
}
public function save(array $data): bool
@ -76,7 +76,7 @@ class Customer extends AbstractEndPoint
$paramsMap = [
'government_id' => 'rut',
'external_id' => 'rut',
'email' => 'email',
'mail' => 'email',
'name' => 'nombreCompleto',
'phone_number' => 'telefono',
'pac_mandate_id' => null,

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