feat: cashier/counter reports, service-order & remission flows, Rached migration utils
Some checks failed
CI / lint-and-test (3.11) (push) Has been cancelled
CI / lint-and-test (3.13) (push) Has been cancelled

- Add "Mis cortes de caja" report for cashiers/counters with sales detail.
- Cash register history scoped to own cuts for non-admin roles; new /register/<id>/sales endpoint.
- Remove dashboard from cashier menu; add Reports to cashier/counter.
- Service orders: assign mechanic, budget field, invoice flag, counter/cashier can add items/remissions, convert to remission.
- Remission notes module (UI, CSS, courier, counter remissions).
- Customer hard-delete and vehicle/customer linkage in workshop.
- POS: always show search results, compact payment grid, credit validation, tier pricing (5%/10%), ticket with customer/folio.
- Inventory: CSV template with sku_secondary, alias import.
- Rached migration scripts and DB migrations.
- Version-bump cached JS/CSS query strings.

Excludes local Rached session tokens/captures (rached_*.json / rached_*.txt).
This commit is contained in:
2026-07-02 12:51:56 +00:00
parent 483498cfcc
commit f42910f4f6
71 changed files with 5388 additions and 626 deletions

View File

@@ -230,6 +230,54 @@ def record_sale(conn, inventory_id, branch_id, quantity, sale_id=None, cost_at_t
return op_id
def record_reservation(conn, inventory_id, branch_id, quantity, sale_id=None,
cost_at_time=None, remaining_stock=None):
"""Reserve stock for a counter remission note (negative quantity).
The reserved quantity is deducted from available stock until the note is
paid or cancelled.
"""
op_id = record_operation(
conn, inventory_id, branch_id, 'REMISSION_RESERVE', -abs(quantity),
reference_id=sale_id, reference_type='sale', cost_at_time=cost_at_time,
notes='Reserva por nota de remision'
)
invalidate_stock(inventory_id, branch_id)
invalidate_stock(inventory_id, None)
try:
remaining = remaining_stock if remaining_stock is not None else get_stock(conn, inventory_id, branch_id)
if remaining <= 0:
cur = conn.cursor()
cur.execute("SELECT part_number, name FROM inventory WHERE id = %s", (inventory_id,))
inv_row = cur.fetchone()
cur.close()
if inv_row:
from services.push_service import notify_owner
notify_owner(
conn,
'Stock en Cero',
f'{inv_row[1] or inv_row[0]} se quedo sin existencias',
'/pos'
)
except Exception:
pass
return op_id
def release_reservation(conn, inventory_id, branch_id, quantity, sale_id=None, notes=None):
"""Release a previous reservation (positive quantity)."""
result = record_operation(
conn, inventory_id, branch_id, 'REMISSION_RELEASE', abs(quantity),
reference_id=sale_id, reference_type='sale',
notes=notes or 'Liberacion de reserva de nota de remision'
)
invalidate_stock(inventory_id, branch_id)
invalidate_stock(inventory_id, None)
return result
def record_return(conn, inventory_id, branch_id, quantity, sale_id=None, notes=None):
"""Record a customer return (positive quantity)."""
result = record_operation(