diff --git a/app/common/Implement/Repository/Factory.php b/app/common/Implement/Repository/Factory.php index 00fd799..10ec520 100644 --- a/app/common/Implement/Repository/Factory.php +++ b/app/common/Implement/Repository/Factory.php @@ -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); + } } } diff --git a/app/resources/database/migrations/20250215133437_create_reservation.php b/app/resources/database/migrations/20250215133437_create_reservations.php similarity index 78% rename from app/resources/database/migrations/20250215133437_create_reservation.php rename to app/resources/database/migrations/20250215133437_create_reservations.php index db9c32a..1a8dec7 100644 --- a/app/resources/database/migrations/20250215133437_create_reservation.php +++ b/app/resources/database/migrations/20250215133437_create_reservations.php @@ -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(); diff --git a/app/resources/database/migrations/20250215135457_create_reservation_datas.php b/app/resources/database/migrations/20250215135457_create_reservation_details.php similarity index 73% rename from app/resources/database/migrations/20250215135457_create_reservation_datas.php rename to app/resources/database/migrations/20250215135457_create_reservation_details.php index ad85f98..3bdcac7 100644 --- a/app/resources/database/migrations/20250215135457_create_reservation_datas.php +++ b/app/resources/database/migrations/20250215135457_create_reservation_details.php @@ -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;'); diff --git a/app/resources/database/migrations/20250215135822_create_reservation_states.php b/app/resources/database/migrations/20250215135822_create_reservation_states.php index e617ef5..490d3dc 100644 --- a/app/resources/database/migrations/20250215135822_create_reservation_states.php +++ b/app/resources/database/migrations/20250215135822_create_reservation_states.php @@ -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;'); diff --git a/app/resources/database/migrations/20250805192903_change_telefono_size_in_propietario.php b/app/resources/database/migrations/20250805192903_change_telefono_size_in_propietario.php new file mode 100644 index 0000000..54a7f08 --- /dev/null +++ b/app/resources/database/migrations/20250805192903_change_telefono_size_in_propietario.php @@ -0,0 +1,26 @@ +table('propietario') + ->changeColumn('telefono', 'biginteger', ['null' => true, 'signed' => false, 'default' => null]) + ->update(); + } +} diff --git a/app/resources/database/migrations/20250805212635_add_comments_to_estado_cierre.php b/app/resources/database/migrations/20250805212635_add_comments_to_estado_cierre.php new file mode 100644 index 0000000..bbd9cae --- /dev/null +++ b/app/resources/database/migrations/20250805212635_add_comments_to_estado_cierre.php @@ -0,0 +1,29 @@ +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(); + } +} diff --git a/app/resources/routes/api/personas.php b/app/resources/routes/api/personas.php new file mode 100644 index 0000000..b01069c --- /dev/null +++ b/app/resources/routes/api/personas.php @@ -0,0 +1,7 @@ +group('/personas', function($app) {}); +$app->group('/persona/{rut}', function($app) { + $app->get('[/]', [Personas::class, 'get']); +}); diff --git a/app/resources/routes/api/proyectos.php b/app/resources/routes/api/proyectos.php index 15d0445..5ff5f90 100644 --- a/app/resources/routes/api/proyectos.php +++ b/app/resources/routes/api/proyectos.php @@ -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']); }); diff --git a/app/resources/routes/api/ventas/reservations.php b/app/resources/routes/api/ventas/reservations.php index 7e2e194..7625931 100644 --- a/app/resources/routes/api/ventas/reservations.php +++ b/app/resources/routes/api/ventas/reservations.php @@ -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) { diff --git a/app/resources/routes/ventas/cierres.php b/app/resources/routes/ventas/cierres.php index 62e3084..bf57def 100644 --- a/app/resources/routes/ventas/cierres.php +++ b/app/resources/routes/ventas/cierres.php @@ -1,5 +1,6 @@ group('/cierres', function($app) { $app->get('[/]', Cierres::class); diff --git a/app/resources/views/ventas/add.blade.php b/app/resources/views/ventas/add.blade.php index e870736..c8aa737 100644 --- a/app/resources/views/ventas/add.blade.php +++ b/app/resources/views/ventas/add.blade.php @@ -156,7 +156,7 @@ +@endpush diff --git a/app/resources/views/ventas/reservations/add_modal.blade.php b/app/resources/views/ventas/reservations/add_modal.blade.php new file mode 100644 index 0000000..3f137b1 --- /dev/null +++ b/app/resources/views/ventas/reservations/add_modal.blade.php @@ -0,0 +1,733 @@ + + +@include('layout.body.scripts.rut') + +@push('page_scripts') + +@endpush diff --git a/app/setup/setups/services.php b/app/setup/setups/services.php index 9cdecfc..5b396f1 100644 --- a/app/setup/setups/services.php +++ b/app/setup/setups/services.php @@ -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'), diff --git a/app/src/Controller/API/Personas.php b/app/src/Controller/API/Personas.php new file mode 100644 index 0000000..ab424a9 --- /dev/null +++ b/app/src/Controller/API/Personas.php @@ -0,0 +1,34 @@ + $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); + } +} diff --git a/app/src/Controller/API/Proyectos.php b/app/src/Controller/API/Proyectos.php index e6994ec..d30adb8 100644 --- a/app/src/Controller/API/Proyectos.php +++ b/app/src/Controller/API/Proyectos.php @@ -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); + } } diff --git a/app/src/Controller/API/Ventas/Reservations.php b/app/src/Controller/API/Ventas/Reservations.php index 01c65db..83fc3f7 100644 --- a/app/src/Controller/API/Ventas/Reservations.php +++ b/app/src/Controller/API/Ventas/Reservations.php @@ -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); + } } diff --git a/app/src/Controller/Ventas/Reservations.php b/app/src/Controller/Ventas/Reservations.php new file mode 100644 index 0000000..9ee1edc --- /dev/null +++ b/app/src/Controller/Ventas/Reservations.php @@ -0,0 +1,28 @@ +getVendibles('descripcion'); + } catch (Read) {} + $regions = []; + try { + $regions = $regionRepository->fetchAll(); + } catch (EmptyResult) {} + return $view->render($response, 'ventas.reservations', compact('projects', 'regions')); + } +} diff --git a/app/src/Exception/Model/InvalidState.php b/app/src/Exception/Model/InvalidState.php new file mode 100644 index 0000000..3c82782 --- /dev/null +++ b/app/src/Exception/Model/InvalidState.php @@ -0,0 +1,13 @@ +datos)) { - $this->datos = $this->runFactory('datos'); + try { + $this->datos = $this->runFactory('datos'); + } catch (EmptyResult) { + $this->datos = null; + } } return $this->datos; } diff --git a/app/src/Model/Proyecto/Broker.php b/app/src/Model/Proyecto/Broker.php index e55df74..342e668 100644 --- a/app/src/Model/Proyecto/Broker.php +++ b/app/src/Model/Proyecto/Broker.php @@ -1,7 +1,10 @@ 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 { diff --git a/app/src/Model/Proyecto/Broker/Contract.php b/app/src/Model/Proyecto/Broker/Contract.php index 89f6014..feb5625 100644 --- a/app/src/Model/Proyecto/Broker/Contract.php +++ b/app/src/Model/Proyecto/Broker/Contract.php @@ -1,6 +1,7 @@ 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 [ diff --git a/app/src/Model/Proyecto/Broker/Contract/State.php b/app/src/Model/Proyecto/Broker/Contract/State.php index c773d6d..c3a982c 100644 --- a/app/src/Model/Proyecto/Broker/Contract/State.php +++ b/app/src/Model/Proyecto/Broker/Contract/State.php @@ -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 [ diff --git a/app/src/Model/Venta/Promotion.php b/app/src/Model/Venta/Promotion.php index 8a1cb25..49b9676 100644 --- a/app/src/Model/Venta/Promotion.php +++ b/app/src/Model/Venta/Promotion.php @@ -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 { diff --git a/app/src/Model/Venta/Reservation.php b/app/src/Model/Venta/Reservation.php index 3a94ebf..bce17b4 100644 --- a/app/src/Model/Venta/Reservation.php +++ b/app/src/Model/Venta/Reservation.php @@ -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() ]; } } diff --git a/app/src/Model/Venta/Reservation/Detail/Type.php b/app/src/Model/Venta/Reservation/Detail/Type.php new file mode 100644 index 0000000..4129449 --- /dev/null +++ b/app/src/Model/Venta/Reservation/Detail/Type.php @@ -0,0 +1,17 @@ + $this->value, + 'description' => $this->name + ]; + } +} diff --git a/app/src/Model/Venta/Reservation/State.php b/app/src/Model/Venta/Reservation/State.php index a24771b..64eeed5 100644 --- a/app/src/Model/Venta/Reservation/State.php +++ b/app/src/Model/Venta/Reservation/State.php @@ -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 ]; } -} \ No newline at end of file +} diff --git a/app/src/Model/Venta/Reservation/State/Type.php b/app/src/Model/Venta/Reservation/State/Type.php index 464ec24..35366ad 100644 --- a/app/src/Model/Venta/Reservation/State/Type.php +++ b/app/src/Model/Venta/Reservation/State/Type.php @@ -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 + ]; } -} \ No newline at end of file + public static function getTypes(): array + { + return [self::ACTIVE->value, self::INACTIVE->value, self::REJECTED->value]; + } +} diff --git a/app/src/Repository/Persona/Datos.php b/app/src/Repository/Persona/Datos.php index bfec12b..3d71052 100644 --- a/app/src/Repository/Persona/Datos.php +++ b/app/src/Repository/Persona/Datos.php @@ -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) { diff --git a/app/src/Repository/Venta/Reservation.php b/app/src/Repository/Venta/Reservation.php index 8ff13c7..c967189 100644 --- a/app/src/Repository/Venta/Reservation.php +++ b/app/src/Repository/Venta/Reservation.php @@ -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) {} + } } diff --git a/app/src/Repository/Venta/Reservation/State.php b/app/src/Repository/Venta/Reservation/State.php index 0a98a6d..9a34efa 100644 --- a/app/src/Repository/Venta/Reservation/State.php +++ b/app/src/Repository/Venta/Reservation/State.php @@ -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')); diff --git a/app/src/Service/Persona.php b/app/src/Service/Persona.php index bfabeca..625e6d1 100644 --- a/app/src/Service/Persona.php +++ b/app/src/Service/Persona.php @@ -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); } diff --git a/app/src/Service/Valor.php b/app/src/Service/Valor.php index 7789e84..1454611 100644 --- a/app/src/Service/Valor.php +++ b/app/src/Service/Valor.php @@ -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 { diff --git a/app/src/Service/Valor/Phone.php b/app/src/Service/Valor/Phone.php new file mode 100644 index 0000000..3fc4eec --- /dev/null +++ b/app/src/Service/Valor/Phone.php @@ -0,0 +1,28 @@ +\d{2})?(?=\d)(?=\d{4})(?=\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); + } +} diff --git a/app/src/Service/Venta/Promotion.php b/app/src/Service/Venta/Promotion.php index 6dc0194..052be9b 100644 --- a/app/src/Service/Venta/Promotion.php +++ b/app/src/Service/Venta/Promotion.php @@ -48,6 +48,20 @@ class Promotion extends Ideal\Service } } + /** + * @param int $project_id + * @return array + * @throws Exception\ServiceAction\Read + */ + public function getByProject(int $project_id): array + { + try { + return array_map([$this, 'process'], $this->promotionRepository->fetchByProject($project_id)); + } catch (Implement\Exception\EmptyResult $exception) { + throw new Exception\ServiceAction\Read(__CLASS__, $exception); + } + } + /** * @param int $contract_id * @return array diff --git a/app/src/Service/Venta/Propietario.php b/app/src/Service/Venta/Propietario.php index 9ea89e5..cec026b 100644 --- a/app/src/Service/Venta/Propietario.php +++ b/app/src/Service/Venta/Propietario.php @@ -1,22 +1,24 @@ id; } $filteredData = $this->propietarioRepository->filterData($data); + if (array_key_exists('telefono', $filteredData)) { + $filteredData['telefono'] = $this->valorService->telefono()->toDatabase($filteredData['telefono']); + } try { return $this->propietarioRepository->edit($propietario, $filteredData); } catch (PDOException | EmptyResult $exception) { @@ -85,6 +90,10 @@ class Propietario extends Service ]); $filtered_data = array_intersect_key($data, $fields); + if (array_key_exists('telefono', $filtered_data)) { + $filtered_data['telefono'] = $this->valorService->telefono()->toDatabase($filtered_data['telefono']); + } + try { $propietario = $this->propietarioRepository->fetchById($data['rut']); $edits = []; @@ -95,6 +104,7 @@ class Propietario extends Service } catch (EmptyResult) { try { $propietario = $this->propietarioRepository->create($filtered_data); + $this->logger->info('Propietario', ['propietario' => $propietario]); $propietario = $this->propietarioRepository->save($propietario); } catch (PDOException $exception) { throw new Create(__CLASS__, $exception); diff --git a/app/src/Service/Venta/Reservation.php b/app/src/Service/Venta/Reservation.php index 9ceca60..166f0d8 100644 --- a/app/src/Service/Venta/Reservation.php +++ b/app/src/Service/Venta/Reservation.php @@ -14,7 +14,9 @@ use Incoviba\Repository; class Reservation extends Ideal\Service\API { - public function __construct(LoggerInterface $logger, protected Repository\Venta\Reservation $reservationRepository) + public function __construct(LoggerInterface $logger, + protected Repository\Venta\Reservation $reservationRepository, + protected Repository\Venta\Reservation\State $stateRepository) { parent::__construct($logger); } @@ -22,11 +24,19 @@ class Reservation extends Ideal\Service\API public function getAll(null|string|array $order = null): array { try { - return $this->reservationRepository->fetchAll($order); + return array_map([$this, 'process'], $this->reservationRepository->fetchAll($order)); } catch (Implement\Exception\EmptyResult) { return []; } } + public function getByProject(int $project_id): array + { + try { + return array_map([$this, 'process'], $this->reservationRepository->fetchByProject($project_id)); + } catch (Implement\Exception\EmptyResult $exception) { + throw new ServiceAction\Read(__CLASS__, $exception); + } + } public function get(int $id): Model\Venta\Reservation { @@ -37,6 +47,48 @@ class Reservation extends Ideal\Service\API } } + /** + * @param int $project_id + * @return array + * @throws ServiceAction\Read + */ + public function getActive(int $project_id): array + { + try { + return array_map([$this, 'process'], $this->reservationRepository->fetchActive($project_id)); + } catch (Implement\Exception\EmptyResult $exception) { + throw new ServiceAction\Read(__CLASS__, $exception); + } + } + + /** + * @param int $project_id + * @return array + * @throws ServiceAction\Read + */ + public function getPending(int $project_id): array + { + try { + return array_map([$this, 'process'], $this->reservationRepository->fetchPending($project_id)); + } catch (Implement\Exception\EmptyResult $exception) { + throw new ServiceAction\Read(__CLASS__, $exception); + } + } + + /** + * @param int $project_id + * @return array + * @throws ServiceAction\Read + */ + public function getRejected(int $project_id): array + { + try { + return array_map([$this, 'process'], $this->reservationRepository->fetchRejected($project_id)); + } catch (Implement\Exception\EmptyResult $exception) { + throw new ServiceAction\Read(__CLASS__, $exception); + } + } + public function add(array $data): Model\Venta\Reservation { try { @@ -76,6 +128,12 @@ class Reservation extends Ideal\Service\API } protected function process(Define\Model $model): Model\Venta\Reservation { + $model->addFactory('states', new Implement\Repository\Factory() + ->setArgs(['reservation_id' => $model->id]) + ->setCallable(function(int $reservation_id) { + return $this->stateRepository->fetchByReservation($reservation_id); + }) + ); return $model; } }