feat(outbound): 完善出库审批邮件通知逻辑,支持申请人与审批人同时收到邮件(带物料明细),审批通过后申请人和库管均收到带物料明细的通知
This commit is contained in:
@ -326,11 +326,14 @@ def get_my_permissions():
|
|||||||
def get_approvers():
|
def get_approvers():
|
||||||
"""
|
"""
|
||||||
查询角色为 SUPER_ADMIN 或 SUPERVISOR 且状态为活跃的用户列表
|
查询角色为 SUPER_ADMIN 或 SUPERVISOR 且状态为活跃的用户列表
|
||||||
返回: [{id, username, email, role}]
|
返回: [{id, username, email, role, is_self}]
|
||||||
|
其中 is_self=true 表示当前登录用户本人(用于前端标记)
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
from app.models.system import SysUser
|
from app.models.system import SysUser
|
||||||
|
|
||||||
|
current_user_id = get_jwt_identity()
|
||||||
|
|
||||||
users = SysUser.query.filter(
|
users = SysUser.query.filter(
|
||||||
SysUser.role.in_(['SUPER_ADMIN', 'SUPERVISOR']),
|
SysUser.role.in_(['SUPER_ADMIN', 'SUPERVISOR']),
|
||||||
SysUser.status == 'active'
|
SysUser.status == 'active'
|
||||||
@ -343,7 +346,8 @@ def get_approvers():
|
|||||||
'id': u.id,
|
'id': u.id,
|
||||||
'username': u.username,
|
'username': u.username,
|
||||||
'email': u.email or '',
|
'email': u.email or '',
|
||||||
'role': u.role
|
'role': u.role,
|
||||||
|
'is_self': (u.id == current_user_id)
|
||||||
} for u in users
|
} for u in users
|
||||||
]
|
]
|
||||||
}), 200
|
}), 200
|
||||||
|
|||||||
@ -688,18 +688,28 @@ class OutboundApprovalService:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _notify_new_request(approval, applicant_id, approver_id=None):
|
def _notify_new_request(approval, applicant_id, approver_id=None):
|
||||||
"""发送新申请通知邮件给审批人(静默处理,不阻断主流程)"""
|
"""发送新申请通知邮件给审批人和申请人(静默处理,不阻断主流程)"""
|
||||||
try:
|
try:
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
from app.utils.email_service import send_new_request_notify
|
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:
|
if approver_id:
|
||||||
# ★ 精准通知模式:直接查询指定审批人
|
|
||||||
user = SysUser.query.get(int(approver_id))
|
user = SysUser.query.get(int(approver_id))
|
||||||
if user and user.email:
|
if user and user.email:
|
||||||
emails.append(user.email)
|
approver_emails.append(user.email)
|
||||||
else:
|
else:
|
||||||
# 兜底:按角色查询
|
# 兜底:按角色查询
|
||||||
approvers = approval.get_allowed_approvers()
|
approvers = approval.get_allowed_approvers()
|
||||||
@ -707,37 +717,49 @@ class OutboundApprovalService:
|
|||||||
for a in approvers:
|
for a in approvers:
|
||||||
if a.get('type') == 'role':
|
if a.get('type') == 'role':
|
||||||
role_codes.append(a.get('value', ''))
|
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
|
return
|
||||||
|
|
||||||
# 获取申请人姓名
|
# 3. 获取物料明细
|
||||||
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))
|
|
||||||
|
|
||||||
# ★ 发送通知,附完整物料清单
|
|
||||||
items = approval.get_items()
|
items = approval.get_items()
|
||||||
|
|
||||||
|
# 4. 分别发送邮件
|
||||||
|
if applicant_emails:
|
||||||
|
try:
|
||||||
send_new_request_notify(
|
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,
|
request_no=approval.request_no,
|
||||||
applicant_name=applicant_name,
|
applicant_name=applicant_name,
|
||||||
remark=approval.remark or '',
|
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:
|
except Exception as e:
|
||||||
# ★ 捕获所有异常,确保邮件发送失败不阻断主流程
|
|
||||||
try:
|
try:
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
current_app.logger.error(f"[Email] 发送新申请通知邮件失败: {e}")
|
current_app.logger.error(f"[Email] 发送新申请通知邮件失败: {e}")
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
# 如果不在 Flask 应用上下文内,降级为标准日志
|
|
||||||
import logging
|
import logging
|
||||||
logging.getLogger(__name__).error(f"[Email] 发送新申请通知邮件失败: {e}")
|
logging.getLogger(__name__).error(f"[Email] 发送新申请通知邮件失败: {e}")
|
||||||
|
|
||||||
@ -864,17 +886,17 @@ class OutboundApprovalService:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"[Email] 通知库管失败: {e}")
|
logger.error(f"[Email] 通知库管失败: {e}")
|
||||||
|
|
||||||
# 3.2 通知申请人(已通过)
|
# 3.2 通知申请人(审批通过,带完整物料清单)
|
||||||
if applicant_emails:
|
if applicant_emails:
|
||||||
try:
|
try:
|
||||||
send_approval_result_notify(
|
send_warehouse_dispatch_notify(
|
||||||
to_emails=applicant_emails,
|
to_emails=applicant_emails,
|
||||||
request_no=approval.request_no,
|
request_no=approval.request_no,
|
||||||
is_passed=True,
|
applicant_name=applicant_name,
|
||||||
applicant_name=applicant_name
|
items=items
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"[Email] 通知申请人通过失败: {e}")
|
logger.error(f"[Email] 通知申请人(通过)失败: {e}")
|
||||||
|
|
||||||
elif action == 'reject':
|
elif action == 'reject':
|
||||||
# 3.3 通知申请人(已驳回)
|
# 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,
|
def send_new_request_notify(to_emails: List[str], request_no: str,
|
||||||
applicant_name: str = '', remark: str = '',
|
applicant_name: str = '', remark: str = '',
|
||||||
items: list = None):
|
items: list = None, is_applicant_notify: bool = False):
|
||||||
"""
|
"""
|
||||||
通知审批人有新的出库申请单待审批(可附带物料清单)
|
通知审批人有新的出库申请单待审批(可附带物料清单)
|
||||||
|
或通知申请人其申请已提交(is_applicant_notify=True 时)
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
to_emails: 审批人邮箱列表
|
to_emails: 审批人邮箱列表
|
||||||
@ -130,9 +131,9 @@ def send_new_request_notify(to_emails: List[str], request_no: str,
|
|||||||
applicant_name: 申请人姓名
|
applicant_name: 申请人姓名
|
||||||
remark: 申请备注
|
remark: 申请备注
|
||||||
items: 物料明细列表(可选)
|
items: 物料明细列表(可选)
|
||||||
|
is_applicant_notify: True=通知申请人(标题:您的出库申请已提交),False=通知审批人(标题:您有一笔新的出库审批待处理)
|
||||||
"""
|
"""
|
||||||
print(f"[DEBUG send_new_request_notify] 入参 items={items}")
|
print(f"[DEBUG send_new_request_notify] 入参 items={items}, is_applicant_notify={is_applicant_notify}")
|
||||||
print(f"[DEBUG send_new_request_notify] items 类型={type(items)}, 长度={len(items) if items else 0}")
|
|
||||||
|
|
||||||
# 拼装物料表格
|
# 拼装物料表格
|
||||||
rows = []
|
rows = []
|
||||||
@ -147,6 +148,27 @@ def send_new_request_notify(to_emails: List[str], request_no: str,
|
|||||||
else:
|
else:
|
||||||
rows.append("(无物料明细)")
|
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}"
|
subject = f"【待审批】出库申请单 {request_no}"
|
||||||
content = f"""您好,
|
content = f"""您好,
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user