data_service.py 50 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393
  1. import json
  2. from math import log
  3. import os
  4. import re
  5. import psycopg2
  6. from config.settings import POSTGRESQL_DB_CONFIG
  7. from typing import List, Dict, Optional, Any
  8. from abc import ABC, abstractmethod
  9. from config.settings import BG_DATA_PATH, IMAGE_PATH, PRODUCT_DATA_PATH, CURRENT_URL
  10. from fudo.fudo import get_all_products, get_category_dict
  11. from logging import getLogger
  12. from datetime import datetime
  13. from cryptography.fernet import Fernet
  14. from rich import print
  15. from config.settings import PIN_KEY
  16. import base64 as b64
  17. from time import time
  18. # Import models
  19. from models.user import User
  20. from models.items import Product
  21. from models.sales import Sale
  22. from models.blacklist import Blacklist
  23. fernet = Fernet(PIN_KEY.encode())
  24. logger = getLogger(__name__)
  25. """
  26. ESQUEMA DE BASE DE DATOS SQLITE (data.db)
  27. 1. Tabla: users
  28. -----------------------------------
  29. - id SERIAL PRIMARY KEY
  30. - email VARCHAR(255) UNIQUE NOT NULL
  31. - name VARCHAR(255) NOT NULL
  32. - rut VARCHAR(20) UNIQUE NOT NULL
  33. - pin_hash TEXT NOT NULL (encriptado)
  34. - kleincoins TEXT NOT NULL (encriptado, valor por defecto "0")
  35. - created_at TEXT NOT NULL (fecha de creación en formato ISO 8601)
  36. - reward_progress INTEGER DEFAULT 0 NOT NULL CHECK (reward_progress >= 0 AND reward_progress <= 100)
  37. (Guarda la información del usuario con su pin hasheado y kleincoins encriptadas)
  38. 2. Tabla: products
  39. -----------------------------------
  40. - id INTEGER PRIMARY KEY
  41. - name VARCHAR(255) NOT NULL
  42. - type VARCHAR(100) NOT NULL
  43. - description TEXT NOT NULL
  44. - price INTEGER NOT NULL
  45. - image TEXT (URL de la imagen)
  46. - status INTEGER DEFAULT 1 NOT NULL CHECK (status IN (0, 1)) -- 0: Inactivo, 1: Activo
  47. - promo_id INTEGER (ID de la promoción asociada, si existe)(puede ser null)
  48. - promo_price INTEGER (Precio promocional, si existe)(puede ser null)
  49. - promo_day INTEGER (Día de la semana para la promoción, 1-7)(puede ser null)
  50. (Guarda los productos disponibles para venta con su estado activo/inactivo)
  51. 3. Tabla: sales
  52. -----------------------------------
  53. - id SERIAL PRIMARY KEY
  54. - user_id INTEGER NOT NULL REFERENCES users.id
  55. - total INTEGER NOT NULL (precio total de la venta)
  56. - fudo_id TEXT UNIQUE NOT NULL (ID string único por venta)
  57. - date TEXT NOT NULL (fecha y hora en formato ISO 8601)
  58. - table INTEGER NOT NULL (número de mesa)
  59. (Guarda cada venta, asociada a un usuario y mesa)
  60. 4. Tabla: sale_products
  61. -----------------------------------
  62. - sale_id INTEGER NOT NULL (relación a sales.id)
  63. - product_id INTEGER NOT NULL (relación a products.id)
  64. - quantity INTEGER NOT NULL DEFAULT 1 (cantidad del producto)
  65. (Relación muchos a muchos entre ventas y productos con cantidad)
  66. 5. Tabla: blacklist
  67. -----------------------------------
  68. - id SERIAL PRIMARY KEY
  69. - user_id INTEGER NOT NULL (relación a users.id)
  70. (Usuarios bloqueados o no autorizados para ciertas acciones)
  71. RELACIONES:
  72. -----------------------------------
  73. - users puede tener muchas sales
  74. - sales puede tener muchos productos (y viceversa), por eso se usa una tabla intermedia (sale_products)
  75. - products pueden repetirse en múltiples sales
  76. """
  77. # Base abstract class for data access
  78. class BaseDataService(ABC):
  79. """Abstract base class for data services"""
  80. def __init__(self):
  81. self.db_config = POSTGRESQL_DB_CONFIG
  82. def _get_connection(self):
  83. """Get database connection"""
  84. return psycopg2.connect(
  85. dbname=self.db_config['dbname'],
  86. user=self.db_config['user'],
  87. password=self.db_config['password'],
  88. host=self.db_config['host'],
  89. port=self.db_config['port']
  90. )
  91. # User Data Service
  92. class UserDataService(BaseDataService):
  93. """Service for managing user data"""
  94. #region Create
  95. def create(self, name: str, email: str, rut: str, pin_hash: str) -> int:
  96. """Add a new user to the database"""
  97. conn = self._get_connection()
  98. cursor = conn.cursor()
  99. try:
  100. cursor.execute(
  101. "INSERT INTO users (name, email, rut, pin_hash, kleincoins, created_at) VALUES (%s, %s, %s, %s, %s, %s) RETURNING id",
  102. (name, email, rut, fernet.encrypt(pin_hash.encode()).decode(), fernet.encrypt(b"0").decode(), datetime.now().isoformat())
  103. )
  104. conn.commit()
  105. user_id = cursor.fetchone()
  106. if user_id:
  107. logger.info(f"User added with ID: {user_id[0]}")
  108. return user_id[0]
  109. else:
  110. logger.error("Failed to add user.")
  111. return -1
  112. except psycopg2.IntegrityError as e:
  113. logger.error(f"Failed to add user: {e}")
  114. return -1
  115. finally:
  116. conn.close()
  117. #endregion
  118. #region Read
  119. def get_all(self) -> List[User]:
  120. """Get all users from the database"""
  121. conn = self._get_connection()
  122. cursor = conn.cursor()
  123. cursor.execute("SELECT * FROM users")
  124. users = cursor.fetchall()
  125. conn.close()
  126. return [
  127. User(
  128. id=user[0],
  129. email=user[1],
  130. name=user[2],
  131. rut=user[3],
  132. pin_hash=fernet.decrypt(user[4].encode()).decode(),
  133. kleincoins=fernet.decrypt(user[5].encode()).decode(),
  134. created_at=user[6],
  135. permissions=user[7],
  136. reward_progress=user[8]
  137. ) for user in users
  138. ]
  139. def get_by_id(self, user_id: int) -> Optional[User]:
  140. """Get user data from the database"""
  141. conn = self._get_connection()
  142. cursor = conn.cursor()
  143. cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
  144. user = cursor.fetchone()
  145. conn.close()
  146. if user:
  147. return User(
  148. id=user[0],
  149. email=user[1],
  150. name=user[2],
  151. rut=user[3],
  152. pin_hash=fernet.decrypt(user[4].encode()).decode(),
  153. kleincoins=fernet.decrypt(user[5].encode()).decode(),
  154. created_at=user[6],
  155. permissions=user[7],
  156. reward_progress=user[8]
  157. )
  158. return None
  159. def get_by_email(self, email: str) -> Optional[User]:
  160. """Get user by email"""
  161. conn = self._get_connection()
  162. cursor = conn.cursor()
  163. cursor.execute("SELECT * FROM users WHERE email = %s", (email,))
  164. user = cursor.fetchone()
  165. conn.close()
  166. logger.debug(f"get_by_email: {email}, user: {user}")
  167. if user:
  168. return User(
  169. id=user[0],
  170. email=user[1],
  171. name=user[2],
  172. rut=user[3],
  173. pin_hash=user[4],
  174. kleincoins=fernet.decrypt(user[5].encode()).decode(),
  175. created_at=user[6],
  176. permissions=user[7],
  177. reward_progress=user[8]
  178. )
  179. return None
  180. def login(self, email: str, pin_hashed: str) -> Optional[User]:
  181. """Login user by email and pin hash"""
  182. user = self.get_by_email(email)
  183. if user:
  184. logger.info(f"comparing {fernet.decrypt(user.pin_hash.encode()).decode()} with {pin_hashed}")
  185. if user and fernet.decrypt(user.pin_hash.encode()).decode() == pin_hashed:
  186. logger.info(f"User {user.email} logged in successfully.")
  187. return user
  188. else:
  189. logger.error("Login failed: Invalid email or pin.")
  190. return None
  191. def permissions(self, user_id: int) -> int:
  192. """Get user permissions"""
  193. conn = self._get_connection()
  194. cursor = conn.cursor()
  195. cursor.execute("SELECT permissions FROM users WHERE id = %s", (user_id,))
  196. result = cursor.fetchone()
  197. if not result:
  198. logger.error(f"User with ID {user_id} not found.")
  199. return 0
  200. logger.info(f"userID: {user_id}, permissions: {result[0]}")
  201. result = result[0]
  202. conn.close()
  203. if result:
  204. return result
  205. return 0
  206. def get_by_rut(self, rut: str) -> Optional[User]:
  207. """Get user by RUT"""
  208. conn = self._get_connection()
  209. cursor = conn.cursor()
  210. cursor.execute("SELECT * FROM users WHERE rut = %s", (rut,))
  211. user = cursor.fetchone()
  212. if not user:
  213. logger.error(f"User with RUT {rut} not found.")
  214. conn.close()
  215. return None
  216. user = user[0]
  217. conn.close()
  218. if user:
  219. return User(
  220. id=user[0],
  221. email=user[1],
  222. name=user[2],
  223. rut=user[3],
  224. pin_hash=fernet.decrypt(user[4].encode()).decode(),
  225. kleincoins=fernet.decrypt(user[5].encode()).decode(),
  226. created_at=user[6],
  227. permissions=user[7],
  228. reward_progress=user[8]
  229. )
  230. return None
  231. def get_next_id(self) -> int:
  232. """Get the next user ID"""
  233. conn = self._get_connection()
  234. cursor = conn.cursor()
  235. cursor.execute("SELECT last_value FROM users_id_seq")
  236. result = cursor.fetchone()
  237. conn.close()
  238. if result and result[0]:
  239. return result[0]
  240. return 1
  241. #endregion
  242. #region Update
  243. def update(self, user_id: int, email=None, name=None, rut=None, pin_hash=None, kleincoins=None) -> bool:
  244. """Update user information in the database"""
  245. conn = self._get_connection()
  246. cursor = conn.cursor()
  247. updates = []
  248. params = []
  249. if email:
  250. updates.append("email = %s")
  251. params.append(email)
  252. if name:
  253. updates.append("name = %s")
  254. params.append(name)
  255. if rut:
  256. updates.append("rut = %s")
  257. params.append(rut)
  258. if pin_hash:
  259. updates.append("pin_hash = %s")
  260. params.append(fernet.encrypt(pin_hash.encode()).decode())
  261. if kleincoins is not None:
  262. updates.append("kleincoins = %s")
  263. params.append(fernet.encrypt(str(kleincoins).encode()).decode())
  264. if not updates:
  265. conn.close()
  266. return False
  267. try:
  268. cursor.execute(f"UPDATE users SET {', '.join(updates)} WHERE id = %s", (*params, user_id))
  269. conn.commit()
  270. success = cursor.rowcount > 0
  271. if success:
  272. logger.info(f"User with ID {user_id} updated.")
  273. return success
  274. except psycopg2.IntegrityError as e:
  275. logger.error(f"Failed to update user: {e}")
  276. return False
  277. finally:
  278. conn.close()
  279. #endregion
  280. def set_reward_progress(self, user_id: int, progress: int) -> bool:
  281. """Add progress to user's reward"""
  282. conn = self._get_connection()
  283. cursor = conn.cursor()
  284. try:
  285. cursor.execute("UPDATE users SET reward_progress = %s WHERE id = %s", (progress, user_id))
  286. conn.commit()
  287. success = cursor.rowcount > 0
  288. if success:
  289. logger.info(f"Reward progress updated for user {user_id}: {progress}")
  290. return success
  291. except psycopg2.IntegrityError as e:
  292. logger.error(f"Failed to update reward progress: {e}")
  293. return False
  294. finally:
  295. conn.close()
  296. #region Delete
  297. def delete(self, user_id: int) -> bool:
  298. """Delete a user from the database"""
  299. conn = self._get_connection()
  300. cursor = conn.cursor()
  301. cursor.execute("DELETE FROM users WHERE id = %s", (user_id,))
  302. conn.commit()
  303. conn.close()
  304. if cursor.rowcount > 0:
  305. logger.info(f"User with ID {user_id} deleted.")
  306. return True
  307. else:
  308. logger.error(f"Failed to delete user with ID {user_id}.")
  309. return False
  310. def update_kleincoins(self, user_id: int, kleincoins: int) -> bool:
  311. """Update user's kleincoins"""
  312. conn = self._get_connection()
  313. cursor = conn.cursor()
  314. try:
  315. cursor.execute(
  316. "UPDATE users SET kleincoins = %s WHERE id = %s",
  317. (fernet.encrypt(str(kleincoins).encode()).decode(), user_id)
  318. )
  319. conn.commit()
  320. success = cursor.rowcount > 0
  321. if success:
  322. logger.info(f"Kleincoins updated for user {user_id}: {kleincoins}")
  323. return success
  324. except psycopg2.IntegrityError as e:
  325. logger.error(f"Failed to update kleincoins: {e}")
  326. return False
  327. finally:
  328. conn.close()
  329. def get_kleincoins(self, user_id: int) -> Optional[int]:
  330. """Get user's kleincoins"""
  331. conn = self._get_connection()
  332. cursor = conn.cursor()
  333. cursor.execute("SELECT kleincoins FROM users WHERE id = %s", (user_id,))
  334. result = cursor.fetchone()
  335. conn.close()
  336. if result:
  337. try:
  338. return int(fernet.decrypt(result[0].encode()).decode())
  339. except Exception as e:
  340. logger.error(f"Failed to decrypt kleincoins for user {user_id}: {e}")
  341. return None
  342. return None
  343. #endregion
  344. # Blacklist Data Service
  345. class BlacklistDataService(BaseDataService):
  346. """Service for managing blacklisted users"""
  347. #region Create
  348. def create(self, user_id: int) -> int:
  349. """Add a user to the blacklist"""
  350. conn = self._get_connection()
  351. cursor = conn.cursor()
  352. try:
  353. cursor.execute("INSERT INTO blacklist (user_id) VALUES (%s) RETURNING id", (user_id,))
  354. conn.commit()
  355. blacklist_id = cursor.fetchone()
  356. if blacklist_id:
  357. logger.info(f"User with ID {blacklist_id[0]} added to blacklist.")
  358. return blacklist_id[0]
  359. else:
  360. logger.error(f"Failed to add user with ID {user_id} to blacklist.")
  361. return -1
  362. except psycopg2.IntegrityError as e:
  363. logger.error(f"Failed to add user to blacklist: {e}")
  364. return -1
  365. finally:
  366. conn.close()
  367. #endregion
  368. #region Read
  369. def get_all(self) -> List[Blacklist]:
  370. """Get all blacklisted users"""
  371. conn = self._get_connection()
  372. cursor = conn.cursor()
  373. cursor.execute("""
  374. SELECT b.id, b.user_id, u.email, u.name, u.rut
  375. FROM blacklist b
  376. LEFT JOIN users u ON b.user_id = u.id
  377. """)
  378. blacklisted = cursor.fetchall()
  379. conn.close()
  380. return [
  381. Blacklist(
  382. id=row[0],
  383. user_id=row[1],
  384. email=row[2],
  385. name=row[3],
  386. rut=row[4]
  387. ) for row in blacklisted
  388. ]
  389. def get_by_id(self, id: int) -> Optional[Blacklist]:
  390. """Get blacklist entry by ID"""
  391. conn = self._get_connection()
  392. cursor = conn.cursor()
  393. cursor.execute("""
  394. SELECT b.id, b.user_id, u.email, u.name, u.rut
  395. FROM blacklist b
  396. LEFT JOIN users u ON b.user_id = u.id
  397. WHERE b.id = %s
  398. """, (id,))
  399. row = cursor.fetchone()
  400. conn.close()
  401. if row:
  402. return Blacklist(
  403. id=row[0],
  404. user_id=row[1],
  405. email=row[2],
  406. name=row[3],
  407. rut=row[4]
  408. )
  409. return None
  410. def get_blacklisted_user_ids(self) -> List[int]:
  411. """Get a list of blacklisted user IDs"""
  412. conn = self._get_connection()
  413. cursor = conn.cursor()
  414. cursor.execute("SELECT user_id FROM blacklist")
  415. blacklisted_users = [row[0] for row in cursor.fetchall()]
  416. conn.close()
  417. return blacklisted_users
  418. def is_user_blacklisted(self, user_id: int) -> bool:
  419. """Check if a user is blacklisted"""
  420. conn = self._get_connection()
  421. cursor = conn.cursor()
  422. cursor.execute("SELECT * FROM blacklist WHERE user_id = %s", (user_id,))
  423. blacklisted = cursor.fetchone() is not None
  424. conn.close()
  425. return blacklisted
  426. #endregion
  427. #region Update
  428. def update(self, id: int, **kwargs) -> bool:
  429. """Update blacklist entry (not commonly used)"""
  430. # Blacklist entries typically don't need updates
  431. return False
  432. #endregion
  433. #region Delete
  434. def delete(self, id: int) -> bool:
  435. """Remove a blacklist entry by ID"""
  436. conn = self._get_connection()
  437. cursor = conn.cursor()
  438. cursor.execute("DELETE FROM blacklist WHERE id = %s", (id,))
  439. conn.commit()
  440. success = cursor.rowcount > 0
  441. conn.close()
  442. if success:
  443. logger.info(f"Blacklist entry with ID {id} removed.")
  444. else:
  445. logger.error(f"Failed to remove blacklist entry with ID {id}.")
  446. return success
  447. def remove_user_from_blacklist(self, user_id: int) -> bool:
  448. """Remove a user from the blacklist"""
  449. conn = self._get_connection()
  450. cursor = conn.cursor()
  451. cursor.execute("DELETE FROM blacklist WHERE user_id = %s", (user_id,))
  452. conn.commit()
  453. success = cursor.rowcount > 0
  454. conn.close()
  455. if success:
  456. logger.info(f"User with ID {user_id} removed from blacklist.")
  457. else:
  458. logger.error(f"Failed to remove user with ID {user_id} from blacklist.")
  459. return success
  460. #endregion
  461. # Product Data Service
  462. class ProductDataService(BaseDataService):
  463. category_dict = {}
  464. product_cache = {
  465. "products": [],
  466. "expires": 0
  467. }
  468. """Service for managing products"""
  469. #region Create
  470. def create(self, id: int, name: str, price: float, type: Optional[str] = None, description: Optional[str] = None, image: Optional[str] = None, status: int = 1) -> int:
  471. """Add a new product to the database"""
  472. conn = self._get_connection()
  473. cursor = conn.cursor()
  474. if image and "base64" in image:
  475. extension = re.search(r'data:image/(.*%s);base64,', image)
  476. image_name = f"{id}_img"
  477. if extension:
  478. image_name += f".{extension.group(1)}"
  479. image = self._image_process(image_name, image)
  480. try:
  481. cursor.execute(
  482. "INSERT INTO products (id, name, type, description, price, image, status) VALUES (%s, %s, %s, %s, %s, %s, %s) RETURNING id",
  483. (id, name, type, description, price, image, status)
  484. )
  485. conn.commit()
  486. product_id = cursor.fetchone()
  487. if product_id:
  488. logger.info(f"Product added with ID: {product_id[0]}")
  489. return product_id[0]
  490. else:
  491. logger.error("Failed to add product.")
  492. return -1
  493. except psycopg2.Error as e:
  494. logger.error(f"Failed to add product: {e}")
  495. return -1
  496. finally:
  497. conn.close()
  498. def load_data(self, products: List[Dict[str, str]]) -> None:
  499. """Load multiple products from a list of dictionaries"""
  500. conn = self._get_connection()
  501. cursor = conn.cursor()
  502. for product in products:
  503. try:
  504. self.create(
  505. id=int(product['id']),
  506. name=product['name'],
  507. price=int(product['price']),
  508. type=product.get('type'),
  509. description=product.get('description'),
  510. image=product.get('image'),
  511. status=int(product.get('status', 1)) # Default to active (1)
  512. )
  513. except Exception as e:
  514. logger.error(f"Failed to load product {product['name']}: {e}")
  515. conn.close()
  516. #endregion
  517. #region Read
  518. async def get_all(self) -> List[Product]:
  519. """Get all products from the database"""
  520. return_list = []
  521. beUpdate = False
  522. # se ve si la cache expiro o si esta vacia
  523. actual_time = time()
  524. if self.product_cache['expires'] < actual_time or not self.product_cache['products']:
  525. self.product_cache['expires'] = actual_time # Por ahora la cache no expira
  526. beUpdate = True
  527. else:
  528. logger.debug("Product cache not expired")
  529. return self.product_cache['products']
  530. products = await get_all_products()
  531. for product in products:
  532. categoryID = product['relationships']['productCategory']['data']['id']
  533. # en caso de que la categoria no este en el diccionario, se actualiza
  534. if categoryID not in self.category_dict:
  535. self.category_dict = get_category_dict()
  536. try:
  537. return_list.append(
  538. Product(
  539. id=int(product['id']),
  540. name=product['attributes']['name'],
  541. type=self.category_dict[categoryID],
  542. description=product['attributes'].get('description'),
  543. price=int(product['attributes'].get('price', 0)),
  544. image=product['attributes'].get('imageUrl'),
  545. kitchen_id=product['relationships']['kitchen']['data']['id'] or None,
  546. status=1 if product['attributes'].get('active') else 0,
  547. promo_id=None,
  548. promo_price=None,
  549. promo_day=None
  550. ))
  551. except Exception as e:
  552. print(e)
  553. continue
  554. if beUpdate:
  555. self.product_cache['products'] = return_list
  556. return return_list
  557. def get_by_id(self, product_id: int) -> Optional[Product]:
  558. """Get product by ID"""
  559. conn = self._get_connection()
  560. cursor = conn.cursor()
  561. cursor.execute("SELECT * FROM products WHERE id = %s", (product_id,))
  562. product = cursor.fetchone()
  563. conn.close()
  564. if product:
  565. return Product(
  566. id=product[0],
  567. name=product[1],
  568. type=product[2],
  569. description=product[3],
  570. price=product[4],
  571. image=product[5],
  572. status=product[6],
  573. promo_id=product[7],
  574. promo_price=product[8],
  575. promo_day=product[9]
  576. )
  577. return None
  578. def get_by_type(self, product_type: str) -> List[Product]:
  579. """Get products by type"""
  580. conn = self._get_connection()
  581. cursor = conn.cursor()
  582. cursor.execute("SELECT * FROM products WHERE type = %s", (product_type,))
  583. products = cursor.fetchall()
  584. conn.close()
  585. return [
  586. Product(
  587. id=product[0],
  588. name=product[1],
  589. type=product[2],
  590. description=product[3],
  591. price=product[4],
  592. image=product[5],
  593. status=product[6],
  594. promo_id=product[7],
  595. promo_price=product[8],
  596. promo_day=product[9]
  597. ) for product in products
  598. ]
  599. def search_by_name(self, name: str) -> List[Product]:
  600. """Search products by name"""
  601. conn = self._get_connection()
  602. cursor = conn.cursor()
  603. cursor.execute("SELECT * FROM products WHERE name LIKE %s", (f"%{name}%",))
  604. products = cursor.fetchall()
  605. conn.close()
  606. return [
  607. Product(
  608. id=product[0],
  609. name=product[1],
  610. type=product[2],
  611. description=product[3],
  612. price=product[4],
  613. image=product[5],
  614. status=product[6],
  615. promo_id=product[7],
  616. promo_price=product[8],
  617. promo_day=product[9]
  618. ) for product in products
  619. ]
  620. async def get_products(self, product_ids: List[int]) -> List[Product]:
  621. """Get multiple products by their IDs"""
  622. products = await self.get_all()
  623. products = list(filter(lambda x: x.id in product_ids, products))
  624. return products
  625. #endregion
  626. #region Update
  627. def _image_process(self, image: str, base64: str) -> str:
  628. """Process image for storage"""
  629. if not image or not base64:
  630. raise ValueError("Image and base64 data must be provided")
  631. image_path = os.path.join(IMAGE_PATH, image)
  632. if not os.path.exists(IMAGE_PATH):
  633. os.makedirs(IMAGE_PATH)
  634. with open(image_path, 'wb') as img_file:
  635. img_file.write(b64.b64decode(base64.split(',')[1]))
  636. return CURRENT_URL+"/images/" + image # Return url
  637. def update(self, product_id: int, name=None, type=None, description=None, price=None, image=None, status=None) -> bool:
  638. """Update product information"""
  639. conn = self._get_connection()
  640. cursor = conn.cursor()
  641. product = self.get_by_id(product_id)
  642. if not product:
  643. logger.error(f"Product with ID {product_id} not found.")
  644. return False
  645. updates = []
  646. params = []
  647. if name is not None:
  648. updates.append("name = %s")
  649. params.append(name)
  650. if type is not None:
  651. updates.append("type = %s")
  652. params.append(type)
  653. if description is not None:
  654. updates.append("description = %s")
  655. params.append(description)
  656. if price is not None:
  657. updates.append("price = %s")
  658. params.append(price)
  659. if image is not None:
  660. if product.image and product.image != image:
  661. if os.path.exists(os.path.join(IMAGE_PATH, product.image)):
  662. os.remove(os.path.join(IMAGE_PATH, product.image))
  663. try:
  664. logger.info(f"Processing image: {image[:40]}... for product {product_id}")
  665. logger.info(f"updates: {updates}, params: {params}")
  666. extension = re.search(r'data:image/(.*?);base64,', image[:40])
  667. if not extension:
  668. raise ValueError("Invalid image format")
  669. extension = extension.group(1)
  670. image = self._image_process(f"{product.id}_img.{extension}", image)
  671. except ValueError as e:
  672. logger.error(f"Failed to process image: {e}")
  673. return False
  674. updates.append("image = %s")
  675. params.append(image)
  676. if status is not None:
  677. updates.append("status = %s")
  678. params.append(status)
  679. if not updates:
  680. conn.close()
  681. return False
  682. try:
  683. cursor.execute(f"UPDATE products SET {', '.join(updates)} WHERE id = %s", (*params, product_id))
  684. conn.commit()
  685. success = cursor.rowcount > 0
  686. if success:
  687. logger.info(f"Product with ID {product_id} updated.")
  688. return success
  689. except psycopg2.Error as e:
  690. logger.error(f"Failed to update product: {e}")
  691. return False
  692. finally:
  693. conn.close()
  694. def get_active_products(self) -> List[Product]:
  695. """Get only active products (status = 1)"""
  696. conn = self._get_connection()
  697. cursor = conn.cursor()
  698. cursor.execute("SELECT * FROM products WHERE status = 1")
  699. products = cursor.fetchall()
  700. conn.close()
  701. return [
  702. Product(
  703. id=product[0],
  704. name=product[1],
  705. type=product[2],
  706. description=product[3],
  707. price=product[4],
  708. image=product[5],
  709. status=product[6],
  710. promo_id=product[7],
  711. promo_price=product[8],
  712. promo_day=product[9]
  713. ) for product in products
  714. ]
  715. def get_inactive_products(self) -> List[Product]:
  716. """Get only inactive products (status = 0)"""
  717. conn = self._get_connection()
  718. cursor = conn.cursor()
  719. cursor.execute("SELECT * FROM products WHERE status = 0")
  720. products = cursor.fetchall()
  721. conn.close()
  722. return [
  723. Product(
  724. id=product[0],
  725. name=product[1],
  726. type=product[2],
  727. description=product[3],
  728. price=product[4],
  729. image=product[5],
  730. status=product[6],
  731. promo_id=product[7],
  732. promo_price=product[8],
  733. promo_day=product[9]
  734. ) for product in products
  735. ]
  736. def activate_product(self, product_id: int) -> bool:
  737. """Activate a product (set status to 1)"""
  738. return self.update(product_id, status=1)
  739. def deactivate_product(self, product_id: int) -> bool:
  740. """Deactivate a product (set status to 0)"""
  741. return self.update(product_id, status=0)
  742. def is_product_active(self, product_id: int) -> Optional[bool]:
  743. """Check if a product is active"""
  744. product = self.get_by_id(product_id)
  745. if product:
  746. return product.status == 1
  747. return None
  748. def update_cache(self):
  749. self.product_cache['expires'] = time() + 60 * 15 # 15 minutos
  750. self.product_cache['products'] = self.get_all()
  751. #endregion
  752. #region Delete
  753. def delete(self, product_id: int) -> bool:
  754. """Delete a product from the database"""
  755. conn = self._get_connection()
  756. cursor = conn.cursor()
  757. product = self.get_by_id(product_id)
  758. if product and product.image and os.path.exists(os.path.join(IMAGE_PATH, product.image.removeprefix(CURRENT_URL + "/images/"))):
  759. os.remove(os.path.join(IMAGE_PATH, product.image.removeprefix(CURRENT_URL + "/images/")))
  760. cursor.execute("DELETE FROM products WHERE id = %s", (product_id,))
  761. conn.commit()
  762. success = cursor.rowcount > 0
  763. conn.close()
  764. if success:
  765. logger.info(f"Product with ID {product_id} deleted.")
  766. else:
  767. logger.error(f"Failed to delete product with ID {product_id}.")
  768. return success
  769. #endregion
  770. # Sales Data Service
  771. class SalesDataService(BaseDataService):
  772. """Service for managing sales"""
  773. #region C
  774. def create(self, user_id: int, fudo_id: str, total: float, table: int, product_ids: List[int], quantities: Optional[List[int]] = None) -> int:
  775. """Create a new sale with products and quantities"""
  776. conn = self._get_connection()
  777. cursor = conn.cursor()
  778. try:
  779. fecha = datetime.now().isoformat()
  780. # Insert sale
  781. cursor.execute(
  782. "INSERT INTO sales (user_id, total, fudo_id, date, table_number) VALUES (%s, %s, %s, %s, %s) RETURNING id",
  783. (user_id, total, fudo_id, fecha, table)
  784. )
  785. last_sale = cursor.fetchone()
  786. sale_id = last_sale[0] if last_sale else None
  787. if sale_id and product_ids:
  788. # Insert sale-product relationships with quantities
  789. if quantities is None:
  790. quantities = [1] * len(product_ids) # Default quantity 1
  791. for i, product_id in enumerate(product_ids):
  792. quantity = quantities[i] if i < len(quantities) else 1
  793. cursor.execute(
  794. "INSERT INTO sale_products (sale_id, product_id, quantity) VALUES (%s, %s, %s)",
  795. (sale_id, product_id, quantity)
  796. )
  797. conn.commit()
  798. if sale_id:
  799. logger.info(f"Sale created with ID: {sale_id}, fudo_id: {fudo_id}")
  800. return sale_id
  801. else:
  802. logger.error("Failed to create sale.")
  803. return -1
  804. except psycopg2.Error as e:
  805. logger.error(f"Failed to create sale: {e}")
  806. conn.rollback()
  807. return -1
  808. finally:
  809. conn.close()
  810. def add_product_to_sale(self, sale_id: int, product_id: int, quantity: int = 1) -> bool:
  811. """Add a product to an existing sale with quantity"""
  812. conn = self._get_connection()
  813. cursor = conn.cursor()
  814. try:
  815. cursor.execute(
  816. "INSERT INTO sale_products (sale_id, product_id, quantity) VALUES (%s, %s, %s)",
  817. (sale_id, product_id, quantity)
  818. )
  819. conn.commit()
  820. success = cursor.rowcount > 0
  821. if success:
  822. logger.info(f"Product {product_id} (qty: {quantity}) added to sale {sale_id}.")
  823. return success
  824. except psycopg2.IntegrityError as e:
  825. logger.error(f"Failed to add product to sale: {e}")
  826. return False
  827. finally:
  828. conn.close()
  829. def add_products_to_sale(self, sale_id: int, product_ids: List[int], quantities: Optional[List[int]] = None) -> bool:
  830. """Add multiple products to an existing sale with quantities"""
  831. conn = self._get_connection()
  832. cursor = conn.cursor()
  833. try:
  834. if quantities is None:
  835. quantities = [1] * len(product_ids) # Default quantity 1
  836. for i, product_id in enumerate(product_ids):
  837. quantity = quantities[i] if i < len(quantities) else 1
  838. cursor.execute(
  839. "INSERT INTO sale_products (sale_id, product_id, quantity) VALUES (%s, %s, %s)",
  840. (sale_id, product_id, quantity)
  841. )
  842. conn.commit()
  843. success = cursor.rowcount > 0
  844. if success:
  845. logger.info(f"Products added to sale {sale_id}.")
  846. return success
  847. except psycopg2.IntegrityError as e:
  848. logger.error(f"Failed to add products to sale: {e}")
  849. return False
  850. finally:
  851. conn.close()
  852. #endregion
  853. #region R
  854. def get_all(self) -> List[Sale]:
  855. """Get all sales from the database"""
  856. conn = self._get_connection()
  857. cursor = conn.cursor()
  858. cursor.execute("""
  859. SELECT s.id, s.user_id, s.total, s.fudo_id, s.date, s.table, u.name, u.email
  860. FROM sales s
  861. LEFT JOIN users u ON s.user_id = u.id
  862. ORDER BY s.date DESC
  863. """)
  864. sales = cursor.fetchall()
  865. conn.close()
  866. return [
  867. Sale(
  868. id=sale[0],
  869. user_id=sale[1],
  870. total=sale[2],
  871. fudo_id=sale[3],
  872. date=sale[4],
  873. table=sale[5],
  874. username=sale[6],
  875. user_email=sale[7],
  876. products=self.get_sale_products(sale[0])
  877. ) for sale in sales
  878. ]
  879. def get_by_id(self, sale_id: int) -> Optional[Sale]:
  880. """Get sale by ID"""
  881. conn = self._get_connection()
  882. cursor = conn.cursor()
  883. cursor.execute("""
  884. SELECT s.id, s.user_id, s.total, s.fudo_id, s.date, s.table, u.name, u.email
  885. FROM sales s
  886. LEFT JOIN users u ON s.user_id = u.id
  887. WHERE s.id = %s
  888. """, (sale_id,))
  889. sale = cursor.fetchone()
  890. conn.close()
  891. if sale:
  892. return Sale(
  893. id=sale[0],
  894. user_id=sale[1],
  895. total=sale[2],
  896. fudo_id=sale[3],
  897. date=sale[4],
  898. table=sale[5],
  899. username=sale[6],
  900. user_email=sale[7]
  901. )
  902. return None
  903. def get_by_fudo_id(self, fudo_id: str) -> Optional[Sale]:
  904. """Get sale by fudo_id"""
  905. conn = self._get_connection()
  906. cursor = conn.cursor()
  907. cursor.execute("""
  908. SELECT s.id, s.user_id, s.total, s.fudo_id, s.date, s.table, u.name, u.email
  909. FROM sales s
  910. LEFT JOIN users u ON s.user_id = u.id
  911. WHERE s.fudo_id = %s
  912. """, (fudo_id,))
  913. sale = cursor.fetchone()
  914. conn.close()
  915. if sale:
  916. return Sale(
  917. id=sale[0],
  918. user_id=sale[1],
  919. total=sale[2],
  920. fudo_id=sale[3],
  921. date=sale[4],
  922. table=sale[5],
  923. username=sale[6],
  924. user_email=sale[7]
  925. )
  926. return None
  927. def get_by_user(self, user_id: int) -> List[Sale]:
  928. """Get sales by user ID"""
  929. conn = self._get_connection()
  930. cursor = conn.cursor()
  931. cursor.execute(
  932. """
  933. SELECT s.id, s.user_id, s.total, s.fudo_id, s.date, s.table_number, u.name, u.email
  934. FROM sales s
  935. LEFT JOIN users u ON s.user_id = u.id
  936. WHERE s.user_id = %s
  937. ORDER BY s.date DESC
  938. """,
  939. (user_id,)
  940. )
  941. sales = cursor.fetchall()
  942. conn.close()
  943. return [
  944. Sale(
  945. id=sale[0],
  946. user_id=sale[1],
  947. total=sale[2],
  948. fudo_id=sale[3],
  949. date=datetime.fromisoformat(sale[4]),
  950. table=sale[5],
  951. username=sale[6],
  952. user_email=sale[7],
  953. products=self.get_sale_products(sale[0])
  954. ) for sale in sales
  955. ]
  956. def get_sale_products_ids(self, sale_id: int) -> List[int]:
  957. """Get product IDs for a specific sale"""
  958. conn = self._get_connection()
  959. cursor = conn.cursor()
  960. cursor.execute("""
  961. SELECT product_id FROM sale_products WHERE sale_id = %s
  962. """, (sale_id,))
  963. products = cursor.fetchall()
  964. conn.close()
  965. return [product[0] for product in products]
  966. def get_sale_products(self, sale_id: int) -> List[Product]:
  967. """Get products for a specific sale with quantities"""
  968. conn = self._get_connection()
  969. cursor = conn.cursor()
  970. cursor.execute("""
  971. SELECT p.id, p.name, p.type, p.description, p.price, p.image, p.status, sp.quantity
  972. FROM sale_products sp
  973. JOIN products p ON sp.product_id = p.id
  974. WHERE sp.sale_id = %s
  975. """, (sale_id,))
  976. products = cursor.fetchall()
  977. conn.close()
  978. return [
  979. Product(
  980. id=product[0],
  981. name=product[1],
  982. type=product[2],
  983. description=product[3],
  984. price=product[4],
  985. image=product[5],
  986. status=product[6],
  987. quantity=product[7],
  988. promo_id=None, # Not applicable for sale products
  989. promo_price=None,
  990. promo_day=None
  991. ) for product in products
  992. ]
  993. def get_by_table(self, table: int) -> List[Sale]:
  994. """Get sales by table number"""
  995. conn = self._get_connection()
  996. cursor = conn.cursor()
  997. cursor.execute(
  998. """
  999. SELECT s.id, s.user_id, s.total, s.fudo_id, s.date, s.table_number, u.name, u.email
  1000. FROM sales s
  1001. LEFT JOIN users u ON s.user_id = u.id
  1002. WHERE s.table_number = %s
  1003. ORDER BY s.date DESC
  1004. """,
  1005. (table,)
  1006. )
  1007. sales = cursor.fetchall()
  1008. conn.close()
  1009. return [
  1010. Sale(
  1011. id=sale[0],
  1012. user_id=sale[1],
  1013. total=sale[2],
  1014. fudo_id=sale[3],
  1015. date=sale[4],
  1016. table=sale[5],
  1017. username=sale[6],
  1018. user_email=sale[7]
  1019. ) for sale in sales
  1020. ]
  1021. def update_product_quantity(self, sale_id: int, product_id: int, new_quantity: int) -> bool:
  1022. """Update quantity of a product in a sale"""
  1023. conn = self._get_connection()
  1024. cursor = conn.cursor()
  1025. try:
  1026. cursor.execute(
  1027. "UPDATE sale_products SET quantity = %s WHERE sale_id = %s AND product_id = %s",
  1028. (new_quantity, sale_id, product_id)
  1029. )
  1030. conn.commit()
  1031. success = cursor.rowcount > 0
  1032. if success:
  1033. logger.info(f"Updated quantity to {new_quantity} for product {product_id} in sale {sale_id}.")
  1034. return success
  1035. except psycopg2.Error as e:
  1036. logger.error(f"Failed to update product quantity: {e}")
  1037. return False
  1038. finally:
  1039. conn.close()
  1040. def get_product_quantity_in_sale(self, sale_id: int, product_id: int) -> Optional[int]:
  1041. """Get quantity of a specific product in a sale"""
  1042. conn = self._get_connection()
  1043. cursor = conn.cursor()
  1044. cursor.execute(
  1045. "SELECT quantity FROM sale_products WHERE sale_id = %s AND product_id = %s",
  1046. (sale_id, product_id)
  1047. )
  1048. result = cursor.fetchone()
  1049. conn.close()
  1050. return result[0] if result else None
  1051. #endregion
  1052. #region U
  1053. def update(self, sale_id: int, user_id=None, total=None, table=None) -> bool:
  1054. """Update sale information (products should be updated via specific methods)"""
  1055. conn = self._get_connection()
  1056. cursor = conn.cursor()
  1057. updates = []
  1058. params = []
  1059. if user_id is not None:
  1060. updates.append("user_id = %s")
  1061. params.append(user_id)
  1062. if total is not None:
  1063. updates.append("total = %s")
  1064. params.append(total)
  1065. if table is not None:
  1066. updates.append("table = %s")
  1067. params.append(table)
  1068. if not updates:
  1069. conn.close()
  1070. return False
  1071. try:
  1072. cursor.execute(f"UPDATE sales SET {', '.join(updates)} WHERE id = %s", (*params, sale_id))
  1073. conn.commit()
  1074. success = cursor.rowcount > 0
  1075. if success:
  1076. logger.info(f"Sale with ID {sale_id} updated.")
  1077. return success
  1078. except psycopg2.Error as e:
  1079. logger.error(f"Failed to update sale: {e}")
  1080. return False
  1081. finally:
  1082. conn.close()
  1083. #endregion
  1084. #region D
  1085. def delete(self, sale_id: int) -> bool:
  1086. """Delete a sale and its product relationships"""
  1087. conn = self._get_connection()
  1088. cursor = conn.cursor()
  1089. try:
  1090. # Delete sale-product relationships first
  1091. cursor.execute("DELETE FROM sale_products WHERE sale_id = %s", (sale_id,))
  1092. # Delete the sale
  1093. cursor.execute("DELETE FROM sales WHERE id = %s", (sale_id,))
  1094. conn.commit()
  1095. success = cursor.rowcount > 0
  1096. if success:
  1097. logger.info(f"Sale with ID {sale_id} deleted.")
  1098. else:
  1099. logger.error(f"Failed to delete sale with ID {sale_id}.")
  1100. return success
  1101. except psycopg2.Error as e:
  1102. logger.error(f"Failed to delete sale: {e}")
  1103. return False
  1104. finally:
  1105. conn.close()
  1106. def remove_product_from_sale(self, sale_id: int, product_id: int) -> bool:
  1107. """Remove a product from a sale (removes all quantity)"""
  1108. conn = self._get_connection()
  1109. cursor = conn.cursor()
  1110. cursor.execute(
  1111. "DELETE FROM sale_products WHERE sale_id = %s AND product_id = %s",
  1112. (sale_id, product_id)
  1113. )
  1114. conn.commit()
  1115. success = cursor.rowcount > 0
  1116. conn.close()
  1117. if success:
  1118. logger.info(f"Product {product_id} removed from sale {sale_id}.")
  1119. else:
  1120. logger.error(f"Failed to remove product {product_id} from sale {sale_id}.")
  1121. return success
  1122. def decrease_product_quantity(self, sale_id: int, product_id: int, decrease_by: int = 1) -> bool:
  1123. """Decrease quantity of a product in a sale, removes if quantity becomes 0 or less"""
  1124. conn = self._get_connection()
  1125. cursor = conn.cursor()
  1126. try:
  1127. # Get current quantity
  1128. cursor.execute(
  1129. "SELECT quantity FROM sale_products WHERE sale_id = %s AND product_id = %s",
  1130. (sale_id, product_id)
  1131. )
  1132. result = cursor.fetchone()
  1133. if not result:
  1134. return False
  1135. current_quantity = result[0]
  1136. new_quantity = current_quantity - decrease_by
  1137. if new_quantity <= 0:
  1138. # Remove the product completely
  1139. cursor.execute(
  1140. "DELETE FROM sale_products WHERE sale_id = %s AND product_id = %s",
  1141. (sale_id, product_id)
  1142. )
  1143. logger.info(f"Product {product_id} removed from sale {sale_id} (quantity reached 0).")
  1144. else:
  1145. # Update with new quantity
  1146. cursor.execute(
  1147. "UPDATE sale_products SET quantity = %s WHERE sale_id = %s AND product_id = %s",
  1148. (new_quantity, sale_id, product_id)
  1149. )
  1150. logger.info(f"Product {product_id} quantity decreased to {new_quantity} in sale {sale_id}.")
  1151. conn.commit()
  1152. return True
  1153. except psycopg2.Error as e:
  1154. logger.error(f"Failed to decrease product quantity: {e}")
  1155. return False
  1156. finally:
  1157. conn.close()
  1158. #endregion
  1159. # Factory class to get service instances
  1160. class DataServiceFactory:
  1161. """Factory class to create data service instances"""
  1162. @staticmethod
  1163. def get_user_service() -> UserDataService:
  1164. """Get user data service instance"""
  1165. return UserDataService()
  1166. @staticmethod
  1167. def get_blacklist_service() -> BlacklistDataService:
  1168. """Get blacklist data service instance"""
  1169. return BlacklistDataService()
  1170. @staticmethod
  1171. def get_product_service() -> ProductDataService:
  1172. """Get product data service instance"""
  1173. return ProductDataService()
  1174. @staticmethod
  1175. def get_sales_service() -> SalesDataService:
  1176. """Get sales data service instance"""
  1177. return SalesDataService()
  1178. # Helper functions for background data
  1179. def load_bg_data() -> List[Dict[str, str]]:
  1180. """Load background data for AI assistant"""
  1181. try:
  1182. with open(BG_DATA_PATH, 'r', encoding='utf-8') as f:
  1183. return json.load(f)
  1184. except FileNotFoundError:
  1185. logger.error(f"Data file not found at {BG_DATA_PATH}. Serving with empty data.")
  1186. return []
  1187. except json.JSONDecodeError:
  1188. logger.error(f"Could not decode JSON from {BG_DATA_PATH}. Serving with empty data.")
  1189. return []
  1190. def initialize_db():
  1191. # En PostgreSQL, no se crea el archivo de base de datos, así que verificamos si las tablas existen.
  1192. try:
  1193. conn = psycopg2.connect(
  1194. dbname=POSTGRESQL_DB_CONFIG['dbname'],
  1195. user=POSTGRESQL_DB_CONFIG['user'],
  1196. password=POSTGRESQL_DB_CONFIG['password'],
  1197. host=POSTGRESQL_DB_CONFIG['host'],
  1198. port=POSTGRESQL_DB_CONFIG['port']
  1199. )
  1200. cursor = conn.cursor()
  1201. # Verificar si la tabla 'users' ya existe
  1202. cursor.execute("""
  1203. SELECT EXISTS (
  1204. SELECT FROM information_schema.tables
  1205. WHERE table_name = 'users'
  1206. );
  1207. """)
  1208. exists = cursor.fetchone()
  1209. if exists:
  1210. logger.info("La base de datos ya está inicializada, no se necesita inicializar.")
  1211. conn.close()
  1212. return
  1213. conn.close()
  1214. except Exception as e:
  1215. logger.error(f"Error comprobando la existencia de tablas: {e}")
  1216. # Si hay error, continuamos con la inicialización
  1217. conn = psycopg2.connect(
  1218. dbname=POSTGRESQL_DB_CONFIG['dbname'],
  1219. user=POSTGRESQL_DB_CONFIG['user'],
  1220. password=POSTGRESQL_DB_CONFIG['password'],
  1221. host=POSTGRESQL_DB_CONFIG['host'],
  1222. port=POSTGRESQL_DB_CONFIG['port']
  1223. )
  1224. cursor = conn.cursor()
  1225. # Crear tabla de usuarios
  1226. logger.info("Inicializando base de datos...")
  1227. logger.info("Creando tabla de usuarios...")
  1228. cursor.execute("""
  1229. CREATE TABLE IF NOT EXISTS users (
  1230. id SERIAL PRIMARY KEY,
  1231. email VARCHAR(255) UNIQUE NOT NULL,
  1232. name VARCHAR(255) NOT NULL,
  1233. rut VARCHAR(20) UNIQUE NOT NULL,
  1234. pin_hash TEXT NOT NULL,
  1235. kleincoins TEXT NOT NULL,
  1236. created_at TEXT NOT NULL,
  1237. permissions INTEGER DEFAULT 0 NOT NULL CHECK (permissions IN (0, 1, 2)) -- 0: Usuario normal, 1: Administrador, 2: Superusuario
  1238. reward_progress INTEGER DEFAULT 0 NOT NULL CHECK (reward_progress >= 0 AND reward_progress <= 100) -- Progreso de recompensas
  1239. );
  1240. """)
  1241. # Crear tabla de productos
  1242. logger.info("Creando tabla de products...")
  1243. cursor.execute("""
  1244. CREATE TABLE IF NOT EXISTS products (
  1245. id INTEGER PRIMARY KEY,
  1246. name VARCHAR(255) NOT NULL,
  1247. type VARCHAR(100) NOT NULL,
  1248. description TEXT NOT NULL,
  1249. price INTEGER NOT NULL,
  1250. image TEXT,
  1251. status INTEGER DEFAULT 1 NOT NULL CHECK (status IN (0, 1)) -- 0: Inactivo, 1: Activo,
  1252. promo_id INTEGER,
  1253. promo_price INTEGER,
  1254. promo_day INTEGER CHECK (promo_day >= 1 AND promo_day <= 7)
  1255. );
  1256. """)
  1257. # Crear tabla de ventas
  1258. logger.info("Creando tabla de sales...")
  1259. cursor.execute("""
  1260. CREATE TABLE IF NOT EXISTS sales (
  1261. id SERIAL PRIMARY KEY,
  1262. user_id INTEGER NOT NULL,
  1263. total INTEGER NOT NULL,
  1264. fudo_id TEXT UNIQUE NOT NULL,
  1265. date TEXT NOT NULL,
  1266. table_number INTEGER NOT NULL,
  1267. FOREIGN KEY (user_id) REFERENCES users(id)
  1268. );
  1269. """)
  1270. # Crear tabla intermedia para ventas y productos
  1271. logger.info("Creando tabla intermedia de sale_products...")
  1272. cursor.execute("""
  1273. CREATE TABLE IF NOT EXISTS sale_products (
  1274. sale_id INTEGER NOT NULL,
  1275. product_id INTEGER NOT NULL,
  1276. quantity INTEGER NOT NULL DEFAULT 1,
  1277. FOREIGN KEY (sale_id) REFERENCES sales(id),
  1278. FOREIGN KEY (product_id) REFERENCES products(id)
  1279. );
  1280. """)
  1281. # Crear tabla de blacklist
  1282. logger.info("Creando tabla de blacklist...")
  1283. cursor.execute("""
  1284. CREATE TABLE IF NOT EXISTS blacklist (
  1285. id SERIAL PRIMARY KEY,
  1286. user_id INTEGER NOT NULL,
  1287. FOREIGN KEY (user_id) REFERENCES users(id)
  1288. );
  1289. """)
  1290. logger.info("Todas las tablas creadas correctamente.")
  1291. logger.info("Cargando datos de productos desde el archivo JSON...")
  1292. products_json = json.loads(open(PRODUCT_DATA_PATH, 'r', encoding='utf-8').read())
  1293. product_service = ProductDataService()
  1294. product_service.load_data(products_json)
  1295. conn.commit()
  1296. conn.close()
  1297. logger.info("Base de datos inicializada correctamente.")
  1298. data_bg_loaded = load_bg_data()
  1299. if __name__ == "__main__":
  1300. data_service = ProductDataService()
  1301. products = data_service.get_all()
  1302. print(products[0])