service.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. from typing import Optional, cast
  2. from app.api.v1.module_system.auth.schema import AuthSchema
  3. from app.api.v1.module_system.user.crud import UserCRUD
  4. from app.api.v1.module_system.user.schema import UserCreateSchema
  5. from app.core.alipay import AlipayClient
  6. from app.core.exceptions import CustomException
  7. from app.core.logger import log
  8. from app.utils.hash_bcrpy_util import PwdUtil
  9. from .crud import EmployeeCRUD
  10. from .schema import (
  11. EmployeeCreateOrUpdateSchema,
  12. EmployeeListOutSchema,
  13. EmployeeOperationOutSchema,
  14. EmployeeInviteQuerySchema,
  15. EmployeeInviteQueryOutSchema,
  16. EmployeeOutSchema,
  17. )
  18. from alipay.aop.api.domain.EmployeeInfoDTO import EmployeeInfoDTO
  19. class EmployeeService:
  20. """员工服务层"""
  21. @classmethod
  22. async def add_employee_service(
  23. cls, auth: AuthSchema, data: EmployeeCreateOrUpdateSchema
  24. ) -> EmployeeOperationOutSchema:
  25. """
  26. 添加员工
  27. 调用: alipay.commerce.ec.employee.add
  28. """
  29. crud = EmployeeCRUD(auth)
  30. from alipay.aop.api.request.AlipayCommerceEcEmployeeAddRequest import (
  31. AlipayCommerceEcEmployeeAddRequest,
  32. )
  33. from alipay.aop.api.domain.AlipayCommerceEcEmployeeAddModel import (
  34. AlipayCommerceEcEmployeeAddModel,
  35. )
  36. from alipay.aop.api.response.AlipayCommerceEcEmployeeAddResponse import (
  37. AlipayCommerceEcEmployeeAddResponse,
  38. )
  39. model = AlipayCommerceEcEmployeeAddModel()
  40. # 必选
  41. model.enterprise_id = data.enterprise_id
  42. model.employee_name = data.employee_name
  43. # 身份标识(identity_type+identity)、身份证(employee_cert_type+employee_cert_no)、
  44. # 手机号、邮箱四者必选其一; 当传入多个时,优先级为:身份标识>身份证>手机号>邮箱
  45. model.identity_type = data.identity_type
  46. model.identity = data.identity
  47. model.identity_open_id = data.identity_open_id
  48. model.employee_mobile = data.employee_mobile
  49. model.employee_email = data.employee_email
  50. model.employee_cert_type = data.employee_cert_type
  51. model.employee_cert_no = data.employee_cert_no
  52. model.iot_check_type = data.iot_check_type
  53. model.employee_no = data.employee_no
  54. model.department_ids = data.department_ids
  55. model.accounting_entity_ids = data.accounting_entity_ids
  56. model.label_names = data.label_names
  57. model.sign_return_url = data.sign_return_url
  58. model.create_share_code = data.create_share_code
  59. model.sign_url_carry_info = data.sign_url_carry_info
  60. model.profiles = data.profiles
  61. request = AlipayCommerceEcEmployeeAddRequest()
  62. request.biz_model = model
  63. client = AlipayClient.get_client()
  64. response = client.execute(request)
  65. if not response:
  66. raise CustomException(msg="添加员工失败: 无响应")
  67. result = AlipayCommerceEcEmployeeAddResponse()
  68. result.parse_response_content(response)
  69. if not result.is_success():
  70. log.error(f"支付宝接口调用失败: {result.code} - {result.msg}")
  71. raise CustomException(msg=f"添加员工失败: {result.sub_msg or result.msg or result.code}")
  72. result_data = EmployeeOperationOutSchema(
  73. employee_id=result.employee_id,
  74. sign_url=result.sign_url,
  75. share_code=result.share_code,
  76. iot_unique_id=result.iot_unique_id,
  77. )
  78. create_data_dict = result_data.model_dump()
  79. create_data_dict.update(data.model_dump(exclude_none=True))
  80. # 自动创建系统用户记录
  81. user_crud = UserCRUD(auth)
  82. # 检查用户是否已存在
  83. username = str(result.employee_id)
  84. existing_user = await user_crud.get_by_username_crud(username=username)
  85. if not existing_user:
  86. # 创建新用户
  87. user_data = UserCreateSchema(
  88. username=username,
  89. password=PwdUtil.set_password_hash(password="123456"), # 默认密码
  90. name=data.employee_name,
  91. mobile=data.employee_mobile,
  92. email=data.employee_email,
  93. tenant_id=auth.tenant_id,
  94. # role_ids=[11],
  95. # status="0", # 启用状态
  96. description=f"e:{data.enterprise_id}:{result.employee_id}"
  97. )
  98. user_dict = user_data.model_dump(exclude_unset=True)
  99. new_user = await user_crud.create(data=user_dict, skip_tenant_id=True)
  100. create_data_dict["user_id"] = new_user.id
  101. else:
  102. create_data_dict["user_id"] = existing_user.id
  103. await crud.create(data=create_data_dict)
  104. return result_data
  105. @classmethod
  106. async def list_service(
  107. cls,
  108. auth: AuthSchema,
  109. page_no: int = 1,
  110. page_size: int = 20,
  111. search: dict | None = None,
  112. ) -> dict:
  113. """
  114. 查询员工列表
  115. """
  116. crud = EmployeeCRUD(auth)
  117. offset = (page_no - 1) * page_size
  118. return await crud.page(
  119. offset=offset,
  120. limit=page_size,
  121. order_by=[{"id": "desc"}],
  122. search=search or {},
  123. out_schema=EmployeeListOutSchema,
  124. preload=["user"]
  125. )
  126. @classmethod
  127. async def info_service(
  128. cls, auth: AuthSchema, employee_id: Optional[str], employee_email: Optional[str], employee_mobile: Optional[str], enterprise_id: str
  129. ) -> EmployeeOutSchema:
  130. crud = EmployeeCRUD(auth)
  131. out_data = await crud.get(employee_id=employee_id, employee_email=employee_email, employee_mobile=employee_mobile, enterprise_id=enterprise_id)
  132. if not out_data:
  133. raise CustomException(msg="员工不存在")
  134. result = EmployeeOutSchema.model_validate(out_data)
  135. # 补充 account_id
  136. if enterprise_id:
  137. from app.plugin.module_payment.enterprise.model import EnterpriseModel
  138. from sqlalchemy import select
  139. ent_stmt = select(EnterpriseModel).where(EnterpriseModel.enterprise_id == enterprise_id).limit(1)
  140. ent_result = await auth.db.execute(ent_stmt)
  141. ent = ent_result.scalar_one_or_none()
  142. if ent and ent.account_id:
  143. result.account_id = ent.account_id
  144. # 补充 user_name / avatar
  145. user = getattr(out_data, 'user', None)
  146. if user:
  147. result.user_id = user.id
  148. result.user_name = user.name or user.username
  149. result.avatar = getattr(user, 'avatar', None)
  150. return result
  151. @classmethod
  152. async def detail_service(
  153. cls, auth: AuthSchema, employee_id: Optional[str], employee_email: Optional[str], employee_mobile: Optional[str], enterprise_id: str
  154. ) -> dict:
  155. """
  156. 查询员工详情
  157. 调用: alipay.commerce.ec.employee.info.query
  158. """
  159. from alipay.aop.api.request.AlipayCommerceEcEmployeeInfoQueryRequest import AlipayCommerceEcEmployeeInfoQueryRequest
  160. from alipay.aop.api.domain.AlipayCommerceEcEmployeeInfoQueryModel import AlipayCommerceEcEmployeeInfoQueryModel
  161. from alipay.aop.api.response.AlipayCommerceEcEmployeeInfoQueryResponse import AlipayCommerceEcEmployeeInfoQueryResponse
  162. model = AlipayCommerceEcEmployeeInfoQueryModel()
  163. model.enterprise_id = enterprise_id
  164. model.employee_id = employee_id
  165. model.employee_email = employee_email
  166. model.mobile = employee_mobile
  167. request = AlipayCommerceEcEmployeeInfoQueryRequest()
  168. request.biz_model = model
  169. client = AlipayClient.get_client()
  170. response = client.execute(request)
  171. if not response:
  172. raise CustomException(msg="查询员工详情失败: 无响应")
  173. result = AlipayCommerceEcEmployeeInfoQueryResponse()
  174. result.parse_response_content(response)
  175. if not result.is_success():
  176. log.error(f"支付宝接口调用失败: {result.code} - {result.msg}")
  177. raise CustomException(msg=f"查询员工详情失败: {result.msg}")
  178. return EmployeeInfoDTO.to_alipay_dict(cast(EmployeeInfoDTO, result.employee_info))
  179. @classmethod
  180. async def delete_employee_service(
  181. cls, auth: AuthSchema, employee_id: str, enterprise_id: str
  182. ) -> EmployeeOperationOutSchema:
  183. """
  184. 删除员工
  185. 调用: alipay.commerce.ec.employee.delete
  186. """
  187. from alipay.aop.api.request.AlipayCommerceEcEmployeeDeleteRequest import AlipayCommerceEcEmployeeDeleteRequest
  188. from alipay.aop.api.domain.AlipayCommerceEcEmployeeDeleteModel import AlipayCommerceEcEmployeeDeleteModel
  189. from alipay.aop.api.response.AlipayCommerceEcEmployeeDeleteResponse import AlipayCommerceEcEmployeeDeleteResponse
  190. model = AlipayCommerceEcEmployeeDeleteModel()
  191. model.enterprise_id = enterprise_id
  192. model.employee_id = employee_id
  193. request = AlipayCommerceEcEmployeeDeleteRequest()
  194. request.biz_model = model
  195. client = AlipayClient.get_client()
  196. response = client.execute(request)
  197. if not response:
  198. raise CustomException(msg="删除员工失败: 无响应")
  199. result = AlipayCommerceEcEmployeeDeleteResponse()
  200. result.parse_response_content(response)
  201. if not result.is_success():
  202. log.error(f"支付宝接口调用失败: {result.code} - {result.msg}")
  203. raise CustomException(msg=f"删除员工失败: {result.sub_msg or result.msg or result.code}")
  204. # 从本地数据库删除, 并删除关联的用户
  205. # 先查询员工是否存在
  206. crud = EmployeeCRUD(auth)
  207. employee = await crud.get(employee_id=employee_id, enterprise_id=enterprise_id, preload=["user"])
  208. if not employee:
  209. raise CustomException(msg=f"员工 {employee_id} 不存在")
  210. # 解约联动:从所有引用该员工的费控制度中移除
  211. try:
  212. from app.plugin.module_payment.expense.institution.scope_sync import remove_employee_from_institution_scopes
  213. await remove_employee_from_institution_scopes(
  214. auth=auth, enterprise_id=enterprise_id, employee_id=employee_id
  215. )
  216. except Exception as e:
  217. log.warning(f"从制度移除解约员工失败(不影响主体操作): {e}")
  218. # 先删除关联的用户
  219. if employee.user_id:
  220. user_service = UserCRUD(auth)
  221. await user_service.delete(ids=[employee.user_id])
  222. await crud.delete_by_employee_id(employee_id)
  223. return EmployeeOperationOutSchema(
  224. employee_id=employee_id,
  225. )
  226. @classmethod
  227. async def invite_query_service(
  228. cls, auth: AuthSchema, data: EmployeeInviteQuerySchema
  229. ) -> EmployeeInviteQueryOutSchema:
  230. """
  231. 获取员工签约激活链接
  232. 调用: alipay.commerce.ec.employee.invite.query
  233. """
  234. from alipay.aop.api.request.AlipayCommerceEcEmployeeInviteQueryRequest import AlipayCommerceEcEmployeeInviteQueryRequest
  235. from alipay.aop.api.domain.AlipayCommerceEcEmployeeInviteQueryModel import AlipayCommerceEcEmployeeInviteQueryModel
  236. from alipay.aop.api.response.AlipayCommerceEcEmployeeInviteQueryResponse import AlipayCommerceEcEmployeeInviteQueryResponse
  237. model = AlipayCommerceEcEmployeeInviteQueryModel()
  238. model.enterprise_id = data.enterprise_id
  239. model.employee_id = data.employee_id
  240. model.page_content_code = data.page_content_code
  241. model.withholding_sign_str = data.withholding_sign_str
  242. model.create_share_code = data.create_share_code
  243. request = AlipayCommerceEcEmployeeInviteQueryRequest()
  244. request.biz_model = model
  245. client = AlipayClient.get_client()
  246. response = client.execute(request)
  247. if not response:
  248. raise CustomException(msg="获取员工签约激活链接失败: 无响应")
  249. result = AlipayCommerceEcEmployeeInviteQueryResponse()
  250. result.parse_response_content(response)
  251. if not result.is_success():
  252. log.error(f"支付宝接口调用失败: {result.code} - {result.msg}")
  253. raise CustomException(msg=f"获取员工签约激活链接失败: {result.msg}")
  254. return EmployeeInviteQueryOutSchema(
  255. enterprise_id=result.enterprise_id or data.enterprise_id,
  256. sign_url=result.sign_url or "",
  257. mini_app_sign_url=result.mini_app_sign_url or "",
  258. share_code=getattr(result, 'share_code', None)
  259. )
  260. @classmethod
  261. async def update_employee_from_alipay(cls, auth: AuthSchema, data: EmployeeCreateOrUpdateSchema):
  262. """
  263. 从支付宝更新员工信息
  264. """
  265. # 先查询支付宝员工信息
  266. employee = await cls.detail_service(
  267. auth=auth,
  268. employee_id=data.employee_id,
  269. employee_email=data.employee_email,
  270. employee_mobile=data.employee_mobile,
  271. enterprise_id=data.enterprise_id
  272. )
  273. if not employee :
  274. raise CustomException(msg=f"员工 {data.employee_id} 不存在")
  275. if hasattr(employee, 'employee_id') and employee['employee_id'] != data.employee_id:
  276. raise CustomException(msg=f"员工 {data.employee_id} 不存在")
  277. crud = EmployeeCRUD(auth)
  278. await crud.update_by(
  279. employee_mobile=data.employee_mobile,
  280. employee_email=data.employee_email,
  281. identity_open_id=data.identity_open_id,
  282. data=data.model_dump(exclude_none=True)
  283. )