Co-authored-by: Juan Pablo Vial <jpvialb@incoviba.cl>
Reviewed-on: #45
This commit is contained in:
2025-10-04 11:40:52 -03:00
parent 6ddc48ec60
commit 742de657c5
815 changed files with 62089 additions and 3287 deletions

View File

@ -52,8 +52,7 @@
<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 class="right aligned"></th>
<th>Orden</th>
</tr>
</thead>
@ -115,10 +114,85 @@
</div>
</div>
</div>
<div class="ui modal" id="edit_modal" data-cartola="">
<div class="header">
Movimiento
<div class="meta">
<span id="edit_inmobiliaria"></span> |
<span id="edit_cuenta"></span> |
<span id="edit_fecha"></span> |
<span id="edit_glosa"></span> |
<span id="edit_cargo"></span> |
<span id="edit_abono"></span> |
<span id="edit_saldo"></span>
</div>
</div>
<div class="content">
<form class="ui form" id="edit_form">
<input type="hidden" name="movimiento_id" value="" />
<div class="equal width fields">
<div class="field">
<label>Centro de Costo</label>
<div class="ui search selection dropdown">
<input type="hidden" name="centro_costo_id" />
<i class="dropdown icon"></i>
<div class="default text">Centro de Costo</div>
<div class="menu">
@foreach($centrosCostos as $centro)
<div class="item" data-value="{{$centro->id}}">
{{$centro->id}} - {{$centro->descripcion}}
</div>
@endforeach
</div>
</div>
</div>
<div class="field">
<label>Categoría</label>
<div class="ui input">
<input type="text" name="categoria" />
</div>
</div>
<div class="field">
<label>Detalle</label>
<div class="ui input">
<input type="text" name="detalle" />
</div>
</div>
</div>
<div class="equal width fields">
<div class="field">
<label>RUT</label>
<div class="ui right labeled input">
<input type="text" name="rut" />
<div class="ui basic label">-<span id="digito"></span></div>
</div>
</div>
<div class="field">
<label>Nombres</label>
<div class="ui input">
<input type="text" name="nombres" />
</div>
</div>
<div class="field">
<label>Identificador</label>
<div class="ui input">
<input type="text" name="identificador" />
</div>
</div>
</div>
</form>
</div>
<div class="actions">
<button class="ui approve button">
Editar
</button>
</div>
</div>
@endsection
@include('layout.head.styles.datatables')
@include('layout.body.scripts.datatables')
@include('layout.body.scripts.rut')
@push('page_scripts')
<script>
@ -126,7 +200,8 @@
idx
inmobiliaria = {
rut: 0,
razon: ''
razon: '',
sigla: ''
}
cuenta = {
id: 0,
@ -187,46 +262,6 @@
}
}
}
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) => {
@ -326,6 +361,10 @@
onChange: (value, text, $choice) => {
this.inmobiliaria.rut = value
this.inmobiliaria.razon = text
const inmobiliaria = diaria.data.inmobiliarias.find(inmobiliaria => inmobiliaria.rut === parseInt(value))
if (typeof inmobiliaria !== 'undefined') {
this.inmobiliaria.sigla = inmobiliaria.sigla
}
this.get().bancos(value).then(() => {
$cuentas_dropdown.dropdown('change values', this.bancos)
})
@ -380,74 +419,42 @@
cartola: ($tbody, dateFormatter, numberFormatter) => {
this.movimientos.forEach((row, idx) => {
$tbody.append(
$('<tr></tr>').append(
'<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>').append(
this.draw().centroCosto(idx)
)
).append(
$('<td></td>').append(
this.draw().detalle(idx)
)
).append(
$('<td></td>').html(idx + 1)
)
[
'<tr>',
`<td>${this.inmobiliaria.sigla ?? this.inmobiliaria.razon}</td>`,
`<td>${this.cuenta.descripcion}</td>`,
`<td>${dateFormatter.format(row.fecha)}</td>`,
`<td>${row.glosa}</td>`,
`<td class="right aligned">${row.cargo === 0 ? '' : numberFormatter.format(row.cargo)}</td>`,
`<td class="right aligned">${row.abono === 0 ? '' : numberFormatter.format(row.abono)}</td>`,
`<td class="right aligned">${row.saldo === 0 ? '' : numberFormatter.format(row.saldo)}</td>`,
'<td class="center aligned">',
`<button class="ui icon button edit_movimiento" data-movimiento="${idx}">`,
'<i class="edit icon"></i>',
'</button>',
'</td>',
`<td>${(idx + 1).toString()}</td>`,
].join("\n")
)
})
Array.from(document.getElementsByClassName('edit_movimiento')).forEach(button => {
button.addEventListener('click', function(clickEvent) {
const idx = clickEvent.currentTarget.dataset['movimiento']
const movimiento = this.movimientos[idx]
const $modal = $('#edit_modal')
$modal.attr('data-cartola', this.idx)
$modal.find('#edit_inmobiliaria').html(this.inmobiliaria.razon)
$modal.find('#edit_cuenta').html(this.cuenta.descripcion)
$modal.find('#edit_fecha').html(dateFormatter.format(movimiento.fecha))
$modal.find('#edit_glosa').html(movimiento.glosa)
$modal.find('#edit_cargo').html(numberFormatter.format(movimiento.cargo))
$modal.find('#edit_abono').html(numberFormatter.format(movimiento.abono))
$modal.find('#edit_saldo').html(numberFormatter.format(movimiento.saldo))
$modal.find('input[name="movimiento_id"]').val(movimiento.id)
$modal.modal('show')
}.bind(this))
})
},
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)
}
}
}
}
@ -460,8 +467,7 @@
'cargo',
'abono',
'saldo',
'centro',
'detalle',
'edit',
'orden'
];
@endphp
@ -471,17 +477,18 @@
inmobiliarias: JSON.parse('{!! json_encode($inmobiliarias) !!}'),
cartolasIdx: [],
cartolas: [],
movimientos: [],
},
dataTableConfig: {
order: [[{{array_search('orden', $columns)}}, 'asc']],
columnDefs: [
{
targets: [{{implode(',', array_keys(array_filter($columns, function($column) {return in_array($column, ['fecha', 'cargo', 'abono', 'saldo']);})))}}],
width: '{{round((1/(count($columns) + 3 - 1)) * 100,2)}}%'
targets: [{{implode(',', array_keys(array_filter($columns, function($column) {return in_array($column, ['fecha', 'cargo', 'abono', 'saldo', 'edit']);})))}}],
width: '{{round((1/(count($columns) + 2)) * 100, 2)}}%'
},
{
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)}}%'
targets: [{{implode(',', array_keys(array_filter($columns, function($column) {return in_array($column, ['sociedad', 'cuenta', 'glosa']);})))}}],
width: '{{round((1/(count($columns) + 2)) * 2 * 100, 2)}}%'
},
{
targets: [{{array_search('orden', $columns)}}],
@ -605,8 +612,12 @@
cargo: row.cargo,
abono: row.abono,
saldo: row.saldo,
centro: row.detalles?.centro_costo.descripcion ?? '',
detalle: row.detalles?.detalle ?? ''
centro: row.detalles?.centro_costo?.id ?? '',
detalle: row.detalles?.detalle ?? '',
categoria: row.detalles?.categoria ?? '',
rut: row.detalles?.rut ?? '',
nombres: row.detalles?.nombres ?? '',
identificador: row.detalles?.identificador ?? ''
}
})
const ayer = new Date(this.data.cartolas[cartolaIdx].fecha.getTime())
@ -638,6 +649,51 @@
$(this.ids.table.base).DataTable(this.dataTableConfig)
this.cartolas().add()
$(this.ids.editModal).modal({
onApprove: $element => {
const idx = $(this.ids.editModal).data('cartola')
const id = $(this.ids.editModal).find('input[name="movimiento_id"]').val()
const body = new FormData($(this.ids.editModal).find('form')[0])
body.set('idx', idx)
if (body.has('rut')) {
body.set('rut', body.get('rut').replace(/\D/g, ''))
body.set('digito', Rut.digitoVerificador(body.get('rut')))
}
const url = '{{$urls->api}}/contabilidad/movimiento/' + id + '/detalles'
return fetchAPI(url, {method: 'post', body}).then(response => {
if (!response) {
return
}
response.json().then(json => {
if (!json.status || json.movimiento === null || json.movimiento.detalles === null) {
return
}
const idx = diaria.data.cartolas.findIndex(cartola => cartola.idx = json.input.idx)
const movimiento = diaria.data.cartolas[idx].movimientos.find(movimiento => movimiento.id === id)
movimiento.centro = json.movimiento.detalles.centro_costo.id
movimiento.categoria = json.movimiento.detalles.categoria
movimiento.detalle = json.movimiento.detalles.detalle
movimiento.rut = json.movimiento.detalles.rut
movimiento.digito = json.movimiento.detalles.digito
movimiento.nombres = json.movimiento.detalles.nombres
movimiento.identificador = json.movimiento.detalles.identificador
})
})
}
})
$(this.ids.editModal).find('.dropdown').dropdown()
$(this.ids.editModal).find('[name="rut"]').on('input', function(event) {
const rut = event.currentTarget.value
const rut_clean = rut.replace(/\D/g, '')
event.currentTarget.value = Rut.format(rut_clean)
document.getElementById('digito').innerHTML = Rut.digitoVerificador(rut_clean)
}.bind(this))
const rut = $(this.ids.editModal).find('[name="rut"]')
const rut_clean = rut.val().replace(/\D/g, '')
rut.val(Rut.format(rut_clean))
document.getElementById('digito').innerHTML = Rut.digitoVerificador(rut_clean)
}
}
const manual = {
@ -807,7 +863,8 @@
add: '#add_button',
process: '#process_button'
},
loader: '#loader'
loader: '#loader',
editModal: '#edit_modal'
})
manual.setup({
modal: '#manual_modal',

View File

@ -0,0 +1,394 @@
@extends('layout.base')
@section('page_content')
<div class="ui container">
<h1 class="ui header">
Importar Cartola Diaria
</h1>
<div class="ui grid">
<div class="right aligned sixteen wide column">
<button class="ui green icon button" id="add_button">
Agregar
<i class="plus icon"></i>
</button>
</div>
</div>
<form class="ui form" id="cartola_form">
</form>
<div class="ui two columns grid">
<div class="column">
<button class="ui icon button" id="process_button">
<i class="file excel icon"></i>
Procesar
</button>
</div>
<div class="right aligned column">
<div class="ui inline active loader" id="loader"></div>
</div>
</div>
</div>
<table class="ui celled table" id="movimientos" style="display: none;">
<thead>
<tr>
<th>Sigla</th>
<th>Fecha</th>
<th>Glosa</th>
<th class="right aligned">Cargo</th>
<th class="right aligned">Abono</th>
<th class="right aligned">Saldo</th>
<th class="center aligned">Centro de Costo</th>
<th>Categoría</th>
<th>Detalle</th>
<th>RUT</th>
<th>Nombres</th>
<th class="center aligned">Identificador</th>
<th></th>
</tr>
</thead>
<tbody>
</tbody>
</table>
@endsection
@push('page_scripts')
<script>
class Cartola {
props
ids
constructor({index, ids}) {
this.props = {
index
}
this.ids = ids
}
get sociedad() {
return $(`#${this.ids.sociedad}${this.props.index}`).dropdown('get value')
}
get banco() {
return $(`#${this.ids.banco}${this.props.index}`).dropdown('get value')
}
get mes() {
return $(`#${this.ids.mes}${this.props.index}`).calendar('get date')
}
get file() {
return $(`#archivo${this.props.index}`)[0].files[0]
}
get data() {
return {
sociedad_rut: this.sociedad,
cuenta_id: this.banco,
mes: [this.mes.getFullYear(), this.mes.getMonth() + 1, 1].join('-'),
file: this.file,
index: this.props.index
}
}
draw({sociedades}) {
const output = [
`<div class="fields" data-idx="${this.props.index}">`,
'<div class="five wide field">',
'<label>Sociedad</label>',
`<div class="ui search selection dropdown" id="${this.ids.sociedad}${this.props.index}">`,
`<input type="hidden" name="sociedad_rut${this.props.index}" />`,
'<i class="dropdown icon"></i>',
'<div class="default text">Sociedad</div>',
'<div class="menu">',
...sociedades.map(sociedad => {
return [
`<div class="item" data-value="${sociedad.rut}">${sociedad.razon}</div>`
].join("\n")
}),
'</div>',
'</div>',
'</div>',
'<div class="four wide field">',
'<label>Banco</label>',
`<div class="ui search selection dropdown" id="${this.ids.banco}${this.props.index}">`,
`<input type="hidden" name="banco_id${this.props.index}" />`,
'<i class="dropdown icon"></i>',
'<div class="default text">Banco</div>',
'<div class="menu"></div>',
'</div>',
'</div>',
'<div class="field">',
'<label>Mes</label>',
`<div class="ui calendar" id="${this.ids.mes}${this.props.index}">`,
'<div class="ui input left icon">',
'<i class="calendar icon"></i>',
`<input type="text" name="mes${this.props.index}" placeholder="Mes" />`,
'</div>',
'</div>',
'</div>',
'<div class="field">',
'<label>Archivo</label>',
`<input type="file" class="ui invisible file input" name="archivo${this.props.index}" id="archivo${this.props.index}" />`,
`<label for="archivo${this.props.index}" class="ui icon button" id="archivo_button">`,
'<i class="file icon"></i>',
'</label>',
'</div>',
]
if (this.props.index > 1) {
output.push(...[
'<div class="field">',
'<label>&nbsp;</label>',
`<button class="ui red icon button remove" data-idx="${this.props.index}">`,
'<i class="trash icon"></i>',
'</button>',
'</div>',
])
}
output.push('</div>')
return output.join("\n")
}
activate() {
$(`#${this.ids.sociedad}${this.props.index}`).dropdown({
onChange: (value, text, $choice) => {
cartolas.fetch().bancos(value).then(response => {
if (!response) {
return
}
return response.json().then(data => {
const dropdown = $(`#${this.ids.banco}${this.props.index}`)
dropdown.dropdown('clear')
dropdown.dropdown('change values', data.cuentas.map(cuenta => {
const desc = [cuenta.banco.nombre, cuenta.cuenta].join(' - ')
return {
name: desc,
value: cuenta.id,
text: desc
}
}))
})
})
}
})
$(`#${this.ids.banco}${this.props.index}`).dropdown()
const cdo = structuredClone(calendar_date_options)
cdo.type = 'month'
$(`#${this.ids.mes}${this.props.index}`).calendar(cdo)
if (this.props.index > 1) {
$(`.${this.ids.buttons.remove}[data-idx="${this.props.index}"]`).click(clickEvent => {
const index = $(clickEvent.currentTarget).data('idx')
this.remove(index)
})
}
}
remove(idx) {
$(`.fields[data-idx=${idx}]`).remove()
cartolas.data.cartolas = cartolas.data.cartolas.filter(cartola => cartola.props.index !== idx)
cartolas.draw().form()
}
}
class Movimiento {
props
constructor({id, sociedad, fecha, glosa, cargo, abono, saldo, categoria, detalle, centro_costo, rut, nombres,
identificador, relacionado, relacionadoType, nuevo = false, obsoleto = false}) {
this.props = {
id,
sociedad,
fecha,
glosa,
cargo,
abono,
saldo,
categoria,
detalle,
centro_costo,
rut,
nombres,
identificador,
relacionado,
relacionadoType,
nuevo,
obsoleto
}
}
draw({formatters}) {
const fecha = new Date(this.props.fecha)
let nombre = ''
if (this.props.nombres) {
if (this.props.relacionado) {
let type = this.props.relacionadoType
type = type.charAt(0).toUpperCase() + type.slice(1)
nombre = `<span data-tooltip="${type}">${this.props.nombres}</span>`
} else {
nombre = this.props.nombres
}
}
let edit = ''
let color = ''
if (this.props.nuevo) {
color = ' class="green"'
}
if (this.props.obsoleto) {
color = ' class="red"'
edit = `<button class="ui tertiary red icon button remove_movimiento" data-id="${this.props.id}"><i class="remove icon"></i></button>`
}
return [
`<tr${color}>`,
`<td>${this.props.sociedad.sigla}</td>`,
`<td>${formatters.date.format(fecha)}</td>`,
`<td>${this.props.glosa}</td>`,
`<td class="right aligned">${formatters.number.format(this.props.cargo ?? 0)}</td>`,
`<td class="right aligned">${formatters.number.format(this.props.abono ?? 0)}</td>`,
`<td class="right aligned">${formatters.number.format(this.props.saldo)}</td>`,
`<td class="center aligned">${this.props.centro_costo ?? ''}</td>`,
`<td>${this.props.categoria ?? ''}</td>`,
`<td>${this.props.detalle ?? ''}</td>`,
`<td>${this.props.rut ?? ''}</td>`,
`<td>${nombre}</td>`,
`<td class="center aligned">${this.props.identificador ?? ''}</td>`,
`<td class="right aligned">${edit}</td>`,
'</tr>'
].join("\n")
}
}
const cartolas = {
ids: {},
data: {
sociedades: {!! json_encode($inmobiliarias) !!},
bancos: {!! json_encode($bancos) !!},
cartolas: [],
movimientos: [],
},
formatters: {
number: new Intl.NumberFormat('es-CL', {minimumFractionDigits: 0, maximumFractionDigits: 0}),
date: new Intl.DateTimeFormat('es-CL', {dateStyle: 'short', timeStyle: 'short'})
},
add() {
return {
cartola: () => {
const idx = cartolas.data.cartolas.length + 1
const cartola = new Cartola({index: idx, ids: cartolas.ids.cartolas})
cartolas.data.cartolas.push(cartola)
cartolas.draw().form()
}
}
},
remove() {
return {
movimiento: id => {
const url = `{{$urls->api}}/contabilidad/movimiento/${id}`
const method = 'delete'
APIClient.fetch(url, {method}).then(response => {
if (!response.status) {
return
}
const index = this.data.movimientos.findIndex(movimiento => movimiento.id === response.movimiento.id)
this.data.movimientos.splice(index, 1)
this.draw().movimientos()
})
}
}
},
draw() {
return {
form: () => {
const form = $(this.ids.form)
form.empty()
form.append(`<input type="hidden" name="cartolas" value="${this.data.cartolas.length}" />`)
this.data.cartolas.forEach(cartola => {
form.append(cartola.draw({sociedades: this.data.sociedades, bancos: this.data.bancos}))
cartola.activate()
})
},
movimientos: () => {
const table = $(this.ids.movimientos)
const tbody = table.find('tbody')
tbody.empty()
this.data.movimientos.forEach(movimiento => {
tbody.append(movimiento.draw({formatters: this.formatters}))
})
table.show()
Object.values(document.getElementsByClassName('remove_movimiento')).forEach(element => {
element.addEventListener('click', clickEvent => {
const id = clickEvent.currentTarget.dataset['id']
this.remove().movimiento(id)
})
})
}
}
},
fetch() {
return {
bancos: sociedad_rut => {
const url = `{{$urls->api}}/inmobiliaria/${sociedad_rut}/cuentas`
return fetchAPI(url)
}
}
},
import() {
return {
cartolas: () => {
const url = '{{$urls->api}}/contabilidad/cartolas/importar'
const method = 'post'
const body = new FormData()
this.data.cartolas.forEach(cartola => {
Object.entries(cartola.data).forEach(([key, value]) => {
const name = `${key}[${cartola.props.index - 1}]`
body.append(name, value)
})
})
APIClient.fetch(url, {method, body}).then(response => {
if (!response) {
return
}
return response.json().then(data => {
if (data.errors.length > 0) {
data.errors.forEach(errorData => {
console.error(errorData)
})
}
this.data.movimientos = data.movimientos.map(movimiento => new Movimiento(movimiento))
this.draw().movimientos()
})
}).catch(error => {
const table = $(this.ids.movimientos)
const tbody = table.find('tbody')
tbody.empty()
table.hide()
}).finally(() => {
$(this.ids.loader).hide()
})
}
}
},
setup(ids) {
this.ids = ids
this.add().cartola()
$(this.ids.buttons.add).click(() => {
this.add().cartola()
})
$(this.ids.loader).hide()
$(this.ids.form).submit(submitEvent => {
submitEvent.preventDefault()
$(this.ids.loader).show()
this.import().cartolas()
return false
})
$(this.ids.buttons.process).click(() => {
$(this.ids.form).submit()
})
}
}
$(document).ready(() => {
cartolas.setup({
form: '#cartola_form',
buttons: {
add: '#add_button',
process: '#process_button'
},
movimientos: '#movimientos',
loader: '#loader',
cartolas: {
sociedad: 'sociedad',
banco: 'banco',
mes: 'mes',
buttons: {
remove: 'remove'
}
}
})
})
</script>
@endpush

View File

@ -14,9 +14,10 @@
<i class="dropdown icon"></i>
<div class="default text">Inmobiliaria</div>
<div class="menu">
@foreach ($inmobiliarias as $inmobiliaria)
<div class="item" data-value="{{$inmobiliaria->rut}}">{{$inmobiliaria->razon}}</div>
@endforeach
@foreach ($inmobiliarias as $inmobiliaria)
<div class="item"
data-value="{{$inmobiliaria->rut}}">{{$inmobiliaria->razon}}</div>
@endforeach
</div>
</div>
</div>
@ -40,7 +41,7 @@
</div>
<div class="field">
<label for="file">Cartola</label>
<input type="file" name="file" id="file" class="ui invisible file input" />
<input type="file" name="file" id="file" class="ui invisible file input"/>
<label for="file" class="ui icon button">
<i class="file icon"></i>
Cargar
@ -119,20 +120,20 @@
mes: '',
movimientos: [],
centrosCostos: {
ingresos: JSON.parse('{!! json_encode(array_values(array_map(function(\Incoviba\Model\CentroCosto $centroCosto) {
ingresos: JSON.parse('{!! json_encode(array_values(array_map(function(\Incoviba\Model\Contabilidad\CentroCosto $centroCosto) {
return [
'id' => $centroCosto->id,
'descripcion' => $centroCosto->descripcion
];
}, array_filter($centrosCostos, function(\Incoviba\Model\CentroCosto $centroCosto) {
}, array_filter($centrosCostos, function(\Incoviba\Model\Contabilidad\CentroCosto $centroCosto) {
return $centroCosto->tipoCentro->descripcion === 'Ingreso';
})))) !!}'),
egresos: JSON.parse('{!! json_encode(array_values(array_map(function(\Incoviba\Model\CentroCosto $centroCosto) {
egresos: JSON.parse('{!! json_encode(array_values(array_map(function(\Incoviba\Model\Contabilidad\CentroCosto $centroCosto) {
return [
'id' => $centroCosto->id,
'descripcion' => $centroCosto->descripcion
];
}, array_filter($centrosCostos, function(\Incoviba\Model\CentroCosto $centroCosto) {
}, array_filter($centrosCostos, function(\Incoviba\Model\Contabilidad\CentroCosto $centroCosto) {
return $centroCosto->tipoCentro->descripcion === 'Egreso';
})))) !!}'),
}
@ -170,7 +171,11 @@
return
}
$(this.ids.form.banco).dropdown('change values', json.cuentas.map(cuenta => {
return {value: cuenta.banco.id, text: cuenta.banco.nombre, name: cuenta.banco.nombre}
return {
value: cuenta.banco.id,
text: cuenta.banco.nombre,
name: cuenta.banco.nombre
}
})).dropdown('refresh')
})
})
@ -247,12 +252,12 @@
const movimientos = this.data.movimientos.map((movimiento, idx) => {
const temp = structuredClone(movimiento)
temp.fecha = movimiento.fecha.toISOString()
let centro = $(".centro[data-index='" + (idx+1) + "']").dropdown('get value')
let centro = $(".centro[data-index='" + (idx + 1) + "']").dropdown('get value')
if (centro.length === 0) {
centro = ''
}
temp.centro_costo = centro
let detalle = $("[name='detalle" + (idx+1) + "']").val()
let detalle = $("[name='detalle" + (idx + 1) + "']").val()
if (typeof detalle === 'undefined') {
detalle = ''
}
@ -294,7 +299,10 @@
month: 'numeric',
day: 'numeric'
})
const numberFormatter = new Intl.NumberFormat('es-CL', {minimumFractionDigits: 0, maximumFractionDigits: 0})
const numberFormatter = new Intl.NumberFormat('es-CL', {
minimumFractionDigits: 0,
maximumFractionDigits: 0
})
this.data.movimientos.forEach((row, idx) => {
tbody.append(
$('<tr></tr>').append(
@ -322,7 +330,7 @@
})
table.DataTable(this.dataTableConfig)
},
centrosDropdown: (idx, ingreso=true) => {
centrosDropdown: (idx, ingreso = true) => {
const menu = $('<div></div>').addClass('menu')
let centros = this.data.centrosCostos.ingresos
if (!ingreso) {

View File

@ -0,0 +1,225 @@
@extends('layout.base')
@section('page_content')
<div class="ui container">
<h1 class="ui header">
Cuadratura de Cartola Mensual
</h1>
<form class="ui form" id="cuadratura_form">
<div class="ui grid">
<div class="fourteen wide column">
<div class="fields">
<div class="five wide field">
<label>Inmobiliaria</label>
<div class="ui selection search dropdown" id="inmobiliaria">
<input type="hidden" name="inmobiliaria"/>
<i class="dropdown icon"></i>
<div class="default text">Inmobiliaria</div>
<div class="menu">
@foreach ($inmobiliarias as $inmobiliaria)
<div class="item" data-value="{{$inmobiliaria->rut}}">{{$inmobiliaria->razon}}</div>
@endforeach
</div>
</div>
</div>
<div class="two wide field">
<label>Banco</label>
<div class="ui selection search dropdown" id="banco">
<input type="hidden" name="banco"/>
<i class="dropdown icon"></i>
<div class="default text">Banco</div>
<div class="menu"></div>
</div>
</div>
<div class="field">
<label>Mes</label>
<div class="ui calendar" id="mes">
<div class="ui icon input">
<i class="calendar icon"></i>
<input type="text" name="mes"/>
</div>
</div>
</div>
<div class="field">
<label for="file">Cartola</label>
<input type="file" name="file" id="file" class="ui invisible file input" />
<label for="file" class="ui icon button">
<i class="file icon"></i>
Cargar
</label>
</div>
</div>
</div>
<div class="two wide middle aligned column">
<button class="ui icon button">
Procesar
<i class="sync icon"></i>
</button>
</div>
</div>
</form>
<div class="ui two columns grid">
<div class="column"></div>
<div class="right aligned column">
<div class="ui inline active loader" id="loader"></div>
</div>
</div>
</div>
<div class="ui fluid container" id="movimientos"></div>
@endsection
@include('layout.head.styles.datatables')
@include('layout.body.scripts.datatables')
@push('page_scripts')
<script>
class LibroMayor {
props
constructor(props) {
this.props = props
}
}
class Cartola {
}
const movimientos = {
ids: {},
data: {
inmobiliaria: {
rut: '',
razon: ''
},
banco: {
id: '',
nombre: ''
}
},
get() {
return {
bancos: inmobiliaria_rut => {
const url = '{{$urls->api}}/inmobiliaria/' + inmobiliaria_rut + '/cuentas'
$(this.ids.loader).show()
return fetchAPI(url).then(response => {
$(this.ids.loader).hide()
if (!response) {
return
}
return response.json().then(json => {
if (json.cuentas.length === 0) {
return
}
$(this.ids.inputs.banco).dropdown('change values', json.cuentas.map(cuenta => {
return {value: cuenta.banco.id, text: cuenta.banco.nombre, name: cuenta.banco.nombre}
})).dropdown('refresh')
})
})
},
firstDate: inmobiliaria_rut => {
const url = '{{$urls->api}}/inmobiliaria/' + inmobiliaria_rut + '/proyectos'
$(this.ids.loader).show()
return fetchAPI(url).then(response => {
$(this.ids.loader).hide()
if (!response) {
return
}
return response.json().then(json => {
if (json.proyectos.length === 0) {
return
}
const min = json.proyectos.reduce((min, proyecto) => {
const date = new Date(proyecto.current_estado.fecha.date)
if (min > date.getTime()) {
return date.getTime()
}
return min
}, (new Date()).getTime())
$(this.ids.inputs.mes).calendar('set minDate', new Date(min))
})
})
}
}
},
parse() {
return {
cartola: submitEvent => {
submitEvent.preventDefault()
const body = new FormData(document.getElementById('asignar_form'))
body.set('mes', $(this.ids.inputs.mes).calendar('get date').toISOString())
const url = '{{$urls->api}}/contabilidad/cartolas/procesar'
$(this.ids.loader).show()
fetchAPI(url, {method: 'post', body}).then(response => {
$(this.ids.loader).hide()
if (!response) {
return
}
return response.json().then(json => {
if (json.movimientos.length === 0) {
return
}
this.data.movimientos = []
json.movimientos.forEach((row, idx) => {
const fecha = new Date(row.fecha)
fecha.setDate(fecha.getDate() + 1)
this.data.movimientos[idx] = {
fecha: fecha,
glosa: row.glosa,
documento: row.documento,
cargo: row.cargo,
abono: row.abono,
}
})
this.draw().cartola()
})
})
return false
}
}
},
setup(ids) {
this.ids = ids
$(this.ids.inputs.inmobiliaria).dropdown({
fireOnInit: true,
onChange: (value, text, $choice) => {
this.data.inmobiliaria.rut = value
this.data.inmobiliaria.razon = text
this.get().bancos(value)
this.get().firstDate(value)
},
})
$(this.ids.inputs.banco).dropdown({
fireOnInit: true,
onChange: (value, text, $choice) => {
this.data.banco.id = value
this.data.banco.nombre = text
}
})
$(this.ids.loader).hide()
calendar_date_options['type'] = 'month'
const lastMonth = new Date()
lastMonth.setDate(0)
calendar_date_options['maxDate'] = lastMonth
calendar_date_options['onChange'] = (date, text, mode) => {
this.data.mes = text
}
$(this.ids.inputs.mes).calendar(calendar_date_options)
$(this.ids.form).submit(this.parse().cartola)
//$(this.ids.button).click(this.export().cartola)
}
}
$(document).ready(() => {
movimientos.setup({
movimientos: '#movimientos',
form: '#cuadratura_form',
loader: '#loader',
inputs: {
inmobiliaria: '#inmobiliaria',
banco: '#banco',
mes: '#mes',
cartola: '#file'
}
})
})
</script>
@endpush

View File

@ -1,14 +1,23 @@
@extends('layout.base')
@push('page_styles')
<style>
tr.bold > th {
font-weight: bold !important;
}
</style>
@endpush
@section('page_content')
<div class="ui container">
<h1 class="ui centered header">Informe de Tesorería</h1>
<h4 class="ui centered sub header">{{$fecha->format('d M Y')}}</h4>
<div class="ui grid">
<div class="three wide column">
<a href="/contabilidad/informes/xlsx/tesoreria/{{$fecha->format('Y-m-d')}}" target="_blank" style="color: inherit;">
<a href="/contabilidad/informes/xlsx/tesoreria/{{$fecha->format('Y-m-d')}}" target="_blank"
style="color: inherit;">
<div class="ui inverted green center aligned segment">
Descargar en Excel <i class="file excel icon"></i>
Descargar en Excel <i class="file excel icon"></i>
</div>
</a>
</div>
@ -19,7 +28,11 @@
<table class="ui collapsing simple table">
<tr>
<td>Informe anterior</td>
<td>{{$anterior->format('d/m/Y')}}</td>
<td>
<a href="{{$urls->base}}/contabilidad/informes/tesoreria/{{$anterior->format('Y-m-d')}}">
{{$anterior->format('d/m/Y')}}
</a>
</td>
</tr>
<tr>
<td>Informe actual</td>
@ -40,6 +53,17 @@
</tr>
</table>
</div>
@if ($siguiente !== null)
<div class="five wide column"></div>
<div class="right aligned two wide column">
<div class="ui segment">
Siguiente
<a href="{{$urls->base}}/contabilidad/informes/tesoreria/{{$siguiente->format('Y-m-d')}}">
{{$siguiente->format('d/m/Y')}} >>
</a>
</div>
</div>
@endif
</div>
<table class="ui striped table">
<thead>
@ -60,27 +84,31 @@
</tr>
</thead>
<tbody>
@foreach ($informes['inmobiliarias'] as $inmobiliaria_rut => $informe)
@foreach ($informes['sociedades'] as $sociedad_rut => $informe)
@foreach ($informe->cuentas as $i => $cuenta)
<tr>
@if ($i === 0)
<td rowspan="{{count($informe->cuentas)}}">{{$informe->inmobiliaria->razon}}</td>
@endif
<td>{{$cuenta->banco}}</td>
<td>{{$cuenta->numero}}</td>
<td class="right aligned">{{$format->pesos($cuenta->anterior)}}</td>
<td class="right aligned">{{$format->pesos($cuenta->actual)}}</td>
<td class="right aligned">{{$format->pesos($cuenta->diferencia())}}</td>
<td class="right aligned">{{$format->pesos($cuenta->ffmm)}}</td>
<td class="right aligned">{{$format->pesos($cuenta->deposito)}}</td>
<td class="right aligned">{{$format->pesos($cuenta->saldo())}}</td>
@if ($i === 0)
<td class="right aligned" rowspan="{{count($informe->cuentas)}}">{{$format->pesos($informe->total())}}</td>
<td class="right aligned" rowspan="{{count($informe->cuentas)}}">{{$format->pesos($informe->ffmm())}}</td>
<td class="right aligned" rowspan="{{count($informe->cuentas)}}">{{$format->pesos($informe->deposito())}}</td>
<td class="right aligned" rowspan="{{count($informe->cuentas)}}">{{$format->pesos($informe->caja())}}</td>
@endif
</tr>
<tr>
@if ($i === 0)
<td rowspan="{{count($informe->cuentas)}}">{{$informe->sociedad->razon}}</td>
@endif
<td>{{$cuenta->banco}}</td>
<td>{{$cuenta->numero}}</td>
<td class="right aligned">{{$format->pesos($cuenta->anterior)}}</td>
<td class="right aligned">{{$format->pesos($cuenta->actual)}}</td>
<td class="right aligned">{{$format->pesos($cuenta->diferencia())}}</td>
<td class="right aligned">{{$format->pesos($cuenta->ffmm)}}</td>
<td class="right aligned">{{$format->pesos($cuenta->deposito)}}</td>
<td class="right aligned">{{$format->pesos($cuenta->saldo())}}</td>
@if ($i === 0)
<td class="right aligned"
rowspan="{{count($informe->cuentas)}}">{{$format->pesos($informe->total())}}</td>
<td class="right aligned"
rowspan="{{count($informe->cuentas)}}">{{$format->pesos($informe->ffmm())}}</td>
<td class="right aligned"
rowspan="{{count($informe->cuentas)}}">{{$format->pesos($informe->deposito())}}</td>
<td class="right aligned"
rowspan="{{count($informe->cuentas)}}">{{$format->pesos($informe->caja())}}</td>
@endif
</tr>
@endforeach
@endforeach
</tbody>
@ -123,7 +151,7 @@
@foreach ($movimientos as $ms)
@foreach ($ms as $movimiento)
<tr>
<td >{{$movimiento->cuenta->inmobiliaria->razon}}</td>
<td>{{$movimiento->cuenta->inmobiliaria->razon}}</td>
<td class="right aligned">{{$format->pesos($movimiento->abono)}}</td>
<td class="right aligned">{{$format->pesos($movimiento->cargo)}}</td>
<td>{{$movimiento->fecha->format('d/m/Y')}}</td>
@ -142,7 +170,7 @@
</tr>
@foreach ($movimientos as $movimiento)
<tr>
<td >{{$movimiento->cuenta->inmobiliaria->razon}}</td>
<td>{{$movimiento->cuenta->inmobiliaria->razon}}</td>
<td class="right aligned">{{$format->pesos($movimiento->abono)}}</td>
<td class="red right aligned">{{$format->pesos($movimiento->cargo)}}</td>
<td>{{$movimiento->fecha->format('d/m/Y')}}</td>

View File

@ -0,0 +1,140 @@
@extends('layout.base')
@include('layout.head.styles.datatables')
@include('layout.head.styles.datatables.searchbuilder')
@include('layout.head.styles.datatables.buttons')
@push('page_styles')
<style>
#table_container {
margin-left: 1rem;
margin-right: 1rem;
}
</style>
@endpush
@section('page_content')
<div class="ui container">
<h1 class="ui header">Movimientos Contables</h1>
<form class="ui form" id="movimientos_form">
<div class="fields">
<div class="six wide field">
<label for="sociedad">Sociedad</label>
<div class="ui selection search multiple dropdown" id="sociedades">
<input type="hidden" name="sociedad[]"/>
<i class="dropdown icon"></i>
<div class="default text">Sociedad</div>
<div class="menu">
@foreach($sociedades as $sociedad)
<div class="item" data-value="{{$sociedad->rut}}">{{$sociedad->razon}}</div>
@endforeach
</div>
</div>
</div>
<div class="three wide field">
<label for="mes">Mes</label>
<div class="ui calendar" id="mes">
<div class="ui right icon input">
<input type="text" name="mes"/>
<i class="calendar icon"></i>
</div>
</div>
</div>
<div class="field">
<label></label>
<button class="ui circular icon button">
<i class="sync alternate icon"></i>
</button>
</div>
</div>
</form>
</div>
<div id="table_container">
<table id="tabla_movimientos" class="ui compact table">
<thead>
<tr>
<th>Sigla</th>
<th>Banco</th>
<th>Cuenta</th>
<th>Fecha</th>
<th>ISO Fecha</th>
<th>Mes</th>
<th>Año</th>
<th class="right aligned">Valor</th>
<th>Valor</th>
<th>Glosa</th>
<th>Centro de Costo</th>
<th>Categoría</th>
<th>Detalle</th>
<th>RUT</th>
<th>Nombre</th>
<th>Identificador</th>
<th>Editar</th>
</tr>
</thead>
<tbody id="movimientos"></tbody>
</table>
</div>
@include('contabilidad.movimientos.edit_modal')
@endsection
@include('layout.body.scripts.datatables')
@include('layout.body.scripts.datatables.searchbuilder')
@include('layout.body.scripts.datatables.buttons')
@include('layout.body.scripts.rut')
@push('page_scripts')
<script>
class CentrosCostos {
static data = []
static get() {
if (this.data.length === 0) {
this.data = JSON.parse('{!! json_encode($centros) !!}')
}
return this.data
}
}
</script>
@endpush
@push('page_scripts')
@include('contabilidad.movimientos.scripts.event_handler')
@include('contabilidad.movimientos.scripts.loading_handler')
@include('contabilidad.movimientos.scripts.control_form')
@include('contabilidad.movimientos.scripts.movimientos_table')
@include('contabilidad.movimientos.scripts.edit_modal')
@include('contabilidad.movimientos.scripts.edit_form')
@include('contabilidad.movimientos.scripts.movimientos_handler')
@include('contabilidad.movimientos.scripts.results_handler')
<script>
const app = {
handlers: {},
setup() {
this.handlers.results = new ResultsHandler()
this.handlers.loading = new LoadingHandler({id: '#movimientos_form'})
this.handlers.movimientos = new MovimientosHandler({
urls: {
get: '{{$urls->api}}/contabilidad/movimientos/sociedad/mes',
edit: '{{$urls->api}}/contabilidad/movimientos/edit',
remove: '{{$urls->api}}/contabilidad/movimiento',
},
loadingHandler: this.handlers.loading,
resultsHandler: this.handlers.results
})
this.handlers.forms = {
edit: new EditForm({ids: {modal: '#movimientos_modal', data: '#movimientos_data', form: '#movimientos_edit',}})
}
this.handlers.modals = {
edit: new EditModal({ids: {modal: '#movimientos_modal'}, editForm: this.handlers.forms.edit})
}
this.handlers.events = new EventHandler({movimientosHandler: this.handlers.movimientos, modalHandler: this.handlers.modals.edit})
this.handlers.table = new MovimientosTable({ids: {table: '#tabla_movimientos', buttons: {edit: '.edit_button', remove: '.remove_button'}}, eventHandler: this.handlers.events})
this.handlers.forms.control = new ControlForm({ids: {form: '#movimientos_form', sociedades: '#sociedades', mes: '#mes',},})
this.handlers.forms.edit.setup()
}
}
$(document).ready(() => {
app.setup()
})
</script>
@endpush

View File

@ -0,0 +1,67 @@
<div class="ui fluid modal" id="movimientos_modal">
<div class="content">
<h3 class="header">
Editar Movimiento
</h3>
<div class="ui divider"></div>
<div class="ui grid container" id="modal_info">
<div class="column sociedad"></div>
<div class="column banco"></div>
<div class="column cuenta"></div>
<div class="column fecha"></div>
<div class="column valor"></div>
<div class="column glosa"></div>
</div>
<div class="ui divider"></div>
<form class="ui form" id="movimientos_edit">
<input type="hidden" name="index"/>
<input type="hidden" name="id"/>
<div class="fields">
<div class="field">
<label for="centro_costo">Centro de Costo</label>
<div class="ui selection search dropdown" id="centro_costo">
<input type="hidden" name="centro_id"/>
<i class="dropdown icon"></i>
<div class="default text">Centro de Costo</div>
<div class="menu">
@foreach($centros as $centroCosto)
<div class="item" data-value="{{$centroCosto->id}}">{{$centroCosto->id}} - {{$centroCosto->descripcion}}</div>
@endforeach
</div>
</div>
</div>
<div class="field">
<label>Categoría</label>
<input type="text" name="categoria" placeholder="Categoría" />
</div>
<div class="field">
<label>Identificador</label>
<input type="text" name="identificador" placeholder="Identificador" />
</div>
</div>
<div class="fields">
<div class="field">
<label>RUT</label>
<div class="ui right labeled input">
<input type="text" name="rut" placeholder="RUT" />
<div class="ui basic label">-<span id="digito"></span></div>
</div>
</div>
<div class="field">
<label>Nombre</label>
<input type="text" name="nombre" placeholder="Nombre" />
</div>
</div>
<div class="field">
<label for="detalle">Detalle</label>
<div class="ui input">
<textarea id="detalle" name="detalle"></textarea>
</div>
</div>
</form>
</div>
<div class="actions">
<div class="ui red cancel button">Cancelar</div>
<div class="ui green approve success button">Guardar</div>
</div>
</div>

View File

@ -0,0 +1,22 @@
<script>
class ControlForm {
props
constructor({ids,}) {
this.props = {
ids,
}
$(this.props.ids.sociedades).dropdown()
const cdo = structuredClone(calendar_date_options)
cdo['type'] = 'month'
cdo['maxDate'] = new Date()
$(this.props.ids.mes).calendar(cdo)
$(this.props.ids.form).on('submit', {
sociedades_id: this.props.ids.sociedades,
mes_id: this.props.ids.mes,
handler: app.handlers.events
}, app.handlers.events.get().movimientos)
}
}
</script>

View File

@ -0,0 +1,98 @@
<script>
class EditForm {
props
fields
constructor({ids}) {
this.props = {
ids,
movimientos: []
}
this.fields = [
{
name: 'index',
value: index => index
},
{
name: 'id',
value: movimiento => movimiento.id
},
{
id: 'centro_costo',
name: 'centro_id',
type: 'dropdown',
value: movimiento => movimiento.detalles.centro_costo.id ?? '',
values: CentrosCostos.get().map(centroCosto => {
return {
value: centroCosto.id,
name: `${centroCosto.id} - ${centroCosto.descripcion}`,
text: `${centroCosto.id} - ${centroCosto.descripcion}`,
}
})
},
{
name: 'rut',
type: 'text',
value: movimiento => movimiento.detalles.rut ?? ''
},
{
id: 'digito',
value: movimiento => movimiento.detalles.digito ?? ''
},
{
name: 'nombre',
type: 'text',
value: movimiento => movimiento.detalles.nombres ?? ''
},
{
name: 'categoria',
type: 'text',
value: movimiento => movimiento.detalles.categoria ?? ''
},
{
name: 'detalle',
type: 'text',
value: movimiento => movimiento.detalles.detalle ?? ''
},
{
name: 'identificador',
type: 'text',
value: movimiento => movimiento.detalles.identificador ?? ''
}
]
this.fields.filter(field => typeof field.values !== 'undefined').forEach(field => {
$(this.props.ids.form).find(`#${field.id}`).dropdown('change values', field.values)
})
}
reset() {
$(this.props.ids.form).form('reset')
}
show({modal, movimiento, index}) {
modal.find(this.props.ids.data).show()
this.fields.forEach(field => {
if (typeof field.name !== 'undefined') {
switch (field.type) {
case 'dropdown':
modal.find(`#${field.id}`).dropdown('set selected', field.value(movimiento))
break
default:
if (field.name === 'index') {
modal.find(`[name='${field.name}']`).val(index)
return
}
modal.find(`[name='${field.name}']`).val(field.value(movimiento))
break
}
return
}
modal.find(`#${field.id}`).text(field.value(movimiento))
})
}
setup() {
$(this.props.ids.form).on('submit', {
form_id: this.props.ids.form,
}, app.handlers.events.edit().modal)
}
}
</script>

View File

@ -0,0 +1,74 @@
<script>
class EditModal {
props
info
constructor({ids, editForm}) {
this.props = {
ids,
editForm,
movimientos: []
}
this.info = [
{
className: 'sociedad',
width: 'three wide',
value: movimiento => `<b>Sociedad</b>: ${movimiento.cuenta.inmobiliaria.sigla}`
},
{
className: 'banco',
width: 'three wide',
value: movimiento => `<b>Banco</b>: ${movimiento.cuenta.banco.nombre}`
},
{
className: 'cuenta',
width: 'three wide',
value: movimiento => `<b>Cuenta</b>: ${movimiento.cuenta.cuenta}`
},
{
className: 'fecha',
width: 'three wide',
value: movimiento => `<b>Fecha</b>: ${movimiento.fecha}`
},
{
className: 'valor',
width: 'three wide',
value: movimiento => `<b>Valor</b>: ${movimiento.abono - movimiento.cargo}`
},
{
className: 'glosa',
width: 'ten wide',
value: movimiento => `<b>Glosa</b>: ${movimiento.glosa}`
},
]
const $info = $(this.props.ids.modal).find('#modal_info')
$info.empty()
this.info.forEach(field => {
$info.append(`<div class="${field.width} column ${field.className}"></div>`)
})
$(this.props.ids.modal).modal({
onApprove: $element => {
$(this.props.editForm.props.ids.form).submit()
}
})
}
reset() {
this.info.forEach(info => {
$(this.props.ids.modal).find(`.${info.className}`).text('')
})
this.props.editForm.reset()
}
show({movimiento, index}) {
$(this.props.ids.modal).modal('show')
this.info.forEach(info => {
$(this.props.ids.modal).find(`.${info.className}`).html(info.value(movimiento))
})
this.props.editForm.show({modal: $(this.props.ids.modal), movimiento, index})
}
hide() {
$(this.props.ids.modal).modal('hide')
this.reset()
}
}
</script>

View File

@ -0,0 +1,54 @@
<script>
class EventHandler {
props
constructor({movimientosHandler, modalHandler}) {
this.props = {
movimientosHandler,
modalHandler
}
}
get() {
return {
movimientos: submitEvent => {
submitEvent.preventDefault()
submitEvent.data.handler.props.movimientosHandler.get().movimientos({
sociedades_ruts: $(submitEvent.data.sociedades_id).dropdown('get values'),
mes: $(submitEvent.data.mes_id).calendar('get date')
}).then(() => {
app.handlers.table.draw().table(app.handlers.results.props.parsed)
})
return false
}
}
}
edit() {
return {
movimiento: clickEvent => {
const id = $(clickEvent.currentTarget).data('id')
const index = $(clickEvent.currentTarget).data('index')
const movimiento = this.props.movimientosHandler.find().id(id)
clickEvent.data.handler.props.modalHandler.show({movimiento, index})
},
modal: submitEvent => {
submitEvent.preventDefault()
const form = submitEvent.currentTarget
const data = new FormData(form)
app.handlers.movimientos.edit().movimiento(data).then(() => {
app.handlers.table.draw().table(app.handlers.results.props.parsed)
})
return false
}
}
}
remove() {
return {
movimiento: clickEvent => {
const id = $(clickEvent.currentTarget).data('id')
app.handlers.movimientos.remove().movimiento(id).then(() => {
app.handlers.table.draw().table(app.handlers.results.props.parsed)
})
}
}
}
}
</script>

View File

@ -0,0 +1,16 @@
<script>
class LoadingHandler {
props
constructor(ids) {
this.props = {
...ids
}
}
show() {
$(this.props.id).addClass('loading')
}
hide() {
$(this.props.id).removeClass('loading')
}
}
</script>

View File

@ -0,0 +1,86 @@
<script>
class MovimientosHandler {
props
constructor({urls, loadingHandler, resultsHandler}) {
this.props = {
urls,
loadingHandler,
resultsHandler
}
}
get() {
return {
movimientos: ({sociedades_ruts, mes}) => {
this.props.loadingHandler.show()
const method = 'post'
const body = new FormData()
sociedades_ruts.forEach(sociedad_rut => {
body.append('sociedades_ruts[]', sociedad_rut)
})
body.set('mes', [mes.getFullYear(), mes.getMonth() + 1, mes.getDate()].join('-'))
return APIClient.fetch(this.props.urls.get, {method, body}).then(response => {
if (!response) {
throw new Error('No se pudo obtener los movimientos')
}
return response.json().then(json => {
this.movimientos = json.movimientos
this.props.resultsHandler.props.movimientos = json.movimientos
this.props.resultsHandler.parse().movimientos()
return json.movimientos
})
}).catch(error => {
console.error(error)
}).finally(() => {
this.props.loadingHandler.hide()
})
}
}
}
edit() {
return {
movimiento: (data) => {
data.set('digito', Rut.digitoVerificador(data.get('rut')))
const method = 'post'
return APIClient.fetch(this.props.urls.edit, {method, body: data}).then(response => {
if (!response) {
throw new Error('No se pudo editar el movimiento')
}
return response.json().then(json => {
this.props.resultsHandler.props.movimientos = this.props.resultsHandler.props.movimientos.map(movimiento => {
if (movimiento.id === json.movimiento.id) {
movimiento.detalles = json.movimiento.detalles
}
return movimiento
})
this.props.resultsHandler.parse().movimientos()
})
})
}
}
}
remove() {
return {
movimiento: id => {
const method = 'delete'
const url = this.props.urls.remove.replace(/\/$/, '') + '/' + id
return APIClient.fetch(url, {method}).then(response => {
if (!response) {
throw new Error('No se pudo eliminar el movimiento')
}
return response.json().then(json => {
this.props.resultsHandler.props.movimientos = this.props.resultsHandler.props.movimientos.filter(movimiento => movimiento.id !== id)
this.props.resultsHandler.parse().movimientos()
})
})
}
}
}
find() {
return {
id: id => {
return this.props.resultsHandler.props.movimientos.find(movimiento => movimiento.id === id)
}
}
}
}
</script>

View File

@ -0,0 +1,231 @@
<script>
class MovimientosTable {
props
columns
constructor({ids, eventHandler}) {
this.props = {
ids,
eventHandler,
movimientos: [],
formatters: {
number: new Intl.NumberFormat('es-CL'),
date: new Intl.DateTimeFormat('es-CL', {
day: '2-digit',
month: '2-digit',
year: 'numeric'
}),
},
}
this.columns = [
{title: 'Sigla', defs: {width: 'short', type: 'string'}, process: movimiento => movimiento.cuenta.inmobiliaria.sigla, args: ['movimiento'], searchable: true, export: true}, //0
{title: 'Banco', defs: {width: 'long', type: 'string'}, process: movimiento => movimiento.cuenta.banco.nombre, args: ['movimiento'], searchable: true, export: true},
{title: 'Cuenta', defs: {width: 'short', type: 'string'}, process: movimiento => movimiento.cuenta.cuenta, args: ['movimiento'], searchable: true, export: true},
{title: 'Fecha', defs: {width: 'long', type: 'string'}, process: fecha => this.props.formatters.date.format(fecha), args: ['fecha'], searchable: true, export: true, format: (value) => value.split('-').reverse().join('-')},
{title: 'ISO Fecha', defs: {visible: false}, process: fecha => [fecha.getFullYear(), fecha.getMonth() + 1, fecha.getDate()].join('-'), args: ['fecha']},
{title: 'Mes', defs: {width: 'short', type: 'string'}, process: fecha => fecha.getMonth() + 1, args: ['fecha'], searchable: true}, //5
{title: 'Año', defs: {width: 'long', type: 'string'}, process: fecha => fecha.getFullYear(), args: ['fecha'], searchable: true},
{title: 'Valor', defs: {className: 'dt-right', width: 'long', type: 'num'}, process: valor => this.props.formatters.number.format(valor), args: ['valor'], export: true, format: (value) => value.replace(/\./g, '')},
{title: 'ISO Valor', defs: {visible: false, type: 'num'}, args: ['valor']},
{title: 'Glosa', defs: {width: 'short', type: 'string'}, process: movimiento => movimiento.glosa, args: ['movimiento'], export: true},
{title: 'Centro de Costo', defs: {width: 'short', type: 'string'}, process: movimiento => (movimiento.detalles && movimiento.detalles.centro_costo) ? movimiento.detalles.centro_costo.id : '', args: ['movimiento'], searchable: true, export: true}, //10
{title: 'Categoría', defs: {width: 'short', type: 'string'}, process: movimiento => (movimiento.detalles) ? movimiento.detalles.categoria : '', args: ['movimiento'], searchable: true, export: true},
{title: 'Detalle', defs: {width: 'long', type: 'string'}, process: movimiento => (movimiento.detalles) ? movimiento.detalles.detalle : '', args: ['movimiento'], export: true},
{title: 'RUT', defs: {width: 'short', type: 'string'}, process: movimiento => (movimiento.detalles && movimiento.detalles.digito) ? `${this.props.formatters.number.format(movimiento.detalles.rut)}-${movimiento.detalles.digito}` : '', args: ['movimiento'], searchable: true, export: true},
{title: 'Nombre', defs: {width: 'short', type: 'string'}, args: ['nombre'], searchable: true, export: true},
{title: 'Identificador', defs: {width: 'short', type: 'string'}, process: movimiento => (movimiento.detalles) ? movimiento.detalles.identificador : '', args: ['movimiento'], export: true}, //15
{title: 'Editar', defs: {width: 'short', type: 'string'}, args: ['buttonsHTML']},
]
const N = this.columns.filter(column => typeof column.defs.visible === 'undefined' || column.defs.visible).length
const nShort = this.columns.filter(column => column.defs.width === 'short').length
const nLong = this.columns.filter(column => column.defs.width === 'long').length
const short = `${(nShort / N).toFixed(2)}%`
const long = `${(nLong / N).toFixed(2)}%`
this.columns = this.columns.map(column => {
if (typeof column.defs.width === 'undefined') {
return column
}
switch (column.defs.width) {
case 'short':
column.defs.width = short
break
case 'long':
column.defs.width = long
break
}
return column
})
const tr = $(this.props.ids.table).find('thead tr')
tr.empty()
this.columns.forEach(column => {
if (typeof column.defs.className === 'undefined') {
tr.append(`<th>${column.title}</th>`)
return
}
switch (column.defs.className) {
case 'dt-right':
tr.append(`<th class="right aligned">${column.title}</th>`)
break
case 'dt-center':
tr.append(`<th class="center aligned">${column.title}</th>`)
break
default:
tr.append(`<th>${column.title}</th>`)
break
}
})
let dtD = structuredClone(datatables_defaults)
const groupedColumns = Object.groupBy(this.columns, ({defs}) => Object.entries(defs).map((field, value) => `${field}: ${value}`).join(' - '))
const columnDefs = Object.values(groupedColumns).map(group => {
return {
targets: group.map(({title}) => title).map(title => this.columns.map(column => column.title).indexOf(title)),
...group[0].defs
}
})
const searchBuilderColumns = this.columns.filter(column => column.searchable)
.map(column => column.title).map(title => this.columns.map(column => column.title).indexOf(title))
const exportColumns = this.columns.filter(column => column.export)
.map(column => column.title).map(title => this.columns.map(column => column.title).indexOf(title))
const order = ['Sigla', 'Banco', 'ISO Fecha'].map(title => {
return [this.columns.map(column => column.title).indexOf(title), 'asc']
})
dtD = Object.assign(dtD, {
columnDefs,
order,
language: Object.assign(dtD.language, {
searchBuilder
}),
layout: {
top1: {
searchBuilder: {
columns: searchBuilderColumns,
}
},
top1End: {
buttons: [
{
extend: 'excelHtml5',
className: 'green',
text: 'Exportar a Excel <i class="file excel icon"></i>',
title: 'Movimientos Contables',
download: 'open',
exportOptions: {
columns: exportColumns,
format: {
body: (data, row, columnIdx, node) => {
const formats = this.columns.filter(columnDef => columnDef.format)
const match = formats.map(({title}) => title).indexOf(this.columns[columnIdx].title)
if (match > -1) {
return formats[match].format(data)
}
if (typeof data === 'string' && data.includes('<span data-tooltip')) {
return data.replace(/<span data-tooltip="(.*)">(.*)<\/span>/, '$2')
}
return data
}
}
},
customize: xlsx => {
const sheet = xlsx.xl.worksheets['sheet1.xml']
const columns = Object.values($('row[r="2"] t', sheet).map((idx, column) => column.textContent))
const columnStylesMap = {
Valor: '63',
Fecha: '15'
}
Object.entries(columnStylesMap).forEach((column, style) => {
const columnIndex = String.fromCharCode('A'.charCodeAt(0) + columns.indexOf(column))
$(`c[r^="${columnIndex}"]`, sheet).attr('s', style)
})
}
}
]
}
},
data: this.props.movimientos,
rowCallback: (row, data) => {
$(row).find(this.props.ids.buttons.edit).on('click', {handler: this.props.eventHandler}, this.props.eventHandler.edit().movimiento)
$(row).find(this.props.ids.buttons.remove).on('click', {handler: this.props.eventHandler}, this.props.eventHandler.remove().movimiento)
}
})
this.props.table = new DataTable(this.props.ids.table, dtD)
}
draw() {
return {
table: (movimientos) => {
const info = this.props.table.page.info()
this.props.table.clear().rows.add(this.draw().movimientos(movimientos)).draw()
this.props.table.page(info.page).draw(false)
$(this.props.ids.buttons.edit).on('click', {handler: this.props.eventHandler}, this.props.eventHandler.edit().movimiento)
$(this.props.ids.buttons.remove).on('click', {handler: this.props.eventHandler}, this.props.eventHandler.remove().movimiento)
},
movimientos: (movimientos) => {
const output = []
movimientos.forEach(movimiento => {
this.draw().movimiento({movimiento, output})
})
return output
},
movimiento: ({movimiento, output}) => {
const valor = movimiento.abono - movimiento.cargo
const fecha = movimiento.fecha
const buttons = {
edit: {
icon: 'edit'
},
remove: {
color: ' red',
icon: 'trash'
}
}
const buttonsHTML = Object.entries(buttons).map(([action, {color='', icon}]) => {
const className = this.props.ids.buttons[action].replace('.', '')
return `<button class="ui${color} icon button ${className}" data-id="${movimiento.id}" data-index="${movimiento.index}" data-type="movimiento"><i class="${icon} icon"></i></button>`
}).join('')
let nombre = ''
if (movimiento.detalles) {
if (movimiento.detalles.relacionado) {
let type = movimiento.detalles.relacionadoType
type = type.charAt(0).toUpperCase() + type.slice(1)
nombre = `<span data-tooltip="${type}">${movimiento.detalles.nombres}</span>`
} else {
nombre = movimiento.detalles.nombres
}
}
const params = {
movimiento,
fecha,
valor,
nombre,
buttonsHTML,
}
const data = {}
this.columns.forEach(column => {
if (column.process) {
const args = Object.entries(params).filter(([param, value]) => column.args.includes(param)).map(arr => arr[1])
data[column.title] = column.process(...args)
return
}
if (column.args) {
data[column.title] = Object.entries(params).filter(([param, value]) => column.args.includes(param)).map(arr => arr[1])[0]
return;
}
data[column.title] = params[column.title.toLowerCase()]
})
const values = []
this.columns.forEach(column => {
values.push(data[column.title])
})
output.push(values)
}
}
}
}
</script>

View File

@ -0,0 +1,37 @@
<script>
class ResultsHandler {
props
constructor() {
this.props = {
movimientos: [],
timezone: ((new Date()).getTimezoneOffset()) / -60
}
}
parse() {
return {
movimientos: () => {
const movimientos = this.props.movimientos
const data = []
movimientos.forEach((movimiento, index) => {
this.parse().movimiento({data, movimiento, index})
})
this.props.parsed = data
},
movimiento: ({data, movimiento, index}) => {
const fecha = new Date(movimiento.fecha + 'Z' + this.props.timezone)
data.push({
tipo: 'movimiento',
id: movimiento.id,
index,
cuenta: movimiento.cuenta,
fecha,
cargo: movimiento.cargo,
abono: movimiento.abono,
glosa: movimiento.glosa,
detalles: movimiento.detalles,
})
}
}
}
}
</script>

View File

@ -0,0 +1,53 @@
@extends('layout.base')
@section('page_content')
<div class="ui container">
<h1 class="ui header">Importar Informe de Tesorería</h1>
<form class="ui form" id="import_form" enctype="multipart/form-data">
<div class="ten wide field">
<label>Informe</label>
<div class="ui file action input">
<input type="file" name="file[]" id="file" accept=".xlsx" multiple placeholder="Informe" />
<label for="file" class="ui icon button">
<i class="file icon"></i>
</label>
</div>
</div>
<button class="ui icon button">
<i class="arrow up icon"></i>
Subir
</button>
</form>
</div>
@endsection
@push('page_scripts')
<script>
$(document).ready(() => {
$('#import_form').submit(submitEvent => {
submitEvent.preventDefault()
let formData = new FormData()
formData.append('file', $('#file')[0].files[0])
const url = '{{$urls->api}}/contabilidad/tesoreria/import'
const method = 'post'
const body = new FormData(submitEvent.currentTarget)
fetchAPI(url, {method, body}).then(response => {
if (!response) {
return
}
return response.json().then(data => {
console.log(data)
})
}).catch(error => {
console.error(error)
})
return false
})
})
</script>
@endpush