feature/cierres (#25)

Varios cambios

Co-authored-by: Juan Pablo Vial <jpvialb@incoviba.cl>
Reviewed-on: #25
This commit is contained in:
2025-07-22 13:18:00 +00:00
parent ba57cad514
commit 307f2ac7d7
418 changed files with 20045 additions and 984 deletions

View File

@ -1,12 +1,15 @@
<?php
namespace Incoviba\Service\Venta;
use DateMalformedStringException;
use Exception;
use DateTimeImmutable;
use Incoviba\Common\Implement\Exception\EmptyResult;
use Incoviba\Exception\ServiceAction\Read;
use PDOException;
use Psr\Log\LoggerInterface;
use Incoviba\Common\Ideal;
use Incoviba\Common\Implement\Exception\EmptyResult;
use Incoviba\Exception\ServiceAction\Create;
use Incoviba\Exception\ServiceAction\Read;
use Incoviba\Repository;
use Incoviba\Model;
use Incoviba\Service;
@ -41,26 +44,44 @@ class Credito extends Ideal\Service
}
/**
* @throws Exception
* @param array $data
* @return Model\Venta\Credito
* @throws Create
*/
public function add(array $data): Model\Venta\Credito
{
$fecha = new DateTimeImmutable($data['fecha']);
try {
$fecha = new DateTimeImmutable($data['fecha']);
} catch (DateMalformedStringException) {
$fecha = new DateTimeImmutable();
}
$uf = $this->valorService->clean($data['uf']) ?? $this->moneyService->getUF($fecha);
$tipoPago = $this->tipoPagoRepository->fetchByDescripcion('carta de resguardo');
try {
$tipoPago = $this->tipoPagoRepository->fetchByDescripcion('carta de resguardo');
} catch (EmptyResult $exception) {
throw new Create(__CLASS__, $exception);
}
$valor = $this->valorService->clean($data['valor']);
$pago = $this->pagoService->add([
'fecha' => $fecha->format('Y-m-d'),
'valor' => $valor * $uf,
'valor' => round($valor * $uf),
'uf' => $uf,
'tipo' => $tipoPago->id
]);
$credito = $this->creditoRepository->create([
'valor' => $valor,
'fecha' => $fecha->format('Y-m-d'),
'pago' => $pago->id
]);
return $this->creditoRepository->save($credito);
try {
$credito = $this->creditoRepository->create([
'valor' => $valor,
'fecha' => $fecha->format('Y-m-d'),
'pago' => $pago->id
]);
} catch (EmptyResult $exception) {
throw new Create(__CLASS__, $exception);
}
try {
return $this->creditoRepository->save($credito);
} catch (PDOException $exception) {
throw new Create(__CLASS__, $exception);
}
}
/**

View File

@ -1,12 +1,15 @@
<?php
namespace Incoviba\Service\Venta;
use PDOException;
use DateTimeImmutable;
use DateInterval;
use Incoviba\Common\Implement\Exception\EmptyResult;
use IntlDateFormatter;
use Psr\Log\LoggerInterface;
use Incoviba\Common\Ideal;
use Incoviba\Common\Implement\Exception\EmptyResult;
use Incoviba\Exception\ServiceAction\Create;
use Incoviba\Exception\ServiceAction\Read;
use Incoviba\Repository;
use Incoviba\Model;
@ -21,6 +24,20 @@ class Cuota extends Ideal\Service
parent::__construct($logger);
}
/**
* @param int $cuota_id
* @return Model\Venta\Cuota
* @throws Read
*/
public function getById(int $cuota_id): Model\Venta\Cuota
{
try {
return $this->process($this->cuotaRepository->fetchById($cuota_id));
} catch (EmptyResult $exception) {
throw new Read(__CLASS__, $exception);
}
}
public function pendientes(): array
{
$cuotas = $this->cuotaRepository->fetchPendientes();
@ -92,9 +109,18 @@ class Cuota extends Ideal\Service
return $this->cuotaRepository->fetchVigenteByPie($pie_id);
}
/**
* @param array $data
* @return Model\Venta\Cuota
* @throws Create
*/
public function add(array $data): Model\Venta\Cuota
{
$tipoPago = $this->tipoPagoRepository->fetchByDescripcion('cheque');
try {
$tipoPago = $this->tipoPagoRepository->fetchByDescripcion('cheque');
} catch (EmptyResult $exception) {
throw new Create(__CLASS__, $exception);
}
$fields = array_flip([
'fecha',
'banco',
@ -112,8 +138,16 @@ class Cuota extends Ideal\Service
$mapped_data = $filtered_data;
$mapped_data['valor_$'] = $mapped_data['valor'];
unset($mapped_data['valor']);
$cuota = $this->cuotaRepository->create($mapped_data);
$this->cuotaRepository->save($cuota);
try {
$cuota = $this->cuotaRepository->create($mapped_data);
return $this->cuotaRepository->save($cuota);
} catch (PDOException $exception) {
throw new Create(__CLASS__, $exception);
}
}
protected function process(Model\Venta\Cuota $cuota): Model\Venta\Cuota
{
return $cuota;
}
}

View File

@ -2,10 +2,12 @@
namespace Incoviba\Service\Venta;
use Error;
use Incoviba\Exception\ServiceAction\Read;
use Exception;
use Psr\Log\LoggerInterface;
use Incoviba\Common\Implement;
use Incoviba\Common\Ideal;
use Incoviba\Exception\ServiceAction\Create;
use Incoviba\Exception\ServiceAction\Read;
use Incoviba\Model;
use Incoviba\Repository;
use Incoviba\Service\Valor;
@ -53,6 +55,10 @@ class FormaPago extends Ideal\Service
return $formaPago;
}
/**
* @param array $data
* @return Model\Venta\FormaPago
*/
public function add(array $data): Model\Venta\FormaPago
{
$fields = [
@ -68,6 +74,9 @@ class FormaPago extends Ideal\Service
$method = 'add' . str_replace(' ', '', ucwords(str_replace('_', ' ', $name)));
$obj = $this->{$method}($data);
$forma_pago->{$name} = $obj;
} catch (Create $exception) {
$this->logger->warning($exception);
continue;
} catch (Error $error) {
$this->logger->critical($error);
}
@ -76,14 +85,19 @@ class FormaPago extends Ideal\Service
return $forma_pago;
}
/**
* @param array $data
* @return Model\Venta\Pie
* @throws Create
*/
protected function addPie(array $data): Model\Venta\Pie
{
$fields = array_fill_keys([
$fields = array_flip([
'fecha_venta',
'pie',
'cuotas',
'uf'
], 0);
]);
$filtered_data = array_intersect_key($data, $fields);
$mapped_data = array_combine([
'fecha',
@ -94,14 +108,20 @@ class FormaPago extends Ideal\Service
$mapped_data['valor'] = $this->valorService->clean($mapped_data['valor']);
return $this->pieService->add($mapped_data);
}
/**
* @param array $data
* @return Model\Venta\Subsidio
* @throws Create
*/
protected function addSubsidio(array $data): Model\Venta\Subsidio
{
$fields = array_fill_keys([
$fields = array_flip([
'fecha_venta',
'ahorro',
'subsidio',
'uf'
], 0);
]);
$filtered_data = array_intersect_key($data, $fields);
$mapped_data = array_combine([
'fecha',
@ -113,13 +133,19 @@ class FormaPago extends Ideal\Service
$mapped_data['subsidio'] = $this->valorService->clean($mapped_data['subsidio']);
return $this->subsidioService->add($mapped_data);
}
/**
* @param array $data
* @return Model\Venta\Credito
* @throws Create
*/
protected function addCredito(array $data): Model\Venta\Credito
{
$fields = array_fill_keys([
$fields = array_flip([
'fecha_venta',
'credito',
'uf'
], 0);
]);
$filtered_data = array_intersect_key($data, $fields);
$mapped_data = array_combine([
'fecha',
@ -129,12 +155,18 @@ class FormaPago extends Ideal\Service
$mapped_data['valor'] = $this->valorService->clean($mapped_data['valor']);
return $this->creditoService->add($mapped_data);
}
/**
* @param array $data
* @return Model\Venta\BonoPie
* @throws Create
*/
protected function addBonoPie(array $data): Model\Venta\BonoPie
{
$fields = array_fill_keys([
$fields = array_flip([
'fecha_venta',
'bono_pie'
], 0);
]);
$filtered_data = array_intersect_key($data, $fields);
$mapped_data = array_combine([
'fecha',

View File

@ -0,0 +1,214 @@
<?php
namespace Incoviba\Service\Venta\MediosPago;
use PDOException;
use Psr\Http\Client\ClientExceptionInterface;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\ResponseInterface;
use Incoviba\Common\Define\Repository;
use Incoviba\Common\Ideal\LoggerEnabled;
use Incoviba\Common\Implement\Exception\{EmptyResponse, EmptyResult, HttpException};
use Incoviba\Exception\InvalidResult;
abstract class AbstractEndPoint extends LoggerEnabled implements EndPoint
{
public function __construct(protected ClientInterface $client) {}
/**
* @param ResponseInterface $response
* @param string $request_uri
* @param array $validStatus
* @param array $invalidStatus
* @return void
* @throws EmptyResponse
*/
protected function validateResponse(ResponseInterface $response, string $request_uri, array $validStatus, array $invalidStatus): void
{
$status = $response->getStatusCode();
$reason = $response->getReasonPhrase();
if (in_array($status, $invalidStatus)) {
$contents = $response->getBody()->getContents();
$this->logger->warning('Invalid Status', [
'uri' => $request_uri,
'code' => $status,
'reason' => $reason,
'contents' => $contents
]);
$exception = new HttpException("{$reason}\n{$contents}", $status);
throw new EmptyResponse($request_uri, $exception);
}
if (!in_array($status, $validStatus)) {
$contents = $response->getBody()->getContents();
$this->logger->warning('Not Valid Status', [
'uri' => $request_uri,
'code' => $status,
'reason' => $reason,
'contents' => $contents
]);
$exception = new HttpException("{$reason}\n{$contents}", $status);
throw new EmptyResponse($request_uri, $exception);
}
}
/**
* @param string $request_uri
* @param array $validStatus
* @param array $invalidStatus
* @return array
* @throws EmptyResponse
*/
protected function sendGet(string $request_uri, array $validStatus, array $invalidStatus): array
{
try {
$response = $this->client->get($request_uri);
} catch (ClientExceptionInterface $exception) {
throw new EmptyResponse($request_uri, $exception);
}
$this->validateResponse($response, $request_uri, $validStatus, $invalidStatus);
return json_decode($response->getBody()->getContents(), true);
}
/**
* @param string $request_uri
* @param array $data
* @param array $validStatus
* @param array $invalidStatus
* @param string|null $accountKey
* @return bool
* @throws EmptyResponse
*/
protected function sendAdd(string $request_uri, array $data, array $validStatus, array $invalidStatus, ?string $accountKey = null): bool
{
$params = $this->mapParams($data);
$this->logger->info('Send Add', ['uri' => $request_uri, 'params' => $params]);
try {
$options = [
'json' => $params
];
if ($accountKey !== null) {
$options['headers'] = [
'X-Account-Key' => $accountKey
];
}
$response = $this->client->post($request_uri, $options);
} catch (ClientExceptionInterface $exception) {
throw new EmptyResponse($request_uri, $exception);
}
$this->validateResponse($response, $request_uri, $validStatus, $invalidStatus);
$contents = $response->getBody()->getContents();
if (trim($contents) === '') {
$this->logger->warning("Empty contents", [
'uri' => $request_uri,
'data' => $data,
'code' => $response->getStatusCode(),
'reason' => $response->getReasonPhrase(),
'contents' => $contents
]);
throw new EmptyResponse($request_uri);
}
$json = json_decode($contents, true);
$this->logger->info('Add Response', $json);
return $this->save($json);
}
/**
* @param string $request_uri
* @param array $data
* @param array $validStatus
* @param array $invalidStatus
* @param string|null $accountKey
* @return bool
* @throws EmptyResponse
*/
protected function sendEdit(string $request_uri, array $data, array $validStatus, array $invalidStatus, ?string $accountKey = null): bool
{
$params = $this->mapParams($data);
try {
$options = [
'json' => $params
];
if ($accountKey !== null) {
$options['headers'] = [
'X-Account-Key' => $accountKey
];
}
$response = $this->client->put($request_uri, $options);
} catch (ClientExceptionInterface $exception) {
throw new EmptyResponse($request_uri, $exception);
}
$this->validateResponse($response, $request_uri, $validStatus, $invalidStatus);
$contents = $response->getBody()->getContents();
$json = json_decode($contents, true);
return $this->save($json);
}
/**
* @param string $request_uri
* @param array $validStatus
* @param array $invalidStatus
* @param array|null $data
* @return void
* @throws EmptyResponse
*/
protected function sendDelete(string $request_uri, array $validStatus, array $invalidStatus, ?array $data = null): void
{
$this->logger->info('Send Delete', ['uri' => $request_uri]);
try {
$options = [];
if ($data !== null) {
$options = ['json' => $data];
}
$response = $this->client->delete($request_uri, $options);
} catch (ClientExceptionInterface $exception) {
throw new EmptyResponse($request_uri, $exception);
}
$this->validateResponse($response, $request_uri, $validStatus, $invalidStatus);
$this->logger->info('Delete Response', ['request_uri' => $request_uri]);
}
protected function doSave(Repository $repository, array $data): bool
{
try {
$repository->fetchByTokuId($data['id']);
return true;
} catch (EmptyResult) {
$mappedData = $this->mapSave($data);
$filteredData = $repository->filterData($mappedData);
$model = $repository->create($filteredData);
try {
$repository->save($model);
return true;
} catch (PDOException $exception) {
$this->logger->warning($exception);
return false;
}
}
}
/**
* @param callable $callable
* @param string $id
* @param string $message
* @return array
* @throws InvalidResult
*/
protected function doGetById(callable $callable, string $id, string $message): array
{
try {
$model = $callable($id);
return json_decode(json_encode($model), true);
} catch (EmptyResult $exception) {
throw new InvalidResult($message, 404, $exception);
}
}
abstract public function save(array $data): bool;
abstract protected function mapParams(array $data): array;
abstract protected function mapSave(array $data): array;
}

View File

@ -0,0 +1,59 @@
<?php
namespace Incoviba\Service\Venta\MediosPago;
use Incoviba\Common\Implement\Exception\EmptyResponse;
use Incoviba\Exception\InvalidResult;
interface EndPoint
{
/**
* @param string $id
* @return array
* @throws InvalidResult
*/
public function getById(string $id): array;
/**
* @param string $id
* @return array
* @throws InvalidResult
*/
public function getByExternalId(string $id): array;
/**
* @param string $id
* @return array
* @throws EmptyResponse
*/
public function get(string $id): array;
/**
* @param array $data
* @param string|null $accountKey
* @return bool
* @throws EmptyResponse
*/
public function add(array $data, ?string $accountKey = null): bool;
/**
* @param string $id
* @param array $data
* @param string|null $accountKey
* @return bool
* @throws EmptyResponse
*/
public function edit(string $id, array $data, ?string $accountKey = null): bool;
/**
* @param string $id
* @return void
* @throws EmptyResponse
*/
public function delete(string $id): void;
/**
* @param array $skip
* @return array
* @throws InvalidResult
*/
public function reset(array $skip = []): array;
}

View File

@ -0,0 +1,526 @@
<?php
namespace Incoviba\Service\Venta\MediosPago;
use Incoviba\Common\Implement\Exception\EmptyResult;
use InvalidArgumentException;
use PDO;
use PDOException;
use Psr\Http\Message\ServerRequestInterface;
use Incoviba\Common\Define\Connection;
use Incoviba\Common\Ideal;
use Incoviba\Common\Implement\Exception\EmptyResponse;
use Incoviba\Exception\InvalidResult;
use Incoviba\Exception\ServiceAction\Read;
use Incoviba\Model;
use Incoviba\Model\Persona;
use Incoviba\Model\Venta\Propietario;
use Incoviba\Service\HMAC;
use Incoviba\Service\Venta\MediosPago\Toku\{Customer,Subscription,Invoice};
use Psr\Log\LoggerInterface;
use Throwable;
class Toku extends Ideal\Service
{
const string CUSTOMER = 'customer';
const string SUBSCRIPTION = 'subscription';
const string INVOICE = 'invoice';
protected Customer $customer;
protected Subscription $subscription;
protected Invoice $invoice;
public function __construct(LoggerInterface $logger, protected Connection $connection, protected HMAC $hmac)
{
parent::__construct($logger);
}
public function register(string $type, EndPoint $endPoint): self
{
if (!in_array(strtolower($type), ['customer', 'subscription', 'invoice'])) {
throw new InvalidArgumentException("{$type} is not a valid type of EndPoint for " . __CLASS__);
}
$this->{strtolower($type)} = $endPoint;
return $this;
}
/**
* @param Persona|Propietario $persona
* @return array
* @throws InvalidResult
*/
public function sendPersona(Model\Persona|Model\Venta\Propietario $persona): array
{
$rut = implode('', [$persona->rut, strtoupper($persona->dv)]);
try {
return $this->customer->getById($rut);
} catch (InvalidResult $exception) {
$datos = $persona->datos;
$customerData = [
'rut' => $rut,
'nombreCompleto' => $persona->nombreCompleto(),
'email' => $datos?->email ?? '',
'telefono' => $datos?->telefono ?? ''
];
try {
if (!$this->customer->add($customerData)) {
throw new InvalidResult("Could not save Customer for Persona {$rut}", 409, $exception);
}
} catch (EmptyResponse $exception) {
throw new InvalidResult("Could not save Customer for Persona {$rut}", 409, $exception);
}
return $this->customer->getById($rut);
}
}
/**
* @param Model\Venta $venta
* @return array
* @throws InvalidResult
*/
public function sendVenta(Model\Venta $venta): array
{
$customer = $this->sendPersona($venta->propietario());
try {
return $this->subscription->getById($venta->id);
} catch (InvalidResult $exception) {
$inmobiliaria = $venta->proyecto()->inmobiliaria();
$accountKey = null;
try {
$accountKey = $this->getAccountKey($inmobiliaria->rut);
} catch (EmptyResult) {}
$subscriptionData = [
'customer' => $customer['toku_id'],
'product_id' => $venta->id,
'venta' => $venta
];
try {
if (!$this->subscription->add($subscriptionData, $accountKey)) {
throw new InvalidResult("Could not save Subscription for Venta {$venta->id}", 409, $exception);
}
} catch (EmptyResponse $exception) {
throw new InvalidResult("Could not save Subscription for Venta {$venta->id}", 409, $exception);
}
return $this->subscription->getById($venta->id);
}
}
/**
* @param Model\Venta $venta
* @param array $cuotas_ids
* @return array
* @throws InvalidResult
*/
public function sendCuotas(Model\Venta $venta, array $cuotas_ids = []): array
{
$customer = $this->sendPersona($venta->propietario());
$subscription = $this->sendVenta($venta);
$customerInvoices = [];
try {
$customerInvoices = $this->invoice->getByCustomer($customer['toku_id']);
$customerInvoices = array_filter($customerInvoices, function($invoiceRow) use ($subscription) {
return $invoiceRow['subscription'] === $subscription['toku_id'];
});
} catch (EmptyResponse) {}
$inmobiliaria = $venta->proyecto()->inmobiliaria();
$accountKey = null;
try {
$accountKey = $this->getAccountKey($inmobiliaria->rut);
} catch (EmptyResult) {}
$invoices = [];
$errors = [];
foreach ($venta->formaPago()->pie->cuotas() as $cuota) {
if (count($cuotas_ids) > 0 and !in_array($cuota->id, $cuotas_ids)) {
continue;
}
if (count($customerInvoices) and in_array($cuota->id, array_column($customerInvoices, 'invoice_external_id'))) {
$invoice = array_find($customerInvoices, function($invoiceRow) use ($cuota) {
return $invoiceRow['invoice_external_id'] === $cuota->id;
});
if ($invoice !== null) {
$invoices []= $invoice;
$this->invoice->save($invoice);
continue;
}
}
try {
$invoices []= $this->invoice->getById($cuota->id);
} catch (InvalidResult $exception) {
try {
$invoiceData = [
'customer' => $customer['toku_id'],
'product_id' => $venta->id,
'subscription' => $subscription['toku_id'],
'cuota' => $cuota,
'venta' => $venta
];
if (!$this->invoice->add($invoiceData, $accountKey)) {
throw new EmptyResponse("Could not add Invoice for Cuota {$cuota->id}", $exception);
}
$invoices []= $this->invoice->getById($cuota->id);
} catch (EmptyResponse $exception) {
$this->logger->warning($exception);
$errors []= $exception;
}
}
}
if (count($errors) > 0) {
$this->logger->warning("Revisar el envío de cuotas de la Venta {$venta->id}");
}
return $invoices;
}
/**
* @param array $input
* @return bool
* @throws InvalidResult
*/
public function successEvent(array $input): bool
{
$validEvents = ['payment_intent.succeeded', 'payment.succeeded', 'transaction.success',
'transaction.bulk_success', 'payment_intent.succeeded_batch'];
if (!in_array($input['event_type'], $validEvents)) {
$this->logger->warning("{$input['event_type']} is not a valid event");
throw new InvalidResult("{$input['event_type']} is not a valid event", 422);
}
switch ($input['event_type']) {
case 'payment_intent.succeeded_batch':
case 'transaction.bulk_success':
return $this->successBulk($input);
case 'transaction.success':
return $this->successTransaction($input);
default:
$paymentData = $this->mapEventData($input);
return $this->updatePago($paymentData);
}
}
public function check(bool $update = false): array
{
try {
list('existingSubscriptions' => $existingSubscriptions, 'missingVentas' => $missingVentas) = $this->subscription->check();
} catch (Read) {
return [];
}
$queues = [];
if (count($missingVentas) > 0) {
foreach ($missingVentas as $venta) {
$cuotas = $venta->formaPago()->pie->cuotas();
if (count($cuotas) === 0) {
continue;
}
$queueData = [
'type' => 'request',
'url' => "/api/external/toku/cuotas/{$venta->id}",
'method' => 'post',
'body' => ['cuotas' => array_map(function(Model\Venta\Cuota $cuota) {return $cuota->id;}, $cuotas)]
];
$queues []= $queueData;
}
}
if ($update and count($existingSubscriptions) > 0) {
foreach ($existingSubscriptions as $subscription) {
$cuotas = $subscription->venta->formaPago()->pie->cuotas();
if (count($cuotas) === 0) {
continue;
}
$propietario = $subscription->venta->propietario();
try {
$customer = $this->customer->getById($propietario->rut());
} catch (InvalidResult) {
continue;
}
try {
$editData = [
'rut' => $customer['rut'],
'nombreCompleto' => $propietario->nombreCompleto(),
'email' => $propietario->datos?->email ?? '',
'telefono' => $propietario->datos?->telefono ?? ''
];
$this->customer->edit($customer['toku_id'], $editData);
} catch (EmptyResponse $exception) {
$this->logger->warning($exception);
}
foreach ($cuotas as $cuota) {
try {
$invoice = $this->invoice->getById($cuota->id);
$editData = [
'customer' => $customer['toku_id'],
'product_id' => $subscription->venta->id,
'subscription' => $subscription->toku_id,
'cuota' => $cuota,
'venta' => $subscription->venta
];
try {
$this->invoice->edit($invoice['toku_id'], $editData);
} catch (EmptyResponse) {}
} catch (InvalidResult) {
$invoiceData = [
'customer' => $customer['toku_id'],
'product_id' => $subscription->venta->id,
'subscription' => $subscription->toku_id,
'cuota' => $cuota,
'venta' => $subscription->venta
];
try {
$this->invoice->add($invoiceData);
} catch (EmptyResponse) {}
}
}
}
}
return $queues;
}
public function reset(array $skips = []): array
{
$output = [];
try {
$output['customer'] = $this->customer->reset($skips['customer'] ?? []);
$output['subscription'] = $this->subscription->reset($skips['subscription'] ?? []);
$output['payments'] = $this->invoice->resetPayments();
$output['invoice'] = $this->invoice->reset($skips['invoice'] ?? []);
} catch (InvalidResult $exception) {
$this->logger->warning($exception);
return [];
}
return $output;
}
public function queue(array $venta_ids): array
{
$queues = [];
foreach ($venta_ids as $venta_id) {
if ($this->subscription->queue($venta_id)) {
$queues []= [
'type' => 'request',
'url' => "/api/external/toku/cuotas/{$venta_id}",
'method' => 'post',
'body' => []
];
}
}
return $queues;
}
public function update(array $ids, ?string $type = null): array
{
if ($type === null) {
$types = [
'customers',
'subscriptions',
'invoices'
];
$results = [];
foreach ($types as $type) {
$results[$type] = $this->update($ids[$type], $type);
}
return $results;
}
$results = [];
switch ($type) {
case 'subscriptions':
try {
$results['subscription'] = $this->subscription->update($ids);
} catch (EmptyResult | EmptyResponse $exception) {
$this->logger->error($exception);
}
break;
case 'invoices':
try {
$results['invoice'] = $this->invoice->updateAll($ids);
} catch (EmptyResult $exception) {
$this->logger->error($exception);
}
break;
}
return $results;
}
/**
* @param ServerRequestInterface $request
* @param array $tokenConfig
* @return bool
*/
public function validateToken(ServerRequestInterface $request, array $tokenConfig): bool
{
if (!$request->hasHeader('User-Agent') or !str_starts_with($request->getHeaderLine('User-Agent'), 'Toku-Webhooks')) {
return false;
}
if (!$request->hasHeader('X-Datadog-Tags') or !$request->hasHeader('Tracestate')) {
return false;
}
if (!$request->hasHeader('Toku-Signature')) {
return false;
}
$tokuSignature = $request->getHeaderLine('Toku-Signature');
try {
list($timestamp, $signature) = array_map(function($elem) {
return explode('=', $elem)[1];
}, explode(',', $tokuSignature));
$body = $request->getBody()->getContents();
$json = json_decode($body, true);
if (!is_array($json)) {
return false;
}
if (!array_key_exists('id', $json)) {
return false;
}
$eventId = $json['id'];
$eventType = $json['event_type'];
$query = $this->connection->getQueryBuilder()
->select('secret')
->from('toku_webhooks')
->where('enabled = ? AND JSON_SEARCH(events, "one", ?) IS NOT NULL');
$params = [true, $eventType];
$statement = $this->connection->prepare($query);
$statement->execute($params);
$results = $statement->fetchAll(PDO::FETCH_COLUMN);
if (count($results) === 0) {
return false;
}
if (array_any($results, fn($secret) => $this->hmac->validate($timestamp, $signature, $eventId, $secret))) {
return true;
}
} catch (Throwable $throwable) {
$this->logger->error($throwable);
}
return false;
}
/**
* @param array $request
* @return bool
* @throws InvalidResult
*/
protected function updatePago(array $request): bool
{
# If $customer is not found, it will throw an exception and stop
$customer = $this->customer->getByExternalId($request['customer']);
$invoice = $this->invoice->getByExternalId($request['invoice']);
return $this->invoice->update($invoice['toku_id'], $request);
}
protected function successTransaction(array $input): bool
{
$intents = $this->mapMultiplePaymentIntentsData($input);
$errors = [];
foreach ($intents as $intent) {
if (!$this->updatePago($intent)) {
$errors []= $intent;
}
}
if (array_key_exists('wallet_movements', $input)) {
foreach ($input['wallet_movements'] as $walletMovement) {
if (array_key_exists('type', $walletMovement) and $walletMovement['type'] === 'SURPLUS') {
$this->logger->alert('Revisar el envío de cuotas de la Venta ' . $walletMovement['product_id']);
}
}
}
return count($errors) === 0;
}
/**
* @param array $input
* @return bool
* @throws InvalidResult
*/
protected function successBulk(array $input): bool
{
return match($input['event_type']) {
'payment_intent.succeeded_batch' => $this->successBulkPaymentIntent($input),
'transaction.bulk_success' => $this->successBulkTransaction($input),
default => false
};
}
/**
* @param array $input
* @return bool
* @throws InvalidResult
*/
protected function successBulkTransaction(array $input): bool
{
$errors = [];
foreach($input['events'] as $event) {
$event['event_type'] = 'transaction.success';
if (!$this->successEvent($event)) {
$errors []= $event;
}
}
return count($errors) === 0;
}
/**
* @param array $input
* @return bool
* @throws InvalidResult
*/
protected function successBulkPaymentIntent(array $input): bool
{
$errors = [];
foreach($input['payment_intent'] as $intent) {
$intent['event_type'] = 'payment_intent.succeeded';
$intent['payment_intent'] = $input['payment_intents'];
unset($intent['payment_intents']);
if (!$this->successEvent($intent)) {
$errors []= $intent;
}
}
return count($errors) === 0;
}
protected function mapEventData(array $input): array
{
return match ($input['event_type']) {
'payment_intent.succeeded' => $this->mapPaymentIntentData($input),
'payment.succeeded' => $this->mapPaymentEventData($input),
default => [],
};
}
protected function mapMultiplePaymentIntentsData(array $input): array
{
$output = [];
foreach ($input['payment_intents'] as $intent) {
$intent['transaction_date'] = $input['transaction']['transaction_date'];
$intent['customer'] = $input['customer']['id'];
$intent['invoice'] = $intent['id_invoice'];
$intent['subscription'] = $intent['id_subscription'];
$intent['cuota_id'] = $intent['invoice_external_id'];
$o = $this->mapPaymentIntentData($intent);
$output []= $o;
}
return $output;
}
protected function mapPaymentEventData(array $input): array
{
$data = $input['payment'];
if (!array_key_exists('amount', $data) and array_key_exists('payment_amount', $data)) {
$data['amount'] = $data['payment_amount'];
}
$data['status'] = 'AUTHORIZED';
$data['date'] = $data['payment_date'];
return $data;
}
protected function mapPaymentIntentData(array $input): array
{
$data = $input['payment_intent'];
$data['date'] = $data['transaction_date'];
return $data;
}
protected function getAccountKey(int $sociedad_rut): string
{
$query = $this->connection->getQueryBuilder()
->select('account_key')
->from('toku_accounts')
->where('enabled = ? AND sociedad_rut = ?');
$params = [true, $sociedad_rut];
try {
$statement = $this->connection->prepare($query);
$statement->execute($params);
return $statement->fetchColumn();
} catch (PDOException $exception) {
$this->logger->error($exception);
throw new EmptyResult($query, $exception);
}
}
}

View File

@ -0,0 +1,132 @@
<?php
namespace Incoviba\Service\Venta\MediosPago\Toku;
use PDOException;
use Psr\Http\Client\ClientInterface;
use Incoviba\Common\Implement\Exception\EmptyResponse;
use Incoviba\Common\Implement\Exception\EmptyResult;
use Incoviba\Repository;
use Incoviba\Service\Venta\MediosPago\AbstractEndPoint;
class Customer extends AbstractEndPoint
{
public function __construct(ClientInterface $client,
protected Repository\Venta\MediosPago\Toku\Customer $customerRepository)
{
parent::__construct($client);
}
public function getById(string $id): array
{
return $this->doGetById([$this->customerRepository, 'fetchByRut'], $id, "No existe toku_id para Persona {$id}");
}
public function getByExternalId(string $id): array
{
return $this->doGetById([$this->customerRepository, 'fetchByTokuId'], $id, "No existe Customer para toku_id {$id}");
}
public function get(string $id): array
{
$request_uri = "/customers/{$id}";
return $this->sendGet($request_uri, [200], [404, 422]);
}
public function add(array $data, ?string $accountKey = null): bool
{
$request_uri = "/customers";
return $this->sendAdd($request_uri, $data, [200, 201], [400, 422], $accountKey);
}
public function edit(string $id, array $data, ?string $accountKey = null): bool
{
$request_uri = "customers/{$id}";
return $this->sendEdit($request_uri, $data, [200], [400, 404, 422], $accountKey);
}
public function delete(string $id): void
{
$request_uri = "/customers/{$id}";
$this->sendDelete($request_uri, [204], [404, 409]);
}
public function reset(array $skip = []): array
{
try {
$tokuIds = $this->customerRepository->fetchAllTokuIds();
$tokuIds = array_filter($tokuIds, function (string $tokuId) use ($skip) {
return !in_array($tokuId, $skip);
});
} catch (EmptyResult $exception) {
$this->logger->warning($exception);
return [];
}
$this->logger->info('Resetando ' . count($tokuIds) . ' clientes');
foreach ($tokuIds as $tokuId) {
try {
$this->delete($tokuId);
$this->customerRepository->removeByTokuId($tokuId);
} catch (EmptyResponse | PDOException $exception) {
$this->logger->warning($exception, ['customer->toku_id' => $tokuId]);
}
}
return $tokuIds;
}
public function save(array $data): bool
{
return $this->doSave($this->customerRepository, $data);
}
protected function mapParams(array $data): array
{
$paramsMap = [
'government_id' => 'rut',
'external_id' => 'rut',
'mail' => 'email',
'name' => 'nombreCompleto',
'phone_number' => 'telefono',
'pac_mandate_id' => null,
'default_agent' => 'contacto@incoviba.cl',
'send_mail' => false,
'agent_phone_number' => null,
'rfc' => null,
'tax_zip_code' => null,
'fiscal_regime' => null,
'default_receipt_type' => null,
'secondary_emails' => null,
'silenced_until' => null,
'metadata' => null
];
$params = [];
foreach ($paramsMap as $key => $ref) {
if ($ref === null) {
continue;
}
if ($ref === 'telefono') {
$value = $data[$ref];
if ($value === '' or $value === null or $value === '0') {
continue;
}
if (!str_starts_with($value, '+')) {
$value = "+56{$value}";
}
$params[$key] = $value;
continue;
}
if (array_key_exists($ref, $data) and $data[$ref] !== '' and $data[$ref] !== null) {
$params[$key] = $data[$ref];
}
}
return $params;
}
protected function mapSave(array $data): array
{
$responseMap = [
'government_id' => 'rut',
'id' => 'toku_id'
];
$mappedData = [];
foreach ($responseMap as $responseKey => $dataKey) {
if (isset($data[$responseKey])) {
$mappedData[$dataKey] = $data[$responseKey];
}
}
return $mappedData;
}
}

View File

@ -0,0 +1,335 @@
<?php
namespace Incoviba\Service\Venta\MediosPago\Toku;
use DateMalformedStringException;
use DateTimeImmutable;
use DateTimeZone;
use PDO;
use PDOException;
use Psr\Http\Client\ClientInterface;
use Psr\Log\LoggerInterface;
use Incoviba\Common\Implement\Exception\EmptyResponse;
use Incoviba\Common\Implement\Exception\EmptyResult;
use Incoviba\Exception\InvalidResult;
use Incoviba\Model;
use Incoviba\Repository;
use Incoviba\Service\UF;
use Incoviba\Service\Venta\MediosPago\AbstractEndPoint;
use Incoviba\Service\Venta\Pago;
class Invoice extends AbstractEndPoint
{
public function __construct(ClientInterface $client,
protected Repository\Venta\MediosPago\Toku\Invoice $invoiceRepository,
protected Pago $pagoService,
protected UF $ufService)
{
parent::__construct($client);
}
public function getById(string $id): array
{
return $this->doGetById([$this->invoiceRepository, 'fetchByCuota'], $id, "No existe toku_id para Cuota {$id}");
}
public function getByExternalId(string $id): array
{
return $this->doGetById([$this->invoiceRepository, 'fetchByTokuId'], $id, "No existe Invoice para toku_id {$id}");
}
public function get(string $id): array
{
$request_uri = "/invoices/{$id}";
return $this->sendGet($request_uri, [200], [404]);
}
public function add(array $data, ?string $accountKey = null): bool
{
$request_uri = "/invoices";
return $this->sendAdd($request_uri, $data, [200, 201], [400, 409, 422], $accountKey);
}
public function edit(string $id, array $data, ?string $accountKey = null): bool
{
$request_uri = "/invoices/{$id}";
return $this->sendEdit($request_uri, $data, [200], [400, 404, 409, 422], $accountKey);
}
public function delete(string $id): void
{
$request_uri = "/invoices/{$id}";
$this->sendDelete($request_uri, [204], [404, 409]);
}
public function reset(array $skip = []): array
{
try {
$tokuIds = $this->invoiceRepository->fetchAllTokuIds();
$tokuIds = array_filter($tokuIds, function (string $tokuId) use ($skip) {
return !in_array($tokuId, $skip);
});
} catch (EmptyResult $exception) {
$this->logger->warning($exception);
return [];
}
foreach ($tokuIds as $tokuId) {
try {
$this->delete($tokuId);
$this->invoiceRepository->removeByTokuId($tokuId);
} catch (EmptyResponse | PDOException $exception) {
$this->logger->warning($exception, ['invoice->toku_id' => $tokuId]);
}
}
return $tokuIds;
}
public function resetPayments(): array
{
$page = 1;
$pageSize = 1000;
$totalPayments = [];
while (true) {
if ($page > 100) {
break;
}
try {
$request_uri = "/organization/payments?page=1&page_size=1000";
$payments = $this->sendGet($request_uri, [200], [404, 422]);
} catch (EmptyResponse $exception) {
$this->logger->warning($exception, ['request_uri' => $request_uri]);
return [];
}
if (array_key_exists('message', $payments)) {
break;
}
$this->altLogger->info('Reset Payments', ['count' => count($payments), 'payments' => $payments]);
$query = [];
/*
"id": "pay_79zh1OU1pVV5g0V0I6kShf5AF-I24cUn",
"created_at": "2025-06-07T07:08:51+0000",
"deleted_at": null,
"invoice": "in_IhbKbT21x0ADlnKRCbV57sn2DDI8neq0",
"customer": "cus_bTXPBVopZxKOqTOWzRZkhvDEM9XXtvWh",
"government_id": "175181431",
"name": "Augusto Felipe Schilfferli Rojas",
"product_id": "1304-d1749582981383358",
"due_date": "2024-11-01",
"transaction_date": "2025-06-07T07:08:51+0000",
"payment_amount": 14.4822,
"buy_order": null,
"processed_by_toku": false,
"payment_method": null,
"card_type": null,
"card_number": null,
"payment_type": null,
"authorization_code": null,
"mc_order_id": null,
"amount_paid": 14.4822
*/
foreach ($payments as $payment) {
$query[] = [
'payment_amount' => $payment['payment_amount'],
'payment_date' => $payment['transaction_date'],
'payment_method' => $payment['payment_method'],
'product_id' => $payment['product_id'],
'due_date' => $payment['due_date'],
];
}
try {
$request_uri = "/payments";
$this->sendDelete($request_uri, [204], [404, 422], ['payments' => $query]);
} catch (EmptyResponse $exception) {
$this->logger->warning($exception, ['request_uri' => $request_uri]);
return [];
}
$this->altLogger->info('Reset Payments Payload', ['count' => count($payments), 'payments' => $query]);
$totalPayments = array_merge($totalPayments, $payments);
if (count($payments) < $pageSize) {
break;
}
$page++;
}
return $totalPayments;
}
/**
* @param string $customer_id
* @return array
* @throws EmptyResponse
*/
public function getByCustomer(string $customer_id): array
{
$request_uri = "/invoices/customer/{$customer_id}";
return $this->sendGet($request_uri, [200], [404]);
}
/**
* @param string $invoice_toku_id
* @param array $data
* @return bool
* @throws InvalidResult
*/
public function update(string $invoice_toku_id, array $data): bool
{
try {
$invoice = $this->invoiceRepository->fetchByTokuId($invoice_toku_id);
} catch (EmptyResult $exception) {
$this->logger->warning($exception, ['invoice_toku_id' => $invoice_toku_id, 'data' => $data]);
throw new InvalidResult("No existe Invoice para toku_id {$invoice_toku_id}", 404, $exception);
}
if ($data['status'] !== 'AUTHORIZED') {
$this->logger->warning("Pago no autorizado", ['invoice_toku_id' => $invoice_toku_id, 'data' => $data]);
throw new InvalidResult("Pago no autorizado", 422);
}
$dateString = $data['date'];
try {
$date = new DateTimeImmutable($dateString);
} catch (DateMalformedStringException $exception) {
$this->logger->warning($exception, ['invoice_toku_id' => $invoice_toku_id, 'data' => $data]);
throw new InvalidResult("Fecha no válida: {$dateString}", 422, $exception);
}
$uf = $this->ufService->get($date);
if ($uf === 0.0) {
$this->logger->warning("No hay UF para la fecha: {$dateString}", ['invoice_toku_id' => $invoice_toku_id, 'data' => $data]);
throw new InvalidResult("No hay UF para la fecha: {$dateString}", 422);
}
$valor = $data['amount'];
if ($valor > 1000) {
$valor = $data['amount'] / $uf;
}
if (abs($valor - $invoice->cuota->pago->valor()) >= 0.0001) {
$this->logger->warning("Valor en UF no coincide: {$data['amount']}, {$valor} <=> {$invoice->cuota->pago->valor()}", ['invoice_toku_id' => $invoice_toku_id, 'data' => $data]);
throw new InvalidResult("Valor en UF no coincide: {$data['amount']}, {$valor} <=> {$invoice->cuota->pago->valor()}", 422);
}
if ($invoice->cuota->pago->isPagado()) {
return true;
}
return $this->pagoService->depositar($invoice->cuota->pago, $date);
}
/**
* @param array $idsData
* @return array
* @throws EmptyResult
*/
public function updateAll(array $idsData): array
{
$tokuIds = array_column($idsData, 'toku_id');
$oldIds = array_column($idsData, 'product_id');
$placeholders = array_map(fn($id) => "id{$id}", array_keys($oldIds));
$placeholdersString = implode(', ', array_map(fn($id) => ":{$id}", $placeholders));
$query = $this->pagoService->getRepository()->getConnection()->getQueryBuilder()
->select('pago.id, CONCAT_WS("-", unidad.descripcion, CONCAT_WS("-", propietario.rut, propietario.dv)) AS old_pid')
->from('pago')
->joined('JOIN cuota ON cuota.pago = pago.id')
->joined('JOIN venta ON venta.pie = cuota.pie')
->joined('JOIN propietario ON propietario.rut = venta.propietario')
->joined('JOIN propiedad_unidad pu ON pu.propiedad = venta.propiedad')
->joined('JOIN unidad ON pu.unidad = unidad.id')
->having("old_pid IN ({$placeholdersString})");
$values = array_combine($placeholders, $oldIds);
try {
$statement = $this->pagoService->getRepository()->getConnection()->execute($query, $values);
$results = $statement->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $exception) {
$this->logger->error($exception);
throw new EmptyResult($query, $exception);
}
$ids = array_column($results, 'pago.id');
$newIds = array_combine($ids, $tokuIds);
return array_map(fn($id) => ['product_id' => $id, 'toku_id' => $newIds[$id]], $ids);
}
public function save(array $data): bool
{
return $this->doSave($this->invoiceRepository, $data);
}
protected LoggerInterface $altLogger;
public function setAltLogger(LoggerInterface $altLogger): self
{
$this->altLogger = $altLogger;
return $this;
}
protected function mapParams(array $data): array
{
$paramsMap = [
'customer' => 'customer',
'product_id' => 'cuota_id',
'due_date' => 'fecha',
'subscription' => 'subscription',
'amount' => 'valor',
'is_paid' => 'isPagada',
'is_void' => 'isRechazada',
'link_payment' => null,
'metadata' => 'datosCuota',
'receipt_type' => null,
'id_receipt' => null,
'disable_automatic_payment' => null,
'currency_code' => 'CLF',
'invoice_external_id' => 'cuota_id'
];
$today = new DateTimeImmutable('now', new DateTimeZone('America/Santiago'));
$params = [];
foreach ($paramsMap as $key => $ref) {
if ($ref === null) {
continue;
}
if ($ref === 'fecha') {
$params[$key] = $data['cuota']->pago->fecha->format('Y-m-d');
continue;
}
if ($ref === 'valor') {
/*$valor = 0;
if ($data['cuota']->pago->fecha <= $today) {
$valor = $data['cuota']->pago->valor();
}
if ($valor === 0) {
$valor = $data['cuota']->pago->valor / $data['venta']->uf;
}
$params[$key] = $valor;*/
$params[$key] = $data['cuota']->pago->valor;
continue;
}
if ($ref === 'datosCuota') {
$params[$key] = $this->datosCuota($data['cuota']);
continue;
}
if ($ref === 'isPagada') {
$params[$key] = $data['cuota']->isPagada();
}
if ($ref === 'isRechazada') {
$params[$key] = $data['cuota']->isRechazada();
}
if ($ref === 'cuota_id') {
$params[$key] = $data['cuota']->id;
continue;
}
if (array_key_exists($ref, $data) and $data[$ref] !== '' and $data[$ref] !== null) {
$params[$key] = $data[$ref];
}
}
return $params;
}
protected function mapSave(array $data): array
{
$responseMap = [
'invoice_external_id' => 'cuota_id',
'id' => 'toku_id'
];
$mappedData = [];
foreach ($responseMap as $responseKey => $dataKey) {
if (isset($data[$responseKey])) {
$mappedData[$dataKey] = $data[$responseKey];
}
}
return $mappedData;
}
protected function datosCuota(Model\Venta\Cuota $cuota): array
{
return [
'Numero' => $cuota->numero,
'Monto_CLP' => $cuota->pago->valor
];
}
}

View File

@ -0,0 +1,302 @@
<?php
namespace Incoviba\Service\Venta\MediosPago\Toku;
use PDO;
use PDOException;
use Psr\Http\Client\ClientInterface;
use Incoviba\Common\Implement\Exception\EmptyResponse;
use Incoviba\Common\Implement\Exception\EmptyResult;
use Incoviba\Exception\ServiceAction\Read;
use Incoviba\Model\Venta;
use Incoviba\Repository;
use Incoviba\Service;
use Incoviba\Service\Venta\MediosPago\AbstractEndPoint;
class Subscription extends AbstractEndPoint
{
public function __construct(ClientInterface $client,
protected Repository\Venta\MediosPago\Toku\Subscription $subscriptionRepsitory,
protected Service\Venta $ventaService)
{
parent::__construct($client);
}
public function getById(string $id): array
{
return $this->doGetById([$this->subscriptionRepsitory, 'fetchByVenta'], $id, "No existe toku_id para Venta {$id}");
}
public function getByExternalId(string $id): array
{
return $this->doGetById([$this->subscriptionRepsitory, 'fetchByTokuId'], $id, "No existe Subscription para toku_id {$id}");
}
public function get(string $id): array
{
$request_uri = "/subscriptions/{$id}";
return $this->sendGet($request_uri, [200], [401, 404, 422]);
}
public function add(array $data, ?string $accountKey = null): bool
{
$request_uri = '/subscriptions';
return $this->sendAdd($request_uri, $data, [200, 201], [401, 404, 409, 422], $accountKey);
}
public function edit(string $id, array $data, ?string $accountKey = null): bool
{
$request_uri = "/subscriptions/{$id}";
return $this->sendEdit($request_uri, $data, [200], [401, 404, 409, 422], $accountKey);
}
public function delete(string $id): void
{
$request_uri = "/subscriptions/{$id}";
$this->sendDelete($request_uri, [204], [404, 409]);
}
public function reset(array $skip = []): array
{
try {
$tokuIds = $this->subscriptionRepsitory->fetchAllTokuIds();
$tokuIds = array_filter($tokuIds, function (string $tokuId) use ($skip) {
return !in_array($tokuId, $skip);
});
} catch (EmptyResult $exception) {
$this->logger->warning($exception);
return [];
}
foreach ($tokuIds as $tokuId) {
try {
$this->delete($tokuId);
$this->subscriptionRepsitory->removeByTokuId($tokuId);
} catch (EmptyResponse | PDOException $exception) {
$this->logger->warning($exception, ['subscription->toku_id' => $tokuId]);
}
}
return $tokuIds;
}
/**
* @return array
* @throws Read
*/
public function check(): array
{
$ventas = $this->ventaService->getAllWithCuotaPending();
$ids = array_column($ventas, 'id');
$existingSubscriptions = [];
try {
$existingSubscriptions = $this->subscriptionRepsitory->fetchByVentas($ids);
array_walk($existingSubscriptions, function(&$subscription) {
$subscription->venta = $this->ventaService->getById($subscription->venta->id);
});
} catch (EmptyResult) {}
if (count($existingSubscriptions) === 0) {
$missingVentas = $ventas;
} else {
$missingVentas = array_filter($ventas, function($venta) use ($existingSubscriptions) {
return !array_any($existingSubscriptions, fn($subscription) => $subscription->venta->id === $venta->id);
});
}
return compact('existingSubscriptions', 'missingVentas');
}
public function queue(int $venta_id): bool
{
try {
$venta = $this->ventaService->getById($venta_id);
} catch (Read $exception) {
$this->logger->warning($exception);
return false;
}
try {
$subscription = $this->subscriptionRepsitory->fetchByVenta($venta_id);
return false;
} catch (EmptyResult) {
return true;
}
}
/**
* @param array $idsData
* @return array
* @throws EmptyResult
* @throws EmptyResponse
*/
public function update(array $idsData): array
{
$tokuIds = array_column($idsData, 'toku_id');
$oldPids = array_column($idsData, 'product_id');
$placeholders = array_map(fn($id) => "id{$id}", array_keys($oldPids));
$placeholdersString = implode(', ', array_map(fn($id) => ":{$id}", $placeholders));
$query = $this->ventaService->getRepository()->getConnection()->getQueryBuilder()
->select('venta.id, CONCAT_WS("-", unidad.descripcion, CONCAT_WS("-", propietario.rut, propietario.dv)) AS old_pid')
->from('venta')
->joined('JOIN propietario ON propietario.rut = venta.propietario')
->joined('JOIN propiedad_unidad pu ON pu.propiedad = venta.propiedad')
->joined('JOIN unidad ON pu.unidad = unidad.id')
->having("old_pid IN ({$placeholdersString})");
$values = array_combine($placeholders, $oldPids);
try {
$statement = $this->ventaService->getRepository()->getConnection()->execute($query, $values);
$results = $statement->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $exception) {
$this->logger->error($exception->getMessage(), [
'query' => $query,
'values' => $values,
'ids' => $idsData,
'exception' => $exception]);
throw new EmptyResult($query, $exception);
}
$accountKeys = $this->getAccountKey(array_column($results, 'id'));
$newPids = [];
$keys = [];
foreach ($results as $result) {
$idx = array_search($result['old_pid'], $oldPids);
$newPids[$idx] = $result['id'];
if (array_key_exists($result['id'], $accountKeys)) {
$keys[$idx] = $accountKeys[$result['id']];
}
}
$output = [];
foreach ($tokuIds as $idx => $tokuId) {
if (!isset($newPids[$idx])) {
continue;
}
$data = [
'product_id' => $newPids[$idx],
];
try {
if (!$this->edit($tokuId, $data, array_key_exists($idx, $keys) ? $keys[$idx] : null)) {
$this->logger->error('Error while updating Toku', [
'toku_id' => $tokuId,
'old_pid' => $oldPids[$idx],
'product_id' => $newPids[$idx],
'account_key' => array_key_exists($idx, $keys) ? $keys[$idx] : null]);
$output[] = [
'toku_id' => $tokuId,
'old_pid' => $oldPids[$idx],
'product_id' => $newPids[$idx],
'account_key' => array_key_exists($idx, $keys) ? $keys[$idx] : null,
'error' => 'Error while updating Toku'
];
continue;
}
} catch (EmptyResponse $exception) {
$this->logger->error($exception->getMessage(), [
'toku_id' => $tokuId,
'old_pid' => $oldPids[$idx],
'product_id' => $newPids[$idx],
'account_key' => array_key_exists($idx, $keys) ? $keys[$idx] : null,
'exception' => $exception]);
$output[] = [
'toku_id' => $tokuId,
'old_pid' => $oldPids[$idx],
'product_id' => $newPids[$idx],
'account_key' => array_key_exists($idx, $keys) ? $keys[$idx] : null,
'error' => $exception->getMessage()
];
continue;
}
$output[] = [
'toku_id' => $tokuId,
'old_pid' => $oldPids[$idx],
'product_id' => $newPids[$idx],
'account_key' => array_key_exists($idx, $keys) ? $keys[$idx] : null
];
}
return $output;
}
public function save(array $data): bool
{
return $this->doSave($this->subscriptionRepsitory, $data);
}
protected function mapParams(array $data): array
{
$paramsMap = [
'customer' => 'customer',
'product_id' => 'product_id',
'pac_mandate_id' => null,
'is_recurring' => null,
'due_day' => null,
'amount' => 'pieValor',
'receipt_product_code' => null,
'metadata' => 'datosVenta'
];
$params = [];
foreach ($paramsMap as $key => $ref) {
if ($ref === null) {
continue;
}
if ($ref === 'pieValor' and array_key_exists('venta', $data)) {
$params[$key] = $data['venta']?->formaPago()?->pie?->valor ?? 0;
continue;
}
if ($ref === 'datosVenta' and array_key_exists('venta', $data)) {
$params[$key] = $this->datosVenta($data['venta']);
continue;
}
if (array_key_exists($ref, $data) and $data[$ref] !== '' and $data[$ref] !== null) {
$params[$key] = $data[$ref];
}
}
return $params;
}
protected function mapSave(array $data): array
{
$responseMap = [
'product_id' => 'venta_id',
'id' => 'toku_id'
];
$mappedData = [];
foreach ($responseMap as $responseKey => $dataKey) {
if (isset($data[$responseKey])) {
$mappedData[$dataKey] = $data[$responseKey];
}
}
return $mappedData;
}
protected function datosVenta(Venta $venta): array
{
return [
'Proyecto' => $venta->proyecto()->descripcion,
'Unidades' => $venta->propiedad()->summary()
];
}
/**
* @param array $ventaIds
* @return array
* @throws EmptyResult
*/
protected function getAccountKey(array $ventaIds): array
{
$placeholders = array_map(fn($id) => "id{$id}", array_keys($ventaIds));
$placeholdersString = implode(', ', array_map(fn($id) => ":{$id}", $placeholders));
$query = $this->ventaService->getRepository()->getConnection()->getQueryBuilder()
->select('account_key, venta.id AS venta_id')
->from('toku_accounts')
->joined('JOIN proyecto ON proyecto.inmobiliaria = toku_accounts.sociedad_rut')
->joined('JOIN proyecto_tipo_unidad ptu ON ptu.proyecto = proyecto.id')
->joined('JOIN unidad ON unidad.pt = ptu.id')
->joined('JOIN propiedad_unidad pu ON pu.unidad = unidad.id')
->joined('JOIN venta ON venta.propiedad = pu.propiedad')
->where("venta.id IN ({$placeholdersString}) AND toku_accounts.enabled = 1");
$values = array_combine($placeholders, $ventaIds);
try {
$statement = $this->ventaService->getRepository()->getConnection()->execute($query, $values);
$results = $statement->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $exception) {
$this->logger->error($exception->getMessage(), [
'query' => $query,
'values' => $values,
'exception' => $exception]);
throw new EmptyResult($query, $exception);
}
$keys = array_column($results, 'account_key');
$ids = array_column($results, 'venta_id');
return array_combine($ids, $keys);
}
}

View File

@ -1,27 +1,40 @@
<?php
namespace Incoviba\Service\Venta;
use Exception;
use DateTimeInterface;
use DateTimeImmutable;
use DateMalformedStringException;
use Incoviba\Common\Define;
use Incoviba\Exception\ServiceAction\Create;
use Incoviba\Exception\ServiceAction\Read;
use Incoviba\Exception\ServiceAction\Update;
use PDOException;
use Incoviba\Common\Ideal;
use Incoviba\Common\Implement\Exception\EmptyResult;
use Incoviba\Repository;
use Incoviba\Model;
use Incoviba\Service;
use Psr\Log\LoggerInterface;
class Pago
class Pago extends Ideal\Service\Repository
{
public function __construct(
LoggerInterface $logger,
protected Repository\Venta\Pago $pagoRepository,
protected Repository\Venta\EstadoPago $estadoPagoRepository,
protected Repository\Venta\TipoEstadoPago $tipoEstadoPagoRepository,
protected Service\UF $ufService,
protected Service\Valor $valorService
) {}
protected Service\Valor $valorService,
protected Service\Queue $queueService
)
{
parent::__construct($logger);
}
public function getRepository(): Define\Repository
{
return $this->pagoRepository;
}
public function depositar(Model\Venta\Pago $pago, DateTimeInterface $fecha): bool
{
@ -37,7 +50,7 @@ class Pago
$pago = $this->process($this->pagoRepository->fetchById($pago->id));
$this->getUF($pago);
return true;
} catch (PDOException) {
} catch (PDOException | EmptyResult) {
return false;
}
}
@ -89,13 +102,23 @@ class Pago
return false;
}
}
/**
* @param int|null $pago_id
* @return Model\Venta\Pago|null
* @throws Read
*/
public function getById(?int $pago_id): ?Model\Venta\Pago
{
if ($pago_id === null) {
return null;
}
$pago = $this->pagoRepository->fetchById($pago_id);
return $this->process($pago);
try {
$pago = $this->pagoRepository->fetchById($pago_id);
return $this->process($pago);
} catch (EmptyResult) {
throw new Read(__CLASS__);
}
}
public function getByVenta(int $venta_id): array
@ -130,6 +153,11 @@ class Pago
}
}
/**
* @param array $data
* @return Model\Venta\Pago
* @throws Create
*/
public function add(array $data): Model\Venta\Pago
{
if (array_key_exists('fecha', $data)) {
@ -139,24 +167,32 @@ class Pago
$fecha = new DateTimeImmutable();
}
$data['fecha'] = $fecha->format('Y-m-d');
if (!array_key_exists('uf', $data)) {
$data['uf'] = $this->ufService->get($fecha);
}
}
$data['valor'] = $this->valorService->toPesos($this->valorService->clean($data['valor']), $data['fecha']);
$filtered_data = $this->pagoRepository->filterData($data);
$pago = $this->pagoRepository->create($filtered_data);
$pago = $this->pagoRepository->save($pago);
try {
$pago = $this->pagoRepository->create($filtered_data);
$pago = $this->pagoRepository->save($pago);
} catch (PDOException $exception) {
throw new Create(__CLASS__, $exception);
}
if (!array_key_exists('uf', $data)) {
$this->getUFAsync($pago);
}
$tipoEstado = $this->tipoEstadoPagoRepository->fetchByDescripcion('no pagado');
$estado = $this->estadoPagoRepository->create([
'pago' => $pago->id,
'fecha' => $pago->fecha->format('Y-m-d'),
'estado' => $tipoEstado->id
]);
$estado = $this->estadoPagoRepository->save($estado);
try {
$estado = $this->estadoPagoRepository->create([
'pago' => $pago->id,
'fecha' => $pago->fecha->format('Y-m-d'),
'estado' => $tipoEstado->id
]);
$estado = $this->estadoPagoRepository->save($estado);
} catch (PDOException $exception) {
throw new Create(__CLASS__, $exception);
}
$pago->currentEstado = $estado;
return $pago;
}
@ -213,6 +249,21 @@ class Pago
return $this->process($this->pagoRepository->fetchById($pago->id));
}
public function updateUF(int $pago_id): bool
{
try {
$pago = $this->getById($pago_id);
} catch (Read) {
return false;
}
$uf = $this->ufService->get($pago->currentEstado->fecha);
try {
$this->pagoRepository->edit($pago, ['uf' => $uf]);
return true;
} catch (EmptyResult | PDOException) {
return false;
}
}
protected function process($pago): Model\Venta\Pago
{
@ -232,13 +283,27 @@ class Pago
return $uf;
}
if ($uf !== 0.0) {
$this->pagoRepository->edit($pago, ['uf' => $uf]);
try {
$this->pagoRepository->edit($pago, ['uf' => $uf]);
} catch (EmptyResult) {}
return $uf;
}
} elseif ($pago->uf === 0.0) {
$this->pagoRepository->edit($pago, ['uf' => null]);
try {
$this->pagoRepository->edit($pago, ['uf' => null]);
} catch (EmptyResult) {}
return null;
}
return $pago->uf;
}
protected function getUFAsync(Model\Venta\Pago $pago): void
{
$queueData = [
'type' => 'service',
'service' => __CLASS__,
'method' => 'updateUF',
'params' => ['pago_id' => $pago->id]
];
$this->queueService->push($queueData);
}
}

