diff --git a/app/common/Define/Connection.php b/app/common/Define/Connection.php index 577c094..a35af54 100644 --- a/app/common/Define/Connection.php +++ b/app/common/Define/Connection.php @@ -11,4 +11,5 @@ interface Connection public function prepare(string $query): PDOStatement; public function execute(string $query, ?array $data = null): PDOStatement; public function getPDO(): PDO; + public function getQueryBuilder(): Query\Builder; } diff --git a/app/common/Define/Model.php b/app/common/Define/Model.php index d7910fd..2f42207 100644 --- a/app/common/Define/Model.php +++ b/app/common/Define/Model.php @@ -5,4 +5,5 @@ use JsonSerializable; interface Model extends JsonSerializable { + public function addFactory(string $property, Repository\Factory $factory): Model; } diff --git a/app/common/Define/Query.php b/app/common/Define/Query.php new file mode 100644 index 0000000..f22208a --- /dev/null +++ b/app/common/Define/Query.php @@ -0,0 +1,15 @@ +build(); + } +} diff --git a/app/common/Ideal/Repository.php b/app/common/Ideal/Repository.php index cb49210..993d62c 100644 --- a/app/common/Ideal/Repository.php +++ b/app/common/Ideal/Repository.php @@ -31,18 +31,25 @@ abstract class Repository implements Define\Repository public function remove(Define\Model $model): void { - $query = "DELETE FROM `{$this->getTable()}` WHERE `{$this->getKey()}` = ?"; - $this->connection->execute($query, [$model->getId()]); + $query = $this->connection->getQueryBuilder() + ->delete()->from($this->getTable()) + ->where("{$this->getKey()} = ?"); + $this->connection->execute($query, [$model->id]); } public function fetchById(int $id): Define\Model { - $query = "SELECT * FROM `{$this->getTable()}` WHERE `{$this->getKey()}` = ?"; + $query = $this->connection->getQueryBuilder() + ->select() + ->from($this->getTable()) + ->where("{$this->getKey()} = ?"); return $this->fetchOne($query, [$id]); } public function fetchAll(): array { - $query = "SELECT * FROM `{$this->getTable()}`"; + $query = $this->connection->getQueryBuilder() + ->select() + ->from($this->getTable()); return $this->fetchMany($query); } @@ -96,9 +103,11 @@ abstract class Repository implements Define\Repository } protected function saveNew(array $columns, array $values): int { - $columns_string = implode(', ', array_map(function($column) {return "`{$column}`";}, $columns)); - $columns_questions = implode(', ', array_fill(0, count($columns), '?')); - $query = "INSERT INTO `{$this->getTable()}` ({$columns_string}) VALUES ($columns_questions)"; + $query = $this->connection->getQueryBuilder() + ->insert() + ->into($this->getTable()) + ->columns($columns) + ->values(array_fill(0, count($columns), '?')); $this->connection->execute($query, $values); return $this->connection->getPDO()->lastInsertId(); } @@ -117,7 +126,10 @@ abstract class Repository implements Define\Repository return $model; } $columns_string = implode(', ', array_map(function($property) {return "`{$property}` = ?";}, $changes)); - $query = "UPDATE `{$this->getTable()}` SET {$columns_string} WHERE `{$this->getKey()}` = ?"; + $query = $this->connection->getQueryBuilder() + ->update($this->getTable()) + ->set($columns_string) + ->where("{$this->getKey()} = ?"); $values []= $model->{$this->getKey()}; $this->connection->execute($query, $values); return $this->fetchById($model->{$this->getKey()}); diff --git a/app/common/Implement/Connection.php b/app/common/Implement/Connection.php index bcb6eab..761151a 100644 --- a/app/common/Implement/Connection.php +++ b/app/common/Implement/Connection.php @@ -8,7 +8,7 @@ use Incoviba\Common\Define; class Connection implements Define\Connection { - public function __construct(protected Define\Database $database) {} + public function __construct(protected Define\Database $database, protected Database\Query\Builder $queryBuilder) {} protected PDO $connection; public function connect(): Define\Connection @@ -27,6 +27,10 @@ class Connection implements Define\Connection $this->connect(); return $this->connection; } + public function getQueryBuilder(): Database\Query\Builder + { + return $this->queryBuilder; + } public function query(string $query): PDOStatement { diff --git a/app/common/Implement/Database/Query/Builder.php b/app/common/Implement/Database/Query/Builder.php new file mode 100644 index 0000000..82a3c5f --- /dev/null +++ b/app/common/Implement/Database/Query/Builder.php @@ -0,0 +1,29 @@ +table($table_name); + } + + public function select(array|string $columns = '*'): Select + { + return (new Select())->columns($columns); + } + public function insert(): Insert + { + return new Insert(); + } + public function update(string $table): Update + { + return (new Update())->table($table); + } + public function delete(): Delete + { + return new Delete(); + } +} diff --git a/app/common/Implement/Database/Query/Create.php b/app/common/Implement/Database/Query/Create.php new file mode 100644 index 0000000..2c21bfe --- /dev/null +++ b/app/common/Implement/Database/Query/Create.php @@ -0,0 +1,102 @@ +name = $name; + return $this; + } + public function definitions(Define\Query\Create\CreateDefinition|array|string $create_definitions): Define\Query\Create + { + if (is_array($create_definitions)) { + foreach ($create_definitions as $definition) { + $this->addDefinition($definition); + } + return $this; + } + return $this->addDefinition($create_definitions); + } + public function options(array|string $table_options): Define\Query\Create + { + if (is_string($table_options)) { + return $this->addOption($table_options); + } + foreach ($table_options as $option) { + $this->addOption($option); + } + return $this; + } + public function partition(array|string $partition_options): Define\Query\Create + { + if (is_string($partition_options)) { + return $this->addPartition($partition_options); + } + foreach ($partition_options as $option) { + $this->addPartition($option); + } + return $this; + } + public function build(): string + { + $query = [ + "CREATE TABLE {$this->name}", + $this->getDefinitions(), + $this->getOptions(), + $this->getPartitions() + ]; + return implode('', $query); + } + + protected function addDefinition(string $definition): Create + { + if (!isset($this->definitions)) { + $this->definitions = []; + } + $this->definitions []= $definition; + return $this; + } + protected function addOption(string $option): Create + { + if (!isset($this->options)) { + $this->options = []; + } + $this->options []= $option; + return $this; + } + protected function addPartition(string $partition): Create + { + if (!isset($this->partitions)) { + $this->partitions = []; + } + $this->partitions []= $partition; + return $this; + } + protected function getDefinitions(): string + { + return ' (' . implode(', ', $this->definitions) . ')'; + } + protected function getOptions(): string + { + if (!isset($this->options) or count($this->options) <= 0) { + return ''; + } + return ' ' . implode(' ', $this->options); + } + protected function getPartitions(): string + { + if (!isset($this->partitions) or count($this->partitions) <= 0) { + return ''; + } + return ' PARTITION BY (' . implode(', ', $this->partitions) . ')'; + } +} diff --git a/app/common/Implement/Database/Query/Create/Definition.php b/app/common/Implement/Database/Query/Create/Definition.php new file mode 100644 index 0000000..a81680c --- /dev/null +++ b/app/common/Implement/Database/Query/Create/Definition.php @@ -0,0 +1,104 @@ +name = $name; + return $this; + } + public function type(string $type, ?int $size = null): CreateDefinition + { + $this->type = $type; + if ($size !== null) { + $this->size = $size; + } + return $this; + } + public function primary(): CreateDefinition + { + $this->primary = true; + return $this; + } + public function autoIncrement(): CreateDefinition + { + $this->auto_increment = true; + return $this; + } + public function unsigned(): CreateDefinition + { + $this->unsigned = true; + return $this; + } + public function default(mixed $value): CreateDefinition + { + $this->default = $value; + return $this; + } + public function foreign(string $reference_table, string $reference_column = 'id', int $on_delete = CreateDefinition::CASCADE, int $on_update = CreateDefinition::CASCADE): CreateDefinition + { + $this->foreign_table = $reference_table; + $this->foreign_key = $reference_column; + $this->foreign_delete = $on_delete; + $this->foreign_update = $on_update; + return $this; + } + + public function __toString(): string + { + $type = $this->type ?? 'int'; + if (isset($this->size)) { + $type = "{$type}({$this->size})"; + } elseif (in_array($type, ['varchar'])) { + $type = "{$type}(255)"; + } + $output = [ + "`{$this->name}`", + $type + ]; + if (isset($this->unsigned)) { + $output []= 'UNSIGNED'; + } + if (isset($this->default)) { + $default = $this->default; + if (in_array($this->type, ['varchar', 'text', 'char'])) { + $default = "'{$default}'"; + } + $output []= "DEFAULT {$default}"; + } + if (isset($this->auto_increment)) { + $output []= 'AUTO_INCREMENT'; + } + if (isset($this->primary)) { + $output []= 'PRIMARY KEY'; + } + if (isset($this->foreign_table)) { + $output []= "REFERENCES `{$this->foreign_table}` (`{$this->foreign_key}`)"; + $on = [ + 'RESTRICT', + 'CASCADE', + 'SET_NULL', + 'NO_ACTION', + 'SET_DEFAULT' + ]; + $output []= "ON DELETE {$on[$this->foreign_delete]}"; + $output []= "ON UPDATE {$on[$this->foreign_update]}"; + } + return implode(' ', $output); + } +} diff --git a/app/common/Implement/Database/Query/Delete.php b/app/common/Implement/Database/Query/Delete.php new file mode 100644 index 0000000..c193d98 --- /dev/null +++ b/app/common/Implement/Database/Query/Delete.php @@ -0,0 +1,89 @@ +table = $table; + return $this; + } + public function where(array|string $conditions): Delete + { + if (is_string($conditions)) { + return $this->addCondition($conditions); + } + foreach ($conditions as $condition) { + $this->addCondition($condition); + } + return $this; + } + public function order(array|string $sorting): Delete + { + if (is_string($sorting)) { + return $this->addOrder($sorting); + } + foreach ($sorting as $order) { + $this->addOrder($order); + } + return $this; + } + public function limit(int $limit): Delete + { + $this->limit = $limit; + return $this; + } + public function build(): string + { + $query = [ + "DELETE FROM {$this->table}", + $this->getConditions(), + $this->getSorting(), + $this->getLimit() + ]; + return implode('', $query); + } + + protected function addCondition(string $condition): Delete + { + if (!isset($this->conditions)) { + $this->conditions = []; + } + $this->conditions []= $condition; + return $this; + } + protected function getConditions(): string + { + return ' WHERE ' . implode(' AND ', $this->conditions); + } + protected function addOrder(string $order): Delete + { + if (!isset($this->sorts)) { + $this->sorts = []; + } + $this->sorts []= $order; + return $this; + } + protected function getSorting(): string + { + if (!isset($this->sorts) or count($this->sorts) === 0) { + return ''; + } + return ' ORDER BY ' . implode(', ', $this->sorts); + } + protected function getLimit(): string + { + if (!isset($this->limit) or $this->limit <= 0) { + return ''; + } + return " LIMIT {$this->limit}"; + } +} diff --git a/app/common/Implement/Database/Query/Insert.php b/app/common/Implement/Database/Query/Insert.php new file mode 100644 index 0000000..6c9b7cf --- /dev/null +++ b/app/common/Implement/Database/Query/Insert.php @@ -0,0 +1,87 @@ +table = $table; + if ($columns !== null) { + return $this->columns($columns); + } + return $this; + } + public function columns(array $columns): Insert + { + foreach ($columns as $column) { + $this->addColumn($column); + } + return $this; + } + public function values(array $values): Insert + { + foreach ($values as $value) { + $this->addValue($value); + } + return $this; + } + public function select(Define\Query\Select $select): Insert + { + $this->select = $select; + return $this; + } + public function build(): string + { + $query = [ + "INSERT INTO {$this->table}" + ]; + if (isset($this->select)) { + $query []= " {$this->select}"; + return implode('', $query); + } + $query []= $this->getColumns(); + $query []= $this->getValues(); + return implode('', $query); + } + + protected function addColumn(string $column): Insert + { + if (!isset($this->columns)) { + $this->columns = []; + } + $this->columns []= $column; + return $this; + } + protected function addValue(mixed $value): Insert + { + if (!isset($this->values)) { + $this->values = []; + } + $this->values []= $value; + return $this; + } + protected function getColumns(): string + { + return ' (' . implode(', ', array_map(function(string $column) {return "`{$column}`";}, $this->columns)) . ')'; + } + protected function getValues(): string + { + return ' VALUES (' . implode(', ', array_map(function($value) { + if ($value === '?') { + return $value; + } + if ($value === (int) $value) { + return $value; + } + return "'{$value}'"; + }, $this->values)) . ')'; + } +} diff --git a/app/common/Implement/Database/Query/Select.php b/app/common/Implement/Database/Query/Select.php new file mode 100644 index 0000000..0f7a88b --- /dev/null +++ b/app/common/Implement/Database/Query/Select.php @@ -0,0 +1,215 @@ +addColumn($expressions); + } + foreach ($expressions as $expression) { + $this->addColumn($expression); + } + return $this; + } + public function from(string $table): Select + { + $this->table = $table; + return $this; + } + public function joined(array|string $joins): Select + { + if (is_string($joins)) { + return $this->addJoin($joins); + } + foreach ($joins as $join) { + $this->addJoin($join); + } + return $this; + } + public function where(array|string $conditions): Select + { + if (is_string($conditions)) { + return $this->addCondition($conditions); + } + foreach ($conditions as $condition) { + $this->addCondition($condition); + } + return $this; + } + public function group(array|string $grouping): Select + { + if (is_string($grouping)) { + return $this->addGroup($grouping); + } + foreach ($grouping as $group) { + $this->addGroup($group); + } + return $this; + } + public function having(array|string $conditions): Select + { + if (is_string($conditions)) { + return $this->addCondition($conditions); + } + foreach ($conditions as $condition) { + $this->addCondition($condition); + } + return $this; + } + public function order(array|string $sorting): Select + { + if (is_string($sorting)) { + return $this->addOrder($sorting); + } + foreach ($sorting as $order) { + $this->addOrder($order); + } + return $this; + } + public function limit(int $limit, ?int $offset = null): Select + { + $this->limit = $limit; + if ($offset !== null) { + return $this->offset($offset); + } + return $this; + } + public function offset(int $offset): Select + { + $this->offset = $offset; + return $this; + } + public function build(): string + { + $query = [ + "SELECT {$this->getColumns()} FROM {$this->table}", + $this->getJoins(), + $this->getConditions(), + $this->getGroups(), + $this->getHaving(), + $this->getOrder(), + $this->getLimit() + ]; + return implode('', $query); + } + + protected function addColumn(string $expression): Select + { + if (!isset($this->columns)) { + $this->columns = []; + } + $this->columns []= $expression; + return $this; + } + protected function addJoin(string $join): Select + { + if (!isset($this->joins)) { + $this->joins = []; + } + $this->joins []= $join; + return $this; + } + protected function addCondition(string $condition): Select + { + if (!isset($this->coditions)) { + $this->conditions = []; + } + $this->conditions []= $condition; + return $this; + } + protected function addGroup(string $group): Select + { + if (!isset($this->groups)) { + $this->groups = []; + } + $this->groups []= $group; + return $this; + } + protected function addHaving(string $having): Select + { + if (!isset($this->haves)) { + $this->haves = []; + } + $this->haves []= $having; + return $this; + } + protected function addOrder(string $order): Select + { + if (!isset($this->orders)) { + $this->orders = []; + } + $this->orders []= $order; + return $this; + } + protected function getColumns(): string + { + if (!isset($this->columns) or count($this->columns) === 0) { + return '*'; + } + return implode(', ', $this->columns); + } + protected function getJoins(): string + { + if (!isset($this->joins) or count($this->joins) === 0) { + return ''; + } + return ' ' . implode(' ', $this->joins); + } + protected function getConditions(): string + { + if (!isset($this->conditions) or count($this->conditions) === 0) { + return ''; + } + return ' WHERE ' . implode(' AND ', $this->conditions); + } + protected function getGroups(): string + { + if (!isset($this->groups) or count($this->groups) === 0) { + return ''; + } + return ' GROUP BY ' . implode(', ', $this->groups); + } + protected function getHaving(): string + { + if (!isset($this->haves) or count($this->haves) === 0) { + return ''; + } + return ' HAVING ' . implode(' AND ', $this->haves); + } + protected function getOrder(): string + { + if (!isset($this->orders) or count($this->orders) === 0) { + return ''; + } + return ' ORDER BY ' . implode(', ', $this->orders); + } + protected function getLimit(): string + { + if (!isset($this->limit) or $this->limit <= 0) { + return ''; + } + return " LIMIT {$this->limit}{$this->getOffset()}"; + } + protected function getOffset(): string + { + if (!isset($this->offset) or $this->offset <= 0) { + return ''; + } + return " OFFSET {$this->offset}"; + } +} diff --git a/app/common/Implement/Database/Query/Update.php b/app/common/Implement/Database/Query/Update.php new file mode 100644 index 0000000..f671363 --- /dev/null +++ b/app/common/Implement/Database/Query/Update.php @@ -0,0 +1,113 @@ +table = $table; + return $this; + } + public function set(array|string $column_pairs): Define\Query\Update + { + if (is_string($column_pairs)) { + return $this->addSet($column_pairs); + } + foreach ($column_pairs as $pair) { + $this->addSet($pair); + } + return $this; + } + public function where(array|string $conditions): Define\Query\Update + { + if (is_string($conditions)) { + return $this->addCondition($conditions); + } + foreach ($conditions as $condition) { + $this->addCondition($condition); + } + return $this; + } + public function order(array|string $ordering): Define\Query\Update + { + if (is_string($ordering)) { + return $this->addOrder($ordering); + } + foreach ($ordering as $order) { + $this->addOrder($order); + } + return $this; + } + public function limit(int $limit): Define\Query\Update + { + $this->limit = $limit; + return $this; + } + public function build(): string + { + $query = [ + "UPDATE {$this->table}", + $this->getSet(), + $this->getConditions(), + $this->getOrder(), + $this->getLimit() + ]; + return implode('', $query); + } + + protected function addSet(string $pair): Update + { + if (!isset($this->setPairs)) { + $this->setPairs = []; + } + $this->setPairs []= $pair; + return $this; + } + protected function addCondition(string $condition): Update + { + if (!isset($this->conditions)) { + $this->conditions = []; + } + $this->conditions []= $condition; + return $this; + } + protected function addOrder(string $order): Update + { + if (!isset($this->orders)) { + $this->orders = []; + } + $this->orders []= $order; + return $this; + } + protected function getSet(): string + { + return ' SET ' . implode(', ', $this->setPairs); + } + protected function getConditions(): string + { + return ' WHERE ' . implode(' AND ', $this->conditions); + } + protected function getOrder(): string + { + if (!isset($this->orders) or count($this->orders) === 0) { + return ''; + } + return ' ORDER BY ' . implode(', ', $this->orders); + } + protected function getLimit(): string + { + if (!isset($this->limit) or $this->limit <= 0) { + return ''; + } + return " LIMIT {$this->limit}"; + } +} diff --git a/app/resources/routes/api/money.php b/app/resources/routes/api/money.php new file mode 100644 index 0000000..2f3fb4c --- /dev/null +++ b/app/resources/routes/api/money.php @@ -0,0 +1,7 @@ +group('/money', function($app) { + $app->post('/ipc[/]', [Money::class, 'ipc']); + $app->post('[/]', [Money::class, 'get']); +}); diff --git a/app/resources/routes/api/ventas.php b/app/resources/routes/api/ventas.php index a635e6b..894128d 100644 --- a/app/resources/routes/api/ventas.php +++ b/app/resources/routes/api/ventas.php @@ -22,6 +22,7 @@ $app->group('/ventas', function($app) { $app->post('[/]', [Ventas::class, 'proyecto']); }); $app->group('/venta/{venta_id}', function($app) { + $app->get('/unidades', [Ventas::class, 'unidades']); $app->get('/comentarios', [Ventas::class, 'comentarios']); $app->get('[/]', [Ventas::class, 'get']); }); diff --git a/app/resources/routes/api/ventas/facturacion.php b/app/resources/routes/api/ventas/facturacion.php new file mode 100644 index 0000000..cae92a8 --- /dev/null +++ b/app/resources/routes/api/ventas/facturacion.php @@ -0,0 +1,6 @@ +group('/facturacion', function($app) { + $app->get('/proyecto/{proyecto_id}[/]', [Facturacion::class, 'proyecto']); +}); diff --git a/app/resources/routes/ventas/facturacion.php b/app/resources/routes/ventas/facturacion.php new file mode 100644 index 0000000..b49f175 --- /dev/null +++ b/app/resources/routes/ventas/facturacion.php @@ -0,0 +1,6 @@ +group('/facturacion', function($app) { + $app->get('[/]', Facturacion::class); +}); diff --git a/app/resources/views/proyectos/unidades.blade.php b/app/resources/views/proyectos/unidades.blade.php index c61af0c..75cfd88 100644 --- a/app/resources/views/proyectos/unidades.blade.php +++ b/app/resources/views/proyectos/unidades.blade.php @@ -224,6 +224,7 @@ const proyecto_id = element.data('proyecto') this.get().tipos(proyecto_id) }) + $('#data').hide() } } $(document).ready(() => { diff --git a/app/resources/views/ventas/facturacion.blade.php b/app/resources/views/ventas/facturacion.blade.php new file mode 100644 index 0000000..7878c84 --- /dev/null +++ b/app/resources/views/ventas/facturacion.blade.php @@ -0,0 +1,384 @@ +@extends('layout.base') + +@section('page_content') +
+

Matriz Facturación

+

+
+
Proyectos
+
+
+ + +
+
+
+

+ +
+
+@endsection + +@include('layout.head.styles.datatables') +@include('layout.body.scripts.datatables') + +@push('page_scripts') + +@endpush diff --git a/app/resources/views/ventas/facturacion/show.blade.php b/app/resources/views/ventas/facturacion/show.blade.php new file mode 100644 index 0000000..07e844e --- /dev/null +++ b/app/resources/views/ventas/facturacion/show.blade.php @@ -0,0 +1,11 @@ +@extends('layout.base') + +@section('page_content') +
+
+
+ +
+
+
+@endsection diff --git a/app/setup/middlewares/97_not_found.php b/app/setup/middlewares/97_not_found.php index 9a61f5f..14d5c25 100644 --- a/app/setup/middlewares/97_not_found.php +++ b/app/setup/middlewares/97_not_found.php @@ -1,2 +1,3 @@ add($app->getContainer()->get(Incoviba\Middleware\NotFound::class)); +$app->add($app->getContainer()->get(Incoviba\Middleware\Errors::class)); diff --git a/app/setup/setups/database.php b/app/setup/setups/database.php index 3cfd071..5ef8d8f 100644 --- a/app/setup/setups/database.php +++ b/app/setup/setups/database.php @@ -12,7 +12,8 @@ return [ }, Incoviba\Common\Define\Connection::class => function(ContainerInterface $container) { return new Incoviba\Common\Implement\Connection( - $container->get(Incoviba\Common\Define\Database::class) + $container->get(Incoviba\Common\Define\Database::class), + $container->get(Incoviba\Common\Implement\Database\Query\Builder::class) ); } ]; diff --git a/app/src/Controller/API/Money.php b/app/src/Controller/API/Money.php new file mode 100644 index 0000000..74d0527 --- /dev/null +++ b/app/src/Controller/API/Money.php @@ -0,0 +1,123 @@ +getParsedBody(); + $output = [ + 'input' => $data + ]; + $output[$data['provider']] = 0; + $redisKey = $data['provider']; + $date = new DateTimeImmutable($data['fecha']); + if (isset($data['start'])) { + $start = new DateTimeImmutable($data['start']); + } + $value = $this->getValue($redisService, $redisKey, $moneyService, $date, $data['provider']); + if (isset($start)) { + $months = $date->diff($start)->m; + $current = clone $start; + $value = $this->getValue($redisService, $redisKey, $moneyService, $current, $data['provider']); + for ($i = 1; $i <= $months; $i ++) { + $current = $current->add(new DateInterval("P{$i}M")); + $value += $this->getValue($redisService, $redisKey, $moneyService, $current, $data['provider']); + } + } + $output[$data['provider']] = $value; + return $this->withJson($response, $output); + } + + protected array $data; + protected function getValue(Service\Redis $redisService, string $redisKey, Service\Money $moneyService, DateTimeInterface $date, string $provider): float + { + if (isset($this->data[$date->format('Y-m-d')])) { + return $this->data[$date->format('Y-m-d')]; + } + try { + $this->data = (array) $this->fetchRedis($redisService, $redisKey); + if (!isset($this->data[$date->format('Y-m-d')])) { + throw new EmptyRedis($redisKey); + } + } catch (EmptyRedis) { + $result = $moneyService->get($provider, $date); + $this->data[$date->format('Y-m-d')] = $result; + $this->saveRedis($redisService, $redisKey, $this->data, $this->time); + } + return $this->data[$date->format('Y-m-d')]; + } + /*public function uf(ServerRequestInterface $request, ResponseInterface $response, Service\Redis $redisService, Service\Money $moneyService): ResponseInterface + { + $body = $request->getParsedBody(); + $output = [ + 'input' => $body, + 'uf' => 0 + ]; + $redisKey = 'uf'; + $date = new DateTimeImmutable($body['fecha']); + try { + $ufs = $this->fetchRedis($redisService, $redisKey); + if (!isset($ufs[$date->format('Y-m-d')])) { + throw new EmptyRedis($redisKey); + } + } catch (EmptyRedis) { + error_log(var_export($ufs,true)); + if (!isset($ufs)) { + $ufs = []; + } + $uf = $moneyService->getUF($date); + $ufs[$date->format('Y-m-d')] = $uf; + $this->saveRedis($redisService, $redisKey, $ufs, 60 * 60 * 24 * 30); + } + $output['uf'] = $ufs[$date->format('Y-m-d')]; + return $this->withJson($response, $output); + }*/ + public function ipc(ServerRequestInterface $request, ResponseInterface $response, Service\Redis $redisService, Service\Money $moneyService): ResponseInterface + { + $data = $request->getParsedBody(); + $output = [ + 'input' => $data, + 'date_string' => '', + 'ipc' => 0 + ]; + $redisKey = 'ipc'; + $start = new DateTimeImmutable($data['start']); + $end = new DateTimeImmutable($data['end']); + $now = new DateTimeImmutable(); + if ($end > $now) { + return $this->withJson($response, $output); + } + $dateKey = "{$start->format('Y-m')}-{$end->format('Y-m')}"; + $months = $start->diff($end)->m; + $current = new DateTimeImmutable((($start <= $end) ? $start : $end)->format('Y-m-15')); + $value = 0; + $ipcs = []; + try { + $ipcs = (array) $this->fetchRedis($redisService, $redisKey); + } catch (EmptyRedis) {} + for ($i = 1; $i < $months; $i ++) { + $current = $current->add(new DateInterval("P1M")); + if (!isset($ipcs[$current->format('Y-m')])) { + $ipcs[$current->format('Y-m')] = $moneyService->getIPC($current); + $this->saveRedis($redisService, $redisKey, $ipcs, $this->time); + } + $value += $ipcs[$current->format('Y-m')]; + } + $output['date_string'] = $dateKey; + $output['ipc'] = $value; + return $this->withJson($response, $output); + } +} diff --git a/app/src/Controller/API/Ventas.php b/app/src/Controller/API/Ventas.php index 2604ae1..c069dd3 100644 --- a/app/src/Controller/API/Ventas.php +++ b/app/src/Controller/API/Ventas.php @@ -2,9 +2,9 @@ namespace Incoviba\Controller\API; use DateTimeImmutable; -use Incoviba\Common\Implement\Exception\EmptyRedis; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; +use Incoviba\Common\Implement\Exception\EmptyRedis; use Incoviba\Common\Implement\Exception\EmptyResult; use Incoviba\Model; use Incoviba\Repository; @@ -153,4 +153,21 @@ class Ventas } return $this->withJson($response, $output); } + public function unidades(ServerRequestInterface $request, ResponseInterface $response, Service\Venta\Unidad $unidadService, int $venta_id): ResponseInterface + { + $output = [ + 'venta_id' => $venta_id, + 'unidades' => [] + ]; + try { + $unidades = $unidadService->getByVenta($venta_id); + $output['unidades'] = json_decode(json_encode($unidades)); + array_walk($output['unidades'], function($unidad, $index, $unidades) { + $unidad->prorrateo = $unidades[$index]->prorrateo; + $unidad->precios = $unidades[$index]->precios; + $unidad->current_precio = $unidades[$index]->currentPrecio; + }, $unidades); + } catch (EmptyResult) {} + return $this->withJson($response, $output); + } } diff --git a/app/src/Controller/API/Ventas/Facturacion.php b/app/src/Controller/API/Ventas/Facturacion.php new file mode 100644 index 0000000..2f94857 --- /dev/null +++ b/app/src/Controller/API/Ventas/Facturacion.php @@ -0,0 +1,36 @@ + $proyecto_id, + 'ventas' => [] + ]; + $today = new DateTimeImmutable(); + $redisKey = "ventas_facturacion-proyecto-{$proyecto_id}-{$today->format('Y-m-d')}"; + try { + $output['ventas'] = $this->fetchRedis($redisService, $redisKey); + } catch (EmptyRedis) { + try { + $output['ventas'] = $ventaService->getActivaByProyecto($proyecto_id); + $this->saveRedis($redisService, $redisKey, $output['ventas']); + } catch (EmptyResult) {} + } + return $this->withJson($response, $output); + } +} diff --git a/app/src/Controller/API/withRedis.php b/app/src/Controller/API/withRedis.php index 0913c11..364d129 100644 --- a/app/src/Controller/API/withRedis.php +++ b/app/src/Controller/API/withRedis.php @@ -14,11 +14,15 @@ trait withRedis } return json_decode($jsonString); } - public function saveRedis(Service\Redis $redisService, string $redisKey, mixed $value): void + public function saveRedis(Service\Redis $redisService, string $redisKey, mixed $value, ?int $expiration = null): void { if (is_array($value) or is_object($value)) { $value = json_encode($value); } + if ($expiration !== null) { + $redisService->set($redisKey, $value, $expiration); + return; + } $redisService->set($redisKey, $value); } } diff --git a/app/src/Controller/Ventas/Facturacion.php b/app/src/Controller/Ventas/Facturacion.php new file mode 100644 index 0000000..90581a9 --- /dev/null +++ b/app/src/Controller/Ventas/Facturacion.php @@ -0,0 +1,21 @@ +getEscriturando(); + return $view->render($response, 'ventas.facturacion', compact('proyectos')); + } + public function show(ServerRequestInterface $request, ResponseInterface $response, View $view, Service\Venta $ventaService, int $venta_id): ResponseInterface + { + $venta = $ventaService->getById($venta_id); + return $view->render($response, 'ventas.facturacion.show', compact('venta')); + } +} diff --git a/app/src/Middleware/Errors.php b/app/src/Middleware/Errors.php new file mode 100644 index 0000000..17cde71 --- /dev/null +++ b/app/src/Middleware/Errors.php @@ -0,0 +1,31 @@ +handle($request); + } catch (Exception $exception) { + $this->logger->notice($exception); + } catch (Error $error) { + $this->logger->error($error); + } + $response = $this->responseFactory->createResponse(404); + if (str_contains($request->getUri()->getPath(), '/api')) { + return $response; + } + return $this->view->render($response, 'construccion'); + } +} diff --git a/app/src/Model/Proyecto/Superficie.php b/app/src/Model/Proyecto/Superficie.php index da7e1a9..5127e3d 100644 --- a/app/src/Model/Proyecto/Superficie.php +++ b/app/src/Model/Proyecto/Superficie.php @@ -1,7 +1,9 @@ bajo_nivel + $this->sobre_nivel; } + + public function jsonSerialize(): mixed + { + return [ + 'sobre_nivel' => $this->sobre_nivel, + 'bajo_nivel' => $this->bajo_nivel, + 'total' => $this->total() + ]; + } } diff --git a/app/src/Model/Proyecto/Terreno.php b/app/src/Model/Proyecto/Terreno.php index ce9c2fe..adcdaad 100644 --- a/app/src/Model/Proyecto/Terreno.php +++ b/app/src/Model/Proyecto/Terreno.php @@ -1,8 +1,21 @@ $this->superficie, + 'valor' => $this->valor, + 'date' => $this->date?->format('Y-m-d') + ]; + } } diff --git a/app/src/Model/Venta/Unidad.php b/app/src/Model/Venta/Unidad.php index 6d66454..d5efdab 100644 --- a/app/src/Model/Venta/Unidad.php +++ b/app/src/Model/Venta/Unidad.php @@ -12,6 +12,7 @@ class Unidad extends Ideal\Model public string $descripcion; public ?string $orientacion = ''; public Model\Proyecto\ProyectoTipoUnidad $proyectoTipoUnidad; + public ?float $prorrateo; public array $precios = []; public ?Precio $currentPrecio = null; @@ -50,7 +51,8 @@ class Unidad extends Ideal\Model 'piso' => $this->piso, 'descripcion' => $this->descripcion, 'orientacion' => $this->orientacion, - 'proyecto_tipo_unidad' => $this->proyectoTipoUnidad + 'proyecto_tipo_unidad' => $this->proyectoTipoUnidad, + 'prorrateo' => $this->prorrateo ]); } } diff --git a/app/src/Repository/Proyecto.php b/app/src/Repository/Proyecto.php index e03de3b..eb7a26b 100644 --- a/app/src/Repository/Proyecto.php +++ b/app/src/Repository/Proyecto.php @@ -1,6 +1,7 @@ superficie = $data['superficie_terreno']; $terreno->valor = $data['valor_terreno']; + $terreno->date = null; + if (isset($data['fecha_terreno']) and $data['fecha_terreno'] !== '') { + $terreno->date = new DateTimeImmutable($data['fecha_terreno']); + } return $terreno; })) ->register('superficie_sobre_nivel', (new Implement\Repository\Mapper()) @@ -67,36 +72,55 @@ class Proyecto extends Ideal\Repository } public function fetchByName(string $name): Define\Model { - $query = "SELECT * FROM `{$this->getTable()}` WHERE `name` = ?"; + $query = $this->connection->getQueryBuilder() + ->select($this->columns()) + ->from("{$this->getTable()} a") + ->joined($this->joinTerreno()) + ->where("name = ?"); return $this->fetchOne($query, [$name]); } public function fetchAllActive(): array { $etapaProyecto = $this->etapaRepository->fetchByDescripcion('Proyecto'); $etapaTerminado = $this->etapaRepository->fetchByDescripcion('Terminado'); - $query = "SELECT a.* -FROM `{$this->getTable()}` a - {$this->joinEstado()} -WHERE et.`orden` BETWEEN {$etapaProyecto->orden} AND ({$etapaTerminado->orden} - 1) -ORDER BY a.`descripcion`"; + $query = $this->connection->getQueryBuilder() + ->select($this->columns()) + ->from("{$this->getTable()} a") + ->joined($this->joinTerreno()) + ->joined($this->joinEstado()) + ->where("et.orden BETWEEN {$etapaProyecto->orden} AND ({$etapaTerminado->orden} - 1)") + ->order('a.descripcion'); return $this->fetchMany($query); } public function fetchAllEscriturando(): array { $etapaRecepcion = $this->etapaRepository->fetchByDescripcion('Recepción'); $etapaTerminado = $this->etapaRepository->fetchByDescripcion('Terminado'); - $query = "SELECT a.* -FROM `{$this->getTable()}` a - {$this->joinEstado()} -WHERE et.`orden` BETWEEN {$etapaRecepcion->orden} AND ({$etapaTerminado->orden} - 1) -ORDER BY a.`descripcion`"; + $query = $this->connection->getQueryBuilder() + ->select($this->columns()) + ->from("{$this->getTable()} a") + ->joined($this->joinTerreno()) + ->joined($this->joinEstado()) + ->where("et.orden BETWEEN {$etapaRecepcion->orden} AND ({$etapaTerminado->orden} - 1)") + ->order('a.descripcion'); return $this->fetchMany($query); } - public function fetchSuperficieVendido(int $proyecto_id): float + /*public function fetchSuperficieVendido(int $proyecto_id): float { - } + }*/ + protected function columns(): string + { + return "a.id, a.inmobiliaria, a.descripcion, a.direccion, a.superficie_terreno, + COALESCE(pt.valor, a.valor_terreno) AS valor_terreno, COALESCE(pt.fecha, '') AS fecha_terreno, a.corredor, + a.superficie_sobre_nivel, a.superficie_bajo_nivel, a.pisos, a.subterraneos"; + } + protected function joinTerreno(): string + { + return "LEFT OUTER JOIN (SELECT pt1.* FROM proyecto_terreno pt1 JOIN ( + SELECT MAX(id) AS id, proyecto_id FROM proyecto_terreno) pt0 ON pt0.id = pt1.id) pt ON pt.proyecto_id = a.id"; + } protected function joinEstado(): string { return "JOIN ( diff --git a/app/src/Repository/Venta/Unidad.php b/app/src/Repository/Venta/Unidad.php index 341665e..252cfa9 100644 --- a/app/src/Repository/Venta/Unidad.php +++ b/app/src/Repository/Venta/Unidad.php @@ -17,7 +17,7 @@ class Unidad extends Ideal\Repository public function create(?array $data = null): Model\Venta\Unidad { - $map = (new Implement\Repository\MapperParser(['subtipo', 'piso', 'descripcion', 'orientacion'])) + $map = (new Implement\Repository\MapperParser(['subtipo', 'piso', 'descripcion', 'orientacion', 'prorrateo'])) ->register('pt', (new Implement\Repository\Mapper()) ->setProperty('proyectoTipoUnidad') ->setFunction(function($data) { @@ -38,62 +38,88 @@ class Unidad extends Ideal\Repository return $this->update($model, ['subtipo', 'piso', 'descripcion', 'orientacion', 'pt'], $new_data); } + public function fetchByVenta(int $venta_id): array + { + $query = $this->connection->getQueryBuilder() + ->select('a.*, up.prorrateo') + ->from("{$this->getTable()} a") + ->joined($this->joinProrrateo()) + ->joined('JOIN propiedad_unidad pu ON pu.unidad = a.id + JOIN venta ON venta.propiedad = pu.propiedad') + ->where('venta.id = ?'); + return $this->fetchMany($query, [$venta_id]); + } public function fetchByPropiedad(int $propiedad_id): array { - $query = "SELECT a.* -FROM `{$this->getTable()}` a - JOIN `propiedad_unidad` pu ON pu.`unidad` = a.`id` -WHERE pu.`propiedad` = ? -GROUP BY a.`id`"; + $query = $this->connection->getQueryBuilder() + ->select('a.*, up.prorrateo') + ->from("{$this->getTable()} a") + ->joined($this->joinProrrateo()) + ->joined('JOIN `propiedad_unidad` pu ON pu.`unidad` = a.`id`') + ->where('pu.propiedad = ?') + ->group('a.id'); return $this->fetchMany($query, [$propiedad_id]); } public function fetchByCierre(int $cierre_id): array { - $query = "SELECT a.* -FROM `{$this->getTable()}` a - JOIN `unidad_cierre` uc ON uc.`unidad` = a.`id` + $query = $this->connection->getQueryBuilder() + ->select('a.*, up.prorrateo') + ->from("{$this->getTable()} a") + ->joined($this->joinProrrateo()) + ->joined("JOIN `unidad_cierre` uc ON uc.`unidad` = a.`id` JOIN `proyecto_tipo_unidad` ptu ON ptu.`id` = a.`pt` - JOIN `tipo_unidad` tu ON tu.`id` = ptu.`tipo` -WHERE uc.`cierre` = ? -GROUP BY a.`id` -ORDER BY tu.`orden`, LPAD(a.`descripcion`, 4, '0')"; + JOIN `tipo_unidad` tu ON tu.`id` = ptu.`tipo`") + ->where('uc.cierre = ?') + ->group('a.id') + ->order("tu.orden, LPAD(a.descripcion, 4, '0')"); return $this->fetchMany($query, [$cierre_id]); } public function fetchByProyecto(int $proyecto_id): array { - $query = "SELECT a.* -FROM `{$this->getTable()}` a - JOIN `proyecto_tipo_unidad` ptu ON ptu.`id` = a.`pt` - JOIN `tipo_unidad` tu ON tu.`id` = ptu.`tipo` -WHERE ptu.`proyecto` = ? -ORDER BY tu.`orden`"; + $query = $this->connection->getQueryBuilder() + ->select('a.*, up.prorrateo') + ->from("{$this->getTable()} a") + ->joined($this->joinProrrateo()) + ->joined("JOIN `proyecto_tipo_unidad` ptu ON ptu.`id` = a.`pt` + JOIN `tipo_unidad` tu ON tu.`id` = ptu.`tipo`") + ->where('ptu.proyecto = ?') + ->order('tu.orden'); return $this->fetchMany($query, [$proyecto_id]); } public function fetchDisponiblesByProyecto(int $proyecto_id): array { - $query = "SELECT DISTINCT a.* -FROM `{$this->getTable()}` a - JOIN `proyecto_tipo_unidad` ptu ON ptu.`id` = a.`pt` + $query = $this->connection->getQueryBuilder() + ->select('DISTINCT a.*, up.prorrateo') + ->from("{$this->getTable()} a") + ->joined($this->joinProrrateo()) + ->joined("JOIN `proyecto_tipo_unidad` ptu ON ptu.`id` = a.`pt` JOIN `tipo_unidad` tu ON tu.`id` = ptu.`tipo` LEFT OUTER JOIN `propiedad_unidad` pu ON pu.`unidad` = a.`id` LEFT OUTER JOIN `venta` ON `venta`.`propiedad` = `pu`.`propiedad` LEFT OUTER JOIN (SELECT ev1.* FROM `estado_venta` ev1 JOIN (SELECT MAX(`id`) as 'id', `venta` FROM `estado_venta`) ev0 ON ev0.`id` = ev1.`id`) ev ON ev.`venta` = `venta`.`id` - LEFT OUTER JOIN `tipo_estado_venta` tev ON tev.`id` = ev.`estado` -WHERE ptu.`proyecto` = ? AND (pu.`id` IS NULL OR `venta`.`id` IS NULL OR tev.`activa` = 0) -ORDER BY tu.`orden`"; + LEFT OUTER JOIN `tipo_estado_venta` tev ON tev.`id` = ev.`estado`") + ->where("ptu.`proyecto` = ? AND (pu.`id` IS NULL OR `venta`.`id` IS NULL OR tev.`activa` = 0)") + ->order('tu.orden'); return $this->fetchMany($query, [$proyecto_id]); } public function fetchDisponiblesByDescripcionAndTipo(string $descripcion, string $tipo): array { - $query = "SELECT DISTINCT a.* -FROM `{$this->getTable()}` a - JOIN `proyecto_tipo_unidad` ptu ON ptu.`id` = a.`pt` + $query = $this->connection->getQueryBuilder() + ->select('DISTINCT a.*, up.prorrateo') + ->from("{$this->getTable()} a") + ->joined($this->joinProrrateo()) + ->joined("JOIN `proyecto_tipo_unidad` ptu ON ptu.`id` = a.`pt` JOIN `tipo_unidad` tu ON tu.`id` = ptu.`tipo` LEFT OUTER JOIN `propiedad_unidad` pu ON pu.`unidad` = a.`id` LEFT OUTER JOIN `venta` ON `venta`.`propiedad` = pu.`propiedad` LEFT OUTER JOIN (SELECT ev1.* FROM `estado_venta` ev1 JOIN (SELECT MAX(`id`) as 'id', `venta` FROM `estado_venta`) ev0 ON ev0.`id` = ev1.`id`) ev ON ev.`venta` = `venta`.`id` - LEFT OUTER JOIN `tipo_estado_venta` tev ON tev.`id` = ev.`estado` -WHERE a.`descripcion` LIKE ? AND tu.`descripcion` = ? AND (pu.`id` IS NULL OR `venta`.`id` IS NULL OR tev.`activa` = 0)"; + LEFT OUTER JOIN `tipo_estado_venta` tev ON tev.`id` = ev.`estado`") + ->where("a.`descripcion` LIKE ? AND tu.`descripcion` = ? AND (pu.`id` IS NULL OR `venta`.`id` IS NULL OR tev.`activa` = 0)"); return $this->fetchMany($query, [$descripcion, $tipo]); } + + protected function joinProrrateo(): string + { + return "LEFT OUTER JOIN unidad_prorrateo up ON up.unidad_id = a.id"; + } } diff --git a/app/src/Service/Money.php b/app/src/Service/Money.php index 3cb7df8..b4d581c 100644 --- a/app/src/Service/Money.php +++ b/app/src/Service/Money.php @@ -2,6 +2,8 @@ namespace Incoviba\Service; use DateTimeInterface; +use DateTimeImmutable; +use DateInterval; use Incoviba\Common\Define\Money\Provider; use Incoviba\Common\Implement\Exception\EmptyResponse; use Incoviba\Service\Money\MiIndicador; @@ -22,6 +24,15 @@ class Money return $this->providers[$name]; } + public function get(string $provider, DateTimeInterface $dateTime): float + { + try { + $upper = strtoupper($provider); + return $this->getProvider($provider)->get(MiIndicador::getSymbol($provider), $dateTime); + } catch (EmptyResponse) { + return 0; + } + } public function getUF(DateTimeInterface $dateTime): float { try { diff --git a/app/src/Service/Money/MiIndicador.php b/app/src/Service/Money/MiIndicador.php index 113bc30..a223250 100644 --- a/app/src/Service/Money/MiIndicador.php +++ b/app/src/Service/Money/MiIndicador.php @@ -36,4 +36,11 @@ class MiIndicador implements Provider } return $json->serie[0]->valor; } + public static function getSymbol(string $identifier): string + { + $upper = strtoupper($identifier); + $output = ''; + eval("\$output = self::{$upper};"); + return $output; + } } diff --git a/app/src/Service/Proyecto.php b/app/src/Service/Proyecto.php index c6de619..2facad9 100644 --- a/app/src/Service/Proyecto.php +++ b/app/src/Service/Proyecto.php @@ -15,6 +15,10 @@ class Proyecto { return $this->proyectoRepository->fetchAllActive(); } + public function getEscriturando(): array + { + return $this->proyectoRepository->fetchAllEscriturando(); + } public function getById(int $venta_id): Model\Proyecto { return $this->process($this->proyectoRepository->fetchById($venta_id)); diff --git a/app/src/Service/Venta/Unidad.php b/app/src/Service/Venta/Unidad.php index b690a2f..899580e 100644 --- a/app/src/Service/Venta/Unidad.php +++ b/app/src/Service/Venta/Unidad.php @@ -15,35 +15,32 @@ class Unidad public function getById(int $unidad_id): Model\Venta\Unidad { - $unidad = $this->unidadRepository->fetchById($unidad_id); - $this->fillPrecios($unidad); - return $unidad; + return $this->process($this->unidadRepository->fetchById($unidad_id)); + } + public function getByVenta(int $venta_id): array + { + return array_map([$this, 'process'], $this->unidadRepository->fetchByVenta($venta_id)); } public function getByPropiedad(int $propiedad_id): array { - $unidades = $this->unidadRepository->fetchByPropiedad($propiedad_id); - array_walk($unidades, [$this, 'fillPrecios']); - return $unidades; + return array_map([$this, 'process'], $this->unidadRepository->fetchByPropiedad($propiedad_id)); } public function getByCierre(int $cierre_id): array { - $unidades = $this->unidadRepository->fetchByCierre($cierre_id); - array_walk($unidades, [$this, 'fillPrecios']); - return $unidades; + return array_map([$this, 'process'], $this->unidadRepository->fetchByCierre($cierre_id)); } public function getDisponiblesByProyecto(int $proyecto_id): array { - $unidades = $this->unidadRepository->fetchDisponiblesByProyecto($proyecto_id); - //array_walk($unidades, [$this, 'fillPrecios']); - return $unidades; + return $this->unidadRepository->fetchDisponiblesByProyecto($proyecto_id); } - protected function fillPrecios(&$unidad): void + protected function process($unidad): Model\Venta\Unidad { try { $unidad->precios = $this->precioService->getByUnidad($unidad->id); $unidad->currentPrecio = $this->precioService->getVigenteByUnidad($unidad->id); } catch (EmptyResult) { } + return $unidad; } }