|
|
@ -26,6 +26,7 @@ import com.xujie.sys.modules.erf.service.ErfTriConfirmService; |
|
|
import com.xujie.sys.modules.sys.dao.SysUserDao; |
|
|
import com.xujie.sys.modules.sys.dao.SysUserDao; |
|
|
import com.xujie.sys.modules.sys.dto.ManagerInfoDto; |
|
|
import com.xujie.sys.modules.sys.dto.ManagerInfoDto; |
|
|
import com.xujie.sys.modules.sys.dto.UserRoleDto; |
|
|
import com.xujie.sys.modules.sys.dto.UserRoleDto; |
|
|
|
|
|
import com.xujie.sys.modules.sys.entity.SysUserEntity; |
|
|
import lombok.extern.slf4j.Slf4j; |
|
|
import lombok.extern.slf4j.Slf4j; |
|
|
import org.apache.commons.lang.StringUtils; |
|
|
import org.apache.commons.lang.StringUtils; |
|
|
import org.springframework.beans.BeanUtils; |
|
|
import org.springframework.beans.BeanUtils; |
|
|
@ -38,6 +39,8 @@ import java.time.LocalDate; |
|
|
import java.time.LocalDateTime; |
|
|
import java.time.LocalDateTime; |
|
|
import java.time.format.DateTimeFormatter; |
|
|
import java.time.format.DateTimeFormatter; |
|
|
import java.util.*; |
|
|
import java.util.*; |
|
|
|
|
|
import java.util.regex.Matcher; |
|
|
|
|
|
import java.util.regex.Pattern; |
|
|
import java.util.stream.Collectors; |
|
|
import java.util.stream.Collectors; |
|
|
|
|
|
|
|
|
import static com.xujie.sys.common.utils.ShiroUtils.getUserId; |
|
|
import static com.xujie.sys.common.utils.ShiroUtils.getUserId; |
|
|
@ -281,9 +284,22 @@ public class ErfExpApplyServiceImpl extends ServiceImpl<ErfExpApplyMapper, ErfEx |
|
|
if (needWaitForOtherApprovals(data.getApplyNo(), data.getNodeCode())) { |
|
|
if (needWaitForOtherApprovals(data.getApplyNo(), data.getNodeCode())) { |
|
|
log.info("当前节点还有其他审批人未完成,暂不流转"); |
|
|
log.info("当前节点还有其他审批人未完成,暂不流转"); |
|
|
} else { |
|
|
} else { |
|
|
// 所有并发审批都完成,流转到下一节点 |
|
|
|
|
|
|
|
|
// 所有并发审批都完成,流转到下一节点(经理全部通过后下一节点为「计划员排产」) |
|
|
|
|
|
String nextNodeCode = erfFlowEngineService.getNextNodeCode(data.getApplyNo(), data.getNodeCode()); |
|
|
erfFlowEngineService.moveToNextNode(data.getApplyNo(), data.getNodeCode(), data); |
|
|
erfFlowEngineService.moveToNextNode(data.getApplyNo(), data.getNodeCode(), data); |
|
|
log.info("所有审批完成,流转到下一节点"); |
|
|
log.info("所有审批完成,流转到下一节点"); |
|
|
|
|
|
if ("计划员排产".equals(nextNodeCode)) { |
|
|
|
|
|
ErfExpApply approvedEntity = this.getById(data.getApplyNo()); |
|
|
|
|
|
if (approvedEntity != null) { |
|
|
|
|
|
List<Long> plannerUserIds = extractPlannerIdsFromFlowRemark(data.getApplyNo()); |
|
|
|
|
|
if (!plannerUserIds.isEmpty()) { |
|
|
|
|
|
sendPlannerSubmitNotification(approvedEntity, plannerUserIds); |
|
|
|
|
|
} else { |
|
|
|
|
|
log.warn("流程 remark 中未解析到计划员ID,跳过计划员邮件: {}", data.getApplyNo()); |
|
|
|
|
|
} |
|
|
|
|
|
sendProjectLeaderApprovalPassedNotification(approvedEntity); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} else if ("驳回".equals(data.getAction())) { |
|
|
} else if ("驳回".equals(data.getAction())) { |
|
|
// 驳回:返回到指定节点或创建人 |
|
|
// 驳回:返回到指定节点或创建人 |
|
|
@ -1312,6 +1328,49 @@ public class ErfExpApplyServiceImpl extends ServiceImpl<ErfExpApplyMapper, ErfEx |
|
|
return true; |
|
|
return true; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 从流程实例 remark(JSON)中解析下达时保存的计划员用户ID列表,与流程引擎 {@code extractPlannerIds} 逻辑一致 |
|
|
|
|
|
*/ |
|
|
|
|
|
private List<Long> extractPlannerIdsFromFlowRemark(String applyNo) { |
|
|
|
|
|
QueryWrapper<ErfFlowInstance> flowQuery = new QueryWrapper<>(); |
|
|
|
|
|
flowQuery.eq("apply_no", applyNo); |
|
|
|
|
|
ErfFlowInstance flowInstance = erfFlowInstanceMapper.selectOne(flowQuery); |
|
|
|
|
|
if (flowInstance == null || flowInstance.getRemark() == null || flowInstance.getRemark().isEmpty()) { |
|
|
|
|
|
return Collections.emptyList(); |
|
|
|
|
|
} |
|
|
|
|
|
String json = flowInstance.getRemark(); |
|
|
|
|
|
try { |
|
|
|
|
|
Pattern p = Pattern.compile("\"plannerIds\":\\[([0-9,]+)\\]"); |
|
|
|
|
|
Matcher m = p.matcher(json); |
|
|
|
|
|
if (m.find()) { |
|
|
|
|
|
String idsStr = m.group(1); |
|
|
|
|
|
return Arrays.stream(idsStr.split(",")) |
|
|
|
|
|
.map(String::trim) |
|
|
|
|
|
.filter(s -> !s.isEmpty()) |
|
|
|
|
|
.map(Long::valueOf) |
|
|
|
|
|
.filter(id -> id > 0) |
|
|
|
|
|
.collect(Collectors.toList()); |
|
|
|
|
|
} |
|
|
|
|
|
int idx = json.indexOf("\"plannerId\":"); |
|
|
|
|
|
if (idx >= 0) { |
|
|
|
|
|
String sub = json.substring(idx + 12).trim(); |
|
|
|
|
|
int end = sub.indexOf(','); |
|
|
|
|
|
if (end < 0) { |
|
|
|
|
|
end = sub.indexOf('}'); |
|
|
|
|
|
} |
|
|
|
|
|
if (end > 0) { |
|
|
|
|
|
long id = Long.parseLong(sub.substring(0, end).trim()); |
|
|
|
|
|
if (id > 0) { |
|
|
|
|
|
return Collections.singletonList(id); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} catch (Exception e) { |
|
|
|
|
|
log.error("解析流程实例中的计划员ID失败 applyNo={}: {}", applyNo, e.getMessage()); |
|
|
|
|
|
} |
|
|
|
|
|
return Collections.emptyList(); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
@Override |
|
|
@Override |
|
|
public void cancelExpApply(String applyNo, Long userId) { |
|
|
public void cancelExpApply(String applyNo, Long userId) { |
|
|
log.info("=== 开始取消试验单 === 试验单号: {}, 操作人: {}", applyNo, userId); |
|
|
log.info("=== 开始取消试验单 === 试验单号: {}, 操作人: {}", applyNo, userId); |
|
|
@ -1531,6 +1590,86 @@ public class ErfExpApplyServiceImpl extends ServiceImpl<ErfExpApplyMapper, ErfEx |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 经理层审批全部通过后(进入计划员排产节点时)通知试验负责人:其负责的试验申请单已审批通过 |
|
|
|
|
|
*/ |
|
|
|
|
|
private void sendProjectLeaderApprovalPassedNotification(ErfExpApply entity) { |
|
|
|
|
|
try { |
|
|
|
|
|
UserEmailInfoDto leader = resolveProjectLeaderEmailInfo(entity); |
|
|
|
|
|
if (leader == null) { |
|
|
|
|
|
log.warn("无法解析试验负责人邮箱,跳过审批通过通知: applyNo={}", entity.getApplyNo()); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
String email = leader.getEmail(); |
|
|
|
|
|
if (email == null || email.trim().isEmpty()) { |
|
|
|
|
|
log.warn("试验负责人 {} 未配置邮箱,跳过审批通过通知", leader.getUsername()); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
MailSendAddressData mailSendData = qcMapper.getSendMailFromAddress(); |
|
|
|
|
|
if (mailSendData == null) { |
|
|
|
|
|
log.error("邮件发送配置未设置,无法发送试验负责人审批通过通知"); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
String todayStr = DateTimeFormatter.ofPattern("yyyy-MM-dd").format(LocalDateTime.now()); |
|
|
|
|
|
String expectedFinishDateStr = entity.getExpectedFinishDate() != null |
|
|
|
|
|
? DateTimeFormatter.ofPattern("yyyy-MM-dd") |
|
|
|
|
|
.format(entity.getExpectedFinishDate().toInstant().atZone(java.time.ZoneId.systemDefault()).toLocalDateTime()) |
|
|
|
|
|
: todayStr; |
|
|
|
|
|
|
|
|
|
|
|
String subject = String.format("【工程实验申请审批通过】%s - %s", |
|
|
|
|
|
entity.getApplyNo(), entity.getTitle() != null ? entity.getTitle() : ""); |
|
|
|
|
|
String tableBody = buildPlannerNotificationTable(entity, todayStr, expectedFinishDateStr); |
|
|
|
|
|
|
|
|
|
|
|
String body = "<html><body>" |
|
|
|
|
|
+ "<h3>尊敬的 " + leader.getUsername() + ":</h3>" |
|
|
|
|
|
+ "<p>您好!您作为试验负责人负责的工程实验试验单,经理层审批已全部通过,流程已进入<strong>计划员排产</strong>环节。</p>" |
|
|
|
|
|
+ "<p>请留意后续排产与执行安排。</p>" |
|
|
|
|
|
+ "<hr/>" + tableBody |
|
|
|
|
|
+ "<br/><p style='color:#888;'>发送时间:" |
|
|
|
|
|
+ LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) |
|
|
|
|
|
+ "</p></body></html>"; |
|
|
|
|
|
|
|
|
|
|
|
MailUtil.sendMail(subject, body, new String[]{email}, mailSendData); |
|
|
|
|
|
log.info("已向试验负责人 {} ({}) 发送审批通过通知,试验单: {}", |
|
|
|
|
|
leader.getUsername(), email, entity.getApplyNo()); |
|
|
|
|
|
|
|
|
|
|
|
SendMailRecord mailRecord = new SendMailRecord(); |
|
|
|
|
|
mailRecord.setType("工程实验申请试验负责人审批通过通知"); |
|
|
|
|
|
mailRecord.setDocumentNo(entity.getApplyNo()); |
|
|
|
|
|
mailRecord.setRecipient(email); |
|
|
|
|
|
mailRecord.setSendDate(new Date()); |
|
|
|
|
|
qcMapper.saveSendMailRecord(mailRecord); |
|
|
|
|
|
} catch (Exception e) { |
|
|
|
|
|
log.error("发送试验负责人审批通过邮件失败,试验单: {}, 错误: {}", entity.getApplyNo(), e.getMessage(), e); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 根据试验单上的试验负责人字段解析系统用户邮箱(优先用户名,其次用户显示名精确匹配) |
|
|
|
|
|
*/ |
|
|
|
|
|
private UserEmailInfoDto resolveProjectLeaderEmailInfo(ErfExpApply entity) { |
|
|
|
|
|
if (entity == null) { |
|
|
|
|
|
return null; |
|
|
|
|
|
} |
|
|
|
|
|
if (StringUtils.isNotBlank(entity.getProjectLeaderName())) { |
|
|
|
|
|
SysUserEntity u = sysUserDao.queryByUserName(entity.getProjectLeaderName()); |
|
|
|
|
|
if (u != null) { |
|
|
|
|
|
return sysUserDao.getUserEmailInfoById(u.getUserId()); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
if (StringUtils.isNotBlank(entity.getProjectLeader())) { |
|
|
|
|
|
SysUserEntity u = sysUserDao.selectOne(new QueryWrapper<SysUserEntity>() |
|
|
|
|
|
.eq("user_display", entity.getProjectLeader()) |
|
|
|
|
|
.last("OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY")); |
|
|
|
|
|
if (u != null) { |
|
|
|
|
|
return sysUserDao.getUserEmailInfoById(u.getUserId()); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
return null; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* 排产完成后向仓库角色用户发送邮件通知 |
|
|
* 排产完成后向仓库角色用户发送邮件通知 |
|
|
* 邮件内容包含试验单信息表格和原材料清单 |
|
|
* 邮件内容包含试验单信息表格和原材料清单 |
|
|
@ -1653,7 +1792,7 @@ public class ErfExpApplyServiceImpl extends ServiceImpl<ErfExpApplyMapper, ErfEx |
|
|
+ "<td>" + (entity.getQuantityReq() != null ? entity.getQuantityReq() : "") + "</td>" |
|
|
+ "<td>" + (entity.getQuantityReq() != null ? entity.getQuantityReq() : "") + "</td>" |
|
|
+ "<td>" + (entity.getRemark() != null ? entity.getRemark() : "") + "</td>" |
|
|
+ "<td>" + (entity.getRemark() != null ? entity.getRemark() : "") + "</td>" |
|
|
+ "<td>" + expectedFinishDateStr + "</td>" |
|
|
+ "<td>" + expectedFinishDateStr + "</td>" |
|
|
+ "<td>" + (entity.getCreatorName() != null ? entity.getCreatorName() : "") + "</td>" |
|
|
|
|
|
|
|
|
+ "<td>" + (entity.getProjectLeader() != null ? entity.getProjectLeader() : "") + "</td>" |
|
|
+ "</tr></tbody></table>"; |
|
|
+ "</tr></tbody></table>"; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|