Просмотр исходного кода

修复合同包并发回调旧快照覆盖导致文档已签但合同包仍显示未签的问题

Co-authored-by: Cursor <cursoragent@cursor.com>
天空之城 3 часов назад
Родитель
Сommit
820fcd1902
2 измененных файлов с 14 добавлено и 1 удалено
  1. 12 1
      alien_contract/repositories/contract_repo.py
  2. 2 0
      alien_contract/services/contract_server.py

+ 12 - 1
alien_contract/repositories/contract_repo.py

@@ -118,8 +118,19 @@ class ContractRepository:
         stmt = ContractDocument.__table__.update().where(ContractDocument.id == document_id).values(**values)
         stmt = ContractDocument.__table__.update().where(ContractDocument.id == document_id).values(**values)
         await self.db.execute(stmt)
         await self.db.execute(stmt)
 
 
+    async def lock_bundle(self, bundle_id: int) -> None:
+        # 行级锁:串行化同一合同包的并发回调,避免 recalc 读到旧快照把"已签署"覆盖回"未签署"
+        await self.db.execute(
+            select(ContractBundle.id).where(ContractBundle.id == bundle_id).with_for_update()
+        )
+
     async def recalc_bundle_status(self, bundle_id: int) -> str:
     async def recalc_bundle_status(self, bundle_id: int) -> str:
-        stmt = select(ContractDocument.status).where(ContractDocument.bundle_id == bundle_id, ContractDocument.delete_flag == 0)
+        # with_for_update 强制当前读,确保读到其它已提交回调写入的最新文档状态
+        stmt = (
+            select(ContractDocument.status)
+            .where(ContractDocument.bundle_id == bundle_id, ContractDocument.delete_flag == 0)
+            .with_for_update()
+        )
         result = await self.db.execute(stmt)
         result = await self.db.execute(stmt)
         statuses = [row[0] for row in result.fetchall()]
         statuses = [row[0] for row in result.fetchall()]
         if not statuses:
         if not statuses:

+ 2 - 0
alien_contract/services/contract_server.py

@@ -376,6 +376,8 @@ class ContractCenterService:
         )
         )
 
 
         if mark_signed:
         if mark_signed:
+            # 先对合同包加行锁,串行化同一合同包的并发回调,避免 recalc 读到旧快照把已签覆盖回未签
+            await self.repo.lock_bundle(bundle.id)
             ts_ms = finish_ms or payload.get("operateTime") or payload.get("timestamp")
             ts_ms = finish_ms or payload.get("operateTime") or payload.get("timestamp")
             signing_dt = _ms_to_naive_cn(ts_ms)
             signing_dt = _ms_to_naive_cn(ts_ms)
             effective_dt = expiry_dt = None
             effective_dt = expiry_dt = None