data_service.py 49 KB

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