orders.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. import time
  2. from logging import getLogger
  3. from threading import Thread
  4. from typing import List
  5. from uuid import uuid4
  6. from pydantic import BaseModel
  7. from enums.locations import Locations
  8. from fastapi import HTTPException, APIRouter, Depends
  9. from fastapi.responses import JSONResponse
  10. from fudo import fudo
  11. from models.sales import ItemWeb, OrderWeb
  12. from models.items import Item, Order, OrderBilling, Product
  13. from models.user import User
  14. from services.fudo_service import add_product_to_fudo
  15. from services.email_service import get_email_sender
  16. import services.print_service as ps
  17. from services.data_service import DataServiceFactory
  18. from config.mails import PRINTER_DISCONNECTED_MAIL
  19. from config.messages import ErrorResponse, SuccessResponse, UserResponse
  20. from config.settings import DEVELOPMENT
  21. from auth.security import get_current_user
  22. from data.product_category import CAT_ITEMS
  23. from utils.responses import error_response, success_response
  24. from datetime import datetime
  25. import subprocess
  26. logger = getLogger(__name__)
  27. # Data services initialization
  28. user_data_service = DataServiceFactory.get_user_service()
  29. product_data_service = DataServiceFactory.get_product_service()
  30. sale_data_service = DataServiceFactory.get_sales_service()
  31. # Global variables
  32. printer_orders = []
  33. order_router = APIRouter()
  34. name_promo = "Cervezas"
  35. class ComparePricesResponse(BaseModel):
  36. product: Product
  37. oldPrice: int
  38. newPrice: int
  39. isAvailable: bool
  40. def compare_prices(products: List[Product], items: List[ItemWeb]) -> list:
  41. """Compare prices of products and items and return the cheapest product"""
  42. # Initialize a dictionary to store the prices
  43. prices: List[ComparePricesResponse] = []
  44. for product, item in zip(products, items):
  45. if product.status == 0:
  46. prices.append(
  47. ComparePricesResponse(
  48. product=product,
  49. oldPrice=item.price,
  50. newPrice=product.price,
  51. isAvailable=False
  52. )
  53. )
  54. continue
  55. if product.price != item.price:
  56. prices.append(
  57. ComparePricesResponse(
  58. product=product,
  59. oldPrice=item.price,
  60. newPrice=product.price,
  61. isAvailable=True
  62. )
  63. )
  64. return list(map(lambda x: x.model_dump(), prices))
  65. @order_router.post("/send")
  66. async def printer_order(order: OrderWeb, current_user: User = Depends(get_current_user)):
  67. """Process printer order"""
  68. logger.info(f"Printer order received from user {current_user.email} for table {order.table}")
  69. # Extract order data
  70. items = order.items
  71. table = order.table
  72. # Input validation
  73. if not items or not table:
  74. logger.warning(f"Invalid order data from user {current_user.email}: missing items or table")
  75. return error_response(message=ErrorResponse.MISSING_FIELDS, status_code=400)
  76. if not isinstance(table, int):
  77. logger.warning(f"Invalid table type from user {current_user.email}: {type(table)}")
  78. return error_response(message=ErrorResponse.INVALID_TABLE_TYPE, status_code=400)
  79. logger.info(f"Processing order for table {table} with {len(items)} items")
  80. # Get products data
  81. try:
  82. products = await product_data_service.get_products([item.id for item in items])
  83. # Me aseguro de que los items y los productos esten en el mismo orden
  84. products = list(sorted(products, key=lambda x: x.id))
  85. items = list(sorted(items, key=lambda x: x.id))
  86. logger.info(f"Retrieved {len(products)} products from database")
  87. except Exception as e:
  88. error_msg = f"Error getting products: {e}"
  89. logger.error(error_msg)
  90. return error_response(message=error_msg, status_code=500)
  91. # Comparo los precios de los items con los productos
  92. prices = compare_prices(products, items)
  93. if prices:
  94. return success_response(data=prices, message="El estado de los productos y items coincide con el de la venta", status_code=409)
  95. printers = {
  96. "ServerPrincipal": "10.10.12.3"
  97. }
  98. # Lista para almacenar impresoras fallidas directamente
  99. failed_printers = []
  100. # Validación de estado
  101. for name, ip in printers.items():
  102. try:
  103. # '-c 1' para Linux/Mac, '-W 2' timeout de 2 segundos para evitar bloqueos largos
  104. # stdout=subprocess.DEVNULL silencia la salida en consola
  105. response = subprocess.run(
  106. ["ping", "-c", "1", "-W", "2", ip],
  107. stdout=subprocess.DEVNULL,
  108. stderr=subprocess.DEVNULL
  109. )
  110. if response.returncode != 0:
  111. failed_printers.append(name)
  112. except Exception as e:
  113. logger.error(f"Error executing ping to {name}: {e}")
  114. failed_printers.append(name)
  115. # Procesamiento de errores
  116. if not DEVELOPMENT:
  117. if failed_printers:
  118. logger.error(f"Printer is not connected: {failed_printers}. Order from user {current_user.email} cannot be processed.")
  119. try:
  120. locations_str = ", ".join(failed_printers)
  121. email_thread = Thread(
  122. target=get_email_sender().send_email,
  123. args=(
  124. PRINTER_DISCONNECTED_MAIL["subject"].format(location=locations_str),
  125. PRINTER_DISCONNECTED_MAIL["body"].format(
  126. location=locations_str,
  127. timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S")
  128. ),
  129. ["erwinjacimino2003@gmail.com", "mompyn@gmail.com"]
  130. ),
  131. daemon=True
  132. )
  133. email_thread.start()
  134. return error_response(message=ErrorResponse.PRINTER_DISCONNECTED, status_code=424)
  135. except Exception as e:
  136. logger.error(f"Error sending notification: {e}")
  137. # Retornamos el error original de impresora, no el del email
  138. return error_response(message=ErrorResponse.PRINTER_DISCONNECTED, status_code=424)
  139. # Input validation
  140. # Add products to Fudo
  141. product_errors = []
  142. beers_for_promo = 0
  143. try:
  144. fudo.get_token()
  145. logger.info("Fudo token obtained successfully")
  146. for item, product in zip(items, products):
  147. try:
  148. if product.type == name_promo and current_user.name != "Guest":
  149. beers_for_promo += item.quantity
  150. logger.debug(f"Added {item.quantity} beers for promotion calculation")
  151. fudo_product = add_product_to_fudo(item.id, item.quantity, table)
  152. logger.info(f"Added product {item.id} to table {table} with quantity {item.quantity} in fudo")
  153. if not fudo_product:
  154. error_msg = f"Error adding product {item.id} to table {table}."
  155. product_errors.append(error_msg)
  156. logger.error(error_msg)
  157. except Exception as e:
  158. error_msg = f"Error processing product {item.id}: {e}"
  159. logger.error(error_msg)
  160. product_errors.append(error_msg)
  161. except Exception as e:
  162. error_msg = f"Error with Fudo integration: {e}"
  163. logger.error(error_msg)
  164. return error_response(message=error_msg, status_code=500)
  165. if product_errors:
  166. logger.error(f"Product errors occurred: {product_errors}")
  167. return error_response(error={"errors": product_errors}, message=ErrorResponse.PRODUCT_ADD_ERROR, status_code=424)
  168. # Get active sale
  169. try:
  170. active_sale_id = fudo.get_active_sale(fudo.get_table(table))
  171. if not active_sale_id:
  172. error_msg = f"No active sale found for table {table}"
  173. logger.error(error_msg)
  174. return error_response(message=error_msg, status_code=404)
  175. active_sale_id = active_sale_id['id']
  176. logger.info(f"Active sale found for table {table}: {active_sale_id}")
  177. except Exception as e:
  178. error_msg = f"Error retrieving active sale for table {table}: {e}"
  179. logger.error(error_msg)
  180. return error_response(message=error_msg, status_code=500)
  181. # Update user reward progress
  182. try:
  183. new_progress = current_user.reward_progress + beers_for_promo * 10
  184. user_data_service.set_reward_progress(current_user.id, new_progress)
  185. logger.info(f"Updated reward progress for user {current_user.email}: {current_user.reward_progress} -> {new_progress}")
  186. except Exception as e:
  187. error_msg = f"Error updating reward progress for user {current_user.id}: {e}"
  188. logger.error(error_msg)
  189. # Don't fail the order for this, just log it
  190. new_progress = current_user.reward_progress
  191. # Create sale record
  192. try:
  193. sale = sale_data_service.create(
  194. order.customerId,
  195. active_sale_id or uuid4().hex,
  196. order.totalAmount,
  197. order.table,
  198. [item.id for item in items],
  199. [item.quantity for item in items]
  200. )
  201. if sale > 0:
  202. logger.info(f"Sale created successfully: ID {sale}")
  203. else:
  204. error_msg = "Failed to create sale record"
  205. logger.error(error_msg)
  206. return error_response(message=error_msg, status_code=500)
  207. except Exception as e:
  208. error_msg = f"Error creating sale record: {e}"
  209. logger.error(error_msg)
  210. return error_response(message=error_msg, status_code=500)
  211. # Print order
  212. try:
  213. print(products)
  214. items = [ Item(name=product.name, price=product.price, quantity=item.quantity, comment=item.comment, kitchen_id=product.kitchen_id) for item, product in zip(items, products)]#type: ignore
  215. if items:
  216. ps.print_order(Order(table=table, items=items, customerName=current_user.name, totalAmount=order.totalAmount, orderDate=order.orderDate))
  217. logger.info(f"Order printed successfully for table {table}")
  218. except Exception as e:
  219. error_msg = f"Error printing order for table {table}: {e}"
  220. logger.error(error_msg)
  221. return error_response(message=error_msg, status_code=500)
  222. # Don't fail the order for print issues, just log it
  223. logger.info(f"Logging order for table {table} with sale ID {sale}, products= {[(item.name, item.quantity) for item in items]}")
  224. logger.info(f"Order processing completed successfully for table {table}, sale ID: {sale}")
  225. return success_response(data={"new_progress": new_progress}, message=SuccessResponse.ORDER_SUCCESS)
  226. @order_router.post("/billing")
  227. async def billing_order(order: OrderBilling, current_user: User = Depends(get_current_user)):
  228. """Process billing order"""
  229. printing = ps.print_billing(order)
  230. if not printing:
  231. return error_response(message=ErrorResponse.PRINTER_DISCONNECTED, status_code=424)
  232. return success_response(data=printing, message=SuccessResponse.ORDER_SUCCESS)