View File

@ -1,18 +1,22 @@
<?php
namespace Incoviba\Service\Venta;
use PDOException;
use DateTimeImmutable;
use DateMalformedStringException;
use Incoviba\Common\Implement\Exception\EmptyResult;
use Incoviba\Common\Implement\Repository\Factory;
use Incoviba\Exception\ServiceAction\Read;
use Incoviba\Repository;
use Incoviba\Exception\ServiceAction\{Create, Read};
use Incoviba\Model;
use Incoviba\Repository;
use Incoviba\Service\UF;
class Pie
{
public function __construct(
protected Repository\Venta\Pie $pieRepository,
protected Cuota $cuotaService,
protected Pago $pagoService
protected Pago $pagoService,
protected UF $ufService
) {}
public function getById(int $pie_id): Model\Venta\Pie
@ -28,12 +32,33 @@ class Pie
}
}
/**
* @param array $data
* @return Model\Venta\Pie
* @throws Create
*/
public function add(array $data): Model\Venta\Pie
{
$filteredData = $this->pieRepository->filterData($data);
$pie = $this->pieRepository->create($filteredData);
return $this->pieRepository->save($pie);
try {
$filteredData = $this->pieRepository->filterData($data);
if (!isset($filteredData['uf'])) {
try {
$date = new DateTimeImmutable($filteredData['fecha']);
$filteredData['uf'] = $this->ufService->get($date);
} catch (DateMalformedStringException) {}
}
$pie = $this->pieRepository->create($filteredData);
return $this->pieRepository->save($pie);
} catch (PDOException $exception) {
throw new Create(__CLASS__, $exception);
}
}
/**
* @param array $data
* @return Model\Venta\Cuota
* @throws Create
*/
public function addCuota(array $data): Model\Venta\Cuota
{
return $this->cuotaService->add($data);
@ -58,6 +83,12 @@ class Pie
if (isset($pie->asociado)) {
$pie->asociado = $this->getById($pie->asociado->id);
}
if (($pie->uf === null or $pie->uf === 0.0) and $pie->fecha <= new DateTimeImmutable()) {
$pie->uf = $this->ufService->get($pie->fecha);
try {
$this->pieRepository->edit($pie, ['uf' => $pie->uf]);
} catch (EmptyResult) {}
}
return $pie;
}
}

