package com.ruoyi.project.common;
|
|
import com.ruoyi.common.enums.ApplyTypeEnum;
|
import com.ruoyi.common.utils.StringUtils;
|
|
import java.math.BigDecimal;
|
import java.math.RoundingMode;
|
import java.text.SimpleDateFormat;
|
import java.util.ArrayList;
|
import java.util.Date;
|
import java.util.List;
|
import java.util.concurrent.ConcurrentHashMap;
|
|
/**
|
* 报销审批钉钉消息内容组装。
|
*
|
* <p>统一「标题 + 单据信息 + 审批意见文案」,供以下发送点复用:
|
* <ul>
|
* <li>致远 OA 回传审批意见 —— {@code ServiceFundServiceImpl#processlog} → {@code sendMeg}</li>
|
* <li>系统内审核驳回(费用报销) —— {@code ServiceFundServiceImpl#checkFund}</li>
|
* <li>系统内审核驳回(差旅费) —— {@code ServiceReimbursementServiceImpl#checkFund}</li>
|
* <li>差旅费上报提交 —— {@code ServiceReimbursementController#travelexpensereport}</li>
|
* </ul>
|
*
|
* <p><b>为什么要一行一个 map:</b>{@code DingTalkReqVo.contents} 的元素是
|
* {@link ConcurrentHashMap},其遍历顺序不保证;一条 map 里塞多行会导致钉钉表单行序错乱。
|
* 这里每条信息单独一个「单键 map」,用 List 的顺序保证显示顺序。
|
*
|
* @author ruoyi
|
*/
|
public final class FundNotifyMessageBuilder {
|
|
/** 钉钉消息统一标题(2026-10-08 业务方确认统一为「OPO报销审批」) */
|
public static final String TITLE = "OPO报销审批";
|
|
/** 审批结论:通过 */
|
public static final String CONCLUSION_PASS = "1";
|
/** 审批结论:驳回 */
|
public static final String CONCLUSION_REJECT = "2";
|
/** 审批结论:完结 */
|
public static final String CONCLUSION_FINISH = "3";
|
|
private static final String KEY_TYPE = "报销单类型:";
|
private static final String KEY_BENEFICIARY = "报销人:";
|
private static final String KEY_AMOUNT = "金额:";
|
private static final String KEY_DONOR = "捐献案例:";
|
private static final String KEY_BILL_NO = "单据编号:";
|
private static final String KEY_TRAVELERS = "出差人:";
|
private static final String KEY_REASON = "出差事由:";
|
private static final String KEY_CHECKER = "审批人:";
|
private static final String KEY_CHECK_TIME = "审批时间:";
|
private static final String KEY_OPINION = "意见:";
|
private static final String KEY_SUBMITTER = "提交人:";
|
private static final String KEY_SUBMIT_TIME = "提交时间:";
|
private static final String KEY_CONTENT = "内容:";
|
|
private FundNotifyMessageBuilder() {
|
}
|
|
/** 新建内容行容器 */
|
public static List<ConcurrentHashMap<String, Object>> newRows() {
|
return new ArrayList<ConcurrentHashMap<String, Object>>();
|
}
|
|
/**
|
* 追加一行。值为空(null / 空串 / 全空白)时**不追加**,避免钉钉里出现空行。
|
*/
|
public static void addRow(List<ConcurrentHashMap<String, Object>> rows, String key, Object value) {
|
if (rows == null || key == null || value == null) {
|
return;
|
}
|
if (value instanceof String && StringUtils.isEmpty(((String) value).trim())) {
|
return;
|
}
|
ConcurrentHashMap<String, Object> row = new ConcurrentHashMap<String, Object>(1);
|
row.put(key, value);
|
rows.add(row);
|
}
|
|
/** 报销单类型:applytype → 中文名;未匹配返回 null(该行不显示) */
|
public static String typeName(String applytype) {
|
return StringUtils.isEmpty(applytype) ? null : ApplyTypeEnum.getDescByCode(applytype);
|
}
|
|
/** 单据编号:优先取业务编号 bh,缺失时退回主键 id */
|
public static String billNo(String bh, Long id) {
|
if (StringUtils.isNotEmpty(bh)) {
|
return bh;
|
}
|
return id == null ? null : String.valueOf(id);
|
}
|
|
/**
|
* 追加报销单基础信息:报销单类型 / 报销人 / 金额 / 捐献案例 / 单据编号。
|
*
|
* <p>金额口径:费用类取 {@code service_fund.pretaxcost}(审核页「申请金额」列),
|
* 差旅费取 {@code service_reimbursement.amountrequested}(审核页「报销金额」列)。
|
*/
|
public static void addBillInfo(List<ConcurrentHashMap<String, Object>> rows,
|
String applytype, String beneficiary, Double amount,
|
String donorname, String billNo) {
|
addRow(rows, KEY_TYPE, typeName(applytype));
|
addRow(rows, KEY_BENEFICIARY, beneficiary);
|
addRow(rows, KEY_AMOUNT, money(amount));
|
addRow(rows, KEY_DONOR, donorname);
|
addRow(rows, KEY_BILL_NO, billNo);
|
}
|
|
/** 追加差旅费专有信息:出差人 / 出差事由 */
|
public static void addTravelInfo(List<ConcurrentHashMap<String, Object>> rows,
|
String travelers, String reason) {
|
addRow(rows, KEY_TRAVELERS, travelers);
|
addRow(rows, KEY_REASON, reason);
|
}
|
|
/**
|
* 追加审批结果:审批人 / 审批时间 / 意见。
|
*
|
* <p>审批时间传 {@link Date}:键名含「审批时间」,由 {@code DingTalkServiceImpl} 统一格式化。
|
*/
|
public static void addAuditResult(List<ConcurrentHashMap<String, Object>> rows,
|
String checker, Date checkTime, String opinion) {
|
addRow(rows, KEY_CHECKER, checker);
|
addRow(rows, KEY_CHECK_TIME, checkTime);
|
addRow(rows, KEY_OPINION, opinion);
|
}
|
|
/** 追加提交通知信息:提交人 / 提交时间 / 内容(时间在此预格式化,避免 Date.toString()) */
|
public static void addSubmitInfo(List<ConcurrentHashMap<String, Object>> rows,
|
String submitter, Date submitTime, String content) {
|
addRow(rows, KEY_SUBMITTER, submitter);
|
addRow(rows, KEY_SUBMIT_TIME, time(submitTime));
|
addRow(rows, KEY_CONTENT, content);
|
}
|
|
/**
|
* 审批意见文案(2026-10-08 业务方确认)。
|
*
|
* <pre>
|
* 通过(1) → "审批通过。" + 意见备注(若有)
|
* 驳回(2) → "审批未通过。" + 意见备注(若有)
|
* 完结(3) → "流程已完结。"
|
* 其他/空 → 原样返回备注;无备注返回空串
|
* </pre>
|
*
|
* <p>实测依据(本地库 {@code service_fundflow.flowlevel=100} 即致远回传记录):
|
* 通过 11618 条中 9606 条意见为空、完结 1457 条全部为空、驳回 743 条中 161 条为空。
|
* 若不给结论兜底文案,钉钉消息的「意见」一栏就是空白(这正是业务反馈的问题)。
|
*/
|
public static String opinion(String conclusion, String content) {
|
String remark = content == null ? "" : content.trim();
|
String base;
|
if (CONCLUSION_PASS.equals(conclusion)) {
|
base = "审批通过。";
|
} else if (CONCLUSION_REJECT.equals(conclusion)) {
|
base = "审批未通过。";
|
} else if (CONCLUSION_FINISH.equals(conclusion)) {
|
base = "流程已完结。";
|
} else {
|
// 结论缺失:只显示备注,不臆测结论
|
return remark;
|
}
|
return remark.isEmpty() ? base : base + remark;
|
}
|
|
/** 金额显示:两位小数、无千分位,如 {@code 1200.00 元} */
|
public static String money(Double amount) {
|
if (amount == null) {
|
return null;
|
}
|
return new BigDecimal(String.valueOf(amount))
|
.setScale(2, RoundingMode.HALF_UP)
|
.toPlainString() + " 元";
|
}
|
|
/** 时间显示:{@code yyyy-MM-dd HH:mm:ss}(24 小时制) */
|
public static String time(Date date) {
|
return date == null ? null : new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date);
|
}
|
}
|