fix:Bug fixed NOTIF_04,09,20,21,22
Browse files- src/auth/service.py +2 -2
- src/data_add/seed_from_excel.py +43 -10
- src/data_add/users.xlsx +0 -0
- src/notifications/router.py +4 -1
- src/profile/notify.py +42 -17
- src/profile/router.py +37 -12
src/auth/service.py
CHANGED
|
@@ -17,7 +17,7 @@ from sqlmodel.ext.asyncio.session import AsyncSession
|
|
| 17 |
async def create_user(session: AsyncSession, name: str, email: str, password: str):
|
| 18 |
"""Create user without sending email"""
|
| 19 |
|
| 20 |
-
if not email.lower()
|
| 21 |
raise HTTPException(status_code=400, detail="Enter you're Yuvabe email ID")
|
| 22 |
|
| 23 |
user = await session.exec(select(Users).where(Users.email_id == email))
|
|
@@ -120,7 +120,7 @@ async def verify_email(session: Session, token: str):
|
|
| 120 |
|
| 121 |
async def login_user(session: Session, email: str, password: str):
|
| 122 |
|
| 123 |
-
if not email.lower()
|
| 124 |
raise HTTPException(status_code=400, detail="Enter you're Yuvabe email ID")
|
| 125 |
|
| 126 |
users = await session.exec(select(Users).where(Users.email_id == email))
|
|
|
|
| 17 |
async def create_user(session: AsyncSession, name: str, email: str, password: str):
|
| 18 |
"""Create user without sending email"""
|
| 19 |
|
| 20 |
+
if not email.lower():
|
| 21 |
raise HTTPException(status_code=400, detail="Enter you're Yuvabe email ID")
|
| 22 |
|
| 23 |
user = await session.exec(select(Users).where(Users.email_id == email))
|
|
|
|
| 120 |
|
| 121 |
async def login_user(session: Session, email: str, password: str):
|
| 122 |
|
| 123 |
+
if not email.lower():
|
| 124 |
raise HTTPException(status_code=400, detail="Enter you're Yuvabe email ID")
|
| 125 |
|
| 126 |
users = await session.exec(select(Users).where(Users.email_id == email))
|
src/data_add/seed_from_excel.py
CHANGED
|
@@ -46,13 +46,11 @@ def normalize_asset_id(value: str) -> str:
|
|
| 46 |
value = value.strip().replace(" ", "")
|
| 47 |
value = value.upper()
|
| 48 |
|
| 49 |
-
# Fix repeated hyphens
|
| 50 |
while "--" in value:
|
| 51 |
value = value.replace("--", "-")
|
| 52 |
|
| 53 |
-
# If looks like YB73M (missing hyphens)
|
| 54 |
if "-" not in value:
|
| 55 |
-
prefix = value[:2]
|
| 56 |
number = "".join(filter(str.isdigit, value))
|
| 57 |
suffix = value[len(prefix) + len(number) :]
|
| 58 |
return f"{prefix}-{number}-{suffix}"
|
|
@@ -109,6 +107,37 @@ def parse_assets_from_excel(raw_value):
|
|
| 109 |
return []
|
| 110 |
|
| 111 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 112 |
# ---------------------------------------------
|
| 113 |
# MAIN IMPORT
|
| 114 |
# ---------------------------------------------
|
|
@@ -131,15 +160,19 @@ async def seed_from_excel(session: AsyncSession, excel_path="src/data_add/users.
|
|
| 131 |
else:
|
| 132 |
dob = raw_dob
|
| 133 |
|
| 134 |
-
# ---
|
|
|
|
|
|
|
|
|
|
|
|
|
| 135 |
user = Users(
|
| 136 |
email_id=row["email"],
|
| 137 |
password=hash_password(row["password"]),
|
| 138 |
user_name=row["User_name"],
|
| 139 |
dob=dob,
|
| 140 |
address=clean(row["address"]),
|
| 141 |
-
join_date=
|
| 142 |
-
is_verified=True,
|
| 143 |
)
|
| 144 |
|
| 145 |
session.add(user)
|
|
@@ -168,10 +201,10 @@ async def seed_from_excel(session: AsyncSession, excel_path="src/data_add/users.
|
|
| 168 |
continue
|
| 169 |
|
| 170 |
asset_obj = Assets(
|
| 171 |
-
id=asset_id,
|
| 172 |
user_id=user.id,
|
| 173 |
-
name=asset_type,
|
| 174 |
-
type=asset_type,
|
| 175 |
status=AssetStatus.ACTIVE,
|
| 176 |
)
|
| 177 |
|
|
@@ -192,7 +225,7 @@ async def run_all_seeds():
|
|
| 192 |
print("\n🟦 Seeding TEAMS...")
|
| 193 |
for t in [
|
| 194 |
"AI/Tech",
|
| 195 |
-
"Shared
|
| 196 |
"Digital Marketing",
|
| 197 |
"Bevolve",
|
| 198 |
"Bridge",
|
|
|
|
| 46 |
value = value.strip().replace(" ", "")
|
| 47 |
value = value.upper()
|
| 48 |
|
|
|
|
| 49 |
while "--" in value:
|
| 50 |
value = value.replace("--", "-")
|
| 51 |
|
|
|
|
| 52 |
if "-" not in value:
|
| 53 |
+
prefix = value[:2]
|
| 54 |
number = "".join(filter(str.isdigit, value))
|
| 55 |
suffix = value[len(prefix) + len(number) :]
|
| 56 |
return f"{prefix}-{number}-{suffix}"
|
|
|
|
| 107 |
return []
|
| 108 |
|
| 109 |
|
| 110 |
+
# ---------------------------------------------
|
| 111 |
+
# FORMAT JOIN DATE (NEW)
|
| 112 |
+
# ---------------------------------------------
|
| 113 |
+
def format_join_date(value):
|
| 114 |
+
"""Always return YYYY-MM-DD string, no time."""
|
| 115 |
+
if value is None:
|
| 116 |
+
return None
|
| 117 |
+
|
| 118 |
+
if isinstance(value, datetime):
|
| 119 |
+
return value.date().isoformat() # remove time
|
| 120 |
+
|
| 121 |
+
if isinstance(value, date):
|
| 122 |
+
return value.isoformat()
|
| 123 |
+
|
| 124 |
+
if isinstance(value, str):
|
| 125 |
+
value = value.strip()
|
| 126 |
+
|
| 127 |
+
# Try standard formats
|
| 128 |
+
for fmt in ("%Y-%m-%d", "%d.%m.%Y", "%d-%m-%Y", "%m/%d/%Y"):
|
| 129 |
+
try:
|
| 130 |
+
d = datetime.strptime(value, fmt).date()
|
| 131 |
+
return d.isoformat()
|
| 132 |
+
except:
|
| 133 |
+
continue
|
| 134 |
+
|
| 135 |
+
# Fallback: return cleaned string
|
| 136 |
+
return value
|
| 137 |
+
|
| 138 |
+
return str(value)
|
| 139 |
+
|
| 140 |
+
|
| 141 |
# ---------------------------------------------
|
| 142 |
# MAIN IMPORT
|
| 143 |
# ---------------------------------------------
|
|
|
|
| 160 |
else:
|
| 161 |
dob = raw_dob
|
| 162 |
|
| 163 |
+
# --- JOIN DATE (FIXED) ---
|
| 164 |
+
raw_join = clean(row["join_date"])
|
| 165 |
+
join_date = format_join_date(raw_join)
|
| 166 |
+
|
| 167 |
+
# --- CREATE USER ---
|
| 168 |
user = Users(
|
| 169 |
email_id=row["email"],
|
| 170 |
password=hash_password(row["password"]),
|
| 171 |
user_name=row["User_name"],
|
| 172 |
dob=dob,
|
| 173 |
address=clean(row["address"]),
|
| 174 |
+
join_date=join_date, # <--- NOW ONLY YYYY-MM-DD
|
| 175 |
+
is_verified=True,
|
| 176 |
)
|
| 177 |
|
| 178 |
session.add(user)
|
|
|
|
| 201 |
continue
|
| 202 |
|
| 203 |
asset_obj = Assets(
|
| 204 |
+
id=asset_id,
|
| 205 |
user_id=user.id,
|
| 206 |
+
name=asset_type,
|
| 207 |
+
type=asset_type,
|
| 208 |
status=AssetStatus.ACTIVE,
|
| 209 |
)
|
| 210 |
|
|
|
|
| 225 |
print("\n🟦 Seeding TEAMS...")
|
| 226 |
for t in [
|
| 227 |
"AI/Tech",
|
| 228 |
+
"Shared Services",
|
| 229 |
"Digital Marketing",
|
| 230 |
"Bevolve",
|
| 231 |
"Bridge",
|
src/data_add/users.xlsx
CHANGED
|
Binary files a/src/data_add/users.xlsx and b/src/data_add/users.xlsx differ
|
|
|
src/notifications/router.py
CHANGED
|
@@ -37,9 +37,12 @@ async def mark_notification_read(
|
|
| 37 |
raise HTTPException(404, "Notification not found")
|
| 38 |
|
| 39 |
# Only owner or mentor should mark
|
| 40 |
-
if str(notif.user_id) != user_id and
|
|
|
|
|
|
|
| 41 |
raise HTTPException(403, "Unauthorized")
|
| 42 |
|
|
|
|
| 43 |
notif.is_read = True
|
| 44 |
await session.commit()
|
| 45 |
|
|
|
|
| 37 |
raise HTTPException(404, "Notification not found")
|
| 38 |
|
| 39 |
# Only owner or mentor should mark
|
| 40 |
+
if str(notif.user_id) != user_id and \
|
| 41 |
+
str(notif.mentor_id) != user_id and \
|
| 42 |
+
str(notif.lead_id) != user_id:
|
| 43 |
raise HTTPException(403, "Unauthorized")
|
| 44 |
|
| 45 |
+
|
| 46 |
notif.is_read = True
|
| 47 |
await session.commit()
|
| 48 |
|
src/profile/notify.py
CHANGED
|
@@ -16,6 +16,9 @@ def ensure_list(value):
|
|
| 16 |
return [value]
|
| 17 |
|
| 18 |
|
|
|
|
|
|
|
|
|
|
| 19 |
# -------------------------------
|
| 20 |
# SEND TO MENTOR + LEAD
|
| 21 |
# -------------------------------
|
|
@@ -23,27 +26,49 @@ async def send_leave_request_notification(session, user, leave, mentor_ids, lead
|
|
| 23 |
mentor_ids = ensure_list(mentor_ids)
|
| 24 |
lead_ids = ensure_list(lead_ids)
|
| 25 |
|
| 26 |
-
|
| 27 |
-
|
|
|
|
|
|
|
| 28 |
for mentor_id in mentor_ids:
|
| 29 |
-
|
| 30 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
for lead_id in lead_ids:
|
| 32 |
-
|
| 33 |
|
| 34 |
-
|
| 35 |
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
|
|
|
| 47 |
|
| 48 |
|
| 49 |
# -------------------------------
|
|
|
|
| 16 |
return [value]
|
| 17 |
|
| 18 |
|
| 19 |
+
# -------------------------------
|
| 20 |
+
# SEND TO MENTOR + LEAD
|
| 21 |
+
# -------------------------------
|
| 22 |
# -------------------------------
|
| 23 |
# SEND TO MENTOR + LEAD
|
| 24 |
# -------------------------------
|
|
|
|
| 26 |
mentor_ids = ensure_list(mentor_ids)
|
| 27 |
lead_ids = ensure_list(lead_ids)
|
| 28 |
|
| 29 |
+
# ---------------------------
|
| 30 |
+
# SEND TO MENTORS (Approval screen)
|
| 31 |
+
# ---------------------------
|
| 32 |
+
mentor_tokens = []
|
| 33 |
for mentor_id in mentor_ids:
|
| 34 |
+
mentor_tokens += await get_user_device_tokens(session, mentor_id)
|
| 35 |
+
|
| 36 |
+
mentor_tokens = list(set(mentor_tokens))
|
| 37 |
+
|
| 38 |
+
if mentor_tokens:
|
| 39 |
+
await send_fcm(
|
| 40 |
+
mentor_tokens,
|
| 41 |
+
"New Leave Request",
|
| 42 |
+
f"{user.user_name} requested leave",
|
| 43 |
+
{
|
| 44 |
+
"type": "leave_request",
|
| 45 |
+
"screen": "MentorApproval",
|
| 46 |
+
"leave_id": str(leave.id),
|
| 47 |
+
},
|
| 48 |
+
priority="high",
|
| 49 |
+
)
|
| 50 |
+
|
| 51 |
+
# ---------------------------
|
| 52 |
+
# SEND TO TEAM LEADS (Leave Details screen)
|
| 53 |
+
# ---------------------------
|
| 54 |
+
lead_tokens = []
|
| 55 |
for lead_id in lead_ids:
|
| 56 |
+
lead_tokens += await get_user_device_tokens(session, lead_id)
|
| 57 |
|
| 58 |
+
lead_tokens = list(set(lead_tokens))
|
| 59 |
|
| 60 |
+
if lead_tokens:
|
| 61 |
+
await send_fcm(
|
| 62 |
+
lead_tokens,
|
| 63 |
+
"New Leave Request",
|
| 64 |
+
f"{user.user_name} requested leave",
|
| 65 |
+
{
|
| 66 |
+
"type": "leave_request",
|
| 67 |
+
"screen": "LeaveDetails",
|
| 68 |
+
"leave_id": str(leave.id),
|
| 69 |
+
},
|
| 70 |
+
priority="high",
|
| 71 |
+
)
|
| 72 |
|
| 73 |
|
| 74 |
# -------------------------------
|
src/profile/router.py
CHANGED
|
@@ -182,21 +182,45 @@ async def list_notifications(
|
|
| 182 |
notifications = []
|
| 183 |
|
| 184 |
for leave in results:
|
| 185 |
-
|
| 186 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 187 |
title = f"Your leave was {leave.status}"
|
| 188 |
body = f"{leave.leave_type} from {leave.from_date} to {leave.to_date}"
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 197 |
else:
|
| 198 |
-
|
| 199 |
-
body = leave.reason or ""
|
| 200 |
|
| 201 |
notifications.append(
|
| 202 |
{
|
|
@@ -205,6 +229,7 @@ async def list_notifications(
|
|
| 205 |
"lead_id": str(leave.lead_id),
|
| 206 |
"title": title,
|
| 207 |
"body": body,
|
|
|
|
| 208 |
"type": leave.status,
|
| 209 |
"updated_at": leave.updated_at.isoformat(),
|
| 210 |
"leave_type": leave.leave_type,
|
|
|
|
| 182 |
notifications = []
|
| 183 |
|
| 184 |
for leave in results:
|
| 185 |
+
|
| 186 |
+
leave_user = str(leave.user_id)
|
| 187 |
+
leave_mentor = str(leave.mentor_id)
|
| 188 |
+
leave_lead = str(leave.lead_id)
|
| 189 |
+
current = str(user_id)
|
| 190 |
+
|
| 191 |
+
employee = await session.get(Users, uuid.UUID(leave_user))
|
| 192 |
+
employee_name = employee.user_name if employee else "Unknown User"
|
| 193 |
+
|
| 194 |
+
# ---------- USER ----------
|
| 195 |
+
if leave_user == current:
|
| 196 |
+
# User should NOT see pending
|
| 197 |
+
if leave.status == LeaveStatus.PENDING:
|
| 198 |
+
continue
|
| 199 |
+
|
| 200 |
title = f"Your leave was {leave.status}"
|
| 201 |
body = f"{leave.leave_type} from {leave.from_date} to {leave.to_date}"
|
| 202 |
+
|
| 203 |
+
# ---------- MENTOR ----------
|
| 204 |
+
elif leave_mentor == current:
|
| 205 |
+
# Mentor should ONLY see pending
|
| 206 |
+
if leave.status == LeaveStatus.PENDING:
|
| 207 |
+
title = "New Leave Request"
|
| 208 |
+
body = f"{leave.leave_type} requested by {employee_name}"
|
| 209 |
+
else:
|
| 210 |
+
continue # Mentor should not see approved/rejected
|
| 211 |
+
|
| 212 |
+
# ---------- TEAM LEAD ----------
|
| 213 |
+
elif leave_lead == current:
|
| 214 |
+
# Lead sees ALL statuses
|
| 215 |
+
if leave.status == LeaveStatus.PENDING:
|
| 216 |
+
title = "Pending Leave Request"
|
| 217 |
+
body = f"{leave.leave_type} requested by {employee_name}"
|
| 218 |
+
else:
|
| 219 |
+
title = f"Leave {leave.status}"
|
| 220 |
+
body = f"{leave.leave_type} updated"
|
| 221 |
+
|
| 222 |
else:
|
| 223 |
+
continue # no match → skip
|
|
|
|
| 224 |
|
| 225 |
notifications.append(
|
| 226 |
{
|
|
|
|
| 229 |
"lead_id": str(leave.lead_id),
|
| 230 |
"title": title,
|
| 231 |
"body": body,
|
| 232 |
+
"employee_name": employee_name,
|
| 233 |
"type": leave.status,
|
| 234 |
"updated_at": leave.updated_at.isoformat(),
|
| 235 |
"leave_type": leave.leave_type,
|