data_service.py 50 KB

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