Ë
    B-j.k  ã                  óX  — U d dl mZ d dlZd dlZd dlZd dlZd dlZd dlmZ d dl	m
Z
mZ d dlmZ d dlmZmZ  ej"                  d«      Zdad	ed
<   dZg d¢Zded<   eD � ch c]  } | d    ’Œ	 c} ZdZdZed„ «       Zd5d„Ze
 G d„ d«      «       Ze
 G d„ d«      «       Zd6d7d„Zd8d„Zd9d„Z d:d;d„Z!d<d„Z"d=d„Z#d>d„Z$d:d?d„Z%d@d„Z&dAd „Z'dBd!„Z(dCd"„Z)dDd#„Z*dEd$„Z+dFd%„Z,dGd&„Z-dHd'„Z.dId(„Z/dJd)„Z0d6dKd*„Z1dLdMd+„Z2dNd,„Z3	 	 	 	 	 	 	 	 	 	 	 	 dOd-„Z4dPd.„Z5dQd/„Z6dRd0„Z7dEd1„Z8dSd2„Z9dTd3„Z:dEd4„Z;yc c} w )Ué    )ÚannotationsN)Úcontextmanager)Ú	dataclassÚfield)ÚOptional)Úcheck_password_hashÚgenerate_password_hashz	apex.rbacÚ ÚstrÚ_DB_PATHzadmin@rptechindia.com))z
sales.viewÚsalesÚview)zprojects.viewÚprojectsr   )zprojects.editr   Úedit)zprojects.edit_anyr   Úedit_any)zreports.sap_hanaÚreportsÚsap_hana)zreports.sap_stockr   Ú	sap_stock)zreports.sap_por   Úsap_po)zreports.sap_wosr   Úsap_wos)zreports.sap_agingr   Ú	sap_aging)zreports.sap_predictiver   Úsap_predictive)zreports.ml_forecastr   Úml_forecast)zreports.ml_demandr   Ú	ml_demand)zreports.ml_anomaliesr   Úml_anomalies)zreports.ml_segmentsr   Úml_segments)zreports.ml_insightsr   Úml_insights)zinventory.viewÚ	inventoryr   )zservice.viewÚservicer   zlist[tuple[str, str, str]]ÚDEFAULT_PERMISSIONSi€Q aj  
CREATE TABLE IF NOT EXISTS users (
    id                         INTEGER PRIMARY KEY AUTOINCREMENT,
    email                      TEXT    UNIQUE NOT NULL,
    name                       TEXT    NOT NULL DEFAULT '',
    department                 TEXT    DEFAULT '',
    role                       TEXT    NOT NULL DEFAULT 'employee',
    status                     TEXT    NOT NULL DEFAULT 'pending',
    password_hash              TEXT    DEFAULT NULL,
    set_password_token         TEXT    DEFAULT NULL,
    set_password_token_expiry  REAL    DEFAULT NULL,
    approved_by                TEXT    DEFAULT NULL,
    approved_at                REAL    DEFAULT NULL,
    created_at                 REAL    NOT NULL
);

CREATE TABLE IF NOT EXISTS permissions (
    id          INTEGER PRIMARY KEY AUTOINCREMENT,
    name        TEXT    UNIQUE NOT NULL,
    module      TEXT    NOT NULL,
    action      TEXT    NOT NULL
);

CREATE TABLE IF NOT EXISTS user_permissions (
    user_id       INTEGER NOT NULL,
    permission_id INTEGER NOT NULL,
    granted_by    TEXT    NOT NULL,
    granted_at    REAL    NOT NULL,
    PRIMARY KEY (user_id, permission_id),
    FOREIGN KEY (user_id)       REFERENCES users(id)       ON DELETE CASCADE,
    FOREIGN KEY (permission_id) REFERENCES permissions(id) ON DELETE CASCADE
);

CREATE TABLE IF NOT EXISTS audit_log (
    id           INTEGER PRIMARY KEY AUTOINCREMENT,
    action       TEXT    NOT NULL,
    actor_email  TEXT    NOT NULL,
    target_email TEXT    DEFAULT NULL,
    detail       TEXT    DEFAULT '',
    created_at   REAL    NOT NULL
);

CREATE TABLE IF NOT EXISTS user_brands (
    user_id      INTEGER NOT NULL,
    brand_code   TEXT    NOT NULL,
    assigned_by  TEXT    NOT NULL DEFAULT '',
    assigned_at  REAL    NOT NULL,
    PRIMARY KEY (user_id, brand_code),
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);

CREATE TABLE IF NOT EXISTS user_requested_brands (
    user_id      INTEGER NOT NULL,
    brand_code   TEXT    NOT NULL,
    requested_at REAL    NOT NULL,
    PRIMARY KEY (user_id, brand_code),
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
c               #  ó<  K  — t        j                  t        «      } t         j                  | _        | j                  d«       	 | –— | j                  «        	 | j                  «        y # t        $ r | j                  «        ‚ w xY w# | j                  «        w xY w­w)NzPRAGMA foreign_keys = ON)
Úsqlite3Úconnectr   ÚRowÚrow_factoryÚexecuteÚcommitÚ	ExceptionÚrollbackÚclose)Úcons    ú2D:\CODES\new-apex\backend\services\rbac_service.pyÚ_connr-   z   sr   è ø€ ä
�/‰/œ(Ó
#€CÜ—k‘k€C„OØ‡K�KÐ*Ô+ðØŠ	Ø�
‰
�ð
 	�	‰	�øô	 ò Ø�‰ŒØðûð 	�	‰	�üs*   ‚A BÁA) ÁBÁ)BÂB ÂBÂBc                óœ  — | a t        j                  t        j                  j	                  | «      d¬«       t        «       5 }|j                  t        «       |j                  d«      j                  «       D �ch c]  }|d   ’Œ	 }}d|vr7|j                  d«       |j                  d«       t        j                  d«       t        D ]  \  }}}|j                  d	|||f«       Œ t        D �cg c]  }|d
   ‘Œ	 }}dj                  dt        |«      z  «      }	|j                  d|	› d�|«       |j                  dt        f«      j!                  «       }
