fix(config): re-sync employee permissions on role change; add sync script
Some checks failed
CI / lint-and-test (3.11) (push) Has been cancelled
CI / lint-and-test (3.13) (push) Has been cancelled

This commit is contained in:
2026-07-02 19:37:19 +00:00
parent 53ec0cc3e5
commit 302d54eb0a
2 changed files with 120 additions and 0 deletions

View File

@@ -486,6 +486,15 @@ def update_employee(emp_id):
params.append(emp_id)
cur.execute(f"UPDATE employees SET {', '.join(updates)} WHERE id = %s", params)
# If the role changed, re-sync permissions to match the new role defaults/config.
if 'role' in data:
cur.execute("DELETE FROM employee_permissions WHERE employee_id = %s", (emp_id,))
for perm in _get_role_permissions(conn, data['role']):
cur.execute(
"INSERT INTO employee_permissions (employee_id, permission) VALUES (%s, %s) ON CONFLICT DO NOTHING",
(emp_id, perm)
)
from services.audit import log_action
log_action(conn, 'EMPLOYEE_UPDATE', 'employee', emp_id,
new_value={k: v for k, v in data.items() if k != 'pin'})

View File

@@ -0,0 +1,111 @@
#!/usr/bin/env python3
"""Re-sync employee_permissions with role defaults / configured role permissions.
Useful when an employee's permissions get out of sync with the role they belong to
(e.g. after a role change or a buggy save).
Example:
python3 scripts/sync_employee_permissions.py --db tenant_refaccionaria_rached
"""
import argparse
import json
import os
import sys
import psycopg2
# Keep in sync with pos/blueprints/config_bp.py
_DEFAULT_ROLE_PERMISSIONS = {
'owner': [],
'admin': [
'config.view', 'config.edit', 'config.edit_prices', 'config.delete',
'pos.view', 'pos.sell', 'pos.cancel', 'pos.remission',
'inventory.view', 'inventory.create', 'inventory.edit', 'inventory.adjust', 'inventory.transfer',
'customers.view', 'customers.create', 'customers.edit', 'customers.delete',
'catalog.view', 'catalog.edit',
'workshop.view', 'workshop.edit', 'workshop.delete',
'invoicing.view', 'invoicing.create',
'reports.view',
],
'manager': [
'pos.view', 'pos.sell', 'pos.cancel', 'pos.remission',
'inventory.view', 'inventory.create', 'inventory.edit', 'inventory.adjust', 'inventory.transfer',
'customers.view', 'customers.create', 'customers.edit',
'catalog.view', 'catalog.edit',
'workshop.view', 'workshop.edit',
'invoicing.view', 'invoicing.create',
'reports.view',
],
'warehouse': [
'inventory.view', 'inventory.create', 'inventory.edit', 'inventory.adjust', 'inventory.transfer',
'pos.view',
],
'counter': [
'pos.sell', 'pos.view', 'pos.remission', 'inventory.view', 'customers.view', 'catalog.view',
],
'cashier': [
'pos.sell', 'pos.view', 'pos.remission', 'inventory.view', 'customers.view', 'catalog.view', 'invoicing.view',
],
'workshop': [
'workshop.view', 'customers.view', 'customers.create', 'inventory.view',
],
'mechanic': [
'workshop.view', 'customers.view', 'customers.create', 'inventory.view',
],
}
def _get_role_permissions(cur, role):
cur.execute("SELECT value FROM tenant_config WHERE key = 'role_permissions'")
row = cur.fetchone()
if row and row[0]:
try:
configured = json.loads(row[0])
if isinstance(configured, dict) and role in configured:
return list(configured.get(role, []))
except (ValueError, TypeError):
pass
return list(_DEFAULT_ROLE_PERMISSIONS.get(role, []))
def sync(db_url):
conn = psycopg2.connect(db_url)
cur = conn.cursor()
cur.execute("SELECT id, name, role FROM employees ORDER BY id")
employees = cur.fetchall()
total = 0
for emp_id, name, role in employees:
perms = _get_role_permissions(cur, role)
cur.execute("DELETE FROM employee_permissions WHERE employee_id = %s", (emp_id,))
for perm in perms:
cur.execute(
"INSERT INTO employee_permissions (employee_id, permission) VALUES (%s, %s) ON CONFLICT DO NOTHING",
(emp_id, perm)
)
total += 1
print(f" [{emp_id:>3}] {name:<30} role={role:<10} perms={len(perms)}")
conn.commit()
cur.close()
conn.close()
print(f"\nSynced {total} employees.")
def main():
parser = argparse.ArgumentParser(description="Sync employee_permissions with role defaults/config")
parser.add_argument('--db', required=True, help='PostgreSQL database name or full connection URL')
args = parser.parse_args()
db = args.db
if db.startswith('postgresql://'):
db_url = db
else:
db_url = f"postgresql://postgres@/{db}"
sync(db_url)
if __name__ == '__main__':
main()