feat(outbound): 完善出库审批邮件通知逻辑,支持申请人与审批人同时收到邮件(带物料明细),审批通过后申请人和库管均收到带物料明细的通知
This commit is contained in:
@ -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
|
||||
|
||||
@ -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 通知申请人(已驳回)
|
||||
|
||||
@ -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"""您好,
|
||||
|
||||
|
||||
Reference in New Issue
Block a user