View File

@ -0,0 +1,464 @@
<?php
namespace Incoviba\Service\Venta;
use Incoviba\Controller\API\Ventas\Promotions;
use PDOException;
use Psr\Log\LoggerInterface;
use Incoviba\Common\Ideal;
use Incoviba\Common\Implement;
use Incoviba\Exception;
use Incoviba\Model;
use Incoviba\Repository;
use Incoviba\Service;
class Promotion extends Ideal\Service
{
public function __construct(LoggerInterface $logger,
protected Repository\Venta\Promotion $promotionRepository,
protected Repository\Proyecto $projectRepository,
protected Repository\Proyecto\Broker\Contract $contractRepository,
protected Repository\Proyecto\Broker $brokerRepository,
protected Repository\Proyecto\TipoUnidad $tipoUnidadRepository,
protected Repository\Proyecto\ProyectoTipoUnidad $proyectoTipoUnidadRepository,
protected Repository\Venta\Unidad $unidadRepository)
{
parent::__construct($logger);
}
public function getAll(null|string|array $order = null): array
{
try {
return array_map([$this, 'process'], $this->promotionRepository->fetchAll($order));
} catch (Implement\Exception\EmptyResult) {
return [];
}
}
/**
* @param int $promotion_id
* @return Model\Venta\Promotion
* @throws Exception\ServiceAction\Read
*/
public function getById(int $promotion_id): Model\Venta\Promotion
{
try {
return $this->process($this->promotionRepository->fetchById($promotion_id));
} catch (Implement\Exception\EmptyResult $exception) {
throw new Exception\ServiceAction\Read(__CLASS__, $exception);
}
}
/**
* @param int $contract_id
* @return array
*/
public function getByContract(int $contract_id): array
{
try {
return array_map([$this, 'process'], $this->promotionRepository->fetchByContract($contract_id));
} catch (Implement\Exception\EmptyResult) {
return [];
}
}
/**
* @param int $contract_id
* @return array
*/
public function getActiveByContract(int $contract_id): array
{
try {
return array_map([$this, 'process'], $this->promotionRepository->fetchActiveByContract($contract_id));
} catch (Implement\Exception\EmptyResult) {
return [];
}
}
/**
* @param array $data
* @return Model\Venta\Promotion
* @throws Exception\ServiceAction\Create
*/
public function add(array $data): Model\Venta\Promotion
{
try {
$filteredData = $this->promotionRepository->filterData($data);
$promotion = $this->promotionRepository->create($filteredData);
return $this->process($this->promotionRepository->save($promotion));
} catch (PDOException $exception) {
throw new Exception\ServiceAction\Create(__CLASS__, $exception);
}
}
/**
* @param Model\Venta\Promotion $promotion
* @param array $data
* @return Model\Venta\Promotion
* @throws Exception\ServiceAction\Update
*/
public function edit(Model\Venta\Promotion $promotion, array $data): Model\Venta\Promotion
{
try {
$filteredData = $this->promotionRepository->filterData($data);
$promotion = $this->promotionRepository->edit($promotion, $filteredData);
return $this->process($promotion);
} catch (PDOException | Implement\Exception\EmptyResult $exception) {
throw new Exception\ServiceAction\Update(__CLASS__, $exception);
}
}
/**
* @param int $promotion_id
* @return Model\Venta\Promotion
* @throws Exception\ServiceAction\Delete
*/
public function remove(int $promotion_id): Model\Venta\Promotion
{
try {
$promotion = $this->promotionRepository->fetchById($promotion_id);
$this->promotionRepository->remove($promotion);
return $promotion;
} catch (PDOException | Implement\Exception\EmptyResult $exception) {
throw new Exception\ServiceAction\Delete(__CLASS__, $exception);
}
}
/**
* @param int $promotion_id
* @param int $project_id
* @return Model\Venta\Promotion
* @throws Exception\ServiceAction\Create
*/
public function addProject(int $promotion_id, int $project_id): Model\Venta\Promotion
{
try {
$promotion = $this->promotionRepository->fetchById($promotion_id);
} catch (Implement\Exception\EmptyResult $exception) {
throw new Exception\ServiceAction\Create(__CLASS__, $exception);
}
try {
$project = $this->projectRepository->fetchById($project_id);
} catch (Implement\Exception\EmptyResult $exception) {
throw new Exception\ServiceAction\Create(__CLASS__, $exception);
}
if (in_array($project, $promotion->projects())) {
return $this->process($promotion);
}
try {
$this->promotionRepository->insertProjectForPromotion($promotion, $project->id);
return $this->process($promotion);
} catch (PDOException $exception) {
throw new Exception\ServiceAction\Create(__CLASS__, $exception);
}
}
/**
* @param int $promotion_id
* @param int $broker_rut
* @return Model\Venta\Promotion
* @throws Exception\ServiceAction\Create
*/
public function addBroker(int $promotion_id, int $broker_rut): Model\Venta\Promotion
{
try {
$promotion = $this->promotionRepository->fetchById($promotion_id);
} catch (Implement\Exception\EmptyResult $exception) {
throw new Exception\ServiceAction\Create(__CLASS__, $exception);
}
try {
$broker = $this->brokerRepository->fetchById($broker_rut);
} catch (Implement\Exception\EmptyResult $exception) {
throw new Exception\ServiceAction\Create(__CLASS__, $exception);
}
if (in_array($broker, $promotion->brokers())) {
return $this->process($promotion);
}
try {
$this->promotionRepository->insertBrokerForPromotion($promotion, $broker->rut);
return $this->process($promotion);
} catch (PDOException $exception) {
throw new Exception\ServiceAction\Create(__CLASS__, $exception);
}
}
/**
* @param int $promotion_id
* @param int $project_id
* @param int $unitType_id
* @return Model\Venta\Promotion
* @throws Exception\ServiceAction\Create
*/
public function addUnitType(int $promotion_id, int $project_id, int $unitType_id): Model\Venta\Promotion
{
try {
$promotion = $this->promotionRepository->fetchById($promotion_id);
} catch (Implement\Exception\EmptyResult $exception) {
throw new Exception\ServiceAction\Create(__CLASS__, $exception);
}
try {
$project = $this->projectRepository->fetchById($project_id);
} catch (Implement\Exception\EmptyResult $exception) {
throw new Exception\ServiceAction\Create(__CLASS__, $exception);
}
try {
$unitType = $this->tipoUnidadRepository->fetchById($unitType_id);
} catch (Implement\Exception\EmptyResult $exception) {
throw new Exception\ServiceAction\Create(__CLASS__, $exception);
}
if (in_array(['project' => $project, 'unitType' => $unitType], $promotion->unitTypes())) {
return $this->process($promotion);
}
try {
$this->promotionRepository->insertUnitTypeForPromotion($promotion, $project->id, $unitType->id);
return $this->process($promotion);
} catch (PDOException $exception) {
throw new Exception\ServiceAction\Create(__CLASS__, $exception);
}
}
/**
* @param int $promotion_id
* @param int $unit_line_id
* @return Model\Venta\Promotion
* @throws Exception\ServiceAction\Create
*/
public function addUnitLine(int $promotion_id, int $unit_line_id): Model\Venta\Promotion
{
try {
$promotion = $this->promotionRepository->fetchById($promotion_id);
} catch (Implement\Exception\EmptyResult $exception) {
throw new Exception\ServiceAction\Create(__CLASS__, $exception);
}
try {
$unitLine = $this->proyectoTipoUnidadRepository->fetchById($unit_line_id);
} catch (Implement\Exception\EmptyResult $exception) {
throw new Exception\ServiceAction\Create(__CLASS__, $exception);
}
if (in_array($unitLine, $promotion->unitLines())) {
return $this->process($promotion);
}
try {
$this->promotionRepository->insertUnitLineForPromotion($promotion, $unitLine->id);
return $this->process($promotion);
} catch (PDOException $exception) {
throw new Exception\ServiceAction\Create(__CLASS__, $exception);
}
}
/**
* @param int $promotion_id
* @param int $unit_id
* @return Model\Venta\Promotion
* @throws Exception\ServiceAction\Create
*/
public function addUnit(int $promotion_id, int $unit_id): Model\Venta\Promotion
{
try {
$promotion = $this->promotionRepository->fetchById($promotion_id);
} catch (Implement\Exception\EmptyResult $exception) {
throw new Exception\ServiceAction\Create(__CLASS__, $exception);
}
try {
$unit = $this->unidadRepository->fetchById($unit_id);
} catch (Implement\Exception\EmptyResult $exception) {
throw new Exception\ServiceAction\Create(__CLASS__, $exception);
}
if (in_array($unit, $promotion->units())) {
return $this->process($promotion);
}
try {
$this->promotionRepository->insertUnitForPromotion($promotion, $unit->id);
return $this->process($promotion);
} catch (PDOException $exception) {
throw new Exception\ServiceAction\Create(__CLASS__, $exception);
}
}
/**
* @param int $promotion_id
* @param int $project_id
* @return void
* @throws Exception\ServiceAction\Delete
*/
public function removeProject(int $promotion_id, int $project_id): void
{
try {
$promotion = $this->promotionRepository->fetchById($promotion_id);
} catch (Implement\Exception\EmptyResult $exception) {
throw new Exception\ServiceAction\Delete(__CLASS__, $exception);
}
try {
$project = $this->projectRepository->fetchById($project_id);
} catch (Implement\Exception\EmptyResult $exception) {
throw new Exception\ServiceAction\Delete(__CLASS__, $exception);
}
try {
$this->promotionRepository->removeProjectForPromotion($promotion, $project->id);
} catch (PDOException $exception) {
throw new Exception\ServiceAction\Delete(__CLASS__, $exception);
}
}
/**
* @param int $promotion_id
* @param int $broker_rut
* @return void
* @throws Exception\ServiceAction\Delete
*/
public function removeBroker(int $promotion_id, int $broker_rut): void
{
try {
$promotion = $this->promotionRepository->fetchById($promotion_id);
} catch (Implement\Exception\EmptyResult $exception) {
throw new Exception\ServiceAction\Delete(__CLASS__, $exception);
}
try {
$broker = $this->brokerRepository->fetchById($broker_rut);
} catch (Implement\Exception\EmptyResult $exception) {
throw new Exception\ServiceAction\Delete(__CLASS__, $exception);
}
try {
$this->promotionRepository->removeBrokerForPromotion($promotion, $broker->rut);
} catch (PDOException $exception) {
throw new Exception\ServiceAction\Delete(__CLASS__, $exception);
}
}
/**
* @param int $promotion_id
* @param int $project_id
* @param int $unitType_id
* @return void
* @throws Exception\ServiceAction\Delete
*/
public function removeUnitType(int $promotion_id, int $project_id, int $unitType_id): void
{
try {
$promotion = $this->promotionRepository->fetchById($promotion_id);
} catch (Implement\Exception\EmptyResult $exception) {
throw new Exception\ServiceAction\Delete(__CLASS__, $exception);
}
try {
$project = $this->projectRepository->fetchById($project_id);
} catch (Implement\Exception\EmptyResult $exception) {
throw new Exception\ServiceAction\Delete(__CLASS__, $exception);
}
try {
$unitType = $this->tipoUnidadRepository->fetchById($unitType_id);
} catch (Implement\Exception\EmptyResult $exception) {
throw new Exception\ServiceAction\Delete(__CLASS__, $exception);
}
try {
$this->promotionRepository->removeUnitTypeForPromotion($promotion, $project->id, $unitType->id);
} catch (PDOException $exception) {
throw new Exception\ServiceAction\Delete(__CLASS__, $exception);
}
}
/**
* @param int $promotion_id
* @param int $unit_line_id
* @return void
* @throws Exception\ServiceAction\Delete
*/
public function removeUnitLine(int $promotion_id, int $unit_line_id): void
{
try {
$promotion = $this->promotionRepository->fetchById($promotion_id);
} catch (Implement\Exception\EmptyResult $exception) {
throw new Exception\ServiceAction\Delete(__CLASS__, $exception);
}
try {
$unitLine = $this->proyectoTipoUnidadRepository->fetchById($unit_line_id);
} catch (Implement\Exception\EmptyResult $exception) {
throw new Exception\ServiceAction\Delete(__CLASS__, $exception);
}
try {
$this->promotionRepository->removeUnitLineForPromotion($promotion, $unitLine->id);
} catch (PDOException $exception) {
throw new Exception\ServiceAction\Delete(__CLASS__, $exception);
}
}
/**
* @param int $promotion_id
* @param int $unit_id
* @return void
* @throws Exception\ServiceAction\Delete
*/
public function removeUnit(int $promotion_id, int $unit_id): void
{
try {
$promotion = $this->promotionRepository->fetchById($promotion_id);
} catch (Implement\Exception\EmptyResult $exception) {
throw new Exception\ServiceAction\Delete(__CLASS__, $exception);
}
try {
$unit = $this->unidadRepository->fetchById($unit_id);
} catch (Implement\Exception\EmptyResult $exception) {
throw new Exception\ServiceAction\Delete(__CLASS__, $exception);
}
try {
$this->promotionRepository->removeUnitForPromotion($promotion, $unit->id);
} catch (PDOException $exception) {
throw new Exception\ServiceAction\Delete(__CLASS__, $exception);
}
}
protected function process(Model\Venta\Promotion $model): Model\Venta\Promotion
{
$model->addFactory('projects', (new Implement\Repository\Factory())
->setCallable(function($promotion_id) {
try {
return $this->projectRepository->fetchByPromotion($promotion_id);
} catch (Implement\Exception\EmptyResult) {
return [];
}
})
->setArgs(['promotion_id' => $model->id]));
$model->addFactory('brokers', (new Implement\Repository\Factory())
->setCallable(function($promotion_id) {
try {
return $this->brokerRepository->fetchByPromotion($promotion_id);
} catch (Implement\Exception\EmptyResult) {
return [];
}
})
->setArgs(['promotion_id' => $model->id]));
$model->addFactory('unitTypes', (new Implement\Repository\Factory())
->setCallable(function($promotion_id) {
try {
return array_map(function(array $ids) {
return [
'project' => $this->projectRepository->fetchById($ids['project_id']),
'unitType' => $this->tipoUnidadRepository->fetchById($ids['id'])
];
}, $this->tipoUnidadRepository->fetchByPromotion($promotion_id));
} catch (Implement\Exception\EmptyResult) {
return [];
}
})
->setArgs(['promotion_id' => $model->id])
);
$model->addFactory('unitLines', (new Implement\Repository\Factory())
->setCallable(function($promotion_id) {
try {
return $this->proyectoTipoUnidadRepository->fetchByPromotion($promotion_id);
} catch (Implement\Exception\EmptyResult) {
return [];
}
})
->setArgs(['promotion_id' => $model->id])
);
$model->addFactory('units', (new Implement\Repository\Factory())
->setCallable(function($promotion_id) {
try {
return $this->unidadRepository->fetchByPromotion($promotion_id);
} catch (Implement\Exception\EmptyResult) {
return [];
}
})
->setArgs(['promotion_id' => $model->id]));
return $model;
}
}

