purchase_plan_tab.py 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723
  1. """
  2. 采购计划Tab页面
  3. """
  4. from PyQt5.QtWidgets import (
  5. QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton,
  6. QTableWidget, QTableWidgetItem, QHeaderView, QMessageBox, QDialog,
  7. QFormLayout, QComboBox, QDateEdit, QRadioButton, QButtonGroup,
  8. QDoubleSpinBox, QFileDialog, QTextEdit
  9. )
  10. from PyQt5.QtCore import Qt, QDate, QDateTime, pyqtSignal
  11. from PyQt5.QtGui import QFont, QKeySequence
  12. from database import Database
  13. import pandas as pd
  14. from datetime import datetime
  15. class PurchasePlanTab(QWidget):
  16. """采购计划Tab"""
  17. def __init__(self, db: Database, main_window=None):
  18. super().__init__()
  19. self.db = db
  20. self.main_window = main_window # 保存主窗口引用,用于跳转到地磅单
  21. self.init_ui()
  22. self.refresh_list()
  23. def init_ui(self):
  24. """初始化界面"""
  25. layout = QVBoxLayout()
  26. layout.setSpacing(10)
  27. layout.setContentsMargins(15, 15, 15, 15)
  28. # 标题
  29. title = QLabel("采购计划管理")
  30. title_font = QFont("微软雅黑", 18, QFont.Bold)
  31. title.setFont(title_font)
  32. layout.addWidget(title)
  33. # 按钮区域
  34. button_layout = QHBoxLayout()
  35. button_layout.setSpacing(10)
  36. self.add_btn = QPushButton("新增")
  37. self.add_btn.setMinimumHeight(45)
  38. self.add_btn.setMinimumWidth(120)
  39. self.add_btn.setFont(QFont("微软雅黑", 16))
  40. self.add_btn.setStyleSheet("background-color: #2196F3; color: white; border-radius: 5px;")
  41. self.add_btn.clicked.connect(self.add_plan)
  42. self.add_btn.setShortcut(QKeySequence("Ctrl+N"))
  43. self.edit_btn = QPushButton("修改")
  44. self.edit_btn.setMinimumHeight(45)
  45. self.edit_btn.setMinimumWidth(120)
  46. self.edit_btn.setFont(QFont("微软雅黑", 16))
  47. self.edit_btn.clicked.connect(self.edit_plan)
  48. self.delete_btn = QPushButton("删除")
  49. self.delete_btn.setMinimumHeight(45)
  50. self.delete_btn.setMinimumWidth(120)
  51. self.delete_btn.setFont(QFont("微软雅黑", 16))
  52. self.delete_btn.setStyleSheet("background-color: #f44336; color: white; border-radius: 5px;")
  53. self.delete_btn.clicked.connect(self.delete_plan)
  54. self.delete_btn.setShortcut(QKeySequence("Delete"))
  55. self.import_btn = QPushButton("Excel导入")
  56. self.import_btn.setMinimumHeight(45)
  57. self.import_btn.setMinimumWidth(120)
  58. self.import_btn.setFont(QFont("微软雅黑", 16))
  59. self.import_btn.setStyleSheet("background-color: #4CAF50; color: white; border-radius: 5px;")
  60. self.import_btn.clicked.connect(self.import_excel)
  61. self.refresh_btn = QPushButton("刷新")
  62. self.refresh_btn.setMinimumHeight(45)
  63. self.refresh_btn.setMinimumWidth(120)
  64. self.refresh_btn.setFont(QFont("微软雅黑", 16))
  65. self.refresh_btn.setStyleSheet("""
  66. QPushButton {
  67. background-color: #607D8B;
  68. color: white;
  69. border-radius: 5px;
  70. border: none;
  71. }
  72. QPushButton:hover {
  73. background-color: #455A64;
  74. }
  75. QPushButton:pressed {
  76. background-color: #37474F;
  77. }
  78. QPushButton:disabled {
  79. background-color: #BDBDBD;
  80. color: #757575;
  81. }
  82. """)
  83. self.refresh_btn.setEnabled(True) # 确保按钮是启用状态
  84. self.refresh_btn.clicked.connect(self.refresh_list)
  85. self.refresh_btn.setShortcut(QKeySequence("F5"))
  86. button_layout.addWidget(self.add_btn)
  87. button_layout.addWidget(self.edit_btn)
  88. button_layout.addWidget(self.delete_btn)
  89. button_layout.addWidget(self.import_btn)
  90. button_layout.addWidget(self.refresh_btn)
  91. button_layout.addStretch()
  92. layout.addLayout(button_layout)
  93. # 表格
  94. self.table = QTableWidget()
  95. self.table.setColumnCount(10)
  96. self.table.setHorizontalHeaderLabels(["序号", "日期", "客户名称", "业务类型", "产品", "规格", "计划量(t)", "车牌号", "备注", "操作"])
  97. self.table.horizontalHeader().setFont(QFont("微软雅黑", 15, QFont.Bold))
  98. self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
  99. self.table.setFont(QFont("微软雅黑", 15))
  100. self.table.setAlternatingRowColors(True)
  101. self.table.setSelectionBehavior(QTableWidget.SelectRows)
  102. self.table.setSelectionMode(QTableWidget.SingleSelection)
  103. layout.addWidget(self.table)
  104. self.setLayout(layout)
  105. def refresh_list(self):
  106. """刷新列表"""
  107. plans = self.db.get_all_purchase_plans()
  108. self.table.setRowCount(len(plans))
  109. for i, plan in enumerate(plans):
  110. # 序号
  111. self.table.setItem(i, 0, QTableWidgetItem(str(i + 1)))
  112. # 日期
  113. self.table.setItem(i, 1, QTableWidgetItem(plan['plan_date']))
  114. # 客户名称
  115. self.table.setItem(i, 2, QTableWidgetItem(plan.get('customer_name', '')))
  116. # 业务类型
  117. self.table.setItem(i, 3, QTableWidgetItem(plan['business_type']))
  118. # 产品
  119. self.table.setItem(i, 4, QTableWidgetItem(plan['product']))
  120. # 规格(保持字符串格式,不进行数值转换)
  121. spec_value = plan.get('specification', '')
  122. self.table.setItem(i, 5, QTableWidgetItem(str(spec_value)))
  123. # 计划量(t)
  124. self.table.setItem(i, 6, QTableWidgetItem(f"{plan['planned_quantity']:.3f}"))
  125. # 车牌号
  126. self.table.setItem(i, 7, QTableWidgetItem(plan['vehicle_no']))
  127. # 备注
  128. remarks = plan.get('remarks', '')
  129. # 如果备注太长,只显示前50个字符
  130. remarks_display = remarks[:50] + '...' if len(remarks) > 50 else remarks
  131. self.table.setItem(i, 8, QTableWidgetItem(remarks_display))
  132. # 操作按钮
  133. btn_widget = QWidget()
  134. btn_layout = QHBoxLayout()
  135. btn_layout.setContentsMargins(5, 2, 5, 2)
  136. jump_btn = QPushButton("跳转到地磅单")
  137. jump_btn.setFont(QFont("微软雅黑", 14))
  138. jump_btn.setStyleSheet("background-color: #FF9800; color: white;")
  139. jump_btn.clicked.connect(lambda checked, pid=plan['id']: self.jump_to_weighing(pid))
  140. btn_layout.addWidget(jump_btn)
  141. btn_layout.addStretch()
  142. btn_widget.setLayout(btn_layout)
  143. self.table.setCellWidget(i, 9, btn_widget)
  144. self.table.resizeRowsToContents()
  145. def get_selected_plan_id(self) -> int:
  146. """获取选中的采购计划ID"""
  147. current_row = self.table.currentRow()
  148. if current_row < 0:
  149. return -1
  150. plans = self.db.get_all_purchase_plans()
  151. if current_row >= len(plans):
  152. return -1
  153. return plans[current_row]['id']
  154. def add_plan(self):
  155. """新增采购计划"""
  156. dialog = PurchasePlanDialog(self.db, parent=self)
  157. if dialog.exec_() == QDialog.Accepted:
  158. self.refresh_list()
  159. def edit_plan(self):
  160. """修改采购计划"""
  161. plan_id = self.get_selected_plan_id()
  162. if plan_id < 0:
  163. QMessageBox.warning(self, "提示", "请先选择要修改的采购计划")
  164. return
  165. plan = self.db.get_purchase_plan_by_id(plan_id)
  166. if not plan:
  167. QMessageBox.warning(self, "错误", "未找到该采购计划")
  168. return
  169. dialog = PurchasePlanDialog(self.db, plan=plan, parent=self)
  170. if dialog.exec_() == QDialog.Accepted:
  171. self.refresh_list()
  172. def delete_plan(self):
  173. """删除采购计划"""
  174. plan_id = self.get_selected_plan_id()
  175. if plan_id < 0:
  176. QMessageBox.warning(self, "提示", "请先选择要删除的采购计划")
  177. return
  178. # 确认删除
  179. reply = QMessageBox.question(
  180. self, "确认", "确定要删除该采购计划吗?",
  181. QMessageBox.Yes | QMessageBox.No,
  182. QMessageBox.No
  183. )
  184. if reply == QMessageBox.Yes:
  185. if self.db.delete_purchase_plan(plan_id):
  186. QMessageBox.information(self, "成功", "删除成功!")
  187. self.refresh_list()
  188. else:
  189. QMessageBox.warning(self, "错误", "删除失败")
  190. def import_excel(self):
  191. """导入Excel文件"""
  192. file_path, _ = QFileDialog.getOpenFileName(
  193. self, "选择Excel文件", "", "Excel Files (*.xlsx *.xls)"
  194. )
  195. if not file_path:
  196. return
  197. try:
  198. # 读取Excel文件(默认读取第一个sheet)
  199. # 先读取列名,确定规格列的位置
  200. df_temp = pd.read_excel(file_path, sheet_name=0, nrows=0)
  201. original_columns = df_temp.columns.tolist()
  202. # 构建converters字典,将规格列转换为字符串,保留原始格式
  203. converters = {}
  204. for col in original_columns:
  205. col_clean = col.strip().replace('\n', '').replace('\r', '')
  206. if col_clean == '规格':
  207. # 定义转换函数,将规格列转换为字符串
  208. def spec_converter(x):
  209. if pd.isna(x):
  210. return ''
  211. # 如果是数值类型(可能是百分比被转换成了小数),尝试恢复
  212. if isinstance(x, (int, float)):
  213. # 检查是否可能是百分比(0-1之间的小数)
  214. if 0 <= x <= 1:
  215. # 转换为百分比字符串
  216. return f"{x * 100:.0f}%"
  217. else:
  218. return str(x)
  219. return str(x)
  220. converters[col] = spec_converter
  221. # 读取Excel,使用converters确保规格列为字符串类型
  222. df = pd.read_excel(file_path, sheet_name=0, converters=converters)
  223. # 去除列名的前后空格和换行符
  224. df.columns = df.columns.str.strip().str.replace('\n', '').str.replace('\r', '')
  225. # 如果存在规格列,确保NaN值被转换为空字符串
  226. if '规格' in df.columns:
  227. df['规格'] = df['规格'].fillna('').astype(str)
  228. # 检查必需的列
  229. required_columns = ['日期', '业务类型', '产品', '计划量(t)', '车牌号']
  230. missing_columns = [col for col in required_columns if col not in df.columns]
  231. if missing_columns:
  232. # 显示实际读取到的列名,方便调试
  233. actual_columns = ', '.join(df.columns.tolist())
  234. QMessageBox.warning(
  235. self, "错误",
  236. f"Excel文件缺少必需的列:{', '.join(missing_columns)}\n\n"
  237. f"必需的列:{', '.join(required_columns)}\n"
  238. f"规格列为可选\n\n"
  239. f"实际读取到的列名:{actual_columns}"
  240. )
  241. return
  242. # 导入数据
  243. success_count = 0
  244. error_count = 0
  245. errors = []
  246. for index, row in df.iterrows():
  247. try:
  248. # 处理日期
  249. plan_date = row['日期']
  250. if pd.isna(plan_date):
  251. errors.append(f"第{index + 2}行:日期为空")
  252. error_count += 1
  253. continue
  254. # 转换为字符串格式
  255. if isinstance(plan_date, pd.Timestamp):
  256. plan_date = plan_date.strftime('%Y-%m-%d')
  257. elif isinstance(plan_date, datetime):
  258. plan_date = plan_date.strftime('%Y-%m-%d')
  259. else:
  260. plan_date = str(plan_date)
  261. # 处理业务类型
  262. business_type = str(row['业务类型']).strip()
  263. if business_type not in ['出库', '入库']:
  264. errors.append(f"第{index + 2}行:业务类型必须是'出库'或'入库'")
  265. error_count += 1
  266. continue
  267. # 处理产品
  268. product = str(row['产品']).strip()
  269. if not product:
  270. errors.append(f"第{index + 2}行:产品为空")
  271. error_count += 1
  272. continue
  273. # 处理计划量
  274. planned_quantity = float(row['计划量(t)'])
  275. if planned_quantity <= 0:
  276. errors.append(f"第{index + 2}行:计划量必须大于0")
  277. error_count += 1
  278. continue
  279. # 处理车牌号
  280. vehicle_no = str(row['车牌号']).strip()
  281. if not vehicle_no:
  282. errors.append(f"第{index + 2}行:车牌号为空")
  283. error_count += 1
  284. continue
  285. # 处理规格(可选)
  286. specification = ''
  287. if '规格' in df.columns and not pd.isna(row.get('规格')):
  288. specification = str(row['规格']).strip()
  289. # 处理客户名称(可选)
  290. customer_name = ''
  291. if '客户名称' in df.columns and not pd.isna(row.get('客户名称')):
  292. customer_name = str(row['客户名称']).strip()
  293. elif '客户' in df.columns and not pd.isna(row.get('客户')):
  294. customer_name = str(row['客户']).strip()
  295. # 处理备注(可选)
  296. remarks = ''
  297. if '备注' in df.columns and not pd.isna(row.get('备注')):
  298. remarks = str(row['备注']).strip()
  299. # 插入数据
  300. data = {
  301. 'plan_date': plan_date,
  302. 'business_type': business_type,
  303. 'product': product,
  304. 'specification': specification,
  305. 'planned_quantity': planned_quantity,
  306. 'vehicle_no': vehicle_no,
  307. 'customer_name': customer_name,
  308. 'remarks': remarks
  309. }
  310. self.db.insert_purchase_plan(data)
  311. success_count += 1
  312. except Exception as e:
  313. errors.append(f"第{index + 2}行:{str(e)}")
  314. error_count += 1
  315. # 显示导入结果
  316. message = f"导入完成!\n成功:{success_count}条\n失败:{error_count}条"
  317. if errors:
  318. message += f"\n\n错误详情:\n" + "\n".join(errors[:10]) # 最多显示10个错误
  319. if len(errors) > 10:
  320. message += f"\n...还有{len(errors) - 10}个错误"
  321. if error_count > 0:
  322. QMessageBox.warning(self, "导入结果", message)
  323. else:
  324. QMessageBox.information(self, "导入成功", message)
  325. self.refresh_list()
  326. except Exception as e:
  327. QMessageBox.critical(self, "错误", f"导入失败:{str(e)}")
  328. def jump_to_weighing(self, plan_id: int):
  329. """跳转到地磅单并填充信息"""
  330. plan = self.db.get_purchase_plan_by_id(plan_id)
  331. if not plan:
  332. QMessageBox.warning(self, "错误", "未找到该采购计划")
  333. return
  334. if not self.main_window:
  335. QMessageBox.warning(self, "错误", "无法访问主窗口")
  336. return
  337. # 切换到地磅单tab(索引1,因为采购计划是0,地磅单是1)
  338. self.main_window.tab_widget.setCurrentIndex(1)
  339. # 获取地磅单tab
  340. weighing_tab = self.main_window.weighing_tab
  341. # 填充信息
  342. # 车牌号
  343. weighing_tab.vehicle_no_input.setText(plan['vehicle_no'])
  344. # 业务类型
  345. business_type = plan['business_type']
  346. if business_type == '出库':
  347. weighing_tab.business_sale_radio.setChecked(True)
  348. elif business_type == '入库':
  349. weighing_tab.business_purchase_radio.setChecked(True)
  350. # 获取客户名称
  351. customer_name = plan.get('customer_name', '').strip() if plan.get('customer_name') else ''
  352. # 固定公司名称
  353. fixed_company_name = "安庆市山峡化工有限公司"
  354. # 第一步:自动维护数据到对应模块(避免手动添加)
  355. # 1. 自动添加产品到货物模块(如果不存在)
  356. product_name = plan['product']
  357. if product_name:
  358. if not self.db.cargo_exists(product_name):
  359. try:
  360. self.db.insert_cargo(product_name)
  361. # 产品已自动维护到货物模块,无需手动添加
  362. except Exception as e:
  363. QMessageBox.warning(self, "警告", f"自动添加货物'{product_name}'失败:{str(e)}")
  364. # 2. 确保"安庆市山峡化工有限公司"在公司列表中(如果不存在则自动添加)
  365. if not self.db.company_exists(fixed_company_name):
  366. try:
  367. self.db.insert_company(fixed_company_name)
  368. except Exception as e:
  369. QMessageBox.warning(self, "警告", f"自动添加公司'{fixed_company_name}'失败:{str(e)}")
  370. # 3. 自动添加客户名称到公司模块(如果不存在)
  371. # 注意:客户名称会自动维护到公司模块,无需手动添加
  372. if customer_name:
  373. if not self.db.company_exists(customer_name):
  374. try:
  375. self.db.insert_company(customer_name)
  376. # 客户名称已自动维护到公司模块,无需手动添加
  377. except Exception as e:
  378. QMessageBox.warning(self, "警告", f"自动添加客户'{customer_name}'到公司模块失败:{str(e)}")
  379. # 第二步:刷新下拉列表(确保新添加的数据显示)
  380. weighing_tab.refresh_dropdowns()
  381. # 第三步:填充表单字段
  382. # 货物名称(产品)
  383. if product_name:
  384. index = weighing_tab.cargo_combo.findText(product_name)
  385. if index >= 0:
  386. weighing_tab.cargo_combo.setCurrentIndex(index)
  387. # 根据业务类型填充供货单位和收货单位
  388. if business_type == '入库':
  389. # 入库:客户名称 -> 供货单位,收货单位固定为"安庆市山峡化工有限公司"
  390. if customer_name:
  391. # 设置供货单位为客户名称
  392. index = weighing_tab.supplier_combo.findText(customer_name)
  393. if index >= 0:
  394. weighing_tab.supplier_combo.setCurrentIndex(index)
  395. # 设置收货单位为"安庆市山峡化工有限公司"
  396. index = weighing_tab.consignee_combo.findText(fixed_company_name)
  397. if index >= 0:
  398. weighing_tab.consignee_combo.setCurrentIndex(index)
  399. elif business_type == '出库':
  400. # 出库:客户名称 -> 收货单位,供货单位固定为"安庆市山峡化工有限公司"
  401. # 设置供货单位为"安庆市山峡化工有限公司"
  402. index = weighing_tab.supplier_combo.findText(fixed_company_name)
  403. if index >= 0:
  404. weighing_tab.supplier_combo.setCurrentIndex(index)
  405. if customer_name:
  406. # 设置收货单位为客户名称
  407. index = weighing_tab.consignee_combo.findText(customer_name)
  408. if index >= 0:
  409. weighing_tab.consignee_combo.setCurrentIndex(index)
  410. # 计划量(转换为kg,因为计划量是t)
  411. planned_quantity_kg = plan['planned_quantity'] * 1000
  412. weighing_tab.planned_quantity_input.setValue(planned_quantity_kg)
  413. # 先清空备注,然后设置采购计划的备注
  414. remarks_parts = []
  415. # 规格可以放在备注中
  416. if plan.get('specification'):
  417. remarks_parts.append(f"规格:{plan['specification']}")
  418. # 采购计划的备注
  419. if plan.get('remarks'):
  420. remarks_parts.append(plan['remarks'])
  421. # 先清空备注,然后设置新的备注内容
  422. if remarks_parts:
  423. new_remarks = "\n".join(remarks_parts)
  424. weighing_tab.remarks_input.setPlainText(new_remarks)
  425. else:
  426. weighing_tab.remarks_input.setPlainText("")
  427. # 清空毛重时间和皮重时间
  428. weighing_tab.gross_weight_time.setDateTime(QDateTime())
  429. if weighing_tab.gross_weight_time.lineEdit():
  430. weighing_tab.gross_weight_time.lineEdit().setText("")
  431. weighing_tab.tare_weight_time.setDateTime(QDateTime())
  432. if weighing_tab.tare_weight_time.lineEdit():
  433. weighing_tab.tare_weight_time.lineEdit().setText("")
  434. class PurchasePlanDialog(QDialog):
  435. """采购计划编辑对话框"""
  436. def __init__(self, db: Database, plan=None, parent=None):
  437. super().__init__(parent)
  438. self.db = db
  439. self.plan = plan
  440. self.init_ui()
  441. def init_ui(self):
  442. """初始化对话框界面"""
  443. if self.plan:
  444. self.setWindowTitle("修改采购计划")
  445. else:
  446. self.setWindowTitle("新增采购计划")
  447. self.setMinimumWidth(500)
  448. layout = QVBoxLayout()
  449. layout.setSpacing(15)
  450. layout.setContentsMargins(20, 20, 20, 20)
  451. # 表单
  452. form_layout = QFormLayout()
  453. form_layout.setSpacing(15)
  454. # 日期
  455. date_label = QLabel("日期 *:")
  456. date_label.setFont(QFont("微软雅黑", 16))
  457. self.date_input = QDateEdit()
  458. self.date_input.setMinimumHeight(45)
  459. self.date_input.setFont(QFont("微软雅黑", 16))
  460. self.date_input.setCalendarPopup(True)
  461. self.date_input.setDate(QDate.currentDate())
  462. self.date_input.setDisplayFormat("yyyy-MM-dd")
  463. form_layout.addRow(date_label, self.date_input)
  464. # 业务类型
  465. business_label = QLabel("业务类型 *:")
  466. business_label.setFont(QFont("微软雅黑", 16))
  467. self.business_type_group = QButtonGroup()
  468. self.business_sale_radio = QRadioButton("出库")
  469. self.business_purchase_radio = QRadioButton("入库")
  470. self.business_sale_radio.setFont(QFont("微软雅黑", 16))
  471. self.business_purchase_radio.setFont(QFont("微软雅黑", 16))
  472. self.business_type_group.addButton(self.business_sale_radio, 0)
  473. self.business_type_group.addButton(self.business_purchase_radio, 1)
  474. business_layout = QHBoxLayout()
  475. business_layout.addWidget(self.business_sale_radio)
  476. business_layout.addWidget(self.business_purchase_radio)
  477. business_layout.addStretch()
  478. business_widget = QWidget()
  479. business_widget.setLayout(business_layout)
  480. form_layout.addRow(business_label, business_widget)
  481. # 产品
  482. product_label = QLabel("产品 *:")
  483. product_label.setFont(QFont("微软雅黑", 16))
  484. self.product_input = QLineEdit()
  485. self.product_input.setMinimumHeight(45)
  486. self.product_input.setFont(QFont("微软雅黑", 16))
  487. form_layout.addRow(product_label, self.product_input)
  488. # 规格
  489. spec_label = QLabel("规格:")
  490. spec_label.setFont(QFont("微软雅黑", 16))
  491. self.spec_input = QLineEdit()
  492. self.spec_input.setMinimumHeight(45)
  493. self.spec_input.setFont(QFont("微软雅黑", 16))
  494. form_layout.addRow(spec_label, self.spec_input)
  495. # 计划量(t)
  496. quantity_label = QLabel("计划量(t) *:")
  497. quantity_label.setFont(QFont("微软雅黑", 16))
  498. self.quantity_input = QDoubleSpinBox()
  499. self.quantity_input.setMinimumHeight(45)
  500. self.quantity_input.setFont(QFont("微软雅黑", 16))
  501. self.quantity_input.setDecimals(3)
  502. self.quantity_input.setMinimum(0.001)
  503. self.quantity_input.setMaximum(999999)
  504. form_layout.addRow(quantity_label, self.quantity_input)
  505. # 车牌号
  506. vehicle_label = QLabel("车牌号 *:")
  507. vehicle_label.setFont(QFont("微软雅黑", 16))
  508. self.vehicle_input = QLineEdit()
  509. self.vehicle_input.setMinimumHeight(45)
  510. self.vehicle_input.setFont(QFont("微软雅黑", 16))
  511. form_layout.addRow(vehicle_label, self.vehicle_input)
  512. # 客户名称
  513. customer_label = QLabel("客户名称:")
  514. customer_label.setFont(QFont("微软雅黑", 16))
  515. self.customer_input = QLineEdit()
  516. self.customer_input.setMinimumHeight(45)
  517. self.customer_input.setFont(QFont("微软雅黑", 16))
  518. form_layout.addRow(customer_label, self.customer_input)
  519. # 备注
  520. remarks_label = QLabel("备注:")
  521. remarks_label.setFont(QFont("微软雅黑", 16))
  522. self.remarks_input = QTextEdit()
  523. self.remarks_input.setMinimumHeight(80)
  524. self.remarks_input.setFont(QFont("微软雅黑", 16))
  525. form_layout.addRow(remarks_label, self.remarks_input)
  526. # 如果是在修改模式,填充数据
  527. if self.plan:
  528. self.date_input.setDate(QDate.fromString(self.plan['plan_date'], "yyyy-MM-dd"))
  529. if self.plan['business_type'] == '出库':
  530. self.business_sale_radio.setChecked(True)
  531. else:
  532. self.business_purchase_radio.setChecked(True)
  533. self.product_input.setText(self.plan['product'])
  534. self.spec_input.setText(self.plan.get('specification', ''))
  535. self.quantity_input.setValue(self.plan['planned_quantity'])
  536. self.vehicle_input.setText(self.plan['vehicle_no'])
  537. self.customer_input.setText(self.plan.get('customer_name', ''))
  538. self.remarks_input.setPlainText(self.plan.get('remarks', ''))
  539. layout.addLayout(form_layout)
  540. # 按钮
  541. button_layout = QHBoxLayout()
  542. button_layout.addStretch()
  543. ok_btn = QPushButton("确定")
  544. ok_btn.setMinimumHeight(45)
  545. ok_btn.setMinimumWidth(100)
  546. ok_btn.setFont(QFont("微软雅黑", 16))
  547. ok_btn.clicked.connect(self.save)
  548. cancel_btn = QPushButton("取消")
  549. cancel_btn.setMinimumHeight(45)
  550. cancel_btn.setMinimumWidth(100)
  551. cancel_btn.setFont(QFont("微软雅黑", 16))
  552. cancel_btn.clicked.connect(self.reject)
  553. button_layout.addWidget(ok_btn)
  554. button_layout.addWidget(cancel_btn)
  555. layout.addLayout(button_layout)
  556. self.setLayout(layout)
  557. def save(self):
  558. """保存"""
  559. # 验证
  560. if not self.date_input.date().isValid():
  561. QMessageBox.warning(self, "验证失败", "请选择日期")
  562. return
  563. if not self.business_type_group.checkedButton():
  564. QMessageBox.warning(self, "验证失败", "请选择业务类型")
  565. return
  566. product = self.product_input.text().strip()
  567. if not product:
  568. QMessageBox.warning(self, "验证失败", "产品不能为空")
  569. return
  570. if self.quantity_input.value() <= 0:
  571. QMessageBox.warning(self, "验证失败", "计划量必须大于0")
  572. return
  573. vehicle_no = self.vehicle_input.text().strip()
  574. if not vehicle_no:
  575. QMessageBox.warning(self, "验证失败", "车牌号不能为空")
  576. return
  577. # 准备数据
  578. data = {
  579. 'plan_date': self.date_input.date().toString("yyyy-MM-dd"),
  580. 'business_type': self.business_type_group.checkedButton().text(),
  581. 'product': product,
  582. 'specification': self.spec_input.text().strip(),
  583. 'planned_quantity': self.quantity_input.value(),
  584. 'vehicle_no': vehicle_no,
  585. 'customer_name': self.customer_input.text().strip(),
  586. 'remarks': self.remarks_input.toPlainText().strip()
  587. }
  588. # 保存
  589. if self.plan:
  590. # 修改模式
  591. if self.db.update_purchase_plan(self.plan['id'], data):
  592. QMessageBox.information(self, "成功", "修改成功!")
  593. self.accept()
  594. else:
  595. QMessageBox.warning(self, "错误", "修改失败")
  596. else:
  597. # 新增模式
  598. try:
  599. self.db.insert_purchase_plan(data)
  600. QMessageBox.information(self, "成功", "新增成功!")
  601. self.accept()
  602. except Exception as e:
  603. QMessageBox.critical(self, "错误", f"新增失败:{str(e)}")