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

@ -280,7 +280,7 @@ class Cartola extends Service
try {
return $this->movimientoRepository->save($movimiento);
} catch (PDOException $exception) {
$this->logger->critical(var_export($data,true));
$this->logger->critical($exception, ['data' => $data]);
throw $exception;
}
}

View File

@ -14,7 +14,7 @@ class Mes extends Banco
}
try {
$reader = PhpSpreadsheet\IOFactory::createReader('Xlsx');
} catch (PhpSpreadsheet\Reader\Exception $exception) {
} catch (PhpSpreadsheet\Reader\Exception) {
return false;
}
$xlsx = $reader->load($filename);

View File

@ -1,7 +1,6 @@
<?php
namespace Incoviba\Service\Contabilidad\Informe\Tesoreria\Input\Excel;
use stdClass;
use PhpOffice\PhpSpreadsheet;
class DAPyFFMM extends Sheet

View File

@ -0,0 +1,52 @@
<?php
namespace Incoviba\Service;
use Psr\Log\LoggerInterface;
use Incoviba\Common\Ideal;
class External extends Ideal\Service
{
public function __construct(LoggerInterface $logger, protected Queue $queueService)
{
parent::__construct($logger);
}
protected array $externalServices;
public function register($service): self
{
if (!isset($this->externalServices)) {
$this->externalServices = [$service];
return $this;
}
if (!in_array($service, $this->externalServices)) {
$this->externalServices []= $service;
}
return $this;
}
public function check(bool $update = false): bool
{
$errors = [];
foreach ($this->externalServices as $externalService) {
if (!method_exists($externalService, 'check')) {
continue;
}
$queueData = [
'type' => 'checkExternal',
'service' => get_class($externalService),
'action' => 'check',
];
if ($update) {
$queueData['args'] = ['update' => true];
}
if (!$this->queueService->enqueue($queueData)) {
$errors []= get_class($externalService);
}
}
if (count($errors) > 0) {
$this->logger->error('Could not enqueue check of external services', ['services' => $errors]);
return false;
}
return true;
}
}

16
app/src/Service/HMAC.php Normal file
View File

@ -0,0 +1,16 @@
<?php
namespace Incoviba\Service;
use Incoviba\Common\Ideal;
class HMAC extends Ideal\Service
{
public function validate(string $timestamp, string $requestSignature, string $requestId, string $secret): bool
{
$message = "{$timestamp}.{$requestId}";
$encodedSecret = mb_convert_encoding($secret, 'UTF-8');
$encodedMessage = mb_convert_encoding($message, 'UTF-8');
$hmacObject = hash_hmac('sha256', $encodedMessage, $encodedSecret);
return hash_equals($hmacObject, $requestSignature);
}
}

135
app/src/Service/Job.php Normal file
View File

@ -0,0 +1,135 @@
<?php
namespace Incoviba\Service;
use DateInvalidTimeZoneException;
use DateMalformedStringException;
use DateTimeImmutable;
use DateTimeZone;
use Psr\Log\LoggerInterface;
use Incoviba\Common\Ideal;
use Incoviba\Exception\MQTT as MQTTException;
use Incoviba\Exception\ServiceAction\{Create, Delete, Read, Update};
use Incoviba\Model;
use Incoviba\Repository;
class Job extends Ideal\Service
{
public function __construct(LoggerInterface $logger, protected MQTT $mqttService,
protected Repository\Job $jobRepository)
{
parent::__construct($logger);
}
public function isPending(): bool
{
try {
return $this->mqttService->exists();
} catch (MQTTException $exception) {
$this->logger->error($exception->getMessage(), ['exception' => $exception]);
return false;
}
}
/**
* @return Model\Job
* @throws Read
*/
public function get(): Model\Job
{
try {
return $this->load(json_decode($this->mqttService->get(), true));
} catch (MQTTException $exception) {
$this->logger->error($exception->getMessage(), ['exception' => $exception]);
throw new Read(__CLASS__, $exception);
}
}
/**
* @param array $configuration
* @return Model\Job
* @throws Create
*/
public function add(array $configuration): Model\Job
{
try {
$now = (new DateTimeImmutable('now', new DateTimeZone($_ENV['TZ'] ?? 'America/Santiago')));
} catch (DateMalformedStringException | DateInvalidTimeZoneException $exception) {
$this->logger->warning($exception->getMessage(), ['exception' => $exception]);
$now = new DateTimeImmutable();
}
$data = [
'id' => $now->format('Uu'),
'configuration' => $configuration,
'executed' => false,
'created_at' => $now->format('Y-m-d H:i:s'),
'updated_at' => null,
'retries' => 0
];
try {
$this->mqttService->set(json_encode($data));
} catch (MQTTException $exception) {
throw new Create(__CLASS__, $exception);
}
return $this->load($data);
}
/**
* @param Model\Job $job
* @return void
* @throws Update
*/
public function update(Model\Job $job): void
{
try {
$now = (new DateTimeImmutable('now', new DateTimeZone($_ENV['TZ'] ?? 'America/Santiago')));
} catch (DateMalformedStringException | DateInvalidTimeZoneException) {
$now = new DateTimeImmutable();
}
$data = json_decode(json_encode($job), true);
$data['updated_at'] = $now->format('Y-m-d H:i:s');
try {
$this->mqttService->update(json_encode($data));
} catch (MQTTException $exception) {
throw new Update(__CLASS__, $exception);
}
}
/**
* @param Model\Job $job
* @throws Delete
*/
public function remove(Model\Job $job): void
{
try {
$this->mqttService->remove();
} catch (MQTTException $exception) {
throw new Delete(__CLASS__, $exception);
}
}
/**
* @param Model\Job $job
* @return bool
*/
public function execute(Model\Job $job): bool
{
try {
$this->mqttService->remove();
return true;
} catch (MQTTException $exception) {
$this->logger->error($exception->getMessage(), ['exception' => $exception]);
return false;
}
}
protected function load(array $data, ?int $id = null): Model\Job
{
$job = new Model\Job();
$job->id = $id ?? $data['id'] ?? null;
$job->configuration = $data['configuration'] ?? [];
$job->executed = $data['executed'] ?? false;
$job->retries = $data['retries'] ?? 0;
return $job;
}
}

View File

@ -6,6 +6,10 @@ use DateTimeImmutable;
use DateInterval;
use Exception;
use PDOException;
use Incoviba\Exception\ServiceAction\Create;
use Incoviba\Exception\ServiceAction\Delete;
use Incoviba\Exception\ServiceAction\Read;
use Incoviba\Exception\ServiceAction\Update;
use Incoviba\Common\Implement\Exception\EmptyResult;
use Incoviba\Repository;
use Incoviba\Model;
@ -40,6 +44,11 @@ class Login
} catch (PDOException|EmptyResult) {}
return false;
}
/**
* @return Model\User
* @throws Exception
*/
public function getUser(): Model\User
{
$login = $this->repository->fetchActiveBySelector($this->selector);
@ -57,20 +66,90 @@ class Login
return $this->cookie_separator;
}
/**
* @param array $data
* @return Model\User
* @throws Create
* @throws Read
*/
public function addUser(array $data): Model\User
{
try {
return $this->userRepository->fetchByName($data['name']);
} catch (EmptyResult) {
list($passphrase, $encrypted) = $this->splitPassword($data['password']);
$password = $this->cryptoJs_aes_decrypt($encrypted, $passphrase);
try {
$password = $this->cryptoJs_aes_decrypt($encrypted, $passphrase);
} catch (Exception $exception) {
throw new Read(__CLASS__, $exception);
}
$password = password_hash($password, PASSWORD_DEFAULT);
$user = $this->userRepository->create([
'name' => $data['name'],
'password' => $password,
'enabled' => $data['enabled'] ?? 1
]);
return $this->userRepository->save($user);
try {
return $this->userRepository->save($user);
} catch (PDOException $exception) {
throw new Create(__CLASS__, $exception);
}
}
}
/**
* @param int $user_id
* @param array $data
* @return Model\User
* @throws Read
* @throws Update
*/
public function editUser(int $user_id, array $data): Model\User
{
try {
$user = $this->userRepository->fetchById($user_id);
} catch (EmptyResult $exception) {
throw new Read(__CLASS__, $exception);
}
if (!isset($data['force']) and !$user->validate($data['old_password'])) {
throw new Read(__CLASS__);
}
list($passphrase, $encrypted) = $this->splitPassword($data['password']);
try {
$password = $this->cryptoJs_aes_decrypt($encrypted, $passphrase);
} catch (Exception $exception) {
throw new Read(__CLASS__, $exception);
}
$password = password_hash($password, PASSWORD_DEFAULT);
$userData = [
'password' => $password
];
try {
$user = $this->userRepository->edit($user, $userData);
} catch (PDOException | EmptyResult $exception) {
throw new Update(__CLASS__, $exception);
}
return $user;
}
/**
* @param int $user_id
* @return Model\User
* @throws Delete
* @throws Read
*/
public function deleteUser(int $user_id): Model\User
{
try {
$user = $this->userRepository->fetchById($user_id);
} catch (EmptyResult $exception) {
throw new Read(__CLASS__, $exception);
}
try {
$this->userRepository->remove($user);
return $user;
} catch (PDOException | EmptyResult $exception) {
throw new Delete(__CLASS__, $exception);
}
}
public function validateUser(Model\User $user, string $encryptedPassword): bool
@ -88,7 +167,13 @@ class Login
try {
$login = $this->repository->fetchActiveByUser($user->id);
$this->logout($login->user);
} catch (PDOException|EmptyResult) {
} catch (EmptyResult $exception) {
$message []= "No logins for user {$user->name}";
$message []= $exception->getMessage();
$message []= $exception->getTraceAsString();
error_log(implode(PHP_EOL, $message).PHP_EOL, 3, '/logs/login-exception.log');
} catch (PDOException $exception) {
error_log($exception.PHP_EOL, 3, '/logs/login-exception.log');
}
try {
@ -104,7 +189,8 @@ class Login
$this->repository->save($login);
$this->saveCookie($selector, $token, $login->dateTime->add(new DateInterval("PT{$this->max_login_time}H")));
return true;
} catch (PDOException|Exception) {
} catch (PDOException | Exception $exception) {
error_log($exception.PHP_EOL, 3, '/logs/login-exception.log');
return false;
}
}

