data_service.py 43 KB

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