data_service.py 49 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386
  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)
  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. logger.info(f"Processing image: {image[:40]}... for product {product_id}")
  666. logger.info(f"updates: {updates}, params: {params}")
  667. extension = re.search(r'data:image/(.*?);base64,', image[:40])
  668. if not extension:
  669. raise ValueError("Invalid image format")
  670. extension = extension.group(1)
  671. image = self._image_process(f"{product.id}_img.{extension}", image)
  672. except ValueError as e:
  673. logger.error(f"Failed to process image: {e}")
  674. return False
  675. updates.append("image = %s")
  676. params.append(image)
  677. if status is not None:
  678. updates.append("status = %s")
  679. params.append(status)
  680. if not updates:
  681. conn.close()
  682. return False
  683. try:
  684. cursor.execute(f"UPDATE products SET {', '.join(updates)} WHERE id = %s", (*params, product_id))
  685. conn.commit()
  686. success = cursor.rowcount > 0
  687. if success:
  688. logger.info(f"Product with ID {product_id} updated.")
  689. return success
  690. except psycopg2.Error as e:
  691. logger.error(f"Failed to update product: {e}")
  692. return False
  693. finally:
  694. conn.close()
  695. def get_active_products(self) -> List[Product]:
  696. """Get only active products (status = 1)"""
  697. conn = self._get_connection()
  698. cursor = conn.cursor()
  699. cursor.execute("SELECT * FROM products WHERE status = 1")
  700. products = cursor.fetchall()
  701. conn.close()
  702. return [
  703. Product(
  704. id=product[0],
  705. name=product[1],
  706. type=product[2],
  707. description=product[3],
  708. price=product[4],
  709. image=product[5],
  710. status=product[6],
  711. promo_id=product[7],
  712. promo_price=product[8],
  713. promo_day=product[9]
  714. ) for product in products
  715. ]
  716. def get_inactive_products(self) -> List[Product]:
  717. """Get only inactive products (status = 0)"""
  718. conn = self._get_connection()
  719. cursor = conn.cursor()
  720. cursor.execute("SELECT * FROM products WHERE status = 0")
  721. products = cursor.fetchall()
  722. conn.close()
  723. return [
  724. Product(
  725. id=product[0],
  726. name=product[1],
  727. type=product[2],
  728. description=product[3],
  729. price=product[4],
  730. image=product[5],
  731. status=product[6],
  732. promo_id=product[7],
  733. promo_price=product[8],
  734. promo_day=product[9]
  735. ) for product in products
  736. ]
  737. def activate_product(self, product_id: int) -> bool:
  738. """Activate a product (set status to 1)"""
  739. return self.update(product_id, status=1)
  740. def deactivate_product(self, product_id: int) -> bool:
  741. """Deactivate a product (set status to 0)"""
  742. return self.update(product_id, status=0)
  743. def is_product_active(self, product_id: int) -> Optional[bool]:
  744. """Check if a product is active"""
  745. product = self.get_by_id(product_id)
  746. if product:
  747. return product.status == 1
  748. return None
  749. #endregion
  750. #region Delete
  751. def delete(self, product_id: int) -> bool:
  752. """Delete a product from the database"""
  753. conn = self._get_connection()
  754. cursor = conn.cursor()
  755. product = self.get_by_id(product_id)
  756. if product and product.image:
  757. os.remove(os.path.join(IMAGE_PATH, product.image.removeprefix(CURRENT_URL + "/images/")))
  758. cursor.execute("DELETE FROM products WHERE id = %s", (product_id,))
  759. conn.commit()
  760. success = cursor.rowcount > 0
  761. conn.close()
  762. if success:
  763. logger.info(f"Product with ID {product_id} deleted.")
  764. else:
  765. logger.error(f"Failed to delete product with ID {product_id}.")
  766. return success
  767. #endregion
  768. # Sales Data Service
  769. class SalesDataService(BaseDataService):
  770. """Service for managing sales"""
  771. #region C
  772. def create(self, user_id: int, fudo_id: str, total: float, table: int, product_ids: List[int], quantities: Optional[List[int]] = None) -> int:
  773. """Create a new sale with products and quantities"""
  774. conn = self._get_connection()
  775. cursor = conn.cursor()
  776. try:
  777. fecha = datetime.now().isoformat()
  778. # Insert sale
  779. cursor.execute(
  780. "INSERT INTO sales (user_id, total, fudo_id, date, table_number) VALUES (%s, %s, %s, %s, %s) RETURNING id",
  781. (user_id, total, fudo_id, fecha, table)
  782. )
  783. last_sale = cursor.fetchone()
  784. sale_id = last_sale[0] if last_sale else None
  785. if sale_id and product_ids:
  786. # Insert sale-product relationships with quantities
  787. if quantities is None:
  788. quantities = [1] * len(product_ids) # Default quantity 1
  789. for i, product_id in enumerate(product_ids):
  790. quantity = quantities[i] if i < len(quantities) else 1
  791. cursor.execute(
  792. "INSERT INTO sale_products (sale_id, product_id, quantity) VALUES (%s, %s, %s)",
  793. (sale_id, product_id, quantity)
  794. )
  795. conn.commit()
  796. if sale_id:
  797. logger.info(f"Sale created with ID: {sale_id}, fudo_id: {fudo_id}")
  798. return sale_id
  799. else:
  800. logger.error("Failed to create sale.")
  801. return -1
  802. except psycopg2.Error as e:
  803. logger.error(f"Failed to create sale: {e}")
  804. conn.rollback()
  805. return -1
  806. finally:
  807. conn.close()
  808. def add_product_to_sale(self, sale_id: int, product_id: int, quantity: int = 1) -> bool:
  809. """Add a product to an existing sale with quantity"""
  810. conn = self._get_connection()
  811. cursor = conn.cursor()
  812. try:
  813. cursor.execute(
  814. "INSERT INTO sale_products (sale_id, product_id, quantity) VALUES (%s, %s, %s)",
  815. (sale_id, product_id, quantity)
  816. )
  817. conn.commit()
  818. success = cursor.rowcount > 0
  819. if success:
  820. logger.info(f"Product {product_id} (qty: {quantity}) added to sale {sale_id}.")
  821. return success
  822. except psycopg2.IntegrityError as e:
  823. logger.error(f"Failed to add product to sale: {e}")
  824. return False
  825. finally:
  826. conn.close()
  827. def add_products_to_sale(self, sale_id: int, product_ids: List[int], quantities: Optional[List[int]] = None) -> bool:
  828. """Add multiple products to an existing sale with quantities"""
  829. conn = self._get_connection()
  830. cursor = conn.cursor()
  831. try:
  832. if quantities is None:
  833. quantities = [1] * len(product_ids) # Default quantity 1
  834. for i, product_id in enumerate(product_ids):
  835. quantity = quantities[i] if i < len(quantities) else 1
  836. cursor.execute(
  837. "INSERT INTO sale_products (sale_id, product_id, quantity) VALUES (%s, %s, %s)",
  838. (sale_id, product_id, quantity)
  839. )
  840. conn.commit()
  841. success = cursor.rowcount > 0
  842. if success:
  843. logger.info(f"Products added to sale {sale_id}.")
  844. return success
  845. except psycopg2.IntegrityError as e:
  846. logger.error(f"Failed to add products to sale: {e}")
  847. return False
  848. finally:
  849. conn.close()
  850. #endregion
  851. #region R
  852. def get_all(self) -> List[Sale]:
  853. """Get all sales from the database"""
  854. conn = self._get_connection()
  855. cursor = conn.cursor()
  856. cursor.execute("""
  857. SELECT s.id, s.user_id, s.total, s.fudo_id, s.date, s.table, u.name, u.email
  858. FROM sales s
  859. LEFT JOIN users u ON s.user_id = u.id
  860. ORDER BY s.date DESC
  861. """)
  862. sales = cursor.fetchall()
  863. conn.close()
  864. return [
  865. Sale(
  866. id=sale[0],
  867. user_id=sale[1],
  868. total=sale[2],
  869. fudo_id=sale[3],
  870. date=sale[4],
  871. table=sale[5],
  872. username=sale[6],
  873. user_email=sale[7],
  874. products=self.get_sale_products(sale[0])
  875. ) for sale in sales
  876. ]
  877. def get_by_id(self, sale_id: int) -> Optional[Sale]:
  878. """Get sale by ID"""
  879. conn = self._get_connection()
  880. cursor = conn.cursor()
  881. cursor.execute("""
  882. SELECT s.id, s.user_id, s.total, s.fudo_id, s.date, s.table, u.name, u.email
  883. FROM sales s
  884. LEFT JOIN users u ON s.user_id = u.id
  885. WHERE s.id = %s
  886. """, (sale_id,))
  887. sale = cursor.fetchone()
  888. conn.close()
  889. if sale:
  890. return Sale(
  891. id=sale[0],
  892. user_id=sale[1],
  893. total=sale[2],
  894. fudo_id=sale[3],
  895. date=sale[4],
  896. table=sale[5],
  897. username=sale[6],
  898. user_email=sale[7]
  899. )
  900. return None
  901. def get_by_fudo_id(self, fudo_id: str) -> Optional[Sale]:
  902. """Get sale by fudo_id"""
  903. conn = self._get_connection()
  904. cursor = conn.cursor()
  905. cursor.execute("""
  906. SELECT s.id, s.user_id, s.total, s.fudo_id, s.date, s.table, u.name, u.email
  907. FROM sales s
  908. LEFT JOIN users u ON s.user_id = u.id
  909. WHERE s.fudo_id = %s
  910. """, (fudo_id,))
  911. sale = cursor.fetchone()
  912. conn.close()
  913. if sale:
  914. return Sale(
  915. id=sale[0],
  916. user_id=sale[1],
  917. total=sale[2],
  918. fudo_id=sale[3],
  919. date=sale[4],
  920. table=sale[5],
  921. username=sale[6],
  922. user_email=sale[7]
  923. )
  924. return None
  925. def get_by_user(self, user_id: int) -> List[Sale]:
  926. """Get sales by user ID"""
  927. conn = self._get_connection()
  928. cursor = conn.cursor()
  929. cursor.execute(
  930. """
  931. SELECT s.id, s.user_id, s.total, s.fudo_id, s.date, s.table_number, u.name, u.email
  932. FROM sales s
  933. LEFT JOIN users u ON s.user_id = u.id
  934. WHERE s.user_id = %s
  935. ORDER BY s.date DESC
  936. """,
  937. (user_id,)
  938. )
  939. sales = cursor.fetchall()
  940. conn.close()
  941. return [
  942. Sale(
  943. id=sale[0],
  944. user_id=sale[1],
  945. total=sale[2],
  946. fudo_id=sale[3],
  947. date=sale[4],
  948. table=sale[5],
  949. username=sale[6],
  950. user_email=sale[7],
  951. products=self.get_sale_products(sale[0])
  952. ) for sale in sales
  953. ]
  954. def get_sale_products_ids(self, sale_id: int) -> List[int]:
  955. """Get product IDs for a specific sale"""
  956. conn = self._get_connection()
  957. cursor = conn.cursor()
  958. cursor.execute("""
  959. SELECT product_id FROM sale_products WHERE sale_id = %s
  960. """, (sale_id,))
  961. products = cursor.fetchall()
  962. conn.close()
  963. return [product[0] for product in products]
  964. def get_sale_products(self, sale_id: int) -> List[Product]:
  965. """Get products for a specific sale with quantities"""
  966. conn = self._get_connection()
  967. cursor = conn.cursor()
  968. cursor.execute("""
  969. SELECT p.id, p.name, p.type, p.description, p.price, p.image, p.status, sp.quantity
  970. FROM sale_products sp
  971. JOIN products p ON sp.product_id = p.id
  972. WHERE sp.sale_id = %s
  973. """, (sale_id,))
  974. products = cursor.fetchall()
  975. conn.close()
  976. return [
  977. Product(
  978. id=product[0],
  979. name=product[1],
  980. type=product[2],
  981. description=product[3],
  982. price=product[4],
  983. image=product[5],
  984. status=product[6],
  985. quantity=product[7],
  986. promo_id=None, # Not applicable for sale products
  987. promo_price=None,
  988. promo_day=None
  989. ) for product in products
  990. ]
  991. def get_by_table(self, table: int) -> List[Sale]:
  992. """Get sales by table number"""
  993. conn = self._get_connection()
  994. cursor = conn.cursor()
  995. cursor.execute(
  996. """
  997. SELECT s.id, s.user_id, s.total, s.fudo_id, s.date, s.table_number, u.name, u.email
  998. FROM sales s
  999. LEFT JOIN users u ON s.user_id = u.id
  1000. WHERE s.table_number = %s
  1001. ORDER BY s.date DESC
  1002. """,
  1003. (table,)
  1004. )
  1005. sales = cursor.fetchall()
  1006. conn.close()
  1007. return [
  1008. Sale(
  1009. id=sale[0],
  1010. user_id=sale[1],
  1011. total=sale[2],
  1012. fudo_id=sale[3],
  1013. date=sale[4],
  1014. table=sale[5],
  1015. username=sale[6],
  1016. user_email=sale[7]
  1017. ) for sale in sales
  1018. ]
  1019. def update_product_quantity(self, sale_id: int, product_id: int, new_quantity: int) -> bool:
  1020. """Update quantity of a product in a sale"""
  1021. conn = self._get_connection()
  1022. cursor = conn.cursor()
  1023. try:
  1024. cursor.execute(
  1025. "UPDATE sale_products SET quantity = %s WHERE sale_id = %s AND product_id = %s",
  1026. (new_quantity, sale_id, product_id)
  1027. )
  1028. conn.commit()
  1029. success = cursor.rowcount > 0
  1030. if success:
  1031. logger.info(f"Updated quantity to {new_quantity} for product {product_id} in sale {sale_id}.")
  1032. return success
  1033. except psycopg2.Error as e:
  1034. logger.error(f"Failed to update product quantity: {e}")
  1035. return False
  1036. finally:
  1037. conn.close()
  1038. def get_product_quantity_in_sale(self, sale_id: int, product_id: int) -> Optional[int]:
  1039. """Get quantity of a specific product in a sale"""
  1040. conn = self._get_connection()
  1041. cursor = conn.cursor()
  1042. cursor.execute(
  1043. "SELECT quantity FROM sale_products WHERE sale_id = %s AND product_id = %s",
  1044. (sale_id, product_id)
  1045. )
  1046. result = cursor.fetchone()
  1047. conn.close()
  1048. return result[0] if result else None
  1049. #endregion
  1050. #region U
  1051. def update(self, sale_id: int, user_id=None, total=None, table=None) -> bool:
  1052. """Update sale information (products should be updated via specific methods)"""
  1053. conn = self._get_connection()
  1054. cursor = conn.cursor()
  1055. updates = []
  1056. params = []
  1057. if user_id is not None:
  1058. updates.append("user_id = %s")
  1059. params.append(user_id)
  1060. if total is not None:
  1061. updates.append("total = %s")
  1062. params.append(total)
  1063. if table is not None:
  1064. updates.append("table = %s")
  1065. params.append(table)
  1066. if not updates:
  1067. conn.close()
  1068. return False
  1069. try:
  1070. cursor.execute(f"UPDATE sales SET {', '.join(updates)} WHERE id = %s", (*params, sale_id))
  1071. conn.commit()
  1072. success = cursor.rowcount > 0
  1073. if success:
  1074. logger.info(f"Sale with ID {sale_id} updated.")
  1075. return success
  1076. except psycopg2.Error as e:
  1077. logger.error(f"Failed to update sale: {e}")
  1078. return False
  1079. finally:
  1080. conn.close()
  1081. #endregion
  1082. #region D
  1083. def delete(self, sale_id: int) -> bool:
  1084. """Delete a sale and its product relationships"""
  1085. conn = self._get_connection()
  1086. cursor = conn.cursor()
  1087. try:
  1088. # Delete sale-product relationships first
  1089. cursor.execute("DELETE FROM sale_products WHERE sale_id = %s", (sale_id,))
  1090. # Delete the sale
  1091. cursor.execute("DELETE FROM sales WHERE id = %s", (sale_id,))
  1092. conn.commit()
  1093. success = cursor.rowcount > 0
  1094. if success:
  1095. logger.info(f"Sale with ID {sale_id} deleted.")
  1096. else:
  1097. logger.error(f"Failed to delete sale with ID {sale_id}.")
  1098. return success
  1099. except psycopg2.Error as e:
  1100. logger.error(f"Failed to delete sale: {e}")
  1101. return False
  1102. finally:
  1103. conn.close()
  1104. def remove_product_from_sale(self, sale_id: int, product_id: int) -> bool:
  1105. """Remove a product from a sale (removes all quantity)"""
  1106. conn = self._get_connection()
  1107. cursor = conn.cursor()
  1108. cursor.execute(
  1109. "DELETE FROM sale_products WHERE sale_id = %s AND product_id = %s",
  1110. (sale_id, product_id)
  1111. )
  1112. conn.commit()
  1113. success = cursor.rowcount > 0
  1114. conn.close()
  1115. if success:
  1116. logger.info(f"Product {product_id} removed from sale {sale_id}.")
  1117. else:
  1118. logger.error(f"Failed to remove product {product_id} from sale {sale_id}.")
  1119. return success
  1120. def decrease_product_quantity(self, sale_id: int, product_id: int, decrease_by: int = 1) -> bool:
  1121. """Decrease quantity of a product in a sale, removes if quantity becomes 0 or less"""
  1122. conn = self._get_connection()
  1123. cursor = conn.cursor()
  1124. try:
  1125. # Get current quantity
  1126. cursor.execute(
  1127. "SELECT quantity FROM sale_products WHERE sale_id = %s AND product_id = %s",
  1128. (sale_id, product_id)
  1129. )
  1130. result = cursor.fetchone()
  1131. if not result:
  1132. return False
  1133. current_quantity = result[0]
  1134. new_quantity = current_quantity - decrease_by
  1135. if new_quantity <= 0:
  1136. # Remove the product completely
  1137. cursor.execute(
  1138. "DELETE FROM sale_products WHERE sale_id = %s AND product_id = %s",
  1139. (sale_id, product_id)
  1140. )
  1141. logger.info(f"Product {product_id} removed from sale {sale_id} (quantity reached 0).")
  1142. else:
  1143. # Update with new quantity
  1144. cursor.execute(
  1145. "UPDATE sale_products SET quantity = %s WHERE sale_id = %s AND product_id = %s",
  1146. (new_quantity, sale_id, product_id)
  1147. )
  1148. logger.info(f"Product {product_id} quantity decreased to {new_quantity} in sale {sale_id}.")
  1149. conn.commit()
  1150. return True
  1151. except psycopg2.Error as e:
  1152. logger.error(f"Failed to decrease product quantity: {e}")
  1153. return False
  1154. finally:
  1155. conn.close()
  1156. #endregion
  1157. # Factory class to get service instances
  1158. class DataServiceFactory:
  1159. """Factory class to create data service instances"""
  1160. @staticmethod
  1161. def get_user_service() -> UserDataService:
  1162. """Get user data service instance"""
  1163. return UserDataService()
  1164. @staticmethod
  1165. def get_blacklist_service() -> BlacklistDataService:
  1166. """Get blacklist data service instance"""
  1167. return BlacklistDataService()
  1168. @staticmethod
  1169. def get_product_service() -> ProductDataService:
  1170. """Get product data service instance"""
  1171. return ProductDataService()
  1172. @staticmethod
  1173. def get_sales_service() -> SalesDataService:
  1174. """Get sales data service instance"""
  1175. return SalesDataService()
  1176. # Helper functions for background data
  1177. def load_bg_data() -> List[Dict[str, str]]:
  1178. """Load background data for AI assistant"""
  1179. try:
  1180. with open(BG_DATA_PATH, 'r', encoding='utf-8') as f:
  1181. return json.load(f)
  1182. except FileNotFoundError:
  1183. logger.error(f"Data file not found at {BG_DATA_PATH}. Serving with empty data.")
  1184. return []
  1185. except json.JSONDecodeError:
  1186. logger.error(f"Could not decode JSON from {BG_DATA_PATH}. Serving with empty data.")
  1187. return []
  1188. def initialize_db():
  1189. # En PostgreSQL, no se crea el archivo de base de datos, así que verificamos si las tablas existen.
  1190. try:
  1191. conn = psycopg2.connect(
  1192. dbname=POSTGRESQL_DB_CONFIG['dbname'],
  1193. user=POSTGRESQL_DB_CONFIG['user'],
  1194. password=POSTGRESQL_DB_CONFIG['password'],
  1195. host=POSTGRESQL_DB_CONFIG['host'],
  1196. port=POSTGRESQL_DB_CONFIG['port']
  1197. )
  1198. cursor = conn.cursor()
  1199. # Verificar si la tabla 'users' ya existe
  1200. cursor.execute("""
  1201. SELECT EXISTS (
  1202. SELECT FROM information_schema.tables
  1203. WHERE table_name = 'users'
  1204. );
  1205. """)
  1206. exists = cursor.fetchone()
  1207. if exists:
  1208. logger.info("La base de datos ya está inicializada, no se necesita inicializar.")
  1209. conn.close()
  1210. return
  1211. conn.close()
  1212. except Exception as e:
  1213. logger.error(f"Error comprobando la existencia de tablas: {e}")
  1214. # Si hay error, continuamos con la inicialización
  1215. conn = psycopg2.connect(
  1216. dbname=POSTGRESQL_DB_CONFIG['dbname'],
  1217. user=POSTGRESQL_DB_CONFIG['user'],
  1218. password=POSTGRESQL_DB_CONFIG['password'],
  1219. host=POSTGRESQL_DB_CONFIG['host'],
  1220. port=POSTGRESQL_DB_CONFIG['port']
  1221. )
  1222. cursor = conn.cursor()
  1223. # Crear tabla de usuarios
  1224. logger.info("Inicializando base de datos...")
  1225. logger.info("Creando tabla de usuarios...")
  1226. cursor.execute("""
  1227. CREATE TABLE IF NOT EXISTS users (
  1228. id SERIAL PRIMARY KEY,
  1229. email VARCHAR(255) UNIQUE NOT NULL,
  1230. name VARCHAR(255) NOT NULL,
  1231. rut VARCHAR(20) UNIQUE NOT NULL,
  1232. pin_hash TEXT NOT NULL,
  1233. kleincoins TEXT NOT NULL,
  1234. created_at TEXT NOT NULL,
  1235. permissions INTEGER DEFAULT 0 NOT NULL CHECK (permissions IN (0, 1, 2)) -- 0: Usuario normal, 1: Administrador, 2: Superusuario
  1236. reward_progress INTEGER DEFAULT 0 NOT NULL CHECK (reward_progress >= 0 AND reward_progress <= 100) -- Progreso de recompensas
  1237. );
  1238. """)
  1239. # Crear tabla de productos
  1240. logger.info("Creando tabla de products...")
  1241. cursor.execute("""
  1242. CREATE TABLE IF NOT EXISTS products (
  1243. id INTEGER PRIMARY KEY,
  1244. name VARCHAR(255) NOT NULL,
  1245. type VARCHAR(100) NOT NULL,
  1246. description TEXT NOT NULL,
  1247. price INTEGER NOT NULL,
  1248. image TEXT,
  1249. status INTEGER DEFAULT 1 NOT NULL CHECK (status IN (0, 1)) -- 0: Inactivo, 1: Activo,
  1250. promo_id INTEGER,
  1251. promo_price INTEGER,
  1252. promo_day INTEGER CHECK (promo_day >= 1 AND promo_day <= 7)
  1253. );
  1254. """)
  1255. # Crear tabla de ventas
  1256. logger.info("Creando tabla de sales...")
  1257. cursor.execute("""
  1258. CREATE TABLE IF NOT EXISTS sales (
  1259. id SERIAL PRIMARY KEY,
  1260. user_id INTEGER NOT NULL,
  1261. total INTEGER NOT NULL,
  1262. fudo_id TEXT UNIQUE NOT NULL,
  1263. date TEXT NOT NULL,
  1264. table_number INTEGER NOT NULL,
  1265. FOREIGN KEY (user_id) REFERENCES users(id)
  1266. );
  1267. """)
  1268. # Crear tabla intermedia para ventas y productos
  1269. logger.info("Creando tabla intermedia de sale_products...")
  1270. cursor.execute("""
  1271. CREATE TABLE IF NOT EXISTS sale_products (
  1272. sale_id INTEGER NOT NULL,
  1273. product_id INTEGER NOT NULL,
  1274. quantity INTEGER NOT NULL DEFAULT 1,
  1275. FOREIGN KEY (sale_id) REFERENCES sales(id),
  1276. FOREIGN KEY (product_id) REFERENCES products(id)
  1277. );
  1278. """)
  1279. # Crear tabla de blacklist
  1280. logger.info("Creando tabla de blacklist...")
  1281. cursor.execute("""
  1282. CREATE TABLE IF NOT EXISTS blacklist (
  1283. id SERIAL PRIMARY KEY,
  1284. user_id INTEGER NOT NULL,
  1285. FOREIGN KEY (user_id) REFERENCES users(id)
  1286. );
  1287. """)
  1288. logger.info("Todas las tablas creadas correctamente.")
  1289. logger.info("Cargando datos de productos desde el archivo JSON...")
  1290. products_json = json.loads(open(PRODUCT_DATA_PATH, 'r', encoding='utf-8').read())
  1291. product_service = ProductDataService()
  1292. product_service.load_data(products_json)
  1293. conn.commit()
  1294. conn.close()
  1295. logger.info("Base de datos inicializada correctamente.")
  1296. data_bg_loaded = load_bg_data()