View File

@ -7,6 +7,7 @@ use Psr\Log\LoggerInterface;
use Incoviba\Common\Define;
use Incoviba\Common\Ideal\Service;
use Incoviba\Common\Implement\Exception\EmptyResult;
use Incoviba\Exception;
use Incoviba\Repository;
use Incoviba\Model;
@ -21,11 +22,48 @@ class Propiedad extends Service
parent::__construct($logger);
}
/**
* @param int $propiedad_id
* @return Model\Venta\Propiedad
* @throws Exception\ServiceAction\Read
*/
public function getById(int $propiedad_id): Model\Venta\Propiedad
{
try {
return $this->process($this->propiedadRepository->fetchById($propiedad_id));
} catch (EmptyResult $exception) {
throw new Exception\ServiceAction\Read(__CLASS__, $exception);
}
}
/**
* @param int $propiedad_id
* @return array
* @throws Exception\ServiceAction\Read
*/
public function getArrayById(int $propiedad_id): array
{
try {
return $this->propiedadRepository->fetchArrayById($propiedad_id);
} catch (EmptyResult $exception) {
throw new Exception\ServiceAction\Read(__CLASS__, $exception);
}
}
/**
* @param array $ids
* @return Model\Venta\Propiedad
* @throws Exception\ServiceAction\Create
*/
public function addPropiedad(array $ids): Model\Venta\Propiedad
{
$unidades = [];
foreach ($ids as $unidad_id) {
$unidades []= $this->unidadRepository->fetchById($unidad_id);
try {
$unidades []= $this->unidadRepository->fetchById($unidad_id);
} catch (EmptyResult $exception) {
$this->logger->warning($exception);
}
}
usort($unidades, function(Model\Venta\Unidad $a, Model\Venta\Unidad $b) {
$t = $a->proyectoTipoUnidad->tipoUnidad->orden - $b->proyectoTipoUnidad->tipoUnidad->orden;
@ -42,13 +80,25 @@ class Propiedad extends Service
'unidad_principal' => $unidades[0]->id,
'estado' => 1
]);
$propiedad = $this->propiedadRepository->save($propiedad);
try {
$propiedad = $this->propiedadRepository->save($propiedad);
} catch (PDOException $exception) {
throw new Exception\ServiceAction\Create(__CLASS__, $exception);
}
}
$this->addUnidades($propiedad, $unidades);
$this->cleanUpUnidades($propiedad, $unidades);
try {
$this->cleanUpUnidades($propiedad, $unidades);
} catch (Exception\ServiceAction\Read|Exception\ServiceAction\Delete) {}
return $propiedad;
}
/**
* @param Model\Venta\Propiedad $propiedad
* @param array $unidades
* @return void
*/
protected function addUnidades(Model\Venta\Propiedad $propiedad, array $unidades): void
{
$query = "SELECT 1 FROM `propiedad_unidad` WHERE `propiedad` = ? AND `unidad` = ?";
@ -63,19 +113,33 @@ class Propiedad extends Service
throw new EmptyResult($query);
}
} catch (PDOException|EmptyResult) {
$insert->execute([$propiedad->id, $unidad->id, ($ix === 0) ? 1 : 0]);
try {
$insert->execute([$propiedad->id, $unidad->id, ($ix === 0) ? 1 : 0]);
} catch (PDOException) {}
}
}
}
/**
* @param Model\Venta\Propiedad $propiedad
* @param array $unidades
* @return void
* @throws Exception\ServiceAction\Delete
* @throws Exception\ServiceAction\Read
*/
protected function cleanUpUnidades(Model\Venta\Propiedad $propiedad, array $unidades): void
{
$query = "SELECT `unidad` FROM `propiedad_unidad` WHERE `propiedad` = ?";
$statement = $this->connection->prepare($query);
$statement->execute([$propiedad->id]);
$results = $statement->fetchAll(PDO::FETCH_ASSOC);
try {
$statement = $this->connection->prepare($query);
$statement->execute([$propiedad->id]);
$results = $statement->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $exception) {
throw new Exception\ServiceAction\Read(__CLASS__, $exception);
}
if (!$results) {
return;
throw new Exception\ServiceAction\Read(__CLASS__);
}
$all_ids = array_map(function($row) {return $row['unidad'];}, $results);
@ -85,9 +149,19 @@ class Propiedad extends Service
return;
}
$query = "DELETE FROM `propiedad_unidad` WHERE `propiedad` = ? AND `unidad` = ?";
$statement = $this->connection->prepare($query);
try {
$statement = $this->connection->prepare($query);
} catch (PDOException $exception) {
throw new Exception\ServiceAction\Delete(__CLASS__, $exception);
}
foreach ($diff as $id) {
$statement->execute([$propiedad->id, $id]);
try {
$statement->execute([$propiedad->id, $id]);
} catch (PDOException) {}
}
}
protected function process(Model\Venta\Propiedad $propiedad): Model\Venta\Propiedad
{
return $propiedad;
}
}

