data_service.py 42 KB

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