orders.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590
  1. import time
  2. from logging import getLogger
  3. from threading import Thread
  4. from uuid import uuid4
  5. from fastapi import HTTPException, APIRouter, Depends
  6. from fastapi.responses import JSONResponse
  7. from fudo import fudo
  8. from models.sales import ItemWeb, OrderWeb
  9. from models.items import Item, Order
  10. from models.user import User
  11. from services.fudo_service import add_product_to_fudo
  12. from services.email_service import get_email_sender
  13. from services.logging_service import log_order, structured_logger, LogLevel
  14. import services.print_service as ps
  15. from services.data_service import DataServiceFactory
  16. from config.mails import PRINTER_DISCONNECTED_MAIL
  17. from config.messages import ErrorResponse, SuccessResponse, UserResponse
  18. from config.settings import DEVELOPMENT
  19. from auth.security import get_current_user
  20. logger = getLogger(__name__)
  21. # Data services initialization
  22. user_data_service = DataServiceFactory.get_user_service()
  23. product_data_service = DataServiceFactory.get_product_service()
  24. sale_data_service = DataServiceFactory.get_sales_service()
  25. # Global variables
  26. printer_orders = []
  27. order_router = APIRouter()
  28. name_promo = "Shop"
  29. @order_router.post("/send")
  30. async def printer_order(order: OrderWeb, current_user: User = Depends(get_current_user)):
  31. """Process printer order"""
  32. logger.info(f"Printer order received from user {current_user.email} for table {order.table}")
  33. structured_logger.log_order_event(
  34. f"Print order received for table {order.table}",
  35. LogLevel.INFO,
  36. {
  37. "table": order.table,
  38. "items_count": len(order.items),
  39. "customer_id": order.customerId,
  40. "total_amount": order.totalAmount,
  41. "order_date": order.orderDate
  42. },
  43. user_id=current_user.id,
  44. user_email=current_user.email
  45. )
  46. # Printer status validation
  47. if not DEVELOPMENT:
  48. try:
  49. printer_status = ps.get_status()
  50. if not printer_status:
  51. logger.error(f"Printer is not connected. Order from user {current_user.email} cannot be processed.")
  52. structured_logger.log_print_event(
  53. f"Order rejected due to printer disconnection",
  54. LogLevel.ERROR,
  55. {
  56. "table": order.table,
  57. "user_id": current_user.id,
  58. "user_email": current_user.email,
  59. "items_count": len(order.items)
  60. },
  61. user_id=current_user.id,
  62. user_email=current_user.email
  63. )
  64. # Send notification email to admins
  65. email_thread = Thread(
  66. target=get_email_sender().send_email,
  67. args=(
  68. PRINTER_DISCONNECTED_MAIL["subject"],
  69. PRINTER_DISCONNECTED_MAIL["body"],
  70. ["erwinjacimino2003@gmail.com", "mompyn@gmail.com"]
  71. ),
  72. daemon=True
  73. )
  74. email_thread.start()
  75. structured_logger.log_email_event(
  76. "Printer disconnection notification sent to admins",
  77. LogLevel.WARNING,
  78. {
  79. "trigger_user": current_user.email,
  80. "admin_emails": ["erwinjacimino2003@gmail.com", "mompyn@gmail.com"]
  81. },
  82. user_id=current_user.id,
  83. user_email=current_user.email
  84. )
  85. return JSONResponse(status_code=424, content={"message": ErrorResponse.PRINTER_DISCONNECTED})
  86. except Exception as e:
  87. logger.error(f"Error checking printer status: {e}")
  88. structured_logger.log_print_event(
  89. "Printer status check failed",
  90. LogLevel.ERROR,
  91. {
  92. "error": str(e),
  93. "error_type": type(e).__name__,
  94. "user_id": current_user.id
  95. },
  96. user_id=current_user.id,
  97. user_email=current_user.email
  98. )
  99. return JSONResponse(status_code=500, content={"message": "Error interno del servidor"})
  100. # Extract order data
  101. items = order.items
  102. table = order.table
  103. # Input validation
  104. if not items or not table:
  105. logger.warning(f"Invalid order data from user {current_user.email}: missing items or table")
  106. structured_logger.log_order_event(
  107. "Order validation failed: missing items or table",
  108. LogLevel.WARNING,
  109. {
  110. "has_items": bool(items),
  111. "table": table,
  112. "items_count": len(items) if items else 0
  113. },
  114. user_id=current_user.id,
  115. user_email=current_user.email
  116. )
  117. return JSONResponse(status_code=400, content={"message": ErrorResponse.MISSING_FIELDS})
  118. if not isinstance(table, int):
  119. logger.warning(f"Invalid table type from user {current_user.email}: {type(table)}")
  120. structured_logger.log_order_event(
  121. "Order validation failed: invalid table type",
  122. LogLevel.WARNING,
  123. {
  124. "table": table,
  125. "table_type": str(type(table))
  126. },
  127. user_id=current_user.id,
  128. user_email=current_user.email
  129. )
  130. return JSONResponse(status_code=400, content={"message": ErrorResponse.INVALID_TABLE_TYPE})
  131. logger.info(f"Processing order for table {table} with {len(items)} items")
  132. structured_logger.log_order_event(
  133. f"Starting order processing for table {table}",
  134. LogLevel.INFO,
  135. {
  136. "table": table,
  137. "items_count": len(items),
  138. "item_ids": [item.id for item in items],
  139. "item_quantities": [item.quantity for item in items]
  140. },
  141. user_id=current_user.id,
  142. user_email=current_user.email
  143. )
  144. # Add products to Fudo
  145. product_errors = []
  146. # Get products data
  147. try:
  148. products = product_data_service.get_products([item.id for item in items])
  149. logger.info(f"Retrieved {len(products)} products from database")
  150. structured_logger.log_order_event(
  151. f"Retrieved products for order processing",
  152. LogLevel.INFO,
  153. {
  154. "table": table,
  155. "requested_products": len(items),
  156. "found_products": len(products),
  157. "product_ids": [p.id for p in products]
  158. },
  159. user_id=current_user.id,
  160. user_email=current_user.email
  161. )
  162. except Exception as e:
  163. error_msg = f"Error retrieving products: {e}"
  164. logger.error(error_msg)
  165. structured_logger.log_database_event(
  166. "Failed to retrieve products for order",
  167. LogLevel.ERROR,
  168. {
  169. "table": table,
  170. "requested_product_ids": [item.id for item in items],
  171. "error": str(e),
  172. "error_type": type(e).__name__
  173. },
  174. user_id=current_user.id,
  175. user_email=current_user.email
  176. )
  177. return JSONResponse(status_code=500, content={"message": "Error interno del servidor"})
  178. beers_for_promo = 0
  179. try:
  180. fudo.get_token()
  181. logger.info("Fudo token obtained successfully")
  182. for item, product in zip(items, products):
  183. try:
  184. # Si es dia de promo
  185. if time.localtime().tm_wday + 1 == product.promo_day and product.promo_id:
  186. logger.info(f"Applying promotion for product {product.id} on table {table}")
  187. fudo_product = add_product_to_fudo(product.promo_id, item.quantity, table)
  188. structured_logger.log_order_event(
  189. f"Promotion applied for product {product.name}",
  190. LogLevel.INFO,
  191. {
  192. "product_id": product.id,
  193. "promo_id": product.promo_id,
  194. "table": table,
  195. "quantity": item.quantity,
  196. "promo_day": product.promo_day,
  197. "current_day": time.localtime().tm_wday + 1
  198. },
  199. user_id=current_user.id,
  200. user_email=current_user.email
  201. )
  202. #en caso contrario
  203. else:
  204. if product.type == name_promo:
  205. beers_for_promo += item.quantity
  206. logger.debug(f"Added {item.quantity} beers for promotion calculation")
  207. fudo_product = add_product_to_fudo(item.id, item.quantity, table)
  208. structured_logger.log_order_event(
  209. f"Added product {product.name} to Fudo",
  210. LogLevel.INFO,
  211. {
  212. "product_id": item.id,
  213. "product_name": product.name,
  214. "product_type": product.type,
  215. "table": table,
  216. "quantity": item.quantity,
  217. "is_beer": product.type == name_promo
  218. },
  219. user_id=current_user.id,
  220. user_email=current_user.email
  221. )
  222. if not fudo_product:
  223. error_msg = f"Error adding product {item.id} to table {table}."
  224. product_errors.append(error_msg)
  225. logger.error(error_msg)
  226. structured_logger.log_order_event(
  227. f"Failed to add product to Fudo",
  228. LogLevel.ERROR,
  229. {
  230. "product_id": item.id,
  231. "table": table,
  232. "quantity": item.quantity
  233. },
  234. user_id=current_user.id,
  235. user_email=current_user.email
  236. )
  237. except Exception as e:
  238. error_msg = f"Error processing product {item.id}: {e}"
  239. logger.error(error_msg)
  240. product_errors.append(error_msg)
  241. structured_logger.log_order_event(
  242. f"Error processing product {item.id}",
  243. LogLevel.ERROR,
  244. {
  245. "product_id": item.id,
  246. "table": table,
  247. "error": str(e),
  248. "error_type": type(e).__name__
  249. },
  250. user_id=current_user.id,
  251. user_email=current_user.email
  252. )
  253. except Exception as e:
  254. error_msg = f"Error with Fudo integration: {e}"
  255. logger.error(error_msg)
  256. structured_logger.log_order_event(
  257. "Fudo integration error",
  258. LogLevel.ERROR,
  259. {
  260. "table": table,
  261. "error": str(e),
  262. "error_type": type(e).__name__
  263. },
  264. user_id=current_user.id,
  265. user_email=current_user.email
  266. )
  267. return JSONResponse(status_code=500, content={"message": "Error interno del servidor"})
  268. if product_errors:
  269. logger.error(f"Product errors occurred: {product_errors}")
  270. structured_logger.log_order_event(
  271. "Order processing failed due to product errors",
  272. LogLevel.ERROR,
  273. {
  274. "table": table,
  275. "product_errors": product_errors,
  276. "error_count": len(product_errors)
  277. },
  278. user_id=current_user.id,
  279. user_email=current_user.email
  280. )
  281. return JSONResponse(
  282. status_code=424,
  283. content={"message": ErrorResponse.PRODUCT_ADD_ERROR, "errors": product_errors}
  284. )
  285. # User validation
  286. try:
  287. user = user_data_service.get_by_id(order.customerId)
  288. if not user:
  289. logger.warning(f"User not found: {order.customerId}")
  290. structured_logger.log_user_event(
  291. f"Order rejected: user not found",
  292. LogLevel.WARNING,
  293. {
  294. "requested_user_id": order.customerId,
  295. "table": table
  296. },
  297. user_id=current_user.id,
  298. user_email=current_user.email
  299. )
  300. return JSONResponse(status_code=404, content={"message": UserResponse.USER_NOT_FOUND.format(user_id=order.customerId)})
  301. logger.info(f"Order customer validated: {user.email}")
  302. structured_logger.log_user_event(
  303. f"Order customer validated",
  304. LogLevel.INFO,
  305. {
  306. "customer_id": user.id,
  307. "customer_email": user.email,
  308. "customer_name": user.name,
  309. "table": table
  310. },
  311. user_id=current_user.id,
  312. user_email=current_user.email
  313. )
  314. except Exception as e:
  315. error_msg = f"Error validating user {order.customerId}: {e}"
  316. logger.error(error_msg)
  317. structured_logger.log_database_event(
  318. "User validation error during order processing",
  319. LogLevel.ERROR,
  320. {
  321. "requested_user_id": order.customerId,
  322. "table": table,
  323. "error": str(e),
  324. "error_type": type(e).__name__
  325. },
  326. user_id=current_user.id,
  327. user_email=current_user.email
  328. )
  329. return JSONResponse(status_code=500, content={"message": "Error interno del servidor"})
  330. # Get active sale
  331. try:
  332. active_sale_id = fudo.get_active_sale(fudo.get_table(table))
  333. if not active_sale_id:
  334. error_msg = f"No active sale found for table {table}"
  335. logger.error(error_msg)
  336. structured_logger.log_order_event(
  337. "Order failed: no active sale found",
  338. LogLevel.ERROR,
  339. {
  340. "table": table,
  341. "customer_id": order.customerId
  342. },
  343. user_id=current_user.id,
  344. user_email=current_user.email
  345. )
  346. raise HTTPException(status_code=404, detail=error_msg)
  347. active_sale_id = active_sale_id['id']
  348. logger.info(f"Active sale found for table {table}: {active_sale_id}")
  349. structured_logger.log_order_event(
  350. f"Active sale retrieved for table {table}",
  351. LogLevel.INFO,
  352. {
  353. "table": table,
  354. "sale_id": active_sale_id
  355. },
  356. user_id=current_user.id,
  357. user_email=current_user.email
  358. )
  359. except HTTPException:
  360. raise
  361. except Exception as e:
  362. error_msg = f"Error retrieving active sale for table {table}: {e}"
  363. logger.error(error_msg)
  364. structured_logger.log_order_event(
  365. "Error retrieving active sale",
  366. LogLevel.ERROR,
  367. {
  368. "table": table,
  369. "error": str(e),
  370. "error_type": type(e).__name__
  371. },
  372. user_id=current_user.id,
  373. user_email=current_user.email
  374. )
  375. raise HTTPException(status_code=500, detail="Error interno del servidor")
  376. # Update user reward progress
  377. try:
  378. new_progress = user.reward_progress + beers_for_promo * 10
  379. user_data_service.set_reward_progress(user.id, new_progress)
  380. logger.info(f"Updated reward progress for user {user.email}: {user.reward_progress} -> {new_progress}")
  381. structured_logger.log_user_event(
  382. f"Reward progress updated",
  383. LogLevel.INFO,
  384. {
  385. "user_id": user.id,
  386. "old_progress": user.reward_progress,
  387. "new_progress": new_progress,
  388. "beers_count": beers_for_promo,
  389. "points_added": beers_for_promo * 10,
  390. "table": table
  391. },
  392. user_id=user.id,
  393. user_email=user.email
  394. )
  395. except Exception as e:
  396. error_msg = f"Error updating reward progress for user {user.id}: {e}"
  397. logger.error(error_msg)
  398. structured_logger.log_database_event(
  399. "Failed to update reward progress",
  400. LogLevel.ERROR,
  401. {
  402. "user_id": user.id,
  403. "beers_for_promo": beers_for_promo,
  404. "error": str(e),
  405. "error_type": type(e).__name__
  406. },
  407. user_id=user.id,
  408. user_email=user.email
  409. )
  410. # Don't fail the order for this, just log it
  411. new_progress = user.reward_progress
  412. # Create sale record
  413. try:
  414. sale = sale_data_service.create(
  415. order.customerId,
  416. active_sale_id or uuid4().hex,
  417. order.totalAmount,
  418. order.table,
  419. [item.id for item in items],
  420. [item.quantity for item in items]
  421. )
  422. if sale > 0:
  423. logger.info(f"Sale created successfully: ID {sale}")
  424. structured_logger.log_order_event(
  425. f"Sale record created successfully",
  426. LogLevel.INFO,
  427. {
  428. "sale_id": sale,
  429. "customer_id": order.customerId,
  430. "table": table,
  431. "total_amount": order.totalAmount,
  432. "fudo_sale_id": active_sale_id,
  433. "items_count": len(items)
  434. },
  435. user_id=current_user.id,
  436. user_email=current_user.email
  437. )
  438. else:
  439. error_msg = "Failed to create sale record"
  440. logger.error(error_msg)
  441. structured_logger.log_database_event(
  442. "Sale creation failed",
  443. LogLevel.ERROR,
  444. {
  445. "customer_id": order.customerId,
  446. "table": table,
  447. "total_amount": order.totalAmount,
  448. "fudo_sale_id": active_sale_id
  449. },
  450. user_id=current_user.id,
  451. user_email=current_user.email
  452. )
  453. return JSONResponse(status_code=500, content={"message": "Error interno del servidor"})
  454. except Exception as e:
  455. error_msg = f"Error creating sale record: {e}"
  456. logger.error(error_msg)
  457. structured_logger.log_database_event(
  458. "Sale creation error",
  459. LogLevel.ERROR,
  460. {
  461. "customer_id": order.customerId,
  462. "table": table,
  463. "error": str(e),
  464. "error_type": type(e).__name__
  465. },
  466. user_id=current_user.id,
  467. user_email=current_user.email
  468. )
  469. return JSONResponse(status_code=500, content={"message": "Error interno del servidor"})
  470. # Print order
  471. try:
  472. order_for_print = Order(
  473. table=table,
  474. items=[Item(name=product.name, price=product.price or 0, quantity=item.quantity) for product, item in zip(products, items)],
  475. customerName=user.name,
  476. totalAmount=order.totalAmount,
  477. orderDate=order.orderDate
  478. )
  479. ps.print_order(order_for_print)
  480. logger.info(f"Order printed successfully for table {table}")
  481. structured_logger.log_print_event(
  482. f"Order printed successfully for table {table}",
  483. LogLevel.INFO,
  484. {
  485. "table": table,
  486. "customer_name": user.name,
  487. "total_amount": order.totalAmount,
  488. "items_count": len(items),
  489. "sale_id": sale
  490. },
  491. user_id=current_user.id,
  492. user_email=current_user.email
  493. )
  494. except Exception as e:
  495. error_msg = f"Error printing order for table {table}: {e}"
  496. logger.error(error_msg)
  497. structured_logger.log_print_event(
  498. f"Order print failed for table {table}",
  499. LogLevel.ERROR,
  500. {
  501. "table": table,
  502. "customer_name": user.name,
  503. "error": str(e),
  504. "error_type": type(e).__name__,
  505. "sale_id": sale
  506. },
  507. user_id=current_user.id,
  508. user_email=current_user.email
  509. )
  510. # Don't fail the order for print issues, just log it
  511. # Log order (legacy function)
  512. try:
  513. log_order(user.name, order.table, order_date=order.orderDate, items=[product.name for product in products])
  514. structured_logger.log_order_event(
  515. f"Order completed successfully for table {table}",
  516. LogLevel.INFO,
  517. {
  518. "table": table,
  519. "customer_id": user.id,
  520. "customer_name": user.name,
  521. "customer_email": user.email,
  522. "total_amount": order.totalAmount,
  523. "items": [{"name": product.name, "quantity": item.quantity, "price": product.price} for product, item in zip(products, items)],
  524. "sale_id": sale,
  525. "new_reward_progress": new_progress,
  526. "beers_for_promo": beers_for_promo
  527. },
  528. user_id=user.id,
  529. user_email=user.email
  530. )
  531. except Exception as e:
  532. logger.error(f"Error in legacy order logging: {e}")
  533. # Don't fail the order for logging issues
  534. logger.info(f"Order processing completed successfully for table {table}, sale ID: {sale}")
  535. return JSONResponse({"message": SuccessResponse.ORDER_SUCCESS, "new_progress": new_progress})