View File

@ -1,33 +1,70 @@
<?php
namespace Incoviba\Service\Venta;
use PDOException;
use Incoviba\Common\Implement\Exception\EmptyResult;
use Incoviba\Exception\ServiceAction\Read;
use Incoviba\Exception\ServiceAction\{Create, Read, Update};
use Incoviba\Model;
use Incoviba\Repository;
use Incoviba\Service;
use Incoviba\Model;
class PropiedadUnidad
{
public function __construct(protected Repository\Venta\PropiedadUnidad $propiedadUnidadRepository,
protected Repository\Venta\Unidad $unidadRepository,
protected Precio $precioService) {}
protected Precio $precioService, protected Service\Valor $valorService) {}
/**
* @param int $unidad_id
* @return Model\Venta\PropiedadUnidad
* @throws Read
*/
public function getById(int $unidad_id): Model\Venta\PropiedadUnidad
{
return $this->process($this->propiedadUnidadRepository->fetchById($unidad_id));
try {
return $this->process($this->propiedadUnidadRepository->fetchById($unidad_id));
} catch (EmptyResult $exception) {
throw new Read(__CLASS__, $exception);
}
}
public function getByVenta(int $venta_id): array
{
return array_map([$this, 'process'], $this->propiedadUnidadRepository->fetchByVenta($venta_id));
try {
return array_map([$this, 'process'], $this->propiedadUnidadRepository->fetchByVenta($venta_id));
} catch (EmptyResult) {
return [];
}
}
public function getByPropiedad(int $propiedad_id): array
{
return array_map([$this, 'process'], $this->propiedadUnidadRepository->fetchByPropiedad($propiedad_id));
try {
return array_map([$this, 'process'], $this->propiedadUnidadRepository->fetchByPropiedad($propiedad_id));
} catch (EmptyResult) {
return [];
}
}
public function getArrayByPropiedad(int $propiedad_id): array
{
try {
return $this->propiedadUnidadRepository->fetchArrayByPropiedad($propiedad_id);
} catch (EmptyResult) {
return [];
}
}
/**
* @param array $data
* @return Model\Venta\PropiedadUnidad
* @throws Create
* @throws Read
*/
public function add(array $data): Model\Venta\PropiedadUnidad
{
$unidad = $this->unidadRepository->fetchById($data['unidad']);
try {
$unidad = $this->unidadRepository->fetchById($data['unidad']);
} catch (EmptyResult $exception) {
throw new Read(__CLASS__, $exception);
}
$temp = json_decode(json_encode($unidad), JSON_OBJECT_AS_ARRAY);
$columnMap = [
'proyecto_tipo_unidad' => 'pt'
@ -40,11 +77,31 @@ class PropiedadUnidad
$temp['propiedad'] = $data['propiedad'];
$temp['valor'] = $data['valor'];
$pu = $this->propiedadUnidadRepository->create($temp);
return $this->process($this->propiedadUnidadRepository->save($pu));
try {
return $this->process($this->propiedadUnidadRepository->save($pu));
} catch (PDOException $exception) {
throw new Create(__CLASS__, $exception);
}
}
/**
* @param Model\Venta\PropiedadUnidad $propiedadUnidad
* @param array $data
* @return Model\Venta\PropiedadUnidad
* @throws EmptyResult
* @throws Update
*/
public function edit(Model\Venta\PropiedadUnidad $propiedadUnidad, array $data): Model\Venta\PropiedadUnidad
{
return $this->process($this->propiedadUnidadRepository->edit($propiedadUnidad, $data));
try {
$filteredData = $this->propiedadUnidadRepository->filterData($data);
if (array_key_exists('valor', $filteredData)) {
$filteredData['valor'] = $this->valorService->clean($filteredData['valor']);
}
return $this->process($this->propiedadUnidadRepository->edit($propiedadUnidad, $filteredData));
} catch (PDOException $exception) {
throw new Update(__CLASS__, $exception);
}
}
protected function process(Model\Venta\PropiedadUnidad $unidad): Model\Venta\PropiedadUnidad
@ -52,7 +109,7 @@ class PropiedadUnidad
try {
$unidad->precios = $this->precioService->getByUnidad($unidad->id);
$unidad->currentPrecio = $this->precioService->getVigenteByUnidad($unidad->id);
if ($unidad->valor === null or $unidad->valor === 0) {
if ($unidad->valor === null) {
$unidad->valor = $unidad->currentPrecio->valor;
}
} catch (Read) {}

View File

@ -3,8 +3,12 @@ namespace Incoviba\Service\Venta;
use Incoviba\Common\Ideal\Service;
use Incoviba\Common\Implement\Exception\EmptyResult;
use Incoviba\Exception\ServiceAction\Create;
use Incoviba\Exception\ServiceAction\Read;
use Incoviba\Exception\ServiceAction\Update;
use Incoviba\Repository;
use Incoviba\Model;
use PDOException;
use Psr\Log\LoggerInterface;
class Propietario extends Service
@ -17,10 +21,27 @@ class Propietario extends Service
parent::__construct($logger);
}
/**
* @param int $rut
* @return Model\Venta\Propietario
* @throws Read
*/
public function getByRut(int $rut): Model\Venta\Propietario
{
return $this->propietarioRepository->fetchById($rut);
try {
return $this->propietarioRepository->fetchById($rut);
} catch (EmptyResult $exception) {
throw new Read(__CLASS__, $exception);
}
}
/**
* @param Model\Venta\Propietario $propietario
* @param array $data
* @return Model\Venta\Propietario
* @throws Create
* @throws Update
*/
public function edit(Model\Venta\Propietario $propietario, array $data): Model\Venta\Propietario
{
if (isset($data['calle']) or isset($data['numero']) or isset($data['extra']) or isset($data['comuna'])) {
@ -28,8 +49,18 @@ class Propietario extends Service
$data['direccion'] = $direccion->id;
}
$filteredData = $this->propietarioRepository->filterData($data);
return $this->propietarioRepository->edit($propietario, $filteredData);
try {
return $this->propietarioRepository->edit($propietario, $filteredData);
} catch (PDOException | EmptyResult $exception) {
throw new Update(__CLASS__, $exception);
}
}
/**
* @param array $data
* @return Model\Venta\Propietario
* @throws Create
*/
public function addPropietario(array $data): Model\Venta\Propietario
{
$direccion = $this->addDireccion($data);
@ -42,14 +73,16 @@ class Propietario extends Service
$data['dv'] = $dv;
}
$fields = array_fill_keys([
$fields = array_flip([
'rut',
'dv',
'nombres',
'apellido_paterno',
'apellido_materno',
'direccion'
], 0);
'direccion',
'email',
'telefono'
]);
$filtered_data = array_intersect_key($data, $fields);
try {
@ -60,11 +93,22 @@ class Propietario extends Service
}
$propietario = $this->propietarioRepository->edit($propietario, $edits);
} catch (EmptyResult) {
$propietario = $this->propietarioRepository->create($filtered_data);
$propietario = $this->propietarioRepository->save($propietario);
try {
$propietario = $this->propietarioRepository->create($filtered_data);
$propietario = $this->propietarioRepository->save($propietario);
} catch (PDOException $exception) {
throw new Create(__CLASS__, $exception);
}
}
return $propietario;
}
/**
* @param array $data
* @return Model\Venta\Propietario
* @throws Create
* @throws Update
*/
public function addSociedad(array $data): Model\Venta\Propietario
{
$direccion = $this->addDireccion($data);
@ -74,12 +118,12 @@ class Propietario extends Service
$data['rut'] = explode('-', $data['rut'])[0];
}
$fields = array_fill_keys([
$fields = array_flip([
'rut',
'razon_social',
'direccion',
'representante'
], 0);
]);
$filtered_data = array_intersect_key($data, $fields);
$mapped_data = array_combine([
'rut',
@ -97,27 +141,45 @@ class Propietario extends Service
if ($sociedad->contacto->rut !== $mapped_data['representante']) {
$edits['representante'] = $mapped_data['representante'];
}
$sociedad = $this->propietarioRepository->edit($sociedad, $edits);
try {
$sociedad = $this->propietarioRepository->edit($sociedad, $edits);
} catch (PDOException $exception) {
throw new Update(__CLASS__, $exception);
}
} catch (EmptyResult) {
$sociedad = $this->propietarioRepository->create($mapped_data);
$sociedad = $this->propietarioRepository->save($sociedad);
try {
$sociedad = $this->propietarioRepository->create($mapped_data);
$sociedad = $this->propietarioRepository->save($sociedad);
} catch (PDOException $exception) {
throw new Create(__CLASS__, $exception);
}
}
return $sociedad;
}
/**
* @param array $data
* @return Model\Direccion
* @throws Create
*/
protected function addDireccion(array $data): Model\Direccion
{
$fields = array_fill_keys([
$fields = array_flip([
'calle',
'numero',
'extra',
'comuna'
], 0);
]);
$filtered_data = array_intersect_key($data, $fields);
try {
$direccion = $this->direccionRepository->fetchByCalleAndNumeroAndExtraAndComuna($filtered_data['calle'], $filtered_data['numero'], $filtered_data['extra'], $filtered_data['comuna']);
} catch (EmptyResult) {
$direccion = $this->direccionRepository->create($filtered_data);
$direccion = $this->direccionRepository->save($direccion);
try {
$direccion = $this->direccionRepository->create($filtered_data);
$direccion = $this->direccionRepository->save($direccion);
} catch (PDOException $exception) {
throw new Create(__CLASS__, $exception);
}
}
return $direccion;
}

