1、修复业务模块bug
2、完善凭证管理
This commit is contained in:
+3
@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.annotation.TableField;
|
|||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import org.springblade.transport.pojo.entity.MasterOrder;
|
import org.springblade.transport.pojo.entity.MasterOrder;
|
||||||
|
import org.springblade.transport.pojo.entity.Waybill;
|
||||||
|
|
||||||
import java.io.Serial;
|
import java.io.Serial;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
@@ -37,6 +38,8 @@ public class MasterOrderVO extends MasterOrder {
|
|||||||
@TableField(exist = false)
|
@TableField(exist = false)
|
||||||
private List<Map<String, Object>> routeProgress;
|
private List<Map<String, Object>> routeProgress;
|
||||||
@TableField(exist = false)
|
@TableField(exist = false)
|
||||||
|
private List<Waybill> boundWaybills;
|
||||||
|
@TableField(exist = false)
|
||||||
private BigDecimal totalQuantity;
|
private BigDecimal totalQuantity;
|
||||||
@TableField(exist = false)
|
@TableField(exist = false)
|
||||||
private String createUserName;
|
private String createUserName;
|
||||||
|
|||||||
+20
-6
@@ -10,6 +10,7 @@ import org.springframework.amqp.core.DirectExchange;
|
|||||||
import org.springframework.amqp.core.Queue;
|
import org.springframework.amqp.core.Queue;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 凭证导入消息队列配置。
|
* 凭证导入消息队列配置。
|
||||||
@@ -18,22 +19,35 @@ import org.springframework.context.annotation.Configuration;
|
|||||||
@Configuration
|
@Configuration
|
||||||
public class VoucherImportRabbitConfig {
|
public class VoucherImportRabbitConfig {
|
||||||
|
|
||||||
public static final String EXCHANGE = "tms.voucher.import.exchange";
|
private final String exchange;
|
||||||
public static final String QUEUE = "tms.voucher.import.queue";
|
private final String queue;
|
||||||
public static final String ROUTING_KEY = "tms.voucher.import";
|
private final String routingKey;
|
||||||
|
|
||||||
|
public VoucherImportRabbitConfig(
|
||||||
|
@Value("${voucher.import.rabbit.exchange:tms.voucher.import.exchange}") String exchange,
|
||||||
|
@Value("${voucher.import.rabbit.queue:tms.voucher.import.queue}") String queue,
|
||||||
|
@Value("${voucher.import.rabbit.routing-key:tms.voucher.import}") String routingKey) {
|
||||||
|
this.exchange = exchange;
|
||||||
|
this.queue = queue;
|
||||||
|
this.routingKey = routingKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getExchange() { return exchange; }
|
||||||
|
public String getQueue() { return queue; }
|
||||||
|
public String getRoutingKey() { return routingKey; }
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public DirectExchange voucherImportExchange() {
|
public DirectExchange voucherImportExchange() {
|
||||||
return new DirectExchange(EXCHANGE, true, false);
|
return new DirectExchange(exchange, true, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public Queue voucherImportQueue() {
|
public Queue voucherImportQueue() {
|
||||||
return new Queue(QUEUE, true);
|
return new Queue(queue, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public Binding voucherImportBinding(Queue voucherImportQueue, DirectExchange voucherImportExchange) {
|
public Binding voucherImportBinding(Queue voucherImportQueue, DirectExchange voucherImportExchange) {
|
||||||
return BindingBuilder.bind(voucherImportQueue).to(voucherImportExchange).with(ROUTING_KEY);
|
return BindingBuilder.bind(voucherImportQueue).to(voucherImportExchange).with(routingKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+21
-2
@@ -5,22 +5,41 @@
|
|||||||
package org.springblade.transport.listener;
|
package org.springblade.transport.listener;
|
||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springblade.transport.config.VoucherImportRabbitConfig;
|
import org.springblade.transport.config.VoucherImportRabbitConfig;
|
||||||
import org.springblade.transport.service.IVoucherManageService;
|
import org.springblade.transport.service.IVoucherManageService;
|
||||||
import org.springframework.amqp.rabbit.annotation.RabbitListener;
|
import org.springframework.amqp.rabbit.annotation.RabbitListener;
|
||||||
|
import org.springframework.amqp.rabbit.listener.MessageListenerContainer;
|
||||||
|
import org.springframework.amqp.rabbit.listener.RabbitListenerEndpointRegistry;
|
||||||
|
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.context.event.EventListener;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 凭证压缩包后台处理消费者。
|
* 凭证压缩包后台处理消费者。
|
||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
|
@Slf4j
|
||||||
public class VoucherImportMessageListener {
|
public class VoucherImportMessageListener {
|
||||||
|
|
||||||
private final IVoucherManageService voucherManageService;
|
private static final String LISTENER_ID = "voucherImportMessageListener";
|
||||||
|
|
||||||
@RabbitListener(queues = VoucherImportRabbitConfig.QUEUE)
|
private final IVoucherManageService voucherManageService;
|
||||||
|
private final VoucherImportRabbitConfig voucherImportRabbitConfig;
|
||||||
|
private final RabbitListenerEndpointRegistry rabbitListenerEndpointRegistry;
|
||||||
|
|
||||||
|
@EventListener(ApplicationReadyEvent.class)
|
||||||
|
public void logConsumerStatus() {
|
||||||
|
MessageListenerContainer container = rabbitListenerEndpointRegistry.getListenerContainer(LISTENER_ID);
|
||||||
|
log.info("[凭证MQ] 消费者状态 listenerId={}, queue={}, registered={}, running={}",
|
||||||
|
LISTENER_ID, voucherImportRabbitConfig.getQueue(), container != null, container != null && container.isRunning());
|
||||||
|
}
|
||||||
|
|
||||||
|
@RabbitListener(id = LISTENER_ID, queues = "${voucher.import.rabbit.queue:tms.voucher.import.queue}")
|
||||||
public void processVoucher(Long voucherId) {
|
public void processVoucher(Long voucherId) {
|
||||||
|
log.info("[凭证MQ] 收到处理任务 queue={}, voucherId={}", voucherImportRabbitConfig.getQueue(), voucherId);
|
||||||
voucherManageService.processUploadedVoucher(voucherId);
|
voucherManageService.processUploadedVoucher(voucherId);
|
||||||
|
log.info("[凭证MQ] 处理任务完成 voucherId={}", voucherId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-2
@@ -5,6 +5,7 @@
|
|||||||
package org.springblade.transport.listener;
|
package org.springblade.transport.listener;
|
||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springblade.transport.config.VoucherImportRabbitConfig;
|
import org.springblade.transport.config.VoucherImportRabbitConfig;
|
||||||
import org.springblade.transport.event.VoucherUploadCompletedEvent;
|
import org.springblade.transport.event.VoucherUploadCompletedEvent;
|
||||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||||
@@ -17,13 +18,17 @@ import org.springframework.transaction.event.TransactionalEventListener;
|
|||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
|
@Slf4j
|
||||||
public class VoucherUploadCompletedListener {
|
public class VoucherUploadCompletedListener {
|
||||||
|
|
||||||
private final RabbitTemplate rabbitTemplate;
|
private final RabbitTemplate rabbitTemplate;
|
||||||
|
private final VoucherImportRabbitConfig voucherImportRabbitConfig;
|
||||||
|
|
||||||
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
|
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
|
||||||
public void publish(VoucherUploadCompletedEvent event) {
|
public void publish(VoucherUploadCompletedEvent event) {
|
||||||
rabbitTemplate.convertAndSend(VoucherImportRabbitConfig.EXCHANGE,
|
log.info("[凭证MQ] 投递处理任务 exchange={}, routingKey={}, voucherId={}",
|
||||||
VoucherImportRabbitConfig.ROUTING_KEY, event.getVoucherId());
|
voucherImportRabbitConfig.getExchange(), voucherImportRabbitConfig.getRoutingKey(), event.getVoucherId());
|
||||||
|
rabbitTemplate.convertAndSend(voucherImportRabbitConfig.getExchange(),
|
||||||
|
voucherImportRabbitConfig.getRoutingKey(), event.getVoucherId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+72
-22
@@ -78,7 +78,7 @@ public class MasterOrderServiceImpl extends BaseServiceImpl<MasterOrderMapper, M
|
|||||||
public MasterOrderVO submit(MasterOrderVO request, boolean draft) {
|
public MasterOrderVO submit(MasterOrderVO request, boolean draft) {
|
||||||
boolean created = Func.isEmpty(request.getId());
|
boolean created = Func.isEmpty(request.getId());
|
||||||
MasterOrder target = created ? new MasterOrder() : getRequired(request.getId());
|
MasterOrder target = created ? new MasterOrder() : getRequired(request.getId());
|
||||||
if (!created && ("dispatching".equals(target.getBusinessStatus()) || "completed".equals(target.getBusinessStatus()))) {
|
if (!created && "completed".equals(target.getBusinessStatus())) {
|
||||||
assertRestrictedEdit(target, request);
|
assertRestrictedEdit(target, request);
|
||||||
}
|
}
|
||||||
if (!created && "closed".equals(target.getBusinessStatus())) throw new ServiceException("调度关闭的总单不能编辑");
|
if (!created && "closed".equals(target.getBusinessStatus())) throw new ServiceException("调度关闭的总单不能编辑");
|
||||||
@@ -175,7 +175,8 @@ public class MasterOrderServiceImpl extends BaseServiceImpl<MasterOrderMapper, M
|
|||||||
waybill.setTransportType(string(dispatch, "transportType")); waybill.setCarrierType(string(dispatch, "carrierType"));
|
waybill.setTransportType(string(dispatch, "transportType")); waybill.setCarrierType(string(dispatch, "carrierType"));
|
||||||
waybill.setCarrierName(string(dispatch, "carrierName")); waybill.setDriverName(string(dispatch, "driverName"));
|
waybill.setCarrierName(string(dispatch, "carrierName")); waybill.setDriverName(string(dispatch, "driverName"));
|
||||||
waybill.setDriverPhone(string(dispatch, "driverPhone")); waybill.setVehicleNo(string(dispatch, "vehicleNo")); waybill.setTrailerVehicleNo(string(dispatch, "trailerVehicleNo"));
|
waybill.setDriverPhone(string(dispatch, "driverPhone")); waybill.setVehicleNo(string(dispatch, "vehicleNo")); waybill.setTrailerVehicleNo(string(dispatch, "trailerVehicleNo"));
|
||||||
waybill.setEscortName(string(dispatch, "escortName")); waybill.setEscortPhone(string(dispatch, "escortPhone")); waybill.setMileage(decimal(dispatch, "mileage"));
|
waybill.setCaptainName(string(dispatch, "captainName")); waybill.setCabinNo(string(dispatch, "cabinNo")); waybill.setContainerNo(string(dispatch, "containerNo"));
|
||||||
|
waybill.setEscortName(string(dispatch, "escortName")); waybill.setEscortPhone(string(dispatch, "escortPhone")); waybill.setMileage(nullableDecimal(dispatch, "mileage"));
|
||||||
waybill.setDepartureName(string(dispatch, "departureName")); waybill.setDepartureAddress(string(dispatch, "departureAddress"));
|
waybill.setDepartureName(string(dispatch, "departureName")); waybill.setDepartureAddress(string(dispatch, "departureAddress"));
|
||||||
waybill.setArrivalName(string(dispatch, "arrivalName")); waybill.setArrivalAddress(string(dispatch, "arrivalAddress"));
|
waybill.setArrivalName(string(dispatch, "arrivalName")); waybill.setArrivalAddress(string(dispatch, "arrivalAddress"));
|
||||||
waybill.setCargoName(joinGoodsField(dispatches, "cargoName")); waybill.setCargoType(joinGoodsField(dispatches, "cargoType"));
|
waybill.setCargoName(joinGoodsField(dispatches, "cargoName")); waybill.setCargoType(joinGoodsField(dispatches, "cargoType"));
|
||||||
@@ -244,7 +245,9 @@ public class MasterOrderServiceImpl extends BaseServiceImpl<MasterOrderMapper, M
|
|||||||
ContractManage contract = resolveContract(entity);
|
ContractManage contract = resolveContract(entity);
|
||||||
vo.setContractNo(contract == null ? null : contract.getContractNo());
|
vo.setContractNo(contract == null ? null : contract.getContractNo());
|
||||||
vo.setRoutes(parseArray(entity.getRouteJson())); vo.setGoods(parseArray(entity.getGoodsJson())); vo.setTotalQuantity(totalQuantity(vo.getGoods()));
|
vo.setRoutes(parseArray(entity.getRouteJson())); vo.setGoods(parseArray(entity.getGoodsJson())); vo.setTotalQuantity(totalQuantity(vo.getGoods()));
|
||||||
vo.setRouteProgress(buildProgress(entity, vo.getRoutes()));
|
List<Waybill> boundWaybills = waybillsByMasterNo(entity.getMasterNo());
|
||||||
|
vo.setBoundWaybills(boundWaybills);
|
||||||
|
vo.setRouteProgress(buildProgress(entity, vo.getRoutes(), boundWaybills));
|
||||||
vo.setCreateUserName(UserCache.getUserRealName(entity.getCreateUser())); vo.setUpdateUserName(UserCache.getUserRealName(entity.getUpdateUser()));
|
vo.setCreateUserName(UserCache.getUserRealName(entity.getCreateUser())); vo.setUpdateUserName(UserCache.getUserRealName(entity.getUpdateUser()));
|
||||||
return vo;
|
return vo;
|
||||||
}
|
}
|
||||||
@@ -260,16 +263,18 @@ public class MasterOrderServiceImpl extends BaseServiceImpl<MasterOrderMapper, M
|
|||||||
return contractManageService.getOne(query, false);
|
return contractManageService.getOne(query, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Map<String, Object>> buildProgress(MasterOrder masterOrder, List<Map<String, Object>> routes) {
|
private List<Map<String, Object>> buildProgress(MasterOrder masterOrder, List<Map<String, Object>> routes, List<Waybill> waybills) {
|
||||||
List<Waybill> waybills = waybillService.list(new LambdaQueryWrapper<Waybill>().eq(Waybill::getMasterNo, masterOrder.getMasterNo()));
|
|
||||||
List<TransportPlan> plans = transportPlanService.list(new LambdaQueryWrapper<TransportPlan>().eq(TransportPlan::getMasterNo, masterOrder.getMasterNo()));
|
List<TransportPlan> plans = transportPlanService.list(new LambdaQueryWrapper<TransportPlan>().eq(TransportPlan::getMasterNo, masterOrder.getMasterNo()));
|
||||||
for (Map<String, Object> route : routes) {
|
for (Map<String, Object> route : routes) {
|
||||||
String segmentNo = string(route, "segmentNo");
|
String segmentNo = string(route, "segmentNo");
|
||||||
route.put("dispatchedQuantity", dispatchedQuantity(masterOrder.getMasterNo(), segmentNo));
|
List<Waybill> routeWaybills = waybills.stream().filter(item -> belongsToRoute(item, route)).toList();
|
||||||
route.put("dispatchedGoods", dispatchedGoods(masterOrder.getMasterNo(), segmentNo));
|
List<TransportPlan> routePlans = plans.stream().filter(item -> belongsToRoute(item, route)).toList();
|
||||||
route.put("arrivedQuantity", waybills.stream().filter(item -> Objects.equals(segmentNo, item.getRelationNo()) && "completed".equals(item.getBusinessStatus())).map(Waybill::getQuantity).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add));
|
Map<String, BigDecimal> dispatchedGoods = dispatchedGoods(routeWaybills, routePlans);
|
||||||
route.put("waybills", waybills.stream().filter(item -> Objects.equals(segmentNo, item.getRelationNo())).toList());
|
route.put("dispatchedQuantity", dispatchedGoods.values().stream().reduce(BigDecimal.ZERO, BigDecimal::add));
|
||||||
route.put("transportPlans", plans.stream().filter(item -> Objects.equals(segmentNo, item.getRelationNo())).toList());
|
route.put("dispatchedGoods", dispatchedGoods);
|
||||||
|
route.put("arrivedQuantity", routeWaybills.stream().filter(item -> "completed".equals(item.getBusinessStatus())).flatMap(item -> waybillGoods(item).stream()).map(item -> decimal(item, "quantity")).reduce(BigDecimal.ZERO, BigDecimal::add));
|
||||||
|
route.put("waybills", routeWaybills);
|
||||||
|
route.put("transportPlans", routePlans);
|
||||||
}
|
}
|
||||||
return routes;
|
return routes;
|
||||||
}
|
}
|
||||||
@@ -282,7 +287,7 @@ public class MasterOrderServiceImpl extends BaseServiceImpl<MasterOrderMapper, M
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void assertRestrictedEdit(MasterOrder oldRecord, MasterOrderVO request) {
|
private void assertRestrictedEdit(MasterOrder oldRecord, MasterOrderVO request) {
|
||||||
if (!Objects.equals(oldRecord.getProjectId(), request.getProjectId()) || !Objects.equals(oldRecord.getRouteJson(), JsonUtil.toJson(request.getRoutes()))) throw new ServiceException("调度中或调度完成的总单不能修改基本信息和路线");
|
if (!Objects.equals(oldRecord.getProjectId(), request.getProjectId()) || !Objects.equals(oldRecord.getRouteJson(), JsonUtil.toJson(request.getRoutes()))) throw new ServiceException("调度完成的总单不能修改基本信息和路线");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void validate(MasterOrder masterOrder, boolean draft) {
|
private void validate(MasterOrder masterOrder, boolean draft) {
|
||||||
@@ -339,9 +344,16 @@ public class MasterOrderServiceImpl extends BaseServiceImpl<MasterOrderMapper, M
|
|||||||
}
|
}
|
||||||
private void validateCarrier(Map<String, Object> dispatch) {
|
private void validateCarrier(Map<String, Object> dispatch) {
|
||||||
String carrierType = string(dispatch, "carrierType", "承运商");
|
String carrierType = string(dispatch, "carrierType", "承运商");
|
||||||
|
boolean road = string(dispatch, "transportType", "").toLowerCase().contains("road") || string(dispatch, "transportType", "").contains("公路");
|
||||||
|
if (!road) {
|
||||||
|
if (Func.isEmpty(string(dispatch, "vehicleNo")) || Func.isEmpty(string(dispatch, "captainName")) || Func.isEmpty(string(dispatch, "driverPhone")) || Func.isEmpty(string(dispatch, "containerNo")) || Func.isEmpty(string(dispatch, "cabinNo")) || Func.isEmpty(string(dispatch, "mileage")) || decimal(dispatch, "mileage").compareTo(BigDecimal.ZERO) <= 0 || ("承运商".equals(carrierType) && Func.isEmpty(string(dispatch, "carrierName")))) {
|
||||||
|
throw new ServiceException("非公路运输的承运信息不完整");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (Func.isEmpty(string(dispatch, "vehicleNo"))) throw new ServiceException("运单车牌号不能为空");
|
if (Func.isEmpty(string(dispatch, "vehicleNo"))) throw new ServiceException("运单车牌号不能为空");
|
||||||
if ("承运商".equals(carrierType)) {
|
if ("承运商".equals(carrierType)) {
|
||||||
if (Func.isEmpty(string(dispatch, "carrierName"))) throw new ServiceException("运单承运商不能为空");
|
if (Func.isEmpty(string(dispatch, "carrierName")) || Func.isEmpty(string(dispatch, "mileage")) || decimal(dispatch, "mileage").compareTo(BigDecimal.ZERO) <= 0) throw new ServiceException("承运商、里程不能为空且里程必须为正整数");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (Func.isEmpty(string(dispatch, "driverName")) || Func.isEmpty(string(dispatch, "driverPhone")) || Func.isEmpty(string(dispatch, "trailerVehicleNo")) || Func.isEmpty(string(dispatch, "escortName")) || Func.isEmpty(string(dispatch, "escortPhone")) || Func.isEmpty(string(dispatch, "mileage")) || decimal(dispatch, "mileage").compareTo(BigDecimal.ZERO) < 0) throw new ServiceException("自运或网货平台的车辆与人员信息不完整");
|
if (Func.isEmpty(string(dispatch, "driverName")) || Func.isEmpty(string(dispatch, "driverPhone")) || Func.isEmpty(string(dispatch, "trailerVehicleNo")) || Func.isEmpty(string(dispatch, "escortName")) || Func.isEmpty(string(dispatch, "escortPhone")) || Func.isEmpty(string(dispatch, "mileage")) || decimal(dispatch, "mileage").compareTo(BigDecimal.ZERO) < 0) throw new ServiceException("自运或网货平台的车辆与人员信息不完整");
|
||||||
@@ -350,19 +362,57 @@ public class MasterOrderServiceImpl extends BaseServiceImpl<MasterOrderMapper, M
|
|||||||
return dispatchedGoods(masterNo, segmentNo).values().stream().reduce(BigDecimal.ZERO, BigDecimal::add);
|
return dispatchedGoods(masterNo, segmentNo).values().stream().reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||||
}
|
}
|
||||||
private Map<String, BigDecimal> dispatchedGoods(String masterNo, String segmentNo) {
|
private Map<String, BigDecimal> dispatchedGoods(String masterNo, String segmentNo) {
|
||||||
Map<String, BigDecimal> result = new LinkedHashMap<>();
|
List<Waybill> waybills = waybillsByMasterNo(masterNo).stream().filter(item -> Func.isEmpty(segmentNo) || Objects.equals(segmentNo, item.getRelationNo())).toList();
|
||||||
LambdaQueryWrapper<Waybill> billQuery = new LambdaQueryWrapper<Waybill>().eq(Waybill::getMasterNo, masterNo);
|
|
||||||
if (Func.isNotEmpty(segmentNo)) billQuery.eq(Waybill::getRelationNo, segmentNo);
|
|
||||||
for (Waybill bill : waybillService.list(billQuery)) {
|
|
||||||
List<Map<String, Object>> goods = parseArray(bill.getGoodsJson());
|
|
||||||
if (goods.isEmpty()) goods = List.of(Map.of("cargoName", bill.getCargoName(), "cargoType", bill.getCargoType(), "quantity", bill.getQuantity()));
|
|
||||||
for (Map<String, Object> goodsItem : goods) result.merge(goodsKey(goodsItem), decimal(goodsItem, "quantity"), BigDecimal::add);
|
|
||||||
}
|
|
||||||
LambdaQueryWrapper<TransportPlan> planQuery = new LambdaQueryWrapper<TransportPlan>().eq(TransportPlan::getMasterNo, masterNo);
|
LambdaQueryWrapper<TransportPlan> planQuery = new LambdaQueryWrapper<TransportPlan>().eq(TransportPlan::getMasterNo, masterNo);
|
||||||
if (Func.isNotEmpty(segmentNo)) planQuery.eq(TransportPlan::getRelationNo, segmentNo);
|
if (Func.isNotEmpty(segmentNo)) planQuery.eq(TransportPlan::getRelationNo, segmentNo);
|
||||||
for (TransportPlan plan : transportPlanService.list(planQuery)) for (Map<String, Object> goods : parseArray(plan.getGoodsJson())) result.merge(goodsKey(goods), decimal(goods, "quantity"), BigDecimal::add);
|
return dispatchedGoods(waybills, transportPlanService.list(planQuery));
|
||||||
|
}
|
||||||
|
private List<Waybill> waybillsByMasterNo(String masterNo) {
|
||||||
|
if (Func.isEmpty(masterNo)) return List.of();
|
||||||
|
return waybillService.list(new LambdaQueryWrapper<Waybill>().eq(Waybill::getMasterNo, masterNo));
|
||||||
|
}
|
||||||
|
private Map<String, BigDecimal> dispatchedGoods(List<Waybill> waybills, List<TransportPlan> plans) {
|
||||||
|
Map<String, BigDecimal> result = new LinkedHashMap<>();
|
||||||
|
for (Waybill bill : waybills) for (Map<String, Object> goods : waybillGoods(bill)) result.merge(goodsKey(goods), decimal(goods, "quantity"), BigDecimal::add);
|
||||||
|
for (TransportPlan plan : plans) for (Map<String, Object> goods : parseArray(plan.getGoodsJson())) result.merge(goodsKey(goods), decimal(goods, "quantity"), BigDecimal::add);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
private List<Map<String, Object>> waybillGoods(Waybill waybill) {
|
||||||
|
List<Map<String, Object>> goods = parseArray(waybill.getGoodsJson());
|
||||||
|
if (!goods.isEmpty()) return goods;
|
||||||
|
Map<String, Object> fallback = new LinkedHashMap<>();
|
||||||
|
fallback.put("cargoName", waybill.getCargoName()); fallback.put("cargoType", waybill.getCargoType()); fallback.put("quantity", waybill.getQuantity());
|
||||||
|
return List.of(fallback);
|
||||||
|
}
|
||||||
|
private boolean belongsToRoute(Waybill waybill, Map<String, Object> route) {
|
||||||
|
return belongsToRoute(waybill.getRelationNo(), waybill.getTransportType(), waybill.getDepartureName(), waybill.getDepartureAddress(), waybill.getArrivalName(), waybill.getArrivalAddress(), route);
|
||||||
|
}
|
||||||
|
private boolean belongsToRoute(TransportPlan plan, Map<String, Object> route) {
|
||||||
|
return belongsToRoute(plan.getRelationNo(), plan.getTransportType(), plan.getDepartureName(), plan.getDepartureAddress(), plan.getArrivalName(), plan.getArrivalAddress(), route);
|
||||||
|
}
|
||||||
|
private boolean belongsToRoute(String relationNo, String transportType, String departureName, String departureAddress, String arrivalName, String arrivalAddress, Map<String, Object> route) {
|
||||||
|
if (Func.isNotEmpty(relationNo)) return Objects.equals(relationNo, string(route, "segmentNo"));
|
||||||
|
return sameTransportType(transportType, string(route, "transportType"))
|
||||||
|
&& sameLocation(departureName, departureAddress, string(route, "departureName"), string(route, "departureAddress"))
|
||||||
|
&& sameLocation(arrivalName, arrivalAddress, string(route, "arrivalName"), string(route, "arrivalAddress"));
|
||||||
|
}
|
||||||
|
private boolean sameLocation(String name, String address, String routeName, String routeAddress) {
|
||||||
|
return (Func.isNotEmpty(address) && Objects.equals(address, routeAddress)) || (Func.isNotEmpty(name) && Objects.equals(name, routeName));
|
||||||
|
}
|
||||||
|
private boolean sameTransportType(String left, String right) {
|
||||||
|
if (Objects.equals(left, right)) return true;
|
||||||
|
return transportTypeName(left).equals(transportTypeName(right));
|
||||||
|
}
|
||||||
|
private String transportTypeName(String value) {
|
||||||
|
String normalized = value == null ? "" : value.toLowerCase();
|
||||||
|
return switch (normalized) {
|
||||||
|
case "road" -> "公路运输";
|
||||||
|
case "railway" -> "铁路运输";
|
||||||
|
case "river" -> "水路运输";
|
||||||
|
case "air" -> "航空运输";
|
||||||
|
default -> value == null ? "" : value;
|
||||||
|
};
|
||||||
|
}
|
||||||
private BigDecimal totalQuantity(List<Map<String, Object>> goods) { return goods.stream().map(item -> decimal(item, "quantity")).reduce(BigDecimal.ZERO, BigDecimal::add); }
|
private BigDecimal totalQuantity(List<Map<String, Object>> goods) { return goods.stream().map(item -> decimal(item, "quantity")).reduce(BigDecimal.ZERO, BigDecimal::add); }
|
||||||
private String buildFreightJson(BigDecimal quantity, BigDecimal freightTotal, Map<String, Object> dispatch) {
|
private String buildFreightJson(BigDecimal quantity, BigDecimal freightTotal, Map<String, Object> dispatch) {
|
||||||
Map<String, Object> freight = new LinkedHashMap<>();
|
Map<String, Object> freight = new LinkedHashMap<>();
|
||||||
@@ -373,7 +423,7 @@ public class MasterOrderServiceImpl extends BaseServiceImpl<MasterOrderMapper, M
|
|||||||
freight.put("currency", string(dispatch, "currency", "CNY"));
|
freight.put("currency", string(dispatch, "currency", "CNY"));
|
||||||
return JsonUtil.toJson(freight);
|
return JsonUtil.toJson(freight);
|
||||||
}
|
}
|
||||||
private String waybillGroupKey(Map<String, Object> dispatch) { return String.join("\u0000", string(dispatch, "segmentNo", ""), string(dispatch, "carrierType", ""), string(dispatch, "carrierName", ""), string(dispatch, "driverName", ""), string(dispatch, "vehicleNo", "")); }
|
private String waybillGroupKey(Map<String, Object> dispatch) { return String.join("\u0000", string(dispatch, "segmentNo", ""), string(dispatch, "carrierType", ""), string(dispatch, "carrierName", ""), string(dispatch, "driverName", ""), string(dispatch, "driverPhone", ""), string(dispatch, "vehicleNo", ""), string(dispatch, "captainName", ""), string(dispatch, "containerNo", ""), string(dispatch, "cabinNo", "")); }
|
||||||
private String joinGoodsField(List<Map<String, Object>> dispatches, String field) { return dispatches.stream().map(item -> string(item, field, "")).filter(Func::isNotEmpty).distinct().reduce((left, right) -> left + "、" + right).orElse(""); }
|
private String joinGoodsField(List<Map<String, Object>> dispatches, String field) { return dispatches.stream().map(item -> string(item, field, "")).filter(Func::isNotEmpty).distinct().reduce((left, right) -> left + "、" + right).orElse(""); }
|
||||||
private BigDecimal decimal(Map<String, Object> values, String key) { try { return new BigDecimal(string(values, key, "0")); } catch (Exception exception) { return BigDecimal.ZERO; } }
|
private BigDecimal decimal(Map<String, Object> values, String key) { try { return new BigDecimal(string(values, key, "0")); } catch (Exception exception) { return BigDecimal.ZERO; } }
|
||||||
private BigDecimal nullableDecimal(Map<String, Object> values, String key) { String value = string(values, key); if (Func.isEmpty(value)) return null; try { return new BigDecimal(value); } catch (Exception exception) { return null; } }
|
private BigDecimal nullableDecimal(Map<String, Object> values, String key) { String value = string(values, key); if (Func.isEmpty(value)) return null; try { return new BigDecimal(value); } catch (Exception exception) { return null; } }
|
||||||
|
|||||||
+46
-5
@@ -154,9 +154,13 @@ public class VoucherManageServiceImpl extends BaseServiceImpl<VoucherManageMappe
|
|||||||
voucher.setWaybillBatchNo(relations.stream().map(VoucherWaybillBatch::getWaybillBatchNo).distinct().collect(Collectors.joining(",")));
|
voucher.setWaybillBatchNo(relations.stream().map(VoucherWaybillBatch::getWaybillBatchNo).distinct().collect(Collectors.joining(",")));
|
||||||
voucher.setRelatedWaybillCount(0);
|
voucher.setRelatedWaybillCount(0);
|
||||||
voucher.setUnRelatedWaybillCount(0);
|
voucher.setUnRelatedWaybillCount(0);
|
||||||
voucher.setProcessStatus("处理中");
|
voucher.setProcessStatus(Func.isNotEmpty(voucher.getFileUrl()) ? "处理中" : "上传中");
|
||||||
updateById(voucher);
|
updateById(voucher);
|
||||||
eventPublisher.publishEvent(new VoucherUploadCompletedEvent(voucher.getId()));
|
if (Func.isNotEmpty(voucher.getFileUrl())) {
|
||||||
|
eventPublisher.publishEvent(new VoucherUploadCompletedEvent(voucher.getId()));
|
||||||
|
} else {
|
||||||
|
log.info("[凭证MQ] 暂不投递处理任务,等待文件上传完成 voucherId={}, fileTaskId={}", voucher.getId(), voucher.getFileTaskId());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -172,8 +176,10 @@ public class VoucherManageServiceImpl extends BaseServiceImpl<VoucherManageMappe
|
|||||||
voucher.setFileTaskId(request.getFileTaskId());
|
voucher.setFileTaskId(request.getFileTaskId());
|
||||||
voucher.setFileName(request.getFileName());
|
voucher.setFileName(request.getFileName());
|
||||||
voucher.setFileUrl(request.getFileUrl());
|
voucher.setFileUrl(request.getFileUrl());
|
||||||
voucher.setProcessStatus("处理中");
|
voucher.setProcessStatus("上传完成");
|
||||||
updateById(voucher);
|
updateById(voucher);
|
||||||
|
log.info("[凭证MQ] 文件上传完成,等待确认关联运输批次后投递任务 voucherId={}, voucherBatchNo={}, fileTaskId={}",
|
||||||
|
voucher.getId(), voucher.getVoucherBatchNo(), voucher.getFileTaskId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -195,17 +201,32 @@ public class VoucherManageServiceImpl extends BaseServiceImpl<VoucherManageMappe
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!force && "处理完成".equals(voucher.getProcessStatus())) {
|
if (!force && "处理完成".equals(voucher.getProcessStatus())) {
|
||||||
|
log.info("[凭证处理] 跳过已完成任务 voucherId={}, voucherBatchNo={}", voucherId, voucher.getVoucherBatchNo());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
if (Func.isEmpty(voucher.getFileUrl())) {
|
||||||
|
throw new ServiceException("凭证文件地址为空,请在上传完成后重新提交");
|
||||||
|
}
|
||||||
|
log.info("[凭证处理] 进度 0%:开始处理 voucherId={}, voucherBatchNo={}, fileName={}, force={}",
|
||||||
|
voucherId, voucher.getVoucherBatchNo(), voucher.getFileName(), force);
|
||||||
validateMinioConfig();
|
validateMinioConfig();
|
||||||
List<Waybill> waybills = listRelatedWaybills(voucher);
|
List<Waybill> waybills = listRelatedWaybills(voucher);
|
||||||
|
log.info("[凭证处理] 进度 10%:已查询关联运单 voucherId={}, waybillCount={}", voucherId, waybills.size());
|
||||||
Map<String, Waybill> waybillByPlate = new HashMap<>();
|
Map<String, Waybill> waybillByPlate = new HashMap<>();
|
||||||
for (Waybill waybill : waybills) {
|
for (Waybill waybill : waybills) {
|
||||||
waybillPlateNumbers(waybill).forEach(plateNo -> waybillByPlate.putIfAbsent(plateNo, waybill));
|
for (String plateNo : waybillPlateNumbers(waybill)) {
|
||||||
|
Waybill existing = waybillByPlate.putIfAbsent(plateNo, waybill);
|
||||||
|
if (existing != null && !Objects.equals(existing.getId(), waybill.getId())) {
|
||||||
|
log.warn("[凭证处理] 车牌对应多个关联运单 voucherId={}, plateNo={}, firstWaybillNo={}, duplicateWaybillNo={}",
|
||||||
|
voucherId, plateNo, existing.getWaybillNo(), waybill.getWaybillNo());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
log.info("[凭证处理] 进度 20%:已建立车牌匹配索引 voucherId={}, plateCount={}", voucherId, waybillByPlate.size());
|
||||||
voucherImageMapper.deleteByVoucherId(voucher.getId());
|
voucherImageMapper.deleteByVoucherId(voucher.getId());
|
||||||
int imageCount = 0;
|
int imageCount = 0;
|
||||||
|
int matchedImageCount = 0;
|
||||||
Set<Long> relatedWaybillIds = new HashSet<>();
|
Set<Long> relatedWaybillIds = new HashSet<>();
|
||||||
try (InputStream source = openSourceFile(voucher.getFileUrl());
|
try (InputStream source = openSourceFile(voucher.getFileUrl());
|
||||||
ZipInputStream zipInputStream = new ZipInputStream(new BufferedInputStream(source), StandardCharsets.UTF_8)) {
|
ZipInputStream zipInputStream = new ZipInputStream(new BufferedInputStream(source), StandardCharsets.UTF_8)) {
|
||||||
@@ -214,7 +235,11 @@ public class VoucherManageServiceImpl extends BaseServiceImpl<VoucherManageMappe
|
|||||||
if (entry.isDirectory()) {
|
if (entry.isDirectory()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
String[] pathParts = entry.getName().replace('\\', '/').split("/");
|
String entryName = entry.getName().replace('\\', '/');
|
||||||
|
if (entryName.equals("__MACOSX") || entryName.startsWith("__MACOSX/")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String[] pathParts = entryName.split("/");
|
||||||
if (pathParts.length < 2 || !isImageFile(pathParts[pathParts.length - 1])) {
|
if (pathParts.length < 2 || !isImageFile(pathParts[pathParts.length - 1])) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -224,6 +249,13 @@ public class VoucherManageServiceImpl extends BaseServiceImpl<VoucherManageMappe
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
Waybill waybill = waybillByPlate.get(plateNo);
|
Waybill waybill = waybillByPlate.get(plateNo);
|
||||||
|
if (waybill == null) {
|
||||||
|
log.warn("[凭证处理] 图片未匹配运单 voucherId={}, plateNo={}, indexedPlates={}",
|
||||||
|
voucherId, plateNo, waybillByPlate.keySet());
|
||||||
|
} else {
|
||||||
|
log.info("[凭证处理] 图片匹配运单 voucherId={}, plateNo={}, waybillId={}, waybillNo={}",
|
||||||
|
voucherId, plateNo, waybill.getId(), waybill.getWaybillNo());
|
||||||
|
}
|
||||||
String waybillNo = waybill == null ? "unmatched" : safePathPart(waybill.getWaybillNo());
|
String waybillNo = waybill == null ? "unmatched" : safePathPart(waybill.getWaybillNo());
|
||||||
String objectKey = buildObjectKey(voucher.getId(), waybillNo, plateNo, imageName);
|
String objectKey = buildObjectKey(voucher.getId(), waybillNo, plateNo, imageName);
|
||||||
minioClient.putObject(PutObjectArgs.builder().bucket(minioBucketName).object(objectKey)
|
minioClient.putObject(PutObjectArgs.builder().bucket(minioBucketName).object(objectKey)
|
||||||
@@ -241,10 +273,17 @@ public class VoucherManageServiceImpl extends BaseServiceImpl<VoucherManageMappe
|
|||||||
voucherImageMapper.insert(image);
|
voucherImageMapper.insert(image);
|
||||||
imageCount++;
|
imageCount++;
|
||||||
if (waybill != null) {
|
if (waybill != null) {
|
||||||
|
matchedImageCount++;
|
||||||
relatedWaybillIds.add(waybill.getId());
|
relatedWaybillIds.add(waybill.getId());
|
||||||
}
|
}
|
||||||
|
if (imageCount % 10 == 0) {
|
||||||
|
log.info("[凭证处理] 图片处理中 voucherId={}, processedImages={}, matchedImages={}, relatedWaybills={}",
|
||||||
|
voucherId, imageCount, matchedImageCount, relatedWaybillIds.size());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
log.info("[凭证处理] 进度 90%:图片解压上传完成 voucherId={}, imageCount={}, matchedImageCount={}",
|
||||||
|
voucherId, imageCount, matchedImageCount);
|
||||||
VoucherManage update = new VoucherManage();
|
VoucherManage update = new VoucherManage();
|
||||||
update.setId(voucher.getId());
|
update.setId(voucher.getId());
|
||||||
update.setVoucherCount(imageCount);
|
update.setVoucherCount(imageCount);
|
||||||
@@ -252,6 +291,8 @@ public class VoucherManageServiceImpl extends BaseServiceImpl<VoucherManageMappe
|
|||||||
update.setUnRelatedWaybillCount(Math.max(waybills.size() - relatedWaybillIds.size(), 0));
|
update.setUnRelatedWaybillCount(Math.max(waybills.size() - relatedWaybillIds.size(), 0));
|
||||||
update.setProcessStatus("处理完成");
|
update.setProcessStatus("处理完成");
|
||||||
updateById(update);
|
updateById(update);
|
||||||
|
log.info("[凭证处理] 进度 100%:处理完成 voucherId={}, voucherBatchNo={}, imageCount={}, relatedWaybillCount={}, unrelatedWaybillCount={}",
|
||||||
|
voucherId, voucher.getVoucherBatchNo(), imageCount, relatedWaybillIds.size(), Math.max(waybills.size() - relatedWaybillIds.size(), 0));
|
||||||
} catch (Exception exception) {
|
} catch (Exception exception) {
|
||||||
VoucherManage update = new VoucherManage();
|
VoucherManage update = new VoucherManage();
|
||||||
update.setId(voucher.getId());
|
update.setId(voucher.getId());
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
-- 非公路运输调度字段补齐。
|
||||||
|
-- 通过 information_schema 判断字段是否存在,可重复执行。
|
||||||
|
SET @db_name = DATABASE();
|
||||||
|
|
||||||
|
SET @sql = IF(
|
||||||
|
EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = @db_name AND table_name = 'blade_waybill' AND column_name = 'captain_name'),
|
||||||
|
'SELECT 1',
|
||||||
|
'ALTER TABLE blade_waybill ADD COLUMN captain_name varchar(50) DEFAULT NULL COMMENT ''船长'' AFTER vehicle_no'
|
||||||
|
);
|
||||||
|
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||||
|
|
||||||
|
SET @sql = IF(
|
||||||
|
EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = @db_name AND table_name = 'blade_waybill' AND column_name = 'container_no'),
|
||||||
|
'SELECT 1',
|
||||||
|
'ALTER TABLE blade_waybill ADD COLUMN container_no varchar(100) DEFAULT NULL COMMENT ''箱号'' AFTER captain_name'
|
||||||
|
);
|
||||||
|
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||||
|
|
||||||
|
SET @sql = IF(
|
||||||
|
EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = @db_name AND table_name = 'blade_waybill' AND column_name = 'cabin_no'),
|
||||||
|
'SELECT 1',
|
||||||
|
'ALTER TABLE blade_waybill ADD COLUMN cabin_no varchar(100) DEFAULT NULL COMMENT ''舱位'' AFTER container_no'
|
||||||
|
);
|
||||||
|
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||||
Reference in New Issue
Block a user