测试版终版

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