diff --git a/app/resources/views/search.blade.php b/app/resources/views/search.blade.php index 59dbc46..9f9af42 100644 --- a/app/resources/views/search.blade.php +++ b/app/resources/views/search.blade.php @@ -45,6 +45,7 @@ draw() { const tipo = this.unidad.proyecto_tipo_unidad.tipo_unidad.descripcion let unidad = tipo.charAt(0).toUpperCase() + tipo.slice(1) + ' ' + this.unidad.descripcion + let precio = 0 let propietario = '' let fecha = '' let fecha_entrega = '' @@ -61,8 +62,10 @@ if (typeof this.venta.entrega !== 'undefined') { fecha_entrega = dateFormatter.format(new Date(this.venta.entrega.fecha)) } + precio = this.venta.valor } else { unidad += '' + precio = this.unidad.current_precio.valor } const numberFormat = new Intl.NumberFormat('es-CL', {minimumFractionDigits: 2, maximumFractionDigits: 2}) @@ -81,7 +84,7 @@ ).append( $('').addClass('right aligned').html(superficie + ' m²') ).append( - $('').addClass('right aligned').html(numberFormat.format(this.unidad.precio)) + $('').addClass('right aligned').html(numberFormat.format(precio)) ).append( $('').html(fecha) ).append( @@ -104,29 +107,84 @@ const uri = '{{$urls->api}}/search' this.data = [] return fetch(uri, {method: 'post', body: data}).then(response => { - this.draw().clear() if (response.ok) { return response.json() } + }).catch(error => { + this.draw().clear() + this.draw().error(error) }).then(data => { - if (typeof data.results !== 'undefined' && data.results.length > 0) { - data.results.forEach(row => { - if (typeof row.proyecto_tipo_unidad === 'undefined') { - const r = new Row({unidad: row.propiedad.departamentos[0], proyecto: row.proyecto}) - r.venta = row - this.data.push(r) - } else { - this.data.push(new Row({unidad: row, proyecto: row.proyecto_tipo_unidad.proyecto})) - } - }) - this.draw().table() + this.draw().clear() + if (typeof data.results === 'undefined' || data.results.length === 0) { + this.draw().empty() return } - this.draw().empty() + const progress = this.draw().progress(data.results.length) + const promises = [] + data.results.forEach(row => { + if (row.tipo === 'venta') { + promises.push(this.get().venta(row.id).then(json => { + const venta = json.venta + progress.progress('increment') + const r = new Row({unidad: venta.propiedad.unidades[0], proyecto: venta.proyecto}) + r.venta = venta + this.data.push(r) + }).catch(error => { + progress.progress('increment') + console.error(row) + console.error(error) + })) + return + } + promises.push(this.get().unidad(row.id).then(json => { + const unidad = json.unidad + progress.progress('increment') + this.data.push(new Row({unidad: unidad, proyecto: unidad.proyecto_tipo_unidad.proyecto})) + }).catch(error => { + progress.progress('increment') + console.error(row) + console.error(error) + })) + }) + Promise.all(promises).then(() => { + this.sort() + this.draw().clear() + this.draw().table() + }) + }) + }, + unidad: id => { + const url = '{{$urls->api}}/ventas/unidad/' + id + return fetch(url).then(response => { + if (response.ok) { + return response.json() + } + }) + }, + venta: id => { + const url = '{{$urls->api}}/venta/' + id + return fetch(url).then(response => { + if (response.ok) { + return response.json() + } }) } } }, + sort: function() { + this.data.sort((a, b) => { + const p = a.proyecto.descripcion.localeCompare(b.proyecto.descripcion) + if (p === 0) { + const t = a.unidad.proyecto_tipo_unidad.tipo_unidad.descripcion + .localeCompare(b.unidad.proyecto_tipo_unidad.tipo_unidad.descripcion) + if (t === 0) { + return a.unidad.descripcion.localeCompare(b.unidad.descripcion) + } + return t + } + return p + }) + }, draw: function() { return { clear: () => { @@ -213,6 +271,35 @@ $('
').addClass('content').html('No se han encontrado resultados.') ) ) + }, + error: error => { + this.draw().separator() + $(this.id).append( + $('
').addClass('ui icon error message').append( + $('').addClass('exclamation triangle icon') + ).append( + $('
').addClass('content').html(error) + ) + ) + }, + progress: cantidad => { + this.draw().separator() + const progress = $('
').addClass('ui active progress').append( + $('
').addClass('bar').append( + $('
').addClass('centered progress') + ) + ).append( + $('
').addClass('label').html('Cargando datos') + ) + progress.progress({ + total: cantidad, + label: 'ratio', + text: { + ratio: '{value} de {total} ({percent}%)' + } + }) + $(this.id).append(progress) + return progress } } }, diff --git a/app/src/Controller/API/Ventas/Unidades.php b/app/src/Controller/API/Ventas/Unidades.php index cc1843e..1d29f81 100644 --- a/app/src/Controller/API/Ventas/Unidades.php +++ b/app/src/Controller/API/Ventas/Unidades.php @@ -16,6 +16,24 @@ class Unidades { use withJson, withRedis; + public function get(ServerRequestInterface $request, ResponseInterface $response, Service\Redis $redisService, + Service\Venta\Unidad $unidadService, int $unidad_id): ResponseInterface + { + $output = [ + 'unidad_id' => $unidad_id, + 'unidad' => null + ]; + $redisKey = "unidad:{$unidad_id}"; + try { + $output['unidad'] = $this->fetchRedis($redisService, $redisKey); + } catch (EmptyRedis) { + try { + $output['unidad'] = $unidadService->getById($unidad_id); + $this->saveRedis($redisService, $redisKey, $output['unidad']); + } catch (EmptyResult) {} + } + return $this->withJson($response, $output); + } public function disponibles(ServerRequestInterface $request, ResponseInterface $response, Repository\Venta\Unidad $unidadRepository, Repository\Proyecto\TipoUnidad $tipoUnidadRepository, Service\Redis $redisService): ResponseInterface { $body = $request->getBody(); diff --git a/app/src/Controller/Ventas.php b/app/src/Controller/Ventas.php index c079d48..1be7461 100644 --- a/app/src/Controller/Ventas.php +++ b/app/src/Controller/Ventas.php @@ -20,7 +20,7 @@ class Ventas { $redisKey = "proyectos:vendibles"; try { - $proyectos = $proyectoService->process($proyectoRepository->load((array) $this->fetchRedis($redisService, $redisKey))); + $proyectos = $this->fetchRedis($redisService, $redisKey); } catch (EmptyRedis) { $proyectos = array_map(function(Model\Proyecto $proyecto) { return ['id' => $proyecto->id, 'descripcion' => $proyecto->descripcion]; diff --git a/app/src/Model/Venta.php b/app/src/Model/Venta.php index 5e8a502..fa2610c 100644 --- a/app/src/Model/Venta.php +++ b/app/src/Model/Venta.php @@ -66,7 +66,7 @@ class Venta extends Ideal\Model public function proyecto(): Proyecto { - return $this->propiedad()->departamentos()[0]->proyectoTipoUnidad->proyecto; + return $this->propiedad()->proyecto(); } protected float $valor_util; diff --git a/app/src/Model/Venta/Propiedad.php b/app/src/Model/Venta/Propiedad.php index 546a984..47e8094 100644 --- a/app/src/Model/Venta/Propiedad.php +++ b/app/src/Model/Venta/Propiedad.php @@ -2,22 +2,40 @@ namespace Incoviba\Model\Venta; use Incoviba\Common\Ideal; +use Incoviba\Model; class Propiedad extends Ideal\Model { public array $unidades; + protected array $departamentos; public function departamentos(): array { - return array_values(array_filter($this->unidades, function(Unidad $unidad) {return $unidad->proyectoTipoUnidad->tipoUnidad->descripcion === 'departamento';})); + if (!isset($this->departamentos)) { + $this->departamentos = array_values(array_filter($this->unidades, function(Unidad $unidad) {return $unidad->proyectoTipoUnidad->tipoUnidad->descripcion === 'departamento';})); + } + return $this->departamentos; } + protected array $estacionamientos; public function estacionamientos(): array { - return array_values(array_filter($this->unidades, function(Unidad $unidad) {return $unidad->proyectoTipoUnidad->tipoUnidad->descripcion === 'estacionamiento';})); + if (!isset($this->estacionamientos)) { + $this->estacionamientos = array_values(array_filter($this->unidades, function(Unidad $unidad) {return $unidad->proyectoTipoUnidad->tipoUnidad->descripcion === 'estacionamiento';})); + } + return $this->estacionamientos; } + protected array $bodegas; public function bodegas(): array { - return array_values(array_filter($this->unidades, function(Unidad $unidad) {return $unidad->proyectoTipoUnidad->tipoUnidad->descripcion === 'bodega';})); + if (!isset($this->bodegas)) { + $this->bodegas = array_values(array_filter($this->unidades, function(Unidad $unidad) {return $unidad->proyectoTipoUnidad->tipoUnidad->descripcion === 'bodega';})); + } + return $this->bodegas; + } + + public function proyecto(): Model\Proyecto + { + return $this->unidades[0]->proyectoTipoUnidad->proyecto; } protected float $vendible; @@ -46,6 +64,7 @@ class Propiedad extends Ideal\Model public function jsonSerialize(): mixed { return [ + 'unidades' => $this->unidades, 'departamentos' => $this->departamentos(), 'estacionamientos' => $this->estacionamientos(), 'bodegas' => $this->bodegas(), diff --git a/app/src/Model/Venta/Unidad.php b/app/src/Model/Venta/Unidad.php index d5efdab..05a54fb 100644 --- a/app/src/Model/Venta/Unidad.php +++ b/app/src/Model/Venta/Unidad.php @@ -46,7 +46,7 @@ class Unidad extends Ideal\Model public function jsonSerialize(): mixed { - return array_merge(parent::jsonSerialize(), [ + $output = array_merge(parent::jsonSerialize(), [ 'subtipo' => $this->subtipo, 'piso' => $this->piso, 'descripcion' => $this->descripcion, @@ -54,5 +54,10 @@ class Unidad extends Ideal\Model 'proyecto_tipo_unidad' => $this->proyectoTipoUnidad, 'prorrateo' => $this->prorrateo ]); + if (isset($this->precios)) { + $output['precios'] = $this->precios; + $output['current_precio'] = $this->currentPrecio; + } + return $output; } } diff --git a/app/src/Repository/Venta.php b/app/src/Repository/Venta.php index c8f28ef..d550dfb 100644 --- a/app/src/Repository/Venta.php +++ b/app/src/Repository/Venta.php @@ -1,7 +1,7 @@ fetchMany($query, [$proyecto_id]); } + public function fetchIdsByProyecto(int $proyecto_id): array + { + $query = "SELECT a.`id` +FROM `{$this->getTable()}` a + JOIN `propiedad_unidad` pu ON pu.`propiedad` = a.`propiedad` + JOIN `unidad` ON `unidad`.`id` = pu.`unidad` AND pu.`principal` = 1 + JOIN `proyecto_tipo_unidad` ptu ON ptu.`id` = `unidad`.`pt` + JOIN (SELECT e1.* FROM `estado_venta` e1 JOIN (SELECT MAX(`id`) AS 'id', `venta` FROM `estado_venta` GROUP BY `venta`) e0 ON e0.`id` = e1.`id`) ev ON ev.`venta` = a.`id` + JOIN `tipo_estado_venta` tev ON tev.`id` = ev.`estado` +WHERE ptu.`proyecto` = ? AND tev.`activa` +GROUP BY a.`id`"; + return $this->connection->execute($query, [$proyecto_id])->fetchAll(PDO::FETCH_ASSOC); + } public function fetchActivaByProyecto(int $proyecto_id): array { $query = "SELECT a.* @@ -197,11 +210,27 @@ FROM `{$this->getTable()}` a WHERE `unidad`.`descripcion` LIKE ? AND tu.`descripcion` = ?"; return $this->fetchMany($query, [$unidad, $tipo]); } + public function fetchIdsByUnidad(string $unidad, string $tipo): array + { + $query = "SELECT a.id +FROM `{$this->getTable()}` a + JOIN `propiedad_unidad` pu ON pu.`propiedad` = a.`propiedad` + JOIN `unidad` ON `unidad`.`id` = pu.`unidad` + JOIN `proyecto_tipo_unidad` ptu ON ptu.`id` = `unidad`.`pt` + JOIN `tipo_unidad` tu ON tu.`id` = ptu.`tipo` +WHERE `unidad`.`descripcion` LIKE ? AND tu.`descripcion` = ?"; + return $this->connection->execute($query, [$unidad, $tipo])->fetchAll(PDO::FETCH_ASSOC); + } public function fetchByPrecio(string $precio): array { $query = "SELECT * FROM `{$this->getTable()}` WHERE `valor_uf` = ?"; return $this->fetchMany($query, [$precio]); } + public function fetchIdsByPrecio(string $precio): array + { + $query = "SELECT `id` FROM `{$this->getTable()}` WHERE `valor_uf` = ?"; + return $this->connection->execute($query, [$precio])->fetchAll(PDO::FETCH_ASSOC); + } public function fetchByPropietario(string $propietario): array { $query = "SELECT a.* @@ -212,6 +241,16 @@ WHERE CONCAT_WS('-', `propietario`.`rut`, `propietario`.`dv`) LIKE :propietario OR CONCAT_WS(' ', `propietario`.`nombres`, `propietario`.`apellido_paterno`, `propietario`.`apellido_materno`) LIKE :propietario"; return $this->fetchMany($query, [':propietario' => "%{$propietario}%"]); } + public function fetchIdsByPropietario(string $propietario): array + { + $query = "SELECT a.id +FROM `{$this->getTable()}` a + JOIN `propietario` ON `propietario`.`rut` = a.`propietario` +WHERE CONCAT_WS('-', `propietario`.`rut`, `propietario`.`dv`) LIKE :propietario OR `propietario`.`nombres` LIKE :propietario + OR `propietario`.`apellido_paterno` LIKE :propietario OR `propietario`.`apellido_materno` LIKE :propietario + OR CONCAT_WS(' ', `propietario`.`nombres`, `propietario`.`apellido_paterno`, `propietario`.`apellido_materno`) LIKE :propietario"; + return $this->connection->execute($query, [':propietario' => "%{$propietario}%"])->fetchAll(PDO::FETCH_ASSOC); + } public function fetchByPropietarioNombreCompleto(string $propietario): array { $query = "SELECT a.* diff --git a/app/src/Repository/Venta/Unidad.php b/app/src/Repository/Venta/Unidad.php index 252cfa9..8605d32 100644 --- a/app/src/Repository/Venta/Unidad.php +++ b/app/src/Repository/Venta/Unidad.php @@ -1,6 +1,7 @@ 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]); } + public function fetchDisponiblesIdsByDescripcionAndTipo(string $descripcion, string $tipo): array + { + $query = $this->connection->getQueryBuilder() + ->select('a.id') + ->from("{$this->getTable()} a") + ->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)"); + return $this->connection->execute($query, [$descripcion, $tipo])->fetchAll(PDO::FETCH_ASSOC); + } protected function joinProrrateo(): string { diff --git a/app/src/Service/Search.php b/app/src/Service/Search.php index 08f6633..5b66a4e 100644 --- a/app/src/Service/Search.php +++ b/app/src/Service/Search.php @@ -22,7 +22,8 @@ class Search } else { $results = $this->find($query, $tipo); } - return $this->sort($results); + return $results; + //return $this->sort($results); } protected function findCualquiera(string $query): array @@ -45,7 +46,7 @@ class Search } protected function find(string $query, string $tipo): array { - preg_match_all('/["\']([\s\w]+)["\']|(\w+)/i', $query, $matches, PREG_SET_ORDER | PREG_UNMATCHED_AS_NULL); + preg_match_all('/["\']([\s\w]+)["\']|(\w+)/iu', $query, $matches, PREG_SET_ORDER | PREG_UNMATCHED_AS_NULL); $queries = array_map(function($match) { array_shift($match); $valid = array_filter($match, function($line) {return $line !== null;}); @@ -56,7 +57,7 @@ class Search foreach ($queries as $q) { $this->add($results, $this->findVentas($q, $tipo)); if (in_array($tipo, $tiposUnidades)) { - $this->add($results, $this->findUnidadesDisponibles($q, $tipo)); + $this->add($results, $this->findUnidadesDisponibles($q, $tipo), false); } } return $results; @@ -91,7 +92,7 @@ class Search protected function findUnidadesDisponibles(string $query, string $tipo): array { try { - return $this->unidadRepository->fetchDisponiblesByDescripcionAndTipo($query, $tipo); + return $this->unidadRepository->fetchDisponiblesIdsByDescripcionAndTipo($query, $tipo); } catch (EmptyResponse) { return []; } @@ -99,7 +100,7 @@ class Search protected function findUnidad(string $query, string $tipo): array { try { - return $this->ventaService->getByUnidad($query, $tipo); + return $this->ventaRepository->fetchIdsByUnidad($query, $tipo); } catch (EmptyResult) { return []; } @@ -107,7 +108,7 @@ class Search protected function findPropietario(string $query): array { try { - return $this->ventaService->getByPropietario($query); + return $this->ventaRepository->fetchIdsByPropietario($query); } catch (EmptyResult) { return []; } @@ -116,7 +117,7 @@ class Search { try { $precio = str_replace(['$', '.', ','], ['', '', '.'], $query); - return $this->ventaService->getByPrecio($precio); + return $this->ventaRepository->fetchIdsByPrecio($precio); } catch (EmptyResult) { return []; } @@ -125,7 +126,7 @@ class Search { try { $proyecto = $this->proyectoService->getByName($query); - return $this->ventaService->getByProyecto($proyecto->id); + return $this->ventaRepository->fetchIdsByProyecto($proyecto->id); } catch (EmptyResult) { return []; } @@ -145,22 +146,21 @@ class Search } return $this->tipos; } - protected function add(array &$results, array $found): void + protected function add(array &$results, array $found, bool $is_venta = true): void { foreach ($found as $item) { if (!$this->inResults($item, $results)) { + $item['tipo'] = 'venta'; + if (!$is_venta) { + $item['tipo'] = 'unidad'; + } $results []= $item; } } } protected function inResults($item, array $results): bool { - foreach ($results as $result) { - if (get_class($item) === get_class($result) and $item->id === $result->id) { - return true; - } - } - return false; + return in_array($item, $results, true); } protected function sort(&$results): array { diff --git a/cli/bin/index.php b/cli/bin/index.php new file mode 100644 index 0000000..9e47d85 --- /dev/null +++ b/cli/bin/index.php @@ -0,0 +1,13 @@ +run(); +} catch (Error $error) { + $app->getContainer()->get(Psr\Log\LoggerInterface::class)->error($error); +} catch (Exception $exception) { + $app->getContainer()->get(Psr\Log\LoggerInterface::class)->notice($exception); +}