测试版终版

This commit is contained in:
DXC
2026-01-19 12:47:03 +08:00
parent eb8e1221fe
commit de5797378e

View File

@ -6,7 +6,7 @@ import re
import urllib.parse
import webbrowser
import json
from datetime import datetime
from datetime import datetime, timedelta
import tkinter as tk
from tkinter import filedialog, messagebox
@ -14,18 +14,23 @@ import requests
import pandas as pd
from lxml import html
# ================= 1. 导入 UI 库 =================
# ================= 1. 导入 UI 库 (已修正路径) =================
import ttkbootstrap as ttk
from ttkbootstrap.constants import *
from ttkbootstrap.dialogs import Messagebox
# 兼容导入
# 修正后的组件导入
try:
from ttkbootstrap.widgets import ScrolledText, Tableview, ToastNotification
except ImportError:
from ttkbootstrap.widgets import DateEntry
from ttkbootstrap.scrolled import ScrolledText
from ttkbootstrap.tableview import Tableview
from ttkbootstrap.toast import ToastNotification
except ImportError:
# 兼容性导入
from ttkbootstrap.widgets import DateEntry
from tkinter.scrolledtext import ScrolledText
from ttkbootstrap.tableview import Tableview
from ttkbootstrap.toast import ToastNotification
# ================= 2. 后端核心逻辑 =================
@ -65,7 +70,6 @@ class CRMCrawler:
return int(time.time() * 1000)
def clean_num(self, val):
"""将 1.0000 转换为 1保留必要的小数为空则返回空字符串"""
if val is None or val == "": return ""
try:
f_val = float(val)
@ -83,12 +87,6 @@ class CRMCrawler:
return 0.0
def fetch_product_details(self, record_id, contract_no, sales_person, outsourced_desc_from_html):
"""
修正后的价格逻辑:
1. 销售单价/销售总价 -> 永远不填 (代码中置空)
2. 厂家是"外购" -> 报价单价/总价置空,总金额填入"外购"
3. 厂家非"外购" -> 金额填入报价单价/总价,"外购"列置空
"""
detail_payload = {
"module": "Plugins", "pluginName": "DetailProductTable", "action": "getTableData",
"moduleName": "SalesOrder", "record": record_id, "actionId": self.get_timestamp(), "isTool": "1"
@ -114,7 +112,6 @@ class CRMCrawler:
products.append(v)
for prod in products:
# 1. 基础信息
manufacturer = self._get_nested_val(prod, 'cf_2128') or self._get_nested_val(prod, 'manufacturer')
prod_desc_text = prod.get('productname', '')
unit = self._get_nested_val(prod, 'usageunit')
@ -122,23 +119,19 @@ class CRMCrawler:
discount = self.clean_num(self._get_nested_val(prod, 'discount_percent'))
currency = self._get_nested_val(prod, 'cf_534')
# 2. 价格获取
list_price_raw = self._get_nested_val(prod, 'listPrice')
f_qty = self._safe_float(qty_raw)
f_list_price = self._safe_float(list_price_raw)
f_total_val = f_list_price * f_qty # 计算总价
f_total_val = f_list_price * f_qty
# 3. 判断外购逻辑
is_outsourced = False
if manufacturer and "外购" in manufacturer:
is_outsourced = True
# 4. 处理产品描述
final_desc = prod_desc_text
if is_outsourced and outsourced_desc_from_html:
final_desc = outsourced_desc_from_html
# 5. 分配金额到指定列
col_quote_unit = ""
col_quote_total = ""
col_sales_unit = ""
@ -146,14 +139,11 @@ class CRMCrawler:
col_outsourced = ""
if is_outsourced:
# 外购:报价列为空,金额填入外购列(总价)
col_outsourced = self.clean_num(f_total_val)
else:
# 非外购:金额填入报价列,外购列为空
col_quote_unit = self.clean_num(f_list_price)
col_quote_total = self.clean_num(f_total_val)
# 构建行数据
row = {
"合同编号": contract_no,
"销售员": sales_person,
@ -165,11 +155,10 @@ class CRMCrawler:
"币种": currency,
"报价单价": col_quote_unit,
"报价总价": col_quote_total,
"销售单价": col_sales_unit, # 留空
"销售总价": col_sales_total, # 留空
"销售单价": col_sales_unit,
"销售总价": col_sales_total,
"折扣率": discount,
"外购": col_outsourced,
# 预留列
"合同币种/美元": "",
"外购转美元": "",
"报价总价美元": "",
@ -429,15 +418,18 @@ class CRMGUI(ttk.Window):
self.nb_mode = ttk.Notebook(mode_grp, bootstyle="primary")
self.nb_mode.pack(fill=BOTH, expand=True)
# === 📅 日期选择部分 ===
f_date = ttk.Frame(self.nb_mode, padding=10)
self.nb_mode.add(f_date, text="📅 按时间范围")
self.ent_start = ttk.Entry(f_date, width=12);
self.ent_start.insert(0, "2026-01-14");
self.ent_start = DateEntry(f_date, dateformat='%Y-%m-%d', width=11, bootstyle="primary")
self.ent_start.pack(side=LEFT, padx=5)
ttk.Label(f_date, text="").pack(side=LEFT)
self.ent_end = ttk.Entry(f_date, width=12);
self.ent_end.insert(0, "2026-01-15");
self.ent_end = DateEntry(f_date, dateformat='%Y-%m-%d', width=11, bootstyle="primary")
self.ent_end.pack(side=LEFT, padx=5)
# =========================
f_search = ttk.Frame(self.nb_mode, padding=10)
self.nb_mode.add(f_search, text="🔍 关键词搜索")
@ -527,7 +519,6 @@ class CRMGUI(ttk.Window):
tv.pack(side=LEFT, fill=BOTH, expand=True)
for c in cols:
# === 全居中设置 ===
tv.heading(c, text=c, anchor="center")
w = 100
if "描述" in c or "标的" in c or "公司" in c or "单位" in c:
@ -540,7 +531,6 @@ class CRMGUI(ttk.Window):
w = 80
tv.column(c, width=w, minwidth=50, anchor="center")
# 移除双击编辑,保留右键菜单(仅用于浏览器打开)
tv.bind("<Button-3>", lambda e: self.on_right_click(e, tv, key))
self.treeviews[key] = tv
@ -587,13 +577,14 @@ class CRMGUI(ttk.Window):
kwargs = {}
if curr_idx == 0:
mode = "date"
kwargs = {'start': self.ent_start.get(), 'end': self.ent_end.get()}
kwargs = {'start': self.ent_start.entry.get(), 'end': self.ent_end.entry.get()}
elif curr_idx == 1:
mode = "search"
kwargs = {'query': self.ent_query.get()}
try:
self.crawler.run_task(mode, **kwargs); self.log_msg("🎉 完成!")
self.crawler.run_task(mode, **kwargs);
self.log_msg("🎉 完成!")
except Exception as e:
self.log_msg(f"❌ 错误: {e}")
finally:
@ -626,7 +617,6 @@ class CRMGUI(ttk.Window):
self.stored_data[main_key][sub_key].append(record)
record_idx = len(self.stored_data[main_key][sub_key]) - 1
# 主表
tv_key = f"{main_key}_{sub_key}"
tv = self.treeviews.get(tv_key)
if tv:
@ -634,7 +624,6 @@ class CRMGUI(ttk.Window):
vals = [record.get(c, "") for c in cols]
tv.insert("", END, iid=f"main_{main_key}_{sub_key}_{record_idx}", values=vals)
# 明细表
detail_key_suffix = ""
if sub_key == "Domestic":
detail_key_suffix = "Domestic"
@ -659,7 +648,6 @@ class CRMGUI(ttk.Window):
if not item_id: return
tv.selection_set(item_id)
# 仅在主表行点击时提供浏览器打开功能
if item_id.startswith("main_"):
parts = item_id.split('_')
main_key, sub_key, idx = parts[1], parts[2], int(parts[3])
@ -675,7 +663,6 @@ class CRMGUI(ttk.Window):
url = f"http://111.198.24.44:88/index.php?module=SalesOrder&action=DetailView&record={crm_id}"
webbrowser.open(url)
# --- 导出功能 ---
def export_data(self):
folder = filedialog.askdirectory()
if not folder: return
@ -709,6 +696,11 @@ class CRMGUI(ttk.Window):
else:
detail_domestic_rows.extend(products)
# ========== 核心修改:按合同编号升序排列 ==========
detail_domestic_rows.sort(key=lambda x: x.get("合同编号", ""))
detail_foreign_rows.sort(key=lambda x: x.get("合同编号", ""))
# ===============================================
path = os.path.join(folder, f"{prefix}_{ts}.xlsx")
try:
with pd.ExcelWriter(path, engine='openpyxl') as writer:
@ -718,6 +710,9 @@ class CRMGUI(ttk.Window):
if c not in df.columns: df[c] = ""
cols = export_cols[:2] + ["内贸合同号"] + export_cols[2:]
df = df.reindex(columns=cols)
# --- 排序 ---
df.sort_values(by="合同编号", ascending=True, inplace=True)
df.to_excel(writer, sheet_name='内贸汇总', index=False)
if data_map['Foreign']:
@ -726,6 +721,9 @@ class CRMGUI(ttk.Window):
if c not in df.columns: df[c] = ""
cols = export_cols[:2] + ["外贸合同号"] + export_cols[2:]
df = df.reindex(columns=cols)
# --- 排序 ---
df.sort_values(by="合同编号", ascending=True, inplace=True)
df.to_excel(writer, sheet_name='外贸汇总', index=False)
if data_map['Other']:
@ -734,16 +732,21 @@ class CRMGUI(ttk.Window):
if c not in df.columns: df[c] = ""
cols = export_cols[:2] + ["内贸合同号"] + export_cols[2:]
df = df.reindex(columns=cols)
# --- 排序 ---
df.sort_values(by="合同编号", ascending=True, inplace=True)
df.to_excel(writer, sheet_name='其他汇总', index=False)
if detail_domestic_rows:
df_d = pd.DataFrame(detail_domestic_rows)
df_d = df_d.reindex(columns=detail_cols_order)
# (已在前面 List 阶段排序)
df_d.to_excel(writer, sheet_name='内贸明细', index=False)
if detail_foreign_rows:
df_f = pd.DataFrame(detail_foreign_rows)
df_f = df_f.reindex(columns=detail_cols_order)
# (已在前面 List 阶段排序)
df_f.to_excel(writer, sheet_name='外贸明细', index=False)
self.log_msg(f" ✅ 导出成功: {os.path.basename(path)}")