10 Commits

Author SHA1 Message Date
186cd0f5b8 Reparaciones con Prueba 2025-05-05 19:01:15 -04:00
167d8e1ab7 PHP Dom en PHP 8.4 2025-05-05 19:00:41 -04:00
46802507a7 SII como provider de UF 2025-05-05 18:16:18 -04:00
aaf2ed7612 UF service en Venta 2025-05-05 18:15:48 -04:00
3ced9e40b1 const en Money y multi provider 2025-05-05 18:15:29 -04:00
594cb68b09 Provider SII 2025-05-05 18:14:50 -04:00
af68c4b8ec Ine null date 2025-05-05 18:14:20 -04:00
ea8f483dd5 FIX: Repository UF 2025-05-05 18:13:55 -04:00
5f53c77a1f Repository::getConnection 2025-05-05 18:13:12 -04:00
b9adb9108b Provider null date 2025-05-05 18:12:58 -04:00
12 changed files with 297 additions and 49 deletions

View File

@ -1,11 +1,12 @@
FROM php:8.2-fpm
FROM php:8.4-fpm
ENV TZ=America/Santiago
RUN apt-get update && apt-get install -y --no-install-recommends libzip-dev libicu-dev git libpng-dev unzip tzdata \
RUN apt-get update && apt-get install -y --no-install-recommends libzip-dev libicu-dev git \
libpng-dev unzip tzdata libxml2-dev \
&& rm -r /var/lib/apt/lists/* \
&& docker-php-ext-install pdo pdo_mysql zip intl gd bcmath \
&& pecl install xdebug-3.3.2 \
&& docker-php-ext-install pdo pdo_mysql zip intl gd bcmath dom \
&& pecl install xdebug-3.4.2 \
&& docker-php-ext-enable xdebug \
&& echo $TZ > /etc/timezone

View File

@ -8,9 +8,9 @@ interface Provider
{
/**
* @param string $money_symbol
* @param DateTimeInterface $dateTime
* @param ?DateTimeInterface $dateTime = null
* @return float
* @throws EmptyResponse
*/
public function get(string $money_symbol, DateTimeInterface $dateTime): float;
public function get(string $money_symbol, ?DateTimeInterface $dateTime = null): float;
}

View File

@ -24,6 +24,11 @@ abstract class Repository implements Define\Repository
return $this;
}
public function getConnection(): Define\Connection
{
return $this->connection;
}
public function load(array $data_row): Define\Model
{
$model = $this->create($data_row);

View File

@ -4,6 +4,7 @@
"type": "project",
"require": {
"ext-curl": "*",
"ext-dom": "*",
"ext-gd": "*",
"ext-openssl": "*",
"ext-pdo": "*",

View File

@ -22,8 +22,12 @@ return [
$ine = new Incoviba\Service\Money\Ine(new GuzzleHttp\Client([
'base_uri' => 'https://api-calculadora.ine.cl/ServiciosCalculadoraVariacion'
]));
$sii = new Incoviba\Service\Money\SII(new GuzzleHttp\Client([
'base_uri' => 'https://www.sii.cl/valores_y_fechas/'
]), $container->get(Incoviba\Service\Valor::class), $container->get(Incoviba\Repository\UF::class));
return (new Incoviba\Service\Money($container->get(Psr\Log\LoggerInterface::class)))
->register('uf', $mindicador)
->register('uf', $sii)
->register('usd', $mindicador)
->register('ipc', $ine);
},

View File

@ -2,8 +2,10 @@
namespace Incoviba\Repository;
use DateTimeInterface;
use DateTimeImmutable;
use Incoviba\Common\Define;
use Incoviba\Common\Ideal;
use Incoviba\Common\Ideal\Repository;
use Incoviba\Common\Implement;
use Incoviba\Model;
@ -13,6 +15,19 @@ class UF extends Ideal\Repository
{
return 'uf';
}
protected function getIndex(Define\Model $model): string
{
return 'fecha';
}
protected function setIndex(Define\Model &$model, mixed $value): Repository
{
$model->fecha = new DateTimeImmutable($value);
return $this;
}
protected function getKey(): string
{
return 'fecha';
}
public function create(?array $data = null): Model\UF
{

View File

@ -5,61 +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 = null): float
{
try {
return $this->getProvider('uf')->get(MiIndicador::UF, $dateTime);
} catch (EmptyResponse $exception) {
$this->logger->debug($exception);
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,10 +9,6 @@ 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) {}
/**
@ -42,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,133 @@
<?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 Service\Valor $valorService,
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);
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();
}
$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->valorService->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();
}
}
}
}

View File

@ -31,7 +31,7 @@ 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);
@ -177,6 +177,15 @@ class Venta extends Service
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]));
@ -186,6 +195,7 @@ class Venta extends Service
$venta->addFactory('currentEstado', (new Implement\Repository\Factory())
->setCallable([$this->estadoVentaRepository, 'fetchCurrentByVenta'])
->setArgs([$venta->id]));
return $venta;
}
@ -207,7 +217,7 @@ class Venta extends Service
public function add(array $data): Model\Venta
{
$fecha = new DateTimeImmutable($data['fecha_venta']);
$data['uf'] = $this->moneyService->getUF($fecha);
$data['uf'] = $this->ufService->get($fecha);
$propietario = $this->addPropietario($data);
$propiedad = $this->addPropiedad($data);
$formaPago = $this->addFormaPago($data);
@ -445,7 +455,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,
@ -471,7 +481,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']),
@ -489,7 +499,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

@ -0,0 +1,62 @@
<?php
namespace ProVM\Unit\Service\Money;
use PDO;
use PDOStatement;
use GuzzleHttp\Client;
use PHPUnit\Framework\TestCase;
use Incoviba\Service;
use Incoviba\Repository;
use Incoviba\Common\Define;
class SIITest extends TestCase
{
protected Client $client;
protected Service\UF $ufService;
protected Service\Valor $valorService;
protected \PDO $pdo;
protected Define\Connection $connection;
protected \PDOStatement $statement;
protected Repository\UF $ufRepository;
protected function setUp(): void
{
$this->client = new Client(['base_uri' => 'https://www.sii.cl/valores_y_fechas/']);
$this->ufService = $this->getMockBuilder(Service\UF::class)
->disableOriginalConstructor()->getMock();
$this->ufService->method('get')->willReturn(1.0);
$this->valorService = new Service\Valor($this->ufService);
$this->pdo = $this->getMockBuilder(PDO::class)
->disableOriginalConstructor()->getMock();
$this->pdo->method('beginTransaction')->willReturn(true);
$this->pdo->method('commit')->willReturn(true);
#$this->pdo->method('rollBack')->willReturn(null);
$this->statement = $this->getMockBuilder(PDOStatement::class)->getMock();
$this->statement->method('fetchAll')->willReturn([]);
$this->connection = $this->getMockBuilder(Define\Connection::class)
->disableOriginalConstructor()->getMock();
$this->connection->method('getPDO')->willReturn($this->pdo);
$this->connection->method('query')->willReturn($this->statement);
$this->connection->method('execute')->willReturn($this->statement);
$this->ufRepository = $this->getMockBuilder(Repository\UF::class)
->disableOriginalConstructor()->getMock();
$this->ufRepository->method('getConnection')->willReturn($this->connection);
$this->ufRepository->method('getTable')->willReturn('uf');
}
public function testGet(): void
{
$provider = new Service\Money\SII($this->client, $this->valorService, $this->ufRepository);
$date = new \DateTimeImmutable('2025-05-05');
$expected = 39107.9;
$this->assertEquals($expected, $provider->get(Service\Money::UF, $date));
}
}