feat(whatsapp): QWEN primary AI backend, Hermes fallback, conversation history, vehicle persistence, demo prompts
- Add QWEN (qwen3.6) as primary AI backend with short system prompt - Hermes remains as fallback with 45s timeout - Increase QWEN timeout to 35s, max_tokens to 4000 - Add conversation history loading from whatsapp_messages (last 4 msgs) - Persist detected vehicle in whatsapp_sessions table - Add 'limpiar chat' / 'nuevo chat' / 'reset' commands to clear history - Fix CSS conflict: rename whatsapp chat-panel classes to wa-chat-panel - Fix JS ID conflicts with chat.js widget (waChatPanel, waChatMessages, etc.) - Improve no-stock response: conversational with alternatives - Split search_query by | for multi-part lookups - Add DEMO_PROMPTS.md and DEMO_PROMPTS_V2.md
This commit is contained in:
123
pos/services/quote_reservation.py
Normal file
123
pos/services/quote_reservation.py
Normal file
@@ -0,0 +1,123 @@
|
||||
"""Quotation stock reservation engine.
|
||||
|
||||
Uses inventory_operations with operation types:
|
||||
QUOTE_RESERVE — negative quantity, reserves stock when quote is created
|
||||
QUOTE_RELEASE — positive quantity, restores stock when quote is cancelled/expired
|
||||
QUOTE_CONVERT — neutral (just a marker), actual sale uses SALE operation
|
||||
|
||||
The trigger update_stock_summary() recalculates inventory_stock_summary
|
||||
by summing ALL operations, so reservations automatically affect visible stock.
|
||||
"""
|
||||
from services.inventory_engine import record_operation
|
||||
|
||||
|
||||
def reserve_for_quotation(conn, quotation_id, items, employee_id=None):
|
||||
"""Reserve stock for each item in a new quotation.
|
||||
|
||||
Args:
|
||||
conn: tenant DB connection (not committed by this function).
|
||||
quotation_id: the quotations.id.
|
||||
items: list of dicts with inventory_id, quantity, branch_id (optional).
|
||||
employee_id: optional, passed explicitly when g.employee_id is unavailable.
|
||||
Returns:
|
||||
list of operation IDs.
|
||||
"""
|
||||
op_ids = []
|
||||
for item in items:
|
||||
inv_id = item.get('inventory_id')
|
||||
qty = item.get('quantity', 0)
|
||||
branch_id = item.get('branch_id')
|
||||
if not inv_id or qty <= 0:
|
||||
continue
|
||||
op_id = record_operation(
|
||||
conn, inv_id, branch_id, 'QUOTE_RESERVE',
|
||||
quantity=-qty,
|
||||
reference_id=quotation_id,
|
||||
reference_type='quotation',
|
||||
notes=f'Reserva cotizacion #{quotation_id}'
|
||||
)
|
||||
op_ids.append(op_id)
|
||||
return op_ids
|
||||
|
||||
|
||||
def release_quotation_reservation(conn, quotation_id, items, employee_id=None):
|
||||
"""Release previously reserved stock (cancel, expire, or convert).
|
||||
|
||||
Args:
|
||||
conn: tenant DB connection.
|
||||
quotation_id: the quotations.id.
|
||||
items: list of dicts with inventory_id, quantity, branch_id.
|
||||
employee_id: optional.
|
||||
Returns:
|
||||
list of operation IDs.
|
||||
"""
|
||||
op_ids = []
|
||||
for item in items:
|
||||
inv_id = item.get('inventory_id')
|
||||
qty = item.get('quantity', 0)
|
||||
branch_id = item.get('branch_id')
|
||||
if not inv_id or qty <= 0:
|
||||
continue
|
||||
op_id = record_operation(
|
||||
conn, inv_id, branch_id, 'QUOTE_RELEASE',
|
||||
quantity=qty,
|
||||
reference_id=quotation_id,
|
||||
reference_type='quotation',
|
||||
notes=f'Liberacion cotizacion #{quotation_id}'
|
||||
)
|
||||
op_ids.append(op_id)
|
||||
return op_ids
|
||||
|
||||
|
||||
def convert_quotation_reservation(conn, quotation_id, items, sale_id=None, employee_id=None):
|
||||
"""Convert reservation to actual sale.
|
||||
|
||||
Flow:
|
||||
1. Release the reservation (QUOTE_RELEASE +qty)
|
||||
2. Record the actual sale (SALE -qty)
|
||||
|
||||
Args:
|
||||
conn: tenant DB connection.
|
||||
quotation_id: the quotations.id.
|
||||
items: list of dicts with inventory_id, quantity, branch_id.
|
||||
sale_id: the resulting sales.id (for reference).
|
||||
employee_id: optional.
|
||||
Returns:
|
||||
list of operation IDs.
|
||||
"""
|
||||
op_ids = release_quotation_reservation(conn, quotation_id, items, employee_id)
|
||||
for item in items:
|
||||
inv_id = item.get('inventory_id')
|
||||
qty = item.get('quantity', 0)
|
||||
branch_id = item.get('branch_id')
|
||||
if not inv_id or qty <= 0:
|
||||
continue
|
||||
op_id = record_operation(
|
||||
conn, inv_id, branch_id, 'SALE',
|
||||
quantity=-qty,
|
||||
reference_id=sale_id or quotation_id,
|
||||
reference_type='sale' if sale_id else 'quotation',
|
||||
notes=f'Venta convertida de cotizacion #{quotation_id}'
|
||||
)
|
||||
op_ids.append(op_id)
|
||||
return op_ids
|
||||
|
||||
|
||||
def get_quotation_items_for_reservation(conn, quotation_id):
|
||||
"""Fetch items from a quotation joined with inventory to get branch_id.
|
||||
|
||||
Returns list of dicts: {inventory_id, quantity, branch_id}
|
||||
"""
|
||||
cur = conn.cursor()
|
||||
cur.execute("""
|
||||
SELECT qi.inventory_id, qi.quantity, i.branch_id
|
||||
FROM quotation_items qi
|
||||
JOIN inventory i ON i.id = qi.inventory_id
|
||||
WHERE qi.quotation_id = %s
|
||||
""", (quotation_id,))
|
||||
rows = cur.fetchall()
|
||||
cur.close()
|
||||
return [
|
||||
{'inventory_id': r[0], 'quantity': r[1], 'branch_id': r[2]}
|
||||
for r in rows
|
||||
]
|
||||
Reference in New Issue
Block a user