447 lines
19 KiB
PHP
447 lines
19 KiB
PHP
@extends('layout.base')
|
|
|
|
@section('page_content')
|
|
<div class="ui container">
|
|
<h2 class="ui header">Matriz Facturación</h2>
|
|
<h4 class="ui dividing header">
|
|
<div class="ui two column grid">
|
|
<div id="list_title" class="column">Proyectos</div>
|
|
<div class="right aligned column">
|
|
<div class="ui tiny icon buttons">
|
|
<button id="up_button" class="ui button">
|
|
<i class="up arrow icon"></i>
|
|
</button>
|
|
<button id="refresh_button" class="ui button">
|
|
<i class="refresh icon"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</h4>
|
|
<div id="proyectos" class="ui link selection list">
|
|
@foreach ($proyectos as $proyecto)
|
|
<div class="item" data-proyecto="{{$proyecto->id}}">{{$proyecto->descripcion}}</div>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
<div id="data"></div>
|
|
@endsection
|
|
|
|
@include('layout.head.styles.datatables')
|
|
@include('layout.body.scripts.datatables')
|
|
|
|
@push('page_scripts')
|
|
<script>
|
|
const money = {
|
|
ufs: {},
|
|
ipcs: {},
|
|
sent: {
|
|
uf: {},
|
|
ipc: {}
|
|
},
|
|
get() {
|
|
return {
|
|
uf: date => {
|
|
if (typeof this.sent.uf[date.toISOString()] !== 'undefined') {
|
|
return this.sent.uf[date.toISOString()]
|
|
}
|
|
const url = '{{$urls->api}}/money/uf'
|
|
const data = new FormData()
|
|
data.set('fecha', date.toISOString())
|
|
const options = {
|
|
method: 'post',
|
|
body: data
|
|
}
|
|
return this.sent.uf[date.toISOString()] = fetchAPI(url, options).then(response => {
|
|
if (response.ok) {
|
|
return response.json()
|
|
}
|
|
}).then(json => {
|
|
return this.ufs[json.input.fecha] = json.uf
|
|
})
|
|
},
|
|
ipc: ({end, start}) => {
|
|
const dateKey = [start.getFullYear(), (start.getMonth()+1), end.getFullYear(), (end.getMonth()+1)].join('-')
|
|
if (typeof this.sent.ipc[dateKey] !== 'undefined') {
|
|
return this.sent.ipc[dateKey]
|
|
}
|
|
let mult = 1
|
|
if (start > end) {
|
|
const tmp = structuredClone(end)
|
|
end = structuredClone(start)
|
|
start = tmp
|
|
mult = -1
|
|
}
|
|
const url = '{{$urls->api}}/money/ipc'
|
|
const data = new FormData()
|
|
data.set('start', [start.getFullYear() + ((start.getMonth() > 0) ? 0 : -1), start.getMonth(), start.getDate()].join('-'))
|
|
data.set('end', [end.getFullYear() + ((end.getMonth() > 0) ? 0 : -1), end.getMonth(), end.getDate()].join('-'))
|
|
const options = {
|
|
method: 'post',
|
|
body: data
|
|
}
|
|
return this.sent.ipc[dateKey] = fetchAPI(url, options).then(response => {
|
|
if (!response) {
|
|
return
|
|
}
|
|
return response.json().then(json => {
|
|
return this.ipcs[dateKey] = json.ipc * mult
|
|
})
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
class Unidad {
|
|
id
|
|
tipo
|
|
descripcion
|
|
prorrateo
|
|
precio
|
|
|
|
constructor({id, tipo, descripcion, prorrateo, precio}) {
|
|
this.id = id
|
|
this.tipo = tipo
|
|
this.descripcion = descripcion
|
|
this.prorrateo = prorrateo
|
|
this.precio = precio
|
|
}
|
|
}
|
|
class Venta {
|
|
id
|
|
precio
|
|
fecha
|
|
escritura
|
|
uf
|
|
ipc
|
|
unidades
|
|
principal
|
|
|
|
constructor({id, precio, fecha, escritura, unidades, principal}) {
|
|
this.id = id
|
|
this.precio = precio
|
|
this.fecha = new Date(fecha)
|
|
this.escritura = new Date(escritura)
|
|
this.unidades = unidades
|
|
this.principal = principal
|
|
}
|
|
|
|
draw({tbody, valor_terreno}) {
|
|
/**
|
|
'Venta',
|
|
'Unidades',
|
|
'Tipo',
|
|
'Unidad',
|
|
'Fecha Promesa',
|
|
'Valor Promesa',
|
|
'Prorrateo',
|
|
'Descuento',
|
|
'Precio Venta',
|
|
'Precio Bruto',
|
|
'IVA',
|
|
'Precio Neto',
|
|
'% IVA/Neto',
|
|
'Fecha Escritura',
|
|
'UF',
|
|
'IPC'
|
|
*/
|
|
const dateFormatter = new Intl.DateTimeFormat('es-CL', {year: 'numeric', month: 'numeric', day: 'numeric'})
|
|
const pesosFormatter = new Intl.NumberFormat('es-CL', {style: 'currency', currency: 'CLP'})
|
|
const ufFormatter = new Intl.NumberFormat('es-CL', {minimumFractionDigits: 2, maximumFractionDigits: 2})
|
|
const venta = this.principal.descripcion
|
|
const cantidad = this.unidades.length
|
|
this.unidades.forEach(unidad => {
|
|
const values = [
|
|
'<a href="{{$urls->base}}/ventas/factura/' + this.id + '">' + venta + '</a>',
|
|
cantidad,
|
|
unidad.tipo,
|
|
unidad.descripcion,
|
|
dateFormatter.format(this.fecha),
|
|
ufFormatter.format(unidad.precio) + ' UF',
|
|
unidad.prorrateo,
|
|
'',
|
|
'',
|
|
'',
|
|
'',
|
|
'',
|
|
'',
|
|
'',
|
|
'',
|
|
''
|
|
]
|
|
if (this.escritura !== null) {
|
|
const ipc = (this.ipc >= 0) ? (1 + this.ipc) : 1 / (1 + this.ipc)
|
|
const descuento = valor_terreno * ipc * unidad.prorrateo
|
|
const precio_venta = unidad.precio * this.uf
|
|
const precio_bruto = precio_venta - descuento
|
|
const precio_neto = precio_bruto / 1.19
|
|
const iva = precio_bruto - precio_neto
|
|
|
|
let i = 7
|
|
values[i++] = pesosFormatter.format(descuento)
|
|
values[i++] = pesosFormatter.format(precio_venta)
|
|
values[i++] = pesosFormatter.format(precio_bruto)
|
|
values[i++] = pesosFormatter.format(iva)
|
|
values[i++] = pesosFormatter.format(precio_neto)
|
|
values[i++] = (iva / precio_venta * 100).toFixed(2) + '%'
|
|
values[i++] = dateFormatter.format(this.escritura)
|
|
values[i++] = ufFormatter.format(this.uf)
|
|
values[i++] = ufFormatter.format(this.ipc >= 0 ? this.ipc * 100 : -this.ipc * 100) + '%'
|
|
}
|
|
const row = $('<tr></tr>')
|
|
values.forEach(value => {
|
|
row.append(
|
|
$('<td></td>').html(value)
|
|
)
|
|
})
|
|
tbody.append(row)
|
|
})
|
|
}
|
|
}
|
|
const proyectos = {
|
|
ids: {},
|
|
selected: 0,
|
|
data: JSON.parse('{!! json_encode($proyectos) !!}'),
|
|
sent: false,
|
|
queues: {
|
|
uf: {},
|
|
ipc: {}
|
|
},
|
|
ufs: {},
|
|
ipcs: {},
|
|
table: null,
|
|
get: function() {
|
|
return {
|
|
ventas: () => {
|
|
if (this.sent) {
|
|
return
|
|
}
|
|
this.sent = true
|
|
const url = '{{$urls->api}}/ventas/facturacion/proyecto/' + this.selected
|
|
this.draw().loading()
|
|
return fetchAPI(url).then(response => {
|
|
if (!response) {
|
|
return
|
|
}
|
|
return response.json().then(json => {
|
|
const idx = this.data.findIndex(proyecto => proyecto.id === json.proyecto_id)
|
|
this.data[idx]['ventas'] = []
|
|
const ventas = []
|
|
const chunkSize = 100
|
|
for (let i = 0; i < json.ventas.length; i += chunkSize) {
|
|
const chunk = json.ventas.slice(i, i + chunkSize).map(venta => venta.id)
|
|
ventas.push(this.get().chunk({idx, chunk}))
|
|
}
|
|
Promise.all(ventas).then(() => {
|
|
const promises = []
|
|
promises.push(...this.get().ufs(idx))
|
|
promises.push(...this.get().ipcs(idx))
|
|
Promise.all(promises).then(() => {
|
|
this.draw().ventas(idx)
|
|
this.sent = false
|
|
})
|
|
})
|
|
})
|
|
})
|
|
},
|
|
chunk: ({idx, chunk}) => {
|
|
const url = '{{$urls->api}}/ventas/facturacion/get'
|
|
const method = 'post'
|
|
const body = new FormData()
|
|
body.set('ventas', chunk)
|
|
|
|
return fetchAPI(url, {method, body}).then(response => {
|
|
if (!response) {
|
|
return response
|
|
}
|
|
return response.json().then(json => {
|
|
json.ventas.forEach(venta => {
|
|
this.add().venta({proyecto_idx: idx, venta})
|
|
})
|
|
});
|
|
})
|
|
},
|
|
ufs: idx => {
|
|
const promises = []
|
|
Object.entries(this.queues.uf).forEach(([dateString, ventas]) => {
|
|
const date = new Date(dateString)
|
|
promises.push(money.get().uf(date).then(uf => {
|
|
ventas.forEach(id => {
|
|
const vidx = this.data[idx].ventas.findIndex(venta => venta.id === id)
|
|
this.data[idx].ventas[vidx].uf = uf
|
|
})
|
|
}))
|
|
})
|
|
return promises
|
|
},
|
|
ipcs: idx => {
|
|
const fecha_terreno = (typeof this.data[idx].terreno.date === 'undefined') ? new Date() : new Date(this.data[idx].terreno.date)
|
|
const promises = []
|
|
Object.entries(this.queues.ipc).forEach(([dateString, ventas]) => {
|
|
const date = new Date(dateString)
|
|
promises.push(money.get().ipc({end: date, start: fecha_terreno}).then(ipc => {
|
|
ventas.forEach(id => {
|
|
const vidx = this.data[idx].ventas.findIndex(venta => venta.id === id)
|
|
this.data[idx].ventas[vidx].ipc = ipc
|
|
})
|
|
}))
|
|
})
|
|
return promises
|
|
}
|
|
}
|
|
},
|
|
add() {
|
|
return {
|
|
venta: ({proyecto_idx, venta}) => {
|
|
const v = new Venta(venta)
|
|
this.data[proyecto_idx].ventas.push(v)
|
|
if (v.escritura !== null) {
|
|
const dateString = v.escritura.toString()
|
|
this.register().uf({dateString, venta_id: v.id})
|
|
this.register().ipc({dateString, venta_id: v.id})
|
|
}
|
|
},
|
|
}
|
|
},
|
|
register() {
|
|
return {
|
|
uf: ({dateString, venta_id}) => {
|
|
if (!Object.hasOwn(this.queues.uf, dateString)) {
|
|
this.queues.uf[dateString] = []
|
|
}
|
|
this.queues.uf[dateString].push(venta_id)
|
|
},
|
|
ipc: ({dateString, venta_id}) => {
|
|
if (!Object.hasOwn(this.queues.ipc, dateString)) {
|
|
this.queues.ipc[dateString] = []
|
|
}
|
|
this.queues.ipc[dateString].push(venta_id)
|
|
}
|
|
}
|
|
},
|
|
draw: function() {
|
|
return {
|
|
proyectos: () => {
|
|
$(this.ids.title).html('Proyectos')
|
|
if (this.table !== null) {
|
|
this.table.clear()
|
|
this.table.destroy()
|
|
$(this.ids.data).html('')
|
|
}
|
|
$(this.ids.data).hide()
|
|
$(this.ids.proyectos).find('.item').css('cursor', 'pointer')
|
|
$(this.ids.proyectos).show()
|
|
},
|
|
ventas: proyecto_idx => {
|
|
$(this.ids.title).html(this.data[proyecto_idx].descripcion)
|
|
const parent = $(this.ids.data)
|
|
if (this.table !== null) {
|
|
this.table.clear()
|
|
this.table.destroy()
|
|
this.table = null
|
|
parent.html('')
|
|
}
|
|
const table = $('<table></table>').addClass('ui table')
|
|
table.append(this.draw().thead())
|
|
table.append(this.draw().tbody(proyecto_idx))
|
|
parent.show()
|
|
parent.html(table)
|
|
$(this.ids.proyectos).hide()
|
|
if (this.table === null) {
|
|
this.table = table.DataTable({
|
|
orders: [
|
|
[0, 'asc']
|
|
]
|
|
})
|
|
}
|
|
},
|
|
thead: () => {
|
|
const thead = $('<thead></thead>')
|
|
const columns = [
|
|
'Venta',
|
|
'Unidades',
|
|
'Tipo',
|
|
'Unidad',
|
|
'Fecha Promesa',
|
|
'Valor Promesa',
|
|
'Prorrateo',
|
|
'Descuento',
|
|
'Precio Venta',
|
|
'Precio Bruto',
|
|
'IVA',
|
|
'Precio Neto',
|
|
'% IVA/Neto',
|
|
'Fecha Escritura',
|
|
'UF',
|
|
'IPC'
|
|
]
|
|
const row = $('<tr></tr>')
|
|
columns.forEach(title => {
|
|
row.append(
|
|
$('<th></th>').html(title)
|
|
)
|
|
})
|
|
thead.append(row)
|
|
return thead
|
|
},
|
|
tbody: proyecto_idx => {
|
|
const tbody = $('<tbody></tbody>')
|
|
this.data[proyecto_idx].ventas.forEach(venta => {
|
|
venta.draw({tbody, valor_terreno: this.data[proyecto_idx].terreno.valor})
|
|
})
|
|
return tbody
|
|
},
|
|
loading: () => {
|
|
$(this.ids.proyectos).find('.item').css('cursor', 'wait')
|
|
}
|
|
}
|
|
},
|
|
actions: function() {
|
|
return {
|
|
up: event => {
|
|
this.draw().proyectos()
|
|
},
|
|
refresh: event => {
|
|
const list = $(this.ids.proyectos)
|
|
if (list.is(':hidden')) {
|
|
const table = $(this.ids.table)
|
|
table.hide()
|
|
if (this.table !== null) {
|
|
this.table.clear().destroy()
|
|
this.table = null
|
|
}
|
|
this.get().ventas()
|
|
} else {
|
|
this.draw().proyectos()
|
|
}
|
|
}
|
|
}
|
|
},
|
|
setup: function({ids}) {
|
|
this.ids = ids
|
|
$(this.ids.data).hide()
|
|
$(this.ids.proyectos).find('.item').click(event => {
|
|
this.selected = $(event.currentTarget).data('proyecto')
|
|
this.get().ventas()
|
|
})
|
|
|
|
$(this.ids.buttons.up).click(this.actions().up)
|
|
$(this.ids.buttons.refresh).click(this.actions().refresh)
|
|
}
|
|
}
|
|
$(document).ready(() => {
|
|
proyectos.setup({ids: {
|
|
title: '#list_title',
|
|
proyectos: '#proyectos',
|
|
data: '#data',
|
|
buttons: {
|
|
up: '#up_button',
|
|
refresh: '#refresh_button'
|
|
}
|
|
}
|
|
})
|
|
})
|
|
</script>
|
|
@endpush
|