- 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
124 lines
4.1 KiB
Python
124 lines
4.1 KiB
Python
"""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
|
|
]
|