My Workspace
-
Today’s work, attendance and personal profile
-
Use this dashboard for your own assignments, attendance, leave, documents, payslips and alerts.
+
Employee Portal
+
Use this dashboard for your assigned work, attendance, leave, documents, payslips, profile and alerts.
- {% if employee %}
-
-
- Open Work
- {{ work_payload.summary.open if work_payload else 0 }}
- Assigned to me
-
-
- In Progress
- {{ work_payload.summary.in_progress if work_payload else 0 }}
- Currently active
-
-
- Blocked
- {{ work_payload.summary.blocked if work_payload else 0 }}
- Need clarification
-
-
- Overdue
- {{ work_payload.summary.overdue if work_payload else 0 }}
- Immediate action
-
-
- Attendance
- {% if today_attendance %}Marked{% else %}Not Marked{% endif %}
- Today
-
-
- Payslips
- {{ payslips|length }}
- Available slips
-
+ {% set tabs = [
+ ('overview','Overview'),
+ ('work','My Work Board'),
+ ('attendance','My Attendance'),
+ ('profile','My Profile'),
+ ('leave','My Leave'),
+ ('documents','My Documents'),
+ ('payslips','My Payslips'),
+ ('offboarding','My Offboarding'),
+ ('alerts','My Alert')
+ ] %}
+
+
+ My Workspace
+
+
+ {% for code, label in tabs %}
+
+ {{ label }}{% if code == 'alerts' and unread_alert_count is defined and unread_alert_count > 0 %}{{ unread_alert_count }} {% endif %}
+
+ {% endfor %}
+
+
-
-
-
-
-
-
My Assignment Board Snapshot
-
Your work is grouped by practical status so you can start from urgent items first.
-
-
Open My Work Board
-
-
-
-
-
-
-
-
Leave & Availability
-
Balances and recent leave requests for your profile.
-
-
Open Leave
-
-
- {% for bal in leave_balances %}
-
-
{{ bal.leave_type.name if bal.leave_type else bal.leave_type_id }}
-
{{ bal.balance_days }}
-
days balance
-
- {% else %}
-
No leave balance available yet.
- {% endfor %}
-
-
-
-
-
+
+ {% include "modules/employees/templates/employees/portal_partials/overview.html" %}
- {% elif pending_request %}
- Employee profile request is pending Your request #{{ pending_request.id }} is waiting for approval.
- {% else %}
- Your login is not linked to an employee master Submit an employee link request or ask Firm Admin/Partner/Branch Manager to link your user account from Employee Master.
Request Employee Link
- {% endif %}
-
-
-
-
-
-
-
📍
-
-
Enable location for attendance
-
- To mark attendance, this app needs your current location to verify whether you are within your branch geofence.
- Click Continue, then choose Allow in the browser location popup.
-
-
- If location is denied/unavailable or you are outside branch range, the punch can still be saved for approval as per existing attendance rules.
-
-
-
-
- Cancel
- Continue
-
-
{% endblock %}
diff --git a/app/modules/employees/templates/employees/portal_partials/alerts.html b/app/modules/employees/templates/employees/portal_partials/alerts.html
new file mode 100644
index 0000000..584a98d
--- /dev/null
+++ b/app/modules/employees/templates/employees/portal_partials/alerts.html
@@ -0,0 +1,12 @@
+
+
My Alert Open your alert centre to view notifications and follow-ups.
+
Open Alerts
+
+
+
{% if unread_alert_count is defined and unread_alert_count > 0 %}You have {{ unread_alert_count }} unread alert(s).{% else %}No unread alert count is pending in this workspace context.{% endif %}
+
+
diff --git a/app/modules/employees/templates/employees/portal_partials/attendance.html b/app/modules/employees/templates/employees/portal_partials/attendance.html
new file mode 100644
index 0000000..3840cf8
--- /dev/null
+++ b/app/modules/employees/templates/employees/portal_partials/attendance.html
@@ -0,0 +1,42 @@
+
+
+{% if not employee %}
+
Your login is not linked to an employee profile yet. Please request employee linkage first.
+{% else %}
+
+ Today
{{ today_attendance.attendance_date if today_attendance else 'Not marked' }}
+ Punch In
{{ today_attendance.punch_in_local_at.strftime('%H:%M') if today_attendance and today_attendance.punch_in_local_at else (today_attendance.punch_in_utc.strftime('%H:%M UTC') if today_attendance and today_attendance.punch_in_utc else '-') }}
+ Punch Out
{{ today_attendance.punch_out_local_at.strftime('%H:%M') if today_attendance and today_attendance.punch_out_local_at else (today_attendance.punch_out_utc.strftime('%H:%M UTC') if today_attendance and today_attendance.punch_out_utc else '-') }}
+
+
+{% include "modules/employees/templates/employees/portal_partials/attendance_punch_card.html" %}
+
+
+
+
+ Date Punch In Punch Out Duration Status Approval Geo/IP
+
+
+ {% for row in attendance_rows %}
+
+ {{ row.attendance_date }}
+ {{ row.punch_in_local_at.strftime('%H:%M') if row.punch_in_local_at else (row.punch_in_utc.strftime('%H:%M UTC') if row.punch_in_utc else '-') }}
+ {{ row.punch_out_local_at.strftime('%H:%M') if row.punch_out_local_at else (row.punch_out_utc.strftime('%H:%M UTC') if row.punch_out_utc else '-') }}
+ {{ (row.work_duration_minutes ~ ' min') if row.work_duration_minutes else '-' }}
+ {{ row.status.replace('_',' ').title() }}
+ {{ row.approval_status.replace('_',' ').title() }}
+ Geo: {{ (row.punch_in_geo_status or '-').replace('_',' ').title() }}
IP: {{ (row.punch_in_ip_status or '-').replace('_',' ').title() }}
+
+ {% else %}
+ No attendance records available yet.
+ {% endfor %}
+
+
+
+{% endif %}
diff --git a/app/modules/employees/templates/employees/portal_partials/attendance_punch_card.html b/app/modules/employees/templates/employees/portal_partials/attendance_punch_card.html
new file mode 100644
index 0000000..ecfccf3
--- /dev/null
+++ b/app/modules/employees/templates/employees/portal_partials/attendance_punch_card.html
@@ -0,0 +1,94 @@
+
+
+
+
Today’s Attendance
+
+ {% if today_attendance %}
+ Punch In: {{ today_attendance.punch_in_local_at.strftime('%H:%M') if today_attendance.punch_in_local_at else (today_attendance.punch_in_utc.strftime('%H:%M UTC') if today_attendance.punch_in_utc else '-') }} ·
+ Punch Out: {{ today_attendance.punch_out_local_at.strftime('%H:%M') if today_attendance.punch_out_local_at else (today_attendance.punch_out_utc.strftime('%H:%M UTC') if today_attendance.punch_out_utc else '-') }}
+ {% else %}
+ Attendance not marked for today.
+ {% endif %}
+
+
+
Full View
+
+
+ {% if employee %}
+
Location not captured yet.
+
+
+
+
+ {% endif %}
+
+
+
diff --git a/app/modules/employees/templates/employees/portal_partials/documents.html b/app/modules/employees/templates/employees/portal_partials/documents.html
new file mode 100644
index 0000000..afe47cd
--- /dev/null
+++ b/app/modules/employees/templates/employees/portal_partials/documents.html
@@ -0,0 +1,9 @@
+
+
My Documents Recently uploaded employee documents and verification status.
+
Open Documents Page
+
+
+
Document Type Status Uploaded Action
+ {% for doc in own_documents %}{{ doc.title or doc.file_name or ('Document #' ~ doc.id) }} {{ doc.document_type.name if doc.document_type else '-' }} {{ doc.status.replace('_',' ').title() if doc.status else '-' }} {{ doc.created_at.strftime('%d-%m-%Y') if doc.created_at else '-' }} View {% else %}No employee documents available yet. {% endfor %}
+
+
diff --git a/app/modules/employees/templates/employees/portal_partials/leave.html b/app/modules/employees/templates/employees/portal_partials/leave.html
new file mode 100644
index 0000000..9f7b17a
--- /dev/null
+++ b/app/modules/employees/templates/employees/portal_partials/leave.html
@@ -0,0 +1,21 @@
+
+
+ {% for bal in leave_balances %}
+
+
{{ bal.leave_type.name if bal.leave_type else bal.leave_type_id }}
+
{{ bal.balance_days }}
+
days balance
+
+ {% else %}
+
No leave balance available yet.
+ {% endfor %}
+
+
+
Recent Leave Requests
+
Type From To Days Status
+ {% for row in leave_requests %}{{ row.leave_type.name if row.leave_type else row.leave_type_id }} {{ row.from_date }} {{ row.to_date }} {{ row.days }} {{ row.status.replace('_',' ').title() }} {% else %}No leave requests available. {% endfor %}
+
+
diff --git a/app/modules/employees/templates/employees/portal_partials/offboarding.html b/app/modules/employees/templates/employees/portal_partials/offboarding.html
new file mode 100644
index 0000000..c187c36
--- /dev/null
+++ b/app/modules/employees/templates/employees/portal_partials/offboarding.html
@@ -0,0 +1,9 @@
+
+
+
Request Reason Status Created
+ {% for row in offboarding_requests %}#{{ row.id }} {{ row.reason or '-' }} {{ row.status.replace('_',' ').title() if row.status else '-' }} {{ row.created_at.strftime('%d-%m-%Y') if row.created_at else '-' }} {% else %}No offboarding requests available. {% endfor %}
+
+
diff --git a/app/modules/employees/templates/employees/portal_partials/overview.html b/app/modules/employees/templates/employees/portal_partials/overview.html
new file mode 100644
index 0000000..1aeb72b
--- /dev/null
+++ b/app/modules/employees/templates/employees/portal_partials/overview.html
@@ -0,0 +1,111 @@
+{% if employee %}
+
+
+
+
+
+
+
+
My Assignment Board Snapshot
+
Your work is grouped by practical status. Open the full board for detailed updates.
+
+
Open My Work Board
+
+
+
+
+
+
+
+
Leave & Availability
+
Balances and recent leave requests for your profile.
+
+
Open Leave
+
+
+ {% for bal in leave_balances %}
+
+
{{ bal.leave_type.name if bal.leave_type else bal.leave_type_id }}
+
{{ bal.balance_days }}
+
days balance
+
+ {% else %}
+
No leave balance available yet.
+ {% endfor %}
+
+
+
+
+
+ {% include "modules/employees/templates/employees/portal_partials/attendance_punch_card.html" %}
+
+
+
Profile Summary
+
+
Name {{ employee.full_name }}
+
Code / Status {{ employee.employee_code }} · {{ employee.status.replace('_',' ').title() }}
+
Designation {{ employee.designation or '-' }}
+
Email {{ employee.email or current_user.email }}
+
Mobile {{ employee.mobile or current_user.mobile or '-' }}
+
+
Update My Profile
+
+
+
+
+
+{% elif pending_request %}
+
Employee profile request is pending Your request #{{ pending_request.id }} is waiting for approval.
+{% else %}
+
Your login is not linked to an employee master Submit an employee link request or ask Firm Admin/Partner/Branch Manager to link your user account from Employee Master.
Request Employee Link
+{% endif %}
diff --git a/app/modules/employees/templates/employees/portal_partials/payslips.html b/app/modules/employees/templates/employees/portal_partials/payslips.html
new file mode 100644
index 0000000..72f03f7
--- /dev/null
+++ b/app/modules/employees/templates/employees/portal_partials/payslips.html
@@ -0,0 +1,16 @@
+
+
+ {% for slip in payslips %}
+
+
Payslip
+
{{ slip.period_label if slip.period_label is defined else ('Payslip #' ~ slip.id) }}
+
Status: {{ slip.status.replace('_',' ').title() if slip.status else '-' }}
+
View
+
+ {% else %}
+
No payslips available yet.
+ {% endfor %}
+
diff --git a/app/modules/employees/templates/employees/portal_partials/profile.html b/app/modules/employees/templates/employees/portal_partials/profile.html
new file mode 100644
index 0000000..405c0dd
--- /dev/null
+++ b/app/modules/employees/templates/employees/portal_partials/profile.html
@@ -0,0 +1,31 @@
+{% if not employee %}
+
Your login is not linked to an employee profile yet.
Request employee profile .
+{% else %}
+
+
+
+
Employment Details
+
+
Name {{ employee.full_name }}
+
Employee Code {{ employee.employee_code }}
+
Status {{ employee.status.replace('_',' ').title() }}
+
Designation {{ employee.designation or '-' }}
+
Department {{ employee.department or '-' }}
+
Date of Joining {{ employee.date_of_joining or '-' }}
+
+
+
+
Contact Details
+
+
Email {{ employee.email or current_user.email }}
+
Mobile {{ employee.mobile or current_user.mobile or '-' }}
+
Alternate Mobile {{ employee.alternate_mobile or '-' }}
+
Address {{ employee.address or '-' }}
+
Emergency Contact {{ employee.emergency_contact_name or '-' }} {{ employee.emergency_contact_mobile or '' }}
+
+
+
+{% endif %}
diff --git a/app/modules/employees/templates/employees/portal_partials/work.html b/app/modules/employees/templates/employees/portal_partials/work.html
new file mode 100644
index 0000000..b98217f
--- /dev/null
+++ b/app/modules/employees/templates/employees/portal_partials/work.html
@@ -0,0 +1,55 @@
+{% if not employee %}
+
+
Your login is not linked to an employee master
+
You may still see tasks assigned directly to your user ID, but full self-service requires employee master linkage.
+
Request Employee Link
+
+{% endif %}
+
+
+
+
My Work Board
+
Snapshot of your assigned engagements. Use the full work board to update task status and communication.
+
+
Open Full Work Board
+
+
+
+ Total
{{ work_payload.summary.total if work_payload else 0 }}
+ Open
{{ work_payload.summary.open if work_payload else 0 }}
+ In Progress
{{ work_payload.summary.in_progress if work_payload else 0 }}
+ Blocked
{{ work_payload.summary.blocked if work_payload else 0 }}
+ Overdue
{{ work_payload.summary.overdue if work_payload else 0 }}
+ Completed
{{ work_payload.summary.completed if work_payload else 0 }}
+
+
+
+ {% if work_payload %}
+ {% for column in work_payload.columns %}
+
+
+
{{ column.label }}
+ {{ column.cards|length }}
+
+
+ {% for card in column.cards[:5] %}
+
+ {{ card.client_code or 'Client' }}
+ {{ card.client_name }}
+
+
{{ card.service_name }}
+
FY {{ card.financial_year }} · Due {{ card.due_date or '-' }}
+
+ Open Work Details
+
+ {% else %}
+
No cards in this column.
+ {% endfor %}
+ {% if column.cards|length > 5 %}
View all {{ column.cards|length }} {% endif %}
+
+
+ {% endfor %}
+ {% else %}
+
No work board data available yet.
+ {% endif %}
+
diff --git a/app/modules/employees/ui.py b/app/modules/employees/ui.py
index d041382..bba5288 100644
--- a/app/modules/employees/ui.py
+++ b/app/modules/employees/ui.py
@@ -2472,8 +2472,56 @@ def employee_status_submit(
db.close()
+
+EMPLOYEE_PORTAL_TABS = {
+ "overview": "modules/employees/templates/employees/portal_partials/overview.html",
+ "work": "modules/employees/templates/employees/portal_partials/work.html",
+ "attendance": "modules/employees/templates/employees/portal_partials/attendance.html",
+ "profile": "modules/employees/templates/employees/portal_partials/profile.html",
+ "leave": "modules/employees/templates/employees/portal_partials/leave.html",
+ "documents": "modules/employees/templates/employees/portal_partials/documents.html",
+ "payslips": "modules/employees/templates/employees/portal_partials/payslips.html",
+ "offboarding": "modules/employees/templates/employees/portal_partials/offboarding.html",
+ "alerts": "modules/employees/templates/employees/portal_partials/alerts.html",
+}
+
+
+def _employee_portal_context(request: Request, db, current_user, *, active_tab: str = "overview"):
+ employee = get_employee_for_user(db, current_user)
+ pending_request = None if employee else get_pending_registration_for_user(db, current_user)
+ work_payload = None
+ if employee:
+ scope = build_employee_scope(
+ db,
+ current_user,
+ tenant_id=request.session.get("active_tenant_id") or current_user.tenant_id,
+ branch_id=request.session.get("active_branch_id"),
+ )
+ work_payload = list_employee_work_kanban(
+ db,
+ scope,
+ q="",
+ status="open",
+ financial_year=_active_financial_year(request),
+ )
+ return {
+ "title": "Employee Portal",
+ "active_tab": active_tab if active_tab in EMPLOYEE_PORTAL_TABS else "overview",
+ "employee": employee,
+ "pending_request": pending_request,
+ "today_attendance": get_today_attendance_for_user(db, current_user),
+ "attendance_rows": list_own_attendance(db, current_user, limit=10),
+ "leave_balances": list_own_leave_balances(db, current_user),
+ "leave_requests": list_own_leave_requests(db, current_user)[:5],
+ "offboarding_requests": list_own_offboarding_requests(db, current_user)[:5],
+ "payslips": list_own_payslips(db, current_user)[:5],
+ "own_documents": list_own_employee_documents(db, current_user)[:8],
+ "work_payload": work_payload,
+ }
+
+
@portal_router.get("/dashboard")
-def employee_portal_dashboard(request: Request):
+def employee_portal_dashboard(request: Request, tab: str = "overview"):
db = CommonSessionLocal()
try:
current_user = get_current_user(request, db=db)
@@ -2483,32 +2531,40 @@ def employee_portal_dashboard(request: Request):
require_permission(db, current_user, "employees.ess.view")
except Exception:
return _redirect_denied()
- employee = get_employee_for_user(db, current_user)
- pending_request = None if employee else get_pending_registration_for_user(db, current_user)
- work_payload = None
- if employee:
- scope = build_employee_scope(db, current_user, tenant_id=request.session.get("active_tenant_id") or current_user.tenant_id, branch_id=request.session.get("active_branch_id"))
- work_payload = list_employee_work_kanban(db, scope, q="", status="open", financial_year=_active_financial_year(request))
+ active_tab = tab if tab in EMPLOYEE_PORTAL_TABS else "overview"
return _render(
request,
"modules/employees/templates/employees/portal_dashboard.html",
db,
current_user,
- title="Employee Portal",
- employee=employee,
- pending_request=pending_request,
- today_attendance=get_today_attendance_for_user(db, current_user),
- attendance_rows=list_own_attendance(db, current_user)[:7],
- leave_balances=list_own_leave_balances(db, current_user),
- leave_requests=list_own_leave_requests(db, current_user)[:5],
- offboarding_requests=list_own_offboarding_requests(db, current_user)[:5],
- payslips=list_own_payslips(db, current_user)[:5],
- work_payload=work_payload,
+ **_employee_portal_context(request, db, current_user, active_tab=active_tab),
)
finally:
db.close()
+@portal_router.get("/dashboard/tab/{tab_name}")
+def employee_portal_dashboard_tab(request: Request, tab_name: str):
+ db = CommonSessionLocal()
+ try:
+ current_user = get_current_user(request, db=db)
+ if not current_user:
+ return _redirect_login()
+ try:
+ require_permission(db, current_user, "employees.ess.view")
+ except Exception:
+ return _redirect_denied()
+ active_tab = tab_name if tab_name in EMPLOYEE_PORTAL_TABS else "overview"
+ return _render(
+ request,
+ EMPLOYEE_PORTAL_TABS[active_tab],
+ db,
+ current_user,
+ **_employee_portal_context(request, db, current_user, active_tab=active_tab),
+ )
+ finally:
+ db.close()
+
@portal_router.get("/attendance")
def employee_self_attendance(request: Request):
db = CommonSessionLocal()