
    {jd                         d dl Z d dlmZmZ d dlm Z d dlmZ d dlmZmZm	Z	 d dl
mZ d dlmZ dZd	d
dddddZdZd ZdZdZdddddZdZdZd Zd Zd Zd Zd Zd Zd  Zd! Zd" Z  G d# d$e	jB                        Z"y)%    N)datetime	timedelta)time)relativedelta)_apimodelshtml_escape)hmac)confirm	validate1validatez#7659ffz#ff9e2ez#fb919ez#fd6dbfz#9f9f9fz#0e7490)ALPLSLMLULBTc                     | st         S | j                  d      }|dk(  r| j                         n| |dz   d  j                         }t        j	                  |t               S )N.   )_DEFAULT_LEAVE_TYPE_FONT_COLORrfindstrip_LEAVE_TYPE_FONT_COLORSget)display_nameidxabbrs       xeno_leave/models/leave_email.py_leave_type_font_colorr#      sY    --


S
!C#&"9<,sQwx2H2N2N2PD"&&t-KLL    xeno_leave_email_actioni u zFull-day LeavezPart-time LeavezFirst-half Day LeavezSecond-half Day Leave)full_day	part_time
first_halfsecond_halfa  
<div style="max-width:600px;margin:0 auto;font-family:Arial,Helvetica,sans-serif;color:#142933;border:1px solid #d8e1e4;">
  <div style="background:#142933;padding:14px 20px;">
    <div style="color:#fff;">
      <div style="font-weight:700;font-size:16px;">XenOptics HR System</div>
      <div style="font-size:12px;color:#b7c7cc;">Employee Leave Request</div>
    </div>
  </div>
  <div style="padding:20px;">
    <div style="display:flex;justify-content:space-between;align-items:baseline;margin-bottom:10px;">
      <h2 style="margin:0;font-size:20px;">Employee Leave Request</h2>
      <a href="%(see_more)s" style="background:#ec1d23;color:#fff;padding:5px 12px;border-radius:14px;font-size:11px;text-decoration:none;">See More</a>
    </div>
    <div style="font-size:13px;margin-bottom:10px;"><strong>Employee Leave Request (%(status)s)</strong></div>
    %(rows_before)s
    %(table)s
    %(rows_after)s
    %(history)s
    %(analysis)s
z
  </div>
  <div style="background:#142933;color:#b7c7cc;text-align:center;padding:8px;font-size:11px;">
    XenOptics HR &middot; Do not reply
  </div>
</div>
c                 (    t        | |       S d      S )N r
   ss    r"   _escr.   L   s    AMq22r22r$   c                     d| d|dS )Nz2<div style="margin:6px 0;font-size:13px;"><strong>:</strong> </div> )labelvalues     r"   	_row_pairr5   P   s    X]_deer$   c                 H    | sy| t        d      z   }|j                  d      S )zdecided_at is stored as naive UTC (Odoo convention) -- shift to
    Asia/Bangkok (UTC+7) for display, matching susu's own template.r+      hoursz%b %d, %Y %I:%M %p)r   strftime)
decided_atlocals     r"   _format_decided_atr=   T   s)     ++E>>.//r$   c                 P    | sy| j                   j                  xs | j                  S )aR  The approver's real name for the email, not their login/username --
    decided_by is a res.users record, so plain .name gives the login-based
    display name (e.g. "Susu") rather than the linked hr.employee's actual
    full name (e.g. "Nyein Su Su"). Falls back to the user's own name for
    an account with no linked employee record.zAny HR/Admin)employee_idname)
