File size: 5,178 Bytes
911e744
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7a4162b
 
 
911e744
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7a4162b
911e744
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7a4162b
911e744
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# src/payslip/service.py
from datetime import datetime, date

from fastapi import HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select

from src.payslip.models import PayslipRequest, PayslipStatus
from src.core.models import Users, Roles, UserTeamsRole, Teams
from src.payslip.schemas import PayslipRequestSchema
from src.payslip.utils import calculate_period, validate_join_date
from src.payslip.googleservice import (
    refresh_google_access_token,
    build_email,
    send_gmail,
)

from src.payslip.utils import decrypt_token
from src.payslip.utils import encrypt_token


async def user_team_name(session: AsyncSession, user_id):
    """Return user's team name."""
    q = select(UserTeamsRole).where(UserTeamsRole.user_id == user_id)
    mapping = (await session.execute(q)).scalar_one_or_none()

    if not mapping:
        return "Unknown Team"

    team = await session.get(Teams, mapping.team_id)
    return team.name if team else "Unknown Team"


async def one_request_per_day(session: AsyncSession, user_id):
    """
    Enforce: one payslip REQUEST per calendar day.
    We count only rows where status != PENDING (i.e., actual requests),
    so that the Gmail-connect row (status=PENDING) does NOT block.
    """
    today_start = datetime.combine(date.today(), datetime.min.time())

    q = select(PayslipRequest).where(
        PayslipRequest.user_id == user_id,
        PayslipRequest.requested_at >= today_start,
        PayslipRequest.status != PayslipStatus.PENDING,
    )

    result = await session.execute(q)
    if result.scalar_one_or_none():
        raise HTTPException(400, "You already sent a payslip request today.")


async def get_hr_email(session: AsyncSession):
    q = select(Roles).where(Roles.name == "HR Manager")
    role = (await session.execute(q)).scalar_one_or_none()
    if not role:
        raise HTTPException(500, "HR role missing")

    q2 = select(UserTeamsRole).where(UserTeamsRole.role_id == role.id)
    mapping = (await session.execute(q2)).scalar_one_or_none()

    if not mapping:
        raise HTTPException(500, "No HR manager mapped")

    hr = await session.get(Users, mapping.user_id)
    return hr.email_id


async def get_latest_payslip_row(session: AsyncSession, user_id):
    """
    Get the most recent payslip row for this user (any status).
    We use this to get the refresh_token and to decide whether to update or insert.
    """
    q = (
        select(PayslipRequest)
        .where(PayslipRequest.user_id == user_id)
        .order_by(PayslipRequest.requested_at.desc())
    )
    return (await session.execute(q)).scalar_one_or_none()


async def process_payslip_request(
    session: AsyncSession, user: Users, payload: PayslipRequestSchema
):
    # 1. Only ONE request per day (for actual payslip sends)
    await one_request_per_day(session, user.id)

    # 2. Validate period based on mode + months
    period_start, period_end = calculate_period(
        payload.mode,
        payload.start_month,
        payload.end_month,
    )

    # 3. Validate join date
    validate_join_date(user.join_date, period_start)

    # 4. Get refresh_token from latest payslip row (DB)
    latest = await get_latest_payslip_row(session, user.id)

    refresh_token = decrypt_token(latest.refresh_token) if latest else None

    if not refresh_token:
        # No token stored yet
        raise HTTPException(
            400, "Please connect your Gmail account before requesting payslip."
        )

    # 5. Refresh access token with Google
    access_token = refresh_google_access_token(refresh_token)

    # 6. Get HR email
    hr_email = await get_hr_email(session)

    # 7. Get team name
    team = await user_team_name(session, user.id)

    # 8. Build email body
    subject = "Payslip Request"
    body = (
        f"Payslip request from {user.user_name} ({user.email_id})\n"
        f"Team: {team}\n"
        f"Period: {period_start}{period_end}\n"
    )

    raw = build_email(user.email_id, hr_email, subject, body)

    # 9. Send email via Gmail API
    send_gmail(access_token, raw)

    # 10. Decide whether to UPDATE existing row or CREATE a new one
    now = datetime.now()

    if latest and latest.status == PayslipStatus.PENDING:
        # This is the "connection row" (created when Gmail was connected)
        # ✅ Update this row with today's request info
        latest.status = PayslipStatus.SENT
        latest.requested_at = now
        latest.error_message = None
        latest.refresh_token = encrypt_token(refresh_token)  # keep token
        session.add(latest)
        await session.commit()
        await session.refresh(latest)
        return latest
    else:
        # Either no row existed, or latest is already SENT/FAILED.
        # ✅ Create a new row for this request, copying the refresh token.
        entry = PayslipRequest(
            user_id=user.id,
            status=PayslipStatus.SENT,
            requested_at=now,
            refresh_token=refresh_token,
            error_message=None,
        )

        session.add(entry)
        await session.commit()
        await session.refresh(entry)
        return entry