data_service.py 45 KB

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