1446 lines
78 KiB
Python
1446 lines
78 KiB
Python
import json
|
|
import re
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
from typing import Any, Dict, Optional
|
|
from uuid import uuid4
|
|
|
|
from fastapi import HTTPException, status
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.models.contract_template import ContractTemplate
|
|
from app.models.contract_template_version import ContractTemplateVersion
|
|
from app.repositories.governance_asset_repository import GovernanceAssetRepository
|
|
from app.repositories.contract_template_repository import ContractTemplateRepository
|
|
from app.repositories.contract_template_version_repository import ContractTemplateVersionRepository
|
|
|
|
|
|
TEMPLATE_LIBRARY_ROOT = Path(__file__).resolve().parents[3] / "project-docs" / "phase13-legal-knowledge-template-library" / "contract-templates"
|
|
|
|
|
|
def _extract_contract_body(markdown_text: str) -> str:
|
|
lines = markdown_text.splitlines()
|
|
try:
|
|
start = lines.index("## 合同正文") + 1
|
|
except ValueError:
|
|
start = 0
|
|
try:
|
|
end = lines.index("## 使用边界")
|
|
except ValueError:
|
|
end = len(lines)
|
|
return "\n".join(lines[start:end]).strip()
|
|
|
|
|
|
def _load_phase13_template_asset(
|
|
folder_name: str,
|
|
template_code: str,
|
|
*,
|
|
name: str,
|
|
contract_type: str,
|
|
document_type_code: str,
|
|
template_category: str,
|
|
industry_category: str,
|
|
description: str,
|
|
) -> dict[str, Any]:
|
|
asset_dir = TEMPLATE_LIBRARY_ROOT / folder_name
|
|
markdown = (asset_dir / f"{template_code}.md").read_text(encoding="utf-8-sig")
|
|
variables_data = json.loads((asset_dir / f"{template_code}-variables.json").read_text(encoding="utf-8-sig"))
|
|
required_variables = variables_data.get("required_variables", [])
|
|
optional_variables = variables_data.get("optional_variables", [])
|
|
variables = [
|
|
{"name": variable_name, "label": variable_name, "required": True}
|
|
for variable_name in required_variables
|
|
] + [
|
|
{"name": variable_name, "label": variable_name, "required": False}
|
|
for variable_name in optional_variables
|
|
]
|
|
return {
|
|
"name": name,
|
|
"contract_type": contract_type,
|
|
"document_type_code": document_type_code,
|
|
"template_category": template_category,
|
|
"industry_category": industry_category,
|
|
"description": description,
|
|
"content": _extract_contract_body(markdown),
|
|
"variables": variables,
|
|
}
|
|
|
|
|
|
DEFAULT_TEMPLATES: list[dict[str, Any]] = [
|
|
{
|
|
"name": "劳动合同标准模板",
|
|
"contract_type": "labor",
|
|
"document_type_code": "labor_contract",
|
|
"template_category": "employment-hr",
|
|
"industry_category": "general-enterprise",
|
|
"description": "适用于企业与员工签署的标准劳动合同。",
|
|
"content": (
|
|
"甲方(用人单位):{{party_a}}\n"
|
|
"乙方(员工):{{party_b}}\n\n"
|
|
"一、合同期限\n"
|
|
"本合同自 {{start_date}} 起至 {{end_date}} 止。\n\n"
|
|
"二、工作岗位\n"
|
|
"乙方在甲方安排的 {{job_title}} 岗位工作,工作地点为 {{work_location}}。\n\n"
|
|
"三、劳动报酬\n"
|
|
"甲方每月向乙方支付税前工资人民币 {{salary}} 元,发薪日为每月 {{payday}} 日。\n\n"
|
|
"四、签署日期\n"
|
|
"{{sign_date}}"
|
|
),
|
|
"variables": [
|
|
{"name": "party_a", "label": "甲方名称"},
|
|
{"name": "party_b", "label": "乙方姓名"},
|
|
{"name": "start_date", "label": "开始日期"},
|
|
{"name": "end_date", "label": "结束日期"},
|
|
{"name": "job_title", "label": "岗位名称"},
|
|
{"name": "work_location", "label": "工作地点"},
|
|
{"name": "salary", "label": "月工资"},
|
|
{"name": "payday", "label": "发薪日"},
|
|
{"name": "sign_date", "label": "签署日期"},
|
|
],
|
|
},
|
|
{
|
|
"name": "货物采购合同模板",
|
|
"contract_type": "procurement",
|
|
"document_type_code": "procurement_goods",
|
|
"template_category": "procurement-supply",
|
|
"industry_category": "general-enterprise",
|
|
"description": "适用于普通 B2B 货物采购、交付、验收、发票付款、质保售后和违约处理场景。",
|
|
"content": (
|
|
"甲方(采购方):{{buyer}}\n"
|
|
"乙方(供应方):{{supplier}}\n\n"
|
|
"一、采购标的与附件\n"
|
|
"产品名称:{{product_name}}\n"
|
|
"规格型号:{{specification}}\n"
|
|
"采购数量:{{quantity}} {{unit}}\n"
|
|
"单价:{{unit_price}}\n"
|
|
"合同价款:{{amount}}\n"
|
|
"质量标准:{{quality_standard}}\n"
|
|
"订单/附件编号:{{order_attachment_no}}\n\n"
|
|
"二、包装、运输、交付与风险转移\n"
|
|
"包装及运输要求:{{packaging_transport}}\n"
|
|
"交付期限:{{delivery_deadline}}\n"
|
|
"交付地点:{{delivery_location}}\n"
|
|
"风险转移节点:{{risk_transfer}}\n"
|
|
"随附文件:{{delivery_documents}}\n"
|
|
"分批交付安排:{{batch_delivery}}\n\n"
|
|
"三、验收与质量异议\n"
|
|
"验收标准:{{acceptance_standard}}\n"
|
|
"验收期限:{{acceptance_period}}\n\n"
|
|
"四、发票、收款账户与付款\n"
|
|
"发票类型:{{invoice_type}}\n"
|
|
"付款安排:{{payment_terms}}\n"
|
|
"付款前置条件:{{payment_conditions}}\n"
|
|
"收款账户:{{payee_account}}\n\n"
|
|
"五、质量保证与售后\n"
|
|
"质量保证期:{{warranty_period}}\n"
|
|
"售后响应要求:{{after_sales_response}}\n\n"
|
|
"六、违约、解除与通知\n"
|
|
"违约责任:{{breach_liability}}\n"
|
|
"违约金计算口径:{{liquidated_damages_formula}}\n"
|
|
"责任上限:{{liability_cap}}\n"
|
|
"解除触发条件:{{rescission_trigger}}\n"
|
|
"通知方式及送达地址:{{notice_address}}\n\n"
|
|
"七、争议解决与电子签署\n"
|
|
"争议解决方式:{{dispute_resolution}}\n"
|
|
"电子签署平台:{{e_signature_platform}}\n"
|
|
"特殊监管说明:{{regulatory_note}}\n"
|
|
"签署日期:{{sign_date}}"
|
|
),
|
|
"variables": [
|
|
{"name": "buyer", "label": "采购方名称"},
|
|
{"name": "supplier", "label": "供应方名称"},
|
|
{"name": "product_name", "label": "产品名称"},
|
|
{"name": "specification", "label": "规格型号"},
|
|
{"name": "quantity", "label": "采购数量"},
|
|
{"name": "unit", "label": "计量单位"},
|
|
{"name": "unit_price", "label": "单价"},
|
|
{"name": "amount", "label": "合同金额"},
|
|
{"name": "quality_standard", "label": "质量标准"},
|
|
{"name": "packaging_transport", "label": "包装及运输要求"},
|
|
{"name": "delivery_deadline", "label": "交付期限"},
|
|
{"name": "delivery_location", "label": "交付地点"},
|
|
{"name": "risk_transfer", "label": "风险转移节点"},
|
|
{"name": "delivery_documents", "label": "随附文件清单"},
|
|
{"name": "acceptance_standard", "label": "验收标准"},
|
|
{"name": "acceptance_period", "label": "验收期限"},
|
|
{"name": "invoice_type", "label": "发票类型"},
|
|
{"name": "payment_terms", "label": "付款安排"},
|
|
{"name": "payee_account", "label": "收款账户"},
|
|
{"name": "warranty_period", "label": "质量保证期"},
|
|
{"name": "after_sales_response", "label": "售后响应要求"},
|
|
{"name": "breach_liability", "label": "违约责任"},
|
|
{"name": "dispute_resolution", "label": "争议解决方式"},
|
|
{"name": "sign_date", "label": "签署日期"},
|
|
{"name": "order_attachment_no", "label": "订单/附件编号", "required": False},
|
|
{"name": "batch_delivery", "label": "分批交付安排", "required": False},
|
|
{"name": "payment_conditions", "label": "付款前置条件", "required": False},
|
|
{"name": "liquidated_damages_formula", "label": "违约金计算口径", "required": False},
|
|
{"name": "liability_cap", "label": "责任上限", "required": False},
|
|
{"name": "rescission_trigger", "label": "解除触发条件", "required": False},
|
|
{"name": "notice_address", "label": "通知方式及送达地址", "required": False},
|
|
{"name": "e_signature_platform", "label": "电子签署平台", "required": False},
|
|
{"name": "regulatory_note", "label": "特殊监管说明", "required": False},
|
|
],
|
|
},
|
|
{
|
|
"name": "通用服务合同模板",
|
|
"contract_type": "service",
|
|
"document_type_code": "service_general",
|
|
"template_category": "service-delivery",
|
|
"industry_category": "professional-services",
|
|
"description": "适用于普通 B2B 咨询、运营、外包、执行支持等通用服务交付场景;涉及个人信息、重要数据或强监管服务时需专项复核。",
|
|
"content": (
|
|
"甲方(委托方/服务接受方):{{buyer}}\n"
|
|
"乙方(服务方):{{supplier}}\n\n"
|
|
"一、服务名称、范围与排除事项\n"
|
|
"服务名称:{{service_name}}\n"
|
|
"服务范围:{{service_scope}}\n"
|
|
"交付成果:{{deliverables}}\n"
|
|
"不包含事项:{{service_exclusions}}\n\n"
|
|
"二、服务期限、人员安排与服务级别\n"
|
|
"服务期限:{{service_period}}\n"
|
|
"服务级别:{{service_level}}\n"
|
|
"响应时效:{{service_response_time}}\n\n"
|
|
"三、费用、结算与发票\n"
|
|
"服务费用:{{service_fee}}\n"
|
|
"付款安排:{{payment_terms}}\n"
|
|
"发票类型:{{invoice_type}}\n"
|
|
"付款前置条件:{{payment_conditions}}\n\n"
|
|
"四、验收、整改与变更\n"
|
|
"验收标准:{{acceptance_standard}}\n"
|
|
"验收期限:{{acceptance_period}}\n"
|
|
"服务不符合约定的,应在合理期限内整改;整改后仍不符合要求的,甲方可要求减免费用、继续履行、赔偿损失或解除合同。\n\n"
|
|
"五、数据、保密与合规\n"
|
|
"数据接触范围:{{data_scope}}\n"
|
|
"保密范围:{{confidentiality_scope}}\n"
|
|
"服务期间如涉及个人信息、重要数据或受监管数据的,应另行完成数据合规专项复核。\n\n"
|
|
"六、违约责任、责任限制与解除\n"
|
|
"违约责任:{{breach_liability}}\n"
|
|
"违约金计算口径:{{liquidated_damages_formula}}\n"
|
|
"责任上限:{{liability_cap}}\n"
|
|
"解除触发条件:{{rescission_trigger}}\n"
|
|
"通知方式及送达地址:{{notice_address}}\n\n"
|
|
"七、争议解决、电子签署与特殊监管\n"
|
|
"争议解决方式:{{dispute_resolution}}\n"
|
|
"电子签署平台:{{e_signature_platform}}\n"
|
|
"特殊监管说明:{{regulatory_note}}\n"
|
|
"签署日期:{{sign_date}}"
|
|
),
|
|
"variables": [
|
|
{"name": "buyer", "label": "甲方名称"},
|
|
{"name": "supplier", "label": "乙方名称"},
|
|
{"name": "service_name", "label": "服务名称"},
|
|
{"name": "service_scope", "label": "服务范围"},
|
|
{"name": "deliverables", "label": "交付成果"},
|
|
{"name": "service_exclusions", "label": "不包含事项", "required": False},
|
|
{"name": "service_period", "label": "服务期限"},
|
|
{"name": "service_level", "label": "服务级别", "required": False},
|
|
{"name": "service_response_time", "label": "响应时效", "required": False},
|
|
{"name": "service_fee", "label": "服务费用"},
|
|
{"name": "payment_terms", "label": "付款安排"},
|
|
{"name": "invoice_type", "label": "发票类型", "required": False},
|
|
{"name": "payment_conditions", "label": "付款前置条件", "required": False},
|
|
{"name": "acceptance_standard", "label": "验收标准"},
|
|
{"name": "acceptance_period", "label": "验收期限", "required": False},
|
|
{"name": "data_scope", "label": "数据接触范围"},
|
|
{"name": "confidentiality_scope", "label": "保密范围"},
|
|
{"name": "breach_liability", "label": "违约责任"},
|
|
{"name": "liquidated_damages_formula", "label": "违约金计算口径", "required": False},
|
|
{"name": "liability_cap", "label": "责任上限", "required": False},
|
|
{"name": "rescission_trigger", "label": "解除触发条件", "required": False},
|
|
{"name": "notice_address", "label": "通知方式及送达地址", "required": False},
|
|
{"name": "dispute_resolution", "label": "争议解决方式"},
|
|
{"name": "e_signature_platform", "label": "电子签署平台", "required": False},
|
|
{"name": "regulatory_note", "label": "特殊监管说明", "required": False},
|
|
{"name": "sign_date", "label": "签署日期"},
|
|
],
|
|
},
|
|
{
|
|
"name": "保密协议标准模板",
|
|
"contract_type": "nda",
|
|
"document_type_code": "nda_commercial",
|
|
"template_category": "commercial-compliance",
|
|
"industry_category": "general-enterprise",
|
|
"description": "适用于商务合作、尽调和方案评估场景的保密协议。",
|
|
"content": (
|
|
"甲方(披露方):{{disclosing_party}}\n"
|
|
"乙方(接收方):{{receiving_party}}\n\n"
|
|
"一、合作事项\n"
|
|
"合作事项:{{cooperation_matter}}\n\n"
|
|
"二、保密信息范围与披露对象\n"
|
|
"保密信息范围:{{confidential_scope}}\n"
|
|
"允许披露对象:{{permitted_disclosure}}\n\n"
|
|
"三、使用目的和例外信息\n"
|
|
"使用目的:{{use_purpose}}\n"
|
|
"例外信息:{{exceptions}}\n\n"
|
|
"四、保密期限\n"
|
|
"保密期限:{{confidential_term}}\n\n"
|
|
"五、保密措施\n"
|
|
"{{protection_measures}}\n\n"
|
|
"六、返还、销毁和副本管理\n"
|
|
"{{return_or_destroy}}\n\n"
|
|
"七、知识产权及衍生资料\n"
|
|
"{{ip_and_derivative_rights}}\n\n"
|
|
"八、违约责任\n"
|
|
"{{liability}}\n\n"
|
|
"九、争议解决和电子签署\n"
|
|
"争议解决方式:{{dispute_forum}}\n"
|
|
"电子签署平台:{{e_signature_platform}}\n\n"
|
|
"十、签署日期\n"
|
|
"{{sign_date}}"
|
|
),
|
|
"variables": [
|
|
{"name": "disclosing_party", "label": "披露方"},
|
|
{"name": "receiving_party", "label": "接收方"},
|
|
{"name": "cooperation_matter", "label": "合作事项"},
|
|
{"name": "confidential_scope", "label": "保密范围"},
|
|
{"name": "permitted_disclosure", "label": "允许披露对象"},
|
|
{"name": "use_purpose", "label": "使用目的"},
|
|
{"name": "exceptions", "label": "例外信息"},
|
|
{"name": "confidential_term", "label": "保密期限"},
|
|
{"name": "protection_measures", "label": "保密措施"},
|
|
{"name": "return_or_destroy", "label": "返还或销毁方式"},
|
|
{"name": "ip_and_derivative_rights", "label": "知识产权及衍生资料归属"},
|
|
{"name": "liability", "label": "违约责任"},
|
|
{"name": "dispute_forum", "label": "争议解决方式"},
|
|
{"name": "e_signature_platform", "label": "电子签署平台"},
|
|
{"name": "sign_date", "label": "签署日期"},
|
|
],
|
|
},
|
|
{
|
|
"name": "投资协议模板",
|
|
"contract_type": "investment_agreement",
|
|
"document_type_code": "investment_agreement",
|
|
"template_category": "investment-finance",
|
|
"industry_category": "general-enterprise",
|
|
"description": "适用于股权投资、增资扩股、交割安排、治理权利、信息披露和退出机制场景。",
|
|
"content": (
|
|
"投资方:{{investor}}\n"
|
|
"目标公司:{{target_company}}\n"
|
|
"创始股东/实际控制人:{{controller}}\n"
|
|
"基础交易文件:{{base_agreement}}\n\n"
|
|
"一、投资安排\n"
|
|
"投资金额:{{investment_amount}}\n"
|
|
"投前/投后估值依据:{{valuation_basis}}\n"
|
|
"对应股权比例:{{equity_ratio}}\n"
|
|
"资金用途:{{use_of_funds}}\n\n"
|
|
"二、公司治理与股东权利\n"
|
|
"董事席位/观察员安排:{{board_rights}}\n"
|
|
"信息披露与检查权:{{information_rights}}\n"
|
|
"重大事项表决机制:{{veto_rights}}\n\n"
|
|
"三、交割与先决条件\n"
|
|
"交割先决条件:{{closing_conditions}}\n"
|
|
"公司及创始股东陈述与保证:{{representations_warranties}}\n"
|
|
"监管/审批事项:{{regulatory_note}}\n\n"
|
|
"四、股权转让与退出机制\n"
|
|
"股权转让限制:{{transfer_restrictions}}\n"
|
|
"退出安排:{{exit_mechanism}}\n\n"
|
|
"五、争议解决与签署\n"
|
|
"争议解决方式:{{dispute_resolution}}\n"
|
|
"电子签署平台:{{e_signature_platform}}\n"
|
|
"签署日期:{{sign_date}}"
|
|
),
|
|
"variables": [
|
|
{"name": "investor", "label": "投资方名称"},
|
|
{"name": "target_company", "label": "目标公司名称"},
|
|
{"name": "controller", "label": "创始股东/实际控制人"},
|
|
{"name": "base_agreement", "label": "基础交易文件"},
|
|
{"name": "investment_amount", "label": "投资金额"},
|
|
{"name": "valuation_basis", "label": "投前/投后估值依据"},
|
|
{"name": "equity_ratio", "label": "对应股权比例"},
|
|
{"name": "use_of_funds", "label": "资金用途"},
|
|
{"name": "board_rights", "label": "董事席位/观察员安排"},
|
|
{"name": "information_rights", "label": "信息披露与检查权"},
|
|
{"name": "veto_rights", "label": "重大事项表决机制", "required": False},
|
|
{"name": "closing_conditions", "label": "交割先决条件"},
|
|
{"name": "representations_warranties", "label": "陈述与保证"},
|
|
{"name": "regulatory_note", "label": "监管/审批事项", "required": False},
|
|
{"name": "transfer_restrictions", "label": "股权转让限制"},
|
|
{"name": "exit_mechanism", "label": "退出安排"},
|
|
{"name": "dispute_resolution", "label": "争议解决方式"},
|
|
{"name": "e_signature_platform", "label": "电子签署平台", "required": False},
|
|
{"name": "sign_date", "label": "签署日期"},
|
|
],
|
|
},
|
|
{
|
|
"name": "企业间借款合同模板",
|
|
"contract_type": "intercompany-loan",
|
|
"document_type_code": "intercompany_loan_agreement",
|
|
"template_category": "investment-finance",
|
|
"industry_category": "general-enterprise",
|
|
"description": "适用于企业间借款、关联交易、利率、还款、担保和资金合规场景。",
|
|
"content": (
|
|
"甲方(出借方):{{lender}}\n"
|
|
"乙方(借款方):{{borrower}}\n"
|
|
"关联关系说明:{{relationship_note}}\n"
|
|
"内部审批确认:{{internal_approval}}\n\n"
|
|
"一、借款金额与用途\n"
|
|
"借款金额:{{loan_amount}}\n"
|
|
"借款用途:{{loan_purpose}}\n"
|
|
"资金划付账户:{{disbursement_account}}\n"
|
|
"资金到账确认:{{receipt_confirmation}}\n\n"
|
|
"二、借款期限与利息\n"
|
|
"借款期限:{{loan_term}}\n"
|
|
"起息日:{{interest_start_date}}\n"
|
|
"年化利率/计息方式:{{interest_rate_method}}\n"
|
|
"利息支付方式:{{interest_payment_method}}\n\n"
|
|
"三、还款与担保\n"
|
|
"还款方式:{{repayment_method}}\n"
|
|
"还款计划:{{repayment_plan}}\n"
|
|
"还款账户:{{repayment_account}}\n"
|
|
"担保方式:{{guarantee_method}}\n"
|
|
"担保文件:{{guarantee_documents}}\n\n"
|
|
"四、合规、违约与争议解决\n"
|
|
"资金用途限制说明:{{fund_usage_restriction}}\n"
|
|
"违约责任:{{breach_liability}}\n"
|
|
"争议解决方式:{{dispute_resolution}}\n"
|
|
"电子签署平台:{{e_signature_platform}}\n"
|
|
"签署日期:{{sign_date}}"
|
|
),
|
|
"variables": [
|
|
{"name": "lender", "label": "甲方名称"},
|
|
{"name": "borrower", "label": "乙方名称"},
|
|
{"name": "relationship_note", "label": "关联关系说明"},
|
|
{"name": "internal_approval", "label": "内部审批确认"},
|
|
{"name": "loan_amount", "label": "借款金额"},
|
|
{"name": "loan_purpose", "label": "借款用途"},
|
|
{"name": "disbursement_account", "label": "资金划付账户"},
|
|
{"name": "receipt_confirmation", "label": "资金到账确认"},
|
|
{"name": "loan_term", "label": "借款期限"},
|
|
{"name": "interest_start_date", "label": "起息日"},
|
|
{"name": "interest_rate_method", "label": "年化利率/计息方式"},
|
|
{"name": "interest_payment_method", "label": "利息支付方式"},
|
|
{"name": "repayment_method", "label": "还款方式"},
|
|
{"name": "repayment_plan", "label": "还款计划"},
|
|
{"name": "repayment_account", "label": "还款账户"},
|
|
{"name": "guarantee_method", "label": "担保方式"},
|
|
{"name": "guarantee_documents", "label": "担保文件", "required": False},
|
|
{"name": "fund_usage_restriction", "label": "资金用途限制说明", "required": False},
|
|
{"name": "breach_liability", "label": "违约责任", "required": False},
|
|
{"name": "dispute_resolution", "label": "争议解决方式"},
|
|
{"name": "e_signature_platform", "label": "电子签署平台", "required": False},
|
|
{"name": "sign_date", "label": "签署日期"},
|
|
],
|
|
},
|
|
{
|
|
"name": "对赌协议模板",
|
|
"contract_type": "valuation-adjustment",
|
|
"document_type_code": "valuation_adjustment_agreement",
|
|
"template_category": "investment-finance",
|
|
"industry_category": "general-enterprise",
|
|
"description": "适用于投融资中的业绩承诺、估值调整、补偿和回购安排场景。",
|
|
"content": (
|
|
"投资方:{{investor}}\n"
|
|
"目标公司:{{target_company}}\n"
|
|
"原股东/实际控制人:{{controller}}\n"
|
|
"基础投资协议名称:{{base_agreement}}\n\n"
|
|
"一、交易背景与估值调整目标\n"
|
|
"估值调整目标:{{valuation_adjustment_target}}\n"
|
|
"投资金额:{{investment_amount}}\n"
|
|
"投前/投后估值:{{valuation_before_after}}\n\n"
|
|
"二、业绩承诺与考核期间\n"
|
|
"考核期间:{{assessment_period}}\n"
|
|
"业绩承诺指标:{{performance_metric}}\n"
|
|
"指标确认方式:{{metric_confirmation_method}}\n"
|
|
"审计机构:{{audit_org}}\n\n"
|
|
"三、补偿与回购安排\n"
|
|
"补偿触发条件:{{compensation_trigger}}\n"
|
|
"补偿方式:{{compensation_method}}\n"
|
|
"补偿计算公式:{{compensation_formula}}\n"
|
|
"补偿履行期限:{{compensation_deadline}}\n"
|
|
"补偿上限/下限:{{compensation_cap_floor}}\n"
|
|
"回购安排:{{repurchase_arrangement}}\n\n"
|
|
"四、公司履行限制与合规提示\n"
|
|
"公司履行限制说明:{{company_performance_restriction}}\n"
|
|
"担保/增信安排:{{guarantee_enhancement}}\n"
|
|
"外资/国资/反垄断说明:{{regulatory_note}}\n\n"
|
|
"五、信息披露与审计权\n"
|
|
"信息披露与审计权:{{disclosure_audit_right}}\n"
|
|
"争议解决方式:{{dispute_resolution}}\n"
|
|
"电子签署平台:{{e_signature_platform}}\n"
|
|
"签署日期:{{sign_date}}"
|
|
),
|
|
"variables": [
|
|
{"name": "investor", "label": "投资方名称"},
|
|
{"name": "target_company", "label": "目标公司名称"},
|
|
{"name": "controller", "label": "原股东/实际控制人名称"},
|
|
{"name": "base_agreement", "label": "基础投资协议名称"},
|
|
{"name": "valuation_adjustment_target", "label": "估值调整目标"},
|
|
{"name": "investment_amount", "label": "投资金额", "required": False},
|
|
{"name": "valuation_before_after", "label": "投前/投后估值", "required": False},
|
|
{"name": "assessment_period", "label": "考核期间"},
|
|
{"name": "performance_metric", "label": "业绩承诺指标"},
|
|
{"name": "metric_confirmation_method", "label": "指标确认方式"},
|
|
{"name": "audit_org", "label": "审计机构", "required": False},
|
|
{"name": "compensation_trigger", "label": "补偿触发条件"},
|
|
{"name": "compensation_method", "label": "补偿方式"},
|
|
{"name": "compensation_formula", "label": "补偿计算公式"},
|
|
{"name": "compensation_deadline", "label": "补偿履行期限"},
|
|
{"name": "compensation_cap_floor", "label": "补偿上限/下限", "required": False},
|
|
{"name": "repurchase_arrangement", "label": "回购安排", "required": False},
|
|
{"name": "company_performance_restriction", "label": "公司履行限制说明"},
|
|
{"name": "guarantee_enhancement", "label": "担保/增信安排", "required": False},
|
|
{"name": "regulatory_note", "label": "外资/国资/反垄断说明", "required": False},
|
|
{"name": "disclosure_audit_right", "label": "信息披露与审计权"},
|
|
{"name": "dispute_resolution", "label": "争议解决方式"},
|
|
{"name": "e_signature_platform", "label": "电子签署平台", "required": False},
|
|
{"name": "sign_date", "label": "签署日期"},
|
|
],
|
|
},
|
|
{
|
|
"name": "股东会决议模板",
|
|
"contract_type": "shareholder-resolution",
|
|
"document_type_code": "shareholder_resolution",
|
|
"template_category": "corporate-governance",
|
|
"industry_category": "general-enterprise",
|
|
"description": "适用于股东会/股东大会决议、授权办理和登记备案场景。",
|
|
"content": (
|
|
"公司名称:{{company_name}}\n"
|
|
"统一社会信用代码:{{company_uscc}}\n"
|
|
"会议类型:{{meeting_type}}\n"
|
|
"会议届次/决议编号:{{meeting_no}}\n"
|
|
"会议时间:{{meeting_time}}\n"
|
|
"会议地点/表决方式:{{meeting_place_or_method}}\n"
|
|
"召集人:{{meeting_convener}}\n"
|
|
"主持人:{{meeting_host}}\n\n"
|
|
"一、通知与出席情况\n"
|
|
"通知发送日期及方式:{{notice_delivery}}\n"
|
|
"应到股东及表决权比例:{{attendance_expected_ratio}}\n"
|
|
"实到/参与表决股东及表决权比例:{{attendance_actual_ratio}}\n"
|
|
"股东名单附件:{{attendance_shareholder_list}}\n\n"
|
|
"二、审议议案与表决结果\n"
|
|
"审议议案:{{proposal_items}}\n"
|
|
"表决结果:{{vote_result}}\n"
|
|
"决议事项:{{resolution_items}}\n"
|
|
"董事/监事/高管任免名单:{{management_appointments}}\n"
|
|
"注册资本/经营范围/住所变更事项:{{registration_change_items}}\n"
|
|
"异议股东意见:{{dissenting_opinion}}\n\n"
|
|
"三、授权办理与材料留存\n"
|
|
"授权办理事项:{{authorization_items}}\n"
|
|
"授权代表/经办人:{{authorization_representative}}\n"
|
|
"工商变更材料清单:{{filing_materials}}\n"
|
|
"文件留存清单:{{evidence_retention_list}}\n"
|
|
"公证/见证安排:{{notary_arrangement}}\n"
|
|
"电子签署平台:{{e_signature_platform}}\n"
|
|
"签署日期:{{sign_date}}"
|
|
),
|
|
"variables": [
|
|
{"name": "company_name", "label": "公司名称"},
|
|
{"name": "company_uscc", "label": "统一社会信用代码"},
|
|
{"name": "meeting_type", "label": "会议类型"},
|
|
{"name": "meeting_no", "label": "会议届次/决议编号"},
|
|
{"name": "meeting_time", "label": "会议时间"},
|
|
{"name": "meeting_place_or_method", "label": "会议地点/表决方式"},
|
|
{"name": "meeting_convener", "label": "召集人"},
|
|
{"name": "meeting_host", "label": "主持人"},
|
|
{"name": "notice_delivery", "label": "通知发送日期及方式"},
|
|
{"name": "attendance_expected_ratio", "label": "应到股东及表决权比例"},
|
|
{"name": "attendance_actual_ratio", "label": "实到/参与表决股东及表决权比例"},
|
|
{"name": "proposal_items", "label": "审议议案"},
|
|
{"name": "vote_result", "label": "表决结果"},
|
|
{"name": "resolution_items", "label": "决议事项"},
|
|
{"name": "authorization_items", "label": "授权办理事项"},
|
|
{"name": "authorization_representative", "label": "授权代表/经办人"},
|
|
{"name": "evidence_retention_list", "label": "文件留存清单"},
|
|
{"name": "sign_date", "label": "签署日期"},
|
|
{"name": "attendance_shareholder_list", "label": "股东名单附件", "required": False},
|
|
{"name": "management_appointments", "label": "董事/监事/高管任免名单", "required": False},
|
|
{"name": "registration_change_items", "label": "注册资本/经营范围/住所变更事项", "required": False},
|
|
{"name": "dissenting_opinion", "label": "异议股东意见", "required": False},
|
|
{"name": "filing_materials", "label": "工商变更材料清单", "required": False},
|
|
{"name": "notary_arrangement", "label": "公证/见证安排", "required": False},
|
|
{"name": "e_signature_platform", "label": "电子签署平台", "required": False},
|
|
],
|
|
},
|
|
{
|
|
"name": "技术服务合同模板",
|
|
"contract_type": "technical_service",
|
|
"document_type_code": "technical_service",
|
|
"template_category": "service-delivery",
|
|
"industry_category": "technology",
|
|
"description": "适用于技术支持、系统实施、接口调试、运维保障和成果交付场景。",
|
|
"content": (
|
|
"# 技术服务合同\n\n"
|
|
"甲方:{{甲方名称}}\n\n"
|
|
"乙方:{{乙方名称}}\n\n"
|
|
"## 一、服务项目和范围\n\n"
|
|
"服务项目名称:{{服务项目名称}}\n\n"
|
|
"服务范围:{{服务范围}}\n\n"
|
|
"交付成果:{{交付成果}}\n\n"
|
|
"## 二、服务期限、验收和费用\n\n"
|
|
"服务期限:{{服务期限}}\n\n"
|
|
"验收标准:{{验收标准}}\n\n"
|
|
"验收日期:{{验收日期}}\n\n"
|
|
"服务费用:{{服务费用}}\n\n"
|
|
"## 三、知识产权、保密和争议解决\n\n"
|
|
"知识产权/成果使用安排:{{知识产权/成果使用安排}}\n\n"
|
|
"保密要求:{{保密要求}}\n\n"
|
|
"争议解决方式:{{争议解决方式}}\n\n"
|
|
"电子签署平台:{{电子签署平台}}\n\n"
|
|
"签署日期:{{签署日期}}"
|
|
),
|
|
"variables": [
|
|
{"name": "甲方名称", "label": "甲方名称"},
|
|
{"name": "乙方名称", "label": "乙方名称"},
|
|
{"name": "服务项目名称", "label": "服务项目名称"},
|
|
{"name": "服务范围", "label": "服务范围"},
|
|
{"name": "交付成果", "label": "交付成果"},
|
|
{"name": "服务期限", "label": "服务期限"},
|
|
{"name": "验收标准", "label": "验收标准"},
|
|
{"name": "验收日期", "label": "验收日期", "required": False},
|
|
{"name": "服务费用", "label": "服务费用"},
|
|
{"name": "知识产权/成果使用安排", "label": "知识产权/成果使用安排", "required": False},
|
|
{"name": "保密要求", "label": "保密要求", "required": False},
|
|
{"name": "争议解决方式", "label": "争议解决方式"},
|
|
{"name": "电子签署平台", "label": "电子签署平台", "required": False},
|
|
{"name": "签署日期", "label": "签署日期"},
|
|
],
|
|
},
|
|
_load_phase13_template_asset(
|
|
"technology-development",
|
|
"TPL-TECH-DEVELOPMENT-001",
|
|
name="软件技术开发合同模板",
|
|
contract_type="software_development",
|
|
document_type_code="software_development",
|
|
template_category="service-delivery",
|
|
industry_category="technology",
|
|
description="适用于软件定制开发、交付、验收、知识产权和维护场景。",
|
|
),
|
|
_load_phase13_template_asset(
|
|
"lease",
|
|
"TPL-LEASE-HOUSE-001",
|
|
name="房屋租赁合同模板",
|
|
contract_type="lease_house",
|
|
document_type_code="lease_house",
|
|
template_category="real-estate",
|
|
industry_category="real-estate",
|
|
description="适用于办公、商业或居住房屋租赁场景。",
|
|
),
|
|
_load_phase13_template_asset(
|
|
"equity-transfer",
|
|
"TPL-EQUITY-TRANSFER-001",
|
|
name="股权转让协议模板",
|
|
contract_type="equity_transfer",
|
|
document_type_code="equity_transfer",
|
|
template_category="investment-finance",
|
|
industry_category="general-enterprise",
|
|
description="适用于有限责任公司股权转让、价款支付和工商变更场景。",
|
|
),
|
|
_load_phase13_template_asset(
|
|
"cooperation",
|
|
"TPL-COOPERATION-FRAMEWORK-001",
|
|
name="合作框架协议模板",
|
|
contract_type="cooperation_framework",
|
|
document_type_code="cooperation_framework",
|
|
template_category="commercial-cooperation",
|
|
industry_category="general-enterprise",
|
|
description="适用于双方建立长期合作意向和原则性约定场景。",
|
|
),
|
|
_load_phase13_template_asset(
|
|
"demand-letter",
|
|
"TPL-DEMAND-LETTER-001",
|
|
name="催告函模板",
|
|
contract_type="demand_letter",
|
|
document_type_code="demand_letter",
|
|
template_category="notices-letters",
|
|
industry_category="general-enterprise",
|
|
description="适用于付款、交付、整改等履约催告场景。",
|
|
),
|
|
_load_phase13_template_asset(
|
|
"termination-notice",
|
|
"TPL-CONTRACT-TERMINATION-NOTICE-001",
|
|
name="解除合同通知书模板",
|
|
contract_type="rescission_notice",
|
|
document_type_code="rescission_notice",
|
|
template_category="notices-letters",
|
|
industry_category="general-enterprise",
|
|
description="适用于依法或依约解除合同并保留证据的通知场景。",
|
|
),
|
|
_load_phase13_template_asset(
|
|
"power-of-attorney",
|
|
"TPL-POWER-OF-ATTORNEY-001",
|
|
name="授权委托书模板",
|
|
contract_type="power_of_attorney",
|
|
document_type_code="power_of_attorney",
|
|
template_category="corporate-governance",
|
|
industry_category="general-enterprise",
|
|
description="适用于企业授权、委托代理、签章和登记备案场景。",
|
|
),
|
|
_load_phase13_template_asset(
|
|
"legal-opinion",
|
|
"TPL-LEGAL-OPINION-001",
|
|
name="法律意见书模板",
|
|
contract_type="legal_opinion",
|
|
document_type_code="legal_opinion",
|
|
template_category="commercial-compliance",
|
|
industry_category="general-enterprise",
|
|
description="适用于企业法律意见、尽职调查、登记备案、融资和合规事项专业文书场景。",
|
|
),
|
|
_load_phase13_template_asset(
|
|
"property-service",
|
|
"TPL-PROPERTY-SERVICE-001",
|
|
name="物业服务合同模板",
|
|
contract_type="property_service",
|
|
document_type_code="property_service",
|
|
template_category="real-estate",
|
|
industry_category="real-estate",
|
|
description="适用于住宅、商办园区和产业园区的物业服务、收费、维修资金、安全协助和资料交接场景。",
|
|
),
|
|
]
|
|
|
|
|
|
class TemplateService:
|
|
def __init__(self, db: Session):
|
|
self.db = db
|
|
self.repository = ContractTemplateRepository(db) if db is not None else None
|
|
self.version_repository = ContractTemplateVersionRepository(db) if db is not None else None
|
|
self.governance_asset_repository = GovernanceAssetRepository(db) if db is not None else None
|
|
|
|
def list_templates(
|
|
self,
|
|
current_user: Dict[str, Any],
|
|
page: int = 1,
|
|
page_size: int = 20,
|
|
contract_type: Optional[str] = None,
|
|
document_type_code: Optional[str] = None,
|
|
template_category: Optional[str] = None,
|
|
industry_category: Optional[str] = None,
|
|
) -> dict:
|
|
self._require_admin(current_user)
|
|
repository = self._require_repository()
|
|
tenant_id = current_user["tenant_id"]
|
|
normalized_page = max(page, 1)
|
|
normalized_page_size = max(min(page_size, 100), 1)
|
|
normalized_contract_type = contract_type.strip() if contract_type else None
|
|
normalized_document_type_code = self._normalize_document_type_code(document_type_code, None) if document_type_code else None
|
|
normalized_template_category = template_category.strip() if template_category else None
|
|
normalized_industry_category = industry_category.strip() if industry_category else None
|
|
items = repository.list_paginated(
|
|
tenant_id=tenant_id,
|
|
page=normalized_page,
|
|
page_size=normalized_page_size,
|
|
contract_type=normalized_contract_type,
|
|
template_category=normalized_template_category,
|
|
industry_category=normalized_industry_category,
|
|
document_type_code=normalized_document_type_code,
|
|
)
|
|
return {
|
|
"items": [self._to_item(item) for item in items],
|
|
"page": normalized_page,
|
|
"page_size": normalized_page_size,
|
|
"total": repository.count(
|
|
tenant_id=tenant_id,
|
|
contract_type=normalized_contract_type,
|
|
template_category=normalized_template_category,
|
|
industry_category=normalized_industry_category,
|
|
document_type_code=normalized_document_type_code,
|
|
),
|
|
}
|
|
|
|
def get_template_versions(self, current_user: Dict[str, Any], template_id: str) -> dict:
|
|
self._require_admin(current_user)
|
|
version_repository = self._require_version_repository()
|
|
contract_template = self._require_template(current_user, template_id)
|
|
versions = version_repository.list_by_template_id(current_user["tenant_id"], template_id)
|
|
if not versions:
|
|
versions = [self._build_fallback_version(contract_template)]
|
|
versions.sort(key=lambda item: (item.is_current, item.created_at), reverse=True)
|
|
return {
|
|
"template_id": template_id,
|
|
"current_version_label": contract_template.current_version_label or "v1.0",
|
|
"version_count": max(contract_template.version_count or 0, len(versions), 1),
|
|
"items": [self._to_version_item(version) for version in versions],
|
|
}
|
|
|
|
def create_template(
|
|
self,
|
|
current_user: Dict[str, Any],
|
|
name: str,
|
|
contract_type: Optional[str],
|
|
description: Optional[str],
|
|
content: str,
|
|
variables_json: Optional[str],
|
|
enabled: bool,
|
|
change_summary: Optional[str] = None,
|
|
template_code: Optional[str] = None,
|
|
document_type_code: Optional[str] = None,
|
|
template_category: Optional[str] = None,
|
|
industry_category: Optional[str] = None,
|
|
) -> dict:
|
|
self._require_admin(current_user)
|
|
repository = self._require_repository()
|
|
normalized_change_summary = self._normalize_change_summary(change_summary, default="Initial template version")
|
|
normalized_contract_type = contract_type.strip() if contract_type else None
|
|
|
|
contract_template = ContractTemplate(
|
|
id=f"tpl-{uuid4().hex[:12]}",
|
|
tenant_id=current_user["tenant_id"],
|
|
name=name.strip(),
|
|
contract_type=normalized_contract_type,
|
|
document_type_code=self._normalize_document_type_code(document_type_code, normalized_contract_type),
|
|
template_code=self._normalize_template_code(template_code, normalized_contract_type, name),
|
|
template_category=self._normalize_template_category(template_category, normalized_contract_type),
|
|
industry_category=self._normalize_industry_category(industry_category, normalized_contract_type),
|
|
description=description.strip() if description else None,
|
|
content=content.strip(),
|
|
variables_json=self._parse_variables(variables_json),
|
|
enabled=enabled,
|
|
current_version_label="v1.0",
|
|
latest_change_summary=normalized_change_summary,
|
|
latest_version_created_at=self._now(),
|
|
version_count=1,
|
|
created_by=current_user.get("id"),
|
|
updated_by=current_user.get("id"),
|
|
)
|
|
created = repository.create(contract_template)
|
|
self._create_version_snapshot(created, current_user, normalized_change_summary, is_current=True)
|
|
return self._to_item(created)
|
|
|
|
def update_template(
|
|
self,
|
|
current_user: Dict[str, Any],
|
|
template_id: str,
|
|
name: str,
|
|
contract_type: Optional[str],
|
|
description: Optional[str],
|
|
content: str,
|
|
variables_json: Optional[str],
|
|
enabled: bool,
|
|
change_summary: Optional[str] = None,
|
|
template_code: Optional[str] = None,
|
|
document_type_code: Optional[str] = None,
|
|
template_category: Optional[str] = None,
|
|
industry_category: Optional[str] = None,
|
|
) -> dict:
|
|
self._require_admin(current_user)
|
|
repository = self._require_repository()
|
|
contract_template = self._require_template(current_user, template_id)
|
|
next_version_label = self._bump_version_label(contract_template.current_version_label)
|
|
normalized_change_summary = self._normalize_change_summary(change_summary, default=f"{next_version_label} template update")
|
|
normalized_contract_type = contract_type.strip() if contract_type else None
|
|
previous_contract_type = contract_template.contract_type
|
|
previous_document_type_code = contract_template.document_type_code
|
|
|
|
contract_template.name = name.strip()
|
|
contract_template.contract_type = normalized_contract_type
|
|
if document_type_code is not None:
|
|
contract_template.document_type_code = self._normalize_document_type_code(document_type_code, normalized_contract_type)
|
|
elif previous_contract_type == normalized_contract_type and previous_document_type_code:
|
|
contract_template.document_type_code = previous_document_type_code
|
|
else:
|
|
contract_template.document_type_code = self._normalize_document_type_code(None, normalized_contract_type)
|
|
contract_template.template_code = self._normalize_template_code(template_code or contract_template.template_code, normalized_contract_type, name)
|
|
contract_template.template_category = self._normalize_template_category(template_category, normalized_contract_type)
|
|
contract_template.industry_category = self._normalize_industry_category(industry_category, normalized_contract_type)
|
|
contract_template.description = description.strip() if description else None
|
|
contract_template.content = content.strip()
|
|
contract_template.variables_json = self._parse_variables(variables_json)
|
|
contract_template.enabled = enabled
|
|
contract_template.current_version_label = next_version_label
|
|
contract_template.latest_change_summary = normalized_change_summary
|
|
contract_template.latest_version_created_at = self._now()
|
|
contract_template.version_count = max(contract_template.version_count or 0, 1) + 1
|
|
contract_template.updated_by = current_user.get("id")
|
|
updated = repository.update(contract_template)
|
|
self._create_version_snapshot(updated, current_user, normalized_change_summary, is_current=True)
|
|
return self._to_item(updated)
|
|
|
|
def delete_template(self, current_user: Dict[str, Any], template_id: str) -> dict:
|
|
self._require_admin(current_user)
|
|
repository = self._require_repository()
|
|
version_repository = self._require_version_repository()
|
|
contract_template = self._require_template(current_user, template_id)
|
|
template_data = self._to_item(contract_template)
|
|
version_repository.delete_by_template_id(current_user["tenant_id"], template_id)
|
|
repository.delete(contract_template)
|
|
return template_data
|
|
|
|
def seed_default_templates(self, current_user: Dict[str, Any]) -> dict:
|
|
self._require_admin(current_user)
|
|
repository = self._require_repository()
|
|
tenant_id = current_user["tenant_id"]
|
|
created_items: list[dict[str, Any]] = []
|
|
existing_items: list[dict[str, Any]] = []
|
|
|
|
for template_data in DEFAULT_TEMPLATES:
|
|
template_code = self._normalize_template_code(None, template_data["contract_type"], template_data["name"])
|
|
existing = None
|
|
get_by_template_code = getattr(repository, "get_by_template_code", None)
|
|
if callable(get_by_template_code):
|
|
existing = get_by_template_code(tenant_id, template_code)
|
|
if existing is None:
|
|
existing = repository.get_by_name(tenant_id, template_data["name"])
|
|
if existing is not None:
|
|
existing_items.append(self._to_item(existing))
|
|
continue
|
|
|
|
contract_template = ContractTemplate(
|
|
id=f"tpl-{uuid4().hex[:12]}",
|
|
tenant_id=tenant_id,
|
|
name=template_data["name"],
|
|
contract_type=template_data["contract_type"],
|
|
document_type_code=template_data["document_type_code"],
|
|
template_code=template_code,
|
|
template_category=template_data["template_category"],
|
|
industry_category=template_data["industry_category"],
|
|
description=template_data["description"],
|
|
content=template_data["content"],
|
|
variables_json=template_data["variables"],
|
|
enabled=True,
|
|
current_version_label="v1.0",
|
|
latest_change_summary="Seeded default template",
|
|
latest_version_created_at=self._now(),
|
|
version_count=1,
|
|
created_by=current_user.get("id"),
|
|
updated_by=current_user.get("id"),
|
|
)
|
|
created = repository.create(contract_template)
|
|
self._create_version_snapshot(created, current_user, "Seeded default template", is_current=True)
|
|
created_items.append(self._to_item(created))
|
|
|
|
return {
|
|
"created": created_items,
|
|
"existing": existing_items,
|
|
"created_count": len(created_items),
|
|
"existing_count": len(existing_items),
|
|
"total": len(DEFAULT_TEMPLATES),
|
|
"template_names": [item["name"] for item in created_items],
|
|
}
|
|
|
|
def publish_template(self, current_user: Dict[str, Any], template_id: str) -> dict:
|
|
self._require_admin(current_user)
|
|
repository = self._require_repository()
|
|
governance_repository = self._require_governance_asset_repository()
|
|
contract_template = self._require_template(current_user, template_id)
|
|
governance_asset = self._require_publishable_governance_asset(current_user, contract_template)
|
|
|
|
contract_template.enabled = True
|
|
contract_template.published_at = self._now()
|
|
contract_template.published_by = current_user.get("id")
|
|
contract_template.latest_governance_asset_id = governance_asset.id
|
|
contract_template.updated_by = current_user.get("id")
|
|
updated = repository.update(contract_template)
|
|
return self._to_item(updated)
|
|
|
|
def unpublish_template(self, current_user: Dict[str, Any], template_id: str) -> dict:
|
|
self._require_admin(current_user)
|
|
repository = self._require_repository()
|
|
contract_template = self._require_template(current_user, template_id)
|
|
contract_template.enabled = False
|
|
contract_template.published_at = None
|
|
contract_template.published_by = None
|
|
contract_template.latest_governance_asset_id = None
|
|
contract_template.updated_by = current_user.get("id")
|
|
updated = repository.update(contract_template)
|
|
return self._to_item(updated)
|
|
|
|
def rollback_template(
|
|
self,
|
|
current_user: Dict[str, Any],
|
|
template_id: str,
|
|
target_version_label: str,
|
|
change_summary: Optional[str] = None,
|
|
) -> dict:
|
|
self._require_admin(current_user)
|
|
repository = self._require_repository()
|
|
version_repository = self._require_version_repository()
|
|
contract_template = self._require_template(current_user, template_id)
|
|
target_version = next(
|
|
(item for item in version_repository.list_by_template_id(current_user["tenant_id"], template_id) if item.version_label == target_version_label),
|
|
None,
|
|
)
|
|
if target_version is None:
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="template version not found")
|
|
|
|
next_version_label = self._bump_version_label(contract_template.current_version_label)
|
|
normalized_change_summary = self._normalize_change_summary(change_summary, default=f"Rollback to {target_version_label}")
|
|
contract_template.name = target_version.name
|
|
contract_template.contract_type = target_version.contract_type
|
|
contract_template.document_type_code = target_version.document_type_code
|
|
contract_template.template_category = target_version.template_category
|
|
contract_template.industry_category = target_version.industry_category
|
|
contract_template.description = target_version.description
|
|
contract_template.content = target_version.content
|
|
contract_template.variables_json = target_version.variables_json
|
|
contract_template.current_version_label = next_version_label
|
|
contract_template.latest_change_summary = normalized_change_summary
|
|
contract_template.latest_version_created_at = self._now()
|
|
contract_template.version_count = max(contract_template.version_count or 0, 1) + 1
|
|
contract_template.enabled = False
|
|
contract_template.published_at = None
|
|
contract_template.published_by = None
|
|
contract_template.latest_governance_asset_id = None
|
|
contract_template.updated_by = current_user.get("id")
|
|
updated = repository.update(contract_template)
|
|
self._create_version_snapshot(updated, current_user, normalized_change_summary, is_current=True)
|
|
return self._to_item(updated)
|
|
|
|
def _require_template(self, current_user: Dict[str, Any], template_id: str) -> ContractTemplate:
|
|
repository = self._require_repository()
|
|
contract_template = repository.get_by_id(template_id)
|
|
if contract_template is None or contract_template.tenant_id != current_user["tenant_id"]:
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="template not found")
|
|
return contract_template
|
|
|
|
def _require_admin(self, current_user: Dict[str, Any]) -> None:
|
|
if current_user.get("role") != "admin":
|
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="forbidden")
|
|
|
|
def _require_repository(self) -> ContractTemplateRepository:
|
|
if self.repository is None:
|
|
raise RuntimeError("template repository is not configured")
|
|
return self.repository
|
|
|
|
def _require_governance_asset_repository(self) -> GovernanceAssetRepository:
|
|
if self.governance_asset_repository is None:
|
|
raise RuntimeError("governance asset repository is not configured")
|
|
return self.governance_asset_repository
|
|
|
|
def _require_version_repository(self) -> ContractTemplateVersionRepository:
|
|
if self.version_repository is None:
|
|
raise RuntimeError("template version repository is not configured")
|
|
return self.version_repository
|
|
|
|
def _parse_variables(self, variables_json: Optional[str]) -> Optional[list[dict[str, Any]]]:
|
|
if not variables_json or not variables_json.strip():
|
|
return None
|
|
try:
|
|
parsed = json.loads(variables_json)
|
|
except json.JSONDecodeError as exc:
|
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="variables_json must be valid JSON") from exc
|
|
if not isinstance(parsed, list):
|
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="variables_json must be a JSON array")
|
|
for item in parsed:
|
|
if not isinstance(item, dict):
|
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="variables_json items must be objects")
|
|
return parsed
|
|
|
|
def _create_version_snapshot(
|
|
self,
|
|
contract_template: ContractTemplate,
|
|
current_user: Dict[str, Any],
|
|
change_summary: str,
|
|
*,
|
|
is_current: bool,
|
|
) -> ContractTemplateVersion:
|
|
version_repository = self._require_version_repository()
|
|
version_repository.clear_current(contract_template.tenant_id, contract_template.id)
|
|
version = ContractTemplateVersion(
|
|
id=f"tplver-{uuid4().hex[:12]}",
|
|
template_id=contract_template.id,
|
|
tenant_id=contract_template.tenant_id,
|
|
version_label=contract_template.current_version_label or "v1.0",
|
|
change_summary=change_summary,
|
|
name=contract_template.name,
|
|
contract_type=contract_template.contract_type,
|
|
document_type_code=contract_template.document_type_code,
|
|
template_category=contract_template.template_category,
|
|
industry_category=contract_template.industry_category,
|
|
description=contract_template.description,
|
|
content=contract_template.content,
|
|
variables_json=contract_template.variables_json,
|
|
is_current=is_current,
|
|
created_by=current_user.get("id"),
|
|
created_by_name=current_user.get("display_name"),
|
|
created_at=contract_template.latest_version_created_at or self._now(),
|
|
)
|
|
return version_repository.create(version)
|
|
|
|
def _build_fallback_version(self, contract_template: ContractTemplate) -> ContractTemplateVersion:
|
|
return ContractTemplateVersion(
|
|
id=f"fallback-{contract_template.id}",
|
|
template_id=contract_template.id,
|
|
tenant_id=contract_template.tenant_id,
|
|
version_label=contract_template.current_version_label or "v1.0",
|
|
change_summary=contract_template.latest_change_summary,
|
|
name=contract_template.name,
|
|
contract_type=contract_template.contract_type,
|
|
document_type_code=contract_template.document_type_code,
|
|
template_category=contract_template.template_category,
|
|
industry_category=contract_template.industry_category,
|
|
description=contract_template.description,
|
|
content=contract_template.content,
|
|
variables_json=contract_template.variables_json,
|
|
is_current=True,
|
|
created_by=contract_template.updated_by or contract_template.created_by,
|
|
created_by_name=None,
|
|
created_at=contract_template.latest_version_created_at or contract_template.updated_at or contract_template.created_at,
|
|
)
|
|
|
|
def _normalize_change_summary(self, value: Optional[str], *, default: str) -> str:
|
|
normalized = value.strip() if value else ""
|
|
return normalized or default
|
|
|
|
def _normalize_template_category(self, template_category: Optional[str], contract_type: Optional[str]) -> str:
|
|
normalized = template_category.strip() if template_category else ""
|
|
if normalized:
|
|
return normalized
|
|
return {
|
|
"labor": "employment-hr",
|
|
"labor-service": "employment-hr",
|
|
"labor_service": "employment-hr",
|
|
"technical-service": "service-delivery",
|
|
"technical_service": "service-delivery",
|
|
"software-development": "service-delivery",
|
|
"software_development": "service-delivery",
|
|
"technology-development": "service-delivery",
|
|
"technology_development": "service-delivery",
|
|
"lease-house": "real-estate",
|
|
"lease_house": "real-estate",
|
|
"equity-transfer": "investment-finance",
|
|
"equity_transfer": "investment-finance",
|
|
"cooperation-framework": "commercial-cooperation",
|
|
"cooperation_framework": "commercial-cooperation",
|
|
"agency-distribution": "commercial-cooperation",
|
|
"agency_distribution": "commercial-cooperation",
|
|
"franchise": "commercial-cooperation",
|
|
"franchise_agreement": "commercial-cooperation",
|
|
"intercompany-loan": "investment-finance",
|
|
"intercompany_loan": "investment-finance",
|
|
"investment": "investment-finance",
|
|
"investment_agreement": "investment-finance",
|
|
"valuation-adjustment": "investment-finance",
|
|
"valuation_adjustment": "investment-finance",
|
|
"shareholder-resolution": "corporate-governance",
|
|
"shareholder_resolution": "corporate-governance",
|
|
"power-of-attorney": "corporate-governance",
|
|
"power_of_attorney": "corporate-governance",
|
|
"legal-opinion": "commercial-compliance",
|
|
"legal_opinion": "commercial-compliance",
|
|
"property-service": "real-estate",
|
|
"property_service": "real-estate",
|
|
"demand-letter": "notices-letters",
|
|
"demand_letter": "notices-letters",
|
|
"rescission-notice": "notices-letters",
|
|
"rescission_notice": "notices-letters",
|
|
"termination-notice": "notices-letters",
|
|
"contract_termination_notice": "notices-letters",
|
|
"procurement": "procurement-supply",
|
|
"sales": "sales-commercial",
|
|
"service": "service-delivery",
|
|
"nda": "commercial-compliance",
|
|
}.get((contract_type or "").strip().lower(), "general-business")
|
|
|
|
def _normalize_industry_category(self, industry_category: Optional[str], contract_type: Optional[str]) -> str:
|
|
normalized = industry_category.strip() if industry_category else ""
|
|
if normalized:
|
|
return normalized
|
|
return {
|
|
"labor": "general-enterprise",
|
|
"labor-service": "general-enterprise",
|
|
"labor_service": "general-enterprise",
|
|
"technical-service": "technology",
|
|
"technical_service": "technology",
|
|
"software-development": "technology",
|
|
"software_development": "technology",
|
|
"technology-development": "technology",
|
|
"technology_development": "technology",
|
|
"lease-house": "real-estate",
|
|
"lease_house": "real-estate",
|
|
"equity-transfer": "general-enterprise",
|
|
"equity_transfer": "general-enterprise",
|
|
"cooperation-framework": "general-enterprise",
|
|
"cooperation_framework": "general-enterprise",
|
|
"agency-distribution": "general-enterprise",
|
|
"agency_distribution": "general-enterprise",
|
|
"franchise": "general-enterprise",
|
|
"franchise_agreement": "general-enterprise",
|
|
"intercompany-loan": "general-enterprise",
|
|
"intercompany_loan": "general-enterprise",
|
|
"investment": "general-enterprise",
|
|
"investment_agreement": "general-enterprise",
|
|
"valuation-adjustment": "general-enterprise",
|
|
"valuation_adjustment": "general-enterprise",
|
|
"shareholder-resolution": "general-enterprise",
|
|
"shareholder_resolution": "general-enterprise",
|
|
"power-of-attorney": "general-enterprise",
|
|
"power_of_attorney": "general-enterprise",
|
|
"legal-opinion": "general-enterprise",
|
|
"legal_opinion": "general-enterprise",
|
|
"property-service": "real-estate",
|
|
"property_service": "real-estate",
|
|
"demand-letter": "general-enterprise",
|
|
"demand_letter": "general-enterprise",
|
|
"rescission-notice": "general-enterprise",
|
|
"rescission_notice": "general-enterprise",
|
|
"termination-notice": "general-enterprise",
|
|
"contract_termination_notice": "general-enterprise",
|
|
"procurement": "general-enterprise",
|
|
"sales": "general-enterprise",
|
|
"service": "professional-services",
|
|
"nda": "general-enterprise",
|
|
}.get((contract_type or "").strip().lower(), "general-enterprise")
|
|
|
|
def _normalize_document_type_code(self, document_type_code: Optional[str], contract_type: Optional[str]) -> Optional[str]:
|
|
normalized = document_type_code.strip().lower().replace("-", "_") if document_type_code else ""
|
|
if normalized:
|
|
return {
|
|
"property_service_agreement": "property_service",
|
|
"contract_termination_notice": "rescission_notice",
|
|
}.get(normalized, normalized)
|
|
return {
|
|
"labor": "labor_contract",
|
|
"labor-service": "labor_service_agreement",
|
|
"labor_service": "labor_service_agreement",
|
|
"technical-service": "technical_service",
|
|
"technical_service": "technical_service",
|
|
"software-development": "software_development",
|
|
"software_development": "software_development",
|
|
"technology-development": "software_development",
|
|
"technology_development": "software_development",
|
|
"lease-house": "lease_house",
|
|
"lease_house": "lease_house",
|
|
"equity-transfer": "equity_transfer",
|
|
"equity_transfer": "equity_transfer",
|
|
"cooperation-framework": "cooperation_framework",
|
|
"cooperation_framework": "cooperation_framework",
|
|
"agency-distribution": "agency_distribution_agreement",
|
|
"agency_distribution": "agency_distribution_agreement",
|
|
"franchise": "franchise_agreement",
|
|
"franchise_agreement": "franchise_agreement",
|
|
"intercompany-loan": "intercompany_loan_agreement",
|
|
"intercompany_loan": "intercompany_loan_agreement",
|
|
"investment": "investment_agreement",
|
|
"investment_agreement": "investment_agreement",
|
|
"valuation-adjustment": "valuation_adjustment_agreement",
|
|
"valuation_adjustment": "valuation_adjustment_agreement",
|
|
"shareholder-resolution": "shareholder_resolution",
|
|
"shareholder_resolution": "shareholder_resolution",
|
|
"power-of-attorney": "power_of_attorney",
|
|
"power_of_attorney": "power_of_attorney",
|
|
"legal-opinion": "legal_opinion",
|
|
"legal_opinion": "legal_opinion",
|
|
"property-service": "property_service",
|
|
"property_service": "property_service",
|
|
"demand-letter": "demand_letter",
|
|
"demand_letter": "demand_letter",
|
|
"rescission-notice": "rescission_notice",
|
|
"rescission_notice": "rescission_notice",
|
|
"termination-notice": "rescission_notice",
|
|
"contract_termination_notice": "rescission_notice",
|
|
"procurement": "procurement_goods",
|
|
"purchase": "procurement_goods",
|
|
"sales": "procurement_goods",
|
|
"service": "service_general",
|
|
"nda": "nda_commercial",
|
|
"development": "software_development",
|
|
"software": "software_development",
|
|
"technical": "technical_service",
|
|
}.get((contract_type or "").strip().lower())
|
|
|
|
def _normalize_template_code(self, template_code: Optional[str], contract_type: Optional[str], name: str) -> str:
|
|
normalized = template_code.strip() if template_code else ""
|
|
if normalized:
|
|
return normalized
|
|
normalized_contract_type = (contract_type or "").strip().lower()
|
|
explicit_map = {
|
|
("labor", "劳动合同标准模板"): "TPL-LABOR-CONTRACT-001",
|
|
("procurement", "货物采购合同模板"): "TPL-PURCHASE-GOODS-001",
|
|
("procurement", "采购合同标准模板"): "TPL-PURCHASE-GOODS-001",
|
|
("purchase", "货物采购合同模板"): "TPL-PURCHASE-GOODS-001",
|
|
("purchase", "采购合同标准模板"): "TPL-PURCHASE-GOODS-001",
|
|
("nda", "保密协议标准模板"): "TPL-NDA-COMMERCIAL-001",
|
|
("technical-service", "技术服务合同模板"): "TPL-TECHNICAL-SERVICE-001",
|
|
("technical_service", "技术服务合同模板"): "TPL-TECHNICAL-SERVICE-001",
|
|
("software-development", "软件开发合同模板"): "TPL-TECH-DEVELOPMENT-001",
|
|
("software_development", "软件开发合同模板"): "TPL-TECH-DEVELOPMENT-001",
|
|
("software-development", "软件技术开发合同模板"): "TPL-TECH-DEVELOPMENT-001",
|
|
("software_development", "软件技术开发合同模板"): "TPL-TECH-DEVELOPMENT-001",
|
|
("technology-development", "软件技术开发合同模板"): "TPL-TECH-DEVELOPMENT-001",
|
|
("technology_development", "软件技术开发合同模板"): "TPL-TECH-DEVELOPMENT-001",
|
|
("sales", "销售合同模板"): "TPL-SALES-GOODS-001",
|
|
("lease-house", "房屋租赁合同模板"): "TPL-LEASE-HOUSE-001",
|
|
("lease_house", "房屋租赁合同模板"): "TPL-LEASE-HOUSE-001",
|
|
("equity-transfer", "股权转让协议模板"): "TPL-EQUITY-TRANSFER-001",
|
|
("equity_transfer", "股权转让协议模板"): "TPL-EQUITY-TRANSFER-001",
|
|
("cooperation-framework", "合作框架协议模板"): "TPL-COOPERATION-FRAMEWORK-001",
|
|
("cooperation_framework", "合作框架协议模板"): "TPL-COOPERATION-FRAMEWORK-001",
|
|
("demand-letter", "催告函模板"): "TPL-DEMAND-LETTER-001",
|
|
("demand_letter", "催告函模板"): "TPL-DEMAND-LETTER-001",
|
|
("rescission-notice", "解除合同通知书模板"): "TPL-CONTRACT-TERMINATION-NOTICE-001",
|
|
("rescission_notice", "解除合同通知书模板"): "TPL-CONTRACT-TERMINATION-NOTICE-001",
|
|
("termination-notice", "解除合同通知书模板"): "TPL-CONTRACT-TERMINATION-NOTICE-001",
|
|
("contract_termination_notice", "解除合同通知书模板"): "TPL-CONTRACT-TERMINATION-NOTICE-001",
|
|
("agency-distribution", "代理/经销协议模板"): "TPL-AGENCY-DISTRIBUTION-001",
|
|
("agency_distribution", "代理/经销协议模板"): "TPL-AGENCY-DISTRIBUTION-001",
|
|
("franchise", "加盟合同模板"): "TPL-FRANCHISE-001",
|
|
("franchise_agreement", "加盟合同模板"): "TPL-FRANCHISE-001",
|
|
("intercompany-loan", "企业间借款合同模板"): "TPL-INTERCOMPANY-LOAN-001",
|
|
("intercompany_loan", "企业间借款合同模板"): "TPL-INTERCOMPANY-LOAN-001",
|
|
("intercompany-loan", "借款合同模板"): "TPL-INTERCOMPANY-LOAN-001",
|
|
("intercompany_loan", "借款合同模板"): "TPL-INTERCOMPANY-LOAN-001",
|
|
("investment", "投资协议模板"): "TPL-INVESTMENT-AGREEMENT-001",
|
|
("investment_agreement", "投资协议模板"): "TPL-INVESTMENT-AGREEMENT-001",
|
|
("valuation-adjustment", "对赌协议模板"): "TPL-VALUATION-ADJUSTMENT-001",
|
|
("valuation_adjustment", "对赌协议模板"): "TPL-VALUATION-ADJUSTMENT-001",
|
|
("shareholder-resolution", "股东会决议模板"): "TPL-SHAREHOLDER-RESOLUTION-001",
|
|
("shareholder_resolution", "股东会决议模板"): "TPL-SHAREHOLDER-RESOLUTION-001",
|
|
("power-of-attorney", "授权委托书模板"): "TPL-POWER-OF-ATTORNEY-001",
|
|
("power_of_attorney", "授权委托书模板"): "TPL-POWER-OF-ATTORNEY-001",
|
|
("legal-opinion", "法律意见书模板"): "TPL-LEGAL-OPINION-001",
|
|
("legal_opinion", "法律意见书模板"): "TPL-LEGAL-OPINION-001",
|
|
("property-service", "物业服务合同模板"): "TPL-PROPERTY-SERVICE-001",
|
|
("property_service", "物业服务合同模板"): "TPL-PROPERTY-SERVICE-001",
|
|
}
|
|
mapped = explicit_map.get((normalized_contract_type, name.strip()))
|
|
if mapped:
|
|
return mapped
|
|
if "saas" in name.strip().lower() or "软件即服务" in name:
|
|
return "TPL-SOFTWARE-SAAS-001"
|
|
if "技术开发" in name:
|
|
return "TPL-TECH-DEVELOPMENT-001"
|
|
if "技术服务" in name:
|
|
return "TPL-TECHNICAL-SERVICE-001"
|
|
if "合作框架" in name:
|
|
return "TPL-COOPERATION-FRAMEWORK-001"
|
|
if "委托服务" in name:
|
|
return "TPL-COMMISSION-SERVICE-001"
|
|
if "房屋租赁" in name:
|
|
return "TPL-LEASE-HOUSE-001"
|
|
if "企业间借款" in name or ("借款合同" in name and "企业" in name) or "intercompany loan" in name.strip().lower():
|
|
return "TPL-INTERCOMPANY-LOAN-001"
|
|
if normalized_contract_type in {"intercompany-loan", "intercompany_loan"}:
|
|
return "TPL-INTERCOMPANY-LOAN-001"
|
|
if "投资协议" in name or "investment agreement" in name.strip().lower():
|
|
return "TPL-INVESTMENT-AGREEMENT-001"
|
|
if normalized_contract_type in {"investment", "investment_agreement"}:
|
|
return "TPL-INVESTMENT-AGREEMENT-001"
|
|
if "对赌协议" in name or "估值调整" in name or "valuation adjustment" in name.strip().lower():
|
|
return "TPL-VALUATION-ADJUSTMENT-001"
|
|
if normalized_contract_type in {"valuation-adjustment", "valuation_adjustment"}:
|
|
return "TPL-VALUATION-ADJUSTMENT-001"
|
|
if "股东会决议" in name or "股东大会决议" in name or "shareholder resolution" in name.strip().lower():
|
|
return "TPL-SHAREHOLDER-RESOLUTION-001"
|
|
if normalized_contract_type in {"shareholder-resolution", "shareholder_resolution"}:
|
|
return "TPL-SHAREHOLDER-RESOLUTION-001"
|
|
if "授权委托书" in name or "power of attorney" in name.strip().lower():
|
|
return "TPL-POWER-OF-ATTORNEY-001"
|
|
if normalized_contract_type in {"power-of-attorney", "power_of_attorney"}:
|
|
return "TPL-POWER-OF-ATTORNEY-001"
|
|
if "法律意见书" in name or "legal opinion" in name.strip().lower():
|
|
return "TPL-LEGAL-OPINION-001"
|
|
if normalized_contract_type in {"legal-opinion", "legal_opinion"}:
|
|
return "TPL-LEGAL-OPINION-001"
|
|
if "物业服务" in name or "property service" in name.strip().lower():
|
|
return "TPL-PROPERTY-SERVICE-001"
|
|
if normalized_contract_type in {"property-service", "property_service"}:
|
|
return "TPL-PROPERTY-SERVICE-001"
|
|
if "通用服务" in name or ("服务合同" in name and "物业" not in name and "委托" not in name and "劳动" not in name and "技术" not in name and "软件" not in name):
|
|
return "TPL-SERVICE-GENERAL-001"
|
|
if "股权转让" in name or "equity transfer" in name.strip().lower():
|
|
return "TPL-EQUITY-TRANSFER-001"
|
|
if normalized_contract_type in {"equity-transfer", "equity_transfer"}:
|
|
return "TPL-EQUITY-TRANSFER-001"
|
|
if "催告函" in name or "demand letter" in name.strip().lower():
|
|
return "TPL-DEMAND-LETTER-001"
|
|
if normalized_contract_type in {"demand-letter", "demand_letter"}:
|
|
return "TPL-DEMAND-LETTER-001"
|
|
if "解除合同通知书" in name or "termination notice" in name.strip().lower() or "rescission notice" in name.strip().lower():
|
|
return "TPL-CONTRACT-TERMINATION-NOTICE-001"
|
|
if normalized_contract_type in {"rescission-notice", "rescission_notice", "termination-notice", "contract_termination_notice"}:
|
|
return "TPL-CONTRACT-TERMINATION-NOTICE-001"
|
|
if "代理/经销" in name or ("代理" in name and "经销" in name):
|
|
return "TPL-AGENCY-DISTRIBUTION-001"
|
|
if normalized_contract_type in {"agency-distribution", "agency_distribution"}:
|
|
return "TPL-AGENCY-DISTRIBUTION-001"
|
|
if "加盟合同" in name or "商业特许经营" in name or "franchise" in name.strip().lower():
|
|
return "TPL-FRANCHISE-001"
|
|
if normalized_contract_type in {"franchise", "franchise_agreement"}:
|
|
return "TPL-FRANCHISE-001"
|
|
if normalized_contract_type in {"technical-service", "technical_service"}:
|
|
return "TPL-TECHNICAL-SERVICE-001"
|
|
if normalized_contract_type in {"software-development", "software_development", "technology-development", "technology_development"}:
|
|
return "TPL-TECH-DEVELOPMENT-001"
|
|
if normalized_contract_type in {"lease-house", "lease_house"}:
|
|
return "TPL-LEASE-HOUSE-001"
|
|
if normalized_contract_type in {"cooperation-framework", "cooperation_framework"}:
|
|
return "TPL-COOPERATION-FRAMEWORK-001"
|
|
if normalized_contract_type in {"labor-service", "labor_service"}:
|
|
return "TPL-LABOR-SERVICE-001"
|
|
if normalized_contract_type == "service":
|
|
return "TPL-SERVICE-GENERAL-001"
|
|
if normalized_contract_type in {"procurement", "purchase"}:
|
|
return "TPL-PURCHASE-GOODS-001"
|
|
if normalized_contract_type == "labor":
|
|
return "TPL-LABOR-CONTRACT-001"
|
|
if normalized_contract_type == "nda":
|
|
return "TPL-NDA-COMMERCIAL-001"
|
|
if normalized_contract_type == "sales":
|
|
return "TPL-SALES-GOODS-001"
|
|
slug = re.sub(r"[^A-Za-z0-9]+", "-", name.strip().upper()).strip("-") or "GENERAL"
|
|
return f"TPL-{slug[:48]}-001"
|
|
|
|
def _require_publishable_governance_asset(self, current_user: Dict[str, Any], contract_template: ContractTemplate):
|
|
governance_repository = self._require_governance_asset_repository()
|
|
if not contract_template.template_code:
|
|
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="template_code is required before publish")
|
|
governance_asset = governance_repository.get_by_identity(current_user["tenant_id"], "contract_template", contract_template.template_code)
|
|
if governance_asset is None:
|
|
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="governance asset not found for template_code")
|
|
if governance_asset.production_status not in {"ready", "approved"}:
|
|
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="governance asset is not approved for production publish")
|
|
return governance_asset
|
|
|
|
def _bump_version_label(self, current_version_label: Optional[str]) -> str:
|
|
if not current_version_label:
|
|
return "v1.1"
|
|
match = re.match(r"^v?(?P<major>\d+)\.(?P<minor>\d+)$", current_version_label.strip(), re.IGNORECASE)
|
|
if not match:
|
|
return "v1.1"
|
|
major = int(match.group("major"))
|
|
minor = int(match.group("minor")) + 1
|
|
return f"v{major}.{minor}"
|
|
|
|
def _now(self) -> datetime:
|
|
return datetime.now(timezone.utc)
|
|
|
|
def _to_item(self, contract_template: ContractTemplate) -> dict:
|
|
return {
|
|
"id": contract_template.id,
|
|
"name": contract_template.name,
|
|
"contract_type": contract_template.contract_type,
|
|
"document_type_code": contract_template.document_type_code,
|
|
"template_code": contract_template.template_code,
|
|
"template_category": contract_template.template_category,
|
|
"industry_category": contract_template.industry_category,
|
|
"description": contract_template.description,
|
|
"content": contract_template.content,
|
|
"variables": contract_template.variables_json or [],
|
|
"enabled": contract_template.enabled,
|
|
"published_at": contract_template.published_at.isoformat() if contract_template.published_at else None,
|
|
"published_by": contract_template.published_by,
|
|
"latest_governance_asset_id": contract_template.latest_governance_asset_id,
|
|
"current_version_label": contract_template.current_version_label or "v1.0",
|
|
"latest_change_summary": contract_template.latest_change_summary,
|
|
"latest_version_created_at": (
|
|
contract_template.latest_version_created_at.isoformat()
|
|
if contract_template.latest_version_created_at is not None
|
|
else None
|
|
),
|
|
"version_count": max(contract_template.version_count or 0, 1),
|
|
"created_by": contract_template.created_by,
|
|
"updated_by": contract_template.updated_by,
|
|
"created_at": contract_template.created_at.isoformat(),
|
|
"updated_at": contract_template.updated_at.isoformat(),
|
|
}
|
|
|
|
def _to_version_item(self, version: ContractTemplateVersion) -> dict:
|
|
return {
|
|
"id": version.id,
|
|
"template_id": version.template_id,
|
|
"version_label": version.version_label,
|
|
"change_summary": version.change_summary,
|
|
"name": version.name,
|
|
"contract_type": version.contract_type,
|
|
"document_type_code": version.document_type_code,
|
|
"template_category": version.template_category,
|
|
"industry_category": version.industry_category,
|
|
"description": version.description,
|
|
"content": version.content,
|
|
"variables": version.variables_json or [],
|
|
"is_current": version.is_current,
|
|
"created_by": version.created_by,
|
|
"created_by_name": version.created_by_name,
|
|
"created_at": version.created_at.isoformat(),
|
|
}
|