mengqiankang 2 месяцев назад
Родитель
Сommit
7cf75b550a

+ 29 - 5
alien_store/api/router.py

@@ -1,17 +1,19 @@
 from fastapi import APIRouter
 from fastapi import APIRouter
 from fastapi.params import Depends
 from fastapi.params import Depends
+from typing import Any
+import json
 from alien_store.api.deps import get_contract_service
 from alien_store.api.deps import get_contract_service
 from alien_store.schemas.request.contract_store import TemplatesCreate
 from alien_store.schemas.request.contract_store import TemplatesCreate
 from alien_store.services.contract_server import ContractServer
 from alien_store.services.contract_server import ContractServer
 from common.esigntool.main import fill_in_template
 from common.esigntool.main import fill_in_template
-import json
+import re, urllib.parse
+
 router = APIRouter()
 router = APIRouter()
 
 
 @router.get("/")
 @router.get("/")
 async def index():
 async def index():
     return {"module": "Contract", "status": "Ok"}
     return {"module": "Contract", "status": "Ok"}
 
 
-
 @router.post("/get_esign_templates")
 @router.post("/get_esign_templates")
 async def create_esign_templates(templates_data: TemplatesCreate, templates_server: ContractServer = Depends(get_contract_service)):
 async def create_esign_templates(templates_data: TemplatesCreate, templates_server: ContractServer = Depends(get_contract_service)):
     # 调用 e签宝生成文件
     # 调用 e签宝生成文件
@@ -24,9 +26,31 @@ async def create_esign_templates(templates_data: TemplatesCreate, templates_serv
     # 从返回结构提取下载链接,需与实际返回字段匹配
     # 从返回结构提取下载链接,需与实际返回字段匹配
     try:
     try:
         contract_url = res_data["data"]["fileDownloadUrl"]
         contract_url = res_data["data"]["fileDownloadUrl"]
+        file_id = res_data["data"]["fileId"]
+        m = re.search(r'/([^/]+)\.pdf', contract_url)
+        if m:
+            encoded_name = m.group(1)
+            file_name = urllib.parse.unquote(encoded_name)
+
     except Exception:
     except Exception:
         return {"success": False, "message": "e签宝返回缺少 fileDownloadUrl", "raw": res_data}
         return {"success": False, "message": "e签宝返回缺少 fileDownloadUrl", "raw": res_data}
-
     # pydantic v2 使用 model_copy 更新字段
     # pydantic v2 使用 model_copy 更新字段
-    data_with_url = templates_data.model_copy(update={"contract_url": contract_url, "seal_url": None})
-    return await templates_server.create_template(data_with_url)
+    result_contract = {
+        "contract_url": contract_url,
+        "file_name": file_name,
+        "file_id": file_id,
+        "status": 0
+    }
+    list_contract = [result_contract]
+    # contract_url 字段为字符串,存储 JSON 字符串避免类型错误
+    result_data = templates_data.model_copy(
+        update={"contract_url": json.dumps(list_contract, ensure_ascii=False), "seal_url": None}
+    )
+    return await templates_server.create_template(result_data)
+
+
+@router.get("/contracts/{store_id}")
+async def list_contracts(store_id: int, templates_server: ContractServer = Depends(get_contract_service)) -> Any:
+    """根据 store_id 查询所有合同"""
+    rows = await templates_server.list_by_store(store_id)
+    return rows

+ 4 - 3
alien_store/repositories/contract_repo.py

@@ -8,12 +8,13 @@ class ContractRepository:
     def __init__(self, db: AsyncSession):
     def __init__(self, db: AsyncSession):
         self.db = db
         self.db = db
 
 
-    async def get_by_id(self, store_id: int):
+    async def get_by_store_id(self, store_id: int):
         """根据店铺id查询所有合同"""
         """根据店铺id查询所有合同"""
         result = await self.db.execute(
         result = await self.db.execute(
-            ContractStore.__table__.select().where(ContractStore.id == store_id)
+            ContractStore.__table__.select().where(ContractStore.store_id == store_id)
         )
         )
-        return result.fetchall()
+        # 返回列表[dict],避免 Pydantic 序列化 Row 对象出错
+        return [dict(row) for row in result.mappings().all()]
 
 
     async def create(self, user_data):
     async def create(self, user_data):
         """创建未签署合同模板"""
         """创建未签署合同模板"""

+ 1 - 1
alien_store/schemas/request/contract_store.py

@@ -8,6 +8,6 @@ class TemplatesCreate(BaseModel):
     business_segment: str = Field(description="入驻店铺经营板块")
     business_segment: str = Field(description="入驻店铺经营板块")
     merchant_name: str = Field(description="商家姓名")
     merchant_name: str = Field(description="商家姓名")
     contact_phone: str = Field(description="联系电话")
     contact_phone: str = Field(description="联系电话")
-    contract_url: str | None = Field(default=None, description="合同下载地址")
+    contract_url: str | None = Field(default=None, description="合同下载地址,合同文件id,以及签署状态")
     seal_url: str | None = Field(default=None, description="印章文件地址")
     seal_url: str | None = Field(default=None, description="印章文件地址")
 
 

+ 3 - 0
alien_store/services/contract_server.py

@@ -11,3 +11,6 @@ class ContractServer:
     async def create_template(self, template_data: TemplatesCreate):
     async def create_template(self, template_data: TemplatesCreate):
         await self.esign_repo.create(template_data)
         await self.esign_repo.create(template_data)
         return {"message": "模板创建成功"}
         return {"message": "模板创建成功"}
+
+    async def list_by_store(self, store_id: int):
+        return await self.esign_repo.get_by_store_id(store_id)

+ 142 - 1
common/esigntool/main.py

@@ -66,4 +66,145 @@ def fill_in_template(name):
     print(resp.text)
     print(resp.text)
     return resp.text
     return resp.text
 
 
-fill_in_template("哈哈")
+def create_by_file(file_id, file_name,  contact_phone, merchant_name):
+    """基于文件发起签署"""
+    api_path = "/v3/sign-flow/create-by-file"
+    method = "POST"
+    body = {
+        "docs": [
+            {
+                "fileId": file_id,
+                "fileName": f"{file_name}.pdf"
+            }
+        ],
+        "signFlowConfig": {
+            "signFlowTitle": "商家入驻U店的签署协议", # 请设置当前签署任务的主题
+            "autoFinish": True,
+            "noticeConfig": {
+                "noticeTypes": "" #
+                # """通知类型,通知发起方、签署方、抄送方,默认不通知(值为""空字符串),允许多种通知方式,请使用英文逗号分隔
+                #
+                # "" - 不通知(默认值)
+                #
+                # 1 - 短信通知(如果套餐内带“分项”字样,请确保开通【电子签名流量费(分项)认证】中的子项:【短信服务】,否则短信通知收不到)
+                #
+                # 2 - 邮件通知
+                #
+                # 3 - 钉钉工作通知(需使用e签宝钉签产品)
+                #
+                # 5 - 微信通知(用户需关注“e签宝电子签名”微信公众号且使用过e签宝微信小程序)
+                #
+                # 6 - 企业微信通知(需要使用e签宝企微版产品)
+                #
+                # 7 - 飞书通知(需要使用e签宝飞书版产品)
+                #
+                # 补充说明:
+                #
+                # 1、2:个人账号中需要绑定短信/邮件才有对应的通知方式;
+                # 3、5、6、7:仅限e签宝正式环境调用才会有。"""
+            },
+            "notifyUrl": "http://120.26.186.130:33333", # 接收相关回调通知的Web地址,
+            "redirectConfig": {
+                "redirectUrl": "https://www.esign.cn/"
+            }
+        },
+        "signers": [
+            {
+                "signConfig": {
+                    "signOrder": 1
+                },
+                "signerType": 1,
+                "signFields": [
+                    {
+                        "customBizNum": "9527", # 开发者自定义业务编号
+                        "fileId": file_id,  #签署区所在待签署文件ID 【注】这里的fileId需先添加在docs数组中,否则会报错“参数错误: 文件id不在签署流程中”。
+                        "normalSignFieldConfig": {
+                            "autoSign": True,
+                            "signFieldStyle": 1,
+                            "signFieldPosition": {
+                                "positionPage": "7",
+                                "positionX": 114,  # 获取需要盖章的位置: https://open.esign.cn/tools/seal-position
+                                "positionY": 666
+                            }
+                        }
+                    }
+                ]
+            },
+            {
+                "psnSignerInfo": {
+                    "psnAccount": contact_phone,
+                    "psnInfo": {
+                        "psnName": merchant_name
+                    }
+                },
+                "signConfig": {
+                    "forcedReadingTime": 10,
+                    "signOrder": 2
+                },
+                "signerType": 0,
+                "signFields": [
+                    {
+                        "customBizNum": "9527",
+                        "fileId": file_id,
+                        "normalSignFieldConfig": {
+                            "signFieldStyle": 1,
+                            "signFieldPosition": {
+                                "positionPage": "7",
+                                "positionX": 294,
+                                "positionY": 668
+                            }
+                        }
+                    }
+                ]
+            }
+        ]
+    }
+    json_headers = buildSignJsonHeader(config.appId, config.scert, method, api_path, body=body)
+    body_json = json.dumps(body, separators=(",", ":"), ensure_ascii=False)
+    resp = requests.request(method, config.host + api_path, data=body_json, headers=json_headers)
+    print(resp.text)
+    return resp.text
+
+# create_by_file(file_id, file_name, store_name, ord_id)
+
+def sign_url(sign_flow_id):
+    api_path = f"/v3/sign-flow/{sign_flow_id}/sign-url"
+    method = "POST"
+    body = {
+    "signFlowId": sign_flow_id,
+    "clientType": "ALL",
+    "needLogin": False,
+    "operator": {
+    "psnAccount": "13923864580"
+  },
+  "urlType": 2
+}
+    json_headers = buildSignJsonHeader(config.appId, config.scert, method, api_path, body=body)
+    body_json = json.dumps(body, separators=(",", ":"), ensure_ascii=False)
+    resp = requests.request(method, config.host + api_path, data=body_json, headers=json_headers)
+    print(resp.text)
+    return resp.text
+
+
+
+
+
+
+def file_download_url(sign_flow_id):
+    """下载已签署文件及附属材料"""
+    api_path = f"/v3/sign-flow/{sign_flow_id}/file-download-url"
+    method = "POST"
+    body = {
+    "urlAvailableDate": "3600"
+}
+    json_headers = buildSignJsonHeader(config.appId, config.scert, method, api_path, body=body)
+    body_json = json.dumps(body, separators=(",", ":"), ensure_ascii=False)
+    resp = requests.request(method, config.host + api_path, data=body_json, headers=json_headers)
+    print(resp.text)
+    return resp.text
+
+sing_data = create_by_file("fd3d1247ed2a40ef8d47d8c56b14791d", "U店在这-商户入驻协议",  "13503301290", "孟骞康")
+sign_json  = json.loads(sing_data)
+sing_id = sign_json["data"]["signFlowId"]
+sign_url(sing_id)
+file_download_url(sing_id)