fix(config): re-sync employee permissions on role change; add sync script
This commit is contained in:
@@ -486,6 +486,15 @@ def update_employee(emp_id):
|
|||||||
params.append(emp_id)
|
params.append(emp_id)
|
||||||
cur.execute(f"UPDATE employees SET {', '.join(updates)} WHERE id = %s", params)
|
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
|
from services.audit import log_action
|
||||||
log_action(conn, 'EMPLOYEE_UPDATE', 'employee', emp_id,
|
log_action(conn, 'EMPLOYEE_UPDATE', 'employee', emp_id,
|
||||||
new_value={k: v for k, v in data.items() if k != 'pin'})
|
new_value={k: v for k, v in data.items() if k != 'pin'})
|
||||||
|
|||||||
111
scripts/sync_employee_permissions.py
Normal file
111
scripts/sync_employee_permissions.py
Normal 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()
|
||||||
Reference in New Issue
Block a user