diff --git a/app/resources/views/proyectos/brokers/contracts/show.blade.php b/app/resources/views/proyectos/brokers/contracts/show.blade.php index d0d0d14..8222756 100644 --- a/app/resources/views/proyectos/brokers/contracts/show.blade.php +++ b/app/resources/views/proyectos/brokers/contracts/show.blade.php @@ -47,6 +47,136 @@ }) } } + class GroupedTableHandler extends TableHandler { + promotions = { + names: new Set(), + values: [] + } + process() { + return { + prices: prices => { + let promotions = {} + prices.map(price => price.promotions).forEach(promotionArray => { + promotionArray.forEach(p => { + if (!Object.hasOwn(promotions, p.name)) { + promotions[p.name] = { + name: p.name, + type: p.type, + amount: [], + final: [] + } + } + promotions[p.name].amount.push(p.amount) + promotions[p.name].final.push(p.final) + }) + }) + return promotions + }, + promotions: () => { + return { + names: promotions => { + Object.keys(promotions).forEach(name => { + this.add().promotion().name(name) + }) + }, + values: ({promotions, formatters}) => { + const temp = Object.values(promotions) + if (temp.length > 0) { + const data = temp.map(p => { + return { + name: p.name, + type: p.type, + amount: { + min: Math.min(...p.amount), + max: Math.max(...p.amount), + desv: Stat.standardDeviation(p.amount), + mean: Stat.mean(p.amount) + }, + final: { + min: Math.min(...p.final), + max: Math.max(...p.final), + desv: Stat.standardDeviation(p.final), + mean: Stat.mean(p.final) + } + } + }) + this.add().promotion().values({promotions: data, formatters}) + return + } + this.promotions.values.push([]) + } + } + } + } + } + add() { + return { + promotion: () => { + return { + name: name => { + this.promotions.names.add(name) + }, + values: ({promotions, formatters}) => { + this.promotions.values.push(promotions.map(promotion => { + const amount_tooltip = [ + `Min: ${promotion.type === 1 ? 'UF ' + formatters.ufs.format(promotion.amount.min) : formatters.percent.format(promotion.amount.min)}`, + `Max: ${promotion.type === 1 ? 'UF ' + formatters.ufs.format(promotion.amount.max) : formatters.percent.format(promotion.amount.max)}`, + `Desv: ${promotion.type === 1 ? 'UF ' + formatters.ufs.format(promotion.amount.desv) : formatters.percent.format(promotion.amount.desv)}` + ].join("\n").replaceAll(' ', ' ') + const final_tooltip = [ + `Min: UF ${formatters.ufs.format(promotion.final.min)}`, + `Max: UF ${formatters.ufs.format(promotion.final.max)}`, + `Desv: UF ${formatters.ufs.format(promotion.final.desv)}` + ].join("\n").replaceAll(' ', ' ') + return { + name: promotion.name, + value: [ + `${promotion.type === 1 ? formatters.ufs.format(promotion.amount.mean) : formatters.percent.format(promotion.amount.mean)}`, + `UF ${formatters.ufs.format(promotion.final.mean)}`, + ].join("\n") + } + })) + } + } + } + } + } + build() { + return { + promotions: (table, tbody) => { + if (this.promotions.names.size > 0) { + const title = document.getElementById(this.ids.promotions) + title.innerHTML = this.promotions.names.size > 0 ? Array.from(this.promotions.names)[0] : '' + title.setAttribute('colspan', '2') + if (this.promotions.names.size > 1) { + const thead = table.querySelector('thead') + Array.from(this.promotions.names).slice(1).forEach(name => { + thead.insertAdjacentHTML('beforeend', `${name}`) + }) + } + const trs = tbody.querySelectorAll('tr') + this.promotions.values.forEach((row, i) => { + const tr = trs[i] + const td = tr.querySelector('td.promotions') + if (row.length === 0) { + td.setAttribute('colspan', 2 * this.promotions.names.size) + return + } + td.remove() + this.promotions.names.forEach(name => { + const index = row.findIndex(r => r.name === name) + if (index === -1) { + tr.insertAdjacentHTML('beforeend', '') + return + } + tr.insertAdjacentHTML('beforeend', row[index].value) + }) + }) + } + } + } + } + } @endprepend diff --git a/app/resources/views/proyectos/brokers/contracts/show/linea.blade.php b/app/resources/views/proyectos/brokers/contracts/show/linea.blade.php index 8ef7067..3536b89 100644 --- a/app/resources/views/proyectos/brokers/contracts/show/linea.blade.php +++ b/app/resources/views/proyectos/brokers/contracts/show/linea.blade.php @@ -8,7 +8,7 @@ Precio Base Comisión Precio Operador - Precio Final + Promociones @@ -16,9 +16,10 @@ @push('page_scripts') diff --git a/app/resources/views/proyectos/brokers/contracts/show/tipo.blade.php b/app/resources/views/proyectos/brokers/contracts/show/tipo.blade.php index f94b9f9..c126d5b 100644 --- a/app/resources/views/proyectos/brokers/contracts/show/tipo.blade.php +++ b/app/resources/views/proyectos/brokers/contracts/show/tipo.blade.php @@ -14,15 +14,11 @@ @push('page_scripts') @endpush diff --git a/app/resources/views/proyectos/brokers/contracts/show/unidades.blade.php b/app/resources/views/proyectos/brokers/contracts/show/unidades.blade.php index 528e594..fac66aa 100644 --- a/app/resources/views/proyectos/brokers/contracts/show/unidades.blade.php +++ b/app/resources/views/proyectos/brokers/contracts/show/unidades.blade.php @@ -17,7 +17,7 @@ Comisión Precio Operador UF/m² - Promociones + Promociones @@ -48,109 +48,158 @@ 'UF/m²', 'promociones', ] + set = { + table: false, + titles: false + } + table constructor(commission) { super(commission) - - const dto = structuredClone(datatables_defaults) - dto.pageLength = 100 - dto.columnDefs = [ - { - target: ['tipo_order', 'unidad_order', 'tipologia', 'piso', 'orientacion', 'metros'].map(column => this.columns.indexOf(column)), - visible: false - }, - { - target: ['tipo'].map(column => this.columns.indexOf(column)), - orderData: ['tipo_order'].map(column => this.columns.indexOf(column)), - }, - { - target: ['unidad'].map(column => this.columns.indexOf(column)), - orderData: ['unidad_order'].map(column => this.columns.indexOf(column)), - }, - { - target: ['unidad', 'precio_base', 'commission', 'precio_operador', 'UF/m²', 'porcentaje', 'precio_final'] - .map(column => this.columns.indexOf(column)), - className: 'dt-right right aligned' - } - ] - dto.order = ['tipo_order', 'unidad_order'].map(column => [this.columns.indexOf(column), 'asc']) - dto.language.searchBuilder = searchBuilder - dto.layout = { - top1Start: { - searchBuilder: { - columns: this.columns.filter(column => !['tipo_order', 'unidad_order'].includes(column)) - .map(column => this.columns.indexOf(column)), + } + setup() { + return { + titles: promotions_names => { + if (this.set.titles || promotions_names.size === 0) { + return } + + const nameArray = Array.from(promotions_names) + + this.columns.pop() + this.columns.push(`${nameArray[0].toLowerCase().replaceAll(' ', '_')}.amount`) + this.columns.push(`${nameArray[0].toLowerCase().replaceAll(' ', '_')}.final`) + + const table = document.getElementById(this.ids.units) + const thead = table.querySelector('thead') + const tr = thead.querySelector('tr') + const th = tr.querySelector('th#unit_promotions') + th.innerHTML = `${nameArray[0]}
Porcentaje` + tr.insertAdjacentHTML('beforeend', `${nameArray[0]}
Precio Final`) + + if (promotions_names.size > 1) { + nameArray.slice(1).forEach(name => { + this.columns.push(`${name.toLowerCase().replaceAll(' ', '_')}.amount`) + this.columns.push(`${name.toLowerCase().replaceAll(' ', '_')}.final`) + + tr.insertAdjacentHTML('beforeend', `${name}
Porcentaje`) + tr.insertAdjacentHTML('beforeend', `${name}
Precio Final`) + }) + } + this.set.titles = true }, - top1End: { - buttons: [ + table: () => { + if (typeof this.table !== 'undefined' || this.set.table) { + return + } + + const dto = structuredClone(datatables_defaults) + dto.pageLength = 100 + dto.columnDefs = [ { - extend: 'excelHtml5', - className: 'green', - text: 'Exportar a Excel ', - title: 'Lista de Precios - {{ $contract->broker->name }} - {{ $contract->project->descripcion }} - {{ (new DateTime())->format('Y-m-d') }}', - download: 'open', - exportOptions: { - columns: ['tipo', 'unidad', 'tipologia', 'piso', 'orientacion', 'metros_interior', 'metros_terraza', 'metros', 'metros_total', 'precio_operador', 'promociones'] - .map(column => this.columns.indexOf(column)), - rows: (idx, data, node) => { - return data[this.columns.indexOf('estado')] === 'Libre' - }, - format: { - body: (data, row, columnIdx, node) => { - if (typeof data === 'string' && data.includes('(.*)<\/span>/, '$1') - } - if (['metros'].map(column => this.columns.indexOf(column)).includes(columnIdx)) { - return data.replaceAll('.', '').replaceAll(',', '.') - } - return data - } - } - }, + target: ['tipo_order', 'unidad_order', 'tipologia', 'piso', 'orientacion', 'metros'].map(column => this.columns.indexOf(column)), + visible: false + }, + { + target: ['tipo'].map(column => this.columns.indexOf(column)), + orderData: ['tipo_order'].map(column => this.columns.indexOf(column)), + }, + { + target: ['unidad'].map(column => this.columns.indexOf(column)), + orderData: ['unidad_order'].map(column => this.columns.indexOf(column)), + }, + { + target: ['unidad', 'precio_base', 'commission', 'precio_operador', 'UF/m²', 'porcentaje', 'precio_final'] + .map(column => this.columns.indexOf(column)), + className: 'dt-right right aligned' } ] + dto.order = ['tipo_order', 'unidad_order'].map(column => [this.columns.indexOf(column), 'asc']) + dto.language.searchBuilder = searchBuilder + const exportColumns = ['tipo', 'unidad', 'tipologia', 'piso', 'orientacion', 'metros_interior', 'metros_terraza', 'metros', 'metros_total', 'precio_operador', 'promociones'] + if (typeof this.columns['promotions'] === 'undefined') { + exportColumns.pop() + this.columns.slice(this.columns.indexOf('UF/m²')).forEach(name => { + exportColumns.push(name) + }) + } + dto.layout = { + top1Start: { + searchBuilder: { + columns: this.columns.filter(column => !['tipo_order', 'unidad_order'].includes(column)) + .map(column => this.columns.indexOf(column)), + } + }, + top1End: { + buttons: [ + { + extend: 'excelHtml5', + className: 'green', + text: 'Exportar a Excel ', + title: 'Lista de Precios - {{ $contract->broker->name }} - {{ $contract->project->descripcion }} - {{ (new DateTime())->format('Y-m-d') }}', + download: 'open', + exportOptions: { + columns: exportColumns + .map(column => this.columns.indexOf(column)), + rows: (idx, data, node) => { + return data[this.columns.indexOf('estado')] === 'Libre' + }, + format: { + body: (data, row, columnIdx, node) => { + if (typeof data === 'string' && data.includes('(.*)<\/span>/, '$1') + } + const formatColumns = ['metros', 'metros_interior', 'metros_terraza', 'metros_total'] + if (this.set.titles) { + this.columns.filter(column => column.includes('.amount') || column.includes('.final')).forEach(name => { + formatColumns.push(name) + }) + } + if (formatColumns.map(column => this.columns.indexOf(column)).includes(columnIdx)) { + return data.replaceAll('.', '').replaceAll(',', '.') + } + return data + } + } + }, + } + ] + } + } + + this.table = $(`#${this.ids.units}`).DataTable(dto) + + this.set.table = true } } - $(`#${this.ids.units}`).DataTable(dto) } draw({units, formatters}) { - const table = $(`#${this.ids.units}`).DataTable() - table.clear() - const tableData = [] const prices = this.prices(units) + + const promotions_names = new Set() + const tableData = [] units.forEach(unidad => { const tipo = unidad.proyecto_tipo_unidad.tipo_unidad.descripcion const price = prices.find(p => p.id === unidad.id) - let subTable = false + let promotions = [] + price.promotions.forEach(p => { + if (Object.hasOwn(promotions, p.name)) { + return + } + promotions[p.name] = { + name: p.name, + type: p.type, + amount: p.type === 1 ? 'UF ' + formatters.ufs.format(p.amount) : formatters.percent.format(p.amount), + final: `UF ${formatters.ufs.format(p.final)}` + } + }) - if (price.promotions.length > 0) { - subTable = document.createElement('table') - subTable.className = 'ui table' - const subTHead = document.createElement('thead') - price.promotions.forEach(p => { - subTHead.insertAdjacentHTML('beforeend', [ - '', - 'Nombre', - 'Forma', - 'Precio Final', - '' - ].join("\n")) - }) - const subTBody = document.createElement('tbody') - price.promotions.forEach(p => { - subTBody.insertAdjacentHTML('beforeend', [ - '', - `${p.name}`, - `${p.type === 1 ? 'UF ' + formatters.ufs.format(p.amount) : formatters.percent.format(p.amount)}`, - `UF ${formatters.ufs.format(p.final)}`, - '' - ].join("\n")) - }) - subTable.appendChild(subTHead).appendChild(subTBody) - } + Object.keys(promotions).forEach(name => { + promotions_names.add(name) + }) + const temp = Object.values(promotions) - tableData.push([ + const data = [ unidad.sold ? `Vendida` : 'Libre', tipo.charAt(0).toUpperCase() + tipo.slice(1), unidad.proyecto_tipo_unidad.tipo_unidad.orden, @@ -167,9 +216,33 @@ formatters.percent.format(price.commission ?? 0), 'UF ' + formatters.ufs.format(price.broker ?? 0), formatters.ufs.format(price.broker / unidad.proyecto_tipo_unidad.vendible), - subTable ? subTable.outerHTML : 'UF ' + formatters.ufs.format(price.broker ?? 0), - ]) + '' + ] + + if (temp.length > 0) { + data.pop() + temp.forEach((p, i) => { + data.push(p.amount) + data.push(p.final) + }) + } else { + if (promotions_names.size > 0) { + Array.from(promotions_names).forEach(name => { + data.push('') + data.push('') + }) + } + } + + tableData.push(data) }) + + this.setup().titles(promotions_names) + this.setup().table() + + const table = this.table + table.clear() + table.rows.add(tableData) table.draw() }