Hp137 commited on
Commit
9797175
·
1 Parent(s): 41be8d4

feat:added cancel service

Browse files
src/notifications/router.py CHANGED
@@ -1,3 +1,4 @@
 
1
  from requests.api import delete
2
  from src.notifications.schemas import RegisterDeviceRequest
3
  from src.notifications.service import register_device
@@ -5,6 +6,9 @@ from fastapi import APIRouter, Depends
5
  from src.auth.utils import get_current_user
6
  from src.core.database import get_async_session
7
  from sqlalchemy.ext.asyncio import AsyncSession
 
 
 
8
 
9
  router = APIRouter(prefix="/notifications", tags=["Notifications"])
10
 
@@ -19,3 +23,42 @@ async def register_device_route(
19
  return {"message": "Device registered", "device": str(device.id)}
20
 
21
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from src.profile.models import Leave
2
  from requests.api import delete
3
  from src.notifications.schemas import RegisterDeviceRequest
4
  from src.notifications.service import register_device
 
6
  from src.auth.utils import get_current_user
7
  from src.core.database import get_async_session
8
  from sqlalchemy.ext.asyncio import AsyncSession
9
+ from fastapi import APIRouter, Depends, HTTPException
10
+ from sqlalchemy import text
11
+ import uuid
12
 
13
  router = APIRouter(prefix="/notifications", tags=["Notifications"])
14
 
 
23
  return {"message": "Device registered", "device": str(device.id)}
24
 
25
 
26
+ @router.post("/{notif_id}/mark-read")
27
+ async def mark_notification_read(
28
+ notif_id: str,
29
+ session: AsyncSession = Depends(get_async_session),
30
+ user_id: str = Depends(get_current_user),
31
+ ):
32
+ notif = await session.get(Leave, uuid.UUID(notif_id))
33
+
34
+ if not notif:
35
+ raise HTTPException(404, "Notification not found")
36
+
37
+ # Only owner or mentor should mark
38
+ if str(notif.user_id) != user_id and str(notif.mentor_id) != user_id:
39
+ raise HTTPException(403, "Unauthorized")
40
+
41
+ notif.is_read = True
42
+ await session.commit()
43
+
44
+ return {"message": "Marked as read"}
45
+
46
+
47
+ @router.post("/mark-all-read")
48
+ async def mark_all_read(
49
+ session: AsyncSession = Depends(get_async_session),
50
+ user_id: str = Depends(get_current_user),
51
+ ):
52
+ await session.execute(
53
+ text(
54
+ """
55
+ UPDATE leave
56
+ SET is_read = TRUE
57
+ WHERE user_id = :uid OR mentor_id = :uid
58
+ """
59
+ ),
60
+ {"uid": user_id},
61
+ )
62
+ await session.commit()
63
+
64
+ return {"message": "All notifications cleared"}
src/profile/models.py CHANGED
@@ -7,17 +7,20 @@ from typing import List, Optional
7
 
8
  from sqlmodel import Field, Relationship, SQLModel
9
 
 
10
  class LeaveType(str, Enum):
11
  SICK = "Sick"
12
  CASUAL = "Casual"
13
  EMERGENCY = "Emergency"
14
 
 
15
  class LeaveStatus(str, Enum):
16
  APPROVED = "Approved"
17
  REJECTED = "Rejected"
18
  CANCELLED = "Cancelled"
19
  PENDING = "Pending"
20
 
 
21
  class Leave(SQLModel, table=True):
22
  __tablename__ = "leave"
23
  id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
@@ -30,12 +33,13 @@ class Leave(SQLModel, table=True):
30
  days: Optional[int] = 1
31
  reason: str = Field(nullable=True)
32
  status: LeaveStatus = Field(default=LeaveStatus.PENDING)
33
- is_delivered: bool = Field(default= False)
34
  is_read: bool = Field(default=False)
35
  requested_at: date = Field(default_factory=date.today)
36
  updated_at: date = Field(default_factory=date.today)
37
  reject_reason: Optional[str] = None
38
 
 
39
  class UserDevices(SQLModel, table=True):
40
  __tablename__ = "user_devices"
41
  id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
@@ -44,4 +48,4 @@ class UserDevices(SQLModel, table=True):
44
  last_seen: datetime = Field(default_factory=datetime.now)
45
  device_model: str
46
  platform: str
47
- updated_at: datetime = Field(default_factory=datetime.now)
 
7
 
8
  from sqlmodel import Field, Relationship, SQLModel
9
 
10
+
11
  class LeaveType(str, Enum):
12
  SICK = "Sick"
13
  CASUAL = "Casual"
14
  EMERGENCY = "Emergency"
15
 
16
+
17
  class LeaveStatus(str, Enum):
18
  APPROVED = "Approved"
19
  REJECTED = "Rejected"
20
  CANCELLED = "Cancelled"
21
  PENDING = "Pending"
22
 
23
+
24
  class Leave(SQLModel, table=True):
25
  __tablename__ = "leave"
26
  id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
 
33
  days: Optional[int] = 1
34
  reason: str = Field(nullable=True)
35
  status: LeaveStatus = Field(default=LeaveStatus.PENDING)
36
+ is_delivered: bool = Field(default=False)
37
  is_read: bool = Field(default=False)
38
  requested_at: date = Field(default_factory=date.today)
39
  updated_at: date = Field(default_factory=date.today)
40
  reject_reason: Optional[str] = None
41
 
42
+
43
  class UserDevices(SQLModel, table=True):
44
  __tablename__ = "user_devices"
45
  id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
 
48
  last_seen: datetime = Field(default_factory=datetime.now)
49
  device_model: str
50
  platform: str
51
+ updated_at: datetime = Field(default_factory=datetime.now)
src/profile/router.py CHANGED
@@ -1,6 +1,8 @@
1
  from src.profile.schemas import LeaveDetailResponse
2
  from src.core.database import get_async_session
3
  from src.auth.utils import get_current_user
 
 
4
  from src.profile.models import Leave, LeaveType, LeaveStatus
5
  from src.auth.schemas import BaseResponse
6
  from sqlalchemy import desc
@@ -21,8 +23,8 @@ from src.profile.schemas import (
21
  CreateLeaveRequest,
22
  LeaveResponse,
23
  ApproveRejectRequest,
24
- LeaveStatus,
25
  )
 
26
  from src.profile.service import create_leave, mentor_decide_leave
27
 
28
 
@@ -158,7 +160,6 @@ async def list_notifications(
158
  session: AsyncSession = Depends(get_async_session),
159
  user_id: str = Depends(get_current_user),
160
  ):
161
- from src.profile.models import Leave
162
 
163
  stmt = (
164
  select(Leave)
@@ -205,6 +206,7 @@ async def list_notifications(
205
  "to_date": str(leave.to_date),
206
  "reject_reason": leave.reject_reason,
207
  "reason": leave.reason,
 
208
  }
209
  )
210
 
@@ -484,3 +486,69 @@ async def get_profile_details(
484
  "mentor_email": ", ".join(mentor_emails),
485
  },
486
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  from src.profile.schemas import LeaveDetailResponse
2
  from src.core.database import get_async_session
3
  from src.auth.utils import get_current_user
4
+ from src.notifications.service import get_user_device_tokens
5
+ from src.notifications.fcm import send_fcm
6
  from src.profile.models import Leave, LeaveType, LeaveStatus
7
  from src.auth.schemas import BaseResponse
8
  from sqlalchemy import desc
 
23
  CreateLeaveRequest,
24
  LeaveResponse,
25
  ApproveRejectRequest,
 
26
  )
