o
    BhD                     @   s   d dl mZ d dlmZ d dlm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mZ G dd	 d	ejZG d
d dejZG dd dejZG dd dejZdS )    )models)timezone)MinValueValidator)ValidationError)EmployeeCompany)Decimal)datetimetimedatec                   @   sn   e Zd ZejddZe ZejdddZ	ej
eejddddZG dd	 d	Zd
d ZeddefddZdS )Holidayd   
max_lengthTz4If True, holiday repeats every year on the same datedefault	help_textz!If null, applies to all companies)	on_deletenullblankr   c                   @   s   e Zd ZdgZddggZdS )zHoliday.Metar   companyN)__name__
__module____qualname__orderingunique_together r   r   &/var/www/html/wtlms/overtime/models.pyMeta   s    r   c                 C   s   | j  d| jd dS )N (z%B %d))namer   strftimeselfr   r   r   __str__   s   zHoliday.__str__N
check_datec                 C   sf   | j jtj|dtjddB d|j|jd}| j jtj|dtjddB d|d}| p0| }|S )z<Check if a given date is a holiday for the specified company)r   T)company__isnull)	recurringdate__month	date__dayF)r(   r   )objectsfilterr   Qmonthdayexists)clsr&   r   recurring_holidaysnon_recurring_holidays
is_holidayr   r   r   r4      s   zHoliday.is_holidayN)r   r   r   r   	CharFieldr!   	DateFieldr   BooleanFieldr(   
ForeignKeyr   CASCADEr   r   r%   classmethodr4   r   r   r   r   r   	   s$    r   c                   @   s
  e Zd ZdZejeejdZej	dddZ
ej	dddZejdd	d
ddZejdd	d
dZejdd	ddZg dZejdeddZejdd	ddddZejdd	dddZejdd	dddZejdddZG dd dZdd Zdefdd Zd!ed"ed#ed$efd%d&Zd'S )(CompanyOvertimeConfigz1Configuration for company-specific overtime rulesr      z)Standard number of working days per monthr      z(Standard number of working hours per day      g      ?z4Multiplier for weekday overtime (e.g., 1.5 for 150%))
max_digitsdecimal_placesr   r   )rB   rC   r   g       @))STANDARDu?   Standard (Basic Pay / Working Days / Hours × OT Hours × Rate))
FIXED_RATEzFixed Rate per Hour)CUSTOMzCustom Formula   rD   r   choicesr   
   Tz*Fixed amount per hour for weekday overtime)rB   rC   r   r   r   )rB   rC   r   r   zUPython expression for custom calculation. Available variables: basic_pay, hours, rate)r   r   c                   @   s   e Zd ZdZdZdS )zCompanyOvertimeConfig.MetazCompany Overtime ConfigurationzCompany Overtime ConfigurationsN)r   r   r   verbose_nameverbose_name_pluralr   r   r   r   r   }   s    r   c                 C   s   | j j dS )Nz Overtime Config)r   r!   r#   r   r   r   r%      s   zCompanyOvertimeConfig.__str__r&   c                 C   s8   t || js| dkr| jS | dkr| jS | jS )z)Get the appropriate rate for a given date      )r   r4   r   weekdaysunday_holiday_ratesaturday_rateweekday_rate)r$   r&   r   r   r   get_rate_for_date   s
   z'CompanyOvertimeConfig.get_rate_for_date	basic_payhoursrater   c           
   
   C   s   | j dkr't|| js| dkr| j| S | dkr"| j| S | j| S | j dkrb| jrbzt	|t	|t	|t
d}t| jdi i|}t
t|W S  tya } z	tdt| d}~ww |t
t| j }|t
t| j }	|	| | S )	z0Calculate overtime amount based on configurationrE   rM   rN   rF   )rT   rU   rV   r   __builtins__zError in custom formula: N)calculation_methodr   r4   r   rO   fixed_sunday_holiday_amountfixed_saturday_amountfixed_weekday_amountcustom_formulafloatr   evalstr	Exceptionr   working_days_per_monthworking_hours_per_day)
r$   rT   rU   rV   r   envresulte
daily_ratehourly_rater   r   r   calculate_amount   s,   