decided_bys    r"   _approver_display_namerB   ]   s%     !!&&9*//9r$   c                 V    |j                   fd      j                  d      }|sy|D cg c]Y  }dt        |j                        dt        t	        |j
                              dt        t        |j                              d[ }}t        dj                  |            S c c}w )zSteps already approved before the one this email is about, so an
    approver further down the chain can see who signed off before them and
    when -- only shown once there IS prior history (never on the very
    first step's own notification).c                 X    | j                   dk(  xr | j                  j                  k  S )Napproved)state
step_order)r-   current_steps    r"   <lambda>z*_render_approval_history.<locals>.<lambda>n   s$    !''Z'RALL<;R;R,R r$   rG   r+   *<div style="margin:4px 0;font-size:13px;"> (z) approved at .</div>)
filteredsortedr.   r3   rB   rA   r=   r;   _approval_blockjoin)rH   	all_stepsprior_approvedr-   liness   `    r"   _render_approval_historyrT   h   s    
 ''Rf\    	  =$5allCD"1<<01	3E  2775>**s   AB&c                 |   | j                  d       j                  d      }|sy|D cg c]o  }dt        |j                        dt        t	        |j
                              dt        |j                        dt        t        |j                              d	q }}t        dj                  |            S c c}w )	a  Every decided step's outcome, for the requestor's final approved/
    rejected notification -- unlike _render_approval_history (only prior
    steps, always "approved", sent mid-chain), this covers the whole chain
    since the leave is now fully decided one way or the other.c                     | j                   dv S )N)rE   rejected)rF   r,   s    r"   rI   z0_render_final_approval_summary.<locals>.<lambda>   s    1776N+N r$   rG   r+   rJ   rK   z	) status(z) at rL   )rM   rN   r.   r3   rB   rA   rF   r=   r;   rO   rP   )rQ   decidedr-   rS   s       r"   _render_final_approval_summaryrY   |   s    
   !NOVVWcdG 	  =$5allCD=$1!,,?@	BE  2775>**s   A4B9c                     d| z  S )Nzs<div style="margin-top:16px;"><div style="font-weight:700;font-size:14px;margin-bottom:6px;">Approval</div>%s</div>r2   )
lines_htmls    r"   rO   rO      s    	 	 r$   c           
      \    | rdnd}| rdnd}d|d|dt        |      dt        |      d		S )
Nz#d97706z#16a34au   ⚠u   ✓z=<div style="margin:4px 0;font-size:13px;"><span style="color:;font-weight:700;">z</span> <strong>r0   r1   )r.   )flaggedr3   messagecoloricons        r"   _analysis_rowrb      s1     IiE5D 
d5k4=2 2r$   c                     | syd| z  S )Nr+   zs<div style="margin-top:16px;"><div style="font-weight:700;font-size:14px;margin-bottom:6px;">Analysis</div>%s</div>r2   )	rows_htmls    r"   _analysis_blockre      s    	 	 r$   c                       e Zd ZdZej
                  d        Zej
                  d        Zej
                  d        ZddZ	d Z
d Zd	 Zdd
ZddZd Zd Zd Zd Zy)HrLeavezhr.leavec                 R    |||||f}t        | j                  d      t        |      S )NT)su)	odoo_hmacenvTOKEN_SCOPE)selfleave_idstep_idactionuser_idexpr_   s          r"   _xeno_email_tokenzHrLeave._xeno_email_token   s,    Wfgs;T*KAAr$   c                     |rt        j                          |kD  ry| j                  |||||      }| j                  ||      S )an  Constant-time check of an email action link. Deliberately NOT the
        same as XENHR's approveStepEmail (confirmed unauthenticated -- any
        link works for anyone): the token is bound to this exact leave,
        step, action and intended user, and expires, so it can't be
        replayed against a different record or reused past its window.
        Real authorization (is this user actually allowed to act on this
        step right now) still runs through the normal action_approve/
        action_refuse permission checks -- this only proves the link is
        genuine and hasn't been tampered with.
        F)r   rs   _xeno_hmac_compare)rm   rn   ro   rp   rq   rr   tokenexpecteds           r"   _xeno_verify_email_tokenz HrLeave._xeno_verify_email_token   sB     		c)))(GVWcR&&x77r$   c                 .    dd l }|j                  ||      S )Nr   )r   compare_digest)rm   ab	_hmac_libs       r"   ru   zHrLeave._xeno_hmac_compare   s     ''1--r$   Nc                 p   | j                          t        t        j                               t        z   }| j	                  | j
                  |j
                  |||      }|xs, | j                  d   j                         j                  d      }|d| j
                  d|j
                  d|d|d|d|S )	Nir.config_parameterweb.base.urlz"/odoo/leave/email_action?leave_id=z	&step_id=z&action=z	&user_id=z&exp=z&token=)	
ensure_oneintr   TOKEN_VALID_SECONDSrs   idrk   sudo	get_param)rm   steprp   rq   base_urlrr   rv   s          r"   _xeno_email_action_urlzHrLeave._xeno_email_action_url   s    $))+!44&&tww#N_txx(=>CCEOOP^_dggtwweE 	Er$   c                 L   | j                          | j                  st        t        d            S | j                  t	        d      z
  }| j                         j                  dd| j                  j                  fddd| j                  fd	d
|fd	d| j                  fdgd      }|st        t        d            S g }|D ]  }t        |j                        }|j                  r+|j                  |j                  k7  r|d|j                  z  z  }|j                  j                  }t        |      }|j                  d|dt        |      dt        |      d|j                  dt        |j                         j                  xs d      
        ddj!                  |      z   S )a!  Every OTHER approved leave for this employee within one month
        before this request's start date, each on its own line with its
        date range, duration and reason -- gives the approver a quick
        read on how often/recently this employee has already been out,
        not just the single most recent instance (the previous design).
        Returns pre-escaped safe HTML (each dynamic piece is escaped
        individually, then joined with a real <br/>) -- callers must NOT
        re-escape this like the other plain-text fields.Noner   )monthsr?   =)rF   r   r   r   z!=request_date_from>=<)xeno_is_system_deductionr   Fzrequest_date_from desc)orderu    – %sz<span style="color:r]   z	</span>: rK   z
 day(s)): r+   z<br/>)r   r   r.   r   r   r   searchr?   r   strrequest_date_toholiday_status_idr@   r#   appendnumber_of_daysrP   )rm   window_startpriorrS   leave
date_range	type_namer`   s           r"   _xeno_recent_leave_notezHrLeave._xeno_recent_leave_note   s    	%%&	?"--Q0GG		""C!1!1!4!45&4! $5 #t'='=> 5
$
 * # 