|
sD|j                  dt        t#        j"                  «       f«       t        j                  dt        «       ddd«       t        j                  d| «       yc c}w c c}w # 1 sw Y   Œ*xY w)z>Create tables, seed permissions, and ensure admin user exists.T)Úexist_okzPRAGMA table_info(user_brands)é   Ú
brand_codez DROP TABLE IF EXISTS user_brandsao  CREATE TABLE user_brands (
                user_id      INTEGER NOT NULL,
                brand_code   TEXT    NOT NULL,
                assigned_by  TEXT    NOT NULL DEFAULT '',
                assigned_at  REAL    NOT NULL,
                PRIMARY KEY (user_id, brand_code),
                FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
            )z4[RBAC] Migrated user_brands: added brand_code columnzGINSERT OR IGNORE INTO permissions (name, module, action) VALUES (?,?,?)r   Ú,Ú?z+DELETE FROM permissions WHERE name NOT IN (Ú)ú"SELECT id FROM users WHERE email=?zvINSERT INTO users (email, name, role, status, created_at)
                   VALUES (?, 'Admin', 'admin', 'active', ?)z[RBAC] Admin user created: %sNz[RBAC] Initialized at %s)r   ÚosÚmakedirsÚpathÚdirnamer-   ÚexecutescriptÚ_SCHEMAr&   ÚfetchallÚloggerÚinfor    ÚjoinÚlenÚADMIN_EMAILÚfetchoneÚtime)Údb_pathr+   ÚrÚcolsÚnameÚmoduleÚactionÚpÚ_validÚ_phÚexistss              r,   Ú	init_rbacrN   ‹   s�  € ð €HÜ‡K�K”—‘—‘ Ó(°4Õ8ä	Œ�CØ×Ñœ'Ô"ð "Ÿk™kÐ*JÓK×TÑTÔVÓWÑV˜��!“ÐVˆÐWØ˜tÑ#Ø�K‰KÐ:Ô;Ø�K‰Kð ô ô �K‰KÐNÔO÷ %8Ñ ˆD�&˜&Ø�K‰KØYØ�v˜vÐ&õð %8õ !4Ó4Ñ 3˜1�!�A“$Ð 3ˆÐ4Ø�h‰h�sœS ›[Ñ(Ó)ˆØ�‰ÐAÀ#ÀÀaÐHÈ&ÔQà—‘ÐAÄKÀ>ÓR×[Ñ[Ó]ˆÙØ�K‰Kð@äœdŸi™i›kÐ*ôô
 �K‰KÐ7¼ÔE÷G 
ôJ ‡K�KÐ*¨GÕ4ùòC Xùò( 5÷1 
ˆús,   Á7GÁ8F8ÂA)GÃ-F=Ã9B GÆ8
GÇGc                  óv   — e Zd ZU ded<   ded<   ded<   ded<   ded<   ded<   d	ed
<   ded<   ded<   dZded<   y)Ú
UserRecordÚintÚidr   ÚemailrG   Ú
departmentÚroleÚstatuszOptional[str]Úapproved_byzOptional[float]Úapproved_atÚfloatÚ
created_atFÚboolÚhas_passwordN)Ú__name__Ú
__module__Ú__qualname__Ú__annotations__r\   © ó    r,   rP   rP   »   s<   … àƒGØƒJØ
ƒIØƒOØ
ƒIØƒKØÓØ Ó ØÓØ€L�$Ôrb   rP   c                  ó6   — e Zd ZU ded<   ded<   ded<   ded<   y)ÚPermissionRecordrQ   rR   r   rG   rH   rI   N)r]   r^   r_   r`   ra   rb   r,   rd   rd   É   s   … àƒGØ
ƒIØƒKØ„Krb   rd   c           
     óö  — | j                  «       j                  «       } 	 |rt        |«      nd }t        «       5 }|j	                  d| |j                  «       |j                  «       |t        j
                  «       f«       d d d «       t        d| | d|› �«       t        j                  d| «       y# 1 sw Y   Œ1xY w# t        j                  $ r Y yt        $ r }t        j                  d|«       Y d }~yd }~ww xY w)	Nz”INSERT INTO users (email, name, department, role, status, password_hash, created_at)
                   VALUES (?, ?, ?, 'employee', 'active', ?, ?)zuser.signupzNew signup: z[RBAC] New signup: %s)TzAccount created successfully.)Fú*An account with this email already exists.z[RBAC] create_user failed: %s)Fz&Registration failed. Please try again.)ÚstripÚlowerr	   r-   r&   rC   Ú_auditr=   r>   r"   ÚIntegrityErrorr(   Úerror)rS   rG   rT   ÚpasswordÚpw_hashr+   Úexcs          r,   Úcreate_userro   Ó   sÑ   € Ø�K‰K‹M×ÑÓ!€Eð?Ù6>Ô(¨Ô2ÀDˆÜŒW˜Ø�K‰KðCà˜Ÿ
™
› j×&6Ñ&6Ó&8¸'Ä4Ç9Á9Ã;ÐOô÷ ô 	ˆ}˜e U¨l¸4¸&Ð,AÔBÜ�‰Ð+¨UÔ3Ø4÷ ˆWûô ×!Ñ!ò CÙBÜò ?Ü�‰Ð4°cÔ:Ü>ûð?ús5    B; ¹AB/Á?/B; Â/B8Â4B; Â;C8ÃC8ÃC3Ã3C8c                óÚ   — | j                  «       j                  «       } t        «       5 }|j                  d| f«      j	                  «       }d d d «       sy t        |«      S # 1 sw Y   ŒxY w)Nú!SELECT * FROM users WHERE email=?)rg   rh   r-   r&   rB   Ú_row_to_user)rS   r+   Úrows      r,   Úget_userrt   ç   sW   € Ø�K‰K‹M×ÑÓ!€EÜ	Œ�CØ�k‰kÐ=À¸xÓH×QÑQÓSˆ÷ 
áØÜ˜ÓÐ÷	 
ˆús   ©"A!Á!A*c                ó    — t        «       5 }|j                  d| f«      j                  «       }d d d «       rt        |«      S d S # 1 sw Y   ŒxY w)NzSELECT * FROM users WHERE id=?)r-   r&   rB   rr   )Úuser_idr+   rs   s      r,   Úget_user_by_idrw   ð   sD   € Ü	Œ�CØ�k‰kÐ:¸W¸JÓG×PÑPÓRˆ÷ 
á #Œ<˜ÓÐ-¨Ð-÷ 
ˆús   ‹"AÁAc                ó  — t        «       5 }| r"|j                  d| f«      j                  «       }n|j                  d«      j                  «       }d d d «       D �cg c]  }t        |«      ‘Œ c}S # 1 sw Y   Œ"xY wc c}w )Nz;SELECT * FROM users WHERE status=? ORDER BY created_at DESCz,SELECT * FROM users ORDER BY created_at DESC)r-   r&   r<   rr   )rV   r+   ÚrowsrE   s       r,   Úget_all_usersrz   ö   sn   € Ü	Œ�CÙØ—;‘;Ð\Ð_eÐ^gÓh×qÑqÓs‰Dà—;‘;ÐMÓN×WÑWÓYˆD÷	 
ñ
 &*Ó*¡T ŒL˜�O TÑ*Ð*÷ 
ˆüò
 +s   ‹AA0ÁA<Á0A9c                ó‚   — t        | d   | d   | d   | d   xs d| d   | d   | d   | d	   | d
   t        | d   «      ¬«
      S )NrR   rS   rG   rT   r
   rU   rV   rW   rX   rZ   Úpassword_hash)
rR   rS   rG   rT   rU   rV   rW   rX   rZ   r\   )rP   r[   )rs   s    r,   rr   rr   ÿ   s_   € ÜØˆt‰9˜C ™L¨s°6©{Ø�|Ñ$Ò*¨°°V±Ø�8‰}¨#¨mÑ*<Ø˜Ñ&°3°|Ñ3DÜ˜#˜oÑ.Ó/ôð rb   c                óR  — t        | «      }|sy|j                  dk(  ryt        j                  d«      }t	        j                  «       t
        z   }t        «       5 }|j                  d|t	        j                  «       ||| f«       |D ]Q  }|j                  d|f«      j                  «       }|sŒ'|j                  d| |d   |t	        j                  «       f«       ŒS 	 d	d	d	«       t        d
||j                  dt        |«      › ddj                  |«      › �«       dd|fS # 1 sw Y   ŒDxY w)zrApprove user, assign permissions, generate set-password token.
    Returns (ok, message, set_password_token).
    ©FúUser not found.r
   Úactive)FzUser is already active.r
   é    z‹UPDATE users SET status='active', approved_by=?, approved_at=?,
               set_password_token=?, set_password_token_expiry=? WHERE id=?ú'SELECT id FROM permissions WHERE name=?zINSERT OR IGNORE INTO user_permissions (user_id, permission_id, granted_by, granted_at)
                       VALUES (?,?,?,?)rR   Nzuser.approvedzApproved with z permission(s): ú, TzUser approved.)rw   rV   ÚsecretsÚtoken_urlsaferC   Ú	TOKEN_TTLr-   r&   rB   ri   rS   r@   r?   )	rv   Úadmin_emailÚpermission_namesÚuserÚtokenÚexpiryr+   ÚpnameÚperms	            r,   Úapprove_userrŽ     s  € ô ˜'Ó"€DÙØ+Ø‡{�{�hÒØ3ä×!Ñ! "Ó%€EÜ�Y‰Y‹[œ9Ñ$€Fä	Œ�CØ�‰ðOàœ$Ÿ)™)›+ u¨f°gÐ>ô	
ó &ˆEØ—;‘;ÐHÈ5È(ÓS×\Ñ\Ó^ˆDÚØ—‘ð+à˜d 4™j¨+´t·y±y³{ÐCõñ &÷ 
ô  ˆ?˜K¨¯©ØœCÐ 0Ó1Ð2Ð2BÀ4Ç9Á9ÐM]ÓC^ÐB_Ð`ôbàÐ! 5Ð(Ð(÷% 
ˆús   ÁADÂ,-DÄD&c                óZ  — t        | «      }|sy|j                  dk7  ry|j                  ryt        j                  d«      }t        j
                  «       t        z   }t        «       5 }|j                  d||| f«       ddd«       t        d||j                  d	«       d