z&CompanyOvertimeConfig.calculate_amountN)r   r   r   __doc__r   OneToOneFieldr   r:   r   PositiveIntegerFieldra   rb   DecimalFieldrR   rQ   rP   CALCULATION_METHODSr6   rX   r[   rZ   rY   	TextFieldr\   r   r%   r   rS   r   rh   r   r   r   r   r<   6   sx    r<   c                   @   sn   e Zd ZdZejdejddZejddZ	ej
ddZejd	ejdd
ZejddZG dd dZdd ZdS )OvertimeRequestHistoryz,Track approval history for overtime requestsOvertimeRequesthistory)r   related_namerG   r   T)r   accounts.Employee)r   r   auto_now_addc                   @   s   e Zd ZdgZdZdZdS )zOvertimeRequestHistory.Metaz
-timestampzOvertime Request HistoryzOvertime Request HistoriesN)r   r   r   r   rK   rL   r   r   r   r   r      s    r   c                 C   s"   | j  d| j d| j d| j S )N - z by z at )requeststatusactor	timestampr#   r   r   r   r%      s   "zOvertimeRequestHistory.__str__N)r   r   r   ri   r   r9   r:   rw   r6   rx   rn   commentSET_NULLry   DateTimeFieldrz   r   r%   r   r   r   r   ro      s    ro   c                       s  e Zd Zg dZddgZejeejdZ	e
 Zdd Zdd Zejed	Zejed	Zejd
dddZejddddZe ZejdeddZejdeddZej
dddZejddddddZejdddddZejdddZejdddZejdddej ddZ!ejdddZ"ejdddZ#ejdddej ddZ$ejdddZ%ejdddZ&ejdddej ddZ'ejdd Z(ejdd!Z)d"d# Z* fd$d%Z+d&d' Z,d(d) Z-d*d+ Z. fd,d-Z/e0d.d/ Z1d0d1 Z2d2d3 Z3G d4d5 d5Z4  Z5S )6rp   ))PENDINGzPending HR Review)HR_APPROVEDzHR Approved)HR_REJECTEDzHR Rejected)REG_APPROVEDzRegistrar Approved)REG_REJECTEDzRegistrar Rejected)	PROCESSEDzPayment Processed)r~   zPending Payment)PAIDPaidr=   c                   C      t  jddddS )N   r   hourminutesecondr   nowreplacer   r   r   r   default_start_time      z"OvertimeRequest.default_start_timec                   C   r   )N   r   r   r   r   r   r   r   default_end_time   r   z OvertimeRequest.default_end_time)r   r@   rA   F)rB   rC   editable   rG   r~   rH   T)r   r   rJ   )rB   rC   r   r   r   zovertime_documents/%Y/%m/z?Upload supporting documentation (e.g., work evidence, receipts))	upload_tor   r   r    )r   r   rs   hr_approved_overtime)r   r   r   rr   registrar_approved_overtimefinance_processed_overtimert   )auto_nowc                 C   s   t | j| jjS )z-Check if the request date is a public holiday)r   r4   r   employeer   r#   r   r   r   is_public_holiday   s   z!OvertimeRequest.is_public_holidayc           	         sv  t    | jr| jr| jstdt| dr| jstdt| jdr)| j n| j}t| jdr7| j n| j}||krDtddit	
  }| jr| j|krXtddi|jd	d
}|jd	kro|j|jd	 dd	d}n
|j|jd	 d	d}| j|k rtddi| jr|  rd S | j }|dk }|rtdd}t| jdr| j n| j}||k rtddid S d S d S )Nz+Date, start time, and end time are requiredr   zEmployee is requiredr
   end_timez)End time cannot be the same as start timer   z(Cannot request overtime for future dates   )r/      )yearr.   r/   )r.   r/   zFOvertime can only be requested for the current month or previous monthrN   r   r   
start_timez;For regular weekdays, overtime can only start after 5:00 PM)supercleanr   r   r   r   hasattrr   r
   r   r   r   r.   r   r   rO   )	r$   r   r   request_datecurrent_month_firstprev_month_firstrO   
