diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/FileTaskCreateDTO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/FileTaskCreateDTO.java index 4b3acf0..2e179b3 100644 --- a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/FileTaskCreateDTO.java +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/FileTaskCreateDTO.java @@ -59,4 +59,10 @@ public class FileTaskCreateDTO implements Serializable { */ @Schema(description = "是否强制创建新任务,默认 false,如果存在相同md5的任务,会创建失败,true则新创建一个任务") private boolean force; + + @Schema(description = "业务类型") + private String businessType; + + @Schema(description = "业务标识") + private String businessId; } diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/entity/FileTask.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/entity/FileTask.java index 36d0ed2..e247eae 100644 --- a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/entity/FileTask.java +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/entity/FileTask.java @@ -91,6 +91,12 @@ public class FileTask implements Serializable { @Schema(description = "状态 uploading:正在上传 completed:上传完成 failed:上传失败 paused:已暂停") private String status; + @Schema(description = "业务类型") + private String businessType; + + @Schema(description = "业务标识") + private String businessId; + /** * 创建人-用户名称 */ diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/FileTaskVO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/FileTaskVO.java index 0407ff8..f84ae52 100644 --- a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/FileTaskVO.java +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/vo/FileTaskVO.java @@ -87,6 +87,12 @@ public class FileTaskVO implements Serializable { @Schema(description = "状态 uploading:正在上传 completed:上传完成 failed:上传失败 paused:已暂停") private String status; + @Schema(description = "业务类型") + private String businessType; + + @Schema(description = "业务标识") + private String businessId; + /** * 创建人-用户名称 */ diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/VoucherManageSubmitRequest.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/VoucherManageSubmitRequest.java new file mode 100644 index 0000000..84e8146 --- /dev/null +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/VoucherManageSubmitRequest.java @@ -0,0 +1,17 @@ +package org.springblade.transport.pojo.dto; + +import lombok.Data; + +import java.util.List; + +@Data +public class VoucherManageSubmitRequest { + private Long id; + private String voucherBatchNo; + private Long projectId; + private String projectName; + private String fileName; + private String fileUrl; + private Long fileTaskId; + private List waybillImportBatchIds; +} diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/VoucherUploadDraftRequest.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/VoucherUploadDraftRequest.java new file mode 100644 index 0000000..452fe25 --- /dev/null +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/VoucherUploadDraftRequest.java @@ -0,0 +1,10 @@ +package org.springblade.transport.pojo.dto; + +import lombok.Data; + +/** 创建执行凭证上传草稿。 */ +@Data +public class VoucherUploadDraftRequest { + private Long projectId; + private String fileName; +} diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/CustomerInvoiceInfo.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/CustomerInvoiceInfo.java index ee9a300..896a10c 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/CustomerInvoiceInfo.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/CustomerInvoiceInfo.java @@ -53,6 +53,9 @@ public class CustomerInvoiceInfo extends TenantEntity { @Schema(description = "受票方名称") private String invoiceTitle; + @Schema(description = "发票类型") + private String invoiceType; + @Schema(description = "纳税人识别号") private String taxNo; diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/VoucherManage.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/VoucherManage.java new file mode 100644 index 0000000..43305b9 --- /dev/null +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/VoucherManage.java @@ -0,0 +1,34 @@ +package org.springblade.transport.pojo.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.springblade.core.tenant.mp.TenantEntity; + +import java.io.Serial; + +/** 凭证批次。 */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("blade_voucher_manage") +@Schema(description = "凭证管理") +public class VoucherManage extends TenantEntity { + + @Serial + private static final long serialVersionUID = 1L; + private Long projectId; + private String projectName; + private String voucherBatchNo; + private String waybillBatchNo; + private String fileName; + private String fileUrl; + private Long fileTaskId; + private String uploadSource; + private String carrierName; + private String processStatus; + private Integer voucherCount; + private Integer relatedWaybillCount; + private Integer unRelatedWaybillCount; + private String auditStatus; +} diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/VoucherWaybillBatch.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/VoucherWaybillBatch.java new file mode 100644 index 0000000..3a3cffa --- /dev/null +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/VoucherWaybillBatch.java @@ -0,0 +1,21 @@ +package org.springblade.transport.pojo.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.springblade.core.tenant.mp.TenantEntity; + +import java.io.Serial; + +/** 凭证与运单批次关联。 */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("blade_voucher_waybill_batch") +public class VoucherWaybillBatch extends TenantEntity { + @Serial + private static final long serialVersionUID = 1L; + private Long voucherId; + private Long waybillImportBatchId; + private String waybillBatchNo; + private Integer waybillCount; +} diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/VoucherManageVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/VoucherManageVO.java new file mode 100644 index 0000000..bea50c6 --- /dev/null +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/VoucherManageVO.java @@ -0,0 +1,19 @@ +package org.springblade.transport.pojo.vo; + +import com.baomidou.mybatisplus.annotation.TableField; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.springblade.transport.pojo.entity.VoucherManage; + +import java.io.Serial; +import java.time.LocalDateTime; + +@Data +@EqualsAndHashCode(callSuper = true) +public class VoucherManageVO extends VoucherManage { + @Serial private static final long serialVersionUID = 1L; + @TableField(exist = false) private String createUserName; + @TableField(exist = false) private String updateUserName; + @TableField(exist = false) private LocalDateTime createTimeStart; + @TableField(exist = false) private LocalDateTime createTimeEnd; +} diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/controller/FileTaskController.java b/blade-service/blade-file/src/main/java/org/springblade/file/controller/FileTaskController.java index 071625d..09cf72d 100644 --- a/blade-service/blade-file/src/main/java/org/springblade/file/controller/FileTaskController.java +++ b/blade-service/blade-file/src/main/java/org/springblade/file/controller/FileTaskController.java @@ -13,11 +13,14 @@ import org.springblade.core.boot.ctrl.BladeController; import org.springblade.core.log.annotation.ApiLog; import org.springblade.core.tenant.annotation.NonDS; import org.springblade.core.tool.api.R; +import org.springblade.core.mp.support.Query; +import com.baomidou.mybatisplus.core.metadata.IPage; import org.springblade.file.pojo.dto.FileTaskCreateDTO; import org.springblade.file.pojo.dto.FileTaskUpdateDTO; import org.springblade.file.pojo.dto.IdsDTO; import org.springblade.file.pojo.vo.FileTaskUpdateVO; import org.springblade.file.service.IFileTaskService; +import org.springblade.file.service.IFileService; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -35,6 +38,7 @@ import org.springframework.web.bind.annotation.*; public class FileTaskController extends BladeController { private final IFileTaskService fileTaskService; + private final IFileService fileService; @GetMapping("/queryFileTask") @Operation(summary = "根据文件的MD5值查询文件任务", description = "文件的MD5值检查分段,参数名:md5") @@ -43,6 +47,24 @@ public class FileTaskController extends BladeController { return R.data(fileTask); } + @GetMapping("/file-url") + @Operation(summary = "获取已完成上传任务文件地址") + public R fileUrl(@RequestParam Long id) { + org.springblade.file.pojo.entity.FileTask task = fileTaskService.getById(id); + if (task == null || !org.springblade.file.pojo.enums.FileTaskStatus.COMPLETED.getCode().equals(task.getStatus())) { + return R.fail("文件尚未上传完成"); + } + return R.data(fileService.getFileUrl(task.getObjectKey(), null)); + } + + @GetMapping("/list") + @Operation(summary = "上传任务分页") + public R> list(Query query, @RequestParam(required = false) String businessType, + @RequestParam(required = false) String businessId, @RequestParam(required = false) String attachmentName, + @RequestParam(required = false) String status) { + return R.data(fileTaskService.selectPage(query, businessType, businessId, attachmentName, status)); + } + @ApiLog("上传任务-创建文件任务") @PostMapping("/createFileTask") @Operation(summary = "创建文件任务", description = "创建文件任务") diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/service/IFileTaskService.java b/blade-service/blade-file/src/main/java/org/springblade/file/service/IFileTaskService.java index 3a4a992..a6788ef 100644 --- a/blade-service/blade-file/src/main/java/org/springblade/file/service/IFileTaskService.java +++ b/blade-service/blade-file/src/main/java/org/springblade/file/service/IFileTaskService.java @@ -26,6 +26,8 @@ package org.springblade.file.service; import com.baomidou.mybatisplus.extension.service.IService; +import com.baomidou.mybatisplus.core.metadata.IPage; +import org.springblade.core.mp.support.Query; import org.springblade.file.pojo.dto.FileTaskCreateDTO; import org.springblade.file.pojo.dto.FileTaskUpdateDTO; import org.springblade.file.pojo.entity.FileTask; @@ -91,4 +93,6 @@ public interface IFileTaskService extends IService { * @return */ boolean updatePaused(List param); + + IPage selectPage(Query query, String businessType, String businessId, String attachmentName, String status); } diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/FileTaskServiceImpl.java b/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/FileTaskServiceImpl.java index af8d4f2..6eba087 100644 --- a/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/FileTaskServiceImpl.java +++ b/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/FileTaskServiceImpl.java @@ -28,12 +28,15 @@ package org.springblade.file.service.impl; import com.baomidou.mybatisplus.core.toolkit.IdWorker; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.baomidou.mybatisplus.core.metadata.IPage; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springblade.core.log.exception.ServiceException; import org.springblade.core.tool.utils.CollectionUtil; import org.springblade.core.tool.utils.Func; import org.springblade.core.tool.utils.StringUtil; +import org.springblade.core.mp.support.Condition; +import org.springblade.core.secure.utils.AuthUtil; import org.springblade.file.config.ObsProperties; import org.springblade.file.convert.FileTaskConvert; import org.springblade.file.listener.FileUploadedEvent; @@ -125,6 +128,8 @@ public class FileTaskServiceImpl extends ServiceImpl i addParam.setUploadId(uploadId); // 正在上传 addParam.setStatus(FileTaskStatus.UPLOADING.getCode()); + // 兼容历史表未配置 is_deleted 默认值的场景。 + addParam.setIsDeleted(0); this.save(addParam); return addParam.getId(); @@ -317,6 +322,17 @@ public class FileTaskServiceImpl extends ServiceImpl i ); } + @Override + public IPage selectPage(org.springblade.core.mp.support.Query query, String businessType, String businessId, String attachmentName, String status) { + IPage page = page(Condition.getPage(query), Wrappers.lambdaQuery() + .eq(StringUtil.isNotBlank(businessType), FileTask::getBusinessType, businessType) + .eq(StringUtil.isNotBlank(businessId), FileTask::getBusinessId, businessId) + .like(StringUtil.isNotBlank(attachmentName), FileTask::getAttachmentName, attachmentName) + .eq(StringUtil.isNotBlank(status), FileTask::getStatus, status) + .eq(FileTask::getCreateUser, AuthUtil.getUserId()).orderByDesc(FileTask::getCreateTime)); + return page.convert(this::getFileTaskUpdateVO); + } + /** * 查询文件任务 * @param id diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/MinioFileServiceImpl.java b/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/MinioFileServiceImpl.java index e075588..5214c62 100644 --- a/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/MinioFileServiceImpl.java +++ b/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/MinioFileServiceImpl.java @@ -28,7 +28,10 @@ package org.springblade.file.service.impl; import io.minio.GetObjectArgs; import io.minio.GetPresignedObjectUrlArgs; import io.minio.MinioClient; +import io.minio.ComposeObjectArgs; +import io.minio.ComposeSource; import io.minio.PutObjectArgs; +import io.minio.RemoveObjectArgs; import io.minio.UploadObjectArgs; import io.minio.http.Method; import lombok.RequiredArgsConstructor; @@ -43,7 +46,10 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.time.Duration; +import java.util.ArrayList; +import java.util.List; import java.util.Optional; +import java.util.UUID; /** * MinIO文件存储 @@ -59,6 +65,44 @@ public class MinioFileServiceImpl extends AbstractFileService { private final FileStorageProperties.StoreProperties properties; private final MinioClient minioClient; + @Override + public String initiateMultipartUpload(String objectKey) { + return UUID.randomUUID().toString(); + } + + @Override + public String getUploadPartUrl(String objectKey, String uploadId, Integer partNumber, Long expires) { + try { + return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder() + .method(Method.PUT).bucket(properties.getBucketName()).object(partObjectKey(objectKey, uploadId, partNumber)) + .expiry(Math.toIntExact(resolveExpires(expires))).build()); + } catch (Exception exception) { + log.error("MinIO获取分片上传链接异常 objectKey:{}", objectKey, exception); + throw new ServiceException("获取分片上传链接异常"); + } + } + + @Override + public void completeMultipartUpload(String objectKey, String uploadId, List partEtags) { + try { + List sources = new ArrayList<>(); + for (org.springblade.file.pojo.vo.PartEtagVO part : partEtags) { + sources.add(ComposeSource.builder().bucket(properties.getBucketName()).object(partObjectKey(objectKey, uploadId, part.getPartNumber())).build()); + } + minioClient.composeObject(ComposeObjectArgs.builder().bucket(properties.getBucketName()).object(resolveObjectKey(objectKey)).sources(sources).build()); + for (org.springblade.file.pojo.vo.PartEtagVO part : partEtags) { + minioClient.removeObject(RemoveObjectArgs.builder().bucket(properties.getBucketName()).object(partObjectKey(objectKey, uploadId, part.getPartNumber())).build()); + } + } catch (Exception exception) { + log.error("MinIO合并分片异常 objectKey:{}", objectKey, exception); + throw new ServiceException("MinIO合并分片异常"); + } + } + + private String partObjectKey(String objectKey, String uploadId, Integer partNumber) { + return resolveObjectKey(".multipart/" + uploadId + "/" + objectKey + ".part-" + partNumber); + } + @Override public void uploadFile(String objectKey, InputStream inputStream) { try { diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/VoucherManageController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/VoucherManageController.java new file mode 100644 index 0000000..e831a64 --- /dev/null +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/VoucherManageController.java @@ -0,0 +1,73 @@ +package org.springblade.transport.controller; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.AllArgsConstructor; +import org.springblade.core.boot.ctrl.BladeController; +import org.springblade.core.mp.support.Condition; +import org.springblade.core.mp.support.Query; +import org.springblade.core.secure.annotation.PreAuth; +import org.springblade.core.tool.api.R; +import org.springblade.transport.pojo.dto.VoucherManageSubmitRequest; +import org.springblade.transport.pojo.dto.VoucherUploadDraftRequest; +import org.springblade.transport.pojo.entity.VoucherManage; +import org.springblade.transport.pojo.vo.VoucherManageVO; +import org.springblade.transport.service.IVoucherManageService; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; +import java.util.Map; + +@RestController +@AllArgsConstructor +@PreAuth(menu = "voucher_manage") +@RequestMapping("/voucher-manage") +@Tag(name = "凭证管理", description = "执行凭证批次管理") +public class VoucherManageController extends BladeController { + private final IVoucherManageService voucherManageService; + + @GetMapping("/list") + @ApiOperationSupport(order = 1) + @Operation(summary = "凭证管理分页") + public R> list(VoucherManageVO voucherManage, Query query) { + return R.data(voucherManageService.selectPage(Condition.getPage(query), voucherManage)); + } + + @GetMapping("/detail") + @ApiOperationSupport(order = 2) + @Operation(summary = "凭证详情") + public R detail(@RequestParam Long id) { return R.data(voucherManageService.detail(id)); } + + @PostMapping("/submit") + @ApiOperationSupport(order = 3) + @Operation(summary = "上传凭证或更换运输批次") + public R submit(@RequestBody VoucherManageSubmitRequest request) { voucherManageService.submit(request); return R.success("提交成功"); } + + @PostMapping("/upload-draft") + @ApiOperationSupport(order = 4) + @Operation(summary = "创建凭证上传草稿") + public R createUploadDraft(@RequestBody VoucherUploadDraftRequest request) { + return R.data(voucherManageService.createUploadDraft(request)); + } + + @PostMapping("/remove") + @ApiOperationSupport(order = 5) + @Operation(summary = "删除凭证") + public R remove(@RequestParam Long id) { voucherManageService.removeVoucher(id); return R.success("删除成功"); } + + @GetMapping("/waybill-batches") + @ApiOperationSupport(order = 6) + @Operation(summary = "可关联运输批次") + public R>> waybillBatches(@RequestParam(required = false) String batchNo, + @RequestParam(required = false) String createUser, @RequestParam(required = false) Integer waybillCount, + @RequestParam(required = false) String createTimeStart, @RequestParam(required = false) String createTimeEnd) { + return R.data(voucherManageService.selectableWaybillBatches(batchNo, createUser, waybillCount, createTimeStart, createTimeEnd)); + } +} diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/VoucherManageMapper.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/VoucherManageMapper.java new file mode 100644 index 0000000..6bfed71 --- /dev/null +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/VoucherManageMapper.java @@ -0,0 +1,6 @@ +package org.springblade.transport.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.springblade.transport.pojo.entity.VoucherManage; + +public interface VoucherManageMapper extends BaseMapper { } diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/VoucherWaybillBatchMapper.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/VoucherWaybillBatchMapper.java new file mode 100644 index 0000000..d5300d1 --- /dev/null +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/VoucherWaybillBatchMapper.java @@ -0,0 +1,15 @@ +package org.springblade.transport.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.springblade.transport.pojo.entity.VoucherWaybillBatch; + +import java.util.List; +import java.util.Map; + +import org.apache.ibatis.annotations.Param; + +public interface VoucherWaybillBatchMapper extends BaseMapper { + List> selectWaybillBatches(@Param("tenantId") String tenantId, @Param("batchNo") String batchNo, @Param("createUser") String createUser, + @Param("waybillCount") Integer waybillCount, @Param("createTimeStart") String createTimeStart, @Param("createTimeEnd") String createTimeEnd); + List> selectWaybillBatchesByIds(@Param("tenantId") String tenantId, @Param("ids") List ids); +} diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/VoucherWaybillBatchMapper.xml b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/VoucherWaybillBatchMapper.xml new file mode 100644 index 0000000..8240c32 --- /dev/null +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/VoucherWaybillBatchMapper.xml @@ -0,0 +1,24 @@ + + + + + + diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IVoucherManageService.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IVoucherManageService.java new file mode 100644 index 0000000..dad9a28 --- /dev/null +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IVoucherManageService.java @@ -0,0 +1,20 @@ +package org.springblade.transport.service; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import org.springblade.core.mp.base.BaseService; +import org.springblade.transport.pojo.dto.VoucherManageSubmitRequest; +import org.springblade.transport.pojo.dto.VoucherUploadDraftRequest; +import org.springblade.transport.pojo.entity.VoucherManage; +import org.springblade.transport.pojo.vo.VoucherManageVO; + +import java.util.List; +import java.util.Map; + +public interface IVoucherManageService extends BaseService { + IPage selectPage(IPage page, VoucherManageVO query); + VoucherManageVO detail(Long id); + void submit(VoucherManageSubmitRequest request); + VoucherManage createUploadDraft(VoucherUploadDraftRequest request); + void removeVoucher(Long id); + List> selectableWaybillBatches(String batchNo, String createUser, Integer waybillCount, String createTimeStart, String createTimeEnd); +} diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CustomerArchiveServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CustomerArchiveServiceImpl.java index 975cc7d..f9b3e17 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CustomerArchiveServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CustomerArchiveServiceImpl.java @@ -216,7 +216,8 @@ public class CustomerArchiveServiceImpl extends BaseServiceImpl implements IVoucherManageService { + + private final VoucherWaybillBatchMapper voucherWaybillBatchMapper; + private final IProjectApplyService projectApplyService; + + public VoucherManageServiceImpl(VoucherWaybillBatchMapper voucherWaybillBatchMapper, IProjectApplyService projectApplyService) { + this.voucherWaybillBatchMapper = voucherWaybillBatchMapper; + this.projectApplyService = projectApplyService; + } + + @Override + public IPage selectPage(IPage page, VoucherManageVO query) { + return VoucherManageWrapper.build().pageVO(page(page, buildQuery(query))); + } + + @Override + public VoucherManageVO detail(Long id) { + VoucherManage record = getById(id); + if (record == null || Objects.equals(record.getIsDeleted(), 1)) throw new ServiceException("凭证批次不存在"); + return VoucherManageWrapper.build().entityVO(record); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public VoucherManage createUploadDraft(VoucherUploadDraftRequest request) { + if (Func.isEmpty(request.getProjectId())) throw new ServiceException("请先选择项目名称"); + if (Func.isEmpty(request.getFileName())) throw new ServiceException("文件名称不能为空"); + ProjectApply project = projectApplyService.getById(request.getProjectId()); + if (project == null || !List.of("approved", "change_approved").contains(project.getApprovalStatus())) { + throw new ServiceException("仅允许选择审核通过或变更审核通过的项目"); + } + VoucherManage voucher = new VoucherManage(); + voucher.setProjectId(project.getId()); + voucher.setProjectName(project.getProjectName()); + voucher.setVoucherBatchNo(nextCode()); + voucher.setFileName(request.getFileName()); + voucher.setFileUrl(""); + voucher.setUploadSource("内部"); + voucher.setProcessStatus("上传中"); + voucher.setAuditStatus("-"); + voucher.setVoucherCount(0); + voucher.setRelatedWaybillCount(0); + voucher.setUnRelatedWaybillCount(0); + save(voucher); + return voucher; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void submit(VoucherManageSubmitRequest request) { + if (Func.isEmpty(request.getProjectId())) throw new ServiceException("请选择项目名称"); + if (Func.isEmpty(request.getFileName()) || Func.isEmpty(request.getFileUrl())) throw new ServiceException("请上传执行凭证压缩包"); + if (Func.isEmpty(request.getWaybillImportBatchIds())) throw new ServiceException("请至少关联一个运输批次"); + VoucherManage voucher = Func.isEmpty(request.getId()) ? new VoucherManage() : getById(request.getId()); + if (voucher == null) throw new ServiceException("凭证批次不存在"); + ProjectApply project = projectApplyService.getById(request.getProjectId()); + if (project == null || !List.of("approved", "change_approved").contains(project.getApprovalStatus())) throw new ServiceException("仅允许选择审核通过或变更审核通过的项目"); + voucher.setProjectId(project.getId()); + voucher.setProjectName(project.getProjectName()); + voucher.setFileName(request.getFileName()); + voucher.setFileUrl(request.getFileUrl()); + voucher.setFileTaskId(request.getFileTaskId()); + voucher.setUploadSource("内部"); + voucher.setProcessStatus("上传中"); + voucher.setAuditStatus("-"); + voucher.setVoucherCount(0); + voucher.setRelatedWaybillCount(0); + voucher.setUnRelatedWaybillCount(0); + if (Func.isEmpty(voucher.getVoucherBatchNo())) voucher.setVoucherBatchNo(nextCode()); + saveOrUpdate(voucher); + voucherWaybillBatchMapper.delete(Wrappers.lambdaQuery().eq(VoucherWaybillBatch::getVoucherId, voucher.getId())); + List> batches = selectableWaybillBatchesByIds(request.getWaybillImportBatchIds()); + if (batches.size() != request.getWaybillImportBatchIds().size()) throw new ServiceException("存在无效的运输批次"); + List relations = new ArrayList<>(); + for (Map batch : batches) { + VoucherWaybillBatch relation = new VoucherWaybillBatch(); + relation.setVoucherId(voucher.getId()); + relation.setWaybillImportBatchId(((Number) batch.get("id")).longValue()); + relation.setWaybillBatchNo((String) batch.get("batchNo")); + relation.setWaybillCount(((Number) batch.get("waybillCount")).intValue()); + relations.add(relation); + } + relations.forEach(voucherWaybillBatchMapper::insert); + voucher.setWaybillBatchNo(relations.stream().map(VoucherWaybillBatch::getWaybillBatchNo).collect(Collectors.joining(","))); + int total = relations.stream().mapToInt(VoucherWaybillBatch::getWaybillCount).sum(); + voucher.setRelatedWaybillCount(total); + voucher.setUnRelatedWaybillCount(0); + updateById(voucher); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void removeVoucher(Long id) { + VoucherManage voucher = getById(id); + if (voucher == null) throw new ServiceException("凭证批次不存在"); + deleteLogic(List.of(id)); + voucherWaybillBatchMapper.delete(Wrappers.lambdaQuery().eq(VoucherWaybillBatch::getVoucherId, id)); + } + + @Override + public List> selectableWaybillBatches(String batchNo, String createUser, Integer waybillCount, String createTimeStart, String createTimeEnd) { + return voucherWaybillBatchMapper.selectWaybillBatches(AuthUtil.getTenantId(), batchNo, createUser, waybillCount, createTimeStart, createTimeEnd); + } + + private List> selectableWaybillBatchesByIds(List ids) { + if (Func.isEmpty(ids)) return List.of(); + return voucherWaybillBatchMapper.selectWaybillBatchesByIds(AuthUtil.getTenantId(), ids); + } + + private LambdaQueryWrapper buildQuery(VoucherManageVO query) { + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery().eq(VoucherManage::getIsDeleted, 0) + .like(Func.isNotEmpty(query.getVoucherBatchNo()), VoucherManage::getVoucherBatchNo, query.getVoucherBatchNo()) + .eq(Func.isNotEmpty(query.getAuditStatus()), VoucherManage::getAuditStatus, query.getAuditStatus()) + .eq(Func.isNotEmpty(query.getProcessStatus()), VoucherManage::getProcessStatus, query.getProcessStatus()) + .like(Func.isNotEmpty(query.getWaybillBatchNo()), VoucherManage::getWaybillBatchNo, query.getWaybillBatchNo()) + .eq(Func.isNotEmpty(query.getUploadSource()), VoucherManage::getUploadSource, query.getUploadSource()) + .like(Func.isNotEmpty(query.getCarrierName()), VoucherManage::getCarrierName, query.getCarrierName()); + if (query.getCreateTimeStart() != null) wrapper.ge(VoucherManage::getCreateTime, query.getCreateTimeStart()); + if (query.getCreateTimeEnd() != null) wrapper.le(VoucherManage::getCreateTime, query.getCreateTimeEnd()); + return wrapper.orderByDesc(VoucherManage::getCreateTime); + } + + private synchronized String nextCode() { + String prefix = "PZ" + java.time.LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE) + "-"; + long count = count(Wrappers.lambdaQuery().likeRight(VoucherManage::getVoucherBatchNo, prefix)); + return prefix + String.format("%03d", count + 1); + } +} diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/wrapper/VoucherManageWrapper.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/wrapper/VoucherManageWrapper.java new file mode 100644 index 0000000..29b2e5e --- /dev/null +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/wrapper/VoucherManageWrapper.java @@ -0,0 +1,20 @@ +package org.springblade.transport.wrapper; + +import org.springblade.core.mp.support.BaseEntityWrapper; +import org.springblade.core.tool.utils.BeanUtil; +import org.springblade.system.cache.UserCache; +import org.springblade.transport.pojo.entity.VoucherManage; +import org.springblade.transport.pojo.vo.VoucherManageVO; + +import java.util.Objects; + +public class VoucherManageWrapper extends BaseEntityWrapper { + public static VoucherManageWrapper build() { return new VoucherManageWrapper(); } + @Override + public VoucherManageVO entityVO(VoucherManage entity) { + VoucherManageVO vo = Objects.requireNonNull(BeanUtil.copyProperties(entity, VoucherManageVO.class)); + vo.setCreateUserName(UserCache.getUserRealName(entity.getCreateUser())); + vo.setUpdateUserName(UserCache.getUserRealName(entity.getUpdateUser())); + return vo; + } +} diff --git a/doc/sql/bladex/blade_file_task_20260812.sql b/doc/sql/bladex/blade_file_task_20260812.sql new file mode 100644 index 0000000..ccae841 --- /dev/null +++ b/doc/sql/bladex/blade_file_task_20260812.sql @@ -0,0 +1,42 @@ +-- 文件服务数据库:分片上传任务主表。 +-- 本脚本需与 blade_file_task_part 表配套执行;请在 blade-file 服务配置的数据源中运行。 + +CREATE TABLE IF NOT EXISTS `blade_file_task` ( + `id` bigint NOT NULL COMMENT '主键ID', + `md5` varchar(128) NOT NULL COMMENT '文件MD5', + `object_key` varchar(1000) NOT NULL COMMENT '对象存储对象键', + `attachment_name` varchar(500) NOT NULL COMMENT '附件名称', + `suffix` varchar(50) DEFAULT NULL COMMENT '文件后缀', + `size` bigint NOT NULL COMMENT '文件大小,单位:字节', + `chunk_size` int NOT NULL COMMENT '分片大小,单位:字节', + `chunk_total` int NOT NULL COMMENT '分片总数', + `current_index` int NOT NULL DEFAULT 0 COMMENT '已完成分片数', + `upload_id` varchar(255) DEFAULT NULL COMMENT '对象存储上传任务ID', + `status` varchar(20) NOT NULL DEFAULT 'uploading' COMMENT '状态:uploading、completed、failed、paused', + `business_type` varchar(50) DEFAULT NULL COMMENT '业务类型,例如 voucher', + `business_id` varchar(64) DEFAULT NULL COMMENT '业务标识,例如凭证批次号', + `create_user_name` varchar(50) DEFAULT NULL COMMENT '创建人名称', + `create_user` bigint DEFAULT NULL COMMENT '创建人ID', + `create_time` datetime DEFAULT NULL COMMENT '创建时间', + `update_user_name` varchar(50) DEFAULT NULL COMMENT '更新人名称', + `update_user` bigint DEFAULT NULL COMMENT '更新人ID', + `update_time` datetime DEFAULT NULL COMMENT '更新时间', + `is_deleted` tinyint NOT NULL DEFAULT 0 COMMENT '是否删除:0否、1是', + PRIMARY KEY (`id`), + KEY `idx_file_task_md5` (`md5`), + KEY `idx_file_task_business` (`business_type`, `business_id`), + KEY `idx_file_task_create_user_time` (`create_user`, `create_time`), + KEY `idx_file_task_status` (`status`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文件分片上传任务'; + +CREATE TABLE IF NOT EXISTS `blade_file_task_part` ( + `id` bigint NOT NULL COMMENT '主键ID', + `file_task_id` bigint NOT NULL COMMENT '文件上传任务ID', + `part_number` int NOT NULL COMMENT '分片编号,从1开始', + `etag` varchar(255) NOT NULL COMMENT '分片ETag', + `create_user` bigint DEFAULT NULL COMMENT '创建人ID', + `create_time` datetime DEFAULT NULL COMMENT '创建时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_file_task_part` (`file_task_id`, `part_number`), + KEY `idx_file_task_part_task` (`file_task_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文件分片上传任务分片记录'; diff --git a/doc/sql/bladex/blade_file_task_upgrade_20260812.sql b/doc/sql/bladex/blade_file_task_upgrade_20260812.sql new file mode 100644 index 0000000..3eb4085 --- /dev/null +++ b/doc/sql/bladex/blade_file_task_upgrade_20260812.sql @@ -0,0 +1,4 @@ +-- 修复旧版 blade_file_task 的逻辑删除字段默认值。 +-- 请在 blade-file 服务配置的数据源执行。 +UPDATE `blade_file_task` SET `is_deleted` = 0 WHERE `is_deleted` IS NULL; +ALTER TABLE `blade_file_task` MODIFY COLUMN `is_deleted` tinyint NOT NULL DEFAULT 0 COMMENT '是否删除:0否、1是'; diff --git a/doc/sql/bladex/blade_file_task_voucher_upload_20260812.sql b/doc/sql/bladex/blade_file_task_voucher_upload_20260812.sql new file mode 100644 index 0000000..040faca --- /dev/null +++ b/doc/sql/bladex/blade_file_task_voucher_upload_20260812.sql @@ -0,0 +1,8 @@ +-- 请在 blade_file 服务所使用的数据源执行本脚本,不要在 transport 库执行。 +-- MySQL 8.0.29+ 支持 IF NOT EXISTS;若版本较低,请先确认字段和索引不存在再执行。 +ALTER TABLE `blade_file_task` + ADD COLUMN IF NOT EXISTS `business_type` varchar(50) DEFAULT NULL COMMENT '业务类型', + ADD COLUMN IF NOT EXISTS `business_id` varchar(64) DEFAULT NULL COMMENT '业务标识'; + +CREATE INDEX IF NOT EXISTS `idx_file_task_business` + ON `blade_file_task` (`business_type`, `business_id`); diff --git a/doc/sql/transport/blade_customer_archive.sql b/doc/sql/transport/blade_customer_archive.sql index 036a419..c1c33d9 100644 --- a/doc/sql/transport/blade_customer_archive.sql +++ b/doc/sql/transport/blade_customer_archive.sql @@ -117,6 +117,7 @@ CREATE TABLE `blade_customer_invoice_info` ( `tenant_id` varchar(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '000000' COMMENT '租户ID', `customer_id` bigint NOT NULL COMMENT '客商ID', `invoice_title` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '受票方名称', + `invoice_type` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '发票类型', `tax_no` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '纳税人识别号', `bank_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '开户行名称', `registered_phone` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '注册电话', diff --git a/doc/sql/transport/blade_customer_invoice_info_invoice_type_20260812.sql b/doc/sql/transport/blade_customer_invoice_info_invoice_type_20260812.sql new file mode 100644 index 0000000..cb46993 --- /dev/null +++ b/doc/sql/transport/blade_customer_invoice_info_invoice_type_20260812.sql @@ -0,0 +1,4 @@ +-- 客商发票信息:新增发票类型 + +ALTER TABLE `blade_customer_invoice_info` + ADD COLUMN `invoice_type` varchar(30) DEFAULT NULL COMMENT '发票类型' AFTER `invoice_title`; diff --git a/doc/sql/transport/blade_voucher_manage_20260812.sql b/doc/sql/transport/blade_voucher_manage_20260812.sql new file mode 100644 index 0000000..071fb70 --- /dev/null +++ b/doc/sql/transport/blade_voucher_manage_20260812.sql @@ -0,0 +1,42 @@ +CREATE TABLE IF NOT EXISTS `blade_voucher_manage` ( + `id` bigint NOT NULL, + `tenant_id` varchar(12) NOT NULL DEFAULT '000000', + `project_id` bigint NOT NULL COMMENT '项目ID', + `project_name` varchar(200) NOT NULL COMMENT '项目名称', + `voucher_batch_no` varchar(30) NOT NULL COMMENT '凭证批次号', + `waybill_batch_no` varchar(1000) DEFAULT NULL COMMENT '关联运单批次号', + `file_name` varchar(100) NOT NULL COMMENT '凭证压缩包文件名', + `file_url` varchar(1000) NOT NULL COMMENT '凭证压缩包地址', + `file_task_id` bigint DEFAULT NULL COMMENT '分片上传任务ID', + `upload_source` varchar(20) NOT NULL DEFAULT '内部' COMMENT '上传来源:内部、承运商', + `carrier_name` varchar(50) DEFAULT NULL COMMENT '承运商名称', + `process_status` varchar(20) NOT NULL DEFAULT '上传中' COMMENT '上传中、处理中、处理完成', + `voucher_count` int NOT NULL DEFAULT 0 COMMENT '凭证数量', + `related_waybill_count` int NOT NULL DEFAULT 0 COMMENT '已关联运单数', + `un_related_waybill_count` int NOT NULL DEFAULT 0 COMMENT '未关联运单数', + `audit_status` varchar(20) NOT NULL DEFAULT '-' COMMENT '待审核、审核通过、审核驳回', + `create_user` bigint DEFAULT NULL, `create_dept` bigint DEFAULT NULL, + `create_time` datetime DEFAULT NULL, `update_user` bigint DEFAULT NULL, `update_time` datetime DEFAULT NULL, + `status` tinyint NOT NULL DEFAULT 1, `is_deleted` tinyint NOT NULL DEFAULT 0, + PRIMARY KEY (`id`), UNIQUE KEY `uk_voucher_manage_no` (`tenant_id`, `voucher_batch_no`), + KEY `idx_voucher_manage_create_time` (`create_time`), KEY `idx_voucher_manage_process_status` (`process_status`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='凭证管理'; + +CREATE TABLE IF NOT EXISTS `blade_voucher_waybill_batch` ( + `id` bigint NOT NULL, `tenant_id` varchar(12) NOT NULL DEFAULT '000000', + `voucher_id` bigint NOT NULL COMMENT '凭证批次ID', `waybill_import_batch_id` bigint NOT NULL COMMENT '运单导入批次ID', + `waybill_batch_no` varchar(64) NOT NULL COMMENT '运单批次号', `waybill_count` int NOT NULL DEFAULT 0 COMMENT '运单数', + `create_user` bigint DEFAULT NULL, `create_dept` bigint DEFAULT NULL, `create_time` datetime DEFAULT NULL, + `update_user` bigint DEFAULT NULL, `update_time` datetime DEFAULT NULL, `status` tinyint NOT NULL DEFAULT 1, `is_deleted` tinyint NOT NULL DEFAULT 0, + PRIMARY KEY (`id`), UNIQUE KEY `uk_voucher_waybill_batch` (`voucher_id`, `waybill_import_batch_id`), + KEY `idx_voucher_waybill_batch_voucher` (`voucher_id`), KEY `idx_voucher_waybill_batch_waybill` (`waybill_import_batch_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='凭证关联运单批次'; + +INSERT INTO `blade_menu` (`id`, `parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `component`, `remark`, `is_deleted`) +VALUES +(2090000000000000900, 2090000000000000000, 'voucher_manage', '凭证管理', 'voucher_manage', '/business/voucher-manage', 'iconfont icon-caidanguanli', 90, 1, 0, 1, NULL, '', 0), +(2090000000000000901, 2090000000000000900, 'voucher_manage_view', '查看', 'voucher_manage_view', '', '', 1, 2, 0, 1, NULL, '', 0), +(2090000000000000902, 2090000000000000900, 'voucher_manage_add', '批量导入凭证', 'voucher_manage_add', '', '', 2, 2, 0, 1, NULL, '', 0), +(2090000000000000903, 2090000000000000900, 'voucher_manage_edit', '更换运单批次', 'voucher_manage_edit', '', '', 3, 2, 0, 1, NULL, '', 0), +(2090000000000000904, 2090000000000000900, 'voucher_manage_delete', '删除', 'voucher_manage_delete', '', '', 4, 2, 0, 1, NULL, '', 0) +ON DUPLICATE KEY UPDATE `name` = VALUES(`name`), `path` = VALUES(`path`), `is_deleted` = 0; diff --git a/doc/sql/transport/blade_voucher_manage_upgrade_20260812.sql b/doc/sql/transport/blade_voucher_manage_upgrade_20260812.sql new file mode 100644 index 0000000..44cc8d0 --- /dev/null +++ b/doc/sql/transport/blade_voucher_manage_upgrade_20260812.sql @@ -0,0 +1,93 @@ +-- MySQL 5.7+ 兼容:为已存在的 blade_voucher_manage 旧表补齐字段和索引。 +-- 请在 transport 数据库执行。本脚本使用 information_schema 判断,不依赖 IF NOT EXISTS 语法。 + +DELIMITER $$ + +DROP PROCEDURE IF EXISTS `upgrade_blade_voucher_manage_20260812`$$ +CREATE PROCEDURE `upgrade_blade_voucher_manage_20260812`() +BEGIN + DECLARE db_name varchar(128); + SET db_name = DATABASE(); + + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = db_name AND table_name = 'blade_voucher_manage' AND column_name = 'tenant_id') THEN + ALTER TABLE `blade_voucher_manage` ADD COLUMN `tenant_id` varchar(12) NOT NULL DEFAULT '000000' COMMENT '租户ID' AFTER `id`; + END IF; + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = db_name AND table_name = 'blade_voucher_manage' AND column_name = 'project_id') THEN + ALTER TABLE `blade_voucher_manage` ADD COLUMN `project_id` bigint NOT NULL DEFAULT 0 COMMENT '项目ID'; + END IF; + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = db_name AND table_name = 'blade_voucher_manage' AND column_name = 'project_name') THEN + ALTER TABLE `blade_voucher_manage` ADD COLUMN `project_name` varchar(200) NOT NULL DEFAULT '' COMMENT '项目名称'; + END IF; + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = db_name AND table_name = 'blade_voucher_manage' AND column_name = 'voucher_batch_no') THEN + ALTER TABLE `blade_voucher_manage` ADD COLUMN `voucher_batch_no` varchar(30) NOT NULL DEFAULT '' COMMENT '凭证批次号'; + END IF; + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = db_name AND table_name = 'blade_voucher_manage' AND column_name = 'waybill_batch_no') THEN + ALTER TABLE `blade_voucher_manage` ADD COLUMN `waybill_batch_no` varchar(1000) DEFAULT NULL COMMENT '关联运单批次号'; + END IF; + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = db_name AND table_name = 'blade_voucher_manage' AND column_name = 'file_name') THEN + ALTER TABLE `blade_voucher_manage` ADD COLUMN `file_name` varchar(100) NOT NULL DEFAULT '' COMMENT '凭证压缩包文件名'; + END IF; + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = db_name AND table_name = 'blade_voucher_manage' AND column_name = 'file_url') THEN + ALTER TABLE `blade_voucher_manage` ADD COLUMN `file_url` varchar(1000) NOT NULL DEFAULT '' COMMENT '凭证压缩包地址'; + END IF; + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = db_name AND table_name = 'blade_voucher_manage' AND column_name = 'file_task_id') THEN + ALTER TABLE `blade_voucher_manage` ADD COLUMN `file_task_id` bigint DEFAULT NULL COMMENT '分片上传任务ID'; + END IF; + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = db_name AND table_name = 'blade_voucher_manage' AND column_name = 'upload_source') THEN + ALTER TABLE `blade_voucher_manage` ADD COLUMN `upload_source` varchar(20) NOT NULL DEFAULT '内部' COMMENT '上传来源:内部、承运商'; + END IF; + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = db_name AND table_name = 'blade_voucher_manage' AND column_name = 'carrier_name') THEN + ALTER TABLE `blade_voucher_manage` ADD COLUMN `carrier_name` varchar(50) DEFAULT NULL COMMENT '承运商名称'; + END IF; + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = db_name AND table_name = 'blade_voucher_manage' AND column_name = 'process_status') THEN + ALTER TABLE `blade_voucher_manage` ADD COLUMN `process_status` varchar(20) NOT NULL DEFAULT '上传中' COMMENT '上传中、处理中、处理完成'; + END IF; + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = db_name AND table_name = 'blade_voucher_manage' AND column_name = 'voucher_count') THEN + ALTER TABLE `blade_voucher_manage` ADD COLUMN `voucher_count` int NOT NULL DEFAULT 0 COMMENT '凭证数量'; + END IF; + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = db_name AND table_name = 'blade_voucher_manage' AND column_name = 'related_waybill_count') THEN + ALTER TABLE `blade_voucher_manage` ADD COLUMN `related_waybill_count` int NOT NULL DEFAULT 0 COMMENT '已关联运单数'; + END IF; + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = db_name AND table_name = 'blade_voucher_manage' AND column_name = 'un_related_waybill_count') THEN + ALTER TABLE `blade_voucher_manage` ADD COLUMN `un_related_waybill_count` int NOT NULL DEFAULT 0 COMMENT '未关联运单数'; + END IF; + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = db_name AND table_name = 'blade_voucher_manage' AND column_name = 'audit_status') THEN + ALTER TABLE `blade_voucher_manage` ADD COLUMN `audit_status` varchar(20) NOT NULL DEFAULT '-' COMMENT '待审核、审核通过、审核驳回'; + END IF; + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = db_name AND table_name = 'blade_voucher_manage' AND column_name = 'create_user') THEN + ALTER TABLE `blade_voucher_manage` ADD COLUMN `create_user` bigint DEFAULT NULL COMMENT '创建人'; + END IF; + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = db_name AND table_name = 'blade_voucher_manage' AND column_name = 'create_dept') THEN + ALTER TABLE `blade_voucher_manage` ADD COLUMN `create_dept` bigint DEFAULT NULL COMMENT '创建部门'; + END IF; + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = db_name AND table_name = 'blade_voucher_manage' AND column_name = 'create_time') THEN + ALTER TABLE `blade_voucher_manage` ADD COLUMN `create_time` datetime DEFAULT NULL COMMENT '创建时间'; + END IF; + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = db_name AND table_name = 'blade_voucher_manage' AND column_name = 'update_user') THEN + ALTER TABLE `blade_voucher_manage` ADD COLUMN `update_user` bigint DEFAULT NULL COMMENT '更新人'; + END IF; + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = db_name AND table_name = 'blade_voucher_manage' AND column_name = 'update_time') THEN + ALTER TABLE `blade_voucher_manage` ADD COLUMN `update_time` datetime DEFAULT NULL COMMENT '更新时间'; + END IF; + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = db_name AND table_name = 'blade_voucher_manage' AND column_name = 'status') THEN + ALTER TABLE `blade_voucher_manage` ADD COLUMN `status` tinyint NOT NULL DEFAULT 1 COMMENT '状态'; + END IF; + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = db_name AND table_name = 'blade_voucher_manage' AND column_name = 'is_deleted') THEN + ALTER TABLE `blade_voucher_manage` ADD COLUMN `is_deleted` tinyint NOT NULL DEFAULT 0 COMMENT '是否删除'; + END IF; + + IF NOT EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema = db_name AND table_name = 'blade_voucher_manage' AND index_name = 'idx_voucher_manage_create_time') THEN + CREATE INDEX `idx_voucher_manage_create_time` ON `blade_voucher_manage` (`create_time`); + END IF; + IF NOT EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema = db_name AND table_name = 'blade_voucher_manage' AND index_name = 'idx_voucher_manage_process_status') THEN + CREATE INDEX `idx_voucher_manage_process_status` ON `blade_voucher_manage` (`process_status`); + END IF; + IF NOT EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema = db_name AND table_name = 'blade_voucher_manage' AND index_name = 'idx_voucher_manage_tenant_batch_no') THEN + CREATE INDEX `idx_voucher_manage_tenant_batch_no` ON `blade_voucher_manage` (`tenant_id`, `voucher_batch_no`); + END IF; +END$$ + +CALL `upgrade_blade_voucher_manage_20260812`()$$ +DROP PROCEDURE `upgrade_blade_voucher_manage_20260812`$$ + +DELIMITER ;