fix: corregir ventas y mejorar catalogos
This commit is contained in:
@@ -1,7 +1,22 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { ExportImportService } from '@toldo/services';
|
||||
import { db } from '@toldo/data';
|
||||
import { JornadaService } from '@toldo/services';
|
||||
|
||||
describe('contratos de operación y respaldo', () => {
|
||||
it('persiste una venta y su movimiento de inventario dentro de la misma transacción', async () => {
|
||||
await db.delete();
|
||||
await db.open();
|
||||
await db.productos.add({ id: 'product-sale', productorId: 'producer-sale', nombrePresentacion: 'Producto de prueba', precioUnitario: 1000, activo: true, createdAt: new Date() } as any);
|
||||
await db.movimientosInventario.add({ id: 'stock-sale', fecha: new Date(), tipo: 'INGRESO_INICIAL', productoId: 'product-sale', cantidad: 3, createdAt: new Date() } as any);
|
||||
const jornadaService = new JornadaService();
|
||||
const jornadaId = await jornadaService.abrirJornada(['Prueba'], 0);
|
||||
const ventaId = crypto.randomUUID();
|
||||
await jornadaService.registrarVentaEnJornada(jornadaId, { id: ventaId, fecha: new Date(), subtotal: 1000, total: 1000, createdAt: new Date() } as any, [{ id: crypto.randomUUID(), ventaId, productoId: 'product-sale', cantidad: 1, precioUnitario: 1000, subtotal: 1000, formaPago: 'EFECTIVO' } as any]);
|
||||
expect(await db.ventas.get(ventaId)).toBeTruthy();
|
||||
expect((await db.movimientosInventario.toArray()).filter((movimiento) => movimiento.referenciaId === ventaId)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('expone la versión actual en las exportaciones', () => {
|
||||
const result = new ExportImportService().exportAll({ productores: [], productos: [], movimientosInventario: [], ventas: [], ventaLineas: [], gastos: [], ajustes: [], cuadres: [] });
|
||||
expect(result.appVersion).toBe('0.0.2');
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'vuetify/styles'
|
||||
import '@mdi/font/css/materialdesignicons.css'
|
||||
import { createVuetify } from 'vuetify'
|
||||
import * as components from 'vuetify/components'
|
||||
import * as directives from 'vuetify/directives'
|
||||
|
||||
@@ -20,8 +20,9 @@
|
||||
<v-btn @click="crearProductor" color="primary" block>Agregar</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-text-field v-model="busquedaProductores" label="Buscar productor" prepend-inner-icon="mdi-magnify" clearable density="compact" />
|
||||
<v-list>
|
||||
<v-list-item v-for="p in productores" :key="p.id" :title="p.nombre"><template #append><v-btn icon="mdi-pencil" variant="text" size="small" :aria-label="`Editar ${p.nombre}`" @click="editarProductor(p)" /></template></v-list-item>
|
||||
<v-list-item v-for="p in productoresFiltrados" :key="p.id" :title="p.nombre"><template #append><v-btn icon="mdi-pencil-outline" color="primary" variant="tonal" size="small" :aria-label="`Editar ${p.nombre}`" @click="editarProductor(p)" /></template></v-list-item>
|
||||
</v-list>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
@@ -35,8 +36,9 @@
|
||||
<v-text-field v-model="nombreProd" label="Nombre/Presentación (ej: Café molido 250gr)" density="compact" class="mb-2"></v-text-field>
|
||||
<v-text-field v-model.number="precioProd" label="Precio unitario" type="number" density="compact" class="mb-2"></v-text-field>
|
||||
<v-btn @click="crearProducto" color="primary" block>Agregar Producto</v-btn>
|
||||
<v-text-field v-model="busquedaProductos" label="Buscar producto" prepend-inner-icon="mdi-magnify" clearable density="compact" class="mt-3" />
|
||||
<v-list class="mt-3">
|
||||
<v-list-item v-for="prod in productos" :key="prod.id" :title="prod.nombrePresentacion" :subtitle="'$' + prod.precioUnitario"><template #append><v-btn icon="mdi-pencil" variant="text" size="small" :aria-label="`Editar ${prod.nombrePresentacion}`" @click="editarProducto(prod)" /></template></v-list-item>
|
||||
<v-list-item v-for="prod in productosFiltrados" :key="prod.id" :title="prod.nombrePresentacion" :subtitle="'$' + prod.precioUnitario"><template #append><v-btn icon="mdi-pencil-outline" color="primary" variant="tonal" size="small" :aria-label="`Editar ${prod.nombrePresentacion}`" @click="editarProducto(prod)" /></template></v-list-item>
|
||||
</v-list>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
@@ -72,7 +74,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { db } from '@toldo/data';
|
||||
import { InventarioService, ImportInventarioCsvService, EdicionService } from '@toldo/services';
|
||||
|
||||
@@ -101,6 +103,10 @@ const edicionProducto = ref<any>({});
|
||||
const originalEdicion = ref<any>({});
|
||||
const actor = ref('');
|
||||
const motivo = ref('');
|
||||
const busquedaProductores = ref('');
|
||||
const busquedaProductos = ref('');
|
||||
const productoresFiltrados = computed(() => productores.value.filter((productor) => !busquedaProductores.value.trim() || productor.nombre.toLocaleLowerCase().includes(busquedaProductores.value.trim().toLocaleLowerCase())));
|
||||
const productosFiltrados = computed(() => productos.value.filter((producto) => !busquedaProductos.value.trim() || producto.nombrePresentacion.toLocaleLowerCase().includes(busquedaProductos.value.trim().toLocaleLowerCase())));
|
||||
function avisar(texto: string, tipo: 'success' | 'error' = 'error') { mensaje.value = texto; tipoMensaje.value = tipo; }
|
||||
function editarProductor(productor: any) { entidadEdicion.value = 'PRODUCTOR'; originalEdicion.value = { ...productor }; edicionProductor.value = { ...productor }; actor.value = ''; motivo.value = ''; dialogoEdicion.value = true; }
|
||||
function editarProducto(producto: any) { entidadEdicion.value = 'PRODUCTO'; originalEdicion.value = { ...producto }; edicionProducto.value = { ...producto }; actor.value = ''; motivo.value = ''; dialogoEdicion.value = true; }
|
||||
|
||||
@@ -3,11 +3,12 @@
|
||||
<div class="d-flex flex-wrap align-center justify-space-between mb-4 ga-2"><div><h1 class="text-h4 mb-1">Punto de venta</h1><div class="text-body-2 text-medium-emphasis">Toca un producto para agregarlo al carrito</div></div><v-chip :color="jornadaAbierta ? 'success' : 'warning'" variant="tonal" :prepend-icon="jornadaAbierta ? 'mdi-check-circle' : 'mdi-alert'">{{ jornadaAbierta ? 'Jornada abierta' : 'Abre una jornada' }}</v-chip></div>
|
||||
<v-row>
|
||||
<v-col cols="12" md="8">
|
||||
<v-card>
|
||||
<v-card>
|
||||
<v-card-title>Productos disponibles</v-card-title>
|
||||
<v-card-text>
|
||||
<v-text-field v-model="busqueda" label="Buscar por producto o productor" prepend-inner-icon="mdi-magnify" clearable density="compact" class="mb-3" />
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4" v-for="prod in productos" :key="prod.id">
|
||||
<v-col cols="12" sm="6" md="4" v-for="prod in productosFiltrados" :key="prod.id">
|
||||
<v-card @click="agregarProducto(prod)" class="cursor-pointer" hover>
|
||||
<v-card-text>
|
||||
<div class="text-subtitle-1 font-weight-bold">{{ prod.nombrePresentacion }}</div>
|
||||
@@ -31,14 +32,14 @@
|
||||
<v-list-item-subtitle>
|
||||
<v-row align="center" class="mt-1">
|
||||
<v-col cols="4">
|
||||
<v-text-field v-model.number="l.cantidad" type="number" density="compact" hide-details @update:model-value="actualizarLinea(i)"></v-text-field>
|
||||
<div class="d-flex align-center justify-center ga-1"><v-btn icon="mdi-minus" size="small" variant="tonal" aria-label="Disminuir cantidad" @click="cambiarCantidad(i, -1)" /><span class="quantity-value">{{ l.cantidad }}</span><v-btn icon="mdi-plus" size="small" variant="tonal" aria-label="Aumentar cantidad" @click="cambiarCantidad(i, 1)" /></div>
|
||||
</v-col>
|
||||
<v-col cols="4">${{ l.precioUnitario }}</v-col>
|
||||
<v-col cols="4">${{ l.subtotal }}</v-col>
|
||||
</v-row>
|
||||
</v-list-item-subtitle>
|
||||
<template v-slot:append>
|
||||
<v-btn icon="mdi-delete" size="small" @click="eliminarLinea(i)"></v-btn>
|
||||
<v-btn icon="mdi-delete-outline" color="error" variant="tonal" size="small" aria-label="Eliminar producto" @click="eliminarLinea(i)"></v-btn>
|
||||
</template>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
@@ -68,6 +69,12 @@ const productos = ref<any[]>([]);
|
||||
const lineas = ref<any[]>([]);
|
||||
const formaPago = ref('EFECTIVO');
|
||||
const jornadaAbierta = ref<any>(null);
|
||||
const busqueda = ref('');
|
||||
const productosFiltrados = computed(() => {
|
||||
const termino = busqueda.value.trim().toLocaleLowerCase();
|
||||
if (!termino) return productos.value;
|
||||
return productos.value.filter((producto) => `${producto.nombrePresentacion} ${producto.productorNombre ?? ''}`.toLocaleLowerCase().includes(termino));
|
||||
});
|
||||
const error = ref('');
|
||||
const exito = ref('');
|
||||
const ventas = ref<any[]>([]);
|
||||
@@ -80,7 +87,8 @@ const motivoVenta = ref('');
|
||||
const edicionService = new EdicionService();
|
||||
|
||||
async function cargar() {
|
||||
productos.value = await db.productos.toArray();
|
||||
const productores = await db.productores.toArray();
|
||||
productos.value = (await db.productos.toArray()).map((producto) => ({ ...producto, productorNombre: productores.find((productor) => productor.id === producto.productorId)?.nombre ?? '' }));
|
||||
jornadaAbierta.value = await jornadaService.getJornadaAbierta();
|
||||
const ventasDelDia = await ventasService.getVentasDelDia(new Date());
|
||||
ventas.value = await Promise.all(ventasDelDia.map(async (venta) => ({ ...venta, lineas: await ventasService.getLineasPorVenta(venta.id) })));
|
||||
@@ -112,6 +120,7 @@ function actualizarLinea(i: number) {
|
||||
if (!Number.isFinite(lineas.value[i].cantidad) || lineas.value[i].cantidad < 1) lineas.value[i].cantidad = 1;
|
||||
lineas.value[i].subtotal = lineas.value[i].cantidad * lineas.value[i].precioUnitario;
|
||||
}
|
||||
function cambiarCantidad(i: number, cambio: number) { lineas.value[i].cantidad = Math.max(1, lineas.value[i].cantidad + cambio); actualizarLinea(i); }
|
||||
|
||||
function eliminarLinea(i: number) {
|
||||
lineas.value.splice(i, 1);
|
||||
|
||||
Reference in New Issue
Block a user