Movimiento + Detalles

This commit is contained in:
Juan Pablo Vial
2024-03-04 19:55:49 -03:00
parent 48bfe5d8ab
commit c7dd309185
11 changed files with 334 additions and 52 deletions

View File

@ -0,0 +1,6 @@
<?php
use Incoviba\Controller\API\Contabilidad\Movimientos;
$app->group('/movimiento/{movimiento_id}', function($app) {
$app->post('/detalles', [Movimientos::class, 'detalles']);
});

View File

@ -52,6 +52,8 @@
<th class="right aligned">Cargo</th>
<th class="right aligned">Abono</th>
<th class="right aligned">Saldo</th>
<th>Centro de Costo</th>
<th>Detalle</th>
<th>Orden</th>
</tr>
</thead>
@ -185,6 +187,46 @@
}
}
}
update() {
return {
centro: (idx, centro_id) => {
const id = this.movimientos[idx].id
const url = '{{$urls->api}}/contabilidad/movimiento/' + id + '/detalles'
const body = new FormData()
body.set('centro_id', centro_id)
return fetchAPI(url, {method: 'post', body}).then(response => {
if (!response) {
return
}
response.json().then(json => {
if (!json.status) {
return
}
const dropdown = $(".dropdown[data-idx='" + idx + "']")
dropdown.dropdown('set selected', json.centro.id, true)
})
})
},
detalle: (idx, detalle) => {
const id = this.movimientos[idx].id
const url = '{{$urls->api}}/contabilidad/movimiento/' + id + '/detalles'
const body = new FormData()
body.set('detalle', detalle)
return fetchAPI(url, {method: 'post', body}).then(response => {
if (!response) {
return
}
response.json().then(json => {
if (!json.status) {
return
}
const input = $("input[data-idx='" + idx + "']")
input.val(json.detalle)
})
})
}
}
}
draw() {
return {
form: ($form, inmobiliarias, first = false) => {
@ -339,25 +381,73 @@
this.movimientos.forEach((row, idx) => {
$tbody.append(
$('<tr></tr>').append(
$('<td></td>').html(this.inmobiliaria.razon)
'<td>' + this.inmobiliaria.razon + '</td>' + "\n"
+ '<td>' + this.cuenta.descripcion + '</td>' + "\n"
+ '<td>' + dateFormatter.format(row.fecha) + '</td>' + "\n"
+ '<td>' + row.glosa + '</td>' + "\n"
+ '<td class="right aligned">' + (row.cargo === 0 ? '' : numberFormatter.format(row.cargo)) + '</td>' + "\n"
+ '<td class="right aligned">' + (row.abono === 0 ? '' : numberFormatter.format(row.abono)) + '</td>' + "\n"
+ '<td class="right aligned">' + (row.saldo === 0 ? '' : numberFormatter.format(row.saldo)) + '</td>' + "\n"
).append(
$('<td></td>').html(this.cuenta.descripcion)
$('<td></td>').append(
this.draw().centroCosto(idx)
)
).append(
$('<td></td>').html(dateFormatter.format(row.fecha))
).append(
$('<td></td>').html(row.glosa)
).append(
$('<td></td>').addClass('right aligned').html(row.cargo === 0 ? '' : numberFormatter.format(row.cargo))
).append(
$('<td></td>').addClass('right aligned').html(row.abono === 0 ? '' : numberFormatter.format(row.abono))
).append(
$('<td></td>').addClass('right aligned').html(row.saldo === 0 ? '' : numberFormatter.format(row.saldo))
$('<td></td>').append(
this.draw().detalle(idx)
)
).append(
$('<td></td>').html(idx + 1)
)
)
})
},
centroCosto: idx => {
const centros = JSON.parse('{!! json_encode($centrosCostos) !!}')
const menu = $('<div></div>').addClass('menu')
centros.forEach(centro => {
menu.append(
'<div class="item" data-value="' + centro.id + '">'
+ centro.id + ' - ' + centro.descripcion
+ '</div>'
)
})
const dropdown = $('<div></div>').addClass('ui search selection dropdown').attr('data-idx', idx).html(
'<input type="hidden" name="centro" />' + "\n" +
'<i class="dropdown icon"></i>' + "\n" +
'<div class="default text">Centro de Costo</div>' + "\n"
).append(menu)
dropdown.dropdown({
onChange: (value, text, $element) => {
const idx = $element.parent().parent().data('idx')
this.update().centro(idx, value)
}
})
if (this.movimientos[idx].centro !== '') {
const cid = centros.findIndex(centro => centro.descripcion === this.movimientos[idx].centro)
dropdown.dropdown('set selected', centros[cid].id, true)
}
return dropdown
},
detalle: idx => {
const detalle = document.createElement('input')
detalle.type = 'text'
detalle.name = 'detalle' + idx
detalle.placeholder = 'Detalle'
detalle.setAttribute('data-idx', idx)
const input = document.createElement('div')
input.className = 'ui input'
input.appendChild(detalle)
if (this.movimientos[idx].detalle !== '') {
detalle.value = this.movimientos[idx].detalle
}
detalle.addEventListener('blur', event => {
const idx = event.currentTarget.dataset['idx']
this.update().detalle(idx, event.currentTarget.value)
})
return $(input)
}
}
}
}
@ -370,6 +460,8 @@
'cargo',
'abono',
'saldo',
'centro',
'detalle',
'orden'
];
@endphp
@ -388,7 +480,7 @@
width: '{{round((1/(count($columns) + 3 - 1)) * 100,2)}}%'
},
{
targets: [{{implode(',', array_keys(array_filter($columns, function($column) {return in_array($column, ['sociedad', 'cuenta', 'glosa']);})))}}],
targets: [{{implode(',', array_keys(array_filter($columns, function($column) {return in_array($column, ['sociedad', 'cuenta', 'glosa', 'centro', 'detalle']);})))}}],
width: '{{round((1/(count($columns) + 3 - 1)) * 2 * 100, 2)}}%'
},
{
@ -506,12 +598,15 @@
const fecha = new Date(row.fecha)
fecha.setDate(fecha.getDate() + 1)
this.data.cartolas[cartolaIdx].movimientos[idx] = {
id: row.id,
fecha: fecha,
glosa: row.glosa,
documento: row.documento,
cargo: row.cargo,
abono: row.abono,
saldo: row.saldo
saldo: row.saldo,
centro: row.detalles?.centro_costo.descripcion ?? '',
detalle: row.detalles?.detalle ?? ''
}
})
const ayer = new Date(this.data.cartolas[cartolaIdx].fecha.getTime())
@ -629,41 +724,31 @@
movimiento: idx => {
$(this.ids.movimientos).append(
$('<div></div>').addClass('fields').attr('data-movimiento', idx).append(
$('<div></div>').addClass('field').append(
$('<label></label>').html('Glosa')
).append(
$('<input />').attr('type', 'text').attr('name', 'glosa' + idx)
)
).append(
$('<div></div>').addClass('field').append(
$('<label></label>').html('Cargo')
).append(
$('<div></div>').addClass('ui left labeled input').append(
$('<div></div>').addClass('ui basic label').html('$')
).append(
$('<input />').attr('type', 'text').attr('name', 'cargo' + idx)
)
)
).append(
$('<div></div>').addClass('field').append(
$('<label></label>').html('Abono')
).append(
$('<div></div>').addClass('ui left labeled input').append(
$('<div></div>').addClass('ui basic label').html('$')
).append(
$('<input />').attr('type', 'text').attr('name', 'abono' + idx)
)
)
).append(
$('<div></div>').addClass('field').append(
$('<label></label>').html('Saldo')
).append(
$('<div></div>').addClass('ui left labeled input').append(
$('<div></div>').addClass('ui basic label').html('$')
).append(
$('<input />').attr('type', 'text').attr('name', 'saldo' + idx)
)
)
'<div class="field">' + "\n"
+ '<label>Glosa</label>' + "\n"
+ '<input type="text" name="glosa' + idx + '" />' + "\n"
+ '</div>' + "\n" +
'<div class="field">' + "\n"
+ '<label>Cargo</label>' + "\n"
+ '<div class="ui left labeled input">' + "\n"
+ '<div class="ui basic label">$</div>' + "\n"
+ '<input type="text" name="cargo' + idx + '" />' + "\n"
+ '</div>' + "\n"
+ '</div>' + "\n" +
'<div class="field">' + "\n"
+ '<label>Abono</label>' + "\n"
+ '<div class="ui left labeled input">' + "\n"
+ '<div class="ui basic label">$</div>' + "\n"
+ '<input type="text" name="abono' + idx + '" />' + "\n"
+ '</div>' + "\n"
+ '</div>' + "\n" +
'<div class="field">' + "\n"
+ '<label>Saldo</label>' + "\n"
+ '<div class="ui left labeled input">' + "\n"
+ '<div class="ui basic label">$</div>' + "\n"
+ '<input type="text" name="saldo' + idx + '" />' + "\n"
+ '</div>' + "\n"
+ '</div>'
).append(
$('<div></div>').addClass('field').append(
$('<label></label>').html('Eliminar')

View File

@ -40,6 +40,7 @@ return [
$container->get(Incoviba\Repository\Inmobiliaria::class),
$container->get(Incoviba\Repository\Inmobiliaria\Cuenta::class),
$container->get(Incoviba\Repository\Movimiento::class),
$container->get(Incoviba\Service\Movimiento::class),
$container->get(Incoviba\Repository\Cartola::class)
))
->register('security', $container->get(Incoviba\Service\Cartola\Security::class))

View File

@ -0,0 +1,50 @@
<?php
namespace Incoviba\Controller\API\Contabilidad;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Incoviba\Common\Implement\Exception\EmptyResult;
use Incoviba\Controller\API\withJson;
use Incoviba\Common\Ideal;
use Incoviba\Repository;
use Incoviba\Service;
class Movimientos extends Ideal\Controller
{
use withJson;
public function detalles(ServerRequestInterface $request, ResponseInterface $response,
Service\Movimiento $movimientoService,
Repository\CentroCosto $centroCostoRepository, int $movimiento_id): ResponseInterface
{
$body = $request->getParsedBody();
$output = [
'movimiento_id' => $movimiento_id,
'input' => $body,
'status' => false,
'movimiento' => null,
'centro' => null,
'detalle' => ''
];
try {
$movimiento = $movimientoService->getById($movimiento_id);
$output['movimiento'] = $movimiento;
$data = [];
if (isset($body['centro_id'])) {
$centro = $centroCostoRepository->fetchById($body['centro_id']);
$data['centro_costo_id'] = $centro->id;
}
if (isset($body['detalle'])) {
$data['detalle'] = $body['detalle'];
}
$movimientoService->setDetalles($movimiento, $data);
if (isset($body['centro_id'])) {
$output['centro'] = $centro;
}
if (isset($body['detalle'])) {
$output['detalle'] = $body['detalle'];
}
} catch (EmptyResult) {}
return $this->withJson($response, $output);
}
}

View File

@ -18,7 +18,8 @@ class Contabilidad extends Controller
public function diaria(ServerRequestInterface $request, ResponseInterface $response, View $view,
Service\Redis $redisService,
Repository\Inmobiliaria $inmobiliariaRepository): ResponseInterface
Repository\Inmobiliaria $inmobiliariaRepository,
Repository\CentroCosto $centroCostoRepository): ResponseInterface
{
$redisKey = 'inmobiliarias';
$inmobiliarias = [];
@ -30,7 +31,8 @@ class Contabilidad extends Controller
$this->saveRedis($redisService, $redisKey, $inmobiliarias, 30 * 24 * 60 * 60);
} catch (EmptyResult) {}
}
return $view->render($response, 'contabilidad.cartolas.diaria', compact('inmobiliarias'));
$centrosCostos = $centroCostoRepository->fetchAll();
return $view->render($response, 'contabilidad.cartolas.diaria', compact('inmobiliarias', 'centrosCostos'));
}
public function depositos(ServerRequestInterface $request, ResponseInterface $response, View $view,
Service\Redis $redisService,

View File

@ -13,7 +13,7 @@ class CentroCosto extends Ideal\Model
public function jsonSerialize(): mixed
{
return array_map(parent::jsonSerialize(), [
return array_merge(parent::jsonSerialize(), [
'tipo_centro' => $this->tipoCentro,
'categoria' => $this->categoria,
'tipo_cuenta' => $this->tipoCuenta,

View File

@ -3,6 +3,7 @@ namespace Incoviba\Model;
use DateTimeInterface;
use Incoviba\Common\Ideal;
use Incoviba\Model\Movimiento\Detalle;
class Movimiento extends Ideal\Model
{
@ -14,6 +15,15 @@ class Movimiento extends Ideal\Model
public int $abono;
public int $saldo;
protected ?Detalle $detalles;
public function getDetalles(): ?Detalle
{
if (!isset($this->detalles)) {
$this->detalles = $this->runFactory('detalles');
}
return $this->detalles;
}
public function jsonSerialize(): mixed
{
return array_merge(parent::jsonSerialize(), [
@ -23,7 +33,8 @@ class Movimiento extends Ideal\Model
'documento' => $this->documento,
'cargo' => $this->cargo,
'abono' => $this->abono,
'saldo' => $this->saldo
'saldo' => $this->saldo,
'detalles' => $this->getDetalles() ?? null
]);
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace Incoviba\Model\Movimiento;
use Incoviba\Common\Ideal;
use Incoviba\Model;
class Detalle extends Ideal\Model
{
public Model\Movimiento $movimiento;
public ?Model\CentroCosto $centroCosto;
public ?string $detalle;
public function jsonSerialize(): mixed
{
return [
'movimiento_id' => $this->movimiento->id,
'centro_costo' => $this->centroCosto,
'detalle' => $this->detalle
];
}
}

View File

@ -0,0 +1,60 @@
<?php
namespace Incoviba\Repository\Movimiento;
use Incoviba\Common\Define;
use Incoviba\Common\Ideal;
use Incoviba\Common\Implement;
use Incoviba\Model;
use Incoviba\Repository;
class Detalle extends Ideal\Repository
{
public function __construct(Define\Connection $connection, protected Repository\Movimiento $movimientoRepository,
protected Repository\CentroCosto $centroCostoRepository)
{
parent::__construct($connection);
$this->setTable('movimientos_detalles');
}
public function create(?array $data = null): Model\Movimiento\Detalle
{
$map = (new Implement\Repository\MapperParser(['detalle']))
->register('movimiento_id', (new Implement\Repository\Mapper())
->setProperty('movimiento')
->setFunction(function(array $data) {
return $this->movimientoRepository->fetchById($data['movimiento_id']);
}))
->register('centro_costo_id', (new Implement\Repository\Mapper())
->setProperty('centroCosto')
->setFunction(function(array $data) {
return $this->centroCostoRepository->fetchById($data['centro_costo_id']);
}));
return $this->parseData(new Model\Movimiento\Detalle(), $data, $map);
}
public function save(Define\Model $model): Model\Movimiento\Detalle
{
$this->saveNew(
['movimiento_id', 'centro_costo_id', 'detalle'],
[$model->movimiento->id, $model->centroCosto->id, $model->detalle]
);
return $model;
}
public function edit(Define\Model $model, array $new_data): Model\Movimiento\Detalle
{
return $this->update($model, ['movimiento_id', 'centro_costo_id', 'detalle'], $new_data);
}
public function fetchByMovimiento(int $movimiento_id): Model\Movimiento\Detalle
{
$query = $this->connection->getQueryBuilder()
->select()
->from($this->getTable())
->where('movimiento_id = ?');
return $this->fetchOne($query, [$movimiento_id]);
}
protected function getKey(): string
{
return 'movimiento_id';
}
}

View File

@ -21,6 +21,7 @@ class Cartola extends Service
protected Repository\Inmobiliaria $inmobiliariaRepository,
protected Repository\Inmobiliaria\Cuenta $cuentaRepository,
protected Repository\Movimiento $movimientoRepository,
protected Movimiento $movimientoService,
protected Repository\Cartola $cartolaRepository) {
parent::__construct($logger);
}
@ -51,6 +52,7 @@ class Cartola extends Service
$movimientos = [];
foreach ($ms as $m) {
$movimiento = $this->buildMovimiento($cuenta, $m);
$movimiento = $this->movimientoService->process($movimiento);
if ($movimiento->fecha->getTimestamp() === $fecha->getTimestamp()) {
$movimientos []= $movimiento;
@ -78,6 +80,7 @@ class Cartola extends Service
$dataMovimiento['fecha'] = $fecha->format('Y-m-d');
$dataMovimiento['documento'] = '';
$movimiento = $this->buildMovimiento($cuenta, $dataMovimiento);
$movimiento = $this->movimientoService->process($movimiento);
$movimientos []= $movimiento;
$cartolaData['cargos'] += $movimiento->cargo;

View File

@ -0,0 +1,43 @@
<?php
namespace Incoviba\Service;
use DateTimeInterface;
use Psr\Log\LoggerInterface;
use Incoviba\Common\Ideal\Service;
use Incoviba\Common\Implement;
use Incoviba\Repository;
use Incoviba\Model;
class Movimiento extends Service
{
public function __construct(LoggerInterface $logger, protected Repository\Movimiento $movimientoRepository,
protected Repository\Movimiento\Detalle $detalleRepository)
{
parent::__construct($logger);
}
public function getById(int $movimiento_id): Model\Movimiento
{
return $this->process($this->movimientoRepository->fetchById($movimiento_id));
}
public function setDetalles(Model\Movimiento $movimiento, array $data): Model\Movimiento
{
try {
$detalles = $this->detalleRepository->fetchByMovimiento($movimiento->id);
$this->detalleRepository->edit($detalles, $data);
} catch (Implement\Exception\EmptyResult) {
$data['movimiento_id'] = $movimiento->id;
$detalles = $this->detalleRepository->create($data);
$this->detalleRepository->save($detalles);
}
return $movimiento;
}
public function process(Model\Movimiento $movimiento): Model\Movimiento
{
$movimiento->addFactory('detalles', (new Implement\Repository\Factory())->setCallable(function(int $movimiento_id) {
return $this->detalleRepository->fetchByMovimiento($movimiento_id);
})->setArgs(['movimiento_id' => $movimiento->id]));
return $movimiento;
}
}