data_service.py 41 KB

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