View File

@ -0,0 +1,81 @@
<?php
namespace Incoviba\Service\Venta;
use DateTimeImmutable;
use DateMalformedStringException;
use PDOException;
use Psr\Log\LoggerInterface;
use Incoviba\Common\Define;
use Incoviba\Common\Ideal;
use Incoviba\Common\Implement;
use Incoviba\Exception\ServiceAction;
use Incoviba\Model;
use Incoviba\Repository;
class Reservation extends Ideal\Service\API
{
public function __construct(LoggerInterface $logger, protected Repository\Venta\Reservation $reservationRepository)
{
parent::__construct($logger);
}
public function getAll(null|string|array $order = null): array
{
try {
return $this->reservationRepository->fetchAll($order);
} catch (Implement\Exception\EmptyResult) {
return [];
}
}
public function get(int $id): Model\Venta\Reservation
{
try {
return $this->process($this->reservationRepository->fetchById($id));
} catch (Implement\Exception\EmptyResult $exception) {
throw new ServiceAction\Read(__CLASS__, $exception);
}
}
public function add(array $data): Model\Venta\Reservation
{
try {
$date = new DateTimeImmutable();
try {
$date = new DateTimeImmutable($data['date']);
} catch (DateMalformedStringException) {}
return $this->process($this->reservationRepository->fetchByBuyerAndDate($data['buyer_rut'], $date));
} catch (Implement\Exception\EmptyResult) {}
try {
$reservationData = $this->reservationRepository->filterData($data);
$reservation = $this->reservationRepository->create($reservationData);
$this->reservationRepository->save($reservation);
return $this->process($reservation);
} catch (PDOException $exception) {
throw new ServiceAction\Create(__CLASS__, $exception);
}
}
public function edit(Define\Model $model, array $new_data): Model\Venta\Reservation
{
try {
return $this->process($this->reservationRepository->edit($model, $new_data));
} catch (PDOException | Implement\Exception\EmptyResult $exception) {
throw new ServiceAction\Update(__CLASS__, $exception);
}
}
public function delete(int $id): Model\Venta\Reservation
{
try {
$reservation = $this->reservationRepository->fetchById($id);
$this->reservationRepository->remove($reservation);
return $reservation;
} catch (PDOException | Implement\Exception\EmptyResult $exception) {
throw new ServiceAction\Delete(__CLASS__, $exception);
}
}
protected function process(Define\Model $model): Model\Venta\Reservation
{
return $model;
}
}

