feat: cashier/counter reports, service-order & remission flows, Rached migration utils
- 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:
@@ -8,6 +8,7 @@ from datetime import datetime, timezone, timedelta
|
||||
from flask import Blueprint, request, jsonify, g, make_response
|
||||
from config import JWT_SECRET, JWT_ACCESS_EXPIRES, PIN_MAX_ATTEMPTS_PER_MINUTE, PIN_LOCKOUT_THRESHOLD, PIN_LOCKOUT_MINUTES
|
||||
from tenant_db import get_tenant_conn, get_master_conn
|
||||
from middleware import require_auth
|
||||
|
||||
auth_bp = Blueprint('auth', __name__, url_prefix='/pos/api/auth')
|
||||
|
||||
@@ -158,6 +159,68 @@ def login_pin():
|
||||
return response
|
||||
|
||||
|
||||
@auth_bp.route('/refresh', methods=['POST'])
|
||||
@require_auth()
|
||||
def refresh_token():
|
||||
"""Reissue the JWT with the employee's current permissions from the DB.
|
||||
|
||||
This lets permission changes take effect without forcing a full re-login.
|
||||
The original expiration time is preserved.
|
||||
"""
|
||||
auth_header = request.headers.get('Authorization', '')
|
||||
try:
|
||||
payload = jwt.decode(auth_header[7:], JWT_SECRET, algorithms=['HS256'])
|
||||
except jwt.InvalidTokenError:
|
||||
return jsonify({'error': 'Invalid token'}), 401
|
||||
|
||||
tenant_id = payload.get('tenant_id')
|
||||
employee_id = payload.get('employee_id')
|
||||
conn = get_tenant_conn(tenant_id)
|
||||
cur = conn.cursor()
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT e.id, e.name, e.role, e.branch_id, e.max_discount_pct
|
||||
FROM employees e
|
||||
WHERE e.id = %s AND e.is_active = true
|
||||
""",
|
||||
(employee_id,)
|
||||
)
|
||||
emp = cur.fetchone()
|
||||
if not emp:
|
||||
cur.close(); conn.close()
|
||||
return jsonify({'error': 'Employee not found or inactive'}), 404
|
||||
|
||||
cur.execute(
|
||||
"SELECT permission FROM employee_permissions WHERE employee_id = %s",
|
||||
(emp[0],)
|
||||
)
|
||||
permissions = [r[0] for r in cur.fetchall()]
|
||||
cur.close(); conn.close()
|
||||
|
||||
new_payload = {
|
||||
'tenant_id': tenant_id,
|
||||
'employee_id': emp[0],
|
||||
'name': emp[1],
|
||||
'role': emp[2],
|
||||
'branch_id': emp[3],
|
||||
'max_discount_pct': float(emp[4]) if emp[4] else 0,
|
||||
'permissions': permissions,
|
||||
'device_id': payload.get('device_id', 'unknown'),
|
||||
'type': 'pos_access',
|
||||
'exp': payload.get('exp'),
|
||||
'iat': datetime.now(timezone.utc),
|
||||
}
|
||||
token = jwt.encode(new_payload, JWT_SECRET, algorithm='HS256')
|
||||
return jsonify({
|
||||
'token': token,
|
||||
'employee': {
|
||||
'id': emp[0], 'name': emp[1], 'role': emp[2],
|
||||
'branch_id': emp[3], 'max_discount_pct': new_payload['max_discount_pct']
|
||||
},
|
||||
'permissions': permissions
|
||||
})
|
||||
|
||||
|
||||
@auth_bp.route('/employees/<int:tenant_id>', methods=['GET'])
|
||||
@auth_bp.route('/employees', methods=['GET'])
|
||||
def list_login_employees(tenant_id=None):
|
||||
@@ -191,7 +254,7 @@ def list_login_employees(tenant_id=None):
|
||||
name = row[1]
|
||||
parts = name.split()
|
||||
initials = ''.join([p[0].upper() for p in parts[:2]]) if parts else '?'
|
||||
role_labels = {'owner': 'Dueño', 'admin': 'Administrador', 'cashier': 'Cajero', 'warehouse': 'Almacén', 'accountant': 'Contador', 'workshop': 'Taller', 'mechanic': 'Mecánico'}
|
||||
role_labels = {'owner': 'Dueño', 'admin': 'Administrador', 'cashier': 'Cajero', 'counter': 'Mostrador', 'warehouse': 'Almacén', 'accountant': 'Contador', 'workshop': 'Taller', 'mechanic': 'Mecánico'}
|
||||
employees.append({
|
||||
'id': row[0],
|
||||
'name': name,
|
||||
|
||||
Reference in New Issue
Block a user