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

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 

13 

14router = Router(tags=["Team & Organization"]) 

15 

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 ] 

36 

37 

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 ] 

58 

59 

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 ) 

75 

76 

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 ) 

92 

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 

112 

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 ) 

130 

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 

155 

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 ) 

178 

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 ) 

200 

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 )