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