fudo.py 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. import math
  2. from time import time
  3. import requests
  4. import os
  5. import redis
  6. from logging import getLogger
  7. logger = getLogger(__name__)
  8. api_token = os.getenv('FUDO_API_KEY')
  9. api_secret = os.getenv('FUDO_API_SECRET')
  10. token = None
  11. token_exp = None
  12. # Configuración de Redis
  13. redis_client = redis.Redis(
  14. host=os.getenv('REDIS_HOST', 'localhost'),
  15. port=int(os.getenv('REDIS_PORT', 6379)),
  16. db=int(os.getenv('REDIS_DB', 0)),
  17. decode_responses=True
  18. )
  19. REDIS_TOKEN_KEY = 'fudo_api_token'
  20. def get_token():
  21. """
  22. Obtiene el token de autenticación de Fudo API.
  23. Primero verifica si existe un token válido en Redis.
  24. Si no existe o ha expirado, solicita uno nuevo y lo guarda en Redis con expiración automática.
  25. """
  26. global token, token_exp
  27. # Intento de obtener el token desde la RAM
  28. if token and token_exp and time() < token_exp:
  29. logger.info("Token obtenido desde variable global")
  30. return token
  31. try:
  32. # Intentar obtener el token desde Redis
  33. cached_token = redis_client.get(REDIS_TOKEN_KEY)
  34. if cached_token:
  35. logger.info("Token obtenido desde Redis cache")
  36. token = cached_token
  37. ttl = redis_client.ttl(REDIS_TOKEN_KEY)
  38. if ttl is None or int(str(ttl)) < 0:
  39. token_exp = None
  40. else:
  41. token_exp = int(str(ttl)) + int(time())
  42. return str(cached_token)
  43. except Exception as e:
  44. logger.error(f"Error al conectar con Redis: {e}")
  45. logger.info("Fallback: obteniendo token sin cache")
  46. # Si no hay token en cache, solicitar uno nuevo
  47. url = 'https://auth.fu.do/api'
  48. data = {
  49. "apiKey": api_token,
  50. "apiSecret": api_secret
  51. }
  52. r = requests.post(url, data=data)
  53. response_data = r.json()
  54. token = response_data['token']
  55. expiration_timestamp = response_data['exp']
  56. # Calcular TTL en segundos para Redis
  57. current_time = int(time())
  58. ttl_seconds = expiration_timestamp - current_time
  59. try:
  60. # Guardar el token en Redis con expiración automática
  61. if ttl_seconds > 0:
  62. redis_client.setex(REDIS_TOKEN_KEY, ttl_seconds, token)
  63. logger.info(f"Token nuevo guardado en Redis (expira en {ttl_seconds} segundos)")
  64. else:
  65. logger.warning("Warning: El token ya está expirado")
  66. except Exception as e:
  67. logger.error(f"Error al guardar en Redis: {e}")
  68. return token
  69. def get_categories():
  70. token = get_token()
  71. url = 'https://api.fu.do/v1alpha1/product-categories'
  72. headers = {
  73. 'Authorization': 'Bearer ' + token
  74. }
  75. r = requests.get(url, headers=headers)
  76. return r.json()
  77. def get_product(id_category:int):
  78. url = 'https://api.fu.do/v1alpha1/products/{}'.format(id_category)
  79. token = get_token()
  80. headers = {
  81. 'Authorization': 'Bearer ' + token
  82. }
  83. r = requests.get(url, headers=headers)
  84. if r.status_code != 200:
  85. logger.error(f"Error al obtener producto: {r.json()['errors']}")
  86. return r.json()
  87. def get_products():
  88. url = 'https://api.fu.do/v1alpha1/products'
  89. token = get_token()
  90. headers = {
  91. 'Authorization': 'Bearer ' + token
  92. }
  93. r = requests.get(url, headers=headers)
  94. return list(filter(lambda x: x['relationships']['productCategory']['data']['id'] == '1', r.json()['data']))
  95. def get_table(number:int):
  96. n_per_page = 10
  97. page = math.ceil(number / n_per_page)
  98. url = 'https://api.fu.do/v1alpha1/tables?page[number]={}&page[size]={}&include=activeSales&sort=number'.format(page, n_per_page)
  99. token = get_token()
  100. headers = {
  101. 'Authorization': 'Bearer ' + token
  102. }
  103. r = requests.get(url, headers=headers)
  104. if r.status_code != 200:
  105. logger.error('Error al obtener tablas:' + str(r.json()['errors']))
  106. return None
  107. try:
  108. return list(filter(lambda x: x['attributes']['number'] == number, r.json()['data']))[0]
  109. except:
  110. logger.error('Error al obtener tabla')
  111. logger.error(r.json())
  112. return None
  113. def get_sale(sale_id:int):
  114. url = 'https://api.fu.do/v1alpha1/sales/{}'.format(sale_id)
  115. token = get_token()
  116. headers = {
  117. 'Authorization': 'Bearer ' + token
  118. }
  119. r = requests.get(url, headers=headers)
  120. if r.status_code != 200:
  121. logger.error('Error al obtener tablas:' + str(r.json()['errors']))
  122. return None
  123. return r.json()
  124. def create_sale(table_id:int):
  125. url = 'https://api.fu.do/v1alpha1/sales'
  126. token = get_token()
  127. headers = {
  128. 'Authorization': 'Bearer ' + token
  129. }
  130. data = {
  131. "data": {
  132. "type": "Sale",
  133. "attributes": {
  134. "people": 1,
  135. "saleType": "EAT-IN",
  136. "comment": "Pedido desde la app pedidos express"
  137. },
  138. "relationships": {
  139. "table": {
  140. "data": {
  141. "id": str(table_id),
  142. "type": "Table"
  143. }
  144. },
  145. "waiter": {
  146. "data": {
  147. "type": "User",
  148. "id": "76"
  149. }
  150. }
  151. }
  152. }
  153. }
  154. r = requests.post(url, headers=headers, json=data)
  155. if r.status_code != 201:
  156. logger.error('Error al crear la venta:', r.json())
  157. return None
  158. return r.json()["data"]
  159. def create_item(product_id:int, quantity:int, sale_id:int, comment = None):
  160. url = 'https://api.fu.do/v1alpha1/items'
  161. token = get_token()
  162. headers = {
  163. 'Authorization': 'Bearer ' + token
  164. }
  165. data = {
  166. "quantity": quantity,
  167. "origin": "MOBILE",
  168. "comment": "Pedido desde pedidos express" + (f" - {comment}" if comment else ""),
  169. }
  170. data = {
  171. "data":{
  172. "type": "Item",
  173. "attributes": data,
  174. "relationships": {
  175. "product": {
  176. "data": {
  177. "type": "Product",
  178. "id": str(product_id)
  179. }
  180. },
  181. "sale": {
  182. "data": {
  183. "type": "Sale",
  184. "id": str(sale_id)
  185. }
  186. }
  187. },
  188. }
  189. }
  190. r = requests.post(url, headers=headers, json=data)
  191. if r.status_code != 201:
  192. logger.error(r.json())
  193. return None
  194. return r.json()["data"]
  195. def get_active_sale(table):
  196. data = table['relationships']['activeSales']['data']
  197. if len(data) == 0:
  198. return None
  199. return data[0]
  200. def clear_token():
  201. """
  202. Elimina el token cached de Redis.
  203. Útil cuando el token es inválido o se necesita forzar una renovación.
  204. """
  205. try:
  206. redis_client.delete(REDIS_TOKEN_KEY)
  207. logger.info("Token eliminado del cache")
  208. except Exception as e:
  209. logger.error(f"Error al eliminar token de Redis: {e}")
  210. if __name__ == "__main__":
  211. table = get_table(107)
  212. if table is None:
  213. logger.error('No se pudo obtener la mesa')
  214. exit()
  215. activeSale = get_active_sale(table)
  216. if not activeSale:
  217. logger.error('No hay una venta activa para la mesa')
  218. activeSale = create_sale(table['id'])
  219. if activeSale is None:
  220. logger.error('No se pudo crear la venta')
  221. exit()
  222. else:
  223. activeSale = activeSale[0]
  224. logger.info('Venta activa: %s', activeSale['id'])
  225. """
  226. Instrucciones para hacer un pedido:
  227. 1. Obtener el token de autenticación con `get_token()` (ahora usa Redis cache).
  228. 2. Obtener la mesa con `get_table(numero_de_mesa)`.
  229. 3. Ver si tiene una activeSale, en caso contrario crear una con `create_sale(id_mesa)`.
  230. 4. Agregar los items a la venta con `create_item(id_producto, cantidad, id_venta, comentario)`.
  231. Configuración de Redis:
  232. - Host: REDIS_HOST (default: localhost)
  233. - Puerto: REDIS_PORT (default: 6379)
  234. - Base de datos: REDIS_DB (default: 0)
  235. """