+ &	?" 		3EU445J$$)>)>%BYBY)Yi%*?*???
//44I*95ELL$y/4
+;U=Q=Q

))/R023		3 e,,,r$   c                 n   | j                          | j                         }|j                  s|j                  sy|j                  }|j                  xs |}|j
                  xs d}|j                  xs d}|j                  xs d}|j                  xs d}|j                  xs d}|j                  d      }	g }
|j                  d      }|t        dd      z   }|j                  dd	|j                  j                  fd
dt!        t"              fdd|fdd|fg      }|
j%                  t'        |dk\  dd||	|j(                  fz               |t+        d      z
  }|j                  dd	|j                  j                  fd
dt!        t"              fdd|fdd|fg      }|
j%                  t'        |dk\  dd|z               |dkD  rP|dz  }||j,                  z  }||z   }||k  rdnd}|
j%                  t'        |dkD  xr ||kD  dd|	|||fz               n|
j%                  t'        ddd             |dkD  r>||z
  }|
j%                  t'        |dk  dd ||j.                  j0                  fz               n|
j%                  t'        ddd!             |t+        d      z
  }|t+        d      z   }| j2                  d"   j                         j5                  d#d$dt7        j8                  |t:        j<                        fd%dt7        j8                  |t:        j>                        fg      }|D ch c])  }|j@                  t+        d&'      z   jC                         + }}d|cxk  xr dk  nc }|xr
 ||v xs ||v }|
