database.py 62 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516
  1. """
  2. 数据库操作模块
  3. 使用SQLite存储地磅单、公司名称、货物名称等数据
  4. 同时支持MySQL数据库存储(双数据库写入)
  5. """
  6. import sqlite3
  7. import os
  8. import sys
  9. from datetime import datetime
  10. from typing import List, Dict, Optional, Tuple
  11. import logging
  12. # 尝试导入MySQL相关模块
  13. try:
  14. import pymysql
  15. PYMySQL_AVAILABLE = True
  16. except ImportError:
  17. PYMySQL_AVAILABLE = False
  18. # 尝试导入配置模块
  19. try:
  20. import config
  21. MYSQL_ENABLED = getattr(config, 'ENABLE_MYSQL', False) and PYMySQL_AVAILABLE
  22. MYSQL_CONFIG = getattr(config, 'MYSQL_CONFIG', {})
  23. CONTINUE_ON_MYSQL_ERROR = getattr(config, 'CONTINUE_ON_MYSQL_ERROR', True)
  24. except ImportError:
  25. MYSQL_ENABLED = False
  26. MYSQL_CONFIG = {}
  27. CONTINUE_ON_MYSQL_ERROR = True
  28. # 配置日志 - 设置为INFO级别以便看到MySQL操作信息
  29. logging.basicConfig(
  30. level=logging.INFO,
  31. format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
  32. )
  33. logger = logging.getLogger(__name__)
  34. def get_base_path():
  35. """
  36. 获取程序运行的基础路径
  37. 在开发环境中返回脚本所在目录,在打包后返回exe所在目录
  38. """
  39. if getattr(sys, 'frozen', False):
  40. # 打包后的exe环境
  41. return os.path.dirname(sys.executable)
  42. else:
  43. # 开发环境
  44. return os.path.dirname(os.path.abspath(__file__))
  45. class Database:
  46. """数据库操作类,支持SQLite和MySQL双数据库存储"""
  47. def __init__(self, db_path: str = None):
  48. """
  49. 初始化数据库连接
  50. :param db_path: 数据库文件路径,如果为None则使用默认路径(exe同目录下的dibang.db)
  51. """
  52. if db_path is None:
  53. # 使用exe同目录下的dibang.db
  54. base_path = get_base_path()
  55. db_path = os.path.join(base_path, "dibang.db")
  56. self.db_path = db_path
  57. self.mysql_enabled = MYSQL_ENABLED
  58. self.mysql_connection = None
  59. self.init_database()
  60. def get_connection(self) -> sqlite3.Connection:
  61. """获取SQLite数据库连接"""
  62. conn = sqlite3.connect(self.db_path)
  63. conn.row_factory = sqlite3.Row # 使查询结果可以通过列名访问
  64. return conn
  65. def get_mysql_connection(self, force_reconnect: bool = False):
  66. """
  67. 获取MySQL数据库连接
  68. :param force_reconnect: 是否强制重新连接(用于确保读取最新数据)
  69. :return: MySQL连接对象
  70. """
  71. if not self.mysql_enabled:
  72. return None
  73. try:
  74. # 如果强制重新连接,先关闭旧连接
  75. if force_reconnect and self.mysql_connection is not None:
  76. try:
  77. self.mysql_connection.close()
  78. logger.info("强制重新建立MySQL连接以获取最新数据")
  79. except:
  80. pass
  81. self.mysql_connection = None
  82. # 检查连接是否存在且有效
  83. if self.mysql_connection is None:
  84. self.mysql_connection = pymysql.connect(
  85. host=MYSQL_CONFIG.get('host', 'localhost'),
  86. port=MYSQL_CONFIG.get('port', 3306),
  87. user=MYSQL_CONFIG.get('user', 'root'),
  88. password=MYSQL_CONFIG.get('password', ''),
  89. database=MYSQL_CONFIG.get('database', 'dibang'),
  90. charset=MYSQL_CONFIG.get('charset', 'utf8mb4'),
  91. cursorclass=pymysql.cursors.DictCursor,
  92. autocommit=False
  93. )
  94. else:
  95. # 检查连接是否有效
  96. try:
  97. self.mysql_connection.ping(reconnect=True)
  98. except Exception as ping_error:
  99. # 连接失效,重新连接
  100. logger.info(f"MySQL连接失效,重新连接: {str(ping_error)}")
  101. try:
  102. self.mysql_connection.close()
  103. except:
  104. pass
  105. self.mysql_connection = pymysql.connect(
  106. host=MYSQL_CONFIG.get('host', 'localhost'),
  107. port=MYSQL_CONFIG.get('port', 3306),
  108. user=MYSQL_CONFIG.get('user', 'root'),
  109. password=MYSQL_CONFIG.get('password', ''),
  110. database=MYSQL_CONFIG.get('database', 'dibang'),
  111. charset=MYSQL_CONFIG.get('charset', 'utf8mb4'),
  112. cursorclass=pymysql.cursors.DictCursor,
  113. autocommit=False
  114. )
  115. return self.mysql_connection
  116. except Exception as e:
  117. logger.warning(f"MySQL连接失败: {str(e)}")
  118. if not CONTINUE_ON_MYSQL_ERROR:
  119. raise
  120. return None
  121. def _execute_mysql(self, sql: str, params: tuple = None):
  122. """执行MySQL语句(内部方法)"""
  123. if not self.mysql_enabled:
  124. return
  125. conn = self.get_mysql_connection()
  126. if conn is None:
  127. return
  128. try:
  129. with conn.cursor() as cursor:
  130. cursor.execute(sql, params)
  131. conn.commit()
  132. except Exception as e:
  133. logger.warning(f"MySQL执行失败: {str(e)}, SQL: {sql}")
  134. if not CONTINUE_ON_MYSQL_ERROR:
  135. raise
  136. try:
  137. conn.rollback()
  138. except:
  139. pass
  140. def init_database(self):
  141. """初始化数据库表结构"""
  142. conn = self.get_connection()
  143. cursor = conn.cursor()
  144. # 创建地磅单表
  145. cursor.execute("""
  146. CREATE TABLE IF NOT EXISTS weighing_slips (
  147. id INTEGER PRIMARY KEY AUTOINCREMENT,
  148. vehicle_no TEXT NOT NULL,
  149. plan_no TEXT,
  150. cargo_name TEXT NOT NULL,
  151. business_type TEXT NOT NULL,
  152. batch_no TEXT,
  153. supplier TEXT NOT NULL,
  154. consignee TEXT NOT NULL,
  155. gross_weight REAL NOT NULL,
  156. gross_weight_time TEXT NOT NULL,
  157. tare_weight REAL NOT NULL,
  158. tare_weight_time TEXT NOT NULL,
  159. deduction REAL DEFAULT 0,
  160. net_weight REAL NOT NULL,
  161. planned_quantity REAL,
  162. executed_quantity REAL,
  163. weighing_point TEXT DEFAULT '山峡地磅',
  164. remarks TEXT,
  165. is_voided INTEGER DEFAULT 0,
  166. created_at TEXT NOT NULL
  167. )
  168. """)
  169. # 创建公司名称表
  170. cursor.execute("""
  171. CREATE TABLE IF NOT EXISTS companies (
  172. id INTEGER PRIMARY KEY AUTOINCREMENT,
  173. name TEXT NOT NULL UNIQUE,
  174. created_at TEXT NOT NULL
  175. )
  176. """)
  177. # 创建货物名称表
  178. cursor.execute("""
  179. CREATE TABLE IF NOT EXISTS cargos (
  180. id INTEGER PRIMARY KEY AUTOINCREMENT,
  181. name TEXT NOT NULL UNIQUE,
  182. created_at TEXT NOT NULL
  183. )
  184. """)
  185. # 创建采购计划表
  186. cursor.execute("""
  187. CREATE TABLE IF NOT EXISTS purchase_plans (
  188. id INTEGER PRIMARY KEY AUTOINCREMENT,
  189. plan_date TEXT NOT NULL,
  190. business_type TEXT NOT NULL,
  191. product TEXT NOT NULL,
  192. specification TEXT,
  193. planned_quantity REAL NOT NULL,
  194. vehicle_no TEXT NOT NULL,
  195. customer_name TEXT,
  196. remarks TEXT,
  197. created_at TEXT NOT NULL
  198. )
  199. """)
  200. # 如果表已存在但没有customer_name字段,则添加该字段
  201. try:
  202. cursor.execute("ALTER TABLE purchase_plans ADD COLUMN customer_name TEXT")
  203. except sqlite3.OperationalError:
  204. # 字段已存在,忽略错误
  205. pass
  206. # 如果表已存在但没有remarks字段,则添加该字段
  207. try:
  208. cursor.execute("ALTER TABLE purchase_plans ADD COLUMN remarks TEXT")
  209. except sqlite3.OperationalError:
  210. # 字段已存在,忽略错误
  211. pass
  212. conn.commit()
  213. conn.close()
  214. # 初始化MySQL数据库表结构
  215. if self.mysql_enabled:
  216. self._init_mysql_database()
  217. def _init_mysql_database(self):
  218. """初始化MySQL数据库表结构"""
  219. conn = self.get_mysql_connection()
  220. if conn is None:
  221. return
  222. try:
  223. with conn.cursor() as cursor:
  224. # 创建地磅单表
  225. cursor.execute("""
  226. CREATE TABLE IF NOT EXISTS weighing_slips (
  227. id INT PRIMARY KEY AUTO_INCREMENT,
  228. vehicle_no VARCHAR(255) NOT NULL,
  229. plan_no VARCHAR(255),
  230. cargo_name VARCHAR(255) NOT NULL,
  231. business_type VARCHAR(255) NOT NULL,
  232. batch_no VARCHAR(255),
  233. supplier VARCHAR(255) NOT NULL,
  234. consignee VARCHAR(255) NOT NULL,
  235. gross_weight DOUBLE NOT NULL,
  236. gross_weight_time VARCHAR(255) NOT NULL,
  237. tare_weight DOUBLE NOT NULL,
  238. tare_weight_time VARCHAR(255) NOT NULL,
  239. deduction DOUBLE DEFAULT 0,
  240. net_weight DOUBLE NOT NULL,
  241. planned_quantity DOUBLE,
  242. executed_quantity DOUBLE,
  243. weighing_point VARCHAR(255) DEFAULT '山峡地磅',
  244. remarks TEXT,
  245. is_voided INT DEFAULT 0,
  246. created_at VARCHAR(255) NOT NULL
  247. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
  248. """)
  249. # 创建公司名称表
  250. cursor.execute("""
  251. CREATE TABLE IF NOT EXISTS companies (
  252. id INT PRIMARY KEY AUTO_INCREMENT,
  253. name VARCHAR(255) NOT NULL UNIQUE,
  254. created_at VARCHAR(255) NOT NULL
  255. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
  256. """)
  257. # 创建货物名称表
  258. cursor.execute("""
  259. CREATE TABLE IF NOT EXISTS cargos (
  260. id INT PRIMARY KEY AUTO_INCREMENT,
  261. name VARCHAR(255) NOT NULL UNIQUE,
  262. created_at VARCHAR(255) NOT NULL
  263. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
  264. """)
  265. # 创建采购计划表
  266. cursor.execute("""
  267. CREATE TABLE IF NOT EXISTS purchase_plans (
  268. id INT PRIMARY KEY AUTO_INCREMENT,
  269. plan_date VARCHAR(255) NOT NULL,
  270. business_type VARCHAR(255) NOT NULL,
  271. product VARCHAR(255) NOT NULL,
  272. specification VARCHAR(255),
  273. planned_quantity DOUBLE NOT NULL,
  274. vehicle_no VARCHAR(255) NOT NULL,
  275. customer_name VARCHAR(255),
  276. remarks TEXT,
  277. created_at VARCHAR(255) NOT NULL
  278. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
  279. """)
  280. # 检查并添加customer_name字段(如果不存在)
  281. try:
  282. cursor.execute("ALTER TABLE purchase_plans ADD COLUMN customer_name VARCHAR(255)")
  283. except:
  284. pass # 字段已存在,忽略错误
  285. # 检查并添加remarks字段(如果不存在)
  286. try:
  287. cursor.execute("ALTER TABLE purchase_plans ADD COLUMN remarks TEXT")
  288. except:
  289. pass # 字段已存在,忽略错误
  290. conn.commit()
  291. except Exception as e:
  292. logger.warning(f"MySQL表初始化失败: {str(e)}")
  293. if not CONTINUE_ON_MYSQL_ERROR:
  294. raise
  295. try:
  296. conn.rollback()
  297. except:
  298. pass
  299. # ========== 地磅单相关操作 ==========
  300. def insert_weighing_slip(self, data: Dict) -> int:
  301. """
  302. 插入地磅单记录
  303. :param data: 地磅单数据字典
  304. :return: 插入记录的ID
  305. """
  306. # 计算净重
  307. net_weight = data['gross_weight'] - data['tare_weight'] - data.get('deduction', 0)
  308. created_at = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  309. record_id = None
  310. # 先操作MySQL,获取MySQL生成的ID
  311. if self.mysql_enabled:
  312. try:
  313. mysql_conn = self.get_mysql_connection()
  314. if mysql_conn:
  315. with mysql_conn.cursor() as mysql_cursor:
  316. mysql_cursor.execute("""
  317. INSERT INTO weighing_slips (
  318. vehicle_no, plan_no, cargo_name, business_type, batch_no,
  319. supplier, consignee, gross_weight, gross_weight_time,
  320. tare_weight, tare_weight_time, deduction, net_weight,
  321. planned_quantity, executed_quantity, weighing_point, remarks,
  322. is_voided, created_at
  323. ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
  324. """, (
  325. data['vehicle_no'],
  326. data.get('plan_no', ''),
  327. data['cargo_name'],
  328. data['business_type'],
  329. data.get('batch_no', ''),
  330. data['supplier'],
  331. data['consignee'],
  332. data['gross_weight'],
  333. data['gross_weight_time'],
  334. data['tare_weight'],
  335. data['tare_weight_time'],
  336. data.get('deduction', 0),
  337. net_weight,
  338. data.get('planned_quantity'),
  339. data.get('executed_quantity'),
  340. data.get('weighing_point', '山峡地磅'),
  341. data.get('remarks', ''),
  342. 0, # is_voided
  343. created_at
  344. ))
  345. record_id = mysql_cursor.lastrowid
  346. mysql_conn.commit()
  347. except Exception as e:
  348. logger.warning(f"MySQL插入地磅单失败: {str(e)}")
  349. if not CONTINUE_ON_MYSQL_ERROR:
  350. raise
  351. # 如果MySQL失败或未启用,使用SQLite生成ID
  352. if record_id is None:
  353. conn = self.get_connection()
  354. cursor = conn.cursor()
  355. cursor.execute("""
  356. INSERT INTO weighing_slips (
  357. vehicle_no, plan_no, cargo_name, business_type, batch_no,
  358. supplier, consignee, gross_weight, gross_weight_time,
  359. tare_weight, tare_weight_time, deduction, net_weight,
  360. planned_quantity, executed_quantity, weighing_point, remarks,
  361. is_voided, created_at
  362. ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
  363. """, (
  364. data['vehicle_no'],
  365. data.get('plan_no', ''),
  366. data['cargo_name'],
  367. data['business_type'],
  368. data.get('batch_no', ''),
  369. data['supplier'],
  370. data['consignee'],
  371. data['gross_weight'],
  372. data['gross_weight_time'],
  373. data['tare_weight'],
  374. data['tare_weight_time'],
  375. data.get('deduction', 0),
  376. net_weight,
  377. data.get('planned_quantity'),
  378. data.get('executed_quantity'),
  379. data.get('weighing_point', '山峡地磅'),
  380. data.get('remarks', ''),
  381. 0, # is_voided
  382. created_at
  383. ))
  384. record_id = cursor.lastrowid
  385. conn.commit()
  386. conn.close()
  387. else:
  388. # 使用MySQL的ID同步到SQLite
  389. conn = self.get_connection()
  390. cursor = conn.cursor()
  391. cursor.execute("""
  392. INSERT INTO weighing_slips (
  393. id, vehicle_no, plan_no, cargo_name, business_type, batch_no,
  394. supplier, consignee, gross_weight, gross_weight_time,
  395. tare_weight, tare_weight_time, deduction, net_weight,
  396. planned_quantity, executed_quantity, weighing_point, remarks,
  397. is_voided, created_at
  398. ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
  399. """, (
  400. record_id, # 使用MySQL返回的ID
  401. data['vehicle_no'],
  402. data.get('plan_no', ''),
  403. data['cargo_name'],
  404. data['business_type'],
  405. data.get('batch_no', ''),
  406. data['supplier'],
  407. data['consignee'],
  408. data['gross_weight'],
  409. data['gross_weight_time'],
  410. data['tare_weight'],
  411. data['tare_weight_time'],
  412. data.get('deduction', 0),
  413. net_weight,
  414. data.get('planned_quantity'),
  415. data.get('executed_quantity'),
  416. data.get('weighing_point', '山峡地磅'),
  417. data.get('remarks', ''),
  418. 0, # is_voided
  419. created_at
  420. ))
  421. conn.commit()
  422. conn.close()
  423. return record_id
  424. def get_all_weighing_slips(self, include_voided: bool = False) -> List[Dict]:
  425. """
  426. 获取所有地磅单记录
  427. :param include_voided: 是否包含作废的记录
  428. :return: 地磅单记录列表
  429. """
  430. # 先尝试从MySQL查询(强制重新连接以确保获取最新数据)
  431. if self.mysql_enabled:
  432. try:
  433. # 强制重新连接以确保读取最新数据
  434. mysql_conn = self.get_mysql_connection(force_reconnect=True)
  435. if mysql_conn:
  436. try:
  437. # 确保事务已提交
  438. mysql_conn.commit()
  439. # 设置隔离级别为READ COMMITTED
  440. with mysql_conn.cursor() as cursor:
  441. cursor.execute("SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED")
  442. with mysql_conn.cursor() as mysql_cursor:
  443. if include_voided:
  444. mysql_cursor.execute("""
  445. SELECT * FROM weighing_slips
  446. ORDER BY id DESC
  447. """)
  448. else:
  449. mysql_cursor.execute("""
  450. SELECT * FROM weighing_slips
  451. WHERE is_voided = 0
  452. ORDER BY id DESC
  453. """)
  454. rows = mysql_cursor.fetchall()
  455. result = [dict(row) for row in rows]
  456. logger.info(f"✅ 从MySQL获取地磅单列表,共{len(result)}条记录")
  457. return result
  458. except Exception as query_error:
  459. logger.error(f"MySQL查询地磅单执行失败: {str(query_error)}", exc_info=True)
  460. raise
  461. else:
  462. logger.warning("⚠️ MySQL连接不可用,将从SQLite查询地磅单列表")
  463. except Exception as e:
  464. logger.warning(f"⚠️ MySQL查询地磅单失败,将从SQLite查询: {str(e)}", exc_info=True)
  465. # MySQL失败或未启用,从SQLite查询
  466. logger.info("从SQLite获取地磅单列表")
  467. conn = self.get_connection()
  468. cursor = conn.cursor()
  469. if include_voided:
  470. cursor.execute("""
  471. SELECT * FROM weighing_slips
  472. ORDER BY id DESC
  473. """)
  474. else:
  475. cursor.execute("""
  476. SELECT * FROM weighing_slips
  477. WHERE is_voided = 0
  478. ORDER BY id DESC
  479. """)
  480. rows = cursor.fetchall()
  481. conn.close()
  482. result = [dict(row) for row in rows]
  483. logger.info(f"从SQLite获取地磅单列表,共{len(result)}条记录")
  484. return result
  485. def get_weighing_slip_by_id(self, slip_id: int) -> Optional[Dict]:
  486. """
  487. 根据ID获取地磅单记录
  488. :param slip_id: 地磅单ID
  489. :return: 地磅单记录字典,如果不存在返回None
  490. """
  491. # 先尝试从MySQL查询
  492. if self.mysql_enabled:
  493. try:
  494. mysql_conn = self.get_mysql_connection()
  495. if mysql_conn:
  496. with mysql_conn.cursor() as mysql_cursor:
  497. mysql_cursor.execute("SELECT * FROM weighing_slips WHERE id = %s", (slip_id,))
  498. row = mysql_cursor.fetchone()
  499. if row:
  500. return dict(row)
  501. except Exception as e:
  502. logger.warning(f"MySQL查询地磅单失败,将从SQLite查询: {str(e)}")
  503. # MySQL失败或未启用,从SQLite查询
  504. conn = self.get_connection()
  505. cursor = conn.cursor()
  506. cursor.execute("SELECT * FROM weighing_slips WHERE id = ?", (slip_id,))
  507. row = cursor.fetchone()
  508. conn.close()
  509. return dict(row) if row else None
  510. def void_weighing_slip(self, slip_id: int) -> bool:
  511. """
  512. 作废地磅单
  513. :param slip_id: 地磅单ID
  514. :return: 是否成功
  515. """
  516. success = False
  517. # 先操作MySQL
  518. if self.mysql_enabled:
  519. try:
  520. mysql_conn = self.get_mysql_connection()
  521. if mysql_conn:
  522. with mysql_conn.cursor() as mysql_cursor:
  523. mysql_cursor.execute("""
  524. UPDATE weighing_slips
  525. SET is_voided = 1
  526. WHERE id = %s
  527. """, (slip_id,))
  528. affected_rows = mysql_cursor.rowcount
  529. mysql_conn.commit()
  530. if affected_rows > 0:
  531. success = True
  532. logger.info(f"MySQL作废地磅单成功: ID {slip_id}")
  533. else:
  534. logger.warning(f"MySQL作废地磅单: ID {slip_id} 未找到记录")
  535. else:
  536. logger.warning(f"MySQL连接失败,无法作废地磅单 ID {slip_id}")
  537. except Exception as e:
  538. logger.error(f"MySQL作废地磅单失败: ID {slip_id}, 错误: {str(e)}", exc_info=True)
  539. if not CONTINUE_ON_MYSQL_ERROR:
  540. raise
  541. # 同步到SQLite
  542. if success or not self.mysql_enabled:
  543. try:
  544. conn = self.get_connection()
  545. cursor = conn.cursor()
  546. cursor.execute("""
  547. UPDATE weighing_slips
  548. SET is_voided = 1
  549. WHERE id = ?
  550. """, (slip_id,))
  551. sqlite_success = cursor.rowcount > 0
  552. conn.commit()
  553. conn.close()
  554. if self.mysql_enabled and not sqlite_success:
  555. logger.warning(f"SQLite作废地磅单: ID {slip_id} 未找到记录,可能ID不匹配")
  556. elif not self.mysql_enabled:
  557. success = sqlite_success
  558. except Exception as e:
  559. logger.error(f"SQLite作废地磅单失败: ID {slip_id}, 错误: {str(e)}")
  560. return success
  561. # ========== 公司名称相关操作 ==========
  562. def insert_company(self, name: str) -> int:
  563. """
  564. 插入公司名称
  565. :param name: 公司名称
  566. :return: 插入记录的ID
  567. """
  568. created_at = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  569. record_id = None
  570. # 先操作MySQL,获取MySQL生成的ID
  571. if self.mysql_enabled:
  572. try:
  573. mysql_conn = self.get_mysql_connection()
  574. if mysql_conn:
  575. with mysql_conn.cursor() as mysql_cursor:
  576. mysql_cursor.execute("""
  577. INSERT INTO companies (name, created_at)
  578. VALUES (%s, %s)
  579. """, (name, created_at))
  580. record_id = mysql_cursor.lastrowid
  581. mysql_conn.commit()
  582. except Exception as e:
  583. logger.warning(f"MySQL插入公司失败: {str(e)}")
  584. if not CONTINUE_ON_MYSQL_ERROR:
  585. raise
  586. # 如果MySQL失败或未启用,使用SQLite生成ID
  587. if record_id is None:
  588. conn = self.get_connection()
  589. cursor = conn.cursor()
  590. cursor.execute("""
  591. INSERT INTO companies (name, created_at)
  592. VALUES (?, ?)
  593. """, (name, created_at))
  594. record_id = cursor.lastrowid
  595. conn.commit()
  596. conn.close()
  597. else:
  598. # 使用MySQL的ID同步到SQLite
  599. conn = self.get_connection()
  600. cursor = conn.cursor()
  601. cursor.execute("""
  602. INSERT INTO companies (id, name, created_at)
  603. VALUES (?, ?, ?)
  604. """, (record_id, name, created_at))
  605. conn.commit()
  606. conn.close()
  607. return record_id
  608. def get_all_companies(self) -> List[Dict]:
  609. """
  610. 获取所有公司名称
  611. :return: 公司名称列表
  612. """
  613. # 先尝试从MySQL查询(强制重新连接以确保获取最新数据)
  614. if self.mysql_enabled:
  615. try:
  616. # 强制重新连接以确保读取最新数据
  617. mysql_conn = self.get_mysql_connection(force_reconnect=True)
  618. if mysql_conn:
  619. try:
  620. # 确保事务已提交
  621. mysql_conn.commit()
  622. # 设置隔离级别为READ COMMITTED
  623. with mysql_conn.cursor() as cursor:
  624. cursor.execute("SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED")
  625. with mysql_conn.cursor() as mysql_cursor:
  626. mysql_cursor.execute("SELECT * FROM companies ORDER BY name")
  627. rows = mysql_cursor.fetchall()
  628. result = [dict(row) for row in rows]
  629. logger.info(f"✅ 从MySQL获取公司列表,共{len(result)}条记录")
  630. return result
  631. except Exception as query_error:
  632. logger.error(f"MySQL查询公司执行失败: {str(query_error)}", exc_info=True)
  633. raise
  634. else:
  635. logger.warning("⚠️ MySQL连接不可用,将从SQLite查询公司列表")
  636. except Exception as e:
  637. logger.warning(f"⚠️ MySQL查询公司失败,将从SQLite查询: {str(e)}", exc_info=True)
  638. # MySQL失败或未启用,从SQLite查询
  639. logger.info("从SQLite获取公司列表")
  640. conn = self.get_connection()
  641. cursor = conn.cursor()
  642. cursor.execute("SELECT * FROM companies ORDER BY name")
  643. rows = cursor.fetchall()
  644. conn.close()
  645. result = [dict(row) for row in rows]
  646. logger.info(f"从SQLite获取公司列表,共{len(result)}条记录")
  647. return result
  648. def update_company(self, company_id: int, name: str) -> bool:
  649. """
  650. 更新公司名称
  651. :param company_id: 公司ID
  652. :param name: 新名称
  653. :return: 是否成功
  654. """
  655. success = False
  656. # 先操作MySQL
  657. if self.mysql_enabled:
  658. try:
  659. mysql_conn = self.get_mysql_connection()
  660. if mysql_conn:
  661. with mysql_conn.cursor() as mysql_cursor:
  662. mysql_cursor.execute("""
  663. UPDATE companies
  664. SET name = %s
  665. WHERE id = %s
  666. """, (name, company_id))
  667. affected_rows = mysql_cursor.rowcount
  668. mysql_conn.commit()
  669. if affected_rows > 0:
  670. success = True
  671. logger.info(f"MySQL更新公司成功: ID {company_id}, 新名称: {name}")
  672. else:
  673. logger.warning(f"MySQL更新公司: ID {company_id} 未找到记录")
  674. else:
  675. logger.warning(f"MySQL连接失败,无法更新公司 ID {company_id}")
  676. except Exception as e:
  677. logger.error(f"MySQL更新公司失败: ID {company_id}, 错误: {str(e)}", exc_info=True)
  678. if not CONTINUE_ON_MYSQL_ERROR:
  679. raise
  680. # 同步到SQLite
  681. if success or not self.mysql_enabled:
  682. try:
  683. conn = self.get_connection()
  684. cursor = conn.cursor()
  685. cursor.execute("""
  686. UPDATE companies
  687. SET name = ?
  688. WHERE id = ?
  689. """, (name, company_id))
  690. sqlite_success = cursor.rowcount > 0
  691. conn.commit()
  692. conn.close()
  693. if self.mysql_enabled and not sqlite_success:
  694. logger.warning(f"SQLite更新公司: ID {company_id} 未找到记录,可能ID不匹配")
  695. elif not self.mysql_enabled:
  696. success = sqlite_success
  697. except Exception as e:
  698. logger.error(f"SQLite更新公司失败: ID {company_id}, 错误: {str(e)}")
  699. return success
  700. def delete_company(self, company_id: int) -> bool:
  701. """
  702. 删除公司名称
  703. :param company_id: 公司ID
  704. :return: 是否成功
  705. """
  706. success = False
  707. # 先操作MySQL
  708. if self.mysql_enabled:
  709. try:
  710. mysql_conn = self.get_mysql_connection()
  711. if mysql_conn:
  712. with mysql_conn.cursor() as mysql_cursor:
  713. mysql_cursor.execute("DELETE FROM companies WHERE id = %s", (company_id,))
  714. affected_rows = mysql_cursor.rowcount
  715. mysql_conn.commit()
  716. if affected_rows > 0:
  717. success = True
  718. logger.info(f"MySQL删除公司成功: ID {company_id}")
  719. else:
  720. logger.warning(f"MySQL删除公司: ID {company_id} 未找到记录")
  721. else:
  722. logger.warning(f"MySQL连接失败,无法删除公司 ID {company_id}")
  723. except Exception as e:
  724. logger.error(f"MySQL删除公司失败: ID {company_id}, 错误: {str(e)}", exc_info=True)
  725. if not CONTINUE_ON_MYSQL_ERROR:
  726. raise
  727. # 同步到SQLite
  728. if success or not self.mysql_enabled:
  729. try:
  730. conn = self.get_connection()
  731. cursor = conn.cursor()
  732. cursor.execute("DELETE FROM companies WHERE id = ?", (company_id,))
  733. sqlite_success = cursor.rowcount > 0
  734. conn.commit()
  735. conn.close()
  736. if self.mysql_enabled and not sqlite_success:
  737. logger.warning(f"SQLite删除公司: ID {company_id} 未找到记录,可能ID不匹配")
  738. elif not self.mysql_enabled:
  739. success = sqlite_success
  740. except Exception as e:
  741. logger.error(f"SQLite删除公司失败: ID {company_id}, 错误: {str(e)}")
  742. return success
  743. def is_company_used(self, company_id: int) -> bool:
  744. """
  745. 检查公司是否被地磅单引用
  746. :param company_id: 公司ID
  747. :return: 是否被引用
  748. """
  749. # 先尝试从MySQL查询
  750. if self.mysql_enabled:
  751. try:
  752. mysql_conn = self.get_mysql_connection()
  753. if mysql_conn:
  754. with mysql_conn.cursor() as mysql_cursor:
  755. # 获取公司名称
  756. mysql_cursor.execute("SELECT name FROM companies WHERE id = %s", (company_id,))
  757. row = mysql_cursor.fetchone()
  758. if not row:
  759. return False
  760. company_name = row['name']
  761. # 检查是否在供货单位或收货单位中被使用
  762. mysql_cursor.execute("""
  763. SELECT COUNT(*) as count FROM weighing_slips
  764. WHERE supplier = %s OR consignee = %s
  765. """, (company_name, company_name))
  766. result = mysql_cursor.fetchone()
  767. return result['count'] > 0 if result else False
  768. except Exception as e:
  769. logger.warning(f"MySQL检查公司使用情况失败,将从SQLite查询: {str(e)}")
  770. # MySQL失败或未启用,从SQLite查询
  771. conn = self.get_connection()
  772. cursor = conn.cursor()
  773. # 获取公司名称
  774. cursor.execute("SELECT name FROM companies WHERE id = ?", (company_id,))
  775. row = cursor.fetchone()
  776. if not row:
  777. conn.close()
  778. return False
  779. company_name = row['name']
  780. # 检查是否在供货单位或收货单位中被使用
  781. cursor.execute("""
  782. SELECT COUNT(*) as count FROM weighing_slips
  783. WHERE supplier = ? OR consignee = ?
  784. """, (company_name, company_name))
  785. result = cursor.fetchone()
  786. conn.close()
  787. return result['count'] > 0
  788. def company_exists(self, name: str, exclude_id: Optional[int] = None) -> bool:
  789. """
  790. 检查公司名称是否存在
  791. :param name: 公司名称
  792. :param exclude_id: 排除的ID(用于修改时检查)
  793. :return: 是否存在
  794. """
  795. # 先尝试从MySQL查询
  796. if self.mysql_enabled:
  797. try:
  798. mysql_conn = self.get_mysql_connection()
  799. if mysql_conn:
  800. with mysql_conn.cursor() as mysql_cursor:
  801. if exclude_id:
  802. mysql_cursor.execute("""
  803. SELECT COUNT(*) as count FROM companies
  804. WHERE name = %s AND id != %s
  805. """, (name, exclude_id))
  806. else:
  807. mysql_cursor.execute("""
  808. SELECT COUNT(*) as count FROM companies
  809. WHERE name = %s
  810. """, (name,))
  811. result = mysql_cursor.fetchone()
  812. return result['count'] > 0 if result else False
  813. except Exception as e:
  814. logger.warning(f"MySQL检查公司是否存在失败,将从SQLite查询: {str(e)}")
  815. # MySQL失败或未启用,从SQLite查询
  816. conn = self.get_connection()
  817. cursor = conn.cursor()
  818. if exclude_id:
  819. cursor.execute("""
  820. SELECT COUNT(*) as count FROM companies
  821. WHERE name = ? AND id != ?
  822. """, (name, exclude_id))
  823. else:
  824. cursor.execute("""
  825. SELECT COUNT(*) as count FROM companies
  826. WHERE name = ?
  827. """, (name,))
  828. result = cursor.fetchone()
  829. conn.close()
  830. return result['count'] > 0
  831. # ========== 货物名称相关操作 ==========
  832. def insert_cargo(self, name: str) -> int:
  833. """
  834. 插入货物名称
  835. :param name: 货物名称
  836. :return: 插入记录的ID
  837. """
  838. created_at = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  839. record_id = None
  840. # 先操作MySQL,获取MySQL生成的ID
  841. if self.mysql_enabled:
  842. try:
  843. mysql_conn = self.get_mysql_connection()
  844. if mysql_conn:
  845. with mysql_conn.cursor() as mysql_cursor:
  846. mysql_cursor.execute("""
  847. INSERT INTO cargos (name, created_at)
  848. VALUES (%s, %s)
  849. """, (name, created_at))
  850. record_id = mysql_cursor.lastrowid
  851. mysql_conn.commit()
  852. except Exception as e:
  853. logger.warning(f"MySQL插入货物失败: {str(e)}")
  854. if not CONTINUE_ON_MYSQL_ERROR:
  855. raise
  856. # 如果MySQL失败或未启用,使用SQLite生成ID
  857. if record_id is None:
  858. conn = self.get_connection()
  859. cursor = conn.cursor()
  860. cursor.execute("""
  861. INSERT INTO cargos (name, created_at)
  862. VALUES (?, ?)
  863. """, (name, created_at))
  864. record_id = cursor.lastrowid
  865. conn.commit()
  866. conn.close()
  867. else:
  868. # 使用MySQL的ID同步到SQLite
  869. conn = self.get_connection()
  870. cursor = conn.cursor()
  871. cursor.execute("""
  872. INSERT INTO cargos (id, name, created_at)
  873. VALUES (?, ?, ?)
  874. """, (record_id, name, created_at))
  875. conn.commit()
  876. conn.close()
  877. return record_id
  878. def get_all_cargos(self) -> List[Dict]:
  879. """
  880. 获取所有货物名称
  881. :return: 货物名称列表
  882. """
  883. # 先尝试从MySQL查询(强制重新连接以确保获取最新数据)
  884. if self.mysql_enabled:
  885. try:
  886. # 强制重新连接以确保读取最新数据
  887. mysql_conn = self.get_mysql_connection(force_reconnect=True)
  888. if mysql_conn:
  889. try:
  890. # 确保事务已提交,并设置隔离级别为READ COMMITTED以读取最新数据
  891. mysql_conn.commit() # 提交任何未提交的事务
  892. # 设置隔离级别为READ COMMITTED,确保读取已提交的最新数据
  893. with mysql_conn.cursor() as cursor:
  894. cursor.execute("SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED")
  895. with mysql_conn.cursor() as mysql_cursor:
  896. mysql_cursor.execute("SELECT * FROM cargos ORDER BY name")
  897. rows = mysql_cursor.fetchall()
  898. result = [dict(row) for row in rows]
  899. logger.info(f"✅ 从MySQL获取货物列表,共{len(result)}条记录")
  900. # 打印前几条记录用于调试
  901. if result:
  902. logger.info(f"示例:id={result[0].get('id')}, name={result[0].get('name')}")
  903. return result
  904. except Exception as query_error:
  905. logger.error(f"MySQL查询货物执行失败: {str(query_error)}", exc_info=True)
  906. raise
  907. else:
  908. logger.warning("⚠️ MySQL连接不可用(返回None),将从SQLite查询货物列表")
  909. except Exception as e:
  910. logger.warning(f"⚠️ MySQL查询货物失败,将从SQLite查询: {str(e)}", exc_info=True)
  911. # MySQL失败或未启用,从SQLite查询
  912. logger.info("ℹ️ 从SQLite获取货物列表")
  913. conn = self.get_connection()
  914. cursor = conn.cursor()
  915. cursor.execute("SELECT * FROM cargos ORDER BY name")
  916. rows = cursor.fetchall()
  917. conn.close()
  918. result = [dict(row) for row in rows]
  919. logger.info(f"从SQLite获取货物列表,共{len(result)}条记录")
  920. if result:
  921. logger.info(f"示例:id={result[0].get('id')}, name={result[0].get('name')}")
  922. return result
  923. def update_cargo(self, cargo_id: int, name: str) -> bool:
  924. """
  925. 更新货物名称
  926. :param cargo_id: 货物ID
  927. :param name: 新名称
  928. :return: 是否成功
  929. """
  930. success = False
  931. # 先操作MySQL
  932. if self.mysql_enabled:
  933. try:
  934. mysql_conn = self.get_mysql_connection()
  935. if mysql_conn:
  936. with mysql_conn.cursor() as mysql_cursor:
  937. mysql_cursor.execute("""
  938. UPDATE cargos
  939. SET name = %s
  940. WHERE id = %s
  941. """, (name, cargo_id))
  942. affected_rows = mysql_cursor.rowcount
  943. mysql_conn.commit()
  944. if affected_rows > 0:
  945. success = True
  946. logger.info(f"MySQL更新货物成功: ID {cargo_id}, 新名称: {name}")
  947. else:
  948. logger.warning(f"MySQL更新货物: ID {cargo_id} 未找到记录")
  949. else:
  950. logger.warning(f"MySQL连接失败,无法更新货物 ID {cargo_id}")
  951. except Exception as e:
  952. logger.error(f"MySQL更新货物失败: ID {cargo_id}, 错误: {str(e)}", exc_info=True)
  953. if not CONTINUE_ON_MYSQL_ERROR:
  954. raise
  955. # 同步到SQLite
  956. if success or not self.mysql_enabled:
  957. try:
  958. conn = self.get_connection()
  959. cursor = conn.cursor()
  960. cursor.execute("""
  961. UPDATE cargos
  962. SET name = ?
  963. WHERE id = ?
  964. """, (name, cargo_id))
  965. sqlite_success = cursor.rowcount > 0
  966. conn.commit()
  967. conn.close()
  968. if self.mysql_enabled and not sqlite_success:
  969. logger.warning(f"SQLite更新货物: ID {cargo_id} 未找到记录,可能ID不匹配")
  970. elif not self.mysql_enabled:
  971. success = sqlite_success
  972. except Exception as e:
  973. logger.error(f"SQLite更新货物失败: ID {cargo_id}, 错误: {str(e)}")
  974. return success
  975. def delete_cargo(self, cargo_id: int) -> bool:
  976. """
  977. 删除货物名称
  978. :param cargo_id: 货物ID
  979. :return: 是否成功
  980. """
  981. success = False
  982. # 先操作MySQL
  983. if self.mysql_enabled:
  984. try:
  985. mysql_conn = self.get_mysql_connection()
  986. if mysql_conn:
  987. with mysql_conn.cursor() as mysql_cursor:
  988. mysql_cursor.execute("DELETE FROM cargos WHERE id = %s", (cargo_id,))
  989. affected_rows = mysql_cursor.rowcount
  990. mysql_conn.commit()
  991. if affected_rows > 0:
  992. success = True
  993. logger.info(f"MySQL删除货物成功: ID {cargo_id}")
  994. else:
  995. logger.warning(f"MySQL删除货物: ID {cargo_id} 未找到记录")
  996. else:
  997. logger.warning(f"MySQL连接失败,无法删除货物 ID {cargo_id}")
  998. except Exception as e:
  999. logger.error(f"MySQL删除货物失败: ID {cargo_id}, 错误: {str(e)}", exc_info=True)
  1000. if not CONTINUE_ON_MYSQL_ERROR:
  1001. raise
  1002. # 同步到SQLite
  1003. if success or not self.mysql_enabled:
  1004. try:
  1005. conn = self.get_connection()
  1006. cursor = conn.cursor()
  1007. cursor.execute("DELETE FROM cargos WHERE id = ?", (cargo_id,))
  1008. sqlite_success = cursor.rowcount > 0
  1009. conn.commit()
  1010. conn.close()
  1011. if self.mysql_enabled and not sqlite_success:
  1012. logger.warning(f"SQLite删除货物: ID {cargo_id} 未找到记录,可能ID不匹配")
  1013. elif not self.mysql_enabled:
  1014. success = sqlite_success
  1015. except Exception as e:
  1016. logger.error(f"SQLite删除货物失败: ID {cargo_id}, 错误: {str(e)}")
  1017. return success
  1018. def is_cargo_used(self, cargo_id: int) -> bool:
  1019. """
  1020. 检查货物是否被地磅单引用
  1021. :param cargo_id: 货物ID
  1022. :return: 是否被引用
  1023. """
  1024. # 先尝试从MySQL查询
  1025. if self.mysql_enabled:
  1026. try:
  1027. mysql_conn = self.get_mysql_connection()
  1028. if mysql_conn:
  1029. with mysql_conn.cursor() as mysql_cursor:
  1030. # 获取货物名称
  1031. mysql_cursor.execute("SELECT name FROM cargos WHERE id = %s", (cargo_id,))
  1032. row = mysql_cursor.fetchone()
  1033. if not row:
  1034. return False
  1035. cargo_name = row['name']
  1036. # 检查是否在地磅单中被使用
  1037. mysql_cursor.execute("""
  1038. SELECT COUNT(*) as count FROM weighing_slips
  1039. WHERE cargo_name = %s
  1040. """, (cargo_name,))
  1041. result = mysql_cursor.fetchone()
  1042. return result['count'] > 0 if result else False
  1043. except Exception as e:
  1044. logger.warning(f"MySQL检查货物使用情况失败,将从SQLite查询: {str(e)}")
  1045. # MySQL失败或未启用,从SQLite查询
  1046. conn = self.get_connection()
  1047. cursor = conn.cursor()
  1048. # 获取货物名称
  1049. cursor.execute("SELECT name FROM cargos WHERE id = ?", (cargo_id,))
  1050. row = cursor.fetchone()
  1051. if not row:
  1052. conn.close()
  1053. return False
  1054. cargo_name = row['name']
  1055. # 检查是否在地磅单中被使用
  1056. cursor.execute("""
  1057. SELECT COUNT(*) as count FROM weighing_slips
  1058. WHERE cargo_name = ?
  1059. """, (cargo_name,))
  1060. result = cursor.fetchone()
  1061. conn.close()
  1062. return result['count'] > 0
  1063. def cargo_exists(self, name: str, exclude_id: Optional[int] = None) -> bool:
  1064. """
  1065. 检查货物名称是否存在
  1066. :param name: 货物名称
  1067. :param exclude_id: 排除的ID(用于修改时检查)
  1068. :return: 是否存在
  1069. """
  1070. # 先尝试从MySQL查询
  1071. if self.mysql_enabled:
  1072. try:
  1073. mysql_conn = self.get_mysql_connection()
  1074. if mysql_conn:
  1075. with mysql_conn.cursor() as mysql_cursor:
  1076. if exclude_id:
  1077. mysql_cursor.execute("""
  1078. SELECT COUNT(*) as count FROM cargos
  1079. WHERE name = %s AND id != %s
  1080. """, (name, exclude_id))
  1081. else:
  1082. mysql_cursor.execute("""
  1083. SELECT COUNT(*) as count FROM cargos
  1084. WHERE name = %s
  1085. """, (name,))
  1086. result = mysql_cursor.fetchone()
  1087. return result['count'] > 0 if result else False
  1088. except Exception as e:
  1089. logger.warning(f"MySQL检查货物是否存在失败,将从SQLite查询: {str(e)}")
  1090. # MySQL失败或未启用,从SQLite查询
  1091. conn = self.get_connection()
  1092. cursor = conn.cursor()
  1093. if exclude_id:
  1094. cursor.execute("""
  1095. SELECT COUNT(*) as count FROM cargos
  1096. WHERE name = ? AND id != ?
  1097. """, (name, exclude_id))
  1098. else:
  1099. cursor.execute("""
  1100. SELECT COUNT(*) as count FROM cargos
  1101. WHERE name = ?
  1102. """, (name,))
  1103. result = cursor.fetchone()
  1104. conn.close()
  1105. return result['count'] > 0
  1106. # ========== 采购计划相关操作 ==========
  1107. def insert_purchase_plan(self, data: Dict) -> int:
  1108. """
  1109. 插入采购计划记录
  1110. :param data: 采购计划数据字典
  1111. :return: 插入记录的ID
  1112. """
  1113. created_at = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  1114. record_id = None
  1115. # 先操作MySQL,获取MySQL生成的ID
  1116. if self.mysql_enabled:
  1117. try:
  1118. mysql_conn = self.get_mysql_connection()
  1119. if mysql_conn:
  1120. with mysql_conn.cursor() as mysql_cursor:
  1121. mysql_cursor.execute("""
  1122. INSERT INTO purchase_plans (
  1123. plan_date, business_type, product, specification,
  1124. planned_quantity, vehicle_no, customer_name, remarks, created_at
  1125. ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
  1126. """, (
  1127. data['plan_date'],
  1128. data['business_type'],
  1129. data['product'],
  1130. data.get('specification', ''),
  1131. data['planned_quantity'],
  1132. data['vehicle_no'],
  1133. data.get('customer_name', ''),
  1134. data.get('remarks', ''),
  1135. created_at
  1136. ))
  1137. record_id = mysql_cursor.lastrowid
  1138. mysql_conn.commit()
  1139. except Exception as e:
  1140. logger.warning(f"MySQL插入采购计划失败: {str(e)}")
  1141. if not CONTINUE_ON_MYSQL_ERROR:
  1142. raise
  1143. # 如果MySQL失败或未启用,使用SQLite生成ID
  1144. if record_id is None:
  1145. conn = self.get_connection()
  1146. cursor = conn.cursor()
  1147. cursor.execute("""
  1148. INSERT INTO purchase_plans (
  1149. plan_date, business_type, product, specification,
  1150. planned_quantity, vehicle_no, customer_name, remarks, created_at
  1151. ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
  1152. """, (
  1153. data['plan_date'],
  1154. data['business_type'],
  1155. data['product'],
  1156. data.get('specification', ''),
  1157. data['planned_quantity'],
  1158. data['vehicle_no'],
  1159. data.get('customer_name', ''),
  1160. data.get('remarks', ''),
  1161. created_at
  1162. ))
  1163. record_id = cursor.lastrowid
  1164. conn.commit()
  1165. conn.close()
  1166. else:
  1167. # 使用MySQL的ID同步到SQLite
  1168. conn = self.get_connection()
  1169. cursor = conn.cursor()
  1170. cursor.execute("""
  1171. INSERT INTO purchase_plans (
  1172. id, plan_date, business_type, product, specification,
  1173. planned_quantity, vehicle_no, customer_name, remarks, created_at
  1174. ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
  1175. """, (
  1176. record_id, # 使用MySQL返回的ID
  1177. data['plan_date'],
  1178. data['business_type'],
  1179. data['product'],
  1180. data.get('specification', ''),
  1181. data['planned_quantity'],
  1182. data['vehicle_no'],
  1183. data.get('customer_name', ''),
  1184. data.get('remarks', ''),
  1185. created_at
  1186. ))
  1187. conn.commit()
  1188. conn.close()
  1189. return record_id
  1190. def get_all_purchase_plans(self) -> List[Dict]:
  1191. """
  1192. 获取所有采购计划记录
  1193. :return: 采购计划记录列表
  1194. """
  1195. # 先尝试从MySQL查询(强制重新连接以确保获取最新数据)
  1196. if self.mysql_enabled:
  1197. try:
  1198. # 强制重新连接以确保读取最新数据
  1199. mysql_conn = self.get_mysql_connection(force_reconnect=True)
  1200. if mysql_conn:
  1201. try:
  1202. # 确保事务已提交
  1203. mysql_conn.commit()
  1204. # 设置隔离级别为READ COMMITTED
  1205. with mysql_conn.cursor() as cursor:
  1206. cursor.execute("SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED")
  1207. with mysql_conn.cursor() as mysql_cursor:
  1208. mysql_cursor.execute("""
  1209. SELECT * FROM purchase_plans
  1210. ORDER BY plan_date DESC, created_at DESC
  1211. """)
  1212. rows = mysql_cursor.fetchall()
  1213. result = [dict(row) for row in rows]
  1214. logger.info(f"✅ 从MySQL获取采购计划列表,共{len(result)}条记录")
  1215. return result
  1216. except Exception as query_error:
  1217. logger.error(f"MySQL查询采购计划执行失败: {str(query_error)}", exc_info=True)
  1218. raise
  1219. else:
  1220. logger.warning("⚠️ MySQL连接不可用,将从SQLite查询采购计划列表")
  1221. except Exception as e:
  1222. logger.warning(f"⚠️ MySQL查询采购计划失败,将从SQLite查询: {str(e)}", exc_info=True)
  1223. # MySQL失败或未启用,从SQLite查询
  1224. logger.info("从SQLite获取采购计划列表")
  1225. conn = self.get_connection()
  1226. cursor = conn.cursor()
  1227. cursor.execute("""
  1228. SELECT * FROM purchase_plans
  1229. ORDER BY plan_date DESC, created_at DESC
  1230. """)
  1231. rows = cursor.fetchall()
  1232. conn.close()
  1233. result = [dict(row) for row in rows]
  1234. logger.info(f"从SQLite获取采购计划列表,共{len(result)}条记录")
  1235. return result
  1236. def get_purchase_plan_by_id(self, plan_id: int) -> Optional[Dict]:
  1237. """
  1238. 根据ID获取采购计划记录
  1239. :param plan_id: 采购计划ID
  1240. :return: 采购计划记录字典,如果不存在返回None
  1241. """
  1242. # 先尝试从MySQL查询
  1243. if self.mysql_enabled:
  1244. try:
  1245. mysql_conn = self.get_mysql_connection()
  1246. if mysql_conn:
  1247. with mysql_conn.cursor() as mysql_cursor:
  1248. mysql_cursor.execute("SELECT * FROM purchase_plans WHERE id = %s", (plan_id,))
  1249. row = mysql_cursor.fetchone()
  1250. if row:
  1251. return dict(row)
  1252. except Exception as e:
  1253. logger.warning(f"MySQL查询采购计划失败,将从SQLite查询: {str(e)}")
  1254. # MySQL失败或未启用,从SQLite查询
  1255. conn = self.get_connection()
  1256. cursor = conn.cursor()
  1257. cursor.execute("SELECT * FROM purchase_plans WHERE id = ?", (plan_id,))
  1258. row = cursor.fetchone()
  1259. conn.close()
  1260. return dict(row) if row else None
  1261. def update_purchase_plan(self, plan_id: int, data: Dict) -> bool:
  1262. """
  1263. 更新采购计划记录
  1264. :param plan_id: 采购计划ID
  1265. :param data: 采购计划数据字典
  1266. :return: 是否成功
  1267. """
  1268. success = False
  1269. # 先操作MySQL
  1270. if self.mysql_enabled:
  1271. try:
  1272. mysql_conn = self.get_mysql_connection()
  1273. if mysql_conn:
  1274. with mysql_conn.cursor() as mysql_cursor:
  1275. mysql_cursor.execute("""
  1276. UPDATE purchase_plans
  1277. SET plan_date = %s, business_type = %s, product = %s,
  1278. specification = %s, planned_quantity = %s,
  1279. vehicle_no = %s, customer_name = %s, remarks = %s
  1280. WHERE id = %s
  1281. """, (
  1282. data['plan_date'],
  1283. data['business_type'],
  1284. data['product'],
  1285. data.get('specification', ''),
  1286. data['planned_quantity'],
  1287. data['vehicle_no'],
  1288. data.get('customer_name', ''),
  1289. data.get('remarks', ''),
  1290. plan_id
  1291. ))
  1292. affected_rows = mysql_cursor.rowcount
  1293. mysql_conn.commit()
  1294. if affected_rows > 0:
  1295. success = True
  1296. logger.info(f"MySQL更新采购计划成功: ID {plan_id}")
  1297. else:
  1298. logger.warning(f"MySQL更新采购计划: ID {plan_id} 未找到记录")
  1299. else:
  1300. logger.warning(f"MySQL连接失败,无法更新采购计划 ID {plan_id}")
  1301. except Exception as e:
  1302. logger.error(f"MySQL更新采购计划失败: ID {plan_id}, 错误: {str(e)}", exc_info=True)
  1303. if not CONTINUE_ON_MYSQL_ERROR:
  1304. raise
  1305. # 同步到SQLite
  1306. if success or not self.mysql_enabled:
  1307. try:
  1308. conn = self.get_connection()
  1309. cursor = conn.cursor()
  1310. cursor.execute("""
  1311. UPDATE purchase_plans
  1312. SET plan_date = ?, business_type = ?, product = ?,
  1313. specification = ?, planned_quantity = ?,
  1314. vehicle_no = ?, customer_name = ?, remarks = ?
  1315. WHERE id = ?
  1316. """, (
  1317. data['plan_date'],
  1318. data['business_type'],
  1319. data['product'],
  1320. data.get('specification', ''),
  1321. data['planned_quantity'],
  1322. data['vehicle_no'],
  1323. data.get('customer_name', ''),
  1324. data.get('remarks', ''),
  1325. plan_id
  1326. ))
  1327. sqlite_success = cursor.rowcount > 0
  1328. conn.commit()
  1329. conn.close()
  1330. if self.mysql_enabled and not sqlite_success:
  1331. logger.warning(f"SQLite更新采购计划: ID {plan_id} 未找到记录,可能ID不匹配")
  1332. elif not self.mysql_enabled:
  1333. success = sqlite_success
  1334. except Exception as e:
  1335. logger.error(f"SQLite更新采购计划失败: ID {plan_id}, 错误: {str(e)}")
  1336. return success
  1337. def delete_purchase_plan(self, plan_id: int) -> bool:
  1338. """
  1339. 删除采购计划记录
  1340. :param plan_id: 采购计划ID
  1341. :return: 是否成功
  1342. """
  1343. success = False
  1344. # 先操作MySQL
  1345. if self.mysql_enabled:
  1346. try:
  1347. mysql_conn = self.get_mysql_connection()
  1348. if mysql_conn:
  1349. with mysql_conn.cursor() as mysql_cursor:
  1350. mysql_cursor.execute("DELETE FROM purchase_plans WHERE id = %s", (plan_id,))
  1351. affected_rows = mysql_cursor.rowcount
  1352. mysql_conn.commit()
  1353. if affected_rows > 0:
  1354. success = True
  1355. logger.info(f"MySQL删除采购计划成功: ID {plan_id}")
  1356. else:
  1357. logger.warning(f"MySQL删除采购计划: ID {plan_id} 未找到记录")
  1358. else:
  1359. logger.warning(f"MySQL连接失败,无法删除采购计划 ID {plan_id}")
  1360. except Exception as e:
  1361. logger.error(f"MySQL删除采购计划失败: ID {plan_id}, 错误: {str(e)}", exc_info=True)
  1362. if not CONTINUE_ON_MYSQL_ERROR:
  1363. raise
  1364. # 同步到SQLite
  1365. if success or not self.mysql_enabled:
  1366. try:
  1367. conn = self.get_connection()
  1368. cursor = conn.cursor()
  1369. cursor.execute("DELETE FROM purchase_plans WHERE id = ?", (plan_id,))
  1370. sqlite_success = cursor.rowcount > 0
  1371. conn.commit()
  1372. conn.close()
  1373. if self.mysql_enabled and not sqlite_success:
  1374. logger.warning(f"SQLite删除采购计划: ID {plan_id} 未找到记录,可能ID不匹配")
  1375. elif not self.mysql_enabled:
  1376. success = sqlite_success
  1377. except Exception as e:
  1378. logger.error(f"SQLite删除采购计划失败: ID {plan_id}, 错误: {str(e)}")
  1379. return success