View File

@ -2,7 +2,10 @@
namespace Incoviba\Service\Venta;
use DateTimeImmutable;
use DateMalformedStringException;
use PDOException;
use Incoviba\Common\Implement\Exception\EmptyResult;
use Incoviba\Exception\ServiceAction\Create;
use Incoviba\Exception\ServiceAction\Read;
use Incoviba\Repository;
use Incoviba\Model;
@ -35,16 +38,29 @@ class Subsidio
}
/**
* @throws Exception
* @param array $data
* @return Model\Venta\Subsidio
* @throws Create
*/
public function add(array $data): Model\Venta\Subsidio
{
$fecha = new DateTimeImmutable($data['fecha']);
$fecha = new DateTimeImmutable();
try {
$fecha = new DateTimeImmutable($data['fecha']);
} catch (DateMalformedStringException) {}
$uf = $data['uf'] ?? $this->moneyService->getUF($fecha);
$tipoPago = $this->tipoPagoRepository->fetchByDescripcion('vale vista');
try {
$tipoPago = $this->tipoPagoRepository->fetchByDescripcion('vale vista');
} catch (EmptyResult $exception) {
throw new Create(__CLASS__, $exception);
}
$ahorro = $this->pagoService->add(['fecha' => $fecha->format('Y-m-d'), 'valor' => $this->valorService->clean($data['ahorro']) * $uf, 'uf' => $uf, 'tipo' => $tipoPago->id]);
$subsidioPago = $this->pagoService->add(['fecha' => $fecha->format('Y-m-d'), 'valor' => $this->valorService->clean($data['subsidio']) * $uf, 'uf' => $uf, 'tipo' => $tipoPago->id]);
$subsidio = $this->subsidioRepository->create(['pago' => $ahorro->id, 'subsidio' => $subsidioPago->id]);
return $this->subsidioRepository->save($subsidio);
try {
return $this->subsidioRepository->save($subsidio);
} catch (PDOException $exception) {
throw new Create(__CLASS__, $exception);
}
}
}

