diff --git a/backend/tests/test_contracts_api.py b/backend/tests/test_contracts_api.py index 9c2a762e..2fb84d6d 100644 --- a/backend/tests/test_contracts_api.py +++ b/backend/tests/test_contracts_api.py @@ -1,3 +1,4 @@ +import json import unittest from datetime import datetime, timedelta, timezone @@ -308,6 +309,29 @@ class StubContractGenerationService: def preview_contract(self, current_user, template_id, variable_values): if template_id == "tpl-missing": raise HTTPException(status_code=404, detail="template not found") + if "甲方名称" in variable_values: + return { + "preview_content": f"甲方:{variable_values.get('甲方名称', '')}\n乙方:{variable_values.get('乙方名称', '')}", + "template_name": "Property Service Template", + "variable_summary": [ + { + "name": "甲方名称", + "label": "甲方名称", + "required": True, + "provided": "甲方名称" in variable_values, + "value_preview": variable_values.get("甲方名称", ""), + "is_empty": not bool(variable_values.get("甲方名称", "").strip()), + }, + { + "name": "乙方名称", + "label": "乙方名称", + "required": True, + "provided": "乙方名称" in variable_values, + "value_preview": variable_values.get("乙方名称", ""), + "is_empty": not bool(variable_values.get("乙方名称", "").strip()), + }, + ], + } return { "preview_content": f"Contract with {variable_values.get('supplier_name', '')}", "template_name": "Procurement Template", @@ -441,13 +465,23 @@ class StubContractGenerationService: ): if template_id == "tpl-missing": raise HTTPException(status_code=404, detail="template not found") + if "甲方名称" in variable_values: + content = f"甲方:{variable_values.get('甲方名称', '')}\n乙方:{variable_values.get('乙方名称', '')}" + contract_type = "property_service" + document_type_code = document_type_code or "property_service" + template_name = "Property Service Template" + else: + content = f"Contract with {variable_values.get('supplier_name', '')}" + contract_type = "procurement" + document_type_code = document_type_code or "procurement_goods" + template_name = "Procurement Template" return { "id": "contract-123", "template_id": template_id, - "template_name": "Procurement Template", - "contract_type": "procurement", - "document_type_code": document_type_code or "procurement_goods", - "generated_content": f"Contract with {variable_values.get('supplier_name', '')}", + "template_name": template_name, + "contract_type": contract_type, + "document_type_code": document_type_code, + "generated_content": content, "generated_html": "
Contract
", "variable_values": variable_values, "elements_snapshot": elements_snapshot, @@ -740,6 +774,25 @@ class ContractsApiTestCase(unittest.TestCase): self.assertEqual([call[0] for call in self.tenant_service.calls], ["generation", "review"]) self.assertEqual(self.audit_log_service.operations[0]["action"], "contract_generation") + def test_user_can_generate_contract_with_chinese_variable_keys(self): + self._login() + payload = { + "甲方名称": "甲方物业公司", + "乙方名称": "乙方服务公司", + } + response = self.client.post( + "/api/v1/contracts/generate", + data={ + "template_id": "tpl-123", + "variable_values_json": json.dumps(payload, ensure_ascii=False), + "export_format": "docx", + }, + ) + self.assertEqual(response.status_code, 200) + body = response.json() + self.assertEqual(body["data"]["variable_values"]["甲方名称"], "甲方物业公司") + self.assertEqual(body["data"]["variable_values"]["乙方名称"], "乙方服务公司") + def test_user_can_generate_contract_from_elements_with_auto_template(self): self._login() response = self.client.post( @@ -779,6 +832,22 @@ class ContractsApiTestCase(unittest.TestCase): self.assertEqual(preview.status_code, 200) self.assertIn("Supplier A", preview.json()["data"]["preview_content"]) + chinese_preview = self.client.post( + "/api/v1/contracts/preview", + data={ + "template_id": "tpl-123", + "variable_values_json": json.dumps( + {"甲方名称": "甲方物业公司", "乙方名称": "乙方服务公司"}, + ensure_ascii=False, + ), + }, + ) + self.assertEqual(chinese_preview.status_code, 200) + self.assertEqual( + chinese_preview.json()["data"]["variable_summary"][0]["name"], + "甲方名称", + ) + download = self.client.get("/api/v1/contracts/contract-123/download") self.assertEqual(download.status_code, 200) self.assertEqual(download.content, b"pdf-bytes") @@ -1168,6 +1237,55 @@ class ContractGenerationServiceTestCase(unittest.TestCase): "contract_generation.template_render.v1", ) + def test_property_service_chinese_variable_template_supports_preview_and_generate(self): + now = datetime.now(timezone.utc) + template = self.ContractTemplate( + id="tpl-property-cn", + tenant_id="tenant-demo-001", + name="物业服务合同模板", + contract_type="property_service", + document_type_code="property_service", + description="Chinese variable template", + content="甲方:{{甲方名称}}\n乙方:{{乙方名称}}\n项目:{{项目/物业名称}}", + variables_json=[ + {"name": "甲方名称", "label": "甲方名称", "required": True}, + {"name": "乙方名称", "label": "乙方名称", "required": True}, + {"name": "项目/物业名称", "label": "项目/物业名称", "required": True}, + ], + enabled=True, + created_by="seed", + updated_by="seed", + created_at=now, + updated_at=now, + ) + self.db.add(template) + self.db.commit() + + variable_values = { + "甲方名称": "甲方物业公司", + "乙方名称": "乙方服务公司", + "项目/物业名称": "测试小区", + } + + preview = self.service.preview_contract( + current_user=self.current_user, + template_id="tpl-property-cn", + variable_values=variable_values, + ) + self.assertIn("甲方物业公司", preview["preview_content"]) + self.assertEqual(preview["variable_summary"][0]["name"], "甲方名称") + self.assertTrue(preview["variable_summary"][0]["provided"]) + + result = self.service.generate_contract( + current_user=self.current_user, + template_id="tpl-property-cn", + variable_values=variable_values, + ) + self.assertEqual(result["template_id"], "tpl-property-cn") + self.assertEqual(result["document_type_code"], "property_service") + self.assertEqual(result["variable_values"]["甲方名称"], "甲方物业公司") + self.assertIn("测试小区", result["generated_content"]) + def test_generate_contract_from_elements_auto_selects_template_and_returns_law_doc_004_payload(self): self._seed_law_doc_004_data() result = self.service.generate_contract_from_elements( diff --git a/scripts/verify-chinese-contract-template-utf8.ps1 b/scripts/verify-chinese-contract-template-utf8.ps1 new file mode 100644 index 00000000..9acb183d --- /dev/null +++ b/scripts/verify-chinese-contract-template-utf8.ps1 @@ -0,0 +1,15 @@ +param( + [string]$ApiBaseUrl = "http://192.168.3.90:8001/api/v1", + [string]$Account = "cyuqei@gmail.com", + [string]$Password = "816301", + [string]$TemplateId = "tpl-006aea61de80" +) + +$ErrorActionPreference = "Stop" + +$scriptPath = Join-Path $PSScriptRoot "verify_chinese_contract_template_utf8.py" +if (-not (Test-Path $scriptPath)) { + throw "Python verifier not found at $scriptPath" +} + +python $scriptPath $ApiBaseUrl $Account $Password $TemplateId diff --git a/scripts/verify_chinese_contract_template_utf8.py b/scripts/verify_chinese_contract_template_utf8.py new file mode 100644 index 00000000..ff04e1a7 --- /dev/null +++ b/scripts/verify_chinese_contract_template_utf8.py @@ -0,0 +1,79 @@ +import json +import sys + +import requests + + +def main() -> int: + api_base = sys.argv[1].rstrip("/") + account = sys.argv[2] + password = sys.argv[3] + template_id = sys.argv[4] + + payload = { + "甲方名称": "甲方物业公司", + "乙方名称": "乙方服务公司", + "项目/物业名称": "测试小区", + "物业坐落": "北京市朝阳区测试路1号", + "物业类型": "住宅", + "服务区域/范围": "全区物业服务", + "服务内容和标准": "保洁安保维修", + "服务期限": "2026年1月1日至2026年12月31日", + "物业服务费标准": "每月10万元", + "费用计收方式": "按月支付", + "付款周期/缴费期限": "每月5日前支付", + "共用部位/共用设施设备维护": "乙方负责日常维护", + "维修资金/专项维修安排": "按维修基金制度执行", + "公共秩序和安全协助事项": "安保巡逻与秩序维护", + "环境卫生与绿化维护": "保洁与绿化养护", + "业主/使用人义务": "遵守物业管理制度", + "业主委员会/授权依据": "业委会授权决议", + "交接验收与资料移交": "完工交接清单", + "违约责任": "违约方承担违约责任", + "争议解决方式": "北京仲裁委员会仲裁", + "签署日期": "2026-05-27", + } + + login = requests.post( + f"{api_base}/auth/login", + json={"account": account, "password": password}, + timeout=30, + ) + login.raise_for_status() + token = login.json()["data"]["access_token"] + headers = {"Authorization": f"Bearer {token}"} + + form = { + "template_id": template_id, + "variable_values_json": json.dumps(payload, ensure_ascii=False), + "export_format": "txt", + } + + preview = requests.post( + f"{api_base}/contracts/preview", + headers={**headers, "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"}, + data=form, + timeout=60, + ) + preview.raise_for_status() + + generate = requests.post( + f"{api_base}/contracts/generate", + headers={**headers, "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"}, + data=form, + timeout=180, + ) + generate.raise_for_status() + + result = { + "preview_status": preview.status_code, + "preview_body": preview.json(), + "generate_status": generate.status_code, + "generate_body": generate.json(), + } + print(json.dumps(result, ensure_ascii=False, indent=2)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())