4a93bd1c44
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
222 lines
12 KiB
Python
222 lines
12 KiB
Python
import json
|
|
from typing import Any, Dict, Optional
|
|
from uuid import uuid4
|
|
|
|
DEFAULT_TEMPLATES: list[dict[str, Any]] = [
|
|
{
|
|
"name": "劳动合同标准模板",
|
|
"contract_type": "labor",
|
|
"description": "适用于企业与员工签署的标准劳动合同。",
|
|
"content": "甲方(用人单位):{{party_a}}\n乙方(员工):{{party_b}}\n\n根据《中华人民共和国劳动合同法》及相关法律法规,甲乙双方在平等自愿、协商一致的基础上,就劳动关系建立事宜达成如下协议:\n\n一、合同期限\n本合同期限自 {{start_date}} 起至 {{end_date}} 止。\n\n二、工作内容与地点\n乙方同意在甲方安排的 {{job_title}} 岗位工作,工作地点为 {{work_location}}。\n\n三、劳动报酬\n甲方每月向乙方支付税前工资人民币 {{salary}} 元,发薪日为每月 {{payday}} 日。\n\n四、工作时间与休息休假\n双方按照国家规定执行工时与休假制度。\n\n五、社会保险与福利\n甲方依法为乙方缴纳社会保险并提供相应福利。\n\n六、保密与竞业\n乙方应对在任职期间知悉的商业秘密承担保密义务。\n\n七、其他\n未尽事宜由双方另行协商,并签署书面补充协议。\n\n甲方(盖章):{{party_a}}\n乙方(签字):{{party_b}}\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",
|
|
"description": "适用于货物采购、交付与验收场景。",
|
|
"content": "甲方(采购方):{{buyer}}\n乙方(供应方):{{supplier}}\n\n鉴于甲方向乙方采购 {{product_name}},双方经协商一致,达成如下采购合同:\n\n一、采购标的\n产品名称:{{product_name}}\n规格型号:{{specification}}\n数量:{{quantity}}\n\n二、合同金额\n本合同总金额为人民币 {{amount}} 元。\n\n三、交付安排\n交付地点:{{delivery_location}}\n交付时间:{{delivery_date}}\n\n四、验收标准\n甲方应在收到货物后 {{acceptance_days}} 日内完成验收。\n\n五、付款方式\n甲方按如下方式付款:{{payment_terms}}\n\n六、违约责任\n任何一方违约的,应承担相应违约责任并赔偿损失。\n\n甲方(盖章):{{buyer}}\n乙方(盖章):{{supplier}}\n签署日期:{{sign_date}}",
|
|
"variables": [
|
|
{"name": "buyer", "label": "采购方名称"},
|
|
{"name": "supplier", "label": "供应方名称"},
|
|
{"name": "product_name", "label": "产品名称"},
|
|
{"name": "specification", "label": "规格型号"},
|
|
{"name": "quantity", "label": "采购数量"},
|
|
{"name": "amount", "label": "合同金额"},
|
|
{"name": "delivery_location", "label": "交付地点"},
|
|
{"name": "delivery_date", "label": "交付日期"},
|
|
{"name": "acceptance_days", "label": "验收期限(天)"},
|
|
{"name": "payment_terms", "label": "付款方式"},
|
|
{"name": "sign_date", "label": "签署日期"}
|
|
],
|
|
},
|
|
{
|
|
"name": "保密协议标准模板",
|
|
"contract_type": "nda",
|
|
"description": "适用于商务合作前的双向保密约定。",
|
|
"content": "披露方:{{disclosing_party}}\n接收方:{{receiving_party}}\n\n鉴于双方拟就 {{cooperation_matter}} 开展合作,接收方将接触披露方的保密信息。为保护双方合法权益,达成如下保密协议:\n\n一、保密信息范围\n包括但不限于 {{confidential_scope}}。\n\n二、保密义务\n接收方仅可为 {{use_purpose}} 之目的使用保密信息,未经披露方书面同意不得向第三方披露。\n\n三、保密期限\n保密义务自本协议生效之日起持续 {{confidential_term}}。\n\n四、违约责任\n如接收方违反本协议,应承担赔偿责任。\n\n五、争议解决\n双方同意提交 {{dispute_forum}} 解决争议。\n\n披露方(盖章):{{disclosing_party}}\n接收方(盖章):{{receiving_party}}\n签署日期:{{sign_date}}",
|
|
"variables": [
|
|
{"name": "disclosing_party", "label": "披露方"},
|
|
{"name": "receiving_party", "label": "接收方"},
|
|
{"name": "cooperation_matter", "label": "合作事项"},
|
|
{"name": "confidential_scope", "label": "保密信息范围"},
|
|
{"name": "use_purpose", "label": "使用目的"},
|
|
{"name": "confidential_term", "label": "保密期限"},
|
|
{"name": "dispute_forum", "label": "争议解决机构"},
|
|
{"name": "sign_date", "label": "签署日期"}
|
|
],
|
|
},
|
|
]
|
|
|
|
|
|
from fastapi import HTTPException, status
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.models.contract_template import ContractTemplate
|
|
from app.repositories.contract_template_repository import ContractTemplateRepository
|
|
|
|
|
|
class TemplateService:
|
|
def __init__(self, db: Session):
|
|
self.db = db
|
|
self.repository = ContractTemplateRepository(db)
|
|
|
|
def list_templates(
|
|
self,
|
|
current_user: Dict[str, Any],
|
|
page: int = 1,
|
|
page_size: int = 20,
|
|
contract_type: Optional[str] = None,
|
|
) -> dict:
|
|
self._require_admin(current_user)
|
|
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
|
|
items = self.repository.list_paginated(tenant_id, normalized_page, normalized_page_size, normalized_contract_type)
|
|
return {
|
|
"items": [self._to_item(item) for item in items],
|
|
"page": normalized_page,
|
|
"page_size": normalized_page_size,
|
|
"total": self.repository.count(tenant_id, normalized_contract_type),
|
|
}
|
|
|
|
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,
|
|
) -> dict:
|
|
self._require_admin(current_user)
|
|
contract_template = ContractTemplate(
|
|
id=f"tpl-{uuid4().hex[:12]}",
|
|
tenant_id=current_user["tenant_id"],
|
|
name=name.strip(),
|
|
contract_type=contract_type.strip() if contract_type else None,
|
|
description=description.strip() if description else None,
|
|
content=content.strip(),
|
|
variables_json=self._parse_variables(variables_json),
|
|
enabled=enabled,
|
|
created_by=current_user.get("id"),
|
|
updated_by=current_user.get("id"),
|
|
)
|
|
created = self.repository.create(contract_template)
|
|
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,
|
|
) -> dict:
|
|
self._require_admin(current_user)
|
|
contract_template = self._require_template(current_user, template_id)
|
|
contract_template.name = name.strip()
|
|
contract_template.contract_type = contract_type.strip() if contract_type else None
|
|
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.updated_by = current_user.get("id")
|
|
updated = self.repository.update(contract_template)
|
|
return self._to_item(updated)
|
|
|
|
def delete_template(self, current_user: Dict[str, Any], template_id: str) -> dict:
|
|
self._require_admin(current_user)
|
|
contract_template = self._require_template(current_user, template_id)
|
|
template_data = self._to_item(contract_template)
|
|
self.repository.delete(contract_template)
|
|
return template_data
|
|
|
|
def seed_default_templates(self, current_user: Dict[str, Any]) -> dict:
|
|
self._require_admin(current_user)
|
|
tenant_id = current_user["tenant_id"]
|
|
created_items: list[dict[str, Any]] = []
|
|
existing_items: list[dict[str, Any]] = []
|
|
|
|
for template_data in DEFAULT_TEMPLATES:
|
|
existing = self.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"],
|
|
description=template_data["description"],
|
|
content=template_data["content"],
|
|
variables_json=template_data["variables"],
|
|
enabled=True,
|
|
created_by=current_user.get("id"),
|
|
updated_by=current_user.get("id"),
|
|
)
|
|
created = self.repository.create(contract_template)
|
|
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),
|
|
}
|
|
|
|
def _require_template(self, current_user: Dict[str, Any], template_id: str) -> ContractTemplate:
|
|
contract_template = self.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]):
|
|
if current_user.get("role") != "admin":
|
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="forbidden")
|
|
|
|
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 _to_item(self, contract_template: ContractTemplate) -> dict:
|
|
return {
|
|
"id": contract_template.id,
|
|
"name": contract_template.name,
|
|
"contract_type": contract_template.contract_type,
|
|
"description": contract_template.description,
|
|
"content": contract_template.content,
|
|
"variables": contract_template.variables_json or [],
|
|
"enabled": contract_template.enabled,
|
|
"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(),
|
|
}
|