1、修复业务模块bug
2、完善凭证管理
This commit is contained in:
+39
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
*/
|
||||
package org.springblade.transport.config;
|
||||
|
||||
import org.springframework.amqp.core.Binding;
|
||||
import org.springframework.amqp.core.BindingBuilder;
|
||||
import org.springframework.amqp.core.DirectExchange;
|
||||
import org.springframework.amqp.core.Queue;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* 凭证导入消息队列配置。
|
||||
* RabbitMQ 连接参数由 Nacos 的 spring.rabbitmq 配置提供。
|
||||
*/
|
||||
@Configuration
|
||||
public class VoucherImportRabbitConfig {
|
||||
|
||||
public static final String EXCHANGE = "tms.voucher.import.exchange";
|
||||
public static final String QUEUE = "tms.voucher.import.queue";
|
||||
public static final String ROUTING_KEY = "tms.voucher.import";
|
||||
|
||||
@Bean
|
||||
public DirectExchange voucherImportExchange() {
|
||||
return new DirectExchange(EXCHANGE, true, false);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Queue voucherImportQueue() {
|
||||
return new Queue(QUEUE, true);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Binding voucherImportBinding(Queue voucherImportQueue, DirectExchange voucherImportExchange) {
|
||||
return BindingBuilder.bind(voucherImportQueue).to(voucherImportExchange).with(ROUTING_KEY);
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
*/
|
||||
package org.springblade.transport.config;
|
||||
|
||||
import io.minio.MinioClient;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* 凭证图片 MinIO 客户端配置。
|
||||
* 连接参数由 Nacos 的 file.storage.minio 配置提供。
|
||||
*/
|
||||
@Configuration
|
||||
public class VoucherMinioConfig {
|
||||
|
||||
@Bean
|
||||
public MinioClient voucherMinioClient(
|
||||
@Value("${file.storage.minio.endpoint:${minio.endpoint:}}") String endpoint,
|
||||
@Value("${file.storage.minio.access-key-id:${minio.access-key:}}") String accessKey,
|
||||
@Value("${file.storage.minio.access-key-secret:${minio.secret-key:}}") String secretKey) {
|
||||
return MinioClient.builder().endpoint(endpoint).credentials(accessKey, secretKey).build();
|
||||
}
|
||||
}
|
||||
+57
-4
@@ -24,11 +24,13 @@ package org.springblade.transport.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
|
||||
import io.minio.GetPresignedObjectUrlArgs;
|
||||
import io.minio.MinioClient;
|
||||
import io.minio.http.Method;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springblade.core.boot.ctrl.BladeController;
|
||||
import org.springblade.core.excel.util.ExcelUtil;
|
||||
import org.springblade.core.mp.support.Condition;
|
||||
@@ -38,10 +40,13 @@ import org.springblade.core.tool.api.R;
|
||||
import org.springblade.core.tool.utils.DateUtil;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.transport.excel.ProcessConfigExcel;
|
||||
import org.springblade.transport.mapper.VoucherImageMapper;
|
||||
import org.springblade.transport.pojo.entity.ProcessConfig;
|
||||
import org.springblade.transport.pojo.entity.VoucherImage;
|
||||
import org.springblade.transport.pojo.vo.BusinessRemoveResultVO;
|
||||
import org.springblade.transport.pojo.vo.ProcessConfigVO;
|
||||
import org.springblade.transport.service.IProcessConfigService;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
@@ -50,7 +55,10 @@ import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 过程配置 控制器
|
||||
@@ -58,19 +66,64 @@ import java.util.List;
|
||||
* @author Chill
|
||||
*/
|
||||
@RestController
|
||||
@AllArgsConstructor
|
||||
@PreAuth(menu = "process_config")
|
||||
@RequestMapping("/process-config")
|
||||
@Tag(name = "过程配置", description = "过程配置")
|
||||
public class ProcessConfigController extends BladeController {
|
||||
|
||||
private final IProcessConfigService processConfigService;
|
||||
private final VoucherImageMapper voucherImageMapper;
|
||||
private final MinioClient minioClient;
|
||||
@Value("${file.storage.minio.bucket-name:${minio.bucket-name:}}")
|
||||
private String minioBucketName;
|
||||
|
||||
public ProcessConfigController(IProcessConfigService processConfigService, VoucherImageMapper voucherImageMapper,
|
||||
MinioClient minioClient) {
|
||||
this.processConfigService = processConfigService;
|
||||
this.voucherImageMapper = voucherImageMapper;
|
||||
this.minioClient = minioClient;
|
||||
}
|
||||
|
||||
@GetMapping("/detail")
|
||||
@ApiOperationSupport(order = 1)
|
||||
@Operation(summary = "详情", description = "传入id")
|
||||
public R<ProcessConfigVO> detail(@Parameter(description = "主键", required = true) @RequestParam Long id) {
|
||||
return R.data(processConfigService.detail(id));
|
||||
public R<ProcessConfigVO> detail(@Parameter(description = "主键", required = true) @RequestParam Long id,
|
||||
@Parameter(description = "运单主键") @RequestParam(required = false) Long waybillId) {
|
||||
ProcessConfigVO detail = processConfigService.detail(id);
|
||||
detail.setHasRelatedVoucher(waybillId != null && voucherImageMapper.selectCount(
|
||||
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<VoucherImage>()
|
||||
.eq(VoucherImage::getWaybillId, waybillId)
|
||||
.eq(VoucherImage::getMatched, 1)) > 0);
|
||||
return R.data(detail);
|
||||
}
|
||||
|
||||
@GetMapping("/voucher-images")
|
||||
@ApiOperationSupport(order = 2)
|
||||
@Operation(summary = "查询运单已关联凭证图片")
|
||||
public R<List<Map<String, Object>>> voucherImages(
|
||||
@Parameter(description = "运单主键", required = true) @RequestParam Long waybillId) {
|
||||
List<Map<String, Object>> images = voucherImageMapper.selectList(
|
||||
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<VoucherImage>()
|
||||
.eq(VoucherImage::getWaybillId, waybillId)
|
||||
.eq(VoucherImage::getMatched, 1)
|
||||
.orderByDesc(VoucherImage::getCreateTime))
|
||||
.stream().map(image -> {
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
result.put("id", image.getId());
|
||||
result.put("imageName", image.getImageName());
|
||||
result.put("plateNo", image.getPlateNo());
|
||||
result.put("waybillNo", image.getWaybillNo());
|
||||
result.put("objectKey", image.getObjectKey());
|
||||
try {
|
||||
result.put("url", minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
|
||||
.method(Method.GET).bucket(minioBucketName).object(image.getObjectKey())
|
||||
.expiry(1, TimeUnit.HOURS).build()));
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("生成凭证图片预览地址失败", e);
|
||||
}
|
||||
return result;
|
||||
}).toList();
|
||||
return R.data(images);
|
||||
}
|
||||
|
||||
@GetMapping("/list")
|
||||
|
||||
+7
@@ -65,6 +65,13 @@ public class VoucherManageController extends BladeController {
|
||||
return R.success("文件已更新");
|
||||
}
|
||||
|
||||
@PostMapping("/reprocess")
|
||||
@Operation(summary = "重新处理已上传凭证")
|
||||
public R reprocess(@RequestParam Long id) {
|
||||
voucherManageService.reprocessUploadedVoucher(id);
|
||||
return R.success("重新处理完成");
|
||||
}
|
||||
|
||||
@PostMapping("/remove")
|
||||
@ApiOperationSupport(order = 5)
|
||||
@Operation(summary = "删除凭证")
|
||||
|
||||
+12
-5
@@ -177,36 +177,43 @@ public class WaybillController extends BladeController {
|
||||
return R.data(waybillService.copy(id));
|
||||
}
|
||||
|
||||
@PostMapping("/cancel")
|
||||
@PostMapping("/change-route")
|
||||
@ApiOperationSupport(order = 11)
|
||||
@Operation(summary = "变更运输路线", description = "传入运单路线与变更记录")
|
||||
public R changeRoute(@RequestBody Waybill waybill) {
|
||||
return R.status(waybillService.changeRoute(waybill));
|
||||
}
|
||||
|
||||
@PostMapping("/cancel")
|
||||
@ApiOperationSupport(order = 12)
|
||||
@Operation(summary = "取消", description = "传入id")
|
||||
public R cancel(@Parameter(description = "主键", required = true) @RequestParam Long id) {
|
||||
return R.status(waybillService.cancel(id));
|
||||
}
|
||||
|
||||
@PostMapping("/reassign")
|
||||
@ApiOperationSupport(order = 12)
|
||||
@ApiOperationSupport(order = 13)
|
||||
@Operation(summary = "重新派单", description = "传入id")
|
||||
public R reassign(@Parameter(description = "主键", required = true) @RequestParam Long id) {
|
||||
return R.status(waybillService.reassign(id));
|
||||
}
|
||||
|
||||
@PostMapping("/complete")
|
||||
@ApiOperationSupport(order = 13)
|
||||
@ApiOperationSupport(order = 14)
|
||||
@Operation(summary = "完成", description = "传入id")
|
||||
public R complete(@Parameter(description = "主键", required = true) @RequestParam Long id) {
|
||||
return R.status(waybillService.complete(id));
|
||||
}
|
||||
|
||||
@PostMapping("/batch-complete")
|
||||
@ApiOperationSupport(order = 14)
|
||||
@ApiOperationSupport(order = 15)
|
||||
@Operation(summary = "批量完成", description = "传入ids")
|
||||
public R<BusinessRemoveResultVO> batchComplete(@Parameter(description = "主键集合", required = true) @RequestParam String ids) {
|
||||
return R.data(waybillService.batchComplete(ids));
|
||||
}
|
||||
|
||||
@PostMapping("/road-loading")
|
||||
@ApiOperationSupport(order = 15)
|
||||
@ApiOperationSupport(order = 16)
|
||||
@Operation(summary = "公路配载", description = "传入ids")
|
||||
public R<LoadingManageVO> roadLoading(@Parameter(description = "主键集合", required = true) @RequestParam String ids) {
|
||||
return R.data(waybillService.roadLoading(ids));
|
||||
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
*/
|
||||
package org.springblade.transport.event;
|
||||
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
/**
|
||||
* 凭证压缩包上传完成事件。
|
||||
*/
|
||||
public class VoucherUploadCompletedEvent extends ApplicationEvent {
|
||||
|
||||
private final Long voucherId;
|
||||
|
||||
public VoucherUploadCompletedEvent(Long voucherId) {
|
||||
super(voucherId);
|
||||
this.voucherId = voucherId;
|
||||
}
|
||||
|
||||
public Long getVoucherId() {
|
||||
return voucherId;
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
*/
|
||||
package org.springblade.transport.listener;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springblade.transport.config.VoucherImportRabbitConfig;
|
||||
import org.springblade.transport.service.IVoucherManageService;
|
||||
import org.springframework.amqp.rabbit.annotation.RabbitListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 凭证压缩包后台处理消费者。
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class VoucherImportMessageListener {
|
||||
|
||||
private final IVoucherManageService voucherManageService;
|
||||
|
||||
@RabbitListener(queues = VoucherImportRabbitConfig.QUEUE)
|
||||
public void processVoucher(Long voucherId) {
|
||||
voucherManageService.processUploadedVoucher(voucherId);
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
*/
|
||||
package org.springblade.transport.listener;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springblade.transport.config.VoucherImportRabbitConfig;
|
||||
import org.springblade.transport.event.VoucherUploadCompletedEvent;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.event.TransactionPhase;
|
||||
import org.springframework.transaction.event.TransactionalEventListener;
|
||||
|
||||
/**
|
||||
* 凭证上传完成后投递后台处理消息。
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class VoucherUploadCompletedListener {
|
||||
|
||||
private final RabbitTemplate rabbitTemplate;
|
||||
|
||||
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
|
||||
public void publish(VoucherUploadCompletedEvent event) {
|
||||
rabbitTemplate.convertAndSend(VoucherImportRabbitConfig.EXCHANGE,
|
||||
VoucherImportRabbitConfig.ROUTING_KEY, event.getVoucherId());
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
*/
|
||||
package org.springblade.transport.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Delete;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.springblade.transport.pojo.entity.VoucherImage;
|
||||
|
||||
/**
|
||||
* 凭证图片明细 Mapper。
|
||||
*/
|
||||
public interface VoucherImageMapper extends BaseMapper<VoucherImage> {
|
||||
|
||||
@Delete("DELETE FROM blade_voucher_image WHERE voucher_id = #{voucherId}")
|
||||
void deleteByVoucherId(@Param("voucherId") Long voucherId);
|
||||
}
|
||||
+4
-3
@@ -2,9 +2,9 @@
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="org.springblade.transport.mapper.VoucherWaybillBatchMapper">
|
||||
<select id="selectVoucherWaybillBatchPage" resultType="java.util.LinkedHashMap">
|
||||
SELECT MAX(w.create_time) AS createTime, MAX(w.create_user) AS createUser,
|
||||
SELECT MIN(w.id) AS id, MAX(w.create_time) AS createTime, MAX(w.create_user) AS createUser,
|
||||
MAX(u.real_name) AS createUserName, COUNT(*) AS waybillCount,
|
||||
COALESCE(NULLIF(w.batch_no, ''), '未分批运单') AS batchNo, GROUP_CONCAT(w.id ORDER BY w.create_time DESC) AS waybillIds
|
||||
COALESCE(NULLIF(w.batch_no, ''), '未分批运单') AS batchNo
|
||||
FROM blade_waybill w
|
||||
LEFT JOIN blade_user u ON u.id = w.create_user
|
||||
WHERE w.is_deleted = 0 AND w.tenant_id = #{tenantId}
|
||||
@@ -17,9 +17,10 @@
|
||||
ORDER BY createTime DESC
|
||||
</select>
|
||||
<select id="selectWaybillBatchesByIds" resultType="java.util.LinkedHashMap">
|
||||
SELECT w.id, COALESCE(NULLIF(w.batch_no, ''), '未分批运单') AS batchNo, 1 AS waybillCount
|
||||
SELECT MIN(w.id) AS id, COALESCE(NULLIF(w.batch_no, ''), '未分批运单') AS batchNo, COUNT(*) AS waybillCount
|
||||
FROM blade_waybill w
|
||||
WHERE w.is_deleted = 0 AND w.tenant_id = #{tenantId} AND w.id IN
|
||||
<foreach collection="ids" item="id" open="(" separator="," close=")">#{id}</foreach>
|
||||
GROUP BY COALESCE(NULLIF(w.batch_no, ''), '未分批运单')
|
||||
</select>
|
||||
</mapper>
|
||||
|
||||
+2
@@ -17,6 +17,8 @@ public interface IVoucherManageService extends BaseService<VoucherManage> {
|
||||
void submit(VoucherManageSubmitRequest request);
|
||||
VoucherManage createUploadDraft(VoucherUploadDraftRequest request);
|
||||
void completeUploadFile(VoucherFileCompleteRequest request);
|
||||
void processUploadedVoucher(Long voucherId);
|
||||
void reprocessUploadedVoucher(Long voucherId);
|
||||
void removeVoucher(Long id);
|
||||
IPage<Map<String, Object>> selectableWaybillBatches(IPage<?> page, String batchNo, String createUser, Integer waybillCount, String createTimeStart, String createTimeEnd);
|
||||
}
|
||||
|
||||
+1
@@ -46,6 +46,7 @@ public interface IWaybillService extends BaseService<Waybill> {
|
||||
List<WaybillExcel> exportWaybill(WaybillVO waybill, String ids);
|
||||
List<WaybillExcel> importWaybill(List<WaybillExcel> data);
|
||||
WaybillVO copy(Long id);
|
||||
boolean changeRoute(Waybill waybill);
|
||||
boolean cancel(Long id);
|
||||
boolean reassign(Long id);
|
||||
boolean complete(Long id);
|
||||
|
||||
+228
-3
@@ -6,38 +6,76 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import org.springblade.core.log.exception.ServiceException;
|
||||
import org.springblade.core.mp.base.BaseServiceImpl;
|
||||
import org.springblade.core.secure.utils.AuthUtil;
|
||||
import org.springblade.core.tool.jackson.JsonUtil;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.transport.mapper.VoucherManageMapper;
|
||||
import org.springblade.transport.mapper.VoucherImageMapper;
|
||||
import org.springblade.transport.mapper.VoucherWaybillBatchMapper;
|
||||
import org.springblade.transport.event.VoucherUploadCompletedEvent;
|
||||
import org.springblade.transport.pojo.dto.VoucherManageSubmitRequest;
|
||||
import org.springblade.transport.pojo.dto.VoucherUploadDraftRequest;
|
||||
import org.springblade.transport.pojo.dto.VoucherFileCompleteRequest;
|
||||
import org.springblade.transport.pojo.entity.VoucherManage;
|
||||
import org.springblade.transport.pojo.entity.VoucherWaybillBatch;
|
||||
import org.springblade.transport.pojo.entity.VoucherImage;
|
||||
import org.springblade.transport.pojo.entity.Waybill;
|
||||
import org.springblade.transport.pojo.entity.ProjectApply;
|
||||
import org.springblade.transport.pojo.vo.VoucherManageVO;
|
||||
import org.springblade.transport.service.IVoucherManageService;
|
||||
import org.springblade.transport.service.IProjectApplyService;
|
||||
import org.springblade.transport.service.IWaybillService;
|
||||
import org.springblade.transport.wrapper.VoucherManageWrapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import io.minio.MinioClient;
|
||||
import io.minio.PutObjectArgs;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.net.URLConnection;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
public class VoucherManageServiceImpl extends BaseServiceImpl<VoucherManageMapper, VoucherManage> implements IVoucherManageService {
|
||||
|
||||
private final VoucherWaybillBatchMapper voucherWaybillBatchMapper;
|
||||
private final IProjectApplyService projectApplyService;
|
||||
private final IWaybillService waybillService;
|
||||
private final VoucherImageMapper voucherImageMapper;
|
||||
private final ApplicationEventPublisher eventPublisher;
|
||||
private final MinioClient minioClient;
|
||||
private final String minioBucketName;
|
||||
private final String minioRootDirectory;
|
||||
|
||||
public VoucherManageServiceImpl(VoucherWaybillBatchMapper voucherWaybillBatchMapper, IProjectApplyService projectApplyService) {
|
||||
public VoucherManageServiceImpl(VoucherWaybillBatchMapper voucherWaybillBatchMapper, IProjectApplyService projectApplyService,
|
||||
IWaybillService waybillService,
|
||||
VoucherImageMapper voucherImageMapper, ApplicationEventPublisher eventPublisher, MinioClient minioClient,
|
||||
@Value("${file.storage.minio.bucket-name:${minio.bucket-name:}}") String minioBucketName,
|
||||
@Value("${file.storage.minio.root-directory:${minio.root-directory:}}") String minioRootDirectory) {
|
||||
this.voucherWaybillBatchMapper = voucherWaybillBatchMapper;
|
||||
this.projectApplyService = projectApplyService;
|
||||
this.waybillService = waybillService;
|
||||
this.voucherImageMapper = voucherImageMapper;
|
||||
this.eventPublisher = eventPublisher;
|
||||
this.minioClient = minioClient;
|
||||
this.minioBucketName = minioBucketName;
|
||||
this.minioRootDirectory = minioRootDirectory;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -114,10 +152,11 @@ public class VoucherManageServiceImpl extends BaseServiceImpl<VoucherManageMappe
|
||||
}
|
||||
relations.forEach(voucherWaybillBatchMapper::insert);
|
||||
voucher.setWaybillBatchNo(relations.stream().map(VoucherWaybillBatch::getWaybillBatchNo).distinct().collect(Collectors.joining(",")));
|
||||
int total = relations.stream().mapToInt(VoucherWaybillBatch::getWaybillCount).sum();
|
||||
voucher.setRelatedWaybillCount(total);
|
||||
voucher.setRelatedWaybillCount(0);
|
||||
voucher.setUnRelatedWaybillCount(0);
|
||||
voucher.setProcessStatus("处理中");
|
||||
updateById(voucher);
|
||||
eventPublisher.publishEvent(new VoucherUploadCompletedEvent(voucher.getId()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -133,9 +172,96 @@ public class VoucherManageServiceImpl extends BaseServiceImpl<VoucherManageMappe
|
||||
voucher.setFileTaskId(request.getFileTaskId());
|
||||
voucher.setFileName(request.getFileName());
|
||||
voucher.setFileUrl(request.getFileUrl());
|
||||
voucher.setProcessStatus("处理中");
|
||||
updateById(voucher);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void processUploadedVoucher(Long voucherId) {
|
||||
processUploadedVoucher(voucherId, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void reprocessUploadedVoucher(Long voucherId) {
|
||||
processUploadedVoucher(voucherId, true);
|
||||
}
|
||||
|
||||
private void processUploadedVoucher(Long voucherId, boolean force) {
|
||||
VoucherManage voucher = getById(voucherId);
|
||||
if (voucher == null || Objects.equals(voucher.getIsDeleted(), 1)) {
|
||||
log.warn("凭证解压任务不存在,忽略消息 voucherId:{}", voucherId);
|
||||
return;
|
||||
}
|
||||
if (!force && "处理完成".equals(voucher.getProcessStatus())) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
validateMinioConfig();
|
||||
List<Waybill> waybills = listRelatedWaybills(voucher);
|
||||
Map<String, Waybill> waybillByPlate = new HashMap<>();
|
||||
for (Waybill waybill : waybills) {
|
||||
waybillPlateNumbers(waybill).forEach(plateNo -> waybillByPlate.putIfAbsent(plateNo, waybill));
|
||||
}
|
||||
voucherImageMapper.deleteByVoucherId(voucher.getId());
|
||||
int imageCount = 0;
|
||||
Set<Long> relatedWaybillIds = new HashSet<>();
|
||||
try (InputStream source = openSourceFile(voucher.getFileUrl());
|
||||
ZipInputStream zipInputStream = new ZipInputStream(new BufferedInputStream(source), StandardCharsets.UTF_8)) {
|
||||
ZipEntry entry;
|
||||
while ((entry = zipInputStream.getNextEntry()) != null) {
|
||||
if (entry.isDirectory()) {
|
||||
continue;
|
||||
}
|
||||
String[] pathParts = entry.getName().replace('\\', '/').split("/");
|
||||
if (pathParts.length < 2 || !isImageFile(pathParts[pathParts.length - 1])) {
|
||||
continue;
|
||||
}
|
||||
String plateNo = normalizePlateNo(pathParts[0]);
|
||||
String imageName = safeFileName(pathParts[pathParts.length - 1]);
|
||||
if (Func.isEmpty(plateNo) || Func.isEmpty(imageName)) {
|
||||
continue;
|
||||
}
|
||||
Waybill waybill = waybillByPlate.get(plateNo);
|
||||
String waybillNo = waybill == null ? "unmatched" : safePathPart(waybill.getWaybillNo());
|
||||
String objectKey = buildObjectKey(voucher.getId(), waybillNo, plateNo, imageName);
|
||||
minioClient.putObject(PutObjectArgs.builder().bucket(minioBucketName).object(objectKey)
|
||||
.stream(zipInputStream, entry.getSize(), 10 * 1024 * 1024).build());
|
||||
VoucherImage image = new VoucherImage();
|
||||
image.setVoucherId(voucher.getId());
|
||||
image.setVoucherBatchNo(voucher.getVoucherBatchNo());
|
||||
image.setWaybillId(waybill == null ? null : waybill.getId());
|
||||
image.setWaybillNo(waybill == null ? null : waybill.getWaybillNo());
|
||||
image.setPlateNo(plateNo);
|
||||
image.setImageName(imageName);
|
||||
image.setObjectKey(objectKey);
|
||||
image.setMatched(waybill == null ? 0 : 1);
|
||||
image.setTenantId(voucher.getTenantId());
|
||||
voucherImageMapper.insert(image);
|
||||
imageCount++;
|
||||
if (waybill != null) {
|
||||
relatedWaybillIds.add(waybill.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
VoucherManage update = new VoucherManage();
|
||||
update.setId(voucher.getId());
|
||||
update.setVoucherCount(imageCount);
|
||||
update.setRelatedWaybillCount(relatedWaybillIds.size());
|
||||
update.setUnRelatedWaybillCount(Math.max(waybills.size() - relatedWaybillIds.size(), 0));
|
||||
update.setProcessStatus("处理完成");
|
||||
updateById(update);
|
||||
} catch (Exception exception) {
|
||||
VoucherManage update = new VoucherManage();
|
||||
update.setId(voucher.getId());
|
||||
update.setProcessStatus("处理失败");
|
||||
updateById(update);
|
||||
log.error("凭证压缩包处理失败 voucherId:{}", voucherId, exception);
|
||||
throw new ServiceException("凭证压缩包处理失败");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void removeVoucher(Long id) {
|
||||
@@ -155,6 +281,105 @@ public class VoucherManageServiceImpl extends BaseServiceImpl<VoucherManageMappe
|
||||
return voucherWaybillBatchMapper.selectWaybillBatchesByIds(AuthUtil.getTenantId(), ids);
|
||||
}
|
||||
|
||||
private List<Waybill> listRelatedWaybills(VoucherManage voucher) {
|
||||
LambdaQueryWrapper<Waybill> query = Wrappers.<Waybill>lambdaQuery()
|
||||
.eq(Waybill::getTenantId, voucher.getTenantId())
|
||||
.eq(Waybill::getIsDeleted, 0);
|
||||
List<String> batchNos = voucherWaybillBatchMapper.selectList(Wrappers.<VoucherWaybillBatch>lambdaQuery()
|
||||
.eq(VoucherWaybillBatch::getVoucherId, voucher.getId()))
|
||||
.stream().map(VoucherWaybillBatch::getWaybillBatchNo).filter(Func::isNotEmpty).distinct().toList();
|
||||
if (Func.isEmpty(batchNos)) return List.of();
|
||||
List<String> normalBatchNos = batchNos.stream().filter(batchNo -> !"未分批运单".equals(batchNo)).toList();
|
||||
boolean containsUnbatchedWaybills = batchNos.contains("未分批运单");
|
||||
query.and(wrapper -> {
|
||||
if (Func.isNotEmpty(normalBatchNos)) {
|
||||
wrapper.in(Waybill::getBatchNo, normalBatchNos);
|
||||
}
|
||||
if (containsUnbatchedWaybills) {
|
||||
if (Func.isNotEmpty(normalBatchNos)) {
|
||||
wrapper.or();
|
||||
}
|
||||
wrapper.isNull(Waybill::getBatchNo).or().eq(Waybill::getBatchNo, "");
|
||||
}
|
||||
});
|
||||
return waybillService.list(query);
|
||||
}
|
||||
|
||||
private InputStream openSourceFile(String fileUrl) throws Exception {
|
||||
URLConnection connection = new java.net.URL(fileUrl).openConnection();
|
||||
connection.setConnectTimeout(30_000);
|
||||
connection.setReadTimeout(300_000);
|
||||
return connection.getInputStream();
|
||||
}
|
||||
|
||||
private void validateMinioConfig() {
|
||||
if (Func.isEmpty(minioBucketName)) {
|
||||
throw new ServiceException("Nacos 未配置 file.storage.minio.bucket-name");
|
||||
}
|
||||
}
|
||||
|
||||
private String buildObjectKey(Long voucherId, String waybillNo, String plateNo, String imageName) {
|
||||
String objectKey = voucherId + "/" + waybillNo + "/" + safePathPart(plateNo) + "/" + imageName;
|
||||
if (Func.isEmpty(minioRootDirectory)) {
|
||||
return objectKey;
|
||||
}
|
||||
return minioRootDirectory.endsWith("/") ? minioRootDirectory + objectKey : minioRootDirectory + "/" + objectKey;
|
||||
}
|
||||
|
||||
private String normalizePlateNo(String plateNo) {
|
||||
return plateNo == null ? null : plateNo.replaceAll("[\\s-]", "").toUpperCase();
|
||||
}
|
||||
|
||||
private Set<String> waybillPlateNumbers(Waybill waybill) {
|
||||
Set<String> plateNumbers = new HashSet<>();
|
||||
addPlateNumber(plateNumbers, waybill.getVehicleNo());
|
||||
addPlateNumber(plateNumbers, waybill.getTrailerVehicleNo());
|
||||
addPlateNumbersFromJson(plateNumbers, waybill.getCarrierJson(), waybill.getId());
|
||||
addPlateNumbersFromJson(plateNumbers, waybill.getTaskInfoJson(), waybill.getId());
|
||||
return plateNumbers;
|
||||
}
|
||||
|
||||
private void addPlateNumbersFromJson(Set<String> plateNumbers, String json, Long waybillId) {
|
||||
if (Func.isEmpty(json)) return;
|
||||
try {
|
||||
Object parsed = JsonUtil.parse(json, Object.class);
|
||||
if (parsed instanceof Map<?, ?> map) {
|
||||
addPlateNumber(plateNumbers, String.valueOf(map.get("vehicleNo")));
|
||||
addPlateNumber(plateNumbers, String.valueOf(map.get("trailerVehicleNo")));
|
||||
} else if (parsed instanceof List<?> rows) {
|
||||
for (Object row : rows) {
|
||||
if (row instanceof Map<?, ?> map) {
|
||||
addPlateNumber(plateNumbers, String.valueOf(map.get("vehicleNo")));
|
||||
addPlateNumber(plateNumbers, String.valueOf(map.get("trailerVehicleNo")));
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception exception) {
|
||||
log.warn("运单车牌信息解析失败 waybillId:{}", waybillId);
|
||||
}
|
||||
}
|
||||
|
||||
private void addPlateNumber(Set<String> plateNumbers, String plateNo) {
|
||||
String normalizedPlateNo = normalizePlateNo(plateNo);
|
||||
if (Func.isNotEmpty(normalizedPlateNo) && !"NULL".equals(normalizedPlateNo)) {
|
||||
plateNumbers.add(normalizedPlateNo);
|
||||
}
|
||||
}
|
||||
|
||||
private String safePathPart(String value) {
|
||||
return value == null ? "" : value.replaceAll("[^0-9A-Za-z\\u4e00-\\u9fa5_-]", "_");
|
||||
}
|
||||
|
||||
private String safeFileName(String value) {
|
||||
return value == null ? "" : safePathPart(value.replaceFirst("(?s)^.*[/\\\\]", ""));
|
||||
}
|
||||
|
||||
private boolean isImageFile(String fileName) {
|
||||
String lowerName = fileName.toLowerCase();
|
||||
return lowerName.endsWith(".jpg") || lowerName.endsWith(".jpeg") || lowerName.endsWith(".png")
|
||||
|| lowerName.endsWith(".bmp") || lowerName.endsWith(".webp");
|
||||
}
|
||||
|
||||
private LambdaQueryWrapper<VoucherManage> buildQuery(VoucherManageVO query) {
|
||||
LambdaQueryWrapper<VoucherManage> wrapper = Wrappers.<VoucherManage>lambdaQuery().eq(VoucherManage::getIsDeleted, 0)
|
||||
.like(Func.isNotEmpty(query.getVoucherBatchNo()), VoucherManage::getVoucherBatchNo, query.getVoucherBatchNo())
|
||||
|
||||
+21
@@ -224,6 +224,7 @@ public class WaybillServiceImpl extends BaseServiceImpl<WaybillMapper, Waybill>
|
||||
target.setCarrierJson(source.getCarrierJson());
|
||||
target.setTaskInfoJson(source.getTaskInfoJson());
|
||||
target.setProcessJson(source.getProcessJson());
|
||||
target.setRouteJson(source.getRouteJson());
|
||||
target.setFreightJson(source.getFreightJson());
|
||||
target.setAttachmentsJson(source.getAttachmentsJson());
|
||||
target.setRemark(source.getRemark());
|
||||
@@ -235,6 +236,24 @@ public class WaybillServiceImpl extends BaseServiceImpl<WaybillMapper, Waybill>
|
||||
return detail(target.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean changeRoute(Waybill waybill) {
|
||||
Waybill oldRecord = loadEditable(waybill.getId(), true);
|
||||
if ("completed".equals(oldRecord.getBusinessStatus()) || "cancelled".equals(oldRecord.getBusinessStatus())) {
|
||||
throw new ServiceException("当前运单状态不允许变更运输路线");
|
||||
}
|
||||
oldRecord.setRouteJson(TransportBusinessSupport.trimToNull(waybill.getRouteJson()));
|
||||
oldRecord.setDepartureAddress(TransportBusinessSupport.trimToNull(waybill.getDepartureAddress()));
|
||||
oldRecord.setArrivalAddress(TransportBusinessSupport.trimToNull(waybill.getArrivalAddress()));
|
||||
oldRecord.setTaskInfoJson(TransportBusinessSupport.trimToNull(waybill.getTaskInfoJson()));
|
||||
TransportBusinessSupport.validateLength(oldRecord.getRouteJson(), 8000, "路线信息不能超过8000字");
|
||||
TransportBusinessSupport.validateLength(oldRecord.getDepartureAddress(), 255, "发货地址不能超过255字");
|
||||
TransportBusinessSupport.validateLength(oldRecord.getArrivalAddress(), 255, "收货地址不能超过255字");
|
||||
TransportBusinessSupport.validateLength(oldRecord.getTaskInfoJson(), 8000, "任务信息不能超过8000字");
|
||||
return updateById(oldRecord);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean cancel(Long id) {
|
||||
@@ -470,6 +489,7 @@ public class WaybillServiceImpl extends BaseServiceImpl<WaybillMapper, Waybill>
|
||||
waybill.setCarrierJson(TransportBusinessSupport.trimToNull(waybill.getCarrierJson()));
|
||||
waybill.setTaskInfoJson(TransportBusinessSupport.trimToNull(waybill.getTaskInfoJson()));
|
||||
waybill.setProcessJson(TransportBusinessSupport.trimToNull(waybill.getProcessJson()));
|
||||
waybill.setRouteJson(TransportBusinessSupport.trimToNull(waybill.getRouteJson()));
|
||||
waybill.setFreightJson(TransportBusinessSupport.trimToNull(waybill.getFreightJson()));
|
||||
waybill.setAttachmentsJson(TransportBusinessSupport.trimToNull(waybill.getAttachmentsJson()));
|
||||
waybill.setDeptName(TransportBusinessSupport.trimToNull(waybill.getDeptName()));
|
||||
@@ -562,6 +582,7 @@ public class WaybillServiceImpl extends BaseServiceImpl<WaybillMapper, Waybill>
|
||||
TransportBusinessSupport.validateLength(waybill.getCarrierJson(), 8000, "承运信息不能超过8000字");
|
||||
TransportBusinessSupport.validateLength(waybill.getTaskInfoJson(), 8000, "任务信息不能超过8000字");
|
||||
TransportBusinessSupport.validateLength(waybill.getProcessJson(), 8000, "过程节点不能超过8000字");
|
||||
TransportBusinessSupport.validateLength(waybill.getRouteJson(), 8000, "路线信息不能超过8000字");
|
||||
TransportBusinessSupport.validateLength(waybill.getFreightJson(), 8000, "费用信息不能超过8000字");
|
||||
TransportBusinessSupport.validateLength(waybill.getAttachmentsJson(), 8000, "附件不能超过8000字");
|
||||
TransportBusinessSupport.validateLength(waybill.getDeptName(), 255, "所属组织不能超过255字");
|
||||
|
||||
Reference in New Issue
Block a user