Reservation Payment detail

This commit is contained in:
Juan Pablo Vial
2025-11-13 15:19:33 -03:00
parent 81ac48afbf
commit a41f306d3f
5 changed files with 419 additions and 68 deletions

View File

@ -14,6 +14,7 @@ class Reservation extends Common\Ideal\Model
public array $units = []; public array $units = [];
public array $promotions = []; public array $promotions = [];
public ?Model\Proyecto\Broker $broker = null; public ?Model\Proyecto\Broker $broker = null;
public ?Model\Venta\Reservation\Payment $payment = null;
public function offer(): float public function offer(): float
{ {
@ -131,6 +132,7 @@ class Reservation extends Common\Ideal\Model
'units' => $this->units, 'units' => $this->units,
'promotions' => $this->promotions, 'promotions' => $this->promotions,
'broker' => $this->broker, 'broker' => $this->broker,
'payment' => $this->payment,
'offer' => $this->offer(), 'offer' => $this->offer(),
'with_commission' => $this->withCommission(), 'with_commission' => $this->withCommission(),
'base' => $this->base(), 'base' => $this->base(),

View File

@ -6,6 +6,9 @@ enum Type: int
case Unit = 1; case Unit = 1;
case Promotion = 2; case Promotion = 2;
case Broker = 3; case Broker = 3;
case Advance = 4; // Pie
case Credit = 5; // Credito
case Subsidy = 6; // Subsidio
public function jsonSerialize(): array public function jsonSerialize(): array
{ {

View File

@ -0,0 +1,23 @@
<?php
namespace Incoviba\Model\Venta\Reservation;
use JsonSerializable;
use Incoviba\Model\Venta\Credito;
use Incoviba\Model\Venta\Pie;
use Incoviba\Model\Venta\Subsidio;
class Payment implements JsonSerializable
{
public ?Pie $pie = null;
public ?Credito $credito = null;
public ?Subsidio $subsidio = null;
public function jsonSerialize(): array
{
return [
'pie' => $this->pie,
'credito' => $this->credito,
'subsidio' => $this->subsidio
];
}
}

View File

@ -3,14 +3,14 @@ namespace Incoviba\Repository\Venta;
use DateTimeInterface; use DateTimeInterface;
use DateInterval; use DateInterval;
use PDO;
use PDOException;
use Incoviba\Common\Define; use Incoviba\Common\Define;
use Incoviba\Common\Implement\Exception\EmptyResult; use Incoviba\Common\Implement\Exception\EmptyResult;
use Incoviba\Exception\Model\InvalidState; use Incoviba\Exception\Model\InvalidState;
use PDO;
use Incoviba\Common; use Incoviba\Common;
use Incoviba\Model; use Incoviba\Model;
use Incoviba\Repository; use Incoviba\Repository;
use PDOException;
class Reservation extends Common\Ideal\Repository class Reservation extends Common\Ideal\Repository
{ {
@ -18,7 +18,9 @@ class Reservation extends Common\Ideal\Repository
protected Repository\Proyecto $proyectoRepository, protected Repository\Proyecto $proyectoRepository,
protected Repository\Persona $personaRepository, protected Repository\Persona $personaRepository,
protected Repository\Proyecto\Broker $brokerRepository, protected Repository\Proyecto\Broker $brokerRepository,
protected Unidad $unitRepository, protected Promotion $promotionRepository) protected Unidad $unitRepository,
protected Promotion $promotionRepository, protected Pie $advanceRepository,
protected Credito $creditRepository, protected Subsidio $subsidyRepository)
{ {
parent::__construct($connection); parent::__construct($connection);
} }
@ -44,6 +46,12 @@ class Reservation extends Common\Ideal\Repository
->register('date', new Common\Implement\Repository\Mapper\DateTime('date')); ->register('date', new Common\Implement\Repository\Mapper\DateTime('date'));
return $this->parseData(new Model\Venta\Reservation(), $data, $map); return $this->parseData(new Model\Venta\Reservation(), $data, $map);
} }
/**
* @param Define\Model $model
* @return Model\Venta\Reservation
* @throws PDOException
*/
public function save(Common\Define\Model $model): Model\Venta\Reservation public function save(Common\Define\Model $model): Model\Venta\Reservation
{ {
if (!isset($model->id)) { if (!isset($model->id)) {
@ -85,8 +93,11 @@ class Reservation extends Common\Ideal\Repository
$this->fetchUnits($model, $data_row); $this->fetchUnits($model, $data_row);
try { try {
$this->fetchBroker($model, $data_row); $this->fetchBroker($model, $data_row);
} catch (Common\Implement\Exception\EmptyResult) {} } catch (EmptyResult) {}
$this->fetchPromotions($model, $data_row); $this->fetchPromotions($model, $data_row);
try {
$this->fetchPayment($model, $data_row);
} catch (EmptyResult) {}
return $model; return $model;
} }
@ -248,6 +259,11 @@ class Reservation extends Common\Ideal\Repository
return $reservations; return $reservations;
} }
/**
* @param Model\Venta\Reservation $reservation
* @return void
* @throws PDOException
*/
protected function saveUnits(Model\Venta\Reservation $reservation): void protected function saveUnits(Model\Venta\Reservation $reservation): void
{ {
if (empty($reservation->units)) { if (empty($reservation->units)) {
@ -290,6 +306,12 @@ class Reservation extends Common\Ideal\Repository
Model\Venta\Reservation\Detail\Type::Unit->value, Model\Venta\Reservation\Detail\Type::Unit->value,
array_map(fn($unit) => $unit->unit->id, $reservation->units)); array_map(fn($unit) => $unit->unit->id, $reservation->units));
} }
/**
* @param Model\Venta\Reservation $reservation
* @return void
* @throws PDOException
*/
protected function saveBroker(Model\Venta\Reservation &$reservation): void protected function saveBroker(Model\Venta\Reservation &$reservation): void
{ {
if ($reservation->broker === null) { if ($reservation->broker === null) {
@ -312,7 +334,7 @@ class Reservation extends Common\Ideal\Repository
$new_id = $reservation->broker->rut; $new_id = $reservation->broker->rut;
$reservation->broker = $this->brokerRepository->fetchById($result['reference_id']); $reservation->broker = $this->brokerRepository->fetchById($result['reference_id']);
$this->editBroker($reservation, ['broker_rut' => $new_id]); $this->editBroker($reservation, ['broker_rut' => $new_id]);
} catch (PDOException) { } catch (EmptyResult | PDOException) {
$queryInsert = $this->connection->getQueryBuilder() $queryInsert = $this->connection->getQueryBuilder()
->insert() ->insert()
->into('reservation_details') ->into('reservation_details')
@ -325,6 +347,12 @@ class Reservation extends Common\Ideal\Repository
]); ]);
} }
} }
/**
* @param Model\Venta\Reservation $reservation
* @return void
* @throws PDOException
*/
protected function savePromotions(Model\Venta\Reservation $reservation): void protected function savePromotions(Model\Venta\Reservation $reservation): void
{ {
if (empty($reservation->promotions)) { if (empty($reservation->promotions)) {
@ -366,6 +394,34 @@ class Reservation extends Common\Ideal\Repository
Model\Venta\Reservation\Detail\Type::Promotion->value, Model\Venta\Reservation\Detail\Type::Promotion->value,
array_map(fn($promotion) => $promotion->id, $reservation->promotions)); array_map(fn($promotion) => $promotion->id, $reservation->promotions));
} }
public function saveDetail(Model\Venta\Reservation $reservation, int $type, int $referenceId, ?float $value = null): void
{
$queryCheck = $this->connection->getQueryBuilder()
->select('1')
->from('reservation_details')
->where('reservation_id = :reservation_id AND type = :type AND reference_id = :reference_id AND value = :value');
$statementCheck = $this->connection->prepare($queryCheck);
$statementCheck->execute([
'reservation_id' => $reservation->id,
'type' => $type,
'reference_id' => $referenceId,
'value' => $value
]);
$result = $statementCheck->fetch(PDO::FETCH_ASSOC);
if ($result === false) {
$queryInsert = $this->connection->getQueryBuilder()
->insert()
->into('reservation_details')
->columns(['reservation_id', 'type', 'reference_id', 'value'])
->values([':reservation_id', ':type', ':reference_id', ':value']);
$this->connection->execute($queryInsert, [
'reservation_id' => $reservation->id,
'type' => $type,
'reference_id' => $referenceId,
'value' => $value
]);
}
}
protected function cleanUpDetails(Model\Venta\Reservation $reservation, int $type_id, array $currentIds): void protected function cleanUpDetails(Model\Venta\Reservation $reservation, int $type_id, array $currentIds): void
{ {
$queryCheck = $this->connection->getQueryBuilder() $queryCheck = $this->connection->getQueryBuilder()
@ -643,4 +699,49 @@ class Reservation extends Common\Ideal\Repository
$reservation->broker = null; $reservation->broker = null;
} catch (PDOException) {} } catch (PDOException) {}
} }
/**
* @param Model\Venta\Reservation $reservation
* @param array $new_data
* @return Model\Venta\Reservation
* @throws EmptyResult
*/
protected function fetchPayment(Model\Venta\Reservation &$reservation, array $new_data): Model\Venta\Reservation
{
$types = [
Model\Venta\Reservation\Detail\Type::Advance->value,
Model\Venta\Reservation\Detail\Type::Credit->value,
Model\Venta\Reservation\Detail\Type::Subsidy->value
];
$typesString = implode(', ', $types);
$query = $this->connection->getQueryBuilder()
->select()
->from('reservation_details')
->where("reservation_id = :id AND type IN ({$typesString})");
try {
$statement = $this->connection->execute($query, ['id' => $reservation->id]);
$results = $statement->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $exception) {
throw new EmptyResult($query, $exception, ['id' => $reservation->id, 'data' => $new_data]);
}
if (count($results) === 0) {
throw new EmptyResult($query);
}
$payment = new Model\Venta\Reservation\Payment();
foreach ($results as $result) {
switch ($result['type']) {
case Model\Venta\Reservation\Detail\Type::Advance->value:
$payment->pie = $this->advanceRepository->fetchById($result['reference_id']);
break;
case Model\Venta\Reservation\Detail\Type::Credit->value:
$payment->credito = $this->creditRepository->fetchById($result['reference_id']);
break;
case Model\Venta\Reservation\Detail\Type::Subsidy->value:
$payment->subsidio = $this->subsidyRepository->fetchById($result['reference_id']);
break;
}
}
$reservation->payment = $payment;
return $reservation;
}
} }

