data_service.py 46 KB

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