View File

@ -1,11 +1,11 @@
<?php
namespace Incoviba\Service\Venta;
use Incoviba\Exception\ServiceAction\Read;
use PDOException;
use Incoviba\Common\Implement\Repository\Factory;
use Incoviba\Common\Implement\Exception\EmptyResult;
use Incoviba\Exception\ServiceAction\Read;
use Incoviba\Repository;
use Incoviba\Service;
use Incoviba\Model;
class Unidad
@ -16,9 +16,18 @@ class Unidad
protected Precio $precioService
) {}
/**
* @param int $unidad_id
* @return Model\Venta\Unidad
* @throws Read
*/
public function getById(int $unidad_id): Model\Venta\Unidad
{
return $this->process($this->unidadRepository->fetchById($unidad_id));
try {
return $this->process($this->unidadRepository->fetchById($unidad_id));
} catch (EmptyResult $exception) {
throw new Read(__CLASS__, $exception);
}
}
public function getByVenta(int $venta_id): array
{
@ -32,6 +41,10 @@ class Unidad
{
return array_map([$this, 'process'], $this->unidadRepository->fetchByCierre($cierre_id));
}
public function getByProyecto(int $proyecto_id): array
{
return array_map([$this, 'process'], $this->unidadRepository->fetchByProyecto($proyecto_id));
}
public function getDisponiblesByProyecto(int $proyecto_id): array
{
return $this->unidadRepository->fetchDisponiblesByProyecto($proyecto_id);
@ -63,6 +76,10 @@ class Unidad
$unidad->precios = $this->precioService->getByUnidad($unidad->id);
$unidad->currentPrecio = $this->precioService->getVigenteByUnidad($unidad->id);
} catch (Read) {}
$unidad->addFactory('sold', (new Factory())
->setCallable([$this->unidadRepository, 'fetchSoldByUnidad'])
->setArgs(['unidad_id' => $unidad->id])
);
return $unidad;
}
}