102
app/src/Service/MQTT.php Normal file
View File

@ -0,0 +1,102 @@
<?php
namespace Incoviba\Service;
use Incoviba\Exception\MQTT as MQTTException;
use Incoviba\Service\MQTT\MQTTInterface;
class MQTT implements MQTTInterface
{
protected array $clients = [];
public function register(string $name, MQTTInterface $client): self
{
$this->clients[$name] = $client;
return $this;
}
public function clientExists(string $name): bool
{
return isset($this->clients[$name]);
}
/**
* @param string|null $name
* @return MQTTInterface
* @throws MQTTException/MissingClient
*/
public function getClient(?string $name = null): MQTTInterface
{
if ($name === null) {
$name = array_keys($this->clients)[0];
}
if (!$this->clientExists($name)) {
throw new MQTTException\MissingClient($name);
}
return $this->clients[$name];
}
/**
* @param string|null $host
* @return bool
* @throws MQTTException/MissingClient
* @throws MQTTException/MissingJob
*/
public function exists(?string $host = null): bool
{
$client = $this->getClient($host);
return $client->exists();
}
/**
* @param string|null $host
* @return string
* @throws MQTTException/MissingClient
* @throws MQTTException/MissingJob
*/
public function get(?string $host = null): string
{
$client = $this->getClient($host);
return $client->get();
}
/**
* @param string $value
* @param int $delay
* @param string|null $host
* @return $this
* @throws MQTTException/MissingClient
* @throws MQTTException/SetJob
*/
public function set(string $value, int $delay = 0, ?string $host = null): self
{
$client = $this->getClient($host);
$client->set($value, $delay);
return $this;
}
/**
* @param int|null $jobId
* @param string|null $host
* @return $this
* @throws MQTTException/MissingJob
* @throws MQTTException/RemoveJob
*/
public function remove(?int $jobId = null, ?string $host = null): self
{
$this->getClient($host)->remove($jobId);
return $this;
}
/**
* @param string $newPayload
* @param int|null $jobId
* @param string|null $host
* @return $this
* @throws MQTTException/MissingJob
* @throws MQTTException/RemoveJob
* @throws MQTTException/SetJob
*/
public function update(string $newPayload, ?int $jobId = null, ?string $host = null): self
{
$this->getClient($host)->update($newPayload, $jobId);
return $this;
}
}

View File