27
+ from datetime import datetime
28
  from src.profile.service import create_leave, mentor_decide_leave
29
 
30
 
 
160
  session: AsyncSession = Depends(get_async_session),
161
  user_id: str = Depends(get_current_user),
162
  ):
 
163
 
164
  stmt = (
165
  select(Leave)
 
206
  "to_date": str(leave.to_date),
207
  "reject_reason": leave.reject_reason,
208
  "reason": leave.reason,
209
+ "is_read": leave.is_read,
210
  }
211
  )
212
 
 
486
  "mentor_email": ", ".join(mentor_emails),
487
  },
488
  )
489
+
490
+
491
+ @router.post("/leave/{leave_id}/cancel")
492
+ async def cancel_leave(
493
+ leave_id: str,
494
+ session: AsyncSession = Depends(get_async_session),
495
+ user_id: str = Depends(get_current_user),
496
+ ):
497
+ leave = await session.get(Leave, uuid.UUID(leave_id))
498
+
499
+ if not leave:
500
+ raise HTTPException(status_code=404, detail="Leave not found")
501
+
502
+ # User must own this leave
503
+ if str(leave.user_id) != user_id:
504
+ raise HTTPException(status_code=403, detail="Not your leave")
505
+
506
+ # Only approved leaves can be cancelled
507
+ if leave.status not in [LeaveStatus.APPROVED, LeaveStatus.PENDING]:
508
+ raise HTTPException(
509
+ status_code=400,
510
+ detail="Only pending or approved leaves can be cancelled",
511
+ )
512
+
513
+ # Check if leave date is future
514
+ from datetime import date
515
+
516
+ if leave.from_date <= date.today():
517
+ raise HTTPException(
518
+ status_code=400,
519
+ detail="Past or current day leaves cannot be cancelled",
520
+ )
521
+
522
+ # Update leave status
523
+ leave.status = LeaveStatus.CANCELLED
524
+ leave.updated_at = datetime.utcnow()
525
+ await session.commit()
526
+
527
+ user = await session.get(Users, leave.user_id)
528
+
529
+ # Notify mentor
530
+ mentor_tokens = await get_user_device_tokens(session, leave.mentor_id)
531
+ await send_fcm(
532
+ mentor_tokens,
533
+ "Leave Cancelled",
534
+ f"{user.user_name} cancelled their approved leave.",
535
+ {"type": "leave_cancel", "leave_id": str(leave.id)},
536
+ )
537
+
538
+ # Notify Team Lead
539
+ lead_tokens = await get_user_device_tokens(session, leave.lead_id)
540
+ await send_fcm(
541
+ lead_tokens,
542
+ "Leave Cancelled",
543
+ f"User {user.user_name} cancelled their approved leave.",
544
+ {"type": "leave_cancel", "leave_id": str(leave.id)},
545
+ )
546
+
547
+ return {
548
+ "code": 200,
549
+ "message": "Leave cancelled successfully",
550
+ "data": {
551
+ "id": str(leave.id),
552
+ "status": leave.status,
553
+ },
554
+ }
src/profile/schemas.py CHANGED
@@ -1,3 +1,4 @@
 
1
  from pydantic import BaseModel, EmailStr, Field
2
  from typing import Optional, List
3
  import uuid
@@ -16,6 +17,7 @@ class LeaveStatus(str, Enum):
16
  APPROVED = "Approved"
17
  REJECTED = "Rejected"
18
  PENDING = "Pending"
 
19
 
20
 
21
  class CreateLeaveRequest(BaseModel):
 
1
+ from sqlalchemy.event.api import CANCEL
2
  from pydantic import BaseModel, EmailStr, Field
3
  from typing import Optional, List
4
  import uuid
 
17
  APPROVED = "Approved"
18
  REJECTED = "Rejected"
19
  PENDING = "Pending"
20
+ CANCELLED = "Cancelled"
21
 
22
 
23
  class CreateLeaveRequest(BaseModel):