j%                  t'        |d(|rd)nd*             tE        djG                  |
            S c c}w )+a  Same 5-check advisory "Analysis" list shown in the Leave
        Approval decision dialog and the leave form's own HR/Admin-only
        sidebar widget (leave_analysis.js / leave_analysis_widget.js),
        ported to Python for the approver's action email -- kept in sync
        by hand since an email can't import a browser-side JS module.
        Thresholds (3/month, 4 in 90 days, 1.5x pace, <1 day remaining)
        match the web version exactly. Skipped entirely for a
        system-driven deduction (xeno_is_system_deduction), same as the
        two web-side call sites -- HR/the system chose the employee/type/
        days directly, so these self-service checks don't apply.
        r+           z%Br   )dayr   )r   daysr?   r   rF   inr   r   z<=   zMonthly frequencyz2%d leave request(s) in %s %d (including this one).Z   )r      zRecent request frequencyz:%d leave request(s) in the 90 days leading up to this one.r   g      (@
SufficientInsufficientzProrated paceuA   Prorated through %s: %.2f days · Used + Pending: %.2f days · %sFzFNo fixed yearly allocation for this leave type -- pace not applicable.zRemaining after approvalz<Approving this leaves %.2f day(s) of %s remaining this year.zINo fixed yearly allocation for this leave type -- balance not applicable.zresource.calendar.leaves)calendar_idr   F	date_fromdate_tor7   r8   zHoliday-adjacent patternzLThis request falls immediately next to a public holiday (possible bridging).z!Not adjacent to a public holiday.)$r   r   r   r   r   xeno_balance_totalxeno_balance_usedxeno_balance_pendingxeno_balance_remainingr   r:   replacer   search_countr?   r   list_ANALYSIS_ACTIVE_STATESr   rb   yearr   monthr   r@   rk   r   r   combinedt_timemaxminr   datere   rP   )rm   Lr   r   totalusedpending	remaining	requested
month_namerowsmonth_start	month_endmonth_countr   rolling_countmonthly_raterw   actualverdictafter
day_before	day_afterholidayshholiday_datesis_shortbridgess                               r"   _xeno_render_analysis_htmlz"HrLeave._xeno_render_analysis_html   s    	IIK%%Q-@-@''	##0y$$+"")c((/C,,3	$$+	''-
  ''A'.-qr"BB	nnC!1!12dD!89: $4 $	2	&
  	M11@ZD9 9: 	; !9"#55C!1!12dD!89: $5 $	2	(
  	MQ :H=XZ 	[ 19 4<L#ioo5HG^F&,&8lnGKK12(!2OS&'W; ;< =
 KKXZ [
 19	)EKK	5N1..33R5 56 7
 KK1[] ^ !22
iQ//	8867<<>EE'$ 0 0GKK HIh..z7;;GHG
 
 MUUq!++	(::@@BUUy%A%Z
m ; YyM?YM/ [?A 	B
 rwwt}-- Vs   *.N2c                     |j                   r|j                   S d}| j                  d   j                         j                  dd| j                  j	                  |      j
                  gfg      S )zSpecific approver if the step names one; otherwise every Time Off
        Officer (a blank approver_user_id means "any HR/Admin may act"), so
        each of them gets their own personalized, individually-tokened link.
        z"hr_holidays.group_hr_holidays_userz	res.usersall_group_idsr   )approver_user_idrk   r   r   refr   )rm   r   HR_OFFICER_GROUPs      r"   _xeno_step_email_recipientsz#HrLeave._xeno_step_email_recipientsf  sq    
   (((? xx$))+22_dHHLL)*--M
 5 4  	r$   c                 f   | j                          | j                         }|j                  }|j                  r t        j                  |j                  d      nd}|j                  dk(  xr |j                  }|r|j                  nd}|r|j                  nd}t        dt        |j                         j                  xs d      dt        |j                              t        dt        |j                  j                              g}ddd	d
