data_service.py 48 KB

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