package com.vincent.rsf.server.manager.schedules;
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
import com.vincent.rsf.framework.exception.CoolException;
|
import com.vincent.rsf.server.manager.controller.params.OutStockToTaskParams;
|
import com.vincent.rsf.server.manager.controller.params.PakinItem;
|
import com.vincent.rsf.server.manager.controller.params.WaitPakinParam;
|
import com.vincent.rsf.server.manager.entity.*;
|
import com.vincent.rsf.server.manager.enums.AsnExceStatus;
|
import com.vincent.rsf.server.manager.enums.OrderType;
|
import com.vincent.rsf.server.manager.enums.OrderWorkType;
|
import com.vincent.rsf.server.manager.enums.TaskStsType;
|
import com.vincent.rsf.server.manager.service.*;
|
import com.vincent.rsf.server.manager.utils.LocManageUtil;
|
import com.vincent.rsf.server.system.constant.GlobalConfigCode;
|
import com.vincent.rsf.server.system.constant.SerialRuleCode;
|
import com.vincent.rsf.server.system.entity.Config;
|
import com.vincent.rsf.server.system.service.ConfigService;
|
import com.vincent.rsf.server.system.utils.SerialRuleUtils;
|
import lombok.extern.slf4j.Slf4j;
|
import org.apache.commons.lang3.StringUtils;
|
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.scheduling.annotation.Scheduled;
|
import org.springframework.stereotype.Component;
|
import org.springframework.transaction.annotation.Transactional;
|
|
import java.util.*;
|
import java.util.stream.Collectors;
|
|
/**
|
* 指定物料自动化定时任务:可配置物料编码后,
|
* 1)有库存时自动生成全版出库单;
|
* 2)该物料出库单自动下发任务;
|
* 3)RCS 入库通知时(可选)自动组托,数量可配置。
|
*/
|
@Slf4j
|
@Component
|
public class MaterialAutoSchedules {
|
|
private static final Long SYSTEM_USER_ID = 1L;
|
private static final String DEFAULT_SITE_NO = "1001";
|
|
@Autowired
|
private ConfigService configService;
|
@Autowired
|
private OutStockService outStockService;
|
@Autowired
|
private AsnOrderItemService asnOrderItemService;
|
@Autowired
|
private LocItemService locItemService;
|
@Autowired
|
private LocService locService;
|
@Autowired
|
private MatnrService matnrService;
|
@Autowired
|
private TaskService taskService;
|
@Autowired
|
private TaskItemService taskItemService;
|
@Autowired
|
private com.vincent.rsf.server.api.service.MobileService mobileService;
|
@Autowired
|
private AsnOrderService asnOrderService;
|
@Autowired
|
private WaitPakinItemService waitPakinItemService;
|
|
/**
|
* 定时任务1:指定物料有库存时自动生成全版出库单
|
* 配置:AUTO_FULL_OUT_MATNR_CODE(物料编码)、AUTO_FULL_OUT_ENABLED(true 启用)
|
*/
|
@Scheduled(cron = "0/35 * * * * ?")
|
@Transactional(rollbackFor = Exception.class)
|
public void autoCreateFullOutOrder() {
|
Config enabledConfig = configService.getOne(new LambdaQueryWrapper<Config>().eq(Config::getFlag, GlobalConfigCode.AUTO_FULL_OUT_ENABLED));
|
if (enabledConfig == null || !Boolean.parseBoolean(enabledConfig.getVal())) {
|
return;
|
}
|
Config matnrConfig = configService.getOne(new LambdaQueryWrapper<Config>().eq(Config::getFlag, GlobalConfigCode.AUTO_FULL_OUT_MATNR_CODE));
|
if (matnrConfig == null || StringUtils.isBlank(matnrConfig.getVal())) {
|
return;
|
}
|
String matnrCode = matnrConfig.getVal().trim();
|
Matnr matnr = matnrService.getOne(new LambdaQueryWrapper<Matnr>().eq(Matnr::getCode, matnrCode));
|
if (matnr == null) {
|
log.warn("[自动全版出库单] 物料不存在: {}", matnrCode);
|
return;
|
}
|
// 已有该物料未下发的出库单则本轮不再生成,等下发完后再生成(避免重复)
|
List<Long> initOrderIds = outStockService.list(new LambdaQueryWrapper<WkOrder>()
|
.eq(WkOrder::getType, OrderType.ORDER_OUT.type)
|
.eq(WkOrder::getExceStatus, AsnExceStatus.OUT_STOCK_STATUS_TASK_INIT.val))
|
.stream().map(WkOrder::getId).collect(Collectors.toList());
|
if (!initOrderIds.isEmpty()) {
|
long hasMatnr = asnOrderItemService.count(new LambdaQueryWrapper<WkOrderItem>()
|
.in(WkOrderItem::getOrderId, initOrderIds)
|
.eq(WkOrderItem::getMatnrCode, matnrCode));
|
if (hasMatnr > 0) {
|
return;
|
}
|
}
|
// 按库位分组:该物料、库位状态为 F 的在库;每个库位各生成一张出库单
|
List<LocItem> items = locItemService.list(new LambdaQueryWrapper<LocItem>()
|
.eq(LocItem::getMatnrCode, matnrCode)
|
.gt(LocItem::getAnfme, 0));
|
if (items.isEmpty()) {
|
return;
|
}
|
Map<Long, List<LocItem>> byLocId = items.stream().collect(Collectors.groupingBy(LocItem::getLocId));
|
for (Long locId : byLocId.keySet()) {
|
Loc loc = locService.getById(locId);
|
if (loc == null || !"F".equals(loc.getUseStatus())) {
|
continue;
|
}
|
List<LocItem> locItems = byLocId.get(locId);
|
double sumQty = locItems.stream().mapToDouble(li -> li.getAnfme() != null ? li.getAnfme() : 0).sum();
|
if (sumQty <= 0) continue;
|
try {
|
String ruleCode = SerialRuleUtils.generateRuleCode(SerialRuleCode.SYS_OUT_STOCK_CODE, null);
|
if (StringUtils.isBlank(ruleCode)) {
|
log.warn("[自动全版出库单] 出库单编码规则未配置");
|
break;
|
}
|
WkOrder order = new WkOrder();
|
order.setCode(ruleCode)
|
.setType(OrderType.ORDER_OUT.type)
|
.setWkType(OrderWorkType.ORDER_WORK_TYPE_STOCK_OUT.type)
|
.setExceStatus(AsnExceStatus.OUT_STOCK_STATUS_TASK_INIT.val)
|
.setAnfme(sumQty)
|
.setWorkQty(0.0)
|
.setQty(0.0)
|
.setCreateBy(SYSTEM_USER_ID)
|
.setUpdateBy(SYSTEM_USER_ID);
|
if (!outStockService.save(order)) {
|
throw new CoolException("出库主单保存失败");
|
}
|
LocItem first = locItems.get(0);
|
WkOrderItem orderItem = new WkOrderItem();
|
orderItem.setOrderId(order.getId())
|
.setOrderCode(order.getCode())
|
.setMatnrId(matnr.getId())
|
.setMatnrCode(matnr.getCode())
|
.setMaktx(matnr.getName())
|
.setAnfme(sumQty)
|
.setWorkQty(0.0)
|
.setQty(0.0)
|
.setStockUnit(first.getUnit() != null ? first.getUnit() : "个")
|
.setPurUnit(first.getUnit() != null ? first.getUnit() : "个")
|
.setSplrBatch(first.getSplrBatch())
|
.setBatch(first.getBatch())
|
.setFieldsIndex(first.getFieldsIndex())
|
.setCreateBy(SYSTEM_USER_ID)
|
.setUpdateBy(SYSTEM_USER_ID);
|
if (!asnOrderItemService.save(orderItem)) {
|
throw new CoolException("出库明细保存失败");
|
}
|
log.info("[自动全版出库单] 已生成出库单: {}, 库位: {}, 物料: {}, 数量: {}", order.getCode(), loc.getCode(), matnrCode, sumQty);
|
} catch (Exception e) {
|
log.error("[自动全版出库单] 生成失败, 库位: {}, 物料: {}", loc.getCode(), matnrCode, e);
|
}
|
}
|
}
|
|
/**
|
* 定时任务2:该物料出库单自动下发任务(每 1 分钟)
|
* 配置:AUTO_FULL_OUT_DISPATCH_ENABLED(true 启用)、AUTO_FULL_OUT_MATNR_CODE
|
*/
|
@Scheduled(cron = "0 0/1 * * * ?")
|
@Transactional(rollbackFor = Exception.class)
|
public void autoDispatchOutTask() {
|
Config enabledConfig = configService.getOne(new LambdaQueryWrapper<Config>().eq(Config::getFlag, GlobalConfigCode.AUTO_FULL_OUT_DISPATCH_ENABLED));
|
if (enabledConfig == null || !Boolean.parseBoolean(enabledConfig.getVal())) {
|
return;
|
}
|
Config matnrConfig = configService.getOne(new LambdaQueryWrapper<Config>().eq(Config::getFlag, GlobalConfigCode.AUTO_FULL_OUT_MATNR_CODE));
|
if (matnrConfig == null || StringUtils.isBlank(matnrConfig.getVal())) {
|
return;
|
}
|
String matnrCode = matnrConfig.getVal().trim();
|
List<WkOrder> orders = outStockService.list(new LambdaQueryWrapper<WkOrder>()
|
.eq(WkOrder::getType, OrderType.ORDER_OUT.type)
|
.eq(WkOrder::getExceStatus, AsnExceStatus.OUT_STOCK_STATUS_TASK_INIT.val));
|
for (WkOrder order : orders) {
|
List<WkOrderItem> orderItems = asnOrderItemService.list(new LambdaQueryWrapper<WkOrderItem>()
|
.eq(WkOrderItem::getOrderId, order.getId())
|
.eq(WkOrderItem::getMatnrCode, matnrCode));
|
if (orderItems.isEmpty()) continue;
|
List<OutStockToTaskParams> paramsList = new ArrayList<>();
|
for (WkOrderItem oi : orderItems) {
|
double remaining = (oi.getAnfme() != null ? oi.getAnfme() : 0) - (oi.getWorkQty() != null ? oi.getWorkQty() : 0);
|
if (remaining <= 0) continue;
|
String batch = oi.getSplrBatch() != null ? oi.getSplrBatch() : oi.getBatch();
|
List<LocItem> locItems = LocManageUtil.getFirstInFirstOutItemList(oi.getMatnrCode(), batch, remaining);
|
for (LocItem locItem : locItems) {
|
if (remaining <= 0) break;
|
Loc loc = locService.getById(locItem.getLocId());
|
if (loc == null || !"F".equals(loc.getUseStatus())) continue;
|
double outQty = Math.min(remaining, locItem.getAnfme() != null ? locItem.getAnfme() : 0);
|
if (outQty <= 0) continue;
|
OutStockToTaskParams param = new OutStockToTaskParams();
|
param.setId(locItem.getId());
|
param.setLocCode(loc.getCode());
|
param.setOutQty(outQty);
|
param.setSiteNo(DEFAULT_SITE_NO);
|
param.setBatch(locItem.getBatch());
|
paramsList.add(param);
|
remaining -= outQty;
|
}
|
}
|
if (paramsList.isEmpty()) continue;
|
try {
|
outStockService.genOutStockTask(paramsList, SYSTEM_USER_ID, order.getId());
|
List<TaskItem> taskItems = taskItemService.list(new LambdaQueryWrapper<TaskItem>().eq(TaskItem::getSourceId, order.getId()));
|
if (!taskItems.isEmpty()) {
|
Set<Long> taskIds = taskItems.stream().map(TaskItem::getTaskId).collect(Collectors.toSet());
|
List<Task> tasks = taskService.listByIds(taskIds).stream()
|
.filter(t -> TaskStsType.GENERATE_OUT.id.equals(t.getTaskStatus()))
|
.collect(Collectors.toList());
|
if (!tasks.isEmpty()) {
|
taskService.pubTaskToWcs(tasks);
|
log.info("[自动下发任务] 出库单: {} 已下发任务", order.getCode());
|
}
|
}
|
} catch (Exception e) {
|
log.error("[自动下发任务] 出库单: {} 下发失败", order.getCode(), e);
|
}
|
}
|
}
|
|
/**
|
* 定时任务3:无订单组托 + 自动生成入库单(仅针对配置物料,)
|
* 先按配置物料与数量做无订单组托,再生成入库单并关联组托明细,便于 RCS 入库闭环。
|
* 配置:AUTO_PAKIN_ON_ASN_ENABLED(true)、AUTO_FULL_OUT_MATNR_CODE、AUTO_PAKIN_QTY(数量)
|
*/
|
@Scheduled(cron = "0/35 * * * * ?")
|
@Transactional(rollbackFor = Exception.class)
|
public void autoPakinOnInbound() {
|
Config enabledConfig = configService.getOne(new LambdaQueryWrapper<Config>().eq(Config::getFlag, GlobalConfigCode.AUTO_PAKIN_ON_ASN_ENABLED));
|
if (enabledConfig == null || !Boolean.parseBoolean(enabledConfig.getVal())) {
|
return;
|
}
|
Config matnrConfig = configService.getOne(new LambdaQueryWrapper<Config>().eq(Config::getFlag, GlobalConfigCode.AUTO_FULL_OUT_MATNR_CODE));
|
if (matnrConfig == null || StringUtils.isBlank(matnrConfig.getVal())) {
|
return;
|
}
|
Config qtyConfig = configService.getOne(new LambdaQueryWrapper<Config>().eq(Config::getFlag, GlobalConfigCode.AUTO_PAKIN_QTY));
|
double autoQty = 1.0;
|
if (qtyConfig != null && StringUtils.isNotBlank(qtyConfig.getVal())) {
|
try {
|
autoQty = Double.parseDouble(qtyConfig.getVal().trim());
|
if (autoQty <= 0) autoQty = 1.0;
|
} catch (NumberFormatException e) {
|
// ignore
|
}
|
}
|
String matnrCode = matnrConfig.getVal().trim();
|
Matnr matnr = matnrService.getOne(new LambdaQueryWrapper<Matnr>().eq(Matnr::getCode, matnrCode));
|
if (matnr == null) {
|
log.warn("[无订单自动组托] 物料不存在: {}", matnrCode);
|
return;
|
}
|
// 已有该物料未执行入库单则本轮不继续生成,避免堆积
|
List<Long> initAsnIds = asnOrderService.list(new LambdaQueryWrapper<WkOrder>()
|
.eq(WkOrder::getType, OrderType.ORDER_IN.type)
|
.eq(WkOrder::getExceStatus, AsnExceStatus.ASN_EXCE_STATUS_UN_EXCE.val))
|
.stream().map(WkOrder::getId).collect(Collectors.toList());
|
if (!initAsnIds.isEmpty()) {
|
long hasMatnr = asnOrderItemService.count(new LambdaQueryWrapper<WkOrderItem>()
|
.in(WkOrderItem::getOrderId, initAsnIds)
|
.eq(WkOrderItem::getMatnrCode, matnrCode));
|
if (hasMatnr > 0) {
|
return;
|
}
|
}
|
// 1)无订单组托:仅物料 + 数量,不传 asnCode/id
|
List<PakinItem> pakinItems = new ArrayList<>();
|
PakinItem pi = new PakinItem();
|
pi.setMatnrId(matnr.getId());
|
pi.setReceiptQty(autoQty);
|
pi.setAsnCode(null);
|
pi.setId(null);
|
pakinItems.add(pi);
|
String barcode = "AUTO-PAKIN-" + System.currentTimeMillis();
|
WaitPakinParam param = new WaitPakinParam();
|
param.setBarcode(barcode);
|
param.setItems(pakinItems);
|
WaitPakin waitPakin;
|
try {
|
waitPakin = mobileService.mergeItems(param, SYSTEM_USER_ID);
|
} catch (Exception e) {
|
log.warn("[无订单自动组托] 组托失败: {}", e.getMessage());
|
throw e; // 重新抛出,避免事务被标记 rollback-only 后仍尝试提交导致 UnexpectedRollbackException
|
}
|
// 2)自动生成入库单(一条明细,配置物料 + 数量)
|
String ruleCode = SerialRuleUtils.generateRuleCode(SerialRuleCode.SYS_ASN_ORDER, null);
|
if (StringUtils.isBlank(ruleCode)) {
|
log.warn("[无订单自动组托] 入库单编码规则未配置");
|
return;
|
}
|
WkOrder order = new WkOrder();
|
order.setCode(ruleCode)
|
.setType(OrderType.ORDER_IN.type)
|
.setExceStatus(AsnExceStatus.ASN_EXCE_STATUS_UN_EXCE.val)
|
.setAnfme(autoQty)
|
.setWorkQty(0.0)
|
.setQty(0.0)
|
.setCreateBy(SYSTEM_USER_ID)
|
.setUpdateBy(SYSTEM_USER_ID);
|
if (!asnOrderService.save(order)) {
|
throw new CoolException("入库主单保存失败");
|
}
|
WkOrderItem orderItem = new WkOrderItem();
|
orderItem.setOrderId(order.getId())
|
.setOrderCode(order.getCode())
|
.setMatnrId(matnr.getId())
|
.setMatnrCode(matnr.getCode())
|
.setMaktx(matnr.getName())
|
.setAnfme(autoQty)
|
.setWorkQty(0.0)
|
.setQty(0.0)
|
.setStockUnit(matnr.getStockUnit() != null ? matnr.getStockUnit() : "个")
|
.setPurUnit(matnr.getPurUnit() != null ? matnr.getPurUnit() : "个")
|
.setFieldsIndex(matnr.getFieldsIndex())
|
.setCreateBy(SYSTEM_USER_ID)
|
.setUpdateBy(SYSTEM_USER_ID);
|
if (!asnOrderItemService.save(orderItem)) {
|
throw new CoolException("入库明细保存失败");
|
}
|
// 3)关联组托明细到入库单(asnId / asnCode / asnItemId)
|
boolean updated = waitPakinItemService.update(new LambdaUpdateWrapper<WaitPakinItem>()
|
.eq(WaitPakinItem::getPakinId, waitPakin.getId())
|
.set(WaitPakinItem::getAsnId, order.getId())
|
.set(WaitPakinItem::getAsnCode, order.getCode())
|
.set(WaitPakinItem::getAsnItemId, orderItem.getId()));
|
if (!updated) {
|
log.warn("[无订单自动组托] 组托明细关联入库单失败, pakinId={}", waitPakin.getId());
|
}
|
log.info("[无订单自动组托] 已组托并生成入库单: {}, 物料: {}, 数量: {}", order.getCode(), matnrCode, autoQty);
|
}
|
}
|