diff --git a/app/resources/routes/api/contabilidad/movimientos.php b/app/resources/routes/api/contabilidad/movimientos.php new file mode 100644 index 0000000..545dd98 --- /dev/null +++ b/app/resources/routes/api/contabilidad/movimientos.php @@ -0,0 +1,6 @@ +group('/movimiento/{movimiento_id}', function($app) { + $app->post('/detalles', [Movimientos::class, 'detalles']); +}); diff --git a/app/resources/views/contabilidad/cartolas/diaria.blade.php b/app/resources/views/contabilidad/cartolas/diaria.blade.php index 9750f1e..316246b 100644 --- a/app/resources/views/contabilidad/cartolas/diaria.blade.php +++ b/app/resources/views/contabilidad/cartolas/diaria.blade.php @@ -52,6 +52,8 @@ Cargo Abono Saldo + Centro de Costo + Detalle Orden @@ -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( $('').append( - $('').html(this.inmobiliaria.razon) + '' + this.inmobiliaria.razon + '' + "\n" + + '' + this.cuenta.descripcion + '' + "\n" + + '' + dateFormatter.format(row.fecha) + '' + "\n" + + '' + row.glosa + '' + "\n" + + '' + (row.cargo === 0 ? '' : numberFormatter.format(row.cargo)) + '' + "\n" + + '' + (row.abono === 0 ? '' : numberFormatter.format(row.abono)) + '' + "\n" + + '' + (row.saldo === 0 ? '' : numberFormatter.format(row.saldo)) + '' + "\n" ).append( - $('').html(this.cuenta.descripcion) + $('').append( + this.draw().centroCosto(idx) + ) ).append( - $('').html(dateFormatter.format(row.fecha)) - ).append( - $('').html(row.glosa) - ).append( - $('').addClass('right aligned').html(row.cargo === 0 ? '' : numberFormatter.format(row.cargo)) - ).append( - $('').addClass('right aligned').html(row.abono === 0 ? '' : numberFormatter.format(row.abono)) - ).append( - $('').addClass('right aligned').html(row.saldo === 0 ? '' : numberFormatter.format(row.saldo)) + $('').append( + this.draw().detalle(idx) + ) ).append( $('').html(idx + 1) ) ) }) }, + centroCosto: idx => { + const centros = JSON.parse('{!! json_encode($centrosCostos) !!}') + const menu = $('
').addClass('menu') + centros.forEach(centro => { + menu.append( + '
' + + centro.id + ' - ' + centro.descripcion + + '
' + ) + }) + const dropdown = $('
').addClass('ui search selection dropdown').attr('data-idx', idx).html( + '' + "\n" + + '' + "\n" + + '
Centro de Costo
' + "\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( $('
').addClass('fields').attr('data-movimiento', idx).append( - $('
').addClass('field').append( - $('').html('Glosa') - ).append( - $('').attr('type', 'text').attr('name', 'glosa' + idx) - ) - ).append( - $('
').addClass('field').append( - $('').html('Cargo') - ).append( - $('
').addClass('ui left labeled input').append( - $('
').addClass('ui basic label').html('$') - ).append( - $('').attr('type', 'text').attr('name', 'cargo' + idx) - ) - ) - ).append( - $('
').addClass('field').append( - $('').html('Abono') - ).append( - $('
').addClass('ui left labeled input').append( - $('
').addClass('ui basic label').html('$') - ).append( - $('').attr('type', 'text').attr('name', 'abono' + idx) - ) - ) - ).append( - $('
').addClass('field').append( - $('').html('Saldo') - ).append( - $('
').addClass('ui left labeled input').append( - $('
').addClass('ui basic label').html('$') - ).append( - $('').attr('type', 'text').attr('name', 'saldo' + idx) - ) - ) + '
' + "\n" + + '' + "\n" + + '' + "\n" + + '
' + "\n" + + '
' + "\n" + + '' + "\n" + + '
' + "\n" + + '
$
' + "\n" + + '' + "\n" + + '
' + "\n" + + '
' + "\n" + + '
' + "\n" + + '' + "\n" + + '
' + "\n" + + '
$
' + "\n" + + '' + "\n" + + '
' + "\n" + + '
' + "\n" + + '
' + "\n" + + '' + "\n" + + '
' + "\n" + + '
$
' + "\n" + + '' + "\n" + + '
' + "\n" + + '
' ).append( $('
').addClass('field').append( $('').html('Eliminar') diff --git a/app/setup/setups/services.php b/app/setup/setups/services.php index aaec098..01dd83e 100644 --- a/app/setup/setups/services.php +++ b/app/setup/setups/services.php @@ -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)) diff --git a/app/src/Controller/API/Contabilidad/Movimientos.php b/app/src/Controller/API/Contabilidad/Movimientos.php new file mode 100644 index 0000000..e792b86 --- /dev/null +++ b/app/src/Controller/API/Contabilidad/Movimientos.php @@ -0,0 +1,50 @@ +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); + } +} diff --git a/app/src/Controller/Contabilidad.php b/app/src/Controller/Contabilidad.php index cf83532..4f9b618 100644 --- a/app/src/Controller/Contabilidad.php +++ b/app/src/Controller/Contabilidad.php @@ -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, diff --git a/app/src/Model/CentroCosto.php b/app/src/Model/CentroCosto.php index 2e59fdb..fd4d5ef 100644 --- a/app/src/Model/CentroCosto.php +++ b/app/src/Model/CentroCosto.php @@ -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, diff --git a/app/src/Model/Movimiento.php b/app/src/Model/Movimiento.php index 6ca1a36..e89eb39 100644 --- a/app/src/Model/Movimiento.php +++ b/app/src/Model/Movimiento.php @@ -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 ]); } } diff --git a/app/src/Model/Movimiento/Detalle.php b/app/src/Model/Movimiento/Detalle.php new file mode 100644 index 0000000..ec56449 --- /dev/null +++ b/app/src/Model/Movimiento/Detalle.php @@ -0,0 +1,21 @@ + $this->movimiento->id, + 'centro_costo' => $this->centroCosto, + 'detalle' => $this->detalle + ]; + } +} diff --git a/app/src/Repository/Movimiento/Detalle.php b/app/src/Repository/Movimiento/Detalle.php new file mode 100644 index 0000000..cdeed75 --- /dev/null +++ b/app/src/Repository/Movimiento/Detalle.php @@ -0,0 +1,60 @@ +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'; + } +} diff --git a/app/src/Service/Cartola.php b/app/src/Service/Cartola.php index c566604..488feb8 100644 --- a/app/src/Service/Cartola.php +++ b/app/src/Service/Cartola.php @@ -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; diff --git a/app/src/Service/Movimiento.php b/app/src/Service/Movimiento.php new file mode 100644 index 0000000..5d9ad67 --- /dev/null +++ b/app/src/Service/Movimiento.php @@ -0,0 +1,43 @@ +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; + } +}