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
« prev ^ index » next coverage.py v7.6.2, created at 2024-10-10 03:02 +0300
1import contextlib
2import uuid
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
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 )
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
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 )
137 await update_clients_in_orders(client_id, updated)
138 return updated
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 )
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})
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 )
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 )
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 )
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]