View File

@ -116,73 +116,20 @@ class Reservation extends Ideal\Service\API
} }
} }
/**
* @param array $data
* @return Model\Venta\Reservation
* @throws ServiceAction\Create
*/
public function add(array $data): Model\Venta\Reservation public function add(array $data): Model\Venta\Reservation
{ {
$date = new DateTimeImmutable(); $date = $this->parseDate($data['date'] ?? 'now');
try { try {
$date = new DateTimeImmutable($data['date']); return $this->updateExistingReservation($data, $date);
} catch (DateMalformedStringException) {} } catch (Read | ServiceAction\Update) {
try { return $this->createNewReservation($data, $date);
$reservation = $this->reservationRepository->fetchByBuyerAndUnitAndDate($data['buyer_rut'], (int) $data['units'][0], $date);
if (array_key_exists('broker_rut', $data) and $data['broker_rut'] !== '') {
try {
$broker = $this->brokerService->get($data['broker_rut']);
$reservation = $this->reservationRepository->edit($reservation, ['broker_rut' => $broker->rut]);
} catch (ServiceAction\Read) {}
} }
} catch (Implement\Exception\EmptyResult) {
if (!$this->reservationRepository->getConnection()->getPDO()->inTransaction()) {
$this->reservationRepository->getConnection()->getPDO()->beginTransaction();
}
$buyerData = [];
foreach ($data as $key => $value) {
if (!str_starts_with($key, 'buyer_')) {
continue;
}
$buyerData[substr($key, strlen('buyer_'))] = $value;
}
$this->personaService->add($buyerData);
$data['date'] = $date->format('Y-m-d');
try {
$reservationData = $this->reservationRepository->filterData($data);
$reservation = $this->reservationRepository->create($reservationData);
$reservation = $this->reservationRepository->save($reservation);
$stateType = Model\Venta\Reservation\State\Type::INACTIVE;
$stateData = [
'reservation_id' => $reservation->id,
'date' => $data['date'],
'type' => $stateType->value,
];
$state = $this->stateRepository->create($stateData);
$this->stateRepository->save($state);
$units = array_combine($data['units'], $data['units_value']);
$this->addUnits($reservation, $units);
if (array_key_exists('broker_rut', $data) and !empty($data['broker_rut'])) {
$this->addBroker($reservation, $data['broker_rut']);
}
if (array_key_exists('promotions', $data)) {
$this->addPromotions($reservation, $data['promotions']);
}
if ($this->reservationRepository->getConnection()->getPDO()->inTransaction()) {
$this->reservationRepository->getConnection()->getPDO()->commit();
}
} catch (PDOException $exception) {
$this->logger->warning($exception->getMessage(), ['exception' => $exception]);
if ($this->reservationRepository->getConnection()->getPDO()->inTransaction()) {
$this->reservationRepository->getConnection()->getPDO()->rollBack();
}
throw new ServiceAction\Create(__CLASS__, $exception);
}
}
return $this->process($reservation);
} }
public function edit(Define\Model $model, array $new_data): Model\Venta\Reservation public function edit(Define\Model $model, array $new_data): Model\Venta\Reservation
{ {
@ -280,6 +227,13 @@ class Reservation extends Ideal\Service\API
$this->reservationRepository->save($reservation); $this->reservationRepository->save($reservation);
} catch (ServiceAction\Read) {} } catch (ServiceAction\Read) {}
} }
/**
* @param Model\Venta\Reservation $reservation
* @param array $promotions
* @return void
* @throws PDOException
*/
protected function addPromotions(Model\Venta\Reservation $reservation, array $promotions): void protected function addPromotions(Model\Venta\Reservation $reservation, array $promotions): void
{ {
foreach ($promotions as $promotion_id) { foreach ($promotions as $promotion_id) {
@ -292,4 +246,272 @@ class Reservation extends Ideal\Service\API
} }
$this->reservationRepository->save($reservation); $this->reservationRepository->save($reservation);
} }
/**
* @param Model\Venta\Reservation $reservation
* @param array $payment
* @return void
* @throws ServiceAction\Create
*/
protected function addPayment(Model\Venta\Reservation $reservation, array $payment): void
{
$fields = [
'pie',
'advance',
'cuotas',
'investments',
'credito',
'credit',
'subsidio',
'subsidy',
'ahorro',
'savings',
];
$filteredData = array_intersect_key($payment, array_flip($fields));
$map = [
'pie' => 'advance',
'cuotas' => 'investments',
'credito' => 'credit',
'subsidio' => 'subsidy',
'ahorro' => 'savings'
];
$mappedData = [];
foreach ($filteredData as $key => $value) {
$mappedData[$map[$key]] = $value;
}
if (array_key_exists('advance', $mappedData)) {
$this->addAdvance($reservation, $mappedData);
}
if (array_key_exists('credit', $mappedData)) {
$this->addCredit($reservation, $mappedData);
}
if (array_key_exists('subsidy', $mappedData)) {
$this->addSubsidy($reservation, $mappedData);
}
}
/**
* @param Model\Venta\Reservation $reservation
* @param array $data
* @return void
* @throws ServiceAction\Create
*/
protected function addAdvance(Model\Venta\Reservation $reservation, array $data): void
{
$fields = [
'advance',
'investments'
];
$filteredData = array_intersect_key($data, array_flip($fields));
$inputData = [
'fecha' => $reservation->date->format('Y-m-d'),
'valor' => $filteredData['advance'],
'cuotas' => $filteredData['investments']
];
$pie = $this->pieService->add($inputData);
try {
$this->reservationRepository->saveDetail($reservation, Model\Venta\Reservation\Detail\Type::Advance->value, $pie->id);
} catch (PDOException $exception) {
throw new ServiceAction\Create(__CLASS__, $exception);
}
}
/**
* @param Model\Venta\Reservation $reservation
* @param array $data
* @return void
* @throws ServiceAction\Create
*/
protected function addCredit(Model\Venta\Reservation $reservation, array $data): void
{
$creditValue = $data['credit'];
$inputData = [
'fecha' => $reservation->date->format('Y-m-d'),
'valor' => $creditValue
];
$credit = $this->creditService->add($inputData);
try {
$this->reservationRepository->saveDetail($reservation, Model\Venta\Reservation\Detail\Type::Credit->value, $credit->id);
} catch (PDOException $exception) {
throw new ServiceAction\Create(__CLASS__, $exception);
}
}
/**
* @param Model\Venta\Reservation $reservation
* @param array $data
* @return void
* @throws ServiceAction\Create
*/
protected function addSubsidy(Model\Venta\Reservation $reservation, array $data): void
{
$fields = [
'subsidy',
'savings'
];
$filteredData = array_intersect_key($data, array_flip($fields));
$inputData = [
'fecha' => $reservation->date->format('Y-m-d'),
'subsidio' => $filteredData['subsidy'],
'ahorro' => $filteredData['savings']
];
$subsidy = $this->subsidyService->add($inputData);
try {
$this->reservationRepository->saveDetail($reservation, Model\Venta\Reservation\Detail\Type::Subsidy->value, $subsidy->id);
} catch (PDOException $exception) {
throw new ServiceAction\Create(__CLASS__, $exception);
}
}
/**
* @param array $data
* @param DateTimeImmutable $date
* @return Model\Venta\Reservation
* @throws Read
* @throws ServiceAction\Update
*/
protected function updateExistingReservation(array $data, DateTimeImmutable $date): Model\Venta\Reservation
{
try {
$reservation = $this->reservationRepository->fetchByBuyerAndUnitAndDate(
$data['buyer_rut'],
(int) ($data['units'][0] ?? 0),
$date
);
} catch (Implement\Exception\EmptyResult $exception) {
throw new Read(__CLASS__, $exception);
}
if (!empty($data['broker_rut'] ?? null)) {
$this->updateBrokerForReservation($reservation, $data['broker_rut']);
}
return $this->process($reservation);
}
/**
* @param array $data
* @param DateTimeImmutable $date
* @return Model\Venta\Reservation
* @throws ServiceAction\Create
*/
protected function createNewReservation(array $data, DateTimeImmutable $date): Model\Venta\Reservation
{
$pdo = $this->reservationRepository->getConnection()->getPDO();
$inTransaction = $pdo->inTransaction();
try {
if (!$inTransaction) {
$pdo->beginTransaction();
}
$this->createOrUpdateBuyer($data);
$reservation = $this->persistReservation($data, $date->format('Y-m-d'));
$this->createInitialState($reservation, $date);
$this->processReservationDetails($reservation, $data);
if (!$inTransaction) {
$pdo->commit();
}
return $this->process($reservation);
} catch (PDOException $exception) {
if (!$inTransaction && $pdo->inTransaction()) {
$pdo->rollBack();
}
$this->logger->error('Failed to create reservation', [
'error' => $exception->getMessage(),
'data' => $this->sanitizeData($data)
]);
throw new ServiceAction\Create(__CLASS__, $exception);
}
}
protected function parseDate(?string $dateString): DateTimeImmutable
{
try {
return new DateTimeImmutable($dateString);
} catch (\Exception) {
return new DateTimeImmutable();
}
}
/**
* @param array $data
* @return void
* @throws ServiceAction\Create
*/
protected function createOrUpdateBuyer(array $data): void
{
$buyerData = array_filter($data, fn($key) => str_starts_with($key, 'buyer_'), ARRAY_FILTER_USE_KEY);
$buyerData = array_combine(
array_map(fn($key) => substr($key, strlen('buyer_')), array_keys($buyerData)),
$buyerData
);
$this->personaService->add($buyerData);
}
protected function persistReservation(array $data, string $date): Model\Venta\Reservation
{
$data['date'] = $date;
$reservationData = $this->reservationRepository->filterData($data);
$reservation = $this->reservationRepository->create($reservationData);
return $this->reservationRepository->save($reservation);
}
protected function createInitialState(Model\Venta\Reservation $reservation, DateTimeInterface $date): void
{
$stateData = [
'reservation_id' => $reservation->id,
'date' => $date->format('Y-m-d'),
'type' => Model\Venta\Reservation\State\Type::INACTIVE->value,
];
$state = $this->stateRepository->create($stateData);
$this->stateRepository->save($state);
}
protected function processReservationDetails(Model\Venta\Reservation $reservation, array $data): void
{
if (!empty($data['units']) && !empty($data['units_value'])) {
$units = array_combine($data['units'], $data['units_value']);
$this->addUnits($reservation, $units);
}
if (!empty($data['broker_rut'])) {
$this->addBroker($reservation, $data['broker_rut']);
}
if (!empty($data['promotions'])) {
$this->addPromotions($reservation, $data['promotions']);
}
$paymentData = array_filter($data, fn($key) => str_starts_with($key, 'payment_'), ARRAY_FILTER_USE_KEY);
if (!empty($paymentData)) {
$this->addPayment($reservation, $paymentData);
}
}
/**
* @param Model\Venta\Reservation $reservation
* @param string $brokerRut
* @return void
* @throws ServiceAction\Update
*/
protected function updateBrokerForReservation(Model\Venta\Reservation $reservation, string $brokerRut): void
{
try {
$broker = $this->brokerService->get($brokerRut);
$this->reservationRepository->edit($reservation, ['broker_rut' => $broker->rut]);
} catch (Implement\Exception\EmptyResult | ServiceAction\Read $exception) {
$this->logger->warning('Broker not found', ['broker_rut' => $brokerRut]);
throw new ServiceAction\Update(__CLASS__, $exception);
}
}
protected function sanitizeData(array $data): array
{
$sensitiveFields = ['password', 'token', 'api_key', 'rut'];
return array_map(
fn($value, $key) => in_array($key, $sensitiveFields, true) ? '***' : $value,
$data,
array_keys($data)
);
}
} }