Coverage for modules\team\api.py: 46%
70 statements
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-06 17:01 +0800
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-06 17:01 +0800
1from ninja import Router
2from typing import List
3from uuid import UUID
4from core.auth import JWTAuth
5from core.permissions import require_tenant, require_admin, require_manager
6from .schemas import (
7 AttendanceActionIn, AttendanceLogOut,
8 DepartmentIn, DepartmentOut,
9 TeamMemberIn, TeamMemberUpdateIn, TeamMemberOut,
10 InviteIn, InvitationOut, LocalUserIn
11)
12from .services import TeamService
14router = Router(tags=["Team & Organization"])
16@router.get("/attendance", auth=JWTAuth(), response={200: List[AttendanceLogOut]})
17def list_attendance(request):
18 tenant_id = require_tenant(request)
19 records = TeamService.list_visible_attendance(tenant_id, request.user)
20 return [
21 AttendanceLogOut(
22 id=r.id,
23 user_id=r.user_id,
24 user_name=r.user.full_name,
25 user_email=r.user.email,
26 department_name=r.user.team_profiles.filter(tenant_id=tenant_id).first().department.name if r.user.team_profiles.filter(tenant_id=tenant_id).first() and r.user.team_profiles.filter(tenant_id=tenant_id).first().department else None,
27 check_in=r.check_in,
28 check_out=r.check_out,
29 status=r.status,
30 notes=r.notes,
31 created_at=r.created_at,
32 updated_at=r.updated_at,
33 )
34 for r in records
35 ]
38@router.get("/attendance/me", auth=JWTAuth(), response={200: List[AttendanceLogOut]})
39def my_attendance(request):
40 tenant_id = require_tenant(request)
41 records = TeamService.list_visible_attendance(tenant_id, request.user)
42 return [
43 AttendanceLogOut(
44 id=r.id,
45 user_id=r.user_id,
46 user_name=r.user.full_name,
47 user_email=r.user.email,
48 department_name=r.user.team_profiles.filter(tenant_id=tenant_id).first().department.name if r.user.team_profiles.filter(tenant_id=tenant_id).first() and r.user.team_profiles.filter(tenant_id=tenant_id).first().department else None,
49 check_in=r.check_in,
50 check_out=r.check_out,
51 status=r.status,
52 notes=r.notes,
53 created_at=r.created_at,
54 updated_at=r.updated_at,
55 )
56 for r in records
57 ]
60@router.post("/attendance/clock-in", auth=JWTAuth(), response={201: AttendanceLogOut})
61def clock_in(request, payload: AttendanceActionIn = None):
62 tenant_id = require_tenant(request)
63 notes = payload.notes if payload else None
64 record = TeamService.clock_in(tenant_id, request.user, notes)
65 return 201, AttendanceLogOut(
66 id=record.id,
67 user_id=record.user_id,
68 check_in=record.check_in,
69 check_out=record.check_out,
70 status=record.status,
71 notes=record.notes,
72 created_at=record.created_at,
73 updated_at=record.updated_at,
74 )
77@router.post("/attendance/clock-out", auth=JWTAuth(), response={200: AttendanceLogOut})
78def clock_out(request, payload: AttendanceActionIn = None):
79 tenant_id = require_tenant(request)
80 notes = payload.notes if payload else None
81 record = TeamService.clock_out(tenant_id, request.user, notes)
82 return AttendanceLogOut(
83 id=record.id,
84 user_id=record.user_id,
85 check_in=record.check_in,
86 check_out=record.check_out,
87 status=record.status,
88 notes=record.notes,
89 created_at=record.created_at,
90 updated_at=record.updated_at,
91 )
93@router.get("/departments", auth=JWTAuth(), response={200: List[DepartmentOut]})
94def list_departments(request):
95 tenant_id = require_tenant(request)
96 depts = TeamService.list_departments(tenant_id)
97 result = []
98 for d in depts:
99 result.append(DepartmentOut(
100 id=d.id,
101 name=d.name,
102 code=d.code,
103 description=d.description,
104 parent_id=d.parent.id if d.parent else None,
105 parent_name=d.parent.name if d.parent else None,
106 manager_id=d.manager.id if d.manager else None,
107 manager_name=d.manager.full_name if d.manager else None,
108 members_count=d.members.count(),
109 created_at=d.created_at,
110 ))
111 return result
113@router.post("/departments", auth=JWTAuth(), response={201: DepartmentOut})
114@require_manager
115def create_department(request, payload: DepartmentIn):
116 tenant_id = require_tenant(request)
117 d = TeamService.create_department(tenant_id, payload)
118 return 201, DepartmentOut(
119 id=d.id,
120 name=d.name,
121 code=d.code,
122 description=d.description,
123 parent_id=d.parent.id if d.parent else None,
124 parent_name=d.parent.name if d.parent else None,
125 manager_id=d.manager.id if d.manager else None,
126 manager_name=d.manager.full_name if d.manager else None,
127 members_count=0,
128 created_at=d.created_at,
129 )
131@router.get("/members", auth=JWTAuth(), response={200: List[TeamMemberOut]})
132def list_members(request):
133 tenant_id = require_tenant(request)
134 members = TeamService.list_members(tenant_id)
135 result = []
136 for m in members:
137 result.append(TeamMemberOut(
138 id=m.id,
139 user_id=m.user.id,
140 email=m.user.email,
141 first_name=m.user.first_name,
142 last_name=m.user.last_name,
143 full_name=m.user.full_name,
144 avatar_url=m.user.avatar_url,
145 phone_number=m.user.phone_number,
146 external_email=m.user.external_email,
147 department_id=m.department.id if m.department else None,
148 department_name=m.department.name if m.department else None,
149 job_title=m.job_title,
150 role=m.role,
151 status=m.status,
152 joined_at=m.joined_at,
153 ))
154 return result
156@router.post("/users", auth=JWTAuth(), response={201: TeamMemberOut})
157@require_admin
158def create_local_user(request, payload: LocalUserIn):
159 tenant_id = require_tenant(request)
160 member = TeamService.create_local_user(tenant_id, payload)
161 return 201, TeamMemberOut(
162 id=member.id,
163 user_id=member.user.id,
164 email=member.user.email,
165 first_name=member.user.first_name,
166 last_name=member.user.last_name,
167 full_name=member.user.full_name,
168 avatar_url=member.user.avatar_url,
169 phone_number=member.user.phone_number,
170 external_email=member.user.external_email,
171 department_id=member.department.id if member.department else None,
172 department_name=member.department.name if member.department else None,
173 job_title=member.job_title,
174 role=member.role,
175 status=member.status,
176 joined_at=member.joined_at,
177 )
179@router.put("/members/{member_id}", auth=JWTAuth(), response={200: TeamMemberOut})
180@require_manager
181def update_member(request, member_id: UUID, payload: TeamMemberUpdateIn):
182 tenant_id = require_tenant(request)
183 m = TeamService.update_member(tenant_id, member_id, payload)
184 return TeamMemberOut(
185 id=m.id,
186 user_id=m.user.id,
187 email=m.user.email,
188 first_name=m.user.first_name,
189 last_name=m.user.last_name,
190 full_name=m.user.full_name,
191 avatar_url=m.user.avatar_url,
192 phone_number=m.user.phone_number,
193 department_id=m.department.id if m.department else None,
194 department_name=m.department.name if m.department else None,
195 job_title=m.job_title,
196 role=m.role,
197 status=m.status,
198 joined_at=m.joined_at,
199 )
201@router.post("/invitations", auth=JWTAuth(), response={201: InvitationOut})
202@require_admin
203def invite_member(request, payload: InviteIn):
204 tenant_id = require_tenant(request)
205 inv = TeamService.create_invitation(tenant_id, request.user, payload)
206 return 201, InvitationOut(
207 id=inv.id,
208 email=inv.email,
209 role=inv.role,
210 department_id=inv.department_id,
211 invited_by_name=request.user.full_name,
212 status=inv.status,
213 expires_at=inv.expires_at,
214 created_at=inv.created_at,
215 )