d|fS # 1 sw Y   Œ&xY w)zPGenerate a fresh set-password token for an active user who hasn't logged in yet.r~   r€   )FzUser is not active.r
   )Fz$User has already set their password.r
   r�   zMUPDATE users SET set_password_token=?, set_password_token_expiry=? WHERE id=?Nzuser.resend_tokenzResent set-password emailTzToken generated.)rw   rV   r\   r„   r…   rC   r†   r-   r&   ri   rS   )rv   r‡   r‰   rŠ   r‹   r+   s         r,   Úresend_set_password_tokenr�   .  sž   € ä˜'Ó"€DÙØ+Ø‡{�{�hÒØ/Ø×ÒØ@Ü×!Ñ! "Ó%€EÜ�Y‰Y‹[œ9Ñ$€FÜ	Œ�CØ�‰Ø[Ø�F˜GÐ$ô	
÷ 
ô
 Ð ¨T¯Z©ZÐ9TÔUØÐ# UÐ*Ð*÷ 
ˆús   Á&B!Â!B*c                ó¼   — t        | «      }|syt        «       5 }|j                  d| f«       d d d «       t        d||j                  |xs d«       y# 1 sw Y   Œ&xY w)N)Fr   z-UPDATE users SET status='rejected' WHERE id=?zuser.rejectedzNo reason given)TzUser rejected.)rw   r-   r&   ri   rS   )rv   r‡   Úreasonr‰   r+   s        r,   Úreject_userr“   A  sR   € Ü˜'Ó"€DÙØ'Ü	Œ�CØ�‰ÐCÀgÀZÔP÷ 
ä
ˆ?˜K¨¯©°VÒ5PÐ?PÔQØ!÷ 
ˆús   ™AÁAc                ó¦   — | syt        «       5 }|j                  d| f«      j                  «       }ddd«       rt        |«      S dS # 1 sw Y   ŒxY w)z@Look up name/expiry for a set-password token (used by GET page).NzLSELECT name, set_password_token_expiry FROM users WHERE set_password_token=?)r-   r&   rB   Údict)rŠ   r+   rs   s      r,   Úget_user_by_set_password_tokenr–   K  sQ   € áØÜ	Œ�CØ�k‰kØZØˆHó
÷ ‰(‹*ð 	÷ 
ñ
 Œ4�‹9Ð% Ð%÷ 
ˆús   Ž"AÁAc                ó|  — t        «       5 }|j                  d| f«      j                  «       }|s
	 ddd«       yt        j                  «       |d   xs dkD  r
	 ddd«       y|d   dk7  r
	 ddd«       y	t	        |«      }|j                  d
||d   f«       ddd«       t        dd   |d   d«       y# 1 sw Y   ŒxY w)z/Set a user's password using the approval token.z.SELECT * FROM users WHERE set_password_token=?N)FzInvalid or expired link.Úset_password_token_expiryr   )Fz,This link has expired. Please contact admin.rV   r€   )FzAccount is not active.zdUPDATE users SET password_hash=?, set_password_token=NULL, set_password_token_expiry=NULL WHERE id=?rR   zuser.set_passwordrS   zPassword set after approval)Tz.Password set successfully. You can now log in.)r-   r&   rB   rC   r	   ri   )rŠ   Únew_passwordr+   rs   rm   s        r,   Úset_password_via_tokenrš   W  sÅ   € ä	Œ�CØ�k‰kØ<¸u¸hó
ç
‰(‹*ð 	ñ Ø4÷ 
ˆô �9‰9‹;˜#Ð9Ñ:Ò?¸aÒ@ØH÷ 
ˆð ˆx‰=˜HÒ$Ø2÷ 
ˆô )¨Ó6ˆØ�‰ØrØ�c˜$‘iÐ ô	
÷ 
ô Ð  G¡¨c°'©lÐ<YÔZØA÷! 
ˆús   ‹%B2¹B2Á!	B2Á3"B2Â2B;c                óú   — t        «       5 }|j                  d| f«      j                  «       }|r9|d   s4t        |«      }|j                  d|| f«       t        j                  d«       ddd«       y# 1 sw Y   yxY w)z6Called on first boot to set admin password if not set.z-SELECT password_hash FROM users WHERE email=?r|   z.UPDATE users SET password_hash=? WHERE email=?z![RBAC] Admin password initializedN)r-   r&   rB   r	   r=   r>   )r‡   r™   r+   rs   rm   s        r,   Úset_admin_passwordrœ   l  se   € ä	Œ�CØ�k‰kÐIÈKÈ>ÓZ×cÑcÓeˆÙ�s˜?Ò+Ü,¨\Ó:ˆGØ�K‰KÐHÈ7ÐT_ÐJ`ÔaÜ�K‰KÐ;Ô<÷ 
�‰ús   ‹AA1Á1A:c                óB  — | j                  «       j                  «       } t        «       5 }|j                  d| f«      j	                  «       }ddd«       sy|d   dk(  ry|d   dk(  ry|d   d	k7  ry
|d   syt        |d   |«      syddt        |«      fS # 1 sw Y   ŒKxY w)z3Verify email/password. Returns (ok, message, user).rq   N)FzInvalid email or password.NrV   Úpending)Fz'Your account is pending admin approval.NÚrejected)Fz4Your access request was not approved. Contact admin.Nr€   )FzAccount is inactive.Nr|   )Fz?Password not set. Check your approval email for the setup link.NTÚOK)rg   rh   r-   r&   rB   r   rr   )rS   rl   r+   rs   s       r,   Úauthenticater¡   x  s¨   € à�K‰K‹M×ÑÓ!€EÜ	Œ�CØ�k‰kÐ=À¸xÓH×QÑQÓSˆ÷ 
áØ8Ø
ˆ8�}˜	Ò!ØEØ
ˆ8�}˜
Ò"ØRØ
ˆ8�}˜Ò Ø2ØˆÒØ]Ü˜s ?Ñ3°XÔ>Ø8Ø�”| CÓ(Ð(Ð(÷ 
ˆús   ©"BÂBc            
     óÚ   — t        «       5 } | j                  d«      j                  «       }d d d «       D �cg c]  }t        |d   |d   |d   |d   ¬«      ‘Œ c}S # 1 sw Y   Œ2xY wc c}w )Nz1SELECT * FROM permissions ORDER BY module, actionrR   rG   rH   rI   )rR   rG   rH   rI   )r-   r&   r<   rd   )r+   ry   rE   s      r,   Úget_all_permissionsr£   Ž  sg   € Ü	Œ�CØ�{‰{ÐNÓO×XÑXÓZˆ÷ 
ájnÓoÑjnÐefÔ  $¡¨a°©iÀÀ(ÁÐTUÐV^ÑT_Ö`ÐjnÑoÐo÷ 
ˆüâos   ‹ A·"A(ÁA%c                ó²   — t        «       5 }|j                  d| f«      j                  «       }d d d «       D �cg c]  }|d   ‘Œ	 c}S # 1 sw Y   ŒxY wc c}w )Nz‡SELECT p.name FROM permissions p
               JOIN user_permissions up ON up.permission_id = p.id
               WHERE up.user_id = ?rG   ©r-   r&   r<   ©rv   r+   ry   rE   s       r,   Úget_user_permissionsr§   ”  s[   € Ü	Œ�CØ�{‰{ð'ð ˆJó	
÷
 ‰(‹*ð 	÷ 
ñ  $Ó$™t˜!ˆAˆf‹I˜tÑ$Ð$÷ 
ˆüò %ó   ‹"A¹AÁAc           
     ó¨  — d}t        «       5 }|D ]W  }|j                  d|f«      j                  «       }|sŒ'	 |j                  d| |d   |t        j                  «       f«       |dz  }ŒY 	 ddd«       |rBt        | «      }t        d||r|j                  n
t        | «      dd	j                  |«      › �«       |S # t        $ r Y Œ´w xY w# 1 sw Y   Œ^xY w)
z:Assign permissions to a user. Returns count of new grants.r   r‚   zƒINSERT OR IGNORE INTO user_permissions (user_id, permission_id, granted_by, granted_at)
                           VALUES (?,?,?,?)rR   r0   Nzpermission.assignz
Assigned: rƒ   )
r-   r&   rB   rC   r(   rw   ri   rS   r   r?   )rv   rˆ   Ú
granted_byÚcountr+   rŒ   r�   r‰   s           r,   Úassign_permissionsr¬   Ÿ  sÓ   € à€EÜ	Œ�CÛ%ˆEØ—;‘;ÐHÈ5È(ÓS×\Ñ\Ó^ˆDÚðØ—K‘Kð/à  $ t¡*¨j¼$¿)¹)»+ÐFôð
 ˜Q‘J‘Eñ &÷ 
ñ Ü˜gÓ&ˆÜÐ" J¹d°·
²
ÌÈGËØ˜DŸI™IÐ&6Ó7Ð8Ð9ô	;à€Løô !ò Ùðú÷ 
ˆús.   �)C¸0B9Á(CÂ9	CÃCÃCÃCÃCc                ó0  — t        «       5 }|j                  d|f«      j                  «       }|s
	 d d d «       y|j                  d| |d   f«       d d d «       t        | «      }t	        d||r|j
                  n
t        | «      d|› �«       y# 1 sw Y   Œ=xY w)Nr‚   Fz@DELETE FROM user_permissions WHERE user_id=? AND permission_id=?rR   zpermission.revokez	Revoked: T)r-   r&   rB   rw   ri   rS   r   )rv   Úpermission_nameÚ
revoked_byr+   r�   r‰   s         r,   Úrevoke_permissionr°   ¶  s’   € Ü	Œ�CØ�{‰{ÐDÀÐFXÓY×bÑbÓdˆÙØ÷ 
ˆð 	�‰ØNØ�d˜4‘jÐ!ô	
÷	 
ô ˜'Ó"€DÜ
Ð 
¹$¨D¯JªJÄCÈÃLØ�Ð'Ð(ô*à÷ 
ˆús   ‹%B¹BÂBc                óÔ   — t        «       5 }|j                  d| f«       d d d «       t        | «      }t        d||r|j                  d«       y t        | «      d«       y # 1 sw Y   Œ@xY w)Nz,DELETE FROM user_permissions WHERE user_id=?zpermission.revoke_allzAll permissions revoked)r-   r&   rw   ri   rS   r   )rv   r¯   r+   r‰   s       r,   Úrevoke_all_permissionsr²   Å  sS   € Ü	Œ�CØ�‰ÐBÀWÀJÔO÷ 
ä˜'Ó"€DÜ
Ð" J¹d°·
±
ÐVoÕpÌÈGËÐVoÕp÷ 
ˆús   ‹AÁA'c                ó¢  — | xs dj                  «       j                  «       }t        «       5 }|j                  d|f«      j	                  «       }ddd«       r|d   dk7  ry|d   }|dk(  ry	|d
k(  r|t
        v S t        «       5 }|j                  d|d   |f«      j	                  «       }ddd«       |duS # 1 sw Y   ŒhxY w# 1 sw Y   duS xY w)z­Check if a user has a specific permission.

    - admin: always True
    - manager: True for all non-admin_panel permissions
    - employee: only if explicitly assigned
    r
   ú0SELECT id, role, status FROM users WHERE email=?NrV   r€   FrU   ÚadminTÚmanagerz�SELECT 1 FROM user_permissions up
               JOIN permissions p ON p.id = up.permission_id
               WHERE up.user_id=? AND p.name=?rR   )rg   rh   r-   r&   rB   ÚMANAGER_PERMISSIONS)Ú
user_emailr®   rS   r+   rs   rU   Úfounds          r,   Úhas_permissionrº   Î  sá   € ð Ò˜2×$Ñ$Ó&×,Ñ,Ó.€EÜ	Œ�CØ�k‰kÐLÈuÈhÓW×`Ñ`Óbˆ÷ 
á�#�h‘- 8Ò+ØØˆv‰;€DØˆw‚ØØˆyÒØÔ"5Ð5Ð5ä	Œ�CØ—‘ð2ð �‰Y˜Ð(ó	
÷
 ‰(‹*ð 	÷ 
ð ˜ÐÐ÷# 
ˆú÷ 
ð ˜ÐÐús   ­"B6Â&CÂ6B?ÃCc                ón  — | xs dj                  «       j                  «       }t        «       5 }|j                  d|f«      j	                  «       }ddd«       r|d   dk7  rg S |d   }|dk(  rt
        D �cg c]  }|d   ‘Œ	 c}S |d	k(  rt        t        «      S t        |d
   «      S # 1 sw Y   ŒXxY wc c}w )z5Return the full effective permission list for a user.r
   r´   NrV   r€   rU   rµ   r   r¶   rR   )	rg   rh   r-   r&   rB   r    Úsortedr·   r§   )r¸   rS   r+   rs   rU   rJ   s         r,   Úget_user_effective_permissionsr½   ê  s³   € àÒ˜2×$Ñ$Ó&×,Ñ,Ó.€EÜ	Œ�CØ�k‰kÐLÈuÈhÓW×`Ñ`Óbˆ÷ 
á�#�h‘- 8Ò+Øˆ	Øˆv‰;€DØˆw‚Ý1Ó2Ñ1˜��!“Ð1Ñ2Ð2ØˆyÒÜÔ)Ó*Ð*Ü  D¡	Ó*Ð*÷ 
ˆüò 3s   ­"B&Á5B2Â&B/c           
     óî   — 	 t        «       5 }|j                  d| |||t        j                  «       f«       d d d «       y # 1 sw Y   y xY w# t        $ r }t        j                  d|«       Y d }~y d }~ww xY w)Nz`INSERT INTO audit_log (action, actor_email, target_email, detail, created_at) VALUES (?,?,?,?,?)z[RBAC] Audit log failed: %s)r-   r&   rC   r(   r=   Úwarning)rI   ÚactorÚtargetÚdetailr+   rn   s         r,   ri   ri   û  s^   € ð;ÜŒW˜Ø�K‰KØrØ˜ ¨´·	±	³Ð<ô÷ �W‰Wûô
 ò ;Ü�‰Ð4°c×:Ñ:ûð;ús1   ‚
A Œ*?¶A ¿AÁA ÁA Á	A4ÁA/Á/A4c                ó¾   — t        «       5 }|j                  d| f«      j                  «       }d d d «       D �cg c]  }t        |«      ‘Œ c}S # 1 sw Y   Œ"xY wc c}w )Nz8SELECT * FROM audit_log ORDER BY created_at DESC LIMIT ?)r-   r&   r<   r•   )Úlimitr+   ry   rE   s       r,   Úget_audit_logrÅ     sT   € Ü	Œ�CØ�{‰{ØFÈÈó
ç
‰(‹*ð 	÷ 
ñ "Ó"™T˜ŒD��G˜TÑ"Ð"÷	 
ˆüò #s   ‹"A¹AÁAc                 ój   — i } t         D ]'  \  }}}| j                  |g «      j                  |«       Œ) | S )N)r    Ú
setdefaultÚappend)ÚresultrG   rH   Ú_s       r,   Úpermissions_by_modulerË     s7   € Ø#%€Fß.‰ˆˆf�aØ×Ñ˜& "Ó%×,Ñ,¨TÕ2ð /à€Mrb   c                ón  — | j                  «       j                  «       } t        j                  d«      }t	        j                  «       t
        z   }	 t        «       5 }|j                  d| |j                  «       |j                  «       ||||t	        j                  «       t	        j                  «       f	«       ddd«       t        d|| d|› �«       t        j                  d| |«       dd|fS # 1 sw Y   Œ6xY w# t        j                  $ r Y y	t        $ r }t        j                  d
|«       Y d}~yd}~ww xY w)uW   Admin creates a user directly â€” status is active, token generated for password setup.r�   zåINSERT INTO users (email, name, department, role, status,
                       set_password_token, set_password_token_expiry, approved_by, approved_at, created_at)
                   VALUES (?, ?, ?, ?, 'active', ?, ?, ?, ?, ?)Nzuser.createzCreated by admin, role=z'[RBAC] Admin created user: %s (role=%s)TzUser created.)Frf   r
   z&[RBAC] create_user_by_admin failed: %s)FzUser creation failed.r
   )rg   rh   r„   r…   rC   r†   r-   r&   ri   r=   r>   r"   rj   r(   rk   )	rS   Ú	full_namerT   rU   r‡   rŠ   r‹   r+   rn   s	            r,   Úcreate_user_by_adminrÎ     s  € ð �K‰K‹M×ÑÓ!€EÜ×!Ñ! "Ó%€EÜ�Y‰Y‹[œ9Ñ$€Fð2ÜŒW˜Ø�K‰KðCð ˜	Ÿ™Ó)¨:×+;Ñ+;Ó+=¸tØ˜ ¬T¯Y©Y«[¼$¿)¹)»+ðGô	÷ ô 	ˆ}˜k¨5Ð4KÈDÈ6Ð2RÔSÜ�‰Ð=¸uÀdÔKØ�_ eÐ+Ð+÷ ˆWûô ×!Ñ!ò GÙFÜò 2Ü�‰Ð=¸sÔCÜ1ûð2ús7   Á
C7 ÁAC+Â64C7 Ã+C4Ã0C7 Ã7D4ÄD4ÄD/Ä/D4c                ó  — 	 t        «       5 }|j                  d|| f«      }ddd«       j                  rt        d|t	        | «      d|› �«       yy# 1 sw Y   Œ1xY w# t
        $ r }t        j                  d|«       Y d}~yd}~ww xY w)z1Set arbitrary status on a user (e.g. 'inactive').z$UPDATE users SET status=? WHERE id=?Nzuser.status_changeu	   statusâ†’TFz![RBAC] set_user_status failed: %s©r-   r&   Úrowcountri   r   r(   r=   rk   )rv   Ú
new_statusr‡   r+   Úcurrn   s         r,   Úset_user_statusrÔ   3  s€   € ðÜŒW˜Ø—+‘+Ø6¸ÀWÐ8MóˆC÷ ð �<Š<ÜÐ'¨´c¸'³lÀiÐPZÈ|ÐD\Ô]ØØ÷ ˆWûô ò Ü�‰Ð8¸#Ô>Üûðúó-   ‚
A ŒA¡.A ÁAÁA Á	BÁ&BÂBc                ó  — 	 t        «       5 }|j                  d|| f«      }ddd«       j                  rt        d|t	        | «      d|› �«       yy# 1 sw Y   Œ1xY w# t
        $ r }t        j                  d|«       Y d}~yd}~ww xY w)z2Change a user's role (employee / manager / admin).z"UPDATE users SET role=? WHERE id=?Nzuser.role_changeu   roleâ†’TFz[RBAC] set_user_role failed: %srÐ   )rv   Únew_roler‡   r+   rÓ   rn   s         r,   Úset_user_rolerØ   D  s{   € ð	ÜŒW˜Ø—+‘+ÐBÀXÈwÐDWÓXˆC÷ à�<Š<ÜÐ% {´C¸³LÀGÈHÈ:ÐBVÔWØØ÷ ˆWûô ò Ü�‰Ð6¸Ô<ÜûðúrÕ   c                óX  — | xs dj                  «       j                  «       } t        «       5 }|j                  d| f«      j	                  «       }|sg cddd«       S |j                  d|d   f«      j                  «       }ddd«       D �cg c]  }|d   ‘Œ	 c}S # 1 sw Y   ŒxY wc c}w )zGReturn brand codes assigned to a user. Empty list means no restriction.r
   r5   Nú2SELECT brand_code FROM user_brands WHERE user_id=?rR   r1   )rg   rh   r-   r&   rB   r<   )rS   r+   rs   ry   rE   s        r,   Úget_user_brands_by_emailrÛ   T  sž   € àŠ[�b×ÑÓ!×'Ñ'Ó)€EÜ	Œ�CØ�k‰kÐ>ÀÀÓI×RÑRÓTˆÙØ÷ 
‰ð �{‰{Ø@À3ÀtÁ9À,ó
ç
‰(‹*ð 	÷	 
ñ &*Ó*¡T ˆAˆl‹O TÑ*Ð*÷ 
ˆüò +s   ­%BÁ$BÂB'ÂB$c                ó²   — t        «       5 }|j                  d| f«      j                  «       }ddd«       D �cg c]  }|d   ‘Œ	 c}S # 1 sw Y   ŒxY wc c}w )z,Return brand codes assigned to a user by ID.rÚ   Nr1   r¥   r¦   s       r,   Úget_user_brandsrÝ   a  sT   € ä	Œ�CØ�{‰{Ø@À7À*ó
ç
‰(‹*ð 	÷ 
ñ &*Ó*¡T ˆAˆl‹O TÑ*Ð*÷	 
ˆüò +r¨   c           
     ó^  — t        «       5 }|j                  d| f«       |D ]8  }|j                  d| |j                  «       |t        j                  «       f«       Œ: 	 ddd«       t	        | «      }t        d||r|j                  n
t        | «      dt        |«      › d�«       y# 1 sw Y   ŒGxY w)z,Replace a user's brand assignments entirely.z'DELETE FROM user_brands WHERE user_id=?zbINSERT OR IGNORE INTO user_brands (user_id, brand_code, assigned_by, assigned_at) VALUES (?,?,?,?)Nzbrands.assignz	Assigned z	 brand(s))	r-   r&   rg   rC   rw   ri   rS   r   r@   )rv   Úbrand_codesÚassigned_byr+   Úcoder‰   s         r,   Úassign_brandsrâ   j  s�   € ä	Œ�CØ�‰Ð=À¸zÔJÛˆDØ�K‰KØtØ˜$Ÿ*™*›,¨´T·Y±Y³[ÐAõñ  ÷ 
ô ˜'Ó"€DÜ
ˆ?˜K±t¨¯ªÄÀWÃØ”s˜;Ó'Ð(¨	Ð2õ4÷ 
ˆús   ‹AB#Â#B,c           	     óâ   — t        «       5 }|j                  d| f«       |D ]7  }|j                  d| |j                  «       t        j                  «       f«       Œ9 	 ddd«       y# 1 sw Y   yxY w)z+Save the brands a user requested at signup.z1DELETE FROM user_requested_brands WHERE user_id=?z^INSERT OR IGNORE INTO user_requested_brands (user_id, brand_code, requested_at) VALUES (?,?,?)N)r-   r&   rg   rC   )rv   rß   r+   rá   s       r,   Úsave_requested_brandsrä   x  sS   € ä	Œ�CØ�‰ÐGÈ'ÈÔTÛˆDØ�K‰KØpØ˜$Ÿ*™*›,¬¯	©	«Ð4õñ  ÷ 
�‰ús   ‹AA%Á%A.c                ó²   — t        «       5 }|j                  d| f«      j                  «       }ddd«       D �cg c]  }|d   ‘Œ	 c}S # 1 sw Y   ŒxY wc c}w )z0Return brand codes the user requested at signup.z<SELECT brand_code FROM user_requested_brands WHERE user_id=?Nr1   r¥   r¦   s       r,   Úget_requested_brandsræ   ƒ  sT   € ä	Œ�CØ�{‰{ØJÈWÈJó
ç
‰(‹*ð 	÷ 
ñ &*Ó*¡T ˆAˆl‹O TÑ*Ð*÷	 
ˆüò +r¨   )rD   r   ÚreturnÚNone)r
   r
   )
rS   r   rG   r   rT   r   rl   r   rç   útuple[bool, str])rS   r   rç   úOptional[UserRecord])rv   rQ   rç   rê   )r
   )rV   r   rç   zlist[UserRecord])rç   rP   )rv   rQ   r‡   r   rˆ   ú	list[str]rç   útuple[bool, str, str])rv   rQ   r‡   r   rç   rì   )rv   rQ   r‡   r   r’   r   rç   ré   )rŠ   r   rç   zdict | None)rŠ   r   r™   r   rç   ré   )r‡   r   r™   r   rç   rè   )rS   r   rl   r   rç   z&tuple[bool, str, Optional[UserRecord]])rç   zlist[PermissionRecord])rv   rQ   rç   rë   )rv   rQ   rˆ   rë   rª   r   rç   rQ   )rv   rQ   r®   r   r¯   r   rç   r[   )rv   rQ   r¯   r   rç   rè   )r¸   r   r®   r   rç   r[   )r¸   r   rç   rë   )
rI   r   rÀ   r   rÁ   r   rÂ   r   rç   rè   )éd   )rÄ   rQ   rç   z
list[dict])rç   zdict[str, list[str]])rS   r   rÍ   r   rT   r   rU   r   r‡   r   rç   rì   )rv   rQ   rÒ   r   r‡   r   rç   r[   )rv   rQ   r×   r   r‡   r   rç   r[   )rS   r   rç   rë   )rv   rQ   rß   rë   rà   r   rç   rè   )rv   rQ   rß   rë   rç   rè   )<Ú
__future__r   Úloggingr6   r„   r"   rC   Ú
contextlibr   Údataclassesr   r   Útypingr   Úwerkzeug.securityr   r	   Ú	getLoggerr=   r   r`   rA   r    r·   r†   r;   r-   rN   rP   rd   ro   rt   rw   rz   rr   rŽ   r�   r“   r–   rš   rœ   r¡   r£   r§   r¬   r°   r²   rº   r½   ri   rÅ   rË   rÎ   rÔ   rØ   rÛ   rÝ   râ   rä   ræ   )rJ   s   0r,   Ú<module>rõ      sž  ðÞ "ã Û 	Û Û Û Ý %ß (Ý ç Ià	ˆ×	Ñ	˜;Ó	'€ð €ˆ#Ó à%€ò3Ð Ð/ó ñ8 &9Ó9Ñ%8 �q˜“tÐ%8Ñ9Ð ð €	ð
:€ð~ ñó ðó +5ð` ÷
ð 
ó ð
ð ÷ð ó ðô?ó(ó.ô+óó)óF+ô&"ó	&óBó*=ó)ó,pó%óó.óqóó8+ô";ô#óð2Øð2Øð2Ø,/ð2Ø7:ð2ØILð2àó2ó4ó"ó 
+ó+ó4óô+ùòa :s   Á D'