432 lines
21 KiB
PHP
432 lines
21 KiB
PHP
<script>
|
|
class Factura {
|
|
props = {
|
|
id: 0,
|
|
venta: null,
|
|
index: 0,
|
|
proporcion: 0,
|
|
terreno: {
|
|
fecha: null,
|
|
valor: 0
|
|
},
|
|
emisor: {
|
|
rut: '',
|
|
nombre: '',
|
|
direccion: '',
|
|
comuna: ''
|
|
},
|
|
receptor: {
|
|
rut: '',
|
|
nombre: '',
|
|
direccion: '',
|
|
comuna: ''
|
|
},
|
|
fecha: null,
|
|
unidades: {
|
|
unidad: null,
|
|
descripcion: '',
|
|
precio: 0,
|
|
prorrateo: 0
|
|
},
|
|
detalle: {
|
|
base: 0,
|
|
terreno: 0,
|
|
neto: 0,
|
|
iva: 0,
|
|
bruto: 0,
|
|
descuento: 0,
|
|
total: 0
|
|
},
|
|
total: {
|
|
neto: 0,
|
|
exento: 0,
|
|
iva: 0,
|
|
total: 0
|
|
},
|
|
uf: {
|
|
fecha: null,
|
|
valor: 0
|
|
}
|
|
}
|
|
constructor(props) {
|
|
this.props = props
|
|
}
|
|
get saved() {
|
|
return this.props.id > 0
|
|
}
|
|
get prorrateo() {
|
|
return this.props.unidades.reduce((sum, unidad) => sum + unidad.prorrateo, 0)
|
|
}
|
|
draw() {
|
|
return {
|
|
divider: () => {
|
|
return '<div class="ui divider" data-index="'+this.props.index+'"></div>'
|
|
},
|
|
factura: ({formatters = {date, pesos, ufs, percent}}) => {
|
|
return [
|
|
this.draw().divider(this.props.index),
|
|
`<div class="factura" data-index="${this.props.index}">`,
|
|
'<div class="ui compact grid">',
|
|
this.draw().cabecera(),
|
|
this.draw().propietario({formatters}),
|
|
this.draw().table({formatters}),
|
|
this.draw().totales({formatters}),
|
|
this.draw().guardar(),
|
|
'</div>',
|
|
'</div>'
|
|
].join("\n")
|
|
},
|
|
cabecera: () => {
|
|
return [
|
|
'<div class="two columns row">',
|
|
this.draw().inmobiliaria(),
|
|
this.draw().rut(),
|
|
'</div>'
|
|
].join("\n")
|
|
},
|
|
inmobiliaria: () => {
|
|
return [
|
|
'<div class="twelve wide column">',
|
|
'<strong>'+this.props.emisor.nombre.toUpperCase()+'</strong><br/>',
|
|
'GIRO: <br/>',
|
|
`Dirección: ${this.props.emisor.direccion}, ${this.props.emisor.comuna}`,
|
|
'</div>',
|
|
].join("\n")
|
|
},
|
|
rut: () => {
|
|
return [
|
|
'<div class="four wide column">',
|
|
'<div class="ui center aligned orange segment">',
|
|
'<strong>',
|
|
`RUT:${this.props.emisor.rut.toUpperCase()}<br/>`,
|
|
'FACTURA ELECTRÓNICA<br/>',
|
|
`<span class="ui red text">N° ${this.props.venta.id}${this.props.index}</span>`,
|
|
'</strong>',
|
|
'</div>',
|
|
'</div>'
|
|
].join("\n")
|
|
},
|
|
propietario: ({formatters}) => {
|
|
return [
|
|
'<div class="row">',
|
|
'<table class="ui table">',
|
|
'<tr>'+
|
|
'<td class="grey"><strong>Señor(es)</strong></td>',
|
|
'<td>'+this.props.receptor.nombre+'</td>',
|
|
'<td class="grey"><strong>RUT</strong></td>',
|
|
'<td>'+this.props.receptor.rut.toUpperCase()+'</td>',
|
|
'</tr>',
|
|
'<tr>',
|
|
'<td class="grey"><strong>Giro</strong></td>',
|
|
'<td>Otras Actividades Profesionales</td>',
|
|
'<td class="grey"><strong>Fecha Emisión</strong></td>',
|
|
'<td>'+formatters.date.format(this.props.fecha)+'</td>',
|
|
'</tr>',
|
|
'<tr>',
|
|
'<td class="grey"><strong>Dirección</strong></td>',
|
|
'<td>'+this.props.receptor.direccion+'</td>',
|
|
'<td class="grey"><strong>Comuna</strong></td>',
|
|
'<td>'+this.props.receptor.comuna.toUpperCase()+'</td>',
|
|
'</tr>',
|
|
'</table>',
|
|
'</div>'
|
|
].join("\n")
|
|
},
|
|
table: ({formatters}) => {
|
|
return [
|
|
'<div class="row">',
|
|
'<table class="ui celled table">',
|
|
'<thead>',
|
|
'<tr class="grey">',
|
|
'<th class="center aligned" colspan="6">DETALLES</th>',
|
|
'</tr>',
|
|
'<tr class="grey">',
|
|
'<th>N°</th>',
|
|
'<th class="center aligned">Descripción</th>',
|
|
'<th class="center aligned">Cant/Unidad</th>',
|
|
'<th class="center aligned">Prec. Unit.</th>',
|
|
'<th class="center aligned">Ind</th>',
|
|
'<th class="center aligned">Total</th>',
|
|
'</tr>',
|
|
'</thead>',
|
|
'<tbody>',
|
|
this.draw().unidades({formatters}),
|
|
'</tbody>',
|
|
'<tfoot>',
|
|
'<tr>',
|
|
'<td colspan="6">',
|
|
'<br />',
|
|
'<br />',
|
|
'<br />',
|
|
'<br />',
|
|
'</td>',
|
|
'</tr>',
|
|
'</tfoot>',
|
|
'</table>',
|
|
'</div>'
|
|
].join("\n")
|
|
},
|
|
unidades: ({formatters}) => {
|
|
const unidadesData = []
|
|
let no = 1
|
|
const classes = [
|
|
'',
|
|
'',
|
|
'center aligned',
|
|
'right aligned',
|
|
'center aligned',
|
|
'right aligned'
|
|
]
|
|
this.props.unidades.forEach(unidad => {
|
|
unidadesData.push(this.draw().unidad({
|
|
unidad,
|
|
no: no++,
|
|
classes,
|
|
formatters
|
|
}))
|
|
})
|
|
unidadesData.push(this.draw().resumen({
|
|
no,
|
|
classes,
|
|
formatters
|
|
}))
|
|
return unidadesData.join("\n")
|
|
},
|
|
unidad: ({unidad, no, classes, formatters}) => {
|
|
const descuento = this.props.terreno.valor * unidad.prorrateo * this.props.proporcion
|
|
const precio = unidad.precio * this.props.proporcion
|
|
const bruto = precio - descuento
|
|
const neto = bruto / 1.19
|
|
const data = [
|
|
no,
|
|
unidad.descripcion,
|
|
'1 UNID',
|
|
formatters.pesos.format(neto),
|
|
'AF',
|
|
formatters.pesos.format(neto)
|
|
]
|
|
|
|
const row = ['<tr>']
|
|
data.forEach((value, i) => {
|
|
const cell = ['<td']
|
|
if (classes[i] !== '') {
|
|
cell.push(' class="' + classes[i] + '"')
|
|
}
|
|
cell.push('>'+value+'</td>')
|
|
row.push(cell.join(''))
|
|
})
|
|
row.push('</tr>')
|
|
return row.join('')
|
|
},
|
|
resumen: ({no, classes, formatters}) => {
|
|
const emptyTerreno = '<div class="ui tiny red horizontal circular label">0</div>'
|
|
const data = [
|
|
no,
|
|
'Valor con Terreno: $' + formatters.pesos.format(this.props.detalle.base) + ' - Menos valor terreno: $' + ((this.props.detalle.terreno > 0) ? formatters.pesos.format(-this.props.detalle.terreno) : emptyTerreno) + '<br />' +
|
|
'Base imponible (Neto): $' + formatters.pesos.format(this.props.detalle.neto) + '<br />' +
|
|
'IVA: $' + formatters.pesos.format(this.props.detalle.iva) + '<br />' +
|
|
'SUBTOTAL (Bruto): $' + formatters.pesos.format(this.props.detalle.bruto) + '<br />' +
|
|
'Mas valor terreno: $' + ((this.props.detalle.terreno > 0) ? formatters.pesos.format(this.props.detalle.terreno) : emptyTerreno) + '<br />' +
|
|
'TOTAL (Escritura): $' + formatters.pesos.format(this.props.detalle.total) + '; ' + formatters.ufs.format(this.props.venta.valor * this.props.proporcion) + ' UF<br /><br />' +
|
|
'Descuento Terreno: ' + ((this.props.detalle.terreno > 0) ? formatters.percent.format(this.props.detalle.descuento * 100) : emptyTerreno) + '%<br /><br />' +
|
|
'UF (' + formatters.date.format(this.props.uf.fecha) + '): $' + formatters.ufs.format(this.props.uf.valor),
|
|
'1 UNID',
|
|
formatters.pesos.format(this.props.detalle.terreno),
|
|
'EX',
|
|
formatters.pesos.format(this.props.detalle.terreno),
|
|
]
|
|
|
|
const row = ['<tr class="top aligned">']
|
|
data.forEach((value, i) => {
|
|
const cell = ['<td']
|
|
if (classes[i] !== '') {
|
|
cell.push(' class="'+classes[i]+'"')
|
|
}
|
|
cell.push('>'+value+'</td>')
|
|
row.push(cell.join(''))
|
|
})
|
|
return row.join('')
|
|
},
|
|
totales: ({formatters}) => {
|
|
let tooltips = {
|
|
neto: null,
|
|
iva: null,
|
|
total: null
|
|
}
|
|
if (this.props.total.neto !== this.props.detalle.neto) {
|
|
tooltips.neto = ` data-tooltip="No coinciden netos! Promesa: ${formatters.pesos.format(this.props.detalle.neto)} - Unidades: ${formatters.pesos.format(this.props.total.neto)}"`
|
|
}
|
|
if (this.props.total.iva !== this.props.detalle.iva) {
|
|
tooltips.iva = ` data-tooltip="No coinciden IVAs! Promesa: ${formatters.pesos.format(this.props.detalle.iva)} - Unidades: ${formatters.pesos.format(this.props.total.iva)}"`
|
|
}
|
|
if (this.props.total.total !== this.props.detalle.total) {
|
|
tooltips.total = ` data-tooltip="No coinciden totales! Promesa: ${formatters.pesos.format(this.props.detalle.total)} - Unidades: ${formatters.pesos.format(this.props.total.total)}"`
|
|
}
|
|
return [
|
|
'<div class="row">',
|
|
'<div class="ten wide column"></div>',
|
|
'<div class="six wide column">',
|
|
'<table class="ui celled very compact table">',
|
|
'<thead>',
|
|
'<tr>',
|
|
'<th class="center aligned grey" colspan="2">TOTALES</th>',
|
|
'</tr>',
|
|
'</thead>',
|
|
'<tbody>',
|
|
'<tr>',
|
|
'<td class="grey">Monto Neto</td>',
|
|
`<td class="right aligned${this.props.total.neto !== this.props.detalle.neto ? ' red' : ''}"${this.props.total.neto !== this.props.detalle.neto ? tooltips.neto : ''} id="neto">${formatters.pesos.format(this.props.total.neto)}</td>`,
|
|
'</tr>',
|
|
'<tr>',
|
|
'<td class="grey">Monto Exento</td>',
|
|
'<td class="right aligned" id="exento">'+formatters.pesos.format(this.props.total.exento)+'</td>',
|
|
'</tr>',
|
|
'<tr>',
|
|
'<td class="grey">19% IVA</td>',
|
|
`<td class="right aligned${this.props.total.iva !== this.props.detalle.iva ? ' red' : ''}"${this.props.total.iva !== this.props.detalle.iva ? tooltips.iva : ''} id="iva">${formatters.pesos.format(this.props.total.iva)}</td>`,
|
|
'</tr>',
|
|
'<tr>',
|
|
'<td class="grey">Monto Total</td>',
|
|
`<td class="right aligned${(this.props.total.total !== this.props.detalle.total) ? ' red' : ''}"${(this.props.total.total !== this.props.detalle.total) ? tooltips.total : ''}><strong id="total">${formatters.pesos.format(this.props.total.total)}</strong></td>`,
|
|
'</tr>',
|
|
'</tbody>',
|
|
'</table>',
|
|
'</div>',
|
|
'</div>'
|
|
].join("\n")
|
|
},
|
|
guardar: () => {
|
|
if (this.props.saved) {
|
|
return [
|
|
'<div class="row">',
|
|
'<div class="fourteen wide column"></div>',
|
|
'<div class="two wide center aligned column">',
|
|
`<div class="ui green message guardar" data-index="${this.props.index}">`,
|
|
'<i class="check icon"></i>',
|
|
'Guardada',
|
|
'</div>',
|
|
'</div>',
|
|
'</div>'
|
|
].join("\n")
|
|
}
|
|
return [
|
|
'<div class="row">',
|
|
'<div class="right aligned sixteen wide column">',
|
|
`<button class="ui primary button guardar" data-index="${this.props.index}">Guardar</button>`,
|
|
'</div>',
|
|
'</div>'
|
|
].join("\n")
|
|
}
|
|
}
|
|
}
|
|
watch() {
|
|
return {
|
|
save: () => {
|
|
document.querySelector(`.guardar[data-index="${this.props.index}"]`).addEventListener('click', clickEvent => {
|
|
const index = clickEvent.currentTarget.getAttribute('data-index')
|
|
facturas.venta.save().factura({index: index - 1})
|
|
})
|
|
}
|
|
}
|
|
}
|
|
validate() {
|
|
if (this.props.venta.id === null || typeof this.props.venta.id === 'undefined') {
|
|
return false
|
|
}
|
|
if (this.props.index === null || typeof this.props.index === 'undefined') {
|
|
return false
|
|
}
|
|
if (this.props.proporcion === null || typeof this.props.proporcion === 'undefined') {
|
|
return false
|
|
}
|
|
return !(this.props.receptor.rut === '' || this.props.receptor.nombre === '' || this.props.receptor.direccion === '' || this.props.receptor.comuna === '');
|
|
|
|
}
|
|
save() {
|
|
if (!this.validate()) {
|
|
return
|
|
}
|
|
let url = '{{$urls->api}}/ventas/facturas/add'
|
|
if (this.saved) {
|
|
url = `{{$urls->api}}/ventas/facturas/${this.props.id}/edit`
|
|
}
|
|
const method = 'post'
|
|
const body = new FormData()
|
|
body.set('venta_id', this.props.venta.id)
|
|
body.set('index', this.props.index)
|
|
body.set('proporcion', this.props.proporcion)
|
|
body.set('cliente', JSON.stringify(this.props.receptor))
|
|
body.set('terreno', this.props.detalle.terreno)
|
|
body.set('unidades', JSON.stringify(this.props.unidades.map(unidad => {
|
|
return {unidad_id: unidad.unidad.props.id, precio: unidad.precio, prorrateo: unidad.prorrateo}
|
|
})))
|
|
body.set('fecha', [this.props.fecha.getFullYear(), this.props.fecha.getMonth()+1, this.props.fecha.getDate()].join('-'))
|
|
body.set('detalle', JSON.stringify(this.props.detalle))
|
|
body.set('total', JSON.stringify(this.props.total))
|
|
body.set('uf', JSON.stringify({fecha: [this.props.uf.fecha.getFullYear(), this.props.uf.fecha.getMonth()+1, this.props.uf.fecha.getDate()].join('-'), valor: this.props.uf.valor}))
|
|
|
|
return APIClient.fetch(url, {method, body}).then(response => {
|
|
if (!response) {
|
|
return
|
|
}
|
|
return response.json().then(json => {
|
|
if (json.success) {
|
|
this.props.id = json.factura.id
|
|
facturas.draw().facturas()
|
|
}
|
|
})
|
|
})
|
|
}
|
|
update() {
|
|
return {
|
|
venta: venta => {
|
|
this.props.venta = venta.props
|
|
this.props.fecha = venta.props.facturas.fecha
|
|
this.props.uf.fecha = venta.props.uf.fecha
|
|
this.props.uf.valor = venta.props.uf.valor
|
|
this.update().propietario(venta.props.propietarios.find(propietario => propietario.props.index === this.props.index))
|
|
this.update().unidades(venta.props.unidades)
|
|
this.props.detalle.total = venta.props.valor * this.props.proporcion * venta.props.uf.valor
|
|
this.update().detalle(venta.props.facturas.terreno.valor * this.prorrateo)
|
|
this.props.detalle.descuento = venta.prorrateo * this.props.proporcion
|
|
this.update().total()
|
|
},
|
|
detalle: terreno => {
|
|
this.props.detalle.terreno = terreno * this.props.proporcion
|
|
this.props.detalle.bruto = this.props.detalle.total - this.props.detalle.terreno
|
|
this.props.detalle.neto = this.props.detalle.bruto / 1.19
|
|
this.props.detalle.iva = this.props.detalle.neto * 0.19
|
|
this.props.detalle.base = this.props.detalle.neto + this.props.detalle.terreno
|
|
},
|
|
total: () => {
|
|
this.props.total.exento = this.props.detalle.terreno
|
|
this.props.total.neto = (this.props.unidades.reduce((sum, unidad) => sum + unidad.precio, 0) * this.props.proporcion - this.props.total.exento) / 1.19
|
|
this.props.total.iva = this.props.total.neto * 0.19
|
|
this.props.total.total = this.props.total.neto + this.props.total.iva + this.props.total.exento
|
|
},
|
|
unidades: unidades => {
|
|
this.props.unidades = []
|
|
unidades.forEach(unidad => {
|
|
this.props.unidades.push({
|
|
unidad: unidad,
|
|
descripcion: unidad.changeDescripcion(this.props.proporcion || 1),
|
|
precio: unidad.props.valor * this.props.uf.valor,
|
|
prorrateo: unidad.props.prorrateo
|
|
})
|
|
})
|
|
},
|
|
propietario: propietario => {
|
|
this.props.proporcion = propietario.props.proporcion
|
|
|
|
this.props.receptor = {
|
|
rut: propietario.props.rut ?? '',
|
|
nombre: propietario.props.nombre ?? '',
|
|
direccion: propietario.props.direccion ?? '',
|
|
comuna: propietario.comuna ?? ''
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</script>
|