测试版终版
This commit is contained in:
75
new_页面内容.py
75
new_页面内容.py
@ -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)}")
|
||||||
|
|||||||
Reference in New Issue
Block a user