Coverage for api/operations/orders.py: 12%

174 statements  

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

1import datetime 

2from typing import Any 

3 

4from bson import ObjectId 

5from dbcc import MongoTableEngine 

6from exceptions import BadParameterHTTPError, NotFoundHTTPError 

7from mongodb import ( 

8 clients_col, 

9 docs_col, 

10 drivers_col, 

11 orders_col, 

12 orgs_col, 

13 subsidiaries_col, 

14 trailers_col, 

15 trucks_col, 

16 users_col, 

17) 

18from pymongo.errors import DuplicateKeyError 

19from pymongo.results import InsertOneResult 

20from services.documents import documents_querier 

21from sotrans_models.models._base import InsertByOIDModel 

22from sotrans_models.models.misc.client import ClientDBModel 

23from sotrans_models.models.misc.document import ( 

24 DocumentCreateModel, 

25 DocumentDBModel, 

26 RequestDocumentType, 

27) 

28from sotrans_models.models.misc.update import UpdateMetadata 

29from sotrans_models.models.orders.order import ( 

30 ConfirmedOrderUpdateModel, 

31 OrderCreateModel, 

32 OrderDBModel, 

33 OrderStatus, 

34 OrderUpdateModel, 

35) 

36from sotrans_models.models.resources.trucks import TruckType 

37from sotrans_models.models.users import SotransOIDCUserModel 

38from sotrans_models.utils.text_mappers import ( 

39 get_clients_text_search, 

40 get_documents_text_search, 

41 update_text_search_for_data, 

42) 

43from utils.check_carriers_resources import check_verification 

44from utils.dt_utils import get_current_datetime 

45from utils.helper import ( 

46 Order, 

47 get_hash, 

48 place_stop_indicies, 

49 update_order_address, 

50) 

51 

52 

53async def fill_carrier_by_oid_company( 

54 order_data: dict, order: OrderCreateModel | OrderUpdateModel 

55): 

56 if order.carrier and "id" in order.carrier.model_fields_set: 

57 if order.carrier.id is None: 

58 order_data["carrier"] = None 

59 else: 

60 carrier = await orgs_col.find_single( 

61 "_id", ObjectId(order.carrier.id), projection={"documents": 0} 

62 ) 

63 if not carrier: 

64 raise NotFoundHTTPError("перевозчик") 

65 order_data["carrier"] = carrier 

66 

67 

68async def fill_orders_subsidiary_or_logistician( 

69 data: dict[str, Any], order: Order 

70): 

71 if "assigned" in data: 

72 del data["assigned"] 

73 if not (order.assigned and order.assigned.company): 

74 return 

75 if ( 

76 order.assigned.company.subsidiary 

77 and "id" in order.assigned.company.subsidiary.model_fields_set 

78 ): 

79 data["assigned"] = {"company": {}} 

80 if order.assigned.company.subsidiary.id is None: 

81 data["assigned"]["company"]["subsidiary"] = None 

82 subsidiary = await subsidiaries_col.find_single( 

83 "_id", order.assigned.company.subsidiary.id 

84 ) 

85 if subsidiary: 

86 data["assigned"]["company"]["subsidiary"] = subsidiary 

87 if ( 

88 order.assigned.company.employee 

89 and "id" in order.assigned.company.employee.model_fields_set 

90 ): 

91 if "assigned" not in data: 

92 data["assigned"] = {"company": {}} 

93 if order.assigned.company.employee.id is None: 

94 data["assigned"]["company"]["employee"] = None 

95 company_logistician = await users_col.find_single( 

96 "id", order.assigned.company.employee.id 

97 ) 

98 if company_logistician is None: 

99 return 

100 data["assigned"]["company"]["employee"] = company_logistician 

101 

102 

103async def fill_orders_client( 

104 data: dict[str, Any], 

105 order: OrderCreateModel | OrderUpdateModel, 

106 by_external: bool = False, 

107): 

108 if "client" not in data: 

109 return # patch case 

110 if "id" in order.client.model_fields_set: 

111 if order.client.id is None: 

112 raise BadParameterHTTPError("нет клиента") 

113 else: 

114 client_data = await clients_col.find_single( 

115 "_id", ObjectId(data["client"]["id"]) 

116 ) 

117 if client_data is None: 

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

119 elif ( 

120 "inn" in data["client"] 

121 and data["client"]["inn"] 

122 and "kpp" in data["client"] 

123 and data["client"]["kpp"] 

124 ): 

125 client_data = await clients_col.collection.find_one( 

126 { 

127 ClientDBModel.inn: data["client"]["inn"], 

128 ClientDBModel.kpp: data["client"]["kpp"], 

129 } 

130 ) 

131 if not client_data: 

132 if by_external: 

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

134 try: 

135 client_data = await clients_col.create(data["client"]) 

136 except DuplicateKeyError: 

137 raise BadParameterHTTPError( 

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

139 ) 

140 client_model = ClientDBModel(**client_data) 

141 text_search = get_clients_text_search(client_model) 

142 await clients_col.update_by_id( 

143 client_model.id, {ClientDBModel.text_search: text_search} 

144 ) 

145 else: 

146 return 

147 

148 data["client"] = client_data 

149 

150 

151async def prepare_to_create_order( 

152 executor: SotransOIDCUserModel, 

153 order: OrderCreateModel, 

154 by_external: bool = False, 

155) -> dict: 

156 place_stop_indicies(order) 

157 

158 order_data = order.model_dump() 

159 await fill_orders_client(order_data, order, by_external=by_external) 

160 await update_order_address(order_data, by_external=by_external) 

161 order_data["created_at"] = get_current_datetime() 

162 order_data["updated"] = [ 

163 UpdateMetadata( 

164 timestamp=order_data["created_at"], 

165 previous_values=None, 

166 author=executor.sub, 

167 ).model_dump() 

168 ] 

169 order_data["etag"] = get_hash(order_data) 

170 order_data["status"] = OrderStatus.buffer.value 

171 return order_data 

172 

173 

174async def update_documents_data( 

175 order_id: ObjectId, 

176 order: ConfirmedOrderUpdateModel | OrderCreateModel | OrderUpdateModel, 

177 order_data: dict, 

178 carrier_container: dict | bool = False, 

179): 

180 """ 

181 :param carrier_container: is used to get existing carrier from order data 

182 False if it is not possible, 

183 True if needs to be retrieved, 

184 order_data if it is queried already 

185 """ 

186 if not order.documents: 

187 return 

188 order_data["documents"] = [] 

189 doc_insert_ids = [ 

190 ObjectId(document.id) 

191 for document in order.documents 

192 if isinstance(document, InsertByOIDModel) and document.id 

193 ] 

194 if doc_insert_ids: 

195 doc_inserts = await docs_col.find_batch( 

196 {"_id": {"$in": doc_insert_ids}} 

197 ) 

198 for doc in doc_inserts: 

199 check_verification( 

200 doc, f'Document {doc.get("name", "(nameless)")}' 

201 ) 

202 order_data["documents"].extend(doc_inserts) 

203 carrier = None 

204 if carrier_container is True: 

205 carrier_container = await orders_col.find_single("id", order_id) 

206 if carrier_container: 

207 carrier = carrier_container.get("carrier") # type: ignore[union-attr] 

208 for doc in order.documents: 

209 if not isinstance(doc, DocumentCreateModel): 

210 continue 

211 doc_data = doc.model_dump() 

212 doc_data["created_at"] = get_current_datetime() 

213 doc_data["object_id"] = order_id 

214 doc_data["collection"] = "orders" 

215 # fixme organization_id of person who loads 

216 doc_data[DocumentDBModel.text_search] = get_documents_text_search( 

217 DocumentDBModel(**doc_data) 

218 ) 

219 if carrier: 

220 doc_data["organization_id"] = carrier.get("id") 

221 doc_created = await docs_col.create( 

222 DocumentDBModel(**doc_data).model_dump() 

223 ) 

224 order_data["documents"].append(doc_created) 

225 

226 

227async def fill_by_insert_oid_resources_docs( 

228 order_data: dict, 

229 order: ConfirmedOrderUpdateModel | OrderCreateModel, 

230 order_id: ObjectId | None = None, 

231 with_carrier: bool = False, 

232) -> dict: 

233 if order.driver and "id" in order.driver.model_fields_set: 

234 if order.driver.id is None: 

235 order_data[OrderDBModel.driver] = None 

236 else: 

237 order_data["driver"] = await drivers_col.find_single( 

238 "_id", ObjectId(order.driver.id) 

239 ) 

240 check_verification(order_data["driver"], "водитель") 

241 if order.truck and "id" in order.truck.model_fields_set: 

242 if order.truck.id is None: 

243 order_data[OrderDBModel.truck] = None 

244 else: 

245 order_data["truck"] = await trucks_col.find_single( 

246 "_id", ObjectId(order.truck.id) 

247 ) 

248 check_verification(order_data["truck"], "тягач") 

249 if order.trailer and "id" in order.trailer.model_fields_set: 

250 if order.trailer.id is None: 

251 order_data[OrderDBModel.trailer] = None 

252 else: 

253 order_data["trailer"] = await trailers_col.find_single( 

254 "_id", ObjectId(order.trailer.id) 

255 ) 

256 check_verification(order_data["trailer"], "прицеп") 

257 if order_id and order.documents: 

258 await update_documents_data(order_id, order, order_data, with_carrier) 

259 return order_data 

260 

261 

262async def patch_operations(update_data: dict, order: OrderUpdateModel): 

263 await fill_orders_subsidiary_or_logistician(update_data, order) 

264 await fill_orders_client(update_data, order) 

265 await update_order_address(update_data, by_external=False) 

266 

267 

268async def make_data_update_for_confirmed( 

269 up_data: dict, order: OrderUpdateModel | OrderCreateModel 

270): 

271 await fill_carrier_by_oid_company(up_data, order) 

272 await fill_by_insert_oid_resources_docs(up_data, order, with_carrier=False) 

273 up_data[OrderDBModel.status] = OrderStatus.confirmed.value 

274 up_data[ 

275 OrderDBModel.confirmation_end_time 

276 ] = get_current_datetime() + datetime.timedelta(hours=1) 

277 

278 

279async def generate_draft_document( 

280 model_to_update: OrderDBModel, 

281) -> DocumentDBModel: 

282 e = BadParameterHTTPError("указано недостаточно ресурсов") 

283 if not (model_to_update.driver and model_to_update.truck): 

284 raise e 

285 if ( 

286 model_to_update.truck.truck_type == TruckType.tractor_unit 

287 and not model_to_update.trailer 

288 ): 

289 raise e 

290 saved_path = await documents_querier.upload_document(model_to_update) 

291 saved_path = saved_path.strip('"') 

292 doc_name = saved_path.split("/")[-1] 

293 now = get_current_datetime() 

294 documents_draft = DocumentDBModel( 

295 type=RequestDocumentType.order_request_draft.value, 

296 link=saved_path, 

297 name=doc_name, 

298 created_at=now, 

299 organization_id=model_to_update.carrier.id, 

300 ) 

301 return documents_draft 

302 

303 

304def pre_process_to_exchange_auction_end_time( 

305 order_data: dict[str, Any], 

306 order: OrderUpdateModel, 

307 update_data: dict[str, Any], 

308): 

309 auction_end_time = order and order.auction_end_time 

310 if not (order_data.get(OrderDBModel.auction_end_time) or auction_end_time): 

311 raise BadParameterHTTPError("нет времени завершения аукциона") 

312 

313 if auction_end_time: 

314 update_data[OrderDBModel.auction_end_time] = auction_end_time 

315 

316 

317async def clear_documents(documents: list[dict[str, ObjectId | Any]] | None): 

318 if not documents: 

319 return 

320 cdt = get_current_datetime() 

321 await docs_col.collection.update_many( 

322 {"id": {"$in": [d.get("id") for d in documents]}}, 

323 {"$set": {DocumentDBModel.deleted_at: cdt}}, 

324 ) 

325 

326 

327async def create_order_with_second_phase( 

328 order_data: dict, col: MongoTableEngine, *, move: bool = False 

329) -> OrderDBModel: 

330 created: InsertOneResult = await col.collection.insert_one(order_data) 

331 

332 order_model = OrderDBModel(**order_data) 

333 if move is False: 

334 short_id = str(created.inserted_id)[-6:].upper() 

335 order_model.short_id = short_id 

336 order_model.id = created.inserted_id 

337 text_search = update_text_search_for_data(order_model) 

338 update_data = {OrderDBModel.text_search: text_search} 

339 if move is False: 

340 update_data |= { 

341 OrderDBModel.id: order_model.id, 

342 OrderDBModel.short_id: order_model.short_id, 

343 } 

344 await col.update_by_id(created.inserted_id, update_data) 

345 order_model.text_search = text_search 

346 return order_model