diff --git a/.gitignore b/.gitignore index 8ef7218..a55a2de 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,4 @@ notes.md *.njsproj *.sln *.sw? +CLAUDE.md \ No newline at end of file diff --git a/src/components/POS/CartItem.vue b/src/components/POS/CartItem.vue index ca8cce0..115ad0a 100644 --- a/src/components/POS/CartItem.vue +++ b/src/components/POS/CartItem.vue @@ -11,7 +11,7 @@ const props = defineProps({ }); /** Emits */ -const emit = defineEmits(['update-quantity', 'remove']); +const emit = defineEmits(['update-quantity', 'remove', 'select-serials']); /** Computados */ const formattedUnitPrice = computed(() => { @@ -29,7 +29,20 @@ const formattedSubtotal = computed(() => { }).format(subtotal); }); -const canIncrement = computed(() => props.item.quantity < props.item.max_stock); +const canIncrement = computed(() => { + // Si tiene seriales, no permitir incremento directo + if (props.item.track_serials) return false; + return props.item.quantity < props.item.max_stock; +}); + +const hasSerials = computed(() => props.item.track_serials); +const serialsSelected = computed(() => { + return props.item.serial_numbers && props.item.serial_numbers.length > 0; +}); + +const needsSerialSelection = computed(() => { + return hasSerials.value && (!serialsSelected.value || props.item.serial_numbers.length !== props.item.quantity); +}); /** Métodos */ const increment = () => { diff --git a/src/lang/es.js b/src/lang/es.js index 797a491..6724b63 100644 --- a/src/lang/es.js +++ b/src/lang/es.js @@ -458,6 +458,7 @@ export default { cashRegister: 'Caja', point: 'Punto de Venta', sales: 'Ventas', + returns: 'Devoluciones', clients: 'Clientes', billingRequests: 'Solicitudes de Facturación' }, diff --git a/src/layouts/AppLayout.vue b/src/layouts/AppLayout.vue index eee004e..7073b8b 100644 --- a/src/layouts/AppLayout.vue +++ b/src/layouts/AppLayout.vue @@ -57,6 +57,11 @@ onMounted(() => { name="pos.sales" to="pos.sales.index" /> + parseFloat(props.cashRegister?.cash_sales || 0) const cardSales = computed(() => parseFloat(props.cashRegister?.card_sales || 0)); const transactionCount = computed(() => parseInt(props.cashRegister?.transaction_count || 0)); -const expectedCash = computed(() => initialCash.value + cashSales.value); +// Devoluciones +const totalReturns = computed(() => parseFloat(props.cashRegister?.total_returns || 0)); +const cashReturns = computed(() => parseFloat(props.cashRegister?.cash_returns || 0)); +const cardReturns = computed(() => parseFloat(props.cashRegister?.card_returns || 0)); +const hasReturns = computed(() => totalReturns.value > 0); + +// Ventas netas (ventas - devoluciones) +const netSales = computed(() => totalSales.value - totalReturns.value); +const netCashSales = computed(() => cashSales.value - cashReturns.value); + +const expectedCash = computed(() => initialCash.value + netCashSales.value); const difference = computed(() => finalCash.value - expectedCash.value); const hasDifference = computed(() => Math.abs(difference.value) > 0.01); @@ -128,6 +138,44 @@ const handleClose = () => { + +
Total Devoluciones
++ -${{ (totalReturns || 0).toLocaleString('es-MX', { minimumFractionDigits: 2 }) }} +
+Efectivo
++ -${{ (cashReturns || 0).toLocaleString('es-MX', { minimumFractionDigits: 2 }) }} +
+Tarjeta
++ -${{ (cardReturns || 0).toLocaleString('es-MX', { minimumFractionDigits: 2 }) }} +
++ {{ step === 1 ? 'Buscar venta' : 'Seleccionar productos a devolver' }} +
+Venta seleccionada:
++ {{ saleData?.invoice_number || `#${saleId}` }} +
+Total de venta:
++ {{ formatCurrency(saleData?.total) }} +
+| + Seleccionar + | ++ Producto + | ++ Cantidad + | ++ Precio + | ++ Subtotal + | +
|---|---|---|---|---|
| + + | +
+ + {{ item.product_name }} + + +
+ Vendido: {{ item.quantity_sold }}
+
+ • Ya devuelto: {{ item.quantity_already_returned }}
+
+
+ • Disponible: {{ item.quantity_returnable }}
+
+
+ |
+ + + + {{ item.quantity }} / {{ item.quantity_returnable }} + + | ++ + {{ formatCurrency(item.unit_price) }} + + | ++ + {{ formatCurrency(parseFloat(item.unit_price) * item.quantity) }} + + | +
|
+ + Selecciona los seriales a devolver: + +
+
+
+ |
+ ||||
Total a Devolver
++ {{ formatCurrency(totalReturn) }} +
+| FOLIO | +VENTA ORIGINAL | +FECHA | +USUARIO | +REEMBOLSO | +TOTAL | +ACCIONES | + + +
|---|---|---|---|---|---|---|
|
+
+
+ {{ model.return_number }}
+
+
+ Cancelada
+
+
+ |
+ + + {{ model.sale?.invoice_number || '-' }} + + | ++ + {{ formatDate(model.created_at) }} + + | ++ + {{ model.user?.name || '-' }} + + | +
+
+ |
+ + + {{ formatCurrency(model.total) }} + + | ++ + | +
+
+
+ + No hay devoluciones registradas + ++ Las devoluciones aparecerán aquí + + |
+
+
+ {{ formattedDate }} +
+Venta Original
++ {{ returnData?.sale?.invoice_number || `#${returnData?.sale_id}` }} +
++ {{ formatDate(returnData.deleted_at) }} +
+Procesado por
++ {{ returnData?.user?.name || '-' }} +
+Método de Reembolso
+ +Motivo de Devolución
++ {{ getReasonLabel(returnData?.reason) }} +
++ {{ returnData.reason_text }} +
+Notas
++ {{ returnData.notes }} +
++ Productos Devueltos ({{ returnItems.length }}) +
+ +| + Producto + | ++ Cantidad + | ++ Precio Unit. + | ++ Subtotal + | +
|---|---|---|---|
|
+
+
+ + {{ item.product_name || item.inventory?.name || 'Producto' }} + ++ SKU: {{ item.inventory.sku }} + + +
+
+ Seriales: +
+
+ {{ serial.serial_number }}
+
+
+ |
+ + + {{ item.quantity_returned || item.quantity }} + + | ++ + {{ formatCurrency(item.unit_price) }} + + | ++ + {{ formatCurrency(item.subtotal) }} + + | +
No hay items en esta devolución
++ Busca y selecciona la venta original para la devolución +
+Venta seleccionada:
++ #{{ selectedSale.invoice_number || selectedSale.id }} +
++ {{ formatCurrency(selectedSale.total) }} +
+| + Seleccionar + | ++ Folio + | ++ Fecha + | ++ Cajero + | ++ Método de Pago + | ++ Total + | + + +
|---|---|---|---|---|---|
| + + | ++ + #{{ sale.invoice_number || sale.id }} + + | ++ + {{ formatDate(sale.created_at) }} + + | ++ + {{ sale.user?.name || sale.cashier?.name || '-' }} + + | +
+
+ |
+ + + {{ formatCurrency(sale.total) }} + + | +
+
+
+ No hay ventas disponibles +Intenta buscar con otros términos + |
+
+