@ -0,0 +1,111 @@
<?php
namespace Incoviba\Service\MQTT;
use Exception;
use Incoviba\Exception\MQTT\MissingJob;
use Incoviba\Exception\MQTT\RemoveJob;
use Incoviba\Exception\MQTT\SetJob;
use Psr\Log\LoggerInterface;
use xobotyi\beansclient;
use xobotyi\beansclient\Exception\ClientException;
use xobotyi\beansclient\Exception\CommandException;
use xobotyi\beansclient\Exception\JobException;
class Beanstalkd implements MQTTInterface
{
/**
* @throws JobException
* @throws ClientException
* @throws CommandException
*/
public function __construct(protected LoggerInterface $logger, protected beansclient\BeansClient $client,
protected string $tube = 'default', protected int $ttr = beansclient\BeansClient::DEFAULT_TTR,
protected int $priority = 1)
{
$this->client->watchTube($this->tube);
}
public function exists(): bool
{
try {
$stats = $this->client->statsTube($this->tube);
return $stats['current-jobs-ready'] > 0;
} catch (Exception $exception) {
$this->logger->error($exception->getMessage(), ['exception' => $exception]);
return false;
}
}
protected ?beansclient\Job $currentJob = null;
public function get(): string
{
if (!$this->exists()) {
throw new MissingJob();
}
try {
$job = $this->client->watchTube($this->tube)->reserve();
$this->currentJob = $job;
return $job->payload;
} catch (Exception $exception) {
$this->logger->error($exception->getMessage(), ['exception' => $exception]);
throw new MissingJob($exception);
}
}
/**
* @param string $value
* @param int $delay
* @return $this
* @throws SetJob
*/
public function set(string $value, int $delay = 0): self
{
try {
$this->client->useTube($this->tube)->put($value, $this->priority, $delay, $this->ttr ?? 0);
} catch (Exception $exception) {
$this->logger->error($exception->getMessage(), ['payload' => $value, 'delay' => $delay, 'exception' => $exception]);
throw new SetJob($value, $exception);
}
return $this;
}
/**
* @param string $newPayload
* @param int|null $jobId
* @return self
* @throws RemoveJob
* @throws SetJob
*/
public function update(string $newPayload, ?int $jobId = null): self
{
if ($jobId === null) {
$jobId = $this->currentJob->id;
}
return $this->remove($jobId)
->set($newPayload);
}
/**
* @param int|null $jobId
* @return $this
* @throws RemoveJob
*/
public function remove(?int $jobId = null): self
{
if ($jobId === null) {
$jobId = $this->currentJob->id;
}
try {
if (!$this->client->useTube($this->tube)->delete($jobId)) {
throw new JobException("Failed to delete job {$jobId}");
}
if ($this->currentJob !== null && $this->currentJob->id === $jobId) {
$this->currentJob = null;
}
} catch (Exception $exception) {
$this->logger->error($exception->getMessage(), ['jobId' => $jobId, 'exception' => $exception]);
throw new RemoveJob($jobId, $exception);
}
return $this;
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace Incoviba\Service\MQTT;
use Incoviba\Exception\MQTT\MissingJob;
interface MQTTInterface {
/**
* @return bool
*/
public function exists(): bool;
/**
* @return string
* @throws MissingJob
*/
public function get(): string;
/**
* @param string $value
* @param int $delay
* @return self
*/
public function set(string $value, int $delay = 0): self;
public function remove(?int $jobId = null): self;
}

View File

@ -0,0 +1,59 @@
<?php
namespace Incoviba\Service\MQTT;
use Incoviba\Common\Ideal\Service;
use Psr\Log\LoggerInterface;
use Pheanstalk as PBA;
class Pheanstalk extends Service implements MQTTInterface
{
const string DEFAULT_TUBE = 'default';
const int DEFAULT_TTR = 60;
const int DEFAULT_PRIORITY = 1_024;
public function __construct(LoggerInterface $logger, protected PBA\Pheanstalk $client, string $tubeName = self::DEFAULT_TUBE)
{
parent::__construct($logger);
$this->tube = new PBA\Values\TubeName($tubeName);
}
protected PBA\Values\TubeName $tube;
public function set(string $value, int $delay = 0): self
{
$this->client->useTube($this->tube);
$this->client->put($value, self::DEFAULT_PRIORITY, $delay, self::DEFAULT_TTR);
return $this;
}
public function exists(): bool
{
$stats = $this->client->statsTube($this->tube);
return $stats->currentJobsReady > 0;
}
protected int $currentJobId;
public function get(): string
{
$this->client->useTube($this->tube);
$job = $this->client->reserve();
$this->currentJobId = $job->getId();
return $job->getData();
}
public function update(string $newPayload, ?int $jobId = null): self
{
if ($jobId === null) {
$jobId = $this->currentJobId;
}
$this->remove($jobId);
$this->set($newPayload);
return $this;
}
public function remove(?int $jobId = null): self
{
if ($jobId === null) {
$jobId = $this->currentJobId;
}
$this->client->useTube($this->tube);
$this->client->delete(new PBA\Values\JobId($jobId));
return $this;
}
}

View File

@ -5,60 +5,89 @@ use DateTimeInterface;
use Psr\Log\LoggerInterface;
use Incoviba\Common\Define\Money\Provider;
use Incoviba\Common\Implement\Exception\EmptyResponse;
use Incoviba\Service\Money\MiIndicador;
class Money
{
const UF = 'uf';
const USD = 'usd';
const IPC = 'ipc';
public function __construct(protected LoggerInterface $logger) {}
protected array $providers;
public function register(string $name, Provider $provider): Money
{
if (isset($this->providers) and isset($this->providers[$name]) and $this->providers[$name] === $provider) {
if (isset($this->providers) and isset($this->providers[$name]) and in_array($provider, $this->providers[$name])) {
return $this;
}
$this->providers[$name] = $provider;
if (!isset($this->providers[$name])) {
$this->providers[$name] = [];
}
$this->providers[$name] []= $provider;
return $this;
}
public function getProvider(string $name): Provider
public function getProviders(string $name): array
{
return $this->providers[$name];
}
public function get(string $provider, DateTimeInterface $dateTime): float
{
try {
return $this->getProvider($provider)->get(MiIndicador::getSymbol($provider), $dateTime);
} catch (EmptyResponse) {
return 0;
$providers = $this->getProviders($provider);
foreach ($providers as $provider) {
try {
return $provider->get(self::getSymbol($provider), $dateTime);
} catch (EmptyResponse $exception) {
$this->logger->notice($exception);
}
}
return 0;
}
public function getUF(DateTimeInterface $dateTime): float
public function getUF(?DateTimeInterface $dateTime = null): float
{
try {
return $this->getProvider('uf')->get(MiIndicador::UF, $dateTime);
} catch (EmptyResponse) {
return 0;
$providers = $this->getProviders('uf');
foreach ($providers as $provider) {
try {
return $provider->get(self::UF, $dateTime);
} catch (EmptyResponse $exception) {
$this->logger->notice($exception);
}
}
return 0;
}
public function getIPC(DateTimeInterface $start, DateTimeInterface $end): float
{
if ($start >= $end) {
return 0;
}
try {
return $this->getProvider('ipc')->getVar($start, $end);
} catch (EmptyResponse) {
return 0;
$providers = $this->getProviders('ipc');
foreach ($providers as $provider) {
try {
return $provider->getVar($start, $end);
} catch (EmptyResponse $exception) {
$this->logger->notice($exception);
}
}
return 0;
}
public function getUSD(DateTimeInterface $dateTime): float
{
try {
return $this->getProvider('usd')->get(MiIndicador::USD, $dateTime);
} catch (EmptyResponse $exception) {
$this->logger->critical($exception);
return 0;
$providers = $this->getProviders('usd');
foreach ($providers as $provider) {
try {
return $provider->get(self::USD, $dateTime);
} catch (EmptyResponse $exception) {
$this->logger->notice($exception);
}
}
return 0;
}
public static function getSymbol(string $identifier): string
{
$upper = strtoupper($identifier);
$output = '';
eval("\$output = self::{$upper};");
return $output;
}
}

View File

@ -5,8 +5,8 @@ use Exception;
use DateTimeInterface;
use DateTimeImmutable;
use DateInterval;
use GuzzleHttp\Exception\GuzzleException;
use Psr\Http\Client\ClientInterface;
use GuzzleHttp\Exception\GuzzleException;
use Incoviba\Common\Implement\Exception\EmptyResponse;
use Incoviba\Common\Define\Money\Provider;
@ -19,8 +19,11 @@ class Ine implements Provider
* @throws EmptyResponse
* @throws Exception
*/
public function get(string $money_symbol, DateTimeInterface $dateTime): float
public function get(string $money_symbol, ?DateTimeInterface $dateTime = null): float
{
if ($dateTime === null) {
$dateTime = new DateTimeImmutable();
}
$end = new DateTimeImmutable($dateTime->format('Y-m-1'));
$start = $end->sub(new DateInterval('P1M'));
return $this->getVar($start, $end);
@ -42,9 +45,6 @@ class Ine implements Provider
$request_uri = implode('?', [
$this->uri,
http_build_query($request_query),
/*implode('&', array_map(function($val, $key) {
return "{$key}={$val}";
}, $request_query, array_keys($request_query)))*/
]);
try {
$response = $this->client->get($request_uri);

View File

@ -9,18 +9,17 @@ use Incoviba\Common\Implement\Exception\EmptyResponse;
class MiIndicador implements Provider
{
const UF = 'uf';
const IPC = 'ipc';
const USD = 'dolar';
public function __construct(protected ClientInterface $client) {}
/**
* @throws EmptyResponse
*/
public function get(string $money_symbol, DateTimeInterface $dateTime): float
public function get(string $money_symbol, ?DateTimeInterface $dateTime = null): float
{
$request_uri = "{$money_symbol}/{$dateTime->format('d-m-Y')}";
$request_uri = "{$money_symbol}";
if ($dateTime !== null) {
$request_uri = "{$money_symbol}/{$dateTime->format('d-m-Y')}";
}
try {
$response = $this->client->get($request_uri);
} catch (GuzzleException) {
@ -39,11 +38,4 @@ class MiIndicador implements Provider
}
return $json->serie[0]->valor;
}
public static function getSymbol(string $identifier): string
{
$upper = strtoupper($identifier);
$output = '';
eval("\$output = self::{$upper};");
return $output;
}
}

View File

@ -0,0 +1,144 @@
<?php
namespace Incoviba\Service\Money;
use DateTimeInterface;
use DateTimeImmutable;
use PDO;
use PDOException;
use Psr\Http\Client\ClientInterface;
use Dom\HTMLDocument;
use GuzzleHttp\Exception\GuzzleException;
use Incoviba\Common\Implement\Exception\EmptyResponse;
use Incoviba\Common\Define;
use Incoviba\Repository;
use Incoviba\Service;
class SII implements Define\Money\Provider
{
public function __construct(protected ClientInterface $client,
protected Repository\UF $ufRepository) {}
public function get(string $money_symbol, ?DateTimeInterface $dateTime = null): float
{
if ($money_symbol === Service\Money::UF) {
return $this->getUF($dateTime);
}
$class = __CLASS__;
throw new EmptyResponse("{$money_symbol} not found in {$class}");
}
/**
* @param DateTimeInterface|null $dateTime
* @return float
* @throws EmptyResponse
*/
protected function getUF(?DateTimeInterface $dateTime = null): float
{
if ($dateTime === null) {
$dateTime = new DateTimeImmutable();
}
$year = $this->getUFYear($dateTime);
if (!isset($year[$dateTime->format('Y-m-d')])) {
throw new EmptyResponse("{$dateTime->format('Y-m-d')} not found");
}
return $year[$dateTime->format('Y-m-d')];
}
/**
* @param DateTimeImmutable|null $dateTime
* @return array
* @throws EmptyResponse
*/
protected function getUFYear(?DateTimeImmutable $dateTime = null): array
{
if ($dateTime === null) {
$dateTime = new DateTimeImmutable();
}
if ($dateTime->format('Y') > (new DateTimeImmutable())->format('Y')) {
throw new EmptyResponse("{$dateTime->format('Y')} not found");
}
$request_uri = "uf/uf{$dateTime->format('Y')}.htm";
try {
$response = $this->client->get($request_uri);
} catch (GuzzleException) {
throw new EmptyResponse($request_uri);
}
if ((int) floor($response->getStatusCode() / 100) !== 2) {
throw new EmptyResponse($request_uri);
}
$body = $response->getBody();
$contents = $body->getContents();
if (trim($contents) === '') {
throw new EmptyResponse($request_uri);
}
$domHandler = HTMLDocument::createFromString($contents);
$table = $domHandler->querySelector('div#mes_all');
$tbody = $table->querySelector('tbody');
$trs = $tbody->querySelectorAll('tr');
/**
* [th Dia, th Ene, Feb, Mar, Abr, May, Jun, Jul, Ago, Sep, Oct, Nov, Dic]
* [th 1, td #, #, #, #, #, #, #, #, #, #, #, #]
*/
$year = [];
$trim = mb_chr(160);
foreach ($trs as $i => $tr) {
$tds = $tr->querySelectorAll('td');
foreach ($tds as $j => $td) {
$value = $td->textContent;
if (trim($value, "{$trim} ") === '') {
continue;
}
$m = str_pad($j + 1, 2, '0', STR_PAD_LEFT);
$d = str_pad($i + 1, 2, '0', STR_PAD_LEFT);
$date = "{$dateTime->format('Y')}-{$m}-{$d}";
$value = $this->clean($value);
$year[$date] = $value;
}
}
$this->saveUFs($year);
return $year;
}
protected function saveUFs(array $ufs): void
{
$dates = array_keys($ufs);
$dateString = "'" . implode("', '", $dates) . "'";
$query1 = $this->ufRepository->getConnection()->getQueryBuilder()
->select('DISTINCT fecha')
->from($this->ufRepository->getTable())
->where("fecha IN ({$dateString})");
try {
$statement = $this->ufRepository->getConnection()->query($query1);
$results = $statement->fetchAll(PDO::FETCH_ASSOC);
foreach ($results as $row) {
if (isset($ufs[$row['fecha']])) {
unset($ufs[$row['fecha']]);
}
}
} catch (PDOException) {
return;
}
$values = [];
foreach ($ufs as $fecha => $value) {
$values []= [$fecha, $value];
}
$valueString = implode(', ', array_fill(0, count($values), '(?, ?)'));
$query2 = "INSERT INTO {$this->ufRepository->getTable()} (fecha, valor) VALUES {$valueString}";
$this->ufRepository->getConnection()->getPDO()->beginTransaction();
try {
$this->ufRepository->getConnection()->execute($query2, $values);
if ($this->ufRepository->getConnection()->getPDO()->inTransaction()) {
$this->ufRepository->getConnection()->getPDO()->commit();
}
} catch (PDOException) {
if ($this->ufRepository->getConnection()->getPDO()->inTransaction()) {
$this->ufRepository->getConnection()->getPDO()->rollBack();
}
}
}
protected function clean(string $value): float
{
return trim(str_replace(',', '.', str_replace(['.', '$'], '', $value)));
}
}

View File

@ -1,12 +1,14 @@
<?php
namespace Incoviba\Service;
use PDOException;
use Psr\Log\LoggerInterface;
use Incoviba\Common\Ideal;
use Incoviba\Common\Implement;
use Incoviba\Exception\ServiceAction\Create;
use Incoviba\Exception\ServiceAction\Read;
use Incoviba\Model;
use Incoviba\Repository;
use Psr\Log\LoggerInterface;
class Persona extends Ideal\Service
{
@ -18,19 +20,42 @@ class Persona extends Ideal\Service
parent::__construct($logger);
}
public function getAll(null|string|array $orderBy = null): array
{
try {
try {
$ruts = $this->personaRepository->fetchMissing();
$this->addMissingPropietarios($ruts);
} catch (Implement\Exception\EmptyResult | Read | Create) {}
return array_map([$this, 'process'], $this->personaRepository->fetchAll($orderBy));
} catch (Implement\Exception\EmptyResult) {
return [];
}
}
/**
* @param int $rut
* @return Model\Persona
* @throws Read
* @throws Read|Create
*/
public function getById(int $rut): Model\Persona
{
try {
return $this->process($this->personaRepository->fetchById($rut));
} catch (Implement\Exception\EmptyResult $exception) {
throw new Read(__CLASS__, $exception);
} catch (Implement\Exception\EmptyResult) {
try {
$this->propietarioRepository->fetchById($rut);
return $this->add(compact('rut'));
} catch (Implement\Exception\EmptyResult $exception) {
throw new Read(__CLASS__, $exception);
}
}
}
/**
* @param array $data
* @return Model\Persona
* @throws Create
*/
public function add(array $data): Model\Persona
{
try {
@ -38,13 +63,20 @@ class Persona extends Ideal\Service
} catch (Implement\Exception\EmptyResult) {
try {
$propietario = $this->propietarioRepository->fetchById($data['rut']);
$data['nombres'] = $propietario->nombres;
$data['apellido_paterno'] = $propietario->apellidos['paterno'];
$data['apellido_materno'] = $propietario->apellidos['materno'];
$data['direccion_id'] = $propietario->datos->direccion->id;
} catch (Implement\Exception\EmptyResult) {}
} catch (Implement\Exception\EmptyResult $exception) {
throw new Create(__CLASS__, $exception);
}
$data['rut'] = $propietario->rut;
$data['digito'] = $propietario->dv;
$data['nombres'] = $propietario->nombres;
$data['apellido_paterno'] = $propietario->apellidos['paterno'];
$data['apellido_materno'] = $propietario->apellidos['materno'] ?? '';
$persona = $this->personaRepository->create($data);
$persona = $this->personaRepository->save($persona);
try {
$persona = $this->personaRepository->save($persona);
} catch (PDOException $exception) {
throw new Create(__CLASS__, $exception);
}
}
if (isset($data['direccion_id']) or isset($data['email']) or isset($data['telefono'])) {
$datosData = ['persona_rut' => $persona->rut];
@ -62,11 +94,69 @@ class Persona extends Ideal\Service
$this->datosPersonaRepository->edit($datos, $data);
} catch (Implement\Exception\EmptyResult) {
$datos = $this->datosPersonaRepository->create($datosData);
$this->datosPersonaRepository->save($datos);
try {
$this->datosPersonaRepository->save($datos);
} catch (PDOException $exception) {
throw new Create(__CLASS__, $exception);
}
}
}
return $this->process($persona);
}
/**
* @param array $ruts
* @param bool $load
* @return array|null
* @throws Create
* @throws Read
*/
public function addMissingPropietarios(array $ruts, bool $load = false): ?array
{
try {
$propietarios = $this->propietarioRepository->fetchByRuts($ruts);
} catch (Implement\Exception\EmptyResult $exception) {
throw new Read(__CLASS__, $exception);
}
$data = [];
$datos = [];
foreach ($propietarios as $propietario) {
$data []= [
'rut' => $propietario->rut,
'digito' => $propietario->dv,
'nombres' => $propietario->nombres,
'apellido_paterno' => $propietario->apellidos['paterno'],
'apellido_materno' => $propietario->apellidos['materno'] ?? '',
];
$datos []= [
'persona_rut' => $propietario->rut,
'direccion_id' => $propietario->datos?->direccion_id,
'email' => $propietario->datos?->email,
'telefono' => $propietario->datos?->telefono,
];
}
try {
$personas = $this->personaRepository->saveMissing($data);
} catch (PDOException|Implement\Exception\EmptyResult $exception) {
throw new Create(__CLASS__, $exception);
}
$personasRuts = array_map(function(Model\Persona $persona) {
return $persona->rut;
}, $personas);
$datos = array_filter($datos, function($row) use ($personasRuts) {
return in_array($row['persona_rut'], $personasRuts);
});
try {
$this->datosPersonaRepository->saveMissing($datos);
} catch (PDOException $exception) {
throw new Create(__CLASS__, $exception);
}
if ($load) {
return array_map([$this, 'process'], $personas);
}
return null;
}
public function edit(Model\Persona $persona, array $data): Model\Persona
{
$filteredData = $this->personaRepository->filterData($data);

View File

@ -11,14 +11,33 @@ class Proyecto
protected Repository\Proyecto $proyectoRepository,
protected Repository\Proyecto\EstadoProyecto $estadoProyecto
) {}
public function getVendibles(): array
/**
* @param string|array|null $orderBy
* @return array
*/
public function getAll(null|string|array $orderBy = null): array
{
return $this->proyectoRepository->fetchAllActive();
try {
return array_map([$this, 'process'], $this->proyectoRepository->fetchAll($orderBy));
} catch (Implement\Exception\EmptyResult) {
return [];
}
}
public function getVendibles(null|string|array $orderBy = null): array
{
return $this->proyectoRepository->fetchAllActive($orderBy);
}
public function getEscriturando(): array
{
return $this->proyectoRepository->fetchAllEscriturando();
}
/**
* @param int $proyecto_id
* @return Model\Proyecto
* @throws Implement\Exception\EmptyResult
*/
public function getById(int $proyecto_id): Model\Proyecto
{
return $this->process($this->proyectoRepository->fetchById($proyecto_id));

View File

@ -0,0 +1,210 @@
<?php
namespace Incoviba\Service\Proyecto;
use PDOException;
use Psr\Log\LoggerInterface;
use Incoviba\Common\Ideal;
use Incoviba\Common\Implement\Exception\EmptyResult;
use Incoviba\Common\Implement\Repository\Factory;
use Incoviba\Exception\ServiceAction;
use Incoviba\Model;
use Incoviba\Repository;
use Incoviba\Service;
class Broker extends Ideal\Service
{
public function __construct(LoggerInterface $logger,
protected Repository\Proyecto\Broker $brokerRepository,
protected Repository\Proyecto\Broker\Data $dataRepository,
protected Repository\Proyecto\Broker\Contact $contactRepository,
protected Repository\Proyecto\Broker\Contract $contractRepository,
protected Service\Proyecto\Broker\Contract $contractService)
{
parent::__construct($logger);
}
/**
* @param array $data
* @return Model\Proyecto\Broker
* @throws ServiceAction\Create
*/
public function add(array $data): Model\Proyecto\Broker
{
try {
$broker = $this->brokerRepository->fetchById($data['rut']);
} catch (EmptyResult) {
$filteredData = $this->brokerRepository->filterData($data);
try {
$broker = $this->brokerRepository->create($filteredData);
$broker = $this->brokerRepository->save($broker);
} catch (PDOException $exception) {
throw new ServiceAction\Create(__CLASS__, $exception);
}
}
$this->addData($broker, $data);
return $this->process($broker);
}
/**
* @param int $id
* @return Model\Proyecto\Broker
* @throws ServiceAction\Read
*/
public function get(int $id): Model\Proyecto\Broker
{
try {
return $this->process($this->brokerRepository->fetchById($id));
} catch (EmptyResult $exception) {
throw new ServiceAction\Read(__CLASS__, $exception);
}
}
/**
* @param null|string|array $orderBy
* @return array
*/
public function getAll(null|string|array $orderBy = null): array
{
try {
return array_map([$this, 'process'], $this->brokerRepository->fetchAll($orderBy));
} catch (EmptyResult) {
return [];
}
}
/**
* @param array $data
* @return Model\Proyecto\Broker
* @throws ServiceAction\Update
*/
public function edit(array $data): Model\Proyecto\Broker
{
try {
$broker = $this->brokerRepository->fetchById($data['rut']);
} catch (EmptyResult $exception) {
throw new ServiceAction\Update(__CLASS__, $exception);
}
$filteredData = $this->brokerRepository->filterData($data);
try {
$broker = $this->brokerRepository->edit($broker, $filteredData);
$this->editData($broker, $data);
} catch (PDOException | EmptyResult) {
} finally {
return $this->process($broker);
}
}
/**
* @param int $broker_rut
* @return Model\Proyecto\Broker
* @throws ServiceAction\Delete
*/
public function delete(int $broker_rut): Model\Proyecto\Broker
{
try {
$broker = $this->brokerRepository->fetchById($broker_rut);
} catch (EmptyResult $exception) {
throw new ServiceAction\Delete(__CLASS__, $exception);
}
try {
$this->brokerRepository->remove($broker);
} catch (PDOException $exception) {
throw new ServiceAction\Delete(__CLASS__, $exception);
} finally {
return $broker;
}
}
protected function process(Model\Proyecto\Broker $broker): Model\Proyecto\Broker
{
$broker->addFactory('data', (new Factory())
->setArgs(['broker_rut' => $broker->rut])
->setCallable([$this->dataRepository, 'fetchByBroker']))
->addFactory('contracts', (new Factory())
->setArgs(['broker_rut' => $broker->rut])
->setCallable([$this->contractService, 'getByBroker']));
return $broker;
}
/**
* @param Model\Proyecto\Broker $broker
* @param array $data
* @return Model\Proyecto\Broker\Data
* @throws ServiceAction\Create
*/
protected function addData(Model\Proyecto\Broker $broker, array $data): Model\Proyecto\Broker\Data
{
$data['broker_rut'] = $broker->rut;
$filteredData = $this->dataRepository->filterData($data);
if (isset($data['contact'])) {
try {
$representative = $this->contactRepository->fetchByName($data['contact']);
$filteredData['representative_id'] = $representative->id;
} catch (EmptyResult) {
$representativeData = $this->contactRepository->filterData($data);
$representativeData['name'] = $data['contact'];
$representative = $this->contactRepository->create($representativeData);
try {
$representative = $this->contactRepository->save($representative);
$filteredData['representative_id'] = $representative->id;
} catch (PDOException) {
unset($representative);
}
}
}
if (isset($filteredData['representative_id'])) {
try {
$this->contactRepository->fetchById($filteredData['representative_id']);
} catch (EmptyResult) {
unset($filteredData['representative_id']);
}
}
try {
$brokerData = $this->dataRepository->fetchByBroker($broker->rut);
return $this->dataRepository->edit($brokerData, $filteredData);
} catch (EmptyResult) {}
try {
$brokerData = $this->dataRepository->create($filteredData);
return $this->dataRepository->save($brokerData);
} catch (PDOException $exception) {
throw new ServiceAction\Create(__CLASS__, $exception);
}
}
/**
* @param Model\Proyecto\Broker $broker
* @param array $data
* @return Model\Proyecto\Broker\Data
* @throws ServiceAction\Update
*/
protected function editData(Model\Proyecto\Broker $broker, array $data): Model\Proyecto\Broker\Data
{
try {
$brokerData = $this->dataRepository->fetchByBroker($broker->rut);
} catch (EmptyResult $exception) {
throw new ServiceAction\Update(__CLASS__, $exception);
}
if (isset($data['contact'])) {
try {
$representative = $this->contactRepository->fetchByName($data['contact']);
$data['representative_id'] = $representative->id;
} catch (EmptyResult) {
$representativeData = $this->contactRepository->filterData($data);
$representativeData['name'] = $data['contact'];
try {
$representative = $this->contactRepository->create($representativeData);
$representative = $this->contactRepository->save($representative);
$data['representative_id'] = $representative->id;
} catch (PDOException) {
unset($representative);
unset($data['contact']);
}
}
}
try {
$data['broker_rut'] = $broker->rut;
$filteredData = $this->dataRepository->filterData($data);
$brokerData = $this->dataRepository->edit($brokerData, $filteredData);
return $this->dataRepository->save($brokerData);
} catch (PDOException | EmptyResult) {
return $brokerData;
}
}
}

View File

@ -0,0 +1,154 @@
<?php
namespace Incoviba\Service\Proyecto\Broker;
use DateTimeInterface;
use DateTimeImmutable;
use DateMalformedStringException;
use PDOException;
use Psr\Log\LoggerInterface;
use Incoviba\Common\Ideal;
use Incoviba\Common\Implement;
use Incoviba\Exception\ServiceAction;
use Incoviba\Model;
use Incoviba\Repository;
class Contract extends Ideal\Service
{
public function __construct(LoggerInterface $logger,
protected Repository\Proyecto\Broker\Contract $contractRepository,
protected Repository\Proyecto\Broker\Contract\State $stateRepository,
protected Repository\Venta\Promotion $promotionRepository)
{
parent::__construct($logger);
}
public function getAll(): array
{
try {
return array_map([$this, 'process'], $this->contractRepository->fetchAll());
} catch (Implement\Exception\EmptyResult) {
return [];
}
}
/**
* @param int $project_id
* @return array
* @throws ServiceAction\Read
*/
public function getByProject(int $project_id): array
{
try {
return array_map([$this, 'process'], $this->contractRepository->fetchByProject($project_id));
} catch (Implement\Exception\EmptyResult $exception) {
throw new ServiceAction\Read(__CLASS__, $exception);
}
}
/**
* @param int $broker_rut
* @return array
* @throws ServiceAction\Read
*/
public function getByBroker(int $broker_rut): array
{
try {
return array_map([$this, 'process'], $this->contractRepository->fetchByBroker($broker_rut));
} catch (Implement\Exception\EmptyResult $exception) {
throw new ServiceAction\Read(__CLASS__, $exception);
}
}
/**
* @throws ServiceAction\Read
*/
public function getById(int $id): Model\Proyecto\Broker\Contract
{
try {
return $this->process($this->contractRepository->fetchById($id));
} catch (Implement\Exception\EmptyResult $exception) {
throw new ServiceAction\Read(__CLASS__, $exception);
}
}
/**
* @throws ServiceAction\Create
*/
public function add(array $data): Model\Proyecto\Broker\Contract
{
try {
return $this->process($this->contractRepository->fetchActiveByProjectAndBroker($data['project_id'], $data['broker_rut']));
} catch (Implement\Exception\EmptyResult) {}
try {
$filteredData = $this->contractRepository->filterData($data);
$contract = $this->contractRepository->create($filteredData);
$contract = $this->contractRepository->save($contract);
$type = Model\Proyecto\Broker\Contract\State\Type::ACTIVE->value;
$date = new DateTimeImmutable();
if (isset($data['date'])) {
try {
$date = new DateTimeImmutable($data['date']);
} catch (DateMalformedStringException) {}
}
$state = $this->stateRepository->create(['contract_id' => $contract->id, 'date' => $date, 'type' => $type]);
$this->stateRepository->save($state);
return $this->process($contract);
} catch (PDOException $exception) {
throw new ServiceAction\Create(__CLASS__, $exception);
}
}
/**
* @throws ServiceAction\Update
*/
public function edit(Model\Proyecto\Broker\Contract $contract, array $data): Model\Proyecto\Broker\Contract
{
try {
$filteredData = $this->contractRepository->filterData($data);
return $this->process($this->contractRepository->edit($contract, $filteredData));
} catch (PDOException | Implement\Exception\EmptyResult) {
throw new ServiceAction\Update(__CLASS__);
}
}
/**
* @throws ServiceAction\Delete
*/
public function delete(int $id): Model\Proyecto\Broker\Contract
{
try {
$contract = $this->contractRepository->fetchById($id);
$this->contractRepository->remove($contract);
return $contract;
} catch (PDOException | Implement\Exception\EmptyResult $exception) {
throw new ServiceAction\Delete(__CLASS__, $exception);
}
}
/**
* @throws ServiceAction\Update
*/
public function inactive(Model\Proyecto\Broker\Contract $contract, DateTimeInterface $date = new DateTimeImmutable()): Model\Proyecto\Broker\Contract
{
try {
$type = Model\Proyecto\Broker\Contract\State\Type::INACTIVE;
$state = $this->stateRepository->create(['contract_id' => $contract->id, 'date' => $date, 'type' => $type]);
$this->stateRepository->save($state);
return $this->process($contract);
} catch (PDOException $exception) {
throw new ServiceAction\Update(__CLASS__, $exception);
}
}
protected function process(Model\Proyecto\Broker\Contract $contract): Model\Proyecto\Broker\Contract
{
$contract->addFactory('states', (new Implement\Repository\Factory())
->setCallable([$this->stateRepository, 'fetchByContract'])
->setArgs(['contract_id' => $contract->id]));
$contract->addFactory('promotions', (new Implement\Repository\Factory())
->setCallable([$this->promotionRepository, 'fetchByContract'])
->setArgs(['contract_id' => $contract->id]));
return $contract;
}
}

114
app/src/Service/Queue.php Normal file
View File

@ -0,0 +1,114 @@
<?php
namespace Incoviba\Service;
use Exception;
use Psr\Http\Message\RequestInterface;
use Psr\Log\LoggerInterface;
use Incoviba\Common\Ideal;
use Incoviba\Exception\ServiceAction\{Create, Delete, Read, Update};
use Incoviba\Service;
use Incoviba\Model;
class Queue extends Ideal\Service
{
public function __construct(LoggerInterface $logger, protected Service\Job $jobService, Worker $defaultWorker,
protected int $maxRetries = 5)
{
parent::__construct($logger);
$this->register('default', $defaultWorker);
}
protected array $workers;
public function register(string $name, Worker $worker): self
{
$this->workers[strtolower($name)] = $worker;
return $this;
}
public function enqueue(array $configuration): bool
{
try {
$this->jobService->add($configuration);
return true;
} catch (Create $exception) {
$final = new Exception("Could not enqueue job", 0, $exception);
$this->logger->warning($final);
return false;
}
}
public function push(array $configuration): bool
{
return $this->enqueue($configuration);
}
public function runJob(Model\Job $job, ?RequestInterface $request = null): bool
{
$type = 'default';
if (isset($job->configuration['type'])) {
$type = strtolower($job->configuration['type']);
}
if (!isset($this->workers[$type])) {
$type = 'default';
}
$worker = $this->workers[$type];
if (is_a($worker, Service\Worker\Request::class) and $request !== null) {
$worker->setRequest($request);
}
try {
if (!$worker->execute($job)) {
$this->logger->debug("Could not execute job {$job->id}");
$job->retries++;
$this->jobService->update($job);
return false;
}
if (!$this->jobService->execute($job)) {
$this->logger->debug("Could not remove job {$job->id}");
return false;
}
} catch (Exception $exception) {
$this->logger->warning("Could not run job {$job->id}", ['exception' => $exception]);
$job->retries++;
try {
$this->jobService->update($job);
} catch (Update $exception) {
$this->logger->error($exception->getMessage(), ['job' => $job, 'exception' => $exception]);
}
return false;
}
return true;
}
public function run(?RequestInterface $request = null): bool
{
if (!$this->jobService->isPending()) {
return true;
}
try {
$job = $this->jobService->get();
} catch (Read $exception) {
$this->logger->error($exception->getMessage(), ['exception' => $exception]);
return false;
}
if ($job->retries >= $this->maxRetries) {
try {
$this->jobService->remove($job);
} catch (Delete $exception) {
$this->logger->error($exception->getMessage(), ['job' => $job, 'exception' => $exception]);
}
return true;
}
try {
$this->runJob($job, $request);
} catch (Exception) {
$job->retries ++;
try {
$this->jobService->update($job);
} catch (Update $exception) {
$this->logger->error($exception->getMessage(), ['job' => $job, 'exception' => $exception]);
}
return false;
}
return true;
}
}

View File

@ -28,7 +28,12 @@ class Redis
public function set(string $name, mixed $value, int $expirationTTL = 60 * 60 * 24): void
{
try {
$this->client->set($name, $value, 'EX', $expirationTTL);
$resolution = 'EX';
if ($expirationTTL === -1) {
$resolution = null;
$expirationTTL = null;
}
$this->client->set($name, $value, $resolution, $expirationTTL);
} catch (ConnectionException) {
return;
}

View File

@ -3,36 +3,52 @@ namespace Incoviba\Service;
use DateTimeInterface;
use DateTimeImmutable;
use Incoviba\Common\Implement\Exception\EmptyRedis;
use PDOException;
use Psr\Log\LoggerInterface;
use Incoviba\Common\Implement\Exception\{EmptyRedis, EmptyResult};
use Incoviba\Repository;
class UF
{
protected string $redisKey = 'uf';
public function __construct(protected Redis $redisService, protected Money $moneyService,
protected Repository\UF $ufRepository,
protected LoggerInterface $logger) {}
public function get(?DateTimeInterface $date = null): float
{
$today = new DateTimeImmutable();
if ($date === null) {
$date = new DateTimeImmutable();
}
$ufs = [];
if ($date->diff($today)->days < 0) {
return 0.0;
}
/**
* 1 - Redis
* 2 - DB
* 3 - Fetch from web
*/
try {
$ufs = json_decode($this->redisService->get($this->redisKey), JSON_OBJECT_AS_ARRAY);
$ufs = $this->getRedisUFs();
if (!isset($ufs[$date->format('Y-m-d')])) {
throw new EmptyRedis($this->redisKey);
}
$uf = $ufs[$date->format('Y-m-d')];
return $ufs[$date->format('Y-m-d')];
} catch (EmptyRedis) {
$uf = $this->moneyService->getUF($date);
if ($uf === 0.0) {
return 0.0;
try {
$model = $this->ufRepository->fetchByFecha($date);
return $model->valor;
} catch (EmptyResult) {
$uf = $this->moneyService->getUF($date);
if ($uf === 0.0) {
return 0.0;
}
$this->saveUF($date, $uf);
}
$ufs[$date->format('Y-m-d')] = $uf;
ksort($ufs);
$this->redisService->set($this->redisKey, json_encode($ufs), 60 * 60 * 24 * 30);
}
return $uf;
}
@ -49,6 +65,7 @@ class UF
if ($uf === 0.0) {
continue;
}
$this->saveUF($date, $uf);
$updated[$date->format('Y-m-d')] = $uf;
$ufs[$date->format('Y-m-d')] = $this->moneyService->getUF($date);
}
@ -63,4 +80,44 @@ class UF
$uf = $this->get($date);
return $input * (($from === 'uf') ? $uf : 1/$uf);
}
protected array $redisUFs;
public function getRedisUFs(): array
{
if (!isset($this->redisUFs)) {
try {
$this->redisUFs = json_decode($this->redisService->get($this->redisKey), JSON_OBJECT_AS_ARRAY);
} catch (EmptyRedis) {
$this->redisUFs = [];
}
}
return $this->redisUFs;
}
protected function saveUF(DateTimeInterface $date, float $value): void
{
$this->saveUFinRedis($date, $value);
$this->saveUFinDB($date, $value);
}
protected function saveUFinRedis(DateTimeInterface $date, float $value): void
{
$ufs = $this->redisUFs;
$ufs[$date->format('Y-m-d')] = $value;
if (count($ufs) > 1) {
ksort($ufs);
}
$this->redisUFs = $ufs;
$this->redisService->set($this->redisKey, json_encode($ufs), 60 * 60 * 24 * 30);
}
protected function saveUFinDB(DateTimeInterface $date, float $value): void
{
try {
$this->ufRepository->fetchByFecha($date);
} catch (EmptyResult) {
try {
$model = $this->ufRepository->create(['fecha' => $date->format('Y-m-d'), 'valor' => $value]);
$this->ufRepository->save($model);
} catch (PDOException) {}
}
}
}

View File

@ -4,6 +4,7 @@ namespace Incoviba\Service;
use DateTimeInterface;
use DateTimeImmutable;
use DateMalformedStringException;
use function PHPUnit\Framework\countOf;
class Valor
{
@ -11,15 +12,22 @@ class Valor
public function clean(string|float|int $value): float
{
if ((float) $value == $value) {
if (!is_string($value)) {
return (float) $value;
}
return (float) str_replace(['.', ','], ['', '.'], $value);
if ((int) $value == $value) {
return (float) $value;
}
if ($this->isUS($value)) {
return $this->formatUS($value);
}
return $this->formatCL($value);
}
public function toPesos(string $value, null|string|DateTimeInterface $date = null, bool $force = false): int
{
$date = $this->getDateTime($date);
if (abs((float) $value - (int) $value) > 0 or $force) {
if ($this->inUF($value) or $force) {
return round($value * $this->ufService->get($date));
}
return (int) $value;
@ -27,7 +35,7 @@ class Valor
public function toUF(string $value, null|string|DateTimeInterface $date = null, bool $force = false): float
{
$date = $this->getDateTime($date);
if (abs((float) $value - (int) $value) > 0 and !$force) {
if ($this->inUF($value) and !$force) {
return (float) $value;
}
return $value / $this->ufService->get($date);
@ -47,4 +55,64 @@ class Valor
}
return $date;
}
protected function isUS(string $value): bool
{
/*
* Chile
* 1.000.000,00
* 10000,000
* 10,53
* 1.000,00
* 1.000 imposible! se asume US si # antes de . < 10
*
* 1,000,000.00
* 10000.00
* 1,000.000
* 10.53
* 1,000 imposible! se asume CL
*/
if (str_contains($value, '.')) {
$parts = explode('.', $value);
if (count($parts) > 2) { // 1.000.000 || 1.000.000,00
return false;
}
if (strlen($parts[0]) > 3) { // 1000.000 || 1,000.000
return true;
}
if (strlen($parts[1]) < 3) { // #####.00
return true;
}
if (str_contains($value, ',')) {
if (strpos($value, ',') > strpos($value, '.')) { // 1.000,000
return false;
}
return true; // 1,000.000
}
if ((int) $parts[0] < 10) { // 1.000
return true;
}
return false;
}
return true;
}
protected function formatCL(string $value): float
{
return (float) str_replace(',', '.', (str_replace('.', '', $value)));
}
protected function formatUS(string $value): float
{
return (float) str_replace(',', '', $value);
}
protected function inUF(string|int|float $value, float $check = 10000): bool
{
if (!is_string($value)) {
if ($value >= $check) { // Valor arbitrario mayor que el cual no es UF
return false;
}
return is_float($value);
}
$cleaned = $this->clean($value);
return round($cleaned) !== $cleaned;
}
}

View File

@ -1,16 +1,18 @@
<?php
namespace Incoviba\Service;
use Exception;
use DateTimeImmutable;
use DateMalformedStringException;
use Incoviba\Exception\ServiceAction\{Create, Read, Update};
use Incoviba\Common\Define;
use PDOException;
use Psr\Log\LoggerInterface;
use Incoviba\Common\Ideal\Service;
use Incoviba\Common\Implement;
use Incoviba\Repository;
use Incoviba\Model;
class Venta extends Service
class Venta extends Service\Repository
{
public function __construct(
LoggerInterface $logger,
@ -30,15 +32,24 @@ class Venta extends Service
protected Venta\BonoPie $bonoPieService,
protected Venta\Pago $pagoService,
protected Proyecto\Terreno $terrenoService,
protected Money $moneyService,
protected UF $ufService,
protected Valor $valorService
) {
parent::__construct($logger);
}
/**
* @param int $venta_id
* @return Model\Venta
* @throws Read
*/
public function getById(int $venta_id): Model\Venta
{
return $this->process($this->ventaRepository->fetchById($venta_id));
try {
return $this->process($this->ventaRepository->fetchById($venta_id));
} catch (Implement\Exception\EmptyResult $exception) {
throw new Read(__CLASS__, $exception);
}
}
public function getByProyecto(int $proyecto_id): array
{
@ -59,14 +70,55 @@ class Venta extends Service
$venta = $this->ventaRepository->fetchByProyectoAndUnidad($proyecto_nombre, $unidad_descripcion);
return $this->process($venta);
}
/**
* @param string $unidad
* @param string $tipo
* @return array
* @throws Read
*/
public function getByUnidad(string $unidad, string $tipo): array
{
$ventas = $this->ventaRepository->fetchByUnidad($unidad, $tipo);
return array_map([$this, 'process'], $ventas);
try {
$ventas = $this->ventaRepository->fetchByUnidad($unidad, $tipo);
return array_map([$this, 'process'], $ventas);
} catch (Implement\Exception\EmptyResult $exception) {
throw new Read(__CLASS__, $exception);
}
}
public function getByUnidadId(int $unidad_id): Model\Venta
/**
* @param int $unidad_id
* @return Model\Venta
* @throws Read
*/
public function getActiveByUnidadId(int $unidad_id): Model\Venta
{
return $this->process($this->ventaRepository->fetchByUnidadId($unidad_id));
try {
return $this->process($this->ventaRepository->fetchActiveByUnidadId($unidad_id));
} catch (Implement\Exception\EmptyResult $exception) {
throw new Read(__CLASS__, $exception);
}
}
/**
* @param array $unidad_ids
* @return array
* @throws Read
*/
public function getActiveByUnidadIds(array $unidad_ids): array
{
try {
$ventas = $this->ventaRepository->fetchActiveArrayByUnidadIds($unidad_ids);
return array_map(function($data) {
return [
'unidad_id' => $data['unidad_id'],
'venta' => $this->processSimple($data['venta'])
];
}, $ventas);
} catch (Implement\Exception\EmptyResult $exception) {
throw new Read(__CLASS__, $exception);
}
}
public function getByPropietario(string $propietario): array
{
@ -124,8 +176,35 @@ class Venta extends Service
return $data;
}
/**
* @return array
* @throws Read
*/
public function getAllWithCuotaPending(): array
{
try {
return array_map([$this, 'process'],$this->ventaRepository->fetchAllWithCuotaPending());
} catch (Implement\Exception\EmptyResult $exception) {
throw new Read(__CLASS__, $exception);
}
}
public function getRepository(): Define\Repository
{
return $this->ventaRepository;
}
protected function process(Model\Venta $venta): Model\Venta
{
if ($venta->uf === 0.0) {
$uf = $this->ufService->get($venta->fecha);
if ($uf > 0.0) {
try {
$venta = $this->ventaRepository->edit($venta, ['uf' => $uf]);
} catch (Implement\Exception\EmptyResult) {}
}
}
$venta->addFactory('formaPago', (new Implement\Repository\Factory())
->setCallable([$this->formaPagoService, 'getByVenta'])
->setArgs(['venta_id' => $venta->id]));
@ -135,19 +214,41 @@ class Venta extends Service
$venta->addFactory('currentEstado', (new Implement\Repository\Factory())
->setCallable([$this->estadoVentaRepository, 'fetchCurrentByVenta'])
->setArgs([$venta->id]));
return $venta;
}
/**
* @throws Exception
* @param array $venta
* @return array
* @throws Read
*/
protected function processSimple(array $venta): array
{
$output = $venta;
$output['propiedad'] = $this->propiedadService->getArrayById($venta['propiedad']);
return $output;
}
/**
* @throws Create|Update
*/
public function add(array $data): Model\Venta
{
$fecha = new DateTimeImmutable($data['fecha_venta']);
$data['uf'] = $this->moneyService->getUF($fecha);
try {
$fecha = new DateTimeImmutable($data['fecha_venta']);
} catch (DateMalformedStringException $exception) {
throw new Create(__CLASS__, $exception);
}
$data['uf'] = $this->ufService->get($fecha);
$propietario = $this->addPropietario($data);
$propiedad = $this->addPropiedad($data);
$formaPago = $this->addFormaPago($data);
try {
$formaPago = $this->addFormaPago($data);
} catch (Create) {}
$venta_data = [
'propietario' => $propietario->rut,
'propiedad' => $propiedad->id,
@ -156,31 +257,46 @@ class Venta extends Service
'fecha_ingreso' => (new DateTimeImmutable())->format('Y-m-d'),
'uf' => $data['uf']
];
$map = ['pie', 'subsidio', 'credito', 'bono_pie'];
foreach ($map as $field) {
$name = lcfirst(str_replace(' ', '', ucwords(str_replace('_', ' ', $field))));
if (isset($formaPago->{$name})) {
$venta_data[$field] = $formaPago->{$name}->id;
if (isset($formaPago)) {
$map = ['pie', 'subsidio', 'credito', 'bono_pie'];
foreach ($map as $field) {
$name = lcfirst(str_replace(' ', '', ucwords(str_replace('_', ' ', $field))));
if (isset($formaPago->{$name})) {
$venta_data[$field] = $formaPago->{$name}->id;
}
}
}
try {
return $this->ventaRepository->fetchByPropietarioAndPropiedad($propietario->rut, $propiedad->id);
} catch (Implement\Exception\EmptyResult) {
$venta = $this->ventaRepository->create($venta_data);
$venta->setFormaPago($formaPago);
if (isset($formaPago)) {
$venta->setFormaPago($formaPago);
}
$venta = $this->ventaRepository->save($venta);
$tipoEstado = $this->tipoEstadoVentaRepository->fetchByDescripcion('vigente');
$estado = $this->estadoVentaRepository->create([
'venta' => $venta->id,
'estado' => $tipoEstado->id,
'fecha' => $venta->fecha->format('Y-m-d')
]);
$this->estadoVentaRepository->save($estado);
try {
$tipoEstado = $this->tipoEstadoVentaRepository->fetchByDescripcion('vigente');
try {
$estado = $this->estadoVentaRepository->create([
'venta' => $venta->id,
'estado' => $tipoEstado->id,
'fecha' => $venta->fecha->format('Y-m-d')
]);
$this->estadoVentaRepository->save($estado);
} catch (PDOException) {}
} catch (Implement\Exception\EmptyResult) {}
return $venta;
}
}
/**
* @param array $data
* @return Model\Venta\Propietario
* @throws Create|Update
*/
protected function addPropietario(array $data): Model\Venta\Propietario
{
if (isset($data['natural_uno'])) {
@ -191,9 +307,15 @@ class Venta extends Service
}
return $this->addSociedad($data);
}
/**
* @param array $data
* @return Model\Venta\Propietario
* @throws Create
*/
protected function addUnPropietario(array $data): Model\Venta\Propietario
{
$fields = array_fill_keys([
$fields = array_flip([
'rut',
'nombres',
'apellido_paterno',
@ -201,14 +323,22 @@ class Venta extends Service
'calle',
'numero',
'extra',
'comuna'
], 0);
'comuna',
'email',
'telefono'
]);
$filtered_data = array_intersect_key($data, $fields);
return $this->propietarioService->addPropietario($filtered_data);
}
/**
* @param array $data
* @return Model\Venta\Propietario
* @throws Create
*/
protected function addDosPropietarios(array $data): Model\Venta\Propietario
{
$fields = array_fill_keys([
$fields = array_flip([
'rut_otro',
'nombres_otro',
'apellido_paterno_otro',
@ -216,8 +346,10 @@ class Venta extends Service
'calle_otro',
'numero_otro',
'extra_otro',
'comuna_otro'
], 0);
'comuna_otro',
'email_otro',
'telefono_otro'
]);
$filtered_data = array_intersect_key($data, $fields);
$mapped_data = array_combine([
'rut',
@ -227,12 +359,14 @@ class Venta extends Service
'calle',
'numero',
'extra',
'comuna'
'comuna',
'email',
'telefono'
], $filtered_data);
$otro = $this->propietarioService->addPropietario($mapped_data);
$data['otro'] = $otro->rut;
$fields = array_fill_keys([
$fields = array_flip([
'rut',
'nombres',
'apellido_paterno',
@ -241,17 +375,26 @@ class Venta extends Service
'numero',
'extra',
'comuna',
'otro'
], 0);
'otro',
'email',
'telefono'
]);
$filtered_data = array_intersect_key($data, $fields);
return $this->propietarioService->addPropietario($filtered_data);
}
/**
* @param array $data
* @return Model\Venta\Propietario
* @throws Create
* @throws Update
*/
protected function addSociedad(array $data): Model\Venta\Propietario
{
$representante = $this->addUnPropietario($data);
$data['representante'] = $representante->rut;
$fields = array_fill_keys([
$fields = array_flip([
'rut_sociedad',
'razon_social',
'calle_comercial',
@ -259,7 +402,7 @@ class Venta extends Service
'extra_comercial',
'comuna_comercial',
'representante'
], 0);
]);
$filtered_data = array_intersect_key($data, $fields);
$mapped_data = array_combine([
'rut',
@ -272,6 +415,12 @@ class Venta extends Service
], $filtered_data);
return $this->propietarioService->addSociedad($mapped_data);
}
/**
* @param array $data
* @return Model\Venta\Propiedad
* @throws Create
*/
protected function addPropiedad(array $data): Model\Venta\Propiedad
{
$ids = array_filter($data, function($key) {
@ -280,11 +429,33 @@ class Venta extends Service
return $this->propiedadService->addPropiedad($ids);
}
/**
* @param array $data
* @return Model\Venta\FormaPago
* @throws Create
*/
protected function addFormaPago(array $data): Model\Venta\FormaPago
{
return $this->formaPagoService->add($data);
}
/**
* @param Model\Venta $venta
* @param array $data
* @return Model\Venta
* @throws Update
*/
public function edit(Model\Venta $venta, array $data): Model\Venta
{
try {
$filteredData = $this->ventaRepository->filterData($data);
return $this->ventaRepository->edit($venta, $filteredData);
} catch (Implement\Exception\EmptyResult $exception) {
throw new Update(__CLASS__, $exception);
}
}
protected function addEstado(Model\Venta $venta, Model\Venta\TipoEstadoVenta $tipoEstadoVenta, array $data): void
{
try {
@ -366,7 +537,7 @@ class Venta extends Service
} catch (DateMalformedStringException) {
$fecha = new DateTimeImmutable();
}
$uf = $this->moneyService->getUF($fecha);
$uf = $this->ufService->get($fecha);
$valor = $data['valor_pago_ufs'] !== '' ? $this->valorService->clean($data['valor_pago_ufs']) * $uf : $this->valorService->clean($data['valor_pago_pesos']);
$pagoData = [
'valor' => $valor,
@ -392,7 +563,7 @@ class Venta extends Service
} catch (DateMalformedStringException) {
$fecha = new DateTimeImmutable();
}
$uf = $this->moneyService->getUF($fecha);
$uf = $this->ufService->get($fecha);
$subsidioData = [
'fecha_venta' => $fecha->format('Y-m-d'),
'ahorro' => $this->valorService->clean($data['valor_ahorro']),
@ -410,7 +581,7 @@ class Venta extends Service
} catch (DateMalformedStringException) {
$fecha = new DateTimeImmutable();
}
$uf = $this->moneyService->getUF($fecha);
$uf = $this->ufService->get($fecha);
$valor = $this->valorService->clean($data['valor_credito']) * $uf;
if ($venta->formaPago()->credito === null) {
if ($data['valor_credito'] === 0) {

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;
}
}

View File

@ -0,0 +1,9 @@
<?php
namespace Incoviba\Service;
use Incoviba\Model;
interface Worker
{
public function execute(Model\Job $job): bool;
}

View File

@ -0,0 +1,51 @@
<?php
namespace Incoviba\Service\Worker;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\ContainerInterface;
use Psr\Container\NotFoundExceptionInterface;
use Psr\Log\LoggerInterface;
use Incoviba\Common\Ideal;
use Incoviba\Model;
use Incoviba\Service;
class CheckExternal extends Ideal\Service implements Service\Worker
{
public function __construct(LoggerInterface $logger, protected ContainerInterface $container)
{
parent::__construct($logger);
}
protected Service\Queue $queueService;
public function execute(Model\Job $job): bool
{
$configuration = $job->configuration;
$serviceClass = $configuration['service'];
if (!$this->container->has($serviceClass)) {
return false;
}
try {
$service = $this->container->get($serviceClass);
} catch (NotFoundExceptionInterface | ContainerExceptionInterface) {
return false;
}
$method = $configuration['method'] ?? 'check';
if (!method_exists($service, $method)) {
return false;
}
if (!isset($this->queueService)) {
$this->queueService = $this->container->get(Service\Queue::class);
}
if (isset($configuration['args'])) {
$args = $configuration['args'];
$queues = call_user_func_array([$service, $method], $args);
} else {
$queues = call_user_func([$service, $method]);
}
foreach ($queues as $queue) {
$this->queueService->enqueue($queue);
}
return true;
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace Incoviba\Service\Worker;
use Incoviba\Common\Ideal;
use Incoviba\Model;
use Incoviba\Service;
class Dummy extends Ideal\Service implements Service\Worker
{
public function execute(Model\Job $job): bool
{
$configuration = $job->configuration;
$this->logger->info('Dummy worker executed', ['configuration' => $configuration]);
return true;
}
}

View File

@ -0,0 +1,62 @@
<?php
namespace Incoviba\Service\Worker;
use Incoviba\Common\Implement\Exception\EmptyResponse;
use Incoviba\Service\Worker;
use Psr\Http\Client\ClientExceptionInterface;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Log\LoggerInterface;
use Incoviba\Common\Ideal;
use Incoviba\Model;
class Request extends Ideal\Service implements Worker
{
public function __construct(LoggerInterface $logger, protected ClientInterface $client)
{
parent::__construct($logger);
}
protected RequestInterface $request;
public function setRequest(RequestInterface $request): self
{
$this->request = $request;
return $this;
}
/**
* @param Model\Job $job
* @return bool
* @throws EmptyResponse
*/
public function execute(Model\Job $job): bool
{
$url = $job->configuration['url'] ?? $job->configuration['action'];
$method = strtolower($job->configuration['method']) ?? 'get';
$body = $job->configuration['body'];
try {
$response = $this->client->{$method}($url, [
'json' => $body,
'headers' => [
'Authorization' => $this->request->getHeaderLine('Authorization')
]
]);
} catch (ClientExceptionInterface $exception) {
throw new EmptyResponse($url, $exception);
}
$statusCode = $response->getStatusCode();
if ((int) floor($statusCode / 100) !== 2) {
throw new EmptyResponse($url);
}
if ($statusCode !== 204) {
$contents = $response->getBody()->getContents();
$data = json_decode($contents, true);
if (!isset($data['success']) or !$data['success']) {
throw new EmptyResponse($url);
}
}
return true;
}
}

View File

@ -0,0 +1,50 @@
<?php
namespace Incoviba\Service\Worker;
use Exception;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\ContainerInterface;
use Psr\Container\NotFoundExceptionInterface;
use Psr\Log\LoggerInterface;
use Incoviba\Common\Ideal;
use Incoviba\Model;
use Incoviba\Service\Worker;
class Service extends Ideal\Service implements Worker
{
public function __construct(protected ContainerInterface $container ,LoggerInterface $logger)
{
parent::__construct($logger);
}
public function execute(Model\Job $job): bool
{
$configuration = $job->configuration;
$serviceClass = $configuration['service'];
$method = $configuration['method'];
$params = $configuration['params'] ?? [];
try {
$service = $this->container->get($serviceClass);
} catch (NotFoundExceptionInterface | ContainerExceptionInterface $exception) {
$this->logger->error($exception->getMessage());
return false;
}
try {
$result = call_user_func_array([$service, $method], $params);
} catch (Exception $exception) {
$this->logger->error($exception->getMessage(), [
'Worker' => __CLASS__,
'job_id' => $job->id,
'service' => $serviceClass,
'method' => $method,
'params' => $params,
'exception' => $exception
]);
return false;
}
if (is_bool($result)) {
return $result;
}
return true;
}
}