is_weekdaywork_end_time	__class__r   r   r      s@   





zOvertimeRequest.cleanc                 C   s
  | j r	| jr	| jstdS | j  }tj| j | j}| j| jk r1tj| j tjdd | j}n	tj| j | j}| 	 rL|| 
 d }tt|S |dk rwtj| j tdd}||k ra|}||kritdS || 
 d }tt|S || 
 d }tt|S )a  Calculate overtime hours based on day type and time
        
        Weekdays (Mon-Fri): Only hours after 17:00 count
        Saturday: All hours worked count as overtime
        Sunday/Public Holidays: All hours count
        
        Handles overnight work (e.g., 17:01 to 02:00 next day)
        
        Returns:
            float: Total overtime hours (without rate multiplier)
        0r   )daysi  rN   r   r   )r   r   r   r   rO   r   r	   combine	timedeltar   total_secondsr_   r
   )r$   rO   startendrU   cutoffr   r   r   calculate_overtime_hours*  s(   
 z(OvertimeRequest.calculate_overtime_hoursc              	   C   s   | j js| j dks|  rtdS tdS z| j jj}|| jW S  tj	t
fyD   | j dks8|  r>td Y S td Y S w )z<Determine overtime rate based on day type and company configrM   z2.0z1.5)r   r   r   rO   r   r   companyovertimeconfigrS   r<   DoesNotExistAttributeError)r$   configr   r   r   calculate_rateX  s   
zOvertimeRequest.calculate_ratec              	   C   s   | j r| j jstdS z| j jj}|j| j j| j| j| jd}W n" t	j
tfyB   | j jtd }|td }|| j | j }Y nw t|dS )u:  Calculate overtime payment amount using company-specific configuration.
        If no company config exists, falls back to standard formula:
        Amount = (Basic Pay / 22 workdays) / 8 hours × Overtime Hours × Rate
        
        Note: Amount is only calculated when Registrar approves the request.
        r   )rT   rU   rV   r   228rA   )r   rT   r   r   r   rh   rU   rV   r   r<   r   r   round)r$   r   amountrf   rg   r   r   r   rh   i  s    	


z OvertimeRequest.calculate_amountc                    s<   |    |  | _|  | _|  | _t j|i | d S r5   )	
full_cleanr   rU   r   rV   rh   r   r   save)r$   argskwargsr   r   r   r     s
   


zOvertimeRequest.savec                 C   s*   | j r| jrtt| j t| j dS dS )z$Calculate total hours including raterA   r   )rU   rV   r   r]   r#   r   r   r   total_hours  s   *zOvertimeRequest.total_hoursc                 C   s    ddddddd}| | jdS )z,Return Bootstrap color class based on statuswarningprimarydangersuccess	secondary)r~   r   r   r   r   r   )getrx   )r$   colorsr   r   r   get_status_color  s   z OvertimeRequest.get_status_colorc                 C   s   | j  d| j d| jddS )Nrv   r   z.2fz hours))r   r   rU   r#   r   r   r   r%     s   zOvertimeRequest.__str__c                   @   s   e Zd ZddgZddgZdS )zOvertimeRequest.Metaz-datez-created_at)can_approve_overtimezCan approve overtime requests)can_process_paymentzCan process overtime paymentsN)r   r   r   r   permissionsr   r   r   r   r     s
    r   )6r   r   r   STATUS_CHOICESPAYMENT_STATUSr   r9   r   r:   r   r7   r   r   r   	TimeFieldr   r   rl   rU   rV   rn   reasonr6   rx   payment_statuspayment_dater   	FileFieldsupporting_document
hr_commentr}   hr_approved_atr|   hr_approved_byregistrar_commentregistrar_approved_atregistrar_approved_byfinance_commentfinance_processed_atfinance_processed_by
created_at
updated_atr   r   r   r   rh   r   propertyr   r   r%   r   __classcell__r   r   r   r   rp      s\    
6.
rp   N)	django.dbr   django.utilsr   django.core.validatorsr   django.core.exceptionsr   accounts.modelsr   r   decimalr   r	   r
   r   Modelr   r<   ro   rp   r   r   r   r   <module>   s    -r