|j                  z  d
|j                  z  d
|j                   z  dz  }	g }
|r$|
j#                  t        dt        |                   |
j#                  t        dd|j$                  z               |
j#                  dt        t'        |j(                  xs d            dt        t'        |j*                  xs d            d       |r>|
j#                  d|j-                  |xs d      d|j-                  |xs d      d       |
j#                  t        dt        |j                         j                  xs d                   |r)|
j#                  t        d|j/                                      dj1                  |      |	dj1                  |
      dS )aR  Shared field block (employee/purpose/balance table/basis/total/
        dates/reason) used by both the pending-action email and the
        requestor's final-outcome email. include_recent_leave is only
        meaningful to an approver deciding whether to approve (susu's
        reference mockup for the final requestor notice omits it).r+   r'   Nu5   พนักงานที่ขอลา (Employee)z . uL   ความประสงค์ของการลา (Purpose of leave)u  
        <table role="presentation" width="100%%" cellspacing="0" cellpadding="0"
               style="border-collapse:collapse;margin:14px 0;font-size:13px;">
          <tr>
            <th style="%(th)s">Leave entitled<br/>สิทธิ์ตามระเบียบบริษัทฯ</th>
            <th style="%(th)s">Accumulate of this year<br/>ยอดใช้ไปสะสมในรอบปี</th>
            <th style="%(th)s">Remaining<br/>สิทธิ์ที่เหลือ</th>
          </tr>
          <tr>
            <td style="%(td)s">%(allocated)s</td>
            <td style="%(td)s">%(used)s</td>
            <td style="%(td)s">%(remaining)s</td>
          </tr>
        </table>
        zwbackground:#e7edef;color:#3f5964;font-weight:600;padding:8px;border:1px solid #d8e1e4;text-align:center;font-size:11px;zYpadding:8px;border:1px solid #d8e1e4;text-align:center;font-variant-numeric:tabular-nums;z%.2f)thtd	allocatedr   r   u0   ชนิดการลา (Base Type of leave)u&   จำนวนวันลา (Total)z	%s day(s)uq   <div style="margin:6px 0;font-size:13px;"><strong>วันที่เริ่มลา (Start date):</strong> uT   &nbsp;&nbsp;&nbsp;<strong>วันสิ้นสุดลา (End date):</strong> r1   u_   <div style="margin:6px 0;font-size:13px;"><strong>จากเวลา (Start time):</strong> r   uH   &nbsp;&nbsp;&nbsp;<strong>ถึงเวลา (Ending time):</strong> u0   เหตุผลในการลา (Reason)zRecent Leave Date)rows_beforetable
rows_after)r   r   r?   xeno_flexible_duration_BASE_TYPE_LABELSr   xeno_request_unitrequest_hour_fromrequest_hour_tor5   r.   xeno_employee_coder@   r   r   r   r   r   r   r   r   r   _xeno_format_hourr   rP   )rm   include_recent_leaver   empbasis_labelis_part_timepart_time_startpart_time_endr   r   r   s              r"   _xeno_leave_email_fieldsz HrLeave._xeno_leave_email_fieldsx  sq    	IIKmmHIH`H`'++A,?,?Dfh**k9Va>V>V1=!--4-9))t G!#((*"?"?"E2FSXXWY ^Q((--.0	
 Lm!"6"66Q000!":"::

, 
i(Z\`al\mno)$Lk\]\l\lNlmn C++1r23T#a>O>O>USU:V5WY	Z  &&'=#>@S@STaTheh@ikl 	)$VX\]^]c]c]e]j]j]pnpXqrs i(;Q=V=V=XYZ 77;/''*-
 	
r$   c                     | j                          |xs, | j                  d   j                         j                  d      }| j                  j	                  d      j
                  }|d|d| j
                  S )zDeep-links straight to this leave's own record (My Team Leaves
        action + this leave's id), so both the requestor and the approver
        land directly on the request itself instead of a generic list
        they'd then have to search through.r   r   z%xeno_leave.action_xeno_my_team_leavesz/odoo/action-/)r   rk   r   r   r   r   )rm   r   	action_ids      r"   _xeno_see_more_urlzHrLeave._xeno_see_more_url  sa    
 	_txx(=>CCEOOP^_HHLL!HILL	)19dggFFr$   c                    | j                          | j                         }| j                  d   j                         j                  d      }|j	                  |      }|j                  |d|j                  |      }|j                  |d|j                  |      }|j                         }t        |j                  d   j                        j                  |j                  |j                        }	t        ||j                        }
|j                         }t         |t#        |	      |
|d|z  d||dz  z   t$        z   }|S )	Nr   r   approverejectrF   see_morestatushistoryanalysisu  
    <hr style="border:none;border-top:1px solid #d8e1e4;margin:16px 0;"/>
    <div style="text-align:center;font-size:13px;margin-bottom:14px;">
      ท่านมีใบลาเพื่อรอการอนุมัติดังนี้
      (You have request for absent as following for approval)
    </div>
    <div style="text-align:center;">
      <a href="%(approve)s" style="display:inline-block;background:#dff2e6;color:#1f7a4d;font-weight:700;padding:8px 22px;border-radius:16px;text-decoration:none;margin:0 6px;">Approve</a>
      <a href="%(reject)s" style="display:inline-block;background:#fbdad9;color:#b21f22;font-weight:700;padding:8px 22px;border-radius:16px;text-decoration:none;margin:0 6px;">Reject</a>
    </div>
)r   r   )r   r   rk   r   r   r   r   r   dict_fields	selectionr   rF   rT   xeno_approval_step_idsr   _HEADERr.   _FOOTER)rm   r   userr   r   see_more_urlapprove_url
reject_urlfieldsstatus_labelhistory_htmlanalysis_htmlbodys                r"   _xeno_render_step_email_htmlz$HrLeave._xeno_render_step_email_html  s0   IIK8812779CCNS++H5..tYR--dHdggxP
++-AIIg.889==aggqwwO/a6N6NO446$<(#%	

 
 

 4
56  9@!@$ r$   c                    | j                          | j                         }| j                  d   j                         j                  d      }|j	                  |      }|j                  d      }t        |j                  d   j                        j                  |j                  |j                        }t        |j                        }t        |t        |      |dd|z  t        z   S )a  The requestor's final-outcome notification -- same layout as the
        approver's action email, but read-only (no Approve/Reject links)
        and the Approval section covers every decided step's outcome
        (approved/rejected), not just the ones before a given step.r   r   F)r   rF   r+   r   )r   r   rk   r   r   r   r   r   r   r   rF   rY   r   r   r.   r   )rm   r   r   r  r  r  r  s          r"   _xeno_render_final_notice_htmlz&HrLeave._xeno_render_final_notice_html  s    
 	IIK8812779CCNS++H5+++GAIIg.889==aggqwwO5a6N6NO$<(#
 

 

 

 
 
	r$   c                    | j                          | j                  |      }|D ]  }|j                  s| j                  ||      }| j                  d   j                         j                  d| j                  j                  d| j                  j                  d||j                  dd      j                           y)zEmail whoever can currently act on this step -- the specific
        approver, or every Time Off Officer for a blank (any-HR) step, each
        with their own individually-tokened Approve/Reject links.	mail.mailu   Employee Leave Request – rK   )Tsubject	body_htmlemail_toauto_deleteN)r   r   emailr	  rk   r   creater?   r@   r   send)rm   r   
recipientsr   htmls        r"   _xeno_send_step_emailzHrLeave._xeno_send_step_email  s     	55d;
 
	D::44T4@DHH[!&&(//$$))4+A+A+F+FH! JJ#1  tv
	r$   c           
         | D ]  }|j                   j                  }|r|j                  s(|j                         }|j                  dk(  rdnd}| j
                  d   j                         j                  d|d|j                   j                  d|j                  j                  d||j                  d	d
      j                           y)zrNotify the requestor once their leave is fully approved or
        rejected -- read-only summary, no action links.r   ApprovedRejectedr  zEmployee Leave Request u    – rK   r  Tr  N)r?   rq   r  r  rF   rk   r   r  r@   r   r  )rm   r   	requestorr  status_words        r"   _xeno_send_final_noticezHrLeave._xeno_send_final_notice  s      	E))11IIOO779D(-z(A*zKHH[!&&(//!2!2!7!79P9P9U9UW!%OO#1  tv	r$   )N)T)__name__
__module____qualname___inheritr   modelrs   rx   ru   r   r   r   r   r   r   r	  r  r  r  r2   r$   r"   rg   rg      s    HYYB B 	YY8 8  	YY. .E(-Tg.R$F
PGB4$r$   rg   )#r   r   r   r   dateutil.relativedeltar   odoor   r   r	   
odoo.toolsr   odoo.tools.miscr   rj   r   r   r   r#   rl   r   r   r   r   r.   r5   r=   rB   rT   rY   rO   rb   re   Modelrg   r2   r$   r"   <module>r*     s     ( $ 0   " ->  




  "+ M ($ 
 !"(*	 *3f0:+(+$2yfll yr$   