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

View File

@ -6,6 +6,9 @@ enum Type: int
case Unit = 1;
case Promotion = 2;
case Broker = 3;
case Advance = 4; // Pie
case Credit = 5; // Credito
case Subsidy = 6; // Subsidio
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 DateInterval;
use PDO;
use PDOException;
use Incoviba\Common\Define;
use Incoviba\Common\Implement\Exception\EmptyResult;
use Incoviba\Exception\Model\InvalidState;
use PDO;
use Incoviba\Common;
use Incoviba\Model;
use Incoviba\Repository;
use PDOException;
class Reservation extends Common\Ideal\Repository
{
@ -18,7 +18,9 @@ class Reservation extends Common\Ideal\Repository
protected Repository\Proyecto $proyectoRepository,
protected Repository\Persona $personaRepository,
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);
}
@ -44,6 +46,12 @@ class Reservation extends Common\Ideal\Repository
->register('date', new Common\Implement\Repository\Mapper\DateTime('date'));
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
{
if (!isset($model->id)) {
@ -85,8 +93,11 @@ class Reservation extends Common\Ideal\Repository
$this->fetchUnits($model, $data_row);
try {
$this->fetchBroker($model, $data_row);
} catch (Common\Implement\Exception\EmptyResult) {}
} catch (EmptyResult) {}
$this->fetchPromotions($model, $data_row);
try {
$this->fetchPayment($model, $data_row);
} catch (EmptyResult) {}
return $model;
}
@ -248,6 +259,11 @@ class Reservation extends Common\Ideal\Repository
return $reservations;
}
/**
* @param Model\Venta\Reservation $reservation
* @return void
* @throws PDOException
*/
protected function saveUnits(Model\Venta\Reservation $reservation): void
{
if (empty($reservation->units)) {
@ -290,6 +306,12 @@ class Reservation extends Common\Ideal\Repository
Model\Venta\Reservation\Detail\Type::Unit->value,
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
{
if ($reservation->broker === null) {
@ -312,7 +334,7 @@ class Reservation extends Common\Ideal\Repository
$new_id = $reservation->broker->rut;
$reservation->broker = $this->brokerRepository->fetchById($result['reference_id']);
$this->editBroker($reservation, ['broker_rut' => $new_id]);
} catch (PDOException) {
} catch (EmptyResult | PDOException) {
$queryInsert = $this->connection->getQueryBuilder()
->insert()
->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
{
if (empty($reservation->promotions)) {
@ -366,6 +394,34 @@ class Reservation extends Common\Ideal\Repository
Model\Venta\Reservation\Detail\Type::Promotion->value,
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
{
$queryCheck = $this->connection->getQueryBuilder()
@ -643,4 +699,49 @@ class Reservation extends Common\Ideal\Repository
$reservation->broker = null;
} 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
{
$date = new DateTimeImmutable();
$date = $this->parseDate($data['date'] ?? 'now');
try {
$date = new DateTimeImmutable($data['date']);
} catch (DateMalformedStringException) {}
try {
$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->updateExistingReservation($data, $date);
} catch (Read | ServiceAction\Update) {
return $this->createNewReservation($data, $date);
}
return $this->process($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);
} 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
{
foreach ($promotions as $promotion_id) {
@ -292,4 +246,272 @@ class Reservation extends Ideal\Service\API
}
$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)
);
}
}