Coverage for api/handlers/misc/clients.py: 19%

135 statements  

« prev     ^ index     » next       coverage.py v7.6.2, created at 2024-10-10 03:02 +0300

1import contextlib 

2import uuid 

3 

4import config 

5import motor 

6from bson import ObjectId 

7from database.entity import update_etag_and_text_search 

8from database.text_search.mongo_search import build_search_query 

9from database.updater import update_clients_in_orders 

10from exceptions import BadParameterHTTPError, NotFoundHTTPError 

11from handlers.authorization.check_role import has_role 

12from handlers.authorization.company_employee_access import ( 

13 get_assigned_clients_ids, 

14) 

15from handlers.grabbers.clients import assigned_clients_grabber 

16from mongodb import clients_col, cm_clients_col, subsidiaries_col, users_col 

17from pymongo import ReturnDocument 

18from pymongo.errors import DuplicateKeyError, OperationFailure, WriteError 

19from services.notifications.director import notification_api 

20from services.opensearch_client import get_ids_query, opensearch_adapter 

21from sotrans_models.models._mongo import PydanticObjectId 

22from sotrans_models.models.misc.client import ( 

23 AssignedToClientResponse, 

24 ClientCreateModel, 

25 ClientDBModel, 

26 ClientUpdateModel, 

27 ManagersClientsDBModel, 

28) 

29from sotrans_models.models.organizations import SubsidiaryDBModel 

30from sotrans_models.models.responses import GenericGetListResponse 

31from sotrans_models.models.roles import SotransRole 

32from sotrans_models.models.users import ( 

33 SotransOIDCUserModel, 

34 SotransUserDBFieldsModel, 

35 SotransUserDBModel, 

36) 

37from sotrans_models.utils.text_mappers import get_clients_text_search 

38from utils.helper import etag_detalizer, get_hash 

39 

40 

41async def get_responsible(ca_data: dict): 

42 if "responsible" not in ca_data or not ca_data["responsible"]: 

43 return 

44 responsible_ids = [uuid.UUID(r["id"]) for r in ca_data["responsible"]] 

45 ca_data["responsible"] = await users_col.find_batch( 

46 {SotransUserDBFieldsModel.id: {"$in": responsible_ids}} 

47 ) 

48 

49 

50async def on_create_client(client: ClientCreateModel): 

51 is_with_subsidiary = client.subsidiary and client.subsidiary.id 

52 if is_with_subsidiary: 

53 sub = subsidiaries_col.find_single("_id", client.subsidiary.id) 

54 if sub is None: 

55 raise NotFoundHTTPError("подразделение") 

56 assigned_clients_col = assigned_clients_grabber.collection.collection 

57 client_data = client.model_dump() 

58 await get_responsible(client_data) 

59 etag = get_hash(client_data) 

60 text_search = get_clients_text_search(client) 

61 try: 

62 created_client = await clients_col.create( 

63 { 

64 **client_data, 

65 **{"etag": etag, ClientDBModel.text_search: text_search}, 

66 } 

67 ) 

68 except DuplicateKeyError: 

69 raise BadParameterHTTPError( 

70 "клиент с такими ИНН и КПП уже зарегистрирован" 

71 ) 

72 created_model = ClientDBModel(**created_client) 

73 if client.responsible: 

74 for uuid_obj in client.responsible: 

75 assigned_clients_col.update_one( 

76 {ManagersClientsDBModel.manager_id: uuid_obj.id}, 

77 { 

78 "$addToSet": { 

79 ManagersClientsDBModel.clients_ids[0]: ObjectId( 

80 created_client["id"] 

81 ) 

82 } 

83 }, 

84 upsert=True, 

85 ) 

86 managers = await users_col.find_batch( 

87 {"id": {"$in": [u.id for u in client.responsible]}} 

88 ) 

89 for m in managers: 

90 notification_api.client_assigned( 

91 created_model, SotransUserDBModel(**m) 

92 ) 

93 if is_with_subsidiary: 

94 sub_model = SubsidiaryDBModel(**sub) 

95 if sub_model.employees: 

96 notification_api.client_assigned( 

97 created_model, sub_model.employees 

98 ) 

99 return created_model 

100 

101 

102async def on_patch_client( 

103 client_id: PydanticObjectId, client: ClientUpdateModel 

104): 

105 client_data = client.model_dump(exclude_none=True) 

106 await get_responsible(client_data) 

107 etag_q = {"etag": client.etag} if client.etag else {} 

108 try: 

109 updated = await clients_col.collection.find_one_and_update( 

110 {"_id": client_id} | etag_q, 

111 {"$set": client_data}, 

112 return_document=ReturnDocument.AFTER, 

113 ) 

114 except DuplicateKeyError: 

115 raise BadParameterHTTPError( 

116 "клиент с такими ИНН и КПП уже зарегистрирован" 

117 ) 

118 if updated is None: 

119 await etag_detalizer(clients_col, etag_q, {"_id": client_id}) 

120 raise NotFoundHTTPError("клиент") 

121 await update_etag_and_text_search( 

122 updated, clients_col, ClientDBModel, get_clients_text_search 

123 ) 

124 if client.responsible is not None: 

125 assigned_clients_motor_col: motor.MotorCollection = ( 

126 assigned_clients_grabber.collection.collection 

127 ) 

128 assigned_clients_motor_col.update_many( 

129 {}, {"$pull": {ManagersClientsDBModel.clients_ids[0]: client_id}} 

130 ) 

131 responsible_uuids = [ca_resp.id for ca_resp in client.responsible] 

132 assigned_clients_motor_col.update_many( 

133 {ManagersClientsDBModel.manager_id: {"$in": responsible_uuids}}, 

134 {"$addToSet": {ManagersClientsDBModel.clients_ids[0]: client_id}}, 

135 ) 

136 

137 await update_clients_in_orders(client_id, updated) 

138 return updated 

139 

140 

141async def on_assign_subsidiary_to_client( 

142 subsidiary_id: ObjectId, client_id: ObjectId 

143) -> GenericGetListResponse[SotransUserDBModel]: 

144 org_data = await subsidiaries_col.find_single("_id", subsidiary_id) 

145 if org_data is None: 

146 raise NotFoundHTTPError("подразделение") 

147 client = await clients_col.collection.find_one_and_update( 

148 {"_id": client_id}, 

149 {"$set": {ClientDBModel.subsidiary: org_data}}, 

150 ) 

151 if client is None: 

152 raise NotFoundHTTPError("клиент") 

153 org = SubsidiaryDBModel(**org_data) 

154 if org.employees: 

155 notification_api.client_assigned( 

156 ClientDBModel(**client), org.employees 

157 ) 

158 total = len(org.employees) if org.employees else 0 

159 return GenericGetListResponse[SotransUserDBModel]( 

160 total=total, items=org.employees if org.employees else [] 

161 ) 

162 

163 

164async def on_revoke_subsidiary(subsidiary_id: ObjectId, client_id: ObjectId): 

165 org_data = await subsidiaries_col.find_single("_id", subsidiary_id) 

166 if org_data is None: 

167 raise NotFoundHTTPError("филиал") 

168 await clients_col.update_by_id(client_id, {ClientDBModel.subsidiary: None}) 

169 

170 

171async def assign_manager_to_client( 

172 client_id: ObjectId, assignee_id: uuid.UUID 

173) -> AssignedToClientResponse: 

174 manager = await users_col.collection.find_one( 

175 {SotransUserDBFieldsModel.id: assignee_id} 

176 ) 

177 if not manager: 

178 raise NotFoundHTTPError("manager") 

179 updated_associative = await cm_clients_col.collection.find_one_and_update( 

180 {ManagersClientsDBModel.manager_id: assignee_id}, 

181 {"$addToSet": {"clients_ids": client_id}}, 

182 upsert=True, 

183 ) 

184 try: 

185 client = await clients_col.collection.find_one_and_update( 

186 {"_id": client_id}, 

187 {"$addToSet": {"responsible": manager}}, 

188 ) 

189 except OperationFailure: 

190 client = await clients_col.collection.find_one_and_update( 

191 {"_id": client_id}, {"$set": {"responsible": [manager]}} 

192 ) 

193 if not ( 

194 updated_associative 

195 and client_id in set(updated_associative["clients_ids"]) 

196 ): 

197 notification_api.client_assigned( 

198 ClientDBModel(**client), SotransUserDBModel(**manager) 

199 ) 

200 return AssignedToClientResponse( 

201 client_id=client_id, employees_ids=[assignee_id] 

202 ) 

203 

204 

205async def on_client_revoke_manager( 

206 client_id: ObjectId, assignee_id: uuid.UUID 

207): 

208 await cm_clients_col.collection.update_one( 

209 {ManagersClientsDBModel.manager_id: assignee_id}, 

210 {"$pull": {"clients_ids": client_id}}, 

211 ) 

212 with contextlib.suppress(WriteError): 

213 await clients_col.collection.update_one( 

214 {"_id": client_id}, {"$pull": {"responsible": {"id": assignee_id}}} 

215 ) 

216 await cm_clients_col.collection.delete_one( 

217 { 

218 ManagersClientsDBModel.manager_id: assignee_id, 

219 "clients_ids": {"$exists": True, "$size": 0}, 

220 }, 

221 ) 

222 

223 

224async def on_get_clients_managers( 

225 client_id: ObjectId, 

226) -> GenericGetListResponse[SotransUserDBModel]: 

227 matches = await cm_clients_col.find_batch({"clients_ids": client_id}) 

228 users = await users_col.find_batch( 

229 { 

230 "id": { 

231 "$in": [m[ManagersClientsDBModel.manager_id] for m in matches] 

232 } 

233 } 

234 ) 

235 return GenericGetListResponse[SotransUserDBModel]( 

236 total=len(users), items=users 

237 ) 

238 

239 

240async def on_get_client_suggestion( 

241 search_q: str, 

242 user: SotransOIDCUserModel, 

243 assignment_enabled: bool, 

244 skip: int, 

245 limit: int, 

246) -> list[ClientDBModel]: 

247 query = {} 

248 assigned_clients = None 

249 if not has_role(user, SotransRole.company_director): 

250 assigned_clients = await get_assigned_clients_ids(user) 

251 elif assignment_enabled is True: 

252 clients_relation = await cm_clients_col.find_single( 

253 "manager_id", user.sub 

254 ) 

255 if not clients_relation: 

256 return [] 

257 assigned_clients = ManagersClientsDBModel( 

258 **clients_relation 

259 ).clients_ids 

260 if assigned_clients is not None and search_q: 

261 ids_with_text = await opensearch_adapter.vector_search( 

262 f"{config.MONGO_DB_NAME}.{clients_col.collection_name}", 

263 search_q, 

264 k=200, 

265 ) 

266 ids_in = list(set(assigned_clients).intersection(set(ids_with_text))) 

267 query.update({"id": {"$in": ids_in}}) 

268 elif search_q: 

269 query.update(await get_ids_query(clients_col, search_q)) 

270 if not ( 

271 query.get("id", {}).get("$in", []) 

272 or query.get("_id", {}).get("$in", []) 

273 ): 

274 clients_fallback_q = ( 

275 {"_id": {"$in": assigned_clients}} if assigned_clients else {} 

276 ) 

277 return [ 

278 ClientDBModel(**c) 

279 for c in await clients_col.find_batch( 

280 build_search_query(search_q, clients_col.collection_name) 

281 | clients_fallback_q, 

282 skip, 

283 limit, 

284 ) 

285 ] 

286 clients = await clients_col.find_batch(query, skip, limit) 

287 return [ClientDBModel(**client) for client in clients]