diff --git a/app/resources/routes/api/ventas/facturacion.php b/app/resources/routes/api/ventas/facturacion.php
index cae92a8..3c9d70d 100644
--- a/app/resources/routes/api/ventas/facturacion.php
+++ b/app/resources/routes/api/ventas/facturacion.php
@@ -3,4 +3,5 @@ use Incoviba\Controller\API\Ventas\Facturacion;
$app->group('/facturacion', function($app) {
$app->get('/proyecto/{proyecto_id}[/]', [Facturacion::class, 'proyecto']);
+ $app->post('/get[/]', [Facturacion::class, 'ventas']);
});
diff --git a/app/resources/views/ventas/facturacion.blade.php b/app/resources/views/ventas/facturacion.blade.php
index e0eac21..bb24aa8 100644
--- a/app/resources/views/ventas/facturacion.blade.php
+++ b/app/resources/views/ventas/facturacion.blade.php
@@ -60,25 +60,33 @@
return this.ufs[json.input.fecha] = json.uf
})
},
- ipc: (end, start) => {
+ 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.toISOString())
- data.set('end', end.toISOString())
+ 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.ok) {
- return response.json()
+ if (!response) {
+ return
}
- }).then(json => {
- return this.ipcs[json.date_string] = json.ipc
+ return response.json().then(json => {
+ return this.ipcs[dateKey] = json.ipc * mult
+ })
})
}
}
@@ -92,8 +100,7 @@
prorrateo
precio
- constructor({id, tipo, descripcion, prorrateo, precio})
- {
+ constructor({id, tipo, descripcion, prorrateo, precio}) {
this.id = id
this.tipo = tipo
this.descripcion = descripcion
@@ -111,44 +118,13 @@
unidades
principal
- constructor({id, precio, fecha, escritura}) {
+ constructor({id, precio, fecha, escritura, unidades, principal}) {
this.id = id
this.precio = precio
- this.fecha = fecha
- this.escritura = escritura
- this.uf = 1
- this.ipc = 1
- this.unidades = []
- }
-
- get() {
- return {
- unidades: () => {
- const url = '{{$urls->api}}/venta/' + this.id + '/unidades'
- return fetchAPI(url).then(response => {
- if (response.ok) {
- return response.json()
- }
- }).then(json => {
- json.unidades.forEach(unidad => {
- const tipo = unidad.proyecto_tipo_unidad.tipo_unidad.descripcion
- const data = {
- id: unidad.id,
- tipo: tipo.charAt(0).toUpperCase() + tipo.slice(1),
- descripcion: unidad.descripcion,
- prorrateo: unidad.prorrateo,
- precio: 0
- }
- if (unidad.current_precio !== null) {
- data.precio = unidad.current_precio.valor
- }
- const u = new Unidad(data)
- this.unidades.push(u)
- })
- this.principal = this.unidades.filter(unidad => unidad.tipo === 'Departamento')[0]
- })
- }
- }
+ this.fecha = new Date(fecha)
+ this.escritura = new Date(escritura)
+ this.unidades = unidades
+ this.principal = principal
}
draw({tbody, valor_terreno}) {
@@ -173,7 +149,7 @@
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.unidades[0].descripcion
+ const venta = this.principal.descripcion
const cantidad = this.unidades.length
this.unidades.forEach(unidad => {
const values = [
@@ -195,7 +171,8 @@
''
]
if (this.escritura !== null) {
- const descuento = valor_terreno * (1 + this.ipc / 100) * unidad.prorrateo
+ 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
@@ -210,7 +187,7 @@
values[i++] = (iva / precio_venta * 100).toFixed(2) + '%'
values[i++] = dateFormatter.format(this.escritura)
values[i++] = ufFormatter.format(this.uf)
- values[i++] = (this.ipc).toFixed(2) + '%'
+ values[i++] = ufFormatter.format(this.ipc >= 0 ? this.ipc * 100 : -this.ipc * 100) + '%'
}
const row = $('
')
values.forEach(value => {
@@ -227,6 +204,10 @@
selected: 0,
data: JSON.parse('{!! json_encode($proyectos) !!}'),
sent: false,
+ queues: {
+ uf: {},
+ ipc: {}
+ },
ufs: {},
ipcs: {},
table: null,
@@ -243,89 +224,99 @@
if (!response) {
return
}
- return response.json()
- }).then(json => {
- const idx = this.data.findIndex(proyecto => proyecto.id === json.proyecto_id)
- const fecha_terreno = (typeof this.data[idx].terreno.date === 'undefined') ? new Date() : new Date(this.data[idx].terreno.date)
- this.data[idx]['ventas'] = []
- const ventas = []
- const unidadesQueue = []
- const ufQueue = {}
- const ipcQueue = {}
- const chunkSize = 100
- const url = '{{$urls->api}}/ventas/get'
- for (let i = 0; i < json.ventas.length; i += chunkSize) {
- const chunk = json.ventas.slice(i, i + chunkSize).map(venta => venta.id)
- const body = new FormData()
- body.set('ventas', chunk)
- const promise = fetchAPI(url, {method: 'post', body}).then(response => {
- if (!response) {
- return response
- }
- return response.json().then(json => {
- json.ventas.forEach(venta => {
- const data = {
- id: venta.id,
- precio: venta.valor,
- fecha: new Date(venta.fecha),
- escritura: new Date(venta.fecha)
- }
- if (['escriturando'].includes(venta.current_estado.tipo_estado_venta.descripcion)) {
- data.escritura = new Date(venta.current_estado.fecha)
- }
- const v = new Venta(data)
- if (v.escritura !== null) {
- const dateString = v.escritura.toString()
- if (!Object.hasOwn(ufQueue, dateString)) {
- ufQueue[dateString] = []
- }
- ufQueue[dateString].push(v.id)
- if (!Object.hasOwn(ipcQueue, dateString)) {
- ipcQueue[dateString] = []
- }
- ipcQueue[dateString].push(v.id)
- }
- unidadesQueue.push(v.id)
- this.data[idx].ventas.push(v)
-
- })
- });
- })
- ventas.push(promise)
- }
- Promise.all(ventas).then(() => {
- const promises = []
- Object.entries(ufQueue).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
- })
- }))
- })
- Object.entries(ipcQueue).forEach(([dateString, ventas]) => {
- const date = new Date(dateString)
- promises.push(money.get().ipc(date, 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
- })
- }))
- })
- for (let i = 0; i < unidadesQueue.length; i += chunkSize) {
- const chunk = unidadesQueue.slice(i, i + chunkSize)
- chunk.forEach(id => {
- const vidx = this.data[idx].ventas.findIndex(venta => venta.id === id)
- promises.push(this.data[idx].ventas[vidx].get().unidades())
- })
+ 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(promises).then(() => {
- this.draw().ventas(idx)
- this.sent = false
+ 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)
}
}
},
diff --git a/app/resources/views/ventas/facturacion/show.blade.php b/app/resources/views/ventas/facturacion/show.blade.php
index 1450208..a2e1b39 100644
--- a/app/resources/views/ventas/facturacion/show.blade.php
+++ b/app/resources/views/ventas/facturacion/show.blade.php
@@ -887,178 +887,6 @@
].join("\n")
}
}
- /*const output = [
- ''+
- ''+
- '
'+
- '
'+
- '
'+
- ''+proyecto.inmobiliaria.nombre.toUpperCase()+'
'+
- 'GIRO:
'+
- 'Dirección:'+proyecto.direccion+
- '
'+
- '
'+
- '
'+
- ''+
- 'RUT:'+proyecto.inmobiliaria.rut.toUpperCase()+'
'+
- 'FACTURA ELECTRÓNICA
'+
- 'N° #'+
- ''+
- '
'+
- '
'+
- '
'+
- '
'+
- '
'+
- ''+
- 'Señor(es) | '+
- ''+propietario.props.nombre+' | '+
- 'RUT | '+
- ''+propietario.props.rut.toUpperCase()+' | '+
- '
'+
- ''+
- 'Giro | '+
- 'Otras Actividades Profesionales | '+
- 'Fecha Emisión | '+
- ''+formatters.date.format(propietario.props.fecha)+' | '+
- '
'+
- ''+
- 'Dirección | '+
- ''+propietario.props.direccion+' | '+
- 'Comuna | '+
- ''+propietario.props.comuna.toUpperCase()+' | '+
- '
'+
- '
'+
- '
'+
- '
'+
- '
'+
- ''+
- ''+
- 'DETALLES | '+
- '
'+
- ''+
- 'N° | '+
- 'Descripción | '+
- 'Cant/Unidad | '+
- 'Prec. Unit. | '+
- 'Ind | '+
- 'Total | '+
- '
'+
- ''
- ]
- const unidadesData = []
-
- let c = 1
- const classes = [
- '',
- '',
- 'center aligned',
- 'right aligned',
- 'center aligned',
- 'right aligned'
- ]
- const uf_mult = this.props.uf / uf
- unidades.forEach(unidad => {
- const descuento = parseFloat(proyecto.terreno) * parseFloat(unidad.prorrateo) * uf_mult
- const bruto = parseFloat(unidad.base) * uf_mult - descuento
- const neto = bruto / 1.19
- const data = [
- c ++,
- unidad.descripcion + ' (UF ' + formatters.ufs.format(unidad.precio * this.props.proporcion * uf_mult) + ')',
- '1 UNID',
- formatters.pesos.format(neto * this.props.proporcion),
- 'AF',
- formatters.pesos.format(neto * this.props.proporcion)
- ]
-
- const row = ['']
- data.forEach((value, i) => {
- const cell = [''+value+' | ')
- row.push(cell.join(''))
- })
- row.push('
')
- unidadesData.push(row.join(''))
- })
-
- const emptyTerreno = '0
'
- const data = [
- c,
- 'Valor con Terreno ' + formatters.pesos.format((venta.base * uf_mult + venta.terreno * uf_mult) * this.props.proporcion) + ' - Menos valor terreno ' + ((venta.terreno > 0) ? formatters.pesos.format(-venta.terreno * uf_mult * this.props.proporcion) : emptyTerreno) + '
' +
- 'Base imponible ' + formatters.pesos.format(venta.base * uf_mult * this.props.proporcion) + '
' +
- 'IVA ' + formatters.pesos.format(venta.iva * uf_mult * this.props.proporcion) + '
' +
- 'SUBTOTAL ' + formatters.pesos.format(venta.subtotal * uf_mult * this.props.proporcion) + '
' +
- 'Mas valor terreno ' + ((venta.terreno > 0) ? formatters.pesos.format(venta.terreno * uf_mult * this.props.proporcion) : emptyTerreno) + '
' +
- 'TOTAL ' + formatters.pesos.format(venta.total * uf_mult * this.props.proporcion) + ';' + formatters.ufs.format(venta.totalUF * this.props.proporcion) + ' UF
' +
- 'Descuento Terreno: ' + ((venta.terreno > 0) ? formatters.percent.format(venta.prorrateo * 100) : emptyTerreno) + '%
' +
- 'UF: ' + formatters.ufs.format(uf),
- '1 UNID',
- formatters.pesos.format(venta.terreno * uf_mult * this.props.proporcion),
- 'EX',
- formatters.pesos.format(venta.terreno * uf_mult * this.props.proporcion)
- ]
-
- const row = ['']
- data.forEach((value, i) => {
- const cell = [''+value+' | ')
- row.push(cell.join(''))
- })
- unidadesData.push(row.join(''))
-
- output.push('
'+unidadesData.join('')+'')
- output.push(
- ''+
- ''+
- ''+
- ' '+
- ' '+
- ' '+
- ' '+
- ' | '+
- '
'+
- ''+
- '
'+
- '
'+
- '
'+
- '
'+
- '
'+
- '
'+
- ''+
- ''+
- 'TOTALES | '+
- '
'+
- ''+
- ''+
- ''+
- 'Monto Neto | '+
- ''+formatters.pesos.format(venta.neto * uf_mult * this.props.proporcion)+' | '+
- '
'+
- ''+
- 'Monto Exento | '+
- ''+formatters.pesos.format(venta.exento * uf_mult * this.props.proporcion)+' | '+
- '
'+
- ''+
- '19% IVA | '+
- ''+formatters.pesos.format(venta.iva * uf_mult * this.props.proporcion)+' | '+
- '
'+
- ''+
- 'Monto Total | '+
- ''+formatters.pesos.format(venta.total * uf_mult * this.props.proporcion)+' | '+
- '
'+
- ''+
- '
'+
- '
'+
- '
'+
- '
'+
- '
'
- )
- return output.join('')*/
}
}
diff --git a/app/src/Controller/API/Ventas/Facturacion.php b/app/src/Controller/API/Ventas/Facturacion.php
index 759a426..e88e7ae 100644
--- a/app/src/Controller/API/Ventas/Facturacion.php
+++ b/app/src/Controller/API/Ventas/Facturacion.php
@@ -2,16 +2,15 @@
namespace Incoviba\Controller\API\Ventas;
use DateTimeImmutable;
-use Incoviba\Common\Implement\Exception\EmptyRedis;
-use Incoviba\Common\Implement\Exception\EmptyResult;
-use Incoviba\Controller\API\emptyBody;
-use Incoviba\Controller\API\withJson;
-use Incoviba\Controller\withRedis;
-use Incoviba\Service;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
+use Incoviba\Common\Implement\Exception\{EmptyRedis,EmptyResult};
+use Incoviba\Controller\API\{emptyBody,withJson};
+use Incoviba\Controller\withRedis;
+use Incoviba\Service;
+use Incoviba\Common\Ideal;
-class Facturacion
+class Facturacion extends Ideal\Service
{
use withJson, withRedis, emptyBody;
@@ -34,4 +33,31 @@ class Facturacion
}
return $this->withJson($response, $output);
}
+ public function ventas(ServerRequestInterface $request, ResponseInterface $response, Service\Redis $redisService,
+ Service\Venta $ventaService, Service\Proyecto\Terreno $terrenoService, Service\UF $ufService,
+ Service\IPC $ipcService): ResponseInterface
+ {
+ $input = $request->getParsedBody();
+ $output = [
+ 'input' => $input,
+ 'ventas' => []
+ ];
+ $ventas = explode(',', $input['ventas']);
+ foreach ($ventas as $venta_id) {
+ $redisKey = "ventas:facturacion:venta:{$venta_id}";
+ try {
+ $venta = $this->fetchRedis($redisService, $redisKey);
+ $output['ventas'] []= $venta;
+ } catch (EmptyRedis) {
+ try {
+ $venta = $ventaService->getFacturacionById($venta_id);
+ $output['ventas'] []= $venta;
+ $this->saveRedis($redisService, $redisKey, $venta);
+ } catch (EmptyResult $exception) {
+ $this->logger->notice($exception);
+ }
+ }
+ }
+ return $this->withJson($response, $output);
+ }
}
diff --git a/app/src/Service/Venta.php b/app/src/Service/Venta.php
index 36d9642..7cc2708 100644
--- a/app/src/Service/Venta.php
+++ b/app/src/Service/Venta.php
@@ -2,11 +2,12 @@
namespace Incoviba\Service;
use DateTimeImmutable;
+use DateInterval;
+use Psr\Log\LoggerInterface;
use Incoviba\Common\Ideal\Service;
use Incoviba\Common\Implement;
use Incoviba\Repository;
use Incoviba\Model;
-use Psr\Log\LoggerInterface;
class Venta extends Service
{
@@ -27,6 +28,7 @@ class Venta extends Service
protected Venta\Credito $creditoService,
protected Venta\BonoPie $bonoPieService,
protected Venta\Pago $pagoService,
+ protected Proyecto\Terreno $terrenoService,
protected Money $moneyService
) {
parent::__construct($logger);
@@ -91,6 +93,33 @@ class Venta extends Service
{
return $this->process($this->ventaRepository->fetchByPie($pie_id));
}
+ public function getFacturacionById(int $venta_id): array
+ {
+ $venta = $this->getById($venta_id);
+ $escritura = (in_array($venta->currentEstado()->tipoEstadoVenta->descripcion, ['escriturando'])) ? $venta->currentEstado()->fecha : $venta->fecha;
+ $data = [
+ 'id' => $venta->id,
+ 'fecha' => $venta->fecha->format('Y-m-d'),
+ 'valor' => $venta->valor,
+ 'escritura' => $escritura->format('Y-m-d'),
+ 'unidades' => [],
+ ];
+ foreach ($venta->propiedad()->unidades as $unidad) {
+ $data['unidades'] []= [
+ 'id' => $unidad->id,
+ 'tipo' => ucwords($unidad->proyectoTipoUnidad->tipoUnidad->descripcion),
+ 'descripcion' => $unidad->descripcion,
+ 'prorrateo' => $unidad->prorrateo,
+ 'precio' => (isset($unidad->currentPrecio)) ? $unidad->currentPrecio->valor : 0
+ ];
+ }
+ $principal = $venta->propiedad()->principal();
+ $data['principal'] = [
+ 'id' => $principal->id,
+ 'descripcion' => $principal->descripcion
+ ];
+ return $data;
+ }
protected function process(Model\Venta $venta): Model\Venta
{