Răsfoiți Sursa

签署流程over

mengqiankang 2 luni în urmă
părinte
comite
3eb80e3532

+ 44 - 8
alien_store/api/router.py

@@ -1,11 +1,12 @@
-from fastapi import APIRouter
-from fastapi.params import Depends
+import datetime
+from fastapi import APIRouter, Depends, Query
 from typing import Any
 import json
+from datetime import datetime
 from alien_store.api.deps import get_contract_service
 from alien_store.schemas.request.contract_store import TemplatesCreate
 from alien_store.services.contract_server import ContractServer
-from common.esigntool.main import fill_in_template
+from common.esigntool.main import *
 import re, urllib.parse
 
 router = APIRouter()
@@ -22,7 +23,6 @@ async def create_esign_templates(templates_data: TemplatesCreate, templates_serv
         res_data = json.loads(res_text)
     except json.JSONDecodeError:
         return {"success": False, "message": "e签宝返回非 JSON", "raw": res_text}
-
     # 从返回结构提取下载链接,需与实际返回字段匹配
     try:
         contract_url = res_data["data"]["fileDownloadUrl"]
@@ -31,15 +31,18 @@ async def create_esign_templates(templates_data: TemplatesCreate, templates_serv
         if m:
             encoded_name = m.group(1)
             file_name = urllib.parse.unquote(encoded_name)
-
     except Exception:
         return {"success": False, "message": "e签宝返回缺少 fileDownloadUrl", "raw": res_data}
-    # pydantic v2 使用 model_copy 更新字段
+
+    sign_data = create_by_file(file_id, file_name, templates_data.contact_phone, templates_data.merchant_name)
+    sign_json = json.loads(sign_data)
+    sing_id = sign_json["data"]["signFlowId"]
     result_contract = {
         "contract_url": contract_url,
         "file_name": file_name,
         "file_id": file_id,
-        "status": 0
+        "status": 0,
+        "sign_flow_id": sing_id,
     }
     list_contract = [result_contract]
     # contract_url 字段为字符串,存储 JSON 字符串避免类型错误
@@ -53,4 +56,37 @@ async def create_esign_templates(templates_data: TemplatesCreate, templates_serv
 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
+    return rows
+
+@router.get("/get_all_templates")
+async def get_all_templates(
+    page: int = Query(1, ge=1, description="页码,从1开始"),
+    page_size: int = Query(10, ge=1, le=100, description="每页条数,默认10"),
+    templates_server: ContractServer = Depends(get_contract_service)
+) -> Any:
+    """分页查询所有合同"""
+    return await templates_server.list_all_paged(page, page_size)
+
+@router.post("/esign/callback")
+async def esign_callback(payload: dict, templates_server: ContractServer = Depends(get_contract_service)) -> Any:
+    """
+    e签宝签署结果回调
+    需求:签署完成 -> 更新 signing_status=已签署,contract_url 中 status=1
+    """
+    sign_result = payload.get("signResult")
+    operator = payload.get("operator") or {}
+    psn_account = operator.get("psnAccount") or {}
+    contact_phone = psn_account.get("accountMobile")
+    # 取回调中的毫秒时间戳,优先 operateTime,其次 timestamp
+    ts_ms = payload.get("operateTime") or payload.get("timestamp")
+    signing_dt = None
+    if ts_ms:
+        try:
+            signing_dt = datetime.fromtimestamp(ts_ms / 1000)
+        except Exception:
+            signing_dt = None
+
+    if sign_result == 2 and contact_phone:
+        updated = await templates_server.mark_signed_by_phone(contact_phone, signing_dt)
+        return {"success": True, "updated": updated}
+    return {"success": False, "message": "未处理: signResult!=2 或手机号缺失"}

+ 3 - 0
alien_store/db/models/contract_store.py

@@ -19,4 +19,7 @@ class ContractStore(Base, AuditMixin):
     signing_status: Mapped[str] = mapped_column(String(20), default="未签署", comment="签署状态(已签署,未签署,已到期)")
     contract_url: Mapped[str] = mapped_column(LONGTEXT, comment='合同URL')
     seal_url: Mapped[str] = mapped_column(LONGTEXT, comment='印章URL')
+    signing_time: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True, comment="签署时间")
+    effective_time: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True, comment="生效时间")
+    expiry_time: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True, comment="到期时间")
 

+ 62 - 0
alien_store/repositories/contract_repo.py

@@ -1,5 +1,7 @@
 from sqlalchemy.ext.asyncio import AsyncSession
 from alien_store.db.models.contract_store import ContractStore
+import json
+from datetime import datetime, timedelta
 
 
 class ContractRepository:
@@ -16,6 +18,19 @@ class ContractRepository:
         # 返回列表[dict],避免 Pydantic 序列化 Row 对象出错
         return [dict(row) for row in result.mappings().all()]
 
+    async def get_all(self):
+        """查询所有合同"""
+        result = await self.db.execute(ContractStore.__table__.select())
+        return [dict(row) for row in result.mappings().all()]
+
+    async def get_all_paged(self, page: int, page_size: int = 10):
+        """分页查询所有合同"""
+        offset = (page - 1) * page_size
+        result = await self.db.execute(
+            ContractStore.__table__.select().offset(offset).limit(page_size)
+        )
+        return [dict(row) for row in result.mappings().all()]
+
     async def create(self, user_data):
         """创建未签署合同模板"""
         db_templates = ContractStore(
@@ -25,12 +40,59 @@ class ContractRepository:
             contact_phone=user_data.contact_phone,
             contract_url=user_data.contract_url,
             seal_url='0.0',
+            signing_status='未签署'
         )
         self.db.add(db_templates)
         await self.db.commit()
         await self.db.refresh(db_templates)
         return db_templates
 
+    async def mark_signed_by_phone(self, contact_phone: str, signing_time: datetime | None = None):
+        """
+        根据手机号将合同标记为已签署,并更新 contract_url 内的 status=1
+        同时写入签署/生效/到期时间(签署时间=T,生效=T+1天,失效=生效+365天)
+        """
+        result = await self.db.execute(
+            ContractStore.__table__.select().where(ContractStore.contact_phone == contact_phone)
+        )
+        rows = result.mappings().all()
+        updated = False
+        for row in rows:
+            contract_url_raw = row.get("contract_url")
+            items = None
+            if contract_url_raw:
+                try:
+                    items = json.loads(contract_url_raw)
+                except Exception:
+                    items = None
+            changed = False
+            if isinstance(items, list):
+                for item in items:
+                    item["status"] = 1
+                    changed = True
+            # 时间处理
+            signing_dt = signing_time
+            effective_dt = expiry_dt = None
+            if signing_dt:
+                effective_dt = signing_dt + timedelta(days=1)
+                expiry_dt = effective_dt + timedelta(days=365)
+            if changed or True:
+                await self.db.execute(
+                    ContractStore.__table__.update()
+                    .where(ContractStore.id == row["id"])
+                    .values(
+                        signing_status="已签署",
+                        contract_url=json.dumps(items, ensure_ascii=False) if items else contract_url_raw,
+                        signing_time=signing_dt,
+                        effective_time=effective_dt,
+                        expiry_time=expiry_dt,
+                    )
+                )
+                updated = True
+        if updated:
+            await self.db.commit()
+        return updated
+
 
 
 

+ 10 - 2
alien_store/services/contract_server.py

@@ -10,7 +10,15 @@ class ContractServer:
 
     async def create_template(self, template_data: TemplatesCreate):
         await self.esign_repo.create(template_data)
-        return {"message": "模板创建成功"}
-
+        return {
+            "message": "模板创建成功",
+            "template_data": template_data
+        }
     async def list_by_store(self, store_id: int):
         return await self.esign_repo.get_by_store_id(store_id)
+
+    async def list_all_paged(self, page: int, page_size: int = 10):
+        return await self.esign_repo.get_all_paged(page, page_size)
+
+    async def mark_signed_by_phone(self, contact_phone: str, signing_time):
+        return await self.esign_repo.mark_signed_by_phone(contact_phone, signing_time)

+ 8 - 16
common/esigntool/main.py

@@ -28,7 +28,6 @@ def get_auth_flow_id():
 
 # get_auth_flow_id()
 
-
 def get_template_detail():
     """查询合同模板中控件详情"""
     api_path = f"/v3/doc-templates/{config.templates_id}"
@@ -37,7 +36,6 @@ def get_template_detail():
     resp = requests.request(method, config.host + api_path, headers=json_headers)
     print(resp.text)
 
-
 def fill_in_template(name):
     """填写模板生成文件"""
     api_path = "/v3/files/create-by-doc-template"
@@ -165,9 +163,7 @@ def create_by_file(file_id, file_name,  contact_phone, merchant_name):
     print(resp.text)
     return resp.text
 
-# create_by_file(file_id, file_name, store_name, ord_id)
-
-def sign_url(sign_flow_id):
+def sign_url(sign_flow_id, contact_phone):
     api_path = f"/v3/sign-flow/{sign_flow_id}/sign-url"
     method = "POST"
     body = {
@@ -175,7 +171,7 @@ def sign_url(sign_flow_id):
     "clientType": "ALL",
     "needLogin": False,
     "operator": {
-    "psnAccount": "13923864580"
+    "psnAccount": contact_phone
   },
   "urlType": 2
 }
@@ -185,11 +181,6 @@ def sign_url(sign_flow_id):
     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"
@@ -203,8 +194,9 @@ def file_download_url(sign_flow_id):
     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)
+# fill_in_template("我勒个去")
+# sing_data = create_by_file("9817792d17e34c3db7ed78c0e7e7444f", "U店在这-商户入驻协议",  "13503301290", "孟骞康")
+# sign_json  = json.loads(sing_data)
+# sing_id = sign_json["data"]["signFlowId"]
+# sign_url(sing_id, "13503301290")
+# file_download_url("56245b135f5546f39329cb2aea47a7d0")

+ 1 - 0
pyproject.toml

@@ -22,6 +22,7 @@ pymysql = "^1.1.0"
 redis = "^5.0.1"
 requests = "^2.32.5"
 aiomysql = "^0.3.2"
+datetime = "^6.0"
 
 [build-system]
 requires = ["poetry-core>=1.0.0"]