Ë
    J>iÒ>  ã                  ó†   — d Z ddlmZ ddlmZ ddlmZmZ ddlm	Z	m
Z
 ddlmZ erddlmZ  G d„ d	e«      Z G d
„ d«      Zy)z?Default Django repository implementation for wallet operations.é    )Úannotations)ÚDecimal)ÚTYPE_CHECKINGÚProtocol)Ú
connectionÚtransaction)ÚF)ÚModelc                  ód   — e Zd ZdZdd„Z	 d		 	 	 	 	 	 	 	 	 d
d„Z	 d		 	 	 	 	 	 	 	 	 dd„Zdd„Zdd„Zy)ÚWalletRepositoryProtocola  
    Interface for wallet operations.
    
    For most use cases, use WalletRepository which implements all methods automatically.
    You only need to define point types and their decimal places.
    
    Custom implementations must implement all methods below.
    c                 ó   — y)zö
        Return a dictionary mapping point types to their decimal places.
        
        Example:
            {
                "credit_balance": 2,
                "reward_points": 0,
                "crypto_balance": 8,
            }
        N© ©Úselfs    úJ/home/cursorai/projects/django-wallet-utils/src/wallet_utils/repository.pyÚget_point_typesz(WalletRepositoryProtocol.get_point_types   s   € ð 	ó    c                 ó   — y)zÐ
        Update user balance by adding amount (positive for add, negative for deduct).
        
        Returns the new balance after the update.
        Should be atomic (use database transactions).
        Nr   ©r   Úuser_idÚ
point_typeÚamountÚallow_negatives        r   Úupdate_balancez'WalletRepositoryProtocol.update_balance&   s   € ð 	r   c                 ó   — y)a  
        Atomically deduct balance using SQL WHERE clause to prevent race conditions.
        
        Uses SQL: UPDATE ... WHERE {point_type} >= {amount} (or >= 0 if allow_negative)
        
        Returns:
            Tuple of (success: bool, new_balance: Decimal)
            - success: True if row was updated (sufficient balance), False otherwise
            - new_balance: The balance after deduction (if successful) or current balance (if failed)
        
        Should be atomic (use database transactions).
        Nr   r   s        r   Údeduct_balance_atomicz.WalletRepositoryProtocol.deduct_balance_atomic5   s   € ð& 	r   c                 ó   — y)z‹
        Get the current balance for a specific point type for a user.
        
        Used for reading balance after operations.
        Nr   )r   r   r   s      r   Úget_user_balancez)WalletRepositoryProtocol.get_user_balanceJ   s   € ð 	r   c                 ó   — y)zß
        Create a transaction record and return the new transaction ID.
        
        Args:
            record: TransactionRecord dataclass instance
        
        Should be atomic (use database transactions).
        Nr   )r   Úrecords     r   Úcreate_transaction_recordz2WalletRepositoryProtocol.create_transaction_recordR   s   € ð 	r   N©Úreturnúdict[str, int]©F©
r   Úintr   Ústrr   r   r   Úboolr#   r   ©
r   r'   r   r(   r   r   r   r)   r#   ztuple[bool, Decimal]©r   r'   r   r(   r#   r   ©r#   r'   )	Ú__name__Ú
__module__Ú__qualname__Ú__doc__r   r   r   r   r!   r   r   r   r   r      sŠ   „ ñóð$  %ðàðð ðð ð	ð
 ðð 
óð(  %ðàðð ðð ð	ð
 ðð 
óó*ô	r   r   c                  ó  — e Zd ZdZ	 	 	 	 d	 	 	 	 	 	 	 	 	 	 	 dd„Zdd„Zdd„Zdd„Zdd„Zdd„Z	ddd	„Z
ej                  	 d	 	 	 	 	 	 	 	 	 dd
„«       Zej                  	 d	 	 	 	 	 	 	 	 	 dd„«       Zdd„Zej                  dd„«       Zy)ÚWalletRepositorya  
    Default Django implementation of WalletRepositoryProtocol.
    
    This repository handles all balance operations and transaction recording automatically.
    Users only need to define point types and their decimal places - that's it!
    
    Example:
        # Using User model for balances (default)
        repo = WalletRepository(
            user_model=User,
            point_types={
                "credit_balance": 2,
                "reward_points": 0,
            },
        )
        
        # Using separate Wallet model for balances
        repo = WalletRepository(
            user_model=User,
            wallet_balance_model=Wallet,
            point_types={
                "credit_balance": 2,
                "reward_points": 0,
            },
        )
    Nc                óœ   — |€ddl m} |}|| _        || _        || _        || _        |xs i | _        || _        |xs || _        |du| _	        y)aø  
        Initialize the repository.
        
        Args:
            user_model: Django User model class (used for reference, or as balance model if wallet_balance_model not provided)
            point_types: Dictionary mapping point types to decimal places
            wallet_balance_model: Optional separate model for storing wallet balances (must have user_id field).
                                 If None, balances are stored on user_model.
            wallet_model: Optional custom WalletTransaction model (defaults to WalletTransaction)
            point_type_field_map: Optional mapping from point_type to database field name
            wallet_user_id_field: Field name in wallet_balance_model that references user_id (default: "user_id")
        Né   )ÚWalletTransaction)
Úmodelsr5   Ú_user_modelÚ_wallet_balance_modelÚ_point_typesÚ_wallet_modelÚ_point_type_field_mapÚ_wallet_user_id_fieldÚ_balance_modelÚ_balance_model_is_separate)r   Ú
user_modelÚpoint_typesÚwallet_balance_modelÚwallet_modelÚpoint_type_field_mapÚwallet_user_id_fieldr5   s           r   Ú__init__zWalletRepository.__init__z   sc   € ð, ÐÝ1Ø,ˆLà%ˆÔØ%9ˆÔ"Ø'ˆÔØ)ˆÔØ%9Ò%?¸RˆÔ"Ø%9ˆÔ"ð 3Ò@°jˆÔØ*>ÀdÐ*JˆÕ'r   c                ó   — | j                   S )z,Return point types and their decimal places.)r9   r   s    r   r   z WalletRepository.get_point_typesŸ   s   € à× Ñ Ð r   c                ó   — | j                   S )zReturn the User model class.)r7   r   s    r   Úget_user_modelzWalletRepository.get_user_model£   s   € à×ÑÐr   c                ó   — | j                   S )z)Return the WalletTransaction model class.)r:   r   s    r   Úget_wallet_modelz!WalletRepository.get_wallet_model§   s   € à×!Ñ!Ð!r   c                ó   — | j                   S )z2Return the model used for storing wallet balances.)r=   r   s    r   Úget_wallet_balance_modelz)WalletRepository.get_wallet_balance_model«   s   € à×"Ñ"Ð"r   c                ó:   — | j                   j                  ||«      S )z0Return the database field name for a point type.)r;   Úget)r   r   s     r   Úget_point_type_fieldz%WalletRepository.get_point_type_field¯   s   € à×)Ñ)×-Ñ-¨j¸*ÓEÐEr   c                ó"  — d}| j                   rµ|rA	  | j                  j                  j                  «       j                  d
i | j
                  |i¤ŽS ||k(  r; | j                  j                  j                  d
i | j
                  |i¤di i¤Ž\  }}|S  | j                  j                  j                  d
i | j
                  |i¤ŽS |r5	 | j                  j                  j                  «       j	                  |¬«      S 	 | j                  j                  j	                  |¬«      S # | j                  j                  $ r; ||k(  r4 | j                  j                  j                  d
i | j
                  |i¤ŽcY S ‚ w xY w# | j                  j                  $ rp ||k(  rii }t        | j                  d«      rd|› �|d<   t        | j                  d«      r	d|› d�|d<    | j                  j                  j                  d
d	|i|¤ŽcY S ‚ w xY w# | j                  j                  $ rp ||k(  rii }t        | j                  d«      rd|› �|d<   t        | j                  d«      r	d|› d�|d<    | j                  j                  j                  d
d	|i|¤ŽcY S ‚ w xY w)a#  
        Get the balance object (wallet or user) for a given user_id.
        Creates the object if it doesn't exist ONLY for system users (user_id == -100).
        For regular users, raises DoesNotExist if user doesn't exist.
        
        Args:
            user_id: The user ID
            for_update: Whether to use select_for_update() for locking
            
        Returns:
            The balance model instance
            
        Raises:
            DoesNotExist: If user doesn't exist and user_id is not -100 (system user)
        iœÿÿÿÚdefaults)ÚpkÚusernameÚsystem_user_ÚemailÚsystem_z@system.localrR   r   )r>   r=   ÚobjectsÚselect_for_updaterN   r<   ÚDoesNotExistÚcreateÚget_or_creater7   Úhasattr)r   r   Ú
for_updateÚSYSTEM_USER_IDÚobjÚcreatedrQ   s          r   Ú_get_balance_objectz$WalletRepository._get_balance_object³   s¾  € ð  ˆà×*Ò*áð
ØN˜4×.Ñ.×6Ñ6×HÑHÓJ×NÑNñ Ø×5Ñ5°wÐ?ñð ð ˜nÒ,Ø#L 4×#6Ñ#6×#>Ñ#>×#LÑ#Lñ $Ø×5Ñ5°wÐ?ñ$à!#ò$‘L�C˜ð �Jà:˜4×.Ñ.×6Ñ6×:Ñ:ñ Ø×5Ñ5°wÐ?ñð ñ
 ðØ×+Ñ+×3Ñ3×EÑEÓG×KÑKÈwÐKÓWÐWðØ×+Ñ+×3Ñ3×7Ñ7¸7Ð7ÓCÐCøðQ ×*Ñ*×7Ñ7ò à .Ò0ØA˜t×2Ñ2×:Ñ:×AÑAñ  Ø#×9Ñ9¸7ÐCñ ò ð ðûð4 ×'Ñ'×4Ñ4ò 
à .Ò0à#%˜Ü" 4×#3Ñ#3°ZÔ@Ø5AÀ'ÀÐ3K˜H ZÑ0Ü" 4×#3Ñ#3°WÔ=Ø29¸'¸À-Ð0P˜H WÑ-Ø>˜t×/Ñ/×7Ñ7×>Ñ>ÑVÀ'ÐVÈXÑVÒVØð
ûð ×'Ñ'×4Ñ4ò 	à .Ò0Ø#%˜Ü" 4×#3Ñ#3°ZÔ@Ø5AÀ'ÀÐ3K˜H ZÑ0Ü" 4×#3Ñ#3°WÔ=Ø29¸'¸À-Ð0P˜H WÑ-Ø>˜t×/Ñ/×7Ñ7×>Ñ>ÑVÀ'ÐVÈXÑVÒVØð	ús8   ’?D" Ã3E9 Ã<%H Ä"AE6Å4E6Å9BHÈ HÈBJÊJc                óÒ  — | j                  |«      }|sA| j                  |d¬«      }t        ||«      }||z   dk  rt        d|› dt	        |«      › �«      ‚| j
                  xs | j                  }| j                  r| j                  }	nd}	|	|i}
  |j                  j                  di |
¤Žj                  di |t        |«      |z   i¤Ž | j                  |d¬«      }t        ||«      }|S )zš
        Update user balance by adding amount (positive for add, negative for deduct).
        
        Returns the new balance after the update.
        F©r]   r   zInsufficient balance: z < Úidr   )rO   ra   ÚgetattrÚ
ValueErrorÚabsr8   r7   r>   r<   rW   ÚfilterÚupdater	   )r   r   r   r   r   Ú
field_nameÚbalance_objÚcurrent_balanceÚbalance_modelÚuser_id_fieldÚfilter_kwargsÚnew_balances               r   r   zWalletRepository.update_balance  s  € ð ×.Ñ.¨zÓ:ˆ
ñ Ø×2Ñ2°7ÀuÐ2ÓMˆKÜ% k°:Ó>ˆOØ Ñ'¨!Ò+Ü Ð#9¸/Ð9JÈ#ÌcÐRXËkÈ]Ð![Ó\Ð\ð ×2Ñ2ÒF°d×6FÑ6Fˆð ×*Ò*à ×6Ñ6‰Mð !ˆMð '¨Ð0ˆð 	=Ð$ˆ×Ñ×$Ñ$Ñ5 }Ñ5×<Ñ<ñ 	
Øœ1˜Z›=¨6Ñ1Ð2ò	
ð
 ×.Ñ.¨wÀ5Ð.ÓIˆÜ˜k¨:Ó6ˆàÐr   c           
     ó~  — | j                  |«      }| j                  j                  j                  }|dk(  r| j	                  ||«      }d|fS | j
                  r8|r| j                  › d�}|g}	n| j                  › d|› d�}||g}	| j                  }
n|rd}|g}	n
d|› d�}||g}	d}
t        j                  «       5 }d	|› d
|› d|› d|› d�	}|j                  ||g|	z   «       |j                  }|dkD  rQ|j                  d|› d|› d|
› d�|g«       |j                  «       }t        t        |d   «      «      }d|fcddd«       S |j                  d|› d|› d|
› d�|g«       |j                  «       }|rt        t        |d   «      «      }n| j                  |d¬«       t        d«      }d|fcddd«       S # 1 sw Y   yxY w)aÏ  
        Atomically deduct balance using SQL WHERE clause to prevent race conditions.
        
        Uses SQL: UPDATE ... WHERE {point_type} >= {amount} (or >= 0 if allow_negative)
        
        Returns:
            Tuple of (success: bool, new_balance: Decimal)
            - success: True if row was updated (sufficient balance), False otherwise
            - new_balance: The balance after deduction (if successful) or current balance (if failed)
        r   Tz = %sz
 = %s AND z >= %szid = %szid = %s AND rd   z
                UPDATE z
                SET z = z - %s
                WHERE z
            zSELECT z FROM z WHERE Nrc   Ú0F)rO   r=   Ú_metaÚdb_tabler   r>   r<   r   ÚcursorÚexecuteÚrowcountÚfetchoner   r(   ra   )r   r   r   r   r   rj   Ú
table_namerl   Úwhere_clauseÚwhere_paramsÚid_fieldru   ÚsqlÚrows_affectedÚrowrp   s                   r   r   z&WalletRepository.deduct_balance_atomic1  s  € ð$ ×.Ñ.¨zÓ:ˆ
Ø×(Ñ(×.Ñ.×7Ñ7ˆ
ð �QŠ;Ø"×3Ñ3°G¸ZÓHˆOØ˜Ð(Ð(ð ×*Ò*áØ"&×"<Ñ"<Ð!=¸UÐC�Ø '˜y‘à"&×"<Ñ"<Ð!=¸ZÈ
À|ÐSYÐZ�Ø '¨Ð0�Ø×1Ñ1‰Hñ Ø(�Ø '˜y‘à!-¨j¨\¸Ð@�Ø '¨Ð0�ØˆHä×ÑÔ  FðØ"�|ð $Ø�L  J <ð 0Ø#�nð %ðˆCð
 �N‰N˜3  ¨<Ñ 7Ô8ð #ŸO™OˆMà˜qÒ à—‘Ø˜j˜\¨°
¨|¸7À8À*ÈEÐRØ�Iôð —o‘oÓ'�Ü%¤c¨#¨a©&£kÓ2�Ø˜[Ð(÷) !Ñ ð. —‘Ø˜j˜\¨°
¨|¸7À8À*ÈEÐRØ�Iôð —o‘oÓ'�ÙÜ&-¬c°#°a±&«kÓ&:‘Oð ×,Ñ,¨WÀÐ,ÔFÜ&-¨c£l�OØ˜oÐ-÷E !× Ò ús   Â9A>F3ÅA(F3Æ3F<c                ób   — | j                  |«      }| j                  |d¬«      }t        ||«      S )z=Get the current balance for a specific point type for a user.Frc   )rO   ra   re   )r   r   r   rj   rk   s        r   r   z!WalletRepository.get_user_balanceƒ  s4   € à×.Ñ.¨zÓ:ˆ
Ø×.Ñ.¨wÀ5Ð.ÓIˆÜ�{ JÓ/Ð/r   c                ó’  — ddl m } |j                  }t        |t        «      r|j	                  |«      }| j
                  j                  j                  |j                  |j                  |j                  |j                  |j                  |j                  |j                  |j                  ||j                   ¬«
      }|j"                  S )z 
        Create a transaction record and return the new transaction ID.
        
        Args:
            record: TransactionRecord dataclass instance
        r   )Údatetime)
ÚwtypeÚiidÚuidÚtyper   ÚbalanceÚ
trans_typeÚdescrÚcdateÚ
extra_data)r‚   rŠ   Ú
isinstancer(   Úfromisoformatr:   rW   rZ   rƒ   r„   r…   r†   r   r‡   rˆ   r‰   r‹   rd   )r   r    r‚   rŠ   Útransaction_objs        r   r!   z*WalletRepository.create_transaction_record‰  s£   € õ 	&ð —‘ˆÜ�eœSÔ!Ø×*Ñ*¨5Ó1ˆEð ×,Ñ,×4Ñ4×;Ñ;Ø—,‘,Ø—
‘
Ø—
‘
Ø—‘Ø—=‘=Ø—N‘NØ×(Ñ(Ø—,‘,ØØ×(Ñ(ð <ó 
ˆð ×!Ñ!Ð!r   )NNNr   )r?   útype[Model]r@   r$   rA   útype[Model] | NonerB   r�   rC   zdict[str, str] | NonerD   r(   r"   )r#   r�   )r#   r�   )r   r(   r#   r(   r%   )r   r'   r]   r)   r&   r*   r+   r,   )r-   r.   r/   r0   rE   r   rH   rJ   rL   rO   ra   r   Úatomicr   r   r   r!   r   r   r   r2   r2   ^   sC  „ ñð> 48Ø+/Ø6:Ø$-ð#Kàð#Kð $ð#Kð 1ð	#Kð
 )ð#Kð 4ð#Kð "ó#KóJ!ó ó"ó#óFôMð^ ×Ñð  %ð,àð,ð ð,ð ð	,ð
 ð,ð 
ò,ó ð,ð\ ×Ñð  %ðO.àðO.ð ðO.ð ð	O.ð
 ðO.ð 
òO.ó ðO.ób0ð ×Ñò"ó ñ"r   r2   N)r0   Ú
__future__r   Údecimalr   Útypingr   r   Ú	django.dbr   r   Údjango.db.modelsr	   r
   r   r2   r   r   r   Ú<module>r—      s8   ðÙ Eå "å ß *ç -Ý áÝ&ôL˜xô L÷^G"ò G"r   