feat(outbound): 完善出库审批邮件通知逻辑,支持申请人与审批人同时收到邮件(带物料明细),审批通过后申请人和库管均收到带物料明细的通知

This commit is contained in:
DXC
2026-05-12 13:42:15 +08:00
parent c86da38a70
commit 3dae206828
3 changed files with 85 additions and 37 deletions

View File

@ -326,11 +326,14 @@ def get_my_permissions():
def get_approvers():
"""
查询角色为 SUPER_ADMIN 或 SUPERVISOR 且状态为活跃的用户列表
返回: [{id, username, email, role}]
返回: [{id, username, email, role, is_self}]
其中 is_self=true 表示当前登录用户本人(用于前端标记)
"""
try:
from app.models.system import SysUser
current_user_id = get_jwt_identity()
users = SysUser.query.filter(
SysUser.role.in_(['SUPER_ADMIN', 'SUPERVISOR']),
SysUser.status == 'active'
@ -343,7 +346,8 @@ def get_approvers():
'id': u.id,
'username': u.username,
'email': u.email or '',
'role': u.role
'role': u.role,
'is_self': (u.id == current_user_id)
} for u in users
]
}), 200

View File

@ -688,18 +688,28 @@ class OutboundApprovalService:
@staticmethod
def _notify_new_request(approval, applicant_id, approver_id=None):
"""发送新申请通知邮件给审批人(静默处理,不阻断主流程)"""
"""发送新申请通知邮件给审批人和申请人(静默处理,不阻断主流程)"""
try:
from flask import current_app
from app.utils.email_service import send_new_request_notify
from app.models.system import SysUser
emails = []
applicant_name = ''
applicant_emails = []
# 1. 收集申请人信息
if applicant_id:
user = SysUser.query.get(int(applicant_id))
if user and user.email:
applicant_emails.append(user.email)
applicant_name = str(user.username).split('/')[0] if '/' in (user.username or '') else (user.username or str(applicant_id))
# 2. 收集审批人信息
approver_emails = []
if approver_id:
# ★ 精准通知模式:直接查询指定审批人
user = SysUser.query.get(int(approver_id))
if user and user.email:
emails.append(user.email)
approver_emails.append(user.email)
else:
# 兜底:按角色查询
approvers = approval.get_allowed_approvers()
@ -707,37 +717,49 @@ class OutboundApprovalService:
for a in approvers:
if a.get('type') == 'role':
role_codes.append(a.get('value', ''))
emails = OutboundApprovalService._get_emails_by_identifiers(role_codes=role_codes)
approver_emails = OutboundApprovalService._get_emails_by_identifiers(role_codes=role_codes)
if not emails:
current_app.logger.info(f"[Email] 审批单 {approval.request_no} 无审批人邮箱,跳过通知")
# 去重
all_emails = list(set(applicant_emails + approver_emails))
if not all_emails:
current_app.logger.info(f"[Email] 审批单 {approval.request_no} 无收件人邮箱,跳过通知")
return
# 获取申请人姓名
applicant_name = ''
if applicant_id:
u = SysUser.query.get(applicant_id)
if u:
# username 格式为 "姓名/账号",取姓名部分
applicant_name = str(u.username).split('/')[0] if '/' in u.username else (u.username or str(applicant_id))
# ★ 发送通知,附完整物料清单
# 3. 获取物料明细
items = approval.get_items()
# 4. 分别发送邮件
if applicant_emails:
try:
send_new_request_notify(
to_emails=emails,
to_emails=applicant_emails,
request_no=approval.request_no,
applicant_name=applicant_name,
remark=f"您的出库申请已提交,等待审批。{approval.remark or ''}",
items=items,
is_applicant_notify=True
)
except Exception as e:
current_app.logger.error(f"[Email] 通知申请人失败: {e}")
if approver_emails:
try:
send_new_request_notify(
to_emails=approver_emails,
request_no=approval.request_no,
applicant_name=applicant_name,
remark=approval.remark or '',
items=items
items=items,
is_applicant_notify=False
)
except Exception as e:
current_app.logger.error(f"[Email] 通知审批人失败: {e}")
except Exception as e:
# ★ 捕获所有异常,确保邮件发送失败不阻断主流程
try:
from flask import current_app
current_app.logger.error(f"[Email] 发送新申请通知邮件失败: {e}")
except RuntimeError:
# 如果不在 Flask 应用上下文内,降级为标准日志
import logging
logging.getLogger(__name__).error(f"[Email] 发送新申请通知邮件失败: {e}")
@ -864,17 +886,17 @@ class OutboundApprovalService:
except Exception as e:
logger.error(f"[Email] 通知库管失败: {e}")
# 3.2 通知申请人(已通过
# 3.2 通知申请人(审批通过,带完整物料清单
if applicant_emails:
try:
send_approval_result_notify(
send_warehouse_dispatch_notify(
to_emails=applicant_emails,
request_no=approval.request_no,
is_passed=True,
applicant_name=applicant_name
applicant_name=applicant_name,
items=items
)
except Exception as e:
logger.error(f"[Email] 通知申请人通过失败: {e}")
logger.error(f"[Email] 通知申请人通过失败: {e}")
elif action == 'reject':
# 3.3 通知申请人(已驳回)

View File

@ -120,9 +120,10 @@ def send_email(to_email: Union[str, List[str]], subject: str, content: str):
def send_new_request_notify(to_emails: List[str], request_no: str,
applicant_name: str = '', remark: str = '',
items: list = None):
items: list = None, is_applicant_notify: bool = False):
"""
通知审批人有新的出库申请单待审批(可附带物料清单)
或通知申请人其申请已提交is_applicant_notify=True 时)
Args:
to_emails: 审批人邮箱列表
@ -130,9 +131,9 @@ def send_new_request_notify(to_emails: List[str], request_no: str,
applicant_name: 申请人姓名
remark: 申请备注
items: 物料明细列表(可选)
is_applicant_notify: True=通知申请人标题您的出库申请已提交False=通知审批人(标题:您有一笔新的出库审批待处理)
"""
print(f"[DEBUG send_new_request_notify] 入参 items={items}")
print(f"[DEBUG send_new_request_notify] items 类型={type(items)}, 长度={len(items) if items else 0}")
print(f"[DEBUG send_new_request_notify] 入参 items={items}, is_applicant_notify={is_applicant_notify}")
# 拼装物料表格
rows = []
@ -147,6 +148,27 @@ def send_new_request_notify(to_emails: List[str], request_no: str,
else:
rows.append("(无物料明细)")
if is_applicant_notify:
subject = f"【已提交】您的出库申请单 {request_no} 已提交"
content = f"""您好,
您的出库申请单 {request_no} 已成功提交,等待审批。
申请单号:{request_no}
申请人:{applicant_name or '未知'}
备注说明:{remark or ''}
物料清单如下:
{chr(10).join(rows)}
---
您可以点击下方链接查看申请状态:
https://172.16.0.198/outbound/selection
---
此邮件由系统自动发送,请勿回复。
"""
else:
subject = f"【待审批】出库申请单 {request_no}"
content = f"""您好,