contract_server.py 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. import datetime
  2. import json
  3. import logging
  4. import os
  5. from typing import Optional, Any
  6. from sqlalchemy.ext.asyncio import AsyncSession
  7. from alien_lawyer.repositories.contract_repo import LawyerContractRepository
  8. from alien_lawyer.schemas.request.contract_lawyer import LawyerTemplatesCreate
  9. from alien_contract.infrastructure.esign import main as esign_main
  10. from alien_contract.infrastructure.esign.contract_builder import build_contract_items, ContractBuildError
  11. from alien_contract.infrastructure.esign.main import sign_url, file_download_url
  12. LOG_DIR = os.path.join("common", "logs", "alien_lawyer")
  13. os.makedirs(LOG_DIR, exist_ok=True)
  14. def _init_logger():
  15. logger = logging.getLogger("alien_lawyer_service")
  16. if logger.handlers:
  17. return logger
  18. logger.setLevel(logging.INFO)
  19. fmt = logging.Formatter("%(asctime)s [%(levelname)s] %(name)s %(message)s")
  20. info_handler = logging.FileHandler(os.path.join(LOG_DIR, "info.log"), encoding="utf-8")
  21. info_handler.setLevel(logging.INFO)
  22. info_handler.setFormatter(fmt)
  23. error_handler = logging.FileHandler(os.path.join(LOG_DIR, "error.log"), encoding="utf-8")
  24. error_handler.setLevel(logging.ERROR)
  25. error_handler.setFormatter(fmt)
  26. logger.addHandler(info_handler)
  27. logger.addHandler(error_handler)
  28. return logger
  29. logger = _init_logger()
  30. LAWYER_CONTRACT_CREATE_CONFIGS = [
  31. ("lawyer_agreement", "律所入驻协议", 1),
  32. ("alipay_auth", "支付宝授权函", 0),
  33. ("wechat_pay_commitment", "微信支付承诺函", 0),
  34. ]
  35. class LawyerContractServer:
  36. def __init__(self, db: AsyncSession):
  37. self.db = db
  38. self.repo = LawyerContractRepository(db)
  39. async def create_esign_templates(self, templates_data: LawyerTemplatesCreate) -> dict:
  40. logger.info("create lawyer esign templates request: %s", templates_data)
  41. try:
  42. generated_contracts = build_contract_items(
  43. configs=LAWYER_CONTRACT_CREATE_CONFIGS,
  44. template_name=templates_data.law_firm_name,
  45. signer_name=templates_data.law_firm_name,
  46. signer_id_num=templates_data.ord_id,
  47. psn_account=templates_data.contact_phone,
  48. psn_name=templates_data.contact_name,
  49. )
  50. except ContractBuildError as exc:
  51. return {"success": False, "message": exc.message, "raw": exc.raw}
  52. for contract_item in generated_contracts:
  53. await self.repo.append_contract_url(templates_data, contract_item)
  54. master_contract = next((item for item in generated_contracts if item.get("is_master") == 1), generated_contracts[0])
  55. return {
  56. "success": True,
  57. "message": "律所合同模板已追加/创建",
  58. "sign_flow_id": master_contract.get("sign_flow_id"),
  59. "file_id": master_contract.get("file_id"),
  60. "contract_url": master_contract.get("contract_url"),
  61. "created_contracts": [
  62. {
  63. "contract_type": item["contract_type"],
  64. "contract_name": item["contract_name"],
  65. "sign_flow_id": item["sign_flow_id"],
  66. "file_id": item["file_id"],
  67. "contract_url": item["contract_url"],
  68. }
  69. for item in generated_contracts
  70. ],
  71. }
  72. async def list_contracts(self, lawyer_id: int, status: Optional[int], page: int, page_size: int) -> dict:
  73. rows = await self.repo.get_by_lawyer_id(lawyer_id)
  74. all_filtered_items: list[dict[str, Any]] = []
  75. for row in rows:
  76. contract_url_raw = row.get("contract_url")
  77. if not contract_url_raw:
  78. continue
  79. try:
  80. items = json.loads(contract_url_raw)
  81. if not isinstance(items, list):
  82. continue
  83. for item in items:
  84. if status is not None and item.get("status") != status:
  85. continue
  86. item_with_info = dict(item)
  87. item_with_info.update(
  88. {
  89. "id": row.get("id"),
  90. "lawyer_id": row.get("lawyer_id"),
  91. "law_firm_name": row.get("law_firm_name"),
  92. "contact_name": row.get("contact_name"),
  93. "contact_phone": row.get("contact_phone"),
  94. }
  95. )
  96. all_filtered_items.append(item_with_info)
  97. except Exception:
  98. continue
  99. total = len(all_filtered_items)
  100. start = (page - 1) * page_size
  101. end = start + page_size
  102. paged_items = all_filtered_items[start:end]
  103. total_pages = (total + page_size - 1) // page_size if total > 0 else 0
  104. return {
  105. "items": paged_items,
  106. "total": total,
  107. "page": page,
  108. "page_size": page_size,
  109. "total_pages": total_pages,
  110. }
  111. async def get_contract_detail(self, sign_flow_id: str) -> dict:
  112. row, item, items = await self.repo.get_contract_item_by_sign_flow_id(sign_flow_id)
  113. if not item:
  114. return {"success": False, "message": "未找到合同"}
  115. status = item.get("status")
  116. if status == 0:
  117. return await self._get_pending_contract_detail(sign_flow_id, row, item, items)
  118. if status == 1:
  119. return await self._get_signed_contract_detail(sign_flow_id, row, item, items)
  120. return {"success": False, "message": "未知合同状态", "raw": {"status": status}}
  121. async def _get_pending_contract_detail(self, sign_flow_id: str, row, item, items) -> dict:
  122. file_id = item.get("file_id")
  123. if not file_id:
  124. return {"success": False, "message": "缺少 file_id,无法获取合同详情"}
  125. try:
  126. detail_resp = esign_main.get_contract_detail(file_id)
  127. detail_json = json.loads(detail_resp)
  128. data = detail_json.get("data") if isinstance(detail_json, dict) else None
  129. contract_url_val = None
  130. if isinstance(data, dict):
  131. contract_url_val = data.get("fileDownloadUrl")
  132. if not contract_url_val and isinstance(detail_json, dict):
  133. contract_url_val = detail_json.get("fileDownloadUrl")
  134. except Exception as exc:
  135. return {"success": False, "message": "获取合同链接失败", "raw": str(exc)}
  136. if row and isinstance(items, list):
  137. for it in items:
  138. if it.get("sign_flow_id") == sign_flow_id:
  139. it["contract_url"] = contract_url_val
  140. break
  141. await self.repo.update_contract_items(row["id"], items)
  142. contact_phone = item.get("contact_phone") or (row.get("contact_phone") if isinstance(row, dict) else None)
  143. if not contact_phone:
  144. return {"success": False, "message": "缺少 contact_phone,无法获取签署链接"}
  145. try:
  146. sign_resp = sign_url(sign_flow_id, contact_phone)
  147. sign_json = json.loads(sign_resp)
  148. sign_data = sign_json.get("data") if isinstance(sign_json, dict) else None
  149. result_sign_url = sign_data.get("url") if isinstance(sign_data, dict) else None
  150. except Exception as exc:
  151. return {"success": False, "message": "获取签署链接失败", "raw": str(exc)}
  152. if not result_sign_url:
  153. return {"success": False, "message": "e签宝返回缺少签署链接", "raw": sign_json}
  154. await self.repo.update_sign_url(contact_phone, sign_flow_id, result_sign_url)
  155. return {
  156. "status": 0,
  157. "contract_url": contract_url_val,
  158. "sign_url": result_sign_url,
  159. "sign_flow_id": sign_flow_id,
  160. }
  161. async def _get_signed_contract_detail(self, sign_flow_id: str, row, item, items) -> dict:
  162. try:
  163. download_resp = file_download_url(sign_flow_id)
  164. download_json = json.loads(download_resp)
  165. contract_download_url = download_json["data"]["files"][0]["downloadUrl"]
  166. except Exception as exc:
  167. return {"success": False, "message": "获取合同下载链接失败", "raw": str(exc)}
  168. if row and isinstance(items, list):
  169. for it in items:
  170. if it.get("sign_flow_id") == sign_flow_id:
  171. it["contract_download_url"] = contract_download_url
  172. it["contract_url"] = contract_download_url
  173. break
  174. await self.repo.update_contract_items(row["id"], items)
  175. return {
  176. "status": 1,
  177. "contract_url": contract_download_url,
  178. "contract_download_url": contract_download_url,
  179. "sign_flow_id": sign_flow_id,
  180. }
  181. async def process_esign_callback(self, payload: dict) -> dict:
  182. sign_result = payload.get("signResult")
  183. sign_flow_id = payload.get("signFlowId")
  184. operator = payload.get("operator") or {}
  185. psn_account = operator.get("psnAccount") or {}
  186. contact_phone = psn_account.get("accountMobile")
  187. ts_ms = payload.get("operateTime") or payload.get("timestamp")
  188. signing_dt = None
  189. if ts_ms:
  190. try:
  191. signing_dt = datetime.datetime.fromtimestamp(ts_ms / 1000)
  192. except Exception:
  193. signing_dt = None
  194. if sign_result == 2:
  195. contract_download_url = None
  196. try:
  197. download_resp = file_download_url(sign_flow_id)
  198. download_json = json.loads(download_resp)
  199. contract_download_url = download_json["data"]["files"][0]["downloadUrl"]
  200. except Exception:
  201. contract_download_url = None
  202. await self.repo.mark_signed_by_phone(contact_phone, sign_flow_id, signing_dt, contract_download_url)
  203. return {"success": True, "code": "200", "msg": "success"}
  204. return {"success": False, "message": "未处理: signResult!=2 或手机号/签署流程缺失"}