|
@@ -1,4 +1,4 @@
|
|
|
-#!/usr/bin/env python3
|
|
|
|
|
|
|
+#!/usr/bin/env python3
|
|
|
# -*- coding: utf-8 -*-
|
|
# -*- coding: utf-8 -*-
|
|
|
"""
|
|
"""
|
|
|
主生成脚本:根据订单信息生成销售合同和Proforma Invoice
|
|
主生成脚本:根据订单信息生成销售合同和Proforma Invoice
|
|
@@ -16,6 +16,7 @@ sys.path.insert(0, script_dir)
|
|
|
|
|
|
|
|
from parse_order_info import parse_order_info
|
|
from parse_order_info import parse_order_info
|
|
|
from match_vehicle import match_vehicle, build_vehicle_description
|
|
from match_vehicle import match_vehicle, build_vehicle_description
|
|
|
|
|
+from parse_quotation import load_quotation, match_vehicle_from_quotation
|
|
|
from number_to_words import format_say_us_only, calculate_payment_split
|
|
from number_to_words import format_say_us_only, calculate_payment_split
|
|
|
|
|
|
|
|
# 模板正文默认字号:12pt
|
|
# 模板正文默认字号:12pt
|
|
@@ -43,6 +44,31 @@ def get_asset_path(filename: str) -> str:
|
|
|
return os.path.join(script_dir, '..', 'assets', filename)
|
|
return os.path.join(script_dir, '..', 'assets', filename)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+def _get_clean_template():
|
|
|
|
|
+ """Return path to a cleaned PI template with no embedded stamp."""
|
|
|
|
|
+ orig = get_asset_path("proforma-invoice-template.xlsx")
|
|
|
|
|
+ cache = os.path.join(os.environ.get("TEMP", os.getcwd()), "_pi_clean_template.xlsx")
|
|
|
|
|
+ if not os.path.exists(cache) or os.path.getmtime(orig) > os.path.getmtime(cache):
|
|
|
|
|
+ import zipfile
|
|
|
|
|
+ import re as _re
|
|
|
|
|
+ with zipfile.ZipFile(orig, "r") as zin:
|
|
|
|
|
+ with zipfile.ZipFile(cache, "w") as zout:
|
|
|
|
|
+ for item in zin.namelist():
|
|
|
|
|
+ if "drawing" in item.lower() or "media" in item.lower():
|
|
|
|
|
+ continue
|
|
|
|
|
+ data = zin.read(item)
|
|
|
|
|
+ if item == "xl/worksheets/sheet1.xml":
|
|
|
|
|
+ data = data.decode("utf-8")
|
|
|
|
|
+ data = _re.sub(r"<drawing[^>]*/>", "", data)
|
|
|
|
|
+ data = data.encode("utf-8")
|
|
|
|
|
+ if item == "xl/worksheets/_rels/sheet1.xml.rels":
|
|
|
|
|
+ data = data.decode("utf-8")
|
|
|
|
|
+ data = _re.sub(r"<Relationship[^>]*drawing[^>]*/>", "", data)
|
|
|
|
|
+ data = data.encode("utf-8")
|
|
|
|
|
+ zout.writestr(item, data)
|
|
|
|
|
+ return cache
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
def generate_contract_no() -> str:
|
|
def generate_contract_no() -> str:
|
|
|
"""生成合同编号: HLXYWPA{年月日}"""
|
|
"""生成合同编号: HLXYWPA{年月日}"""
|
|
|
return f"HLXYWPA{datetime.now().strftime('%Y%m%d')}"
|
|
return f"HLXYWPA{datetime.now().strftime('%Y%m%d')}"
|
|
@@ -84,6 +110,48 @@ def get_port_names(port_input: str) -> tuple:
|
|
|
# 默认返回输入值
|
|
# 默认返回输入值
|
|
|
return port_input, port_input
|
|
return port_input, port_input
|
|
|
|
|
|
|
|
|
|
+# 国家名称中文→英文映射(用于Export Destination字段)
|
|
|
|
|
+COUNTRY_NAME_MAP = {
|
|
|
|
|
+ '加纳': 'Ghana',
|
|
|
|
|
+ '巴拿马': 'Panama',
|
|
|
|
|
+ '安哥拉': 'Angola',
|
|
|
|
|
+ '阿尔及利亚': 'Algeria',
|
|
|
|
|
+ '阿根廷': 'Argentina',
|
|
|
|
|
+ '阿联酋': 'UAE',
|
|
|
|
|
+ '埃及': 'Egypt',
|
|
|
|
|
+ '埃塞俄比亚': 'Ethiopia',
|
|
|
|
|
+ '澳大利亚': 'Australia',
|
|
|
|
|
+ '巴基斯坦': 'Pakistan',
|
|
|
|
|
+ '巴西': 'Brazil',
|
|
|
|
|
+ '刚果': 'Congo',
|
|
|
|
|
+ '哥伦比亚': 'Colombia',
|
|
|
|
|
+ '哈萨克斯坦': 'Kazakhstan',
|
|
|
|
|
+ '韩国': 'South Korea',
|
|
|
|
|
+ '荷兰': 'Netherlands',
|
|
|
|
|
+ '加拿大': 'Canada',
|
|
|
|
|
+ '肯尼亚': 'Kenya',
|
|
|
|
|
+ '马来西亚': 'Malaysia',
|
|
|
|
|
+ '美国': 'USA',
|
|
|
|
|
+ '孟加拉': 'Bangladesh',
|
|
|
|
|
+ '秘鲁': 'Peru',
|
|
|
|
|
+ '墨西哥': 'Mexico',
|
|
|
|
|
+ '南非': 'South Africa',
|
|
|
|
|
+ '尼日利亚': 'Nigeria',
|
|
|
|
|
+ '日本': 'Japan',
|
|
|
|
|
+ '泰国': 'Thailand',
|
|
|
|
|
+ '坦桑尼亚': 'Tanzania',
|
|
|
|
|
+ '土耳其': 'Turkey',
|
|
|
|
|
+ '乌兹别克斯坦': 'Uzbekistan',
|
|
|
|
|
+ '西班牙': 'Spain',
|
|
|
|
|
+ '意大利': 'Italy',
|
|
|
|
|
+ '印度': 'India',
|
|
|
|
|
+ '印度尼西亚': 'Indonesia',
|
|
|
|
|
+ '英国': 'UK',
|
|
|
|
|
+ '越南': 'Vietnam',
|
|
|
|
|
+ '智利': 'Chile',
|
|
|
|
|
+ '中国': 'China',
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
|
|
|
def number_to_chinese(num: int) -> str:
|
|
def number_to_chinese(num: int) -> str:
|
|
|
"""将整数金额转换为中文大写(壹贰叁肆伍陆柒捌玖拾佰仟万)"""
|
|
"""将整数金额转换为中文大写(壹贰叁肆伍陆柒捌玖拾佰仟万)"""
|
|
@@ -305,6 +373,8 @@ def build_full_description(vehicle: dict, match_result: dict = None) -> str:
|
|
|
name_en = vehicle.get("name_en", "")
|
|
name_en = vehicle.get("name_en", "")
|
|
|
color_en, color_cn = COLOR_MAP.get(color, (color, color))
|
|
color_en, color_cn = COLOR_MAP.get(color, (color, color))
|
|
|
color_display = color if color else ""
|
|
color_display = color if color else ""
|
|
|
|
|
+ qty = vehicle.get("quantity", 0)
|
|
|
|
|
+ qty_str = f" {qty}" if qty else ""
|
|
|
lines = []
|
|
lines = []
|
|
|
if match_result:
|
|
if match_result:
|
|
|
series_en = match_result.get("series_en", "")
|
|
series_en = match_result.get("series_en", "")
|
|
@@ -337,7 +407,7 @@ def build_full_description(vehicle: dict, match_result: dict = None) -> str:
|
|
|
lines.append(config_en)
|
|
lines.append(config_en)
|
|
|
elif config_cn:
|
|
elif config_cn:
|
|
|
lines.append(config_cn)
|
|
lines.append(config_cn)
|
|
|
- lines.append("Color: " + color_en if color_display else "Color: ")
|
|
|
|
|
|
|
+ lines.append("Color: " + color_en + qty_str if color_display else "Color: ")
|
|
|
cn_line = chr(22411) + chr(21495) + chr(65306) + model_code + ", " + eng_first
|
|
cn_line = chr(22411) + chr(21495) + chr(65306) + model_code + ", " + eng_first
|
|
|
if desc_cn:
|
|
if desc_cn:
|
|
|
cn_line += " " + desc_cn
|
|
cn_line += " " + desc_cn
|
|
@@ -351,7 +421,7 @@ def build_full_description(vehicle: dict, match_result: dict = None) -> str:
|
|
|
lines.append(config_cn)
|
|
lines.append(config_cn)
|
|
|
elif config_en:
|
|
elif config_en:
|
|
|
lines.append(config_en)
|
|
lines.append(config_en)
|
|
|
- lines.append(chr(39068) + chr(33394) + chr(65306) + color_cn if color_display else chr(39068) + chr(33394) + chr(65306))
|
|
|
|
|
|
|
+ lines.append(chr(39068) + chr(33394) + chr(65306) + color_cn + qty_str if color_display else chr(39068) + chr(33394) + chr(65306))
|
|
|
else:
|
|
else:
|
|
|
eng_lines = [l.strip() for l in engine_code.split(chr(10)) if l.strip()]
|
|
eng_lines = [l.strip() for l in engine_code.split(chr(10)) if l.strip()]
|
|
|
eng_first = eng_lines[0] if eng_lines else engine_code
|
|
eng_first = eng_lines[0] if eng_lines else engine_code
|
|
@@ -365,7 +435,7 @@ def build_full_description(vehicle: dict, match_result: dict = None) -> str:
|
|
|
lines.append(eng_line)
|
|
lines.append(eng_line)
|
|
|
for extra in eng_extra:
|
|
for extra in eng_extra:
|
|
|
lines.append(extra)
|
|
lines.append(extra)
|
|
|
- lines.append("Color: " + color_en if color_display else "Color: ")
|
|
|
|
|
|
|
+ lines.append("Color: " + color_en + qty_str if color_display else "Color: ")
|
|
|
cn_line = chr(22411) + chr(21495) + chr(65306) + model_code + ", " + eng_first
|
|
cn_line = chr(22411) + chr(21495) + chr(65306) + model_code + ", " + eng_first
|
|
|
if name_cn:
|
|
if name_cn:
|
|
|
cn_line += " " + name_cn
|
|
cn_line += " " + name_cn
|
|
@@ -374,9 +444,9 @@ def build_full_description(vehicle: dict, match_result: dict = None) -> str:
|
|
|
lines.append(cn_line)
|
|
lines.append(cn_line)
|
|
|
for extra in eng_extra:
|
|
for extra in eng_extra:
|
|
|
lines.append(extra)
|
|
lines.append(extra)
|
|
|
- lines.append(chr(39068) + chr(33394) + chr(65306) + color_cn if color_display else chr(39068) + chr(33394) + chr(65306))
|
|
|
|
|
|
|
+ lines.append(chr(39068) + chr(33394) + chr(65306) + color_cn + qty_str if color_display else chr(39068) + chr(33394) + chr(65306))
|
|
|
return chr(10).join(lines)
|
|
return chr(10).join(lines)
|
|
|
-def process_vehicles(vehicles: list, trade_term: str) -> list:
|
|
|
|
|
|
|
+def process_vehicles(vehicles: list, trade_term: str, quotation: dict = None) -> list:
|
|
|
"""
|
|
"""
|
|
|
处理车型列表,匹配价格表或提示用户提供价格
|
|
处理车型列表,匹配价格表或提示用户提供价格
|
|
|
|
|
|
|
@@ -395,7 +465,10 @@ def process_vehicles(vehicles: list, trade_term: str) -> list:
|
|
|
continue
|
|
continue
|
|
|
|
|
|
|
|
# 从价格表匹配
|
|
# 从价格表匹配
|
|
|
- match_result = match_vehicle(model_code, trade_term)
|
|
|
|
|
|
|
+ if quotation:
|
|
|
|
|
+ match_result = match_vehicle_from_quotation(model_code, quotation)
|
|
|
|
|
+ else:
|
|
|
|
|
+ match_result = match_vehicle(model_code, trade_term)
|
|
|
if match_result and match_result.get('unit_price_usd'):
|
|
if match_result and match_result.get('unit_price_usd'):
|
|
|
v['unit_price_usd'] = match_result['unit_price_usd']
|
|
v['unit_price_usd'] = match_result['unit_price_usd']
|
|
|
v['config_desc'] = build_vehicle_description(v, match_result)
|
|
v['config_desc'] = build_vehicle_description(v, match_result)
|
|
@@ -434,9 +507,10 @@ def calculate_summary(vehicles: list) -> dict:
|
|
|
|
|
|
|
|
|
|
|
|
|
def _add_stamp_to_sheet(ws, template_path, output_path):
|
|
def _add_stamp_to_sheet(ws, template_path, output_path):
|
|
|
- """Add stamp image. Clean old drawing files first."""
|
|
|
|
|
|
|
+ """Add stamp image to E30. Remove existing stamp from template first to avoid overlap."""
|
|
|
import zipfile, os, tempfile
|
|
import zipfile, os, tempfile
|
|
|
from openpyxl.drawing.image import Image
|
|
from openpyxl.drawing.image import Image
|
|
|
|
|
+ from openpyxl.drawing.spreadsheet_drawing import SpreadsheetDrawing
|
|
|
from io import BytesIO
|
|
from io import BytesIO
|
|
|
try:
|
|
try:
|
|
|
img_data = None
|
|
img_data = None
|
|
@@ -445,12 +519,23 @@ def _add_stamp_to_sheet(ws, template_path, output_path):
|
|
|
img_data = tz.read("xl/media/image1.png")
|
|
img_data = tz.read("xl/media/image1.png")
|
|
|
if not img_data:
|
|
if not img_data:
|
|
|
return
|
|
return
|
|
|
|
|
+
|
|
|
|
|
+ # \u6e05\u7406\u6a21\u677f\u4e2d\u5df2\u6709\u7684\u5370\u7ae0\u56fe\u7247\uff0c\u907f\u514d\u91cd\u53e0
|
|
|
|
|
+ ws._images.clear()
|
|
|
|
|
+ ws._drawing = None
|
|
|
|
|
+
|
|
|
tmp = os.path.join(tempfile.gettempdir(), "_sgmw_stamp.png")
|
|
tmp = os.path.join(tempfile.gettempdir(), "_sgmw_stamp.png")
|
|
|
with open(tmp, "wb") as f:
|
|
with open(tmp, "wb") as f:
|
|
|
f.write(img_data)
|
|
f.write(img_data)
|
|
|
img = Image(tmp)
|
|
img = Image(tmp)
|
|
|
img.anchor = "E30"
|
|
img.anchor = "E30"
|
|
|
ws.add_image(img)
|
|
ws.add_image(img)
|
|
|
|
|
+
|
|
|
|
|
+ # \u660e\u786e\u6784\u5efa SpreadsheetDrawing\uff0c\u786e\u4fdd save \u65f6\u53ea\u6709\u4e00\u4e2a\u5370\u7ae0
|
|
|
|
|
+ drawing = SpreadsheetDrawing()
|
|
|
|
|
+ drawing.charts = ws._charts
|
|
|
|
|
+ drawing.images = ws._images
|
|
|
|
|
+ ws._drawing = drawing
|
|
|
except Exception as e:
|
|
except Exception as e:
|
|
|
print("Stamp warning:", e)
|
|
print("Stamp warning:", e)
|
|
|
def generate_proforma_invoice(order: dict, vehicles: list, summary: dict,
|
|
def generate_proforma_invoice(order: dict, vehicles: list, summary: dict,
|
|
@@ -462,10 +547,11 @@ def generate_proforma_invoice(order: dict, vehicles: list, summary: dict,
|
|
|
from io import BytesIO
|
|
from io import BytesIO
|
|
|
import zipfile
|
|
import zipfile
|
|
|
|
|
|
|
|
- template_path = get_asset_path("proforma-invoice-template.xlsx")
|
|
|
|
|
|
|
+ original_template = get_asset_path("proforma-invoice-template.xlsx")
|
|
|
|
|
+ clean_template = _get_clean_template()
|
|
|
output_filename = contract_no + "-Proforma Invoice.xlsx"
|
|
output_filename = contract_no + "-Proforma Invoice.xlsx"
|
|
|
output_path = os.path.join(output_dir, output_filename)
|
|
output_path = os.path.join(output_dir, output_filename)
|
|
|
- shutil.copy(template_path, output_path)
|
|
|
|
|
|
|
+ shutil.copy(clean_template, output_path)
|
|
|
wb = openpyxl.load_workbook(output_path)
|
|
wb = openpyxl.load_workbook(output_path)
|
|
|
ws = wb[chr(73) + chr(78) + chr(86) + chr(79) + chr(73) + chr(67) + chr(69)]
|
|
ws = wb[chr(73) + chr(78) + chr(86) + chr(79) + chr(73) + chr(67) + chr(69)]
|
|
|
|
|
|
|
@@ -506,7 +592,7 @@ def generate_proforma_invoice(order: dict, vehicles: list, summary: dict,
|
|
|
"2. 70% of the Total Amount (USD " + str(summary["balance_70"]) + ") shall be paid to Seller within 10 working days before delivery. ")
|
|
"2. 70% of the Total Amount (USD " + str(summary["balance_70"]) + ") shall be paid to Seller within 10 working days before delivery. ")
|
|
|
ws["B29"] = payment_text
|
|
ws["B29"] = payment_text
|
|
|
|
|
|
|
|
- _add_stamp_to_sheet(ws, template_path, output_path)
|
|
|
|
|
|
|
+ _add_stamp_to_sheet(ws, original_template, output_path)
|
|
|
|
|
|
|
|
wb.save(output_path)
|
|
wb.save(output_path)
|
|
|
wb.close()
|
|
wb.close()
|
|
@@ -592,7 +678,13 @@ def generate_sales_contract(order: dict, vehicles: list, summary: dict,
|
|
|
dest_raw = order.get('destination_country', '')
|
|
dest_raw = order.get('destination_country', '')
|
|
|
if dest_raw and para.runs:
|
|
if dest_raw and para.runs:
|
|
|
# 从混合字符串中提取英文部分(如 "巴拿马Panama" → "Panama")
|
|
# 从混合字符串中提取英文部分(如 "巴拿马Panama" → "Panama")
|
|
|
- dest_en = re.sub(r'[\u4e00-\u9fff]+', '', dest_raw).strip()
|
|
|
|
|
|
|
+ dest_en = re.sub(r'[\u4e00-\u9fff\u200b-\u200d\ufeff]+', '', dest_raw).strip()
|
|
|
|
|
+ if not dest_en:
|
|
|
|
|
+ # 如果纯中文,尝试通过中→英映射表查找英文名
|
|
|
|
|
+ for cn, en in COUNTRY_NAME_MAP.items():
|
|
|
|
|
+ if cn in dest_raw.replace('\u200b', ''):
|
|
|
|
|
+ dest_en = en
|
|
|
|
|
+ break
|
|
|
if not dest_en:
|
|
if not dest_en:
|
|
|
dest_en = dest_raw
|
|
dest_en = dest_raw
|
|
|
# 找到包含国家名的 run(非 bold、非括号说明部分),替换为新国家名
|
|
# 找到包含国家名的 run(非 bold、非括号说明部分),替换为新国家名
|
|
@@ -606,7 +698,7 @@ def generate_sales_contract(order: dict, vehicles: list, summary: dict,
|
|
|
dest_raw = order.get('destination_country_cn', '') or order.get('destination_country', '')
|
|
dest_raw = order.get('destination_country_cn', '') or order.get('destination_country', '')
|
|
|
if dest_raw and para.runs:
|
|
if dest_raw and para.runs:
|
|
|
# 从混合字符串中提取中文部分(如 "巴拿马Panama" → "巴拿马")
|
|
# 从混合字符串中提取中文部分(如 "巴拿马Panama" → "巴拿马")
|
|
|
- dest_cn = re.sub(r'[A-Za-z0-9\s]+', '', dest_raw).strip()
|
|
|
|
|
|
|
+ dest_cn = re.sub(r'[A-Za-z0-9\s\u200b-\u200d\ufeff]+', '', dest_raw).strip()
|
|
|
if not dest_cn:
|
|
if not dest_cn:
|
|
|
dest_cn = dest_raw
|
|
dest_cn = dest_raw
|
|
|
# 找到":"run 之后的第一个内容 run(非括号说明),替换国家名
|
|
# 找到":"run 之后的第一个内容 run(非括号说明),替换国家名
|
|
@@ -714,6 +806,45 @@ def generate_sales_contract(order: dict, vehicles: list, summary: dict,
|
|
|
return output_path
|
|
return output_path
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+
|
|
|
|
|
+from copy import deepcopy
|
|
|
|
|
+def _ensure_data_rows(table, needed_rows, summary_row_idx):
|
|
|
|
|
+ """Add extra data rows before the summary row if needed."""
|
|
|
|
|
+ from lxml import etree
|
|
|
|
|
+ ns = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
|
|
|
|
|
+ # Count existing data rows
|
|
|
|
|
+ data_rows = []
|
|
|
|
|
+ for idx, row in enumerate(table.rows):
|
|
|
|
|
+ if idx == 0 or idx == summary_row_idx:
|
|
|
|
|
+ continue
|
|
|
|
|
+ data_rows.append((idx, row))
|
|
|
|
|
+
|
|
|
|
|
+ existing = len(data_rows)
|
|
|
|
|
+ if existing >= needed_rows:
|
|
|
|
|
+ return existing
|
|
|
|
|
+
|
|
|
|
|
+ # Need to add (needed_rows - existing) rows
|
|
|
|
|
+ cells_count = len(table.rows[1].cells)
|
|
|
|
|
+ ref_row = table.rows[1]
|
|
|
|
|
+ ref_tr = ref_row._tr
|
|
|
|
|
+ tbl = table._tbl
|
|
|
|
|
+
|
|
|
|
|
+ # Find the summary row in the XML
|
|
|
|
|
+ summary_tr = table.rows[summary_row_idx]._tr
|
|
|
|
|
+
|
|
|
|
|
+ for _ in range(needed_rows - existing):
|
|
|
|
|
+ new_tr = deepcopy(ref_tr)
|
|
|
|
|
+ # Clear text content in cells
|
|
|
|
|
+ for tc in new_tr.findall(f"{{{ns}}}tc"):
|
|
|
|
|
+ for p in tc.findall(f"{{{ns}}}p"):
|
|
|
|
|
+ for r in p.findall(f"{{{ns}}}r"):
|
|
|
|
|
+ for t in r.findall(f"{{{ns}}}t"):
|
|
|
|
|
+ t.text = ""
|
|
|
|
|
+ # Insert before summary row
|
|
|
|
|
+ tbl.insert(list(tbl).index(summary_tr), new_tr)
|
|
|
|
|
+
|
|
|
|
|
+ return needed_rows
|
|
|
|
|
+
|
|
|
def _fill_vehicle_table(table, vehicles, summary, trade_term, port_cn, port_en):
|
|
def _fill_vehicle_table(table, vehicles, summary, trade_term, port_cn, port_en):
|
|
|
"""填充销售合同中的车辆明细表(保留格式 + 删除多余行)"""
|
|
"""填充销售合同中的车辆明细表(保留格式 + 删除多余行)"""
|
|
|
data_start_row = 1 # 表头后第1行开始数据
|
|
data_start_row = 1 # 表头后第1行开始数据
|
|
@@ -735,7 +866,23 @@ def _fill_vehicle_table(table, vehicles, summary, trade_term, port_cn, port_en):
|
|
|
break
|
|
break
|
|
|
available_data_rows.append(idx)
|
|
available_data_rows.append(idx)
|
|
|
|
|
|
|
|
- # 1. 填充车辆数据
|
|
|
|
|
|
|
+ # Ensure enough data rows for all vehicles
|
|
|
|
|
+ summary_row_idx = _ensure_data_rows(table, len(vehicles), summary_row_idx)
|
|
|
|
|
+ # Re-find summary row and recompute available rows after row inserts
|
|
|
|
|
+ for idx, row in enumerate(table.rows):
|
|
|
|
|
+ if idx == 0:
|
|
|
|
|
+ continue
|
|
|
|
|
+ row_text = ' '.join(cell.text for cell in row.cells)
|
|
|
|
|
+ if 'Total' in row_text or '总数量' in row_text or 'SAY USD' in row_text:
|
|
|
|
|
+ summary_row_idx = idx
|
|
|
|
|
+ break
|
|
|
|
|
+ available_data_rows = []
|
|
|
|
|
+ for idx in range(data_start_row, len(table.rows)):
|
|
|
|
|
+ if idx == summary_row_idx:
|
|
|
|
|
+ break
|
|
|
|
|
+ available_data_rows.append(idx)
|
|
|
|
|
+
|
|
|
|
|
+ # 1. Fill vehicle data
|
|
|
for i, v in enumerate(vehicles):
|
|
for i, v in enumerate(vehicles):
|
|
|
if i >= len(available_data_rows):
|
|
if i >= len(available_data_rows):
|
|
|
break
|
|
break
|
|
@@ -780,7 +927,8 @@ def _fill_vehicle_table(table, vehicles, summary, trade_term, port_cn, port_en):
|
|
|
|
|
|
|
|
|
|
|
|
|
def generate_contracts(order_text: str, output_dir: str = '.',
|
|
def generate_contracts(order_text: str, output_dir: str = '.',
|
|
|
- user_prices: dict = None) -> dict:
|
|
|
|
|
|
|
+ user_prices: dict = None,
|
|
|
|
|
+ quotation_path: str = None) -> dict:
|
|
|
"""
|
|
"""
|
|
|
主函数:根据订单文本生成合同
|
|
主函数:根据订单文本生成合同
|
|
|
|
|
|
|
@@ -788,6 +936,7 @@ def generate_contracts(order_text: str, output_dir: str = '.',
|
|
|
order_text: 订单信息文本
|
|
order_text: 订单信息文本
|
|
|
output_dir: 输出目录
|
|
output_dir: 输出目录
|
|
|
user_prices: 用户手动提供的价格 {model_code: price}
|
|
user_prices: 用户手动提供的价格 {model_code: price}
|
|
|
|
|
+ quotation_path: 报价表xlsx文件路径(可选)
|
|
|
|
|
|
|
|
Returns:
|
|
Returns:
|
|
|
{
|
|
{
|
|
@@ -814,7 +963,11 @@ def generate_contracts(order_text: str, output_dir: str = '.',
|
|
|
if v['model_code'] in user_prices:
|
|
if v['model_code'] in user_prices:
|
|
|
v['unit_price_usd'] = user_prices[v['model_code']]
|
|
v['unit_price_usd'] = user_prices[v['model_code']]
|
|
|
|
|
|
|
|
- processed, missing = process_vehicles(vehicles, trade_term)
|
|
|
|
|
|
|
+ quotation_data = None
|
|
|
|
|
+ if quotation_path:
|
|
|
|
|
+ quotation_data = load_quotation(quotation_path)
|
|
|
|
|
+
|
|
|
|
|
+ processed, missing = process_vehicles(vehicles, trade_term, quotation_data)
|
|
|
|
|
|
|
|
# 如果有缺失价格,返回提示信息
|
|
# 如果有缺失价格,返回提示信息
|
|
|
if missing:
|
|
if missing:
|
|
@@ -850,6 +1003,7 @@ if __name__ == '__main__':
|
|
|
parser.add_argument('order_file', help='Path to order info text file')
|
|
parser.add_argument('order_file', help='Path to order info text file')
|
|
|
parser.add_argument('-o', '--output', default='.', help='Output directory')
|
|
parser.add_argument('-o', '--output', default='.', help='Output directory')
|
|
|
parser.add_argument('-p', '--prices', help='JSON string of manual prices {"LZW1028SPY": 6176}')
|
|
parser.add_argument('-p', '--prices', help='JSON string of manual prices {"LZW1028SPY": 6176}')
|
|
|
|
|
+ parser.add_argument('-q', '--quotation', help='Path to quotation xlsx file')
|
|
|
|
|
|
|
|
args = parser.parse_args()
|
|
args = parser.parse_args()
|
|
|
|
|
|
|
@@ -861,7 +1015,7 @@ if __name__ == '__main__':
|
|
|
import json
|
|
import json
|
|
|
user_prices = json.loads(args.prices)
|
|
user_prices = json.loads(args.prices)
|
|
|
|
|
|
|
|
- result = generate_contracts(order_text, args.output, user_prices)
|
|
|
|
|
|
|
+ result = generate_contracts(order_text, args.output, user_prices, args.quotation)
|
|
|
|
|
|
|
|
import json as json_mod
|
|
import json as json_mod
|
|
|
sys.stdout.reconfigure(encoding='utf-8')
|
|
sys.stdout.reconfigure(encoding='utf-8')
|