data_service.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815
  1. import json
  2. import sqlite3
  3. from typing import List, Dict, Optional
  4. from abc import ABC, abstractmethod
  5. from config.settings import BG_DATA_PATH, DB_PATH
  6. from logging import getLogger
  7. from datetime import datetime
  8. import uuid
  9. logger = getLogger(__name__)
  10. """
  11. ESQUEMA DE BASE DE DATOS SQLITE (data.db)
  12. 1. Tabla: users
  13. -----------------------------------
  14. - id INTEGER PRIMARY KEY AUTOINCREMENT
  15. - correo TEXT UNIQUE NOT NULL
  16. - nombre TEXT NOT NULL
  17. - rut TEXT UNIQUE NOT NULL
  18. - pin_hash TEXT NOT NULL
  19. (Guarda la información del usuario con su pin hasheado)
  20. 2. Tabla: productos
  21. -----------------------------------
  22. - id INTEGER PRIMARY KEY AUTOINCREMENT
  23. - name TEXT NOT NULL
  24. - type TEXT
  25. - description TEXT
  26. - price REAL NOT NULL
  27. - image TEXT (URL de la imagen)
  28. (Guarda los productos disponibles para venta)
  29. 3. Tabla: ventas
  30. -----------------------------------
  31. - id INTEGER PRIMARY KEY AUTOINCREMENT
  32. - user_id INTEGER NOT NULL (relación a users.id)
  33. - total REAL NOT NULL (precio total de la venta)
  34. - venta_uuid TEXT UNIQUE NOT NULL (ID string único por venta)
  35. - fecha TEXT NOT NULL (fecha y hora en formato ISO 8601)
  36. (Guarda cada venta, asociada a un usuario)
  37. 4. Tabla: venta_productos
  38. -----------------------------------
  39. - venta_id INTEGER NOT NULL (relación a ventas.id)
  40. - producto_id INTEGER NOT NULL (relación a productos.id)
  41. (Relación muchos a muchos entre ventas y productos)
  42. 5. Tabla: blacklist
  43. -----------------------------------
  44. - id INTEGER PRIMARY KEY AUTOINCREMENT
  45. - user_id INTEGER NOT NULL (relación a users.id)
  46. (Usuarios bloqueados o no autorizados para ciertas acciones)
  47. RELACIONES:
  48. -----------------------------------
  49. - users puede tener muchas ventas
  50. - ventas puede tener muchos productos (y viceversa), por eso se usa una tabla intermedia (venta_productos)
  51. - productos pueden repetirse en múltiples ventas
  52. """
  53. # Base abstract class for data access
  54. class BaseDataService(ABC):
  55. """Abstract base class for data services"""
  56. def __init__(self, db_path: str = DB_PATH):
  57. self.db_path = db_path
  58. def _get_connection(self) -> sqlite3.Connection:
  59. """Get database connection"""
  60. return sqlite3.connect(self.db_path)
  61. @abstractmethod
  62. def get_all(self) -> List[Dict]:
  63. """Get all records"""
  64. pass
  65. @abstractmethod
  66. def get_by_id(self, id: int) -> Optional[Dict]:
  67. """Get record by ID"""
  68. pass
  69. @abstractmethod
  70. def create(self, **kwargs) -> int:
  71. """Create new record"""
  72. pass
  73. @abstractmethod
  74. def update(self, id: int, **kwargs) -> bool:
  75. """Update record"""
  76. pass
  77. @abstractmethod
  78. def delete(self, id: int) -> bool:
  79. """Delete record"""
  80. pass
  81. # User Data Service
  82. class UserDataService(BaseDataService):
  83. """Service for managing user data"""
  84. #region Create
  85. def create(self, correo: str, nombre: str, rut: str, pin_hash: str) -> int:
  86. """Add a new user to the database"""
  87. conn = self._get_connection()
  88. cursor = conn.cursor()
  89. try:
  90. cursor.execute(
  91. "INSERT INTO users (correo, nombre, rut, pin_hash) VALUES (?, ?, ?, ?)",
  92. (correo, nombre, rut, pin_hash)
  93. )
  94. conn.commit()
  95. user_id = cursor.lastrowid
  96. if user_id:
  97. logger.info(f"User added with ID: {user_id}")
  98. return user_id
  99. else:
  100. logger.error("Failed to add user.")
  101. return -1
  102. except sqlite3.IntegrityError as e:
  103. logger.error(f"Failed to add user: {e}")
  104. return -1
  105. finally:
  106. conn.close()
  107. #endregion
  108. #region Read
  109. def get_all(self) -> List[Dict[str, str]]:
  110. """Get all users from the database"""
  111. conn = self._get_connection()
  112. cursor = conn.cursor()
  113. cursor.execute("SELECT * FROM users")
  114. users = cursor.fetchall()
  115. conn.close()
  116. return [
  117. {
  118. "id": user[0],
  119. "correo": user[1],
  120. "nombre": user[2],
  121. "rut": user[3],
  122. "pin_hash": user[4]
  123. } for user in users
  124. ]
  125. def get_by_id(self, user_id: int) -> Optional[Dict[str, str]]:
  126. """Get user data from the database"""
  127. conn = self._get_connection()
  128. cursor = conn.cursor()
  129. cursor.execute("SELECT * FROM users WHERE id = ?", (user_id,))
  130. user = cursor.fetchone()
  131. conn.close()
  132. if user:
  133. return {
  134. "id": user[0],
  135. "correo": user[1],
  136. "nombre": user[2],
  137. "rut": user[3],
  138. "pin_hash": user[4]
  139. }
  140. return None
  141. def get_by_email(self, correo: str) -> Optional[Dict[str, str]]:
  142. """Get user by email"""
  143. conn = self._get_connection()
  144. cursor = conn.cursor()
  145. cursor.execute("SELECT * FROM users WHERE correo = ?", (correo,))
  146. user = cursor.fetchone()
  147. conn.close()
  148. if user:
  149. return {
  150. "id": user[0],
  151. "correo": user[1],
  152. "nombre": user[2],
  153. "rut": user[3],
  154. "pin_hash": user[4]
  155. }
  156. return None
  157. def get_by_rut(self, rut: str) -> Optional[Dict[str, str]]:
  158. """Get user by RUT"""
  159. conn = self._get_connection()
  160. cursor = conn.cursor()
  161. cursor.execute("SELECT * FROM users WHERE rut = ?", (rut,))
  162. user = cursor.fetchone()
  163. conn.close()
  164. if user:
  165. return {
  166. "id": user[0],
  167. "correo": user[1],
  168. "nombre": user[2],
  169. "rut": user[3],
  170. "pin_hash": user[4]
  171. }
  172. return None
  173. #endregion
  174. #region Update
  175. def update(self, user_id: int, correo=None, nombre=None, rut=None, pin_hash=None) -> bool:
  176. """Update user information in the database"""
  177. conn = self._get_connection()
  178. cursor = conn.cursor()
  179. updates = []
  180. params = []
  181. if correo:
  182. updates.append("correo = ?")
  183. params.append(correo)
  184. if nombre:
  185. updates.append("nombre = ?")
  186. params.append(nombre)
  187. if rut:
  188. updates.append("rut = ?")
  189. params.append(rut)
  190. if pin_hash:
  191. updates.append("pin_hash = ?")
  192. params.append(pin_hash)
  193. if not updates:
  194. conn.close()
  195. return False
  196. try:
  197. cursor.execute(f"UPDATE users SET {', '.join(updates)} WHERE id = ?", (*params, user_id))
  198. conn.commit()
  199. success = cursor.rowcount > 0
  200. if success:
  201. logger.info(f"User with ID {user_id} updated.")
  202. return success
  203. except sqlite3.IntegrityError as e:
  204. logger.error(f"Failed to update user: {e}")
  205. return False
  206. finally:
  207. conn.close()
  208. #endregion
  209. #region Delete
  210. def delete(self, user_id: int) -> bool:
  211. """Delete a user from the database"""
  212. conn = self._get_connection()
  213. cursor = conn.cursor()
  214. cursor.execute("DELETE FROM users WHERE id = ?", (user_id,))
  215. conn.commit()
  216. conn.close()
  217. if cursor.rowcount > 0:
  218. logger.info(f"User with ID {user_id} deleted.")
  219. return True
  220. else:
  221. logger.error(f"Failed to delete user with ID {user_id}.")
  222. return False
  223. #endregion
  224. # Blacklist Data Service
  225. class BlacklistDataService(BaseDataService):
  226. """Service for managing blacklisted users"""
  227. #region Create
  228. def create(self, user_id: int) -> int:
  229. """Add a user to the blacklist"""
  230. conn = self._get_connection()
  231. cursor = conn.cursor()
  232. try:
  233. cursor.execute("INSERT INTO blacklist (user_id) VALUES (?)", (user_id,))
  234. conn.commit()
  235. blacklist_id = cursor.lastrowid
  236. if blacklist_id:
  237. logger.info(f"User with ID {user_id} added to blacklist.")
  238. return blacklist_id
  239. else:
  240. logger.error(f"Failed to add user with ID {user_id} to blacklist.")
  241. return -1
  242. except sqlite3.IntegrityError as e:
  243. logger.error(f"Failed to add user to blacklist: {e}")
  244. return -1
  245. finally:
  246. conn.close()
  247. #endregion
  248. #region Read
  249. def get_all(self) -> List[Dict]:
  250. """Get all blacklisted users"""
  251. conn = self._get_connection()
  252. cursor = conn.cursor()
  253. cursor.execute("""
  254. SELECT b.id, b.user_id, u.correo, u.nombre, u.rut
  255. FROM blacklist b
  256. LEFT JOIN users u ON b.user_id = u.id
  257. """)
  258. blacklisted = cursor.fetchall()
  259. conn.close()
  260. return [
  261. {
  262. "id": row[0],
  263. "user_id": row[1],
  264. "correo": row[2],
  265. "nombre": row[3],
  266. "rut": row[4]
  267. } for row in blacklisted
  268. ]
  269. def get_by_id(self, id: int) -> Optional[Dict]:
  270. """Get blacklist entry by ID"""
  271. conn = self._get_connection()
  272. cursor = conn.cursor()
  273. cursor.execute("""
  274. SELECT b.id, b.user_id, u.correo, u.nombre, u.rut
  275. FROM blacklist b
  276. LEFT JOIN users u ON b.user_id = u.id
  277. WHERE b.id = ?
  278. """, (id,))
  279. row = cursor.fetchone()
  280. conn.close()
  281. if row:
  282. return {
  283. "id": row[0],
  284. "user_id": row[1],
  285. "correo": row[2],
  286. "nombre": row[3],
  287. "rut": row[4]
  288. }
  289. return None
  290. def get_blacklisted_user_ids(self) -> List[int]:
  291. """Get a list of blacklisted user IDs"""
  292. conn = self._get_connection()
  293. cursor = conn.cursor()
  294. cursor.execute("SELECT user_id FROM blacklist")
  295. blacklisted_users = [row[0] for row in cursor.fetchall()]
  296. conn.close()
  297. return blacklisted_users
  298. def is_user_blacklisted(self, user_id: int) -> bool:
  299. """Check if a user is blacklisted"""
  300. conn = self._get_connection()
  301. cursor = conn.cursor()
  302. cursor.execute("SELECT * FROM blacklist WHERE user_id = ?", (user_id,))
  303. blacklisted = cursor.fetchone() is not None
  304. conn.close()
  305. return blacklisted
  306. #endregion
  307. #region Update
  308. def update(self, id: int, **kwargs) -> bool:
  309. """Update blacklist entry (not commonly used)"""
  310. # Blacklist entries typically don't need updates
  311. return False
  312. #endregion
  313. #region Delete
  314. def delete(self, id: int) -> bool:
  315. """Remove a blacklist entry by ID"""
  316. conn = self._get_connection()
  317. cursor = conn.cursor()
  318. cursor.execute("DELETE FROM blacklist WHERE id = ?", (id,))
  319. conn.commit()
  320. success = cursor.rowcount > 0
  321. conn.close()
  322. if success:
  323. logger.info(f"Blacklist entry with ID {id} removed.")
  324. else:
  325. logger.error(f"Failed to remove blacklist entry with ID {id}.")
  326. return success
  327. def remove_user_from_blacklist(self, user_id: int) -> bool:
  328. """Remove a user from the blacklist"""
  329. conn = self._get_connection()
  330. cursor = conn.cursor()
  331. cursor.execute("DELETE FROM blacklist WHERE user_id = ?", (user_id,))
  332. conn.commit()
  333. success = cursor.rowcount > 0
  334. conn.close()
  335. if success:
  336. logger.info(f"User with ID {user_id} removed from blacklist.")
  337. else:
  338. logger.error(f"Failed to remove user with ID {user_id} from blacklist.")
  339. return success
  340. #endregion
  341. # Product Data Service
  342. class ProductDataService(BaseDataService):
  343. """Service for managing products"""
  344. #region Create
  345. def create(self, name: str, price: float, type: Optional[str] = None, description: Optional[str] = None, image: Optional[str] = None) -> int:
  346. """Add a new product to the database"""
  347. conn = self._get_connection()
  348. cursor = conn.cursor()
  349. try:
  350. cursor.execute(
  351. "INSERT INTO productos (name, type, description, price, image) VALUES (?, ?, ?, ?, ?)",
  352. (name, type, description, price, image)
  353. )
  354. conn.commit()
  355. product_id = cursor.lastrowid
  356. if product_id:
  357. logger.info(f"Product added with ID: {product_id}")
  358. return product_id
  359. else:
  360. logger.error("Failed to add product.")
  361. return -1
  362. except sqlite3.Error as e:
  363. logger.error(f"Failed to add product: {e}")
  364. return -1
  365. finally:
  366. conn.close()
  367. #endregion
  368. #region Read
  369. def get_all(self) -> List[Dict[str, str]]:
  370. """Get all products from the database"""
  371. conn = self._get_connection()
  372. cursor = conn.cursor()
  373. cursor.execute("SELECT * FROM productos")
  374. products = cursor.fetchall()
  375. conn.close()
  376. return [
  377. {
  378. "id": product[0],
  379. "name": product[1],
  380. "type": product[2],
  381. "description": product[3],
  382. "price": product[4],
  383. "image": product[5]
  384. } for product in products
  385. ]
  386. def get_by_id(self, product_id: int) -> Optional[Dict]:
  387. """Get product by ID"""
  388. conn = self._get_connection()
  389. cursor = conn.cursor()
  390. cursor.execute("SELECT * FROM productos WHERE id = ?", (product_id,))
  391. product = cursor.fetchone()
  392. conn.close()
  393. if product:
  394. return {
  395. "id": product[0],
  396. "name": product[1],
  397. "type": product[2],
  398. "description": product[3],
  399. "price": product[4],
  400. "image": product[5]
  401. }
  402. return None
  403. def get_by_type(self, product_type: str) -> List[Dict]:
  404. """Get products by type"""
  405. conn = self._get_connection()
  406. cursor = conn.cursor()
  407. cursor.execute("SELECT * FROM productos WHERE type = ?", (product_type,))
  408. products = cursor.fetchall()
  409. conn.close()
  410. return [
  411. {
  412. "id": product[0],
  413. "name": product[1],
  414. "type": product[2],
  415. "description": product[3],
  416. "price": product[4],
  417. "image": product[5]
  418. } for product in products
  419. ]
  420. def search_by_name(self, name: str) -> List[Dict]:
  421. """Search products by name"""
  422. conn = self._get_connection()
  423. cursor = conn.cursor()
  424. cursor.execute("SELECT * FROM productos WHERE name LIKE ?", (f"%{name}%",))
  425. products = cursor.fetchall()
  426. conn.close()
  427. return [
  428. {
  429. "id": product[0],
  430. "name": product[1],
  431. "type": product[2],
  432. "description": product[3],
  433. "price": product[4],
  434. "image": product[5]
  435. } for product in products
  436. ]
  437. #endregion
  438. #region Update
  439. def update(self, product_id: int, name=None, type=None, description=None, price=None, image=None) -> bool:
  440. """Update product information"""
  441. conn = self._get_connection()
  442. cursor = conn.cursor()
  443. updates = []
  444. params = []
  445. if name is not None:
  446. updates.append("name = ?")
  447. params.append(name)
  448. if type is not None:
  449. updates.append("type = ?")
  450. params.append(type)
  451. if description is not None:
  452. updates.append("description = ?")
  453. params.append(description)
  454. if price is not None:
  455. updates.append("price = ?")
  456. params.append(price)
  457. if image is not None:
  458. updates.append("image = ?")
  459. params.append(image)
  460. if not updates:
  461. conn.close()
  462. return False
  463. try:
  464. cursor.execute(f"UPDATE productos SET {', '.join(updates)} WHERE id = ?", (*params, product_id))
  465. conn.commit()
  466. success = cursor.rowcount > 0
  467. if success:
  468. logger.info(f"Product with ID {product_id} updated.")
  469. return success
  470. except sqlite3.Error as e:
  471. logger.error(f"Failed to update product: {e}")
  472. return False
  473. finally:
  474. conn.close()
  475. #endregion
  476. #region Delete
  477. def delete(self, product_id: int) -> bool:
  478. """Delete a product from the database"""
  479. conn = self._get_connection()
  480. cursor = conn.cursor()
  481. cursor.execute("DELETE FROM productos WHERE id = ?", (product_id,))
  482. conn.commit()
  483. success = cursor.rowcount > 0
  484. conn.close()
  485. if success:
  486. logger.info(f"Product with ID {product_id} deleted.")
  487. else:
  488. logger.error(f"Failed to delete product with ID {product_id}.")
  489. return success
  490. #endregion
  491. # Sales Data Service
  492. class SalesDataService(BaseDataService):
  493. """Service for managing sales"""
  494. #region C
  495. def create(self, user_id: int, total: float, product_ids: List[int]) -> int:
  496. """Create a new sale with products"""
  497. conn = self._get_connection()
  498. cursor = conn.cursor()
  499. try:
  500. # Generate unique UUID for the sale
  501. venta_uuid = str(uuid.uuid4())
  502. fecha = datetime.now().isoformat()
  503. # Insert sale
  504. cursor.execute(
  505. "INSERT INTO ventas (user_id, total, venta_uuid, fecha) VALUES (?, ?, ?, ?)",
  506. (user_id, total, venta_uuid, fecha)
  507. )
  508. sale_id = cursor.lastrowid
  509. if sale_id and product_ids:
  510. # Insert sale-product relationships
  511. for product_id in product_ids:
  512. cursor.execute(
  513. "INSERT INTO venta_productos (venta_id, producto_id) VALUES (?, ?)",
  514. (sale_id, product_id)
  515. )
  516. conn.commit()
  517. if sale_id:
  518. logger.info(f"Sale created with ID: {sale_id}, UUID: {venta_uuid}")
  519. return sale_id
  520. else:
  521. logger.error("Failed to create sale.")
  522. return -1
  523. except sqlite3.Error as e:
  524. logger.error(f"Failed to create sale: {e}")
  525. conn.rollback()
  526. return -1
  527. finally:
  528. conn.close()
  529. def add_product_to_sale(self, sale_id: int, product_id: int) -> bool:
  530. """Add a product to an existing sale"""
  531. conn = self._get_connection()
  532. cursor = conn.cursor()
  533. try:
  534. cursor.execute(
  535. "INSERT INTO venta_productos (venta_id, producto_id) VALUES (?, ?)",
  536. (sale_id, product_id)
  537. )
  538. conn.commit()
  539. success = cursor.rowcount > 0
  540. if success:
  541. logger.info(f"Product {product_id} added to sale {sale_id}.")
  542. return success
  543. except sqlite3.IntegrityError as e:
  544. logger.error(f"Failed to add product to sale: {e}")
  545. return False
  546. finally:
  547. conn.close()
  548. #endregion
  549. #region R
  550. def get_all(self) -> List[Dict]:
  551. """Get all sales from the database"""
  552. conn = self._get_connection()
  553. cursor = conn.cursor()
  554. cursor.execute("""
  555. SELECT v.id, v.user_id, v.total, v.venta_uuid, v.fecha, u.nombre, u.correo
  556. FROM ventas v
  557. LEFT JOIN users u ON v.user_id = u.id
  558. ORDER BY v.fecha DESC
  559. """)
  560. sales = cursor.fetchall()
  561. conn.close()
  562. return [
  563. {
  564. "id": sale[0],
  565. "user_id": sale[1],
  566. "total": sale[2],
  567. "venta_uuid": sale[3],
  568. "fecha": sale[4],
  569. "user_name": sale[5],
  570. "user_email": sale[6]
  571. } for sale in sales
  572. ]
  573. def get_by_id(self, sale_id: int) -> Optional[Dict]:
  574. """Get sale by ID"""
  575. conn = self._get_connection()
  576. cursor = conn.cursor()
  577. cursor.execute("""
  578. SELECT v.id, v.user_id, v.total, v.venta_uuid, v.fecha, u.nombre, u.correo
  579. FROM ventas v
  580. LEFT JOIN users u ON v.user_id = u.id
  581. WHERE v.id = ?
  582. """, (sale_id,))
  583. sale = cursor.fetchone()
  584. conn.close()
  585. if sale:
  586. return {
  587. "id": sale[0],
  588. "user_id": sale[1],
  589. "total": sale[2],
  590. "venta_uuid": sale[3],
  591. "fecha": sale[4],
  592. "user_name": sale[5],
  593. "user_email": sale[6]
  594. }
  595. return None
  596. def get_by_uuid(self, venta_uuid: str) -> Optional[Dict]:
  597. """Get sale by UUID"""
  598. conn = self._get_connection()
  599. cursor = conn.cursor()
  600. cursor.execute("""
  601. SELECT v.id, v.user_id, v.total, v.venta_uuid, v.fecha, u.nombre, u.correo
  602. FROM ventas v
  603. LEFT JOIN users u ON v.user_id = u.id
  604. WHERE v.venta_uuid = ?
  605. """, (venta_uuid,))
  606. sale = cursor.fetchone()
  607. conn.close()
  608. if sale:
  609. return {
  610. "id": sale[0],
  611. "user_id": sale[1],
  612. "total": sale[2],
  613. "venta_uuid": sale[3],
  614. "fecha": sale[4],
  615. "user_name": sale[5],
  616. "user_email": sale[6]
  617. }
  618. return None
  619. def get_by_user(self, user_id: int) -> List[Dict]:
  620. """Get sales by user ID"""
  621. conn = self._get_connection()
  622. cursor = conn.cursor()
  623. cursor.execute("""
  624. SELECT v.id, v.user_id, v.total, v.venta_uuid, v.fecha, u.nombre, u.correo
  625. FROM ventas v
  626. LEFT JOIN users u ON v.user_id = u.id
  627. WHERE v.user_id = ?
  628. ORDER BY v.fecha DESC
  629. """, (user_id,))
  630. sales = cursor.fetchall()
  631. conn.close()
  632. return [
  633. {
  634. "id": sale[0],
  635. "user_id": sale[1],
  636. "total": sale[2],
  637. "venta_uuid": sale[3],
  638. "fecha": sale[4],
  639. "user_name": sale[5],
  640. "user_email": sale[6]
  641. } for sale in sales
  642. ]
  643. def get_sale_products(self, sale_id: int) -> List[Dict]:
  644. """Get products for a specific sale"""
  645. conn = self._get_connection()
  646. cursor = conn.cursor()
  647. cursor.execute("""
  648. SELECT p.id, p.name, p.type, p.description, p.price, p.image
  649. FROM venta_productos vp
  650. JOIN productos p ON vp.producto_id = p.id
  651. WHERE vp.venta_id = ?
  652. """, (sale_id,))
  653. products = cursor.fetchall()
  654. conn.close()
  655. return [
  656. {
  657. "id": product[0],
  658. "name": product[1],
  659. "type": product[2],
  660. "description": product[3],
  661. "price": product[4],
  662. "image": product[5]
  663. } for product in products
  664. ]
  665. #endregion
  666. #region U
  667. def update(self, sale_id: int, user_id=None, total=None) -> bool:
  668. """Update sale information (products cannot be updated directly)"""
  669. conn = self._get_connection()
  670. cursor = conn.cursor()
  671. updates = []
  672. params = []
  673. if user_id is not None:
  674. updates.append("user_id = ?")
  675. params.append(user_id)
  676. if total is not None:
  677. updates.append("total = ?")
  678. params.append(total)
  679. if not updates:
  680. conn.close()
  681. return False
  682. try:
  683. cursor.execute(f"UPDATE ventas SET {', '.join(updates)} WHERE id = ?", (*params, sale_id))
  684. conn.commit()
  685. success = cursor.rowcount > 0
  686. if success:
  687. logger.info(f"Sale with ID {sale_id} updated.")
  688. return success
  689. except sqlite3.Error as e:
  690. logger.error(f"Failed to update sale: {e}")
  691. return False
  692. finally:
  693. conn.close()
  694. #endregion
  695. #region D
  696. def delete(self, sale_id: int) -> bool:
  697. """Delete a sale and its product relationships"""
  698. conn = self._get_connection()
  699. cursor = conn.cursor()
  700. try:
  701. # Delete sale-product relationships first
  702. cursor.execute("DELETE FROM venta_productos WHERE venta_id = ?", (sale_id,))
  703. # Delete the sale
  704. cursor.execute("DELETE FROM ventas WHERE id = ?", (sale_id,))
  705. conn.commit()
  706. success = cursor.rowcount > 0
  707. if success:
  708. logger.info(f"Sale with ID {sale_id} deleted.")
  709. else:
  710. logger.error(f"Failed to delete sale with ID {sale_id}.")
  711. return success
  712. except sqlite3.Error as e:
  713. logger.error(f"Failed to delete sale: {e}")
  714. return False
  715. finally:
  716. conn.close()
  717. def remove_product_from_sale(self, sale_id: int, product_id: int) -> bool:
  718. """Remove a product from a sale"""
  719. conn = self._get_connection()
  720. cursor = conn.cursor()
  721. cursor.execute(
  722. "DELETE FROM venta_productos WHERE venta_id = ? AND producto_id = ?",
  723. (sale_id, product_id)
  724. )
  725. conn.commit()
  726. success = cursor.rowcount > 0
  727. conn.close()
  728. if success:
  729. logger.info(f"Product {product_id} removed from sale {sale_id}.")
  730. else:
  731. logger.error(f"Failed to remove product {product_id} from sale {sale_id}.")
  732. return success
  733. #endregion
  734. # Factory class to get service instances
  735. class DataServiceFactory:
  736. """Factory class to create data service instances"""
  737. @staticmethod
  738. def get_user_service() -> UserDataService:
  739. """Get user data service instance"""
  740. return UserDataService()
  741. @staticmethod
  742. def get_blacklist_service() -> BlacklistDataService:
  743. """Get blacklist data service instance"""
  744. return BlacklistDataService()
  745. @staticmethod
  746. def get_product_service() -> ProductDataService:
  747. """Get product data service instance"""
  748. return ProductDataService()
  749. @staticmethod
  750. def get_sales_service() -> SalesDataService:
  751. """Get sales data service instance"""
  752. return SalesDataService()
  753. # Helper functions for background data
  754. def load_bg_data() -> List[Dict[str, str]]:
  755. """Load background data for AI assistant"""
  756. try:
  757. with open(BG_DATA_PATH, 'r', encoding='utf-8') as f:
  758. return json.load(f)
  759. except FileNotFoundError:
  760. logger.error(f"Data file not found at {BG_DATA_PATH}. Serving with empty data.")
  761. return []
  762. except json.JSONDecodeError:
  763. logger.error(f"Could not decode JSON from {BG_DATA_PATH}. Serving with empty data.")
  764. return []