From 5a16f694def7a713edb5efc994a49040ca68eca7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Consultor=C3=ADa=20Alcaraz=20Salazar?= Date: Tue, 29 Sep 2026 23:33:12 +0000 Subject: [PATCH] Inventario: campo de precio de venta editable (faltaba en la UI) El modelo tenia el campo price y el POS lo usa para cobrar, pero ni el formulario de alta lo capturaba ni existia modal de edicion: los 170 articulos tenian precio en 0 y recepcion tecleaba el precio a mano en cada venta. - Backend: inventory_create acepta price; inventory_update lo permite entre los campos editables (con conversion float). - Frontend: columna 'Precio venta' en la tabla, campo en el alta y modal nuevo 'Editar articulo' (nombre, precio, costo, SKU, categoria, unidad, niveles y notas). La existencia sigue cambiando solo via Ajustar para dejar registro del movimiento. --- frontend/src/pages/Inventario.tsx | 70 +++++++++++++++++-- .../skeen_whatsapp/controllers/frontend.py | 5 +- 2 files changed, 69 insertions(+), 6 deletions(-) diff --git a/frontend/src/pages/Inventario.tsx b/frontend/src/pages/Inventario.tsx index dd2fbda..a11235d 100644 --- a/frontend/src/pages/Inventario.tsx +++ b/frontend/src/pages/Inventario.tsx @@ -1,13 +1,13 @@ import type { FC } from 'react'; import { useCallback, useEffect, useState } from 'react'; import { useSearchParams } from 'react-router-dom'; -import { Boxes, Plus, AlertTriangle, PackageCheck, ArrowDownToLine, ArrowUpFromLine, History, Wallet, FlaskConical, ClipboardCheck, Printer } from 'lucide-react'; +import { Boxes, Plus, AlertTriangle, PackageCheck, ArrowDownToLine, ArrowUpFromLine, History, Wallet, FlaskConical, ClipboardCheck, Printer, Pencil } from 'lucide-react'; import Layout from '../components/Layout'; import MovimientosPanel from '../components/inventario/MovimientosPanel'; import AlertasPanel from '../components/inventario/AlertasPanel'; import ConteoFisicoModal from '../components/inventario/ConteoFisicoModal'; import ConteoPrint from '../components/inventario/ConteoPrint'; -import { Card, Button, Input, Select, Modal, PageHeader, EmptyState, Skeleton, toast } from '../components/ui'; +import { Card, Button, Input, Select, TextArea, Modal, PageHeader, EmptyState, Skeleton, toast } from '../components/ui'; import { odooApi } from '../services/odoo'; import type { InventoryItem, InventoryMove, InventoryKind, InventoryLevel, InventoryMoveType, InventorySummary, InventoryConteo } from '../services/odoo'; @@ -60,7 +60,7 @@ const MOVE_OPTIONS = [ const emptyItem: Partial = { name: '', kind: 'producto', sku: '', category: '', unit: 'pieza', - qty: 0, qty_optimal: 0, qty_min: 0, cost: 0, expiry_date: null, notes: '', + qty: 0, qty_optimal: 0, qty_min: 0, cost: 0, price: 0, expiry_date: null, notes: '', }; const Inventario: FC = () => { @@ -87,6 +87,11 @@ const Inventario: FC = () => { const [historyItem, setHistoryItem] = useState(null); const [moves, setMoves] = useState([]); + // Edición del artículo (nombre, precios, niveles, etc.) + const [editItem, setEditItem] = useState(null); + const [editForm, setEditForm] = useState>({}); + const [savingEdit, setSavingEdit] = useState(false); + // Conteo físico cíclico const [conteoOpen, setConteoOpen] = useState(false); const [conteoPrint, setConteoPrint] = useState(null); @@ -170,6 +175,28 @@ const Inventario: FC = () => { setMoveRef(''); }; + const openEdit = (it: InventoryItem) => { + setEditItem(it); + setEditForm({ name: it.name, sku: it.sku, category: it.category, unit: it.unit, + cost: it.cost, price: it.price, qty_min: it.qty_min, qty_optimal: it.qty_optimal, notes: it.notes }); + }; + + const saveEdit = async () => { + if (!editItem) return; + if (!editForm.name || !(editForm.name || '').trim()) { toast.error('El nombre es obligatorio'); return; } + setSavingEdit(true); + try { + await odooApi.updateInventoryItem(editItem.id, editForm); + toast.success('Artículo actualizado'); + setEditItem(null); + load(); + } catch { + toast.error('Error al actualizar el artículo'); + } finally { + setSavingEdit(false); + } + }; + const submitMove = async () => { if (!adjustItem) return; const q = parseFloat(moveQty); @@ -383,6 +410,7 @@ const Inventario: FC = () => { Existencia Nivel Costo + Precio venta Valor Caducidad @@ -403,10 +431,19 @@ const Inventario: FC = () => { {it.qty} {it.unit} {LEVEL_META[it.stock_level].label} {fmtMoney(it.cost)} - {fmtMoney(it.inventory_value)} + {fmtMoney(it.price ?? 0)} + {fmtMoney(it.inventory_value)} {it.expiry_date || '—'}
+
+ {/* Modal editar artículo */} + setEditItem(null)} title={editItem ? `Editar: ${editItem.name}` : 'Editar'} maxWidth="md" + footer={<> + + + }> + {editItem && ( +
+
setEditForm({ ...editForm, name: e.target.value })} />
+ setEditForm({ ...editForm, price: parseFloat(e.target.value) || 0 })} /> + setEditForm({ ...editForm, cost: parseFloat(e.target.value) || 0 })} /> + setEditForm({ ...editForm, sku: e.target.value })} /> + setEditForm({ ...editForm, category: e.target.value })} /> + setEditForm({ ...editForm, unit: e.target.value })} /> + setEditForm({ ...editForm, qty_min: parseFloat(e.target.value) || 0 })} /> + setEditForm({ ...editForm, qty_optimal: parseFloat(e.target.value) || 0 })} /> +
+