data_service.py 49 KB

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