付款排程调度优化

This commit is contained in:
李贵强 2025-10-17 17:37:41 +08:00
parent 65ce495bf4
commit fefd6e772e
3 changed files with 256 additions and 113 deletions

View File

@ -171,15 +171,16 @@ public class PaybillOperation extends AbstractOperationServicePlugIn implements
header.put("FM_ExpenseTypeCode", FM_ExpenseTypeCode);// 业务大类编码(EQ49,EQ44,EQ1101)
header.put("FM_CurrencyCode", "RMB");// 币种编码默认:RMB
header.put("FM_RequestName", bill.getString("description"));// 单据主题_拼接
List<Map<String, Object>> attachments = AttachmentServiceHelper.getAttachments(bill.getDataEntityType().getName(), bill.getLong("id"), "attachmentpanel");
if (attachments.size()!=0){
JSONArray attachUrls = new JSONArray();
for (Map<String, Object> attachment : attachments) {
String url = (String)attachment.get("url");
attachUrls.add(url);
}
header.put("FM_AttachUrl", attachUrls);//附件URL地址:array
}
// List<Map<String, Object>> attachments = AttachmentServiceHelper.getAttachments(bill.getDataEntityType().getName(), bill.getLong("id"), "attachmentpanel");
// if (attachments.size()!=0){
// JSONArray attachUrls = new JSONArray();
// for (Map<String, Object> attachment : attachments) {
// String url = (String)attachment.get("url");
// attachUrls.add(url);
// }
// header.put("FM_AttachUrl", attachUrls);//附件URL地址:array
// }
header.put("FM_AttachUrl", null);//附件URL地址:array
header.put("FM_BudType", 0);//预算类别0-组织预算,1-全年预算,当前只处理0 int
header.put("Remark", bill.getString("description"));// 事项描述_摘要

View File

@ -17,153 +17,291 @@ import kd.bos.servicehelper.QueryServiceHelper;
import kd.bos.servicehelper.operation.OperationServiceHelper;
import kd.sdk.plugin.Plugin;
import java.math.BigDecimal;
import java.util.*;
/**
* 定时任务 付款申请定时生成付款单
*
* @author LiGuiQiang
*/
public class PayApplyFukuanTask extends AbstractTask implements Plugin {
private static final String AP_PAYAPPLY ="ap_payapply";// 付款申请单
private static final String CAS_PAYBILL ="cas_paybill";// 付款单
private static final String AP_PAYAPPLY = "ap_payapply";// 付款申请单
//private static final String CAS_PAYBILL = "cas_paybill";// 付款单
private static final Log logger = LogFactory.getLog(PayApplyFukuanTask.class);
@Override
public void execute(RequestContext requestContext, Map<String, Object> map) throws KDException {
// 单据状态=已审核
// 初始查询条件
QFilter qFilter = new QFilter("billstatus", QCP.equals, "C");
//qFilter.and(new QFilter("billno",QCP.equals,"FKSQ2507112268"));//测试用
DynamicObject[] collection = BusinessDataServiceHelper.load(AP_PAYAPPLY, "id,billstatus", qFilter.toArray());
if (collection.length!=0){
ArrayList<Long> ids = new ArrayList<>();
for (DynamicObject dynamicObject : collection) {
ids.add(dynamicObject.getLong("id"));
}
Map<Object, DynamicObject> recBillMap = BusinessDataServiceHelper.loadFromCache(ids.toArray(), AP_PAYAPPLY);
OperateOption operateOption = OperateOption.create();
// 不执行警告级别校验器
operateOption.setVariableValue(OperateOptionConst.IGNOREWARN, String.valueOf(true));
// 不显示交互提示自动执行到底
operateOption.setVariableValue(OperateOptionConst.IGNOREINTERACTION, String.valueOf(true));
// 全部校验通过才保存
//operateOption.setVariableValue(OperateOptionConst.STRICTVALIDATION, String.valueOf(true));
//同一个用户在多个界面操作同一张也不允许操作
operateOption.setVariableValue(OperateOptionConst.MUTEX_ISSTRICT, String.valueOf(true));
qFilter.and(new QFilter("paystatus", QCP.not_equals, "Alreadypay"));
// qFilter.and(new QFilter("billno", QCP.equals, "FKSQ2510170003"));
DynamicObject[] collection = BusinessDataServiceHelper.load(AP_PAYAPPLY, "id,billstatus,applyamount,shjh_externalsystemdocume,entry.id,entry.e_asstacttype,entry.e_asstact,entry.e_duedate,entry.lockedamt", qFilter.toArray());
if (collection.length == 0) {
logger.info("没有符合条件的付款申请单");
return;
}
// 批量处理逻辑
processPayApplyBatch(collection);
}
private void processPayApplyBatch(DynamicObject[] collection) throws KDException {
// 收集所有ID
List<Long> payApplyIds = new ArrayList<>();
for (DynamicObject dynamicObject : collection) {
payApplyIds.add(dynamicObject.getLong("id"));
}
// 批量预计算过滤条件
Map<Long, Boolean> supplierFilterMap = batchSupplierFilter(collection);
Set<Long> hasChargeBackIds = batchScheduleFilter(payApplyIds);
Date endOfCurrentMonth = getEndOfCurrentMonth();
OperateOption operateOption = createOperateOption();
List<DynamicObject> qualifiedBills = new ArrayList<>();
// 内存中快速过滤
for (DynamicObject dynamicObject : collection) {
Long payApplyId = dynamicObject.getLong("id");
// 快速跳过检查
if (hasChargeBackIds.contains(payApplyId)) {
continue;
}
DynamicObjectCollection entry = dynamicObject.getDynamicObjectCollection("entry");
if (entry == null || entry.isEmpty()) {
continue;
}
// 金额过滤 - 检查所有分录
BigDecimal applyamount = dynamicObject.getBigDecimal("applyamount");
if (amountEqualFilter(applyamount, entry)) {
continue;
}
// 供应商过滤 - 仅检查第一行分录
Boolean isFilterSupplier = supplierFilterMap.get(payApplyId);
if (isFilterSupplier != null && isFilterSupplier) {
continue;
}
// 日期过滤 - 检查所有分录
if (!expirationDateFilter(entry, endOfCurrentMonth)) {
continue;
}
List<DynamicObject> qualifiedBills = new ArrayList<>();//需要下推的付款申请单的集合
DynamicObjectCollection entry;
boolean scheduleflag;
for (DynamicObject dynamicObject : recBillMap.values()) {
// 注意executeOperate 需要一个数组形式的 DynamicObject[]
entry = dynamicObject.getDynamicObjectCollection("entry");
if (entry!=null && entry.size()!=0){
//判断是否集团内-境内外供应商(外部系统单据类型=I,返回false)
boolean supplierFilter = supplierFilter(dynamicObject,entry.get(0));
//判断是否存在数据在下推期间范围到期日是当前月月底前
boolean expirationDateFilter = expirationDateFilter(entry);
//如果付款申请单有下游排程单且排程单是已退单则说明这个付款申请单不用再下推了
scheduleflag = scheduleFilter(dynamicObject.getLong("id"));
if (!supplierFilter && expirationDateFilter && scheduleflag){
qualifiedBills.add(dynamicObject);
}
executePushOperation(qualifiedBills, operateOption);
}
/**
* 批量处理供应商过滤 - 仅检查第一行分录
*/
private Map<Long, Boolean> batchSupplierFilter(DynamicObject[] collection) {
Map<Long, Boolean> supplierFilterMap = new HashMap<>();
Set<Long> supplierIds = new HashSet<>();
Map<Long, Long> payApplySupplierMap = new HashMap<>();
for (DynamicObject dynamicObject : collection) {
Long payApplyId = dynamicObject.getLong("id");
// 先检查外部系统单据类型
String billType = dynamicObject.getString("shjh_externalsystemdocume");
if ("I".equals(billType)) {
supplierFilterMap.put(payApplyId, false);
continue;
}
if (!qualifiedBills.isEmpty()){
DynamicObject[] billArray = qualifiedBills.toArray(new DynamicObject[0]);
OperationResult operationResult = OperationServiceHelper.executeOperate("pushandsave", AP_PAYAPPLY, billArray, operateOption);
// 可以根据需要处理 operationResult例如检查是否成功获取返回值等
if (operationResult.isSuccess()) {
logger.info("执行 pay 成功,共处理单据数量:" + qualifiedBills.size());
// Log individual successful bills if needed
for (DynamicObject bill : qualifiedBills) {
logger.info("成功单据编号:" + bill.getPkValue());
DynamicObjectCollection entry = dynamicObject.getDynamicObjectCollection("entry");
if (entry != null && !entry.isEmpty()) {
// 仅检查第一行分录
DynamicObject firstEntry = entry.get(0);
String asstacttype = firstEntry.getString("e_asstacttype");
if ("bd_supplier".equals(asstacttype)) {
DynamicObject asstact = firstEntry.getDynamicObject("e_asstact");
if (asstact != null) {
Long supplierId = asstact.getLong("id");
supplierIds.add(supplierId);
payApplySupplierMap.put(payApplyId, supplierId);
} else {
// 第一行分录有供应商类型但没有供应商对象不过滤
supplierFilterMap.put(payApplyId, false);
}
} else {
logger.error("批量下推失败:" + operationResult.getMessage());
}
}else {
logger.info("没有符合条件的单据需要下推");
// 第一行分录不是供应商类型不过滤
supplierFilterMap.put(payApplyId, false);
}
} else {
// 没有分录不过滤
supplierFilterMap.put(payApplyId, false);
}
}
private boolean scheduleFilter(Long payrequestid){
//如果付款申请单有下游排程单且排程单是已退单则说明这个付款申请单不用再下推了返回false
//否则返回true继续下推
//付款申请单的id存于排程单的sourcebillid中 如果id无效果则用sourcebillnumber 对应付款申请单的billno
QFilter sqfilter = new QFilter("sourcebillid", QCP.equals, payrequestid);
sqfilter.and("schedulstatus", QCP.equals, "yetchargeback");//排程状态已退单
sqfilter.and("modifytime",QCP.large_equals,getDate("start"));
sqfilter.and("modifytime",QCP.less_equals,getDate("end"));
if (QueryServiceHelper.exists("psd_schedulebill", sqfilter.toArray())) {
return false;
}
return true;
// 批量查询供应商分类
if (!supplierIds.isEmpty()) {
QFilter supplierFilter = new QFilter("supplier.id", QCP.in, supplierIds.toArray());
DynamicObject[] supplierDetails = BusinessDataServiceHelper.load(
"bd_suppliergroupdetail",
"supplier.id,group.number",
supplierFilter.toArray()
);
// 构建供应商分类映射
Map<Long, String> supplierGroupMap = new HashMap<>();
for (DynamicObject detail : supplierDetails) {
Long supplierId = detail.getLong("supplier.id");
String groupNumber = detail.getString("group.number");
supplierGroupMap.put(supplierId, groupNumber);
}
//entry为付款申请单的第一行
private boolean supplierFilter(DynamicObject dynamicObject,DynamicObject entry){
boolean isFilterSupplier = false;
//如果外部系统单据类型=I,直接返回false
String billType = dynamicObject.getString("shjh_externalsystemdocume");
if ("I".equals(billType)){
return false;
// 为每个单据设置过滤标志
for (Map.Entry<Long, Long> entry : payApplySupplierMap.entrySet()) {
Long payApplyId = entry.getKey();
Long supplierId = entry.getValue();
String groupNumber = supplierGroupMap.get(supplierId);
boolean isFilter = "D200".equals(groupNumber) || "D201".equals(groupNumber);
supplierFilterMap.put(payApplyId, isFilter);
}
String asstacttype = entry.getString("e_asstacttype");
if ("bd_supplier".equals(asstacttype)){
DynamicObject asstact = entry.getDynamicObject("e_asstact");
if (asstact!=null){
//获取供应商分类映射
QFilter qFilter1 = new QFilter("supplier.id", QCP.equals, asstact.getPkValue());
DynamicObject dynamicObject1 = BusinessDataServiceHelper.loadSingle("bd_suppliergroupdetail", qFilter1.toArray());
if (dynamicObject1!=null){
String number = dynamicObject1.getString("group.number");
if ("D200".equals(number) || "D201".equals(number)){
isFilterSupplier=true;
}
}
}
}
return isFilterSupplier;
}
private boolean expirationDateFilter(DynamicObjectCollection entry){
boolean hasValidDueDate = false; // 默认false表示没有符合条件的数据
// 确保所有单据都有过滤结果
for (DynamicObject dynamicObject : collection) {
Long payApplyId = dynamicObject.getLong("id");
if (!supplierFilterMap.containsKey(payApplyId)) {
supplierFilterMap.put(payApplyId, false);
}
}
// 获取当前月的最后一天月底
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.DAY_OF_MONTH, calendar.getActualMaximum(Calendar.DAY_OF_MONTH));
Date endOfCurrentMonth = calendar.getTime();
return supplierFilterMap;
}
//遍历entry集合检查是否有到期日小于等于当前月月底的数据
/**
* 金额过滤方法 - 检查所有分录
*/
private boolean amountEqualFilter(BigDecimal applyamount, DynamicObjectCollection entry) {
BigDecimal entryAmount = BigDecimal.ZERO;
for (DynamicObject dynamicObject : entry) {
BigDecimal lockedamt = dynamicObject.getBigDecimal("lockedamt");
if (lockedamt != null) {
entryAmount = entryAmount.add(lockedamt);
}
}
return applyamount.compareTo(entryAmount) == 0;
}
/**
* 日期过滤方法 - 检查所有分录
* 只要有一个分录的到期日在当前月月底前就返回true
*/
private boolean expirationDateFilter(DynamicObjectCollection entry, Date endOfCurrentMonth) {
for (DynamicObject object : entry) {
Date dueDate = object.getDate("e_duedate");
if (dueDate != null && dueDate.compareTo(endOfCurrentMonth) <= 0) {
hasValidDueDate = true; // 找到符合条件的数据
break; // 只要有一条符合就退出循环
return true;
}
}
return hasValidDueDate;
return false;
}
private Date getDate(String key){
/**
* 批量处理排程单过滤
*/
private Set<Long> batchScheduleFilter(List<Long> payApplyIds) {
Set<Long> hasChargeBackIds = new HashSet<>();
if (payApplyIds.isEmpty()) {
return hasChargeBackIds;
}
// 批量查询有退单的付款申请单
QFilter scheduleFilter = new QFilter("sourcebillid", QCP.in, payApplyIds.toArray());
scheduleFilter.and("schedulstatus", QCP.equals, "yetchargeback");
scheduleFilter.and("modifytime", QCP.large_equals, getDate("start"));
scheduleFilter.and("modifytime", QCP.less_equals, getDate("end"));
DynamicObject[] scheduleBills = BusinessDataServiceHelper.load(
"psd_schedulebill",
"sourcebillid",
scheduleFilter.toArray()
);
for (DynamicObject scheduleBill : scheduleBills) {
hasChargeBackIds.add(scheduleBill.getLong("sourcebillid"));
}
return hasChargeBackIds;
}
// 其他辅助方法保持不变
private Date getEndOfCurrentMonth() {
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.DAY_OF_MONTH, calendar.getActualMaximum(Calendar.DAY_OF_MONTH));
calendar.set(Calendar.HOUR_OF_DAY, 23);
calendar.set(Calendar.MINUTE, 59);
calendar.set(Calendar.SECOND, 59);
calendar.set(Calendar.MILLISECOND, 999);
return calendar.getTime();
}
if ("start".equals(key)){
// 获取月初1号 00:00:00.000
private OperateOption createOperateOption() {
OperateOption operateOption = OperateOption.create();
operateOption.setVariableValue(OperateOptionConst.IGNOREWARN, "true");
operateOption.setVariableValue(OperateOptionConst.IGNOREINTERACTION, "true");
operateOption.setVariableValue(OperateOptionConst.MUTEX_ISSTRICT, "true");
return operateOption;
}
private void executePushOperation(List<DynamicObject> qualifiedBills, OperateOption operateOption) {
if (!qualifiedBills.isEmpty()) {
DynamicObject[] billArray = qualifiedBills.toArray(new DynamicObject[0]);
OperationResult operationResult = OperationServiceHelper.executeOperate(
"pushandsave", AP_PAYAPPLY, billArray, operateOption
);
if (operationResult.isSuccess()) {
logger.info("执行pay成功共处理单据数量" + qualifiedBills.size());
// 可以记录成功单据的ID
for (DynamicObject bill : qualifiedBills) {
logger.info("成功下推单据ID" + bill.getLong("id"));
}
} else {
logger.error("批量下推失败:" + operationResult.getMessage());
// 记录失败详情
if (operationResult.getMessage() != null) {
logger.error("错误信息:" + operationResult.getMessage());
}
}
} else {
logger.info("没有符合条件的单据需要下推");
}
}
private Date getDate(String key) {
Calendar calendar = Calendar.getInstance();
if ("start".equals(key)) {
calendar.set(Calendar.DAY_OF_MONTH, 1);
calendar.set(Calendar.HOUR_OF_DAY, 0);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);
return calendar.getTime();
}else if ("end".equals(key)){
// 获取下个月1号然后减1天得到月底时间部分仍为 00:00:00.000
} else if ("end".equals(key)) {
calendar.add(Calendar.MONTH, 1);
calendar.set(Calendar.DAY_OF_MONTH, 1);
calendar.add(Calendar.DATE, -1);
calendar.set(Calendar.HOUR_OF_DAY, 23);
calendar.set(Calendar.MINUTE, 59);
calendar.set(Calendar.SECOND, 59);
calendar.set(Calendar.MILLISECOND, 999);
return calendar.getTime();
}
return null;

View File

@ -45,12 +45,16 @@ public class RecPushSapTask extends AbstractTask implements Plugin {
logger.info("未找到符合条件的收款单");
return;
}
ArrayList<Long> ids = new ArrayList<>();
for (DynamicObject recBill : recBillList) {
ids.add(recBill.getLong("id"));
}
//按金额正负分组
List<DynamicObject> normalBills = new ArrayList<>(); //正数单据
List<DynamicObject> redBills = new ArrayList<>(); //负数单据红冲
Map<Object, DynamicObject> recBillMap = BusinessDataServiceHelper.loadFromCache(ids.toArray(), KEY_REC_BILL);
for (DynamicObject bill : recBillList) {
for (DynamicObject bill : recBillMap.values()) {
if (bill.getBigDecimal("actrecamt").compareTo(BigDecimal.ZERO) > 0) {
normalBills.add(bill);
} else {