From 62a110b6de7f4753ceb07c710a816f2cdf14706e Mon Sep 17 00:00:00 2001 From: b2894lxlx <517289602@qq.com> Date: Tue, 4 Aug 2026 20:53:57 +0800 Subject: [PATCH] =?UTF-8?q?1=E3=80=81=E4=BF=AE=E5=A4=8D=E8=8F=9C=E5=8D=95?= =?UTF-8?q?=E6=97=A0=E6=B3=95=E7=BC=96=E8=BE=91=202=E3=80=81=E5=AE=8C?= =?UTF-8?q?=E5=96=84=E8=BF=90=E8=BE=93=E8=AE=A1=E5=88=92=203=E3=80=81?= =?UTF-8?q?=E5=AE=8C=E5=96=84=E8=BF=90=E5=8D=95=E7=AE=A1=E7=90=86=204?= =?UTF-8?q?=E3=80=81=E6=96=B0=E5=A2=9E=E9=85=8D=E7=BD=AE=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../FileCertificateBatchRecognitionDTO.java | 24 + .../transport/pojo/entity/LoadingManage.java | 143 +++++ .../transport/pojo/entity/Waybill.java | 86 +++ .../transport/pojo/vo/LoadingManageVO.java | 69 ++ .../transport/pojo/vo/WaybillVO.java | 3 + .../file/controller/FileController.java | 92 ++- .../service/impl/OCRConvertServiceImpl.java | 32 +- .../system/service/impl/MenuServiceImpl.java | 17 +- .../controller/LoadingManageController.java | 128 ++++ .../controller/WaybillController.java | 31 +- .../transport/excel/LoadingManageExcel.java | 101 +++ .../transport/excel/WaybillExcel.java | 51 ++ .../transport/mapper/LoadingManageMapper.java | 17 + .../service/ILoadingManageService.java | 45 ++ .../transport/service/IWaybillService.java | 1 + .../impl/LoadingManageServiceImpl.java | 600 ++++++++++++++++++ .../service/impl/WaybillServiceImpl.java | 274 +++++++- .../wrapper/LoadingManageWrapper.java | 53 ++ .../dto/CertificateBatchRecognitionDTO.java | 16 + .../ocr/service/impl/OCRServiceImpl.java | 122 +++- .../blade_loading_manage_20260804.sql | 65 ++ doc/sql/transport/blade_tms_business.sql | 103 ++- .../blade_waybill_patch_20260804.sql | 30 + 23 files changed, 2081 insertions(+), 22 deletions(-) create mode 100644 blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/LoadingManage.java create mode 100644 blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/LoadingManageVO.java create mode 100644 blade-service/blade-transport/src/main/java/org/springblade/transport/controller/LoadingManageController.java create mode 100644 blade-service/blade-transport/src/main/java/org/springblade/transport/excel/LoadingManageExcel.java create mode 100644 blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/LoadingManageMapper.java create mode 100644 blade-service/blade-transport/src/main/java/org/springblade/transport/service/ILoadingManageService.java create mode 100644 blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/LoadingManageServiceImpl.java create mode 100644 blade-service/blade-transport/src/main/java/org/springblade/transport/wrapper/LoadingManageWrapper.java create mode 100644 doc/sql/transport/blade_loading_manage_20260804.sql create mode 100644 doc/sql/transport/blade_waybill_patch_20260804.sql diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/FileCertificateBatchRecognitionDTO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/FileCertificateBatchRecognitionDTO.java index 92c6a0c..21aec90 100644 --- a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/FileCertificateBatchRecognitionDTO.java +++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/pojo/dto/FileCertificateBatchRecognitionDTO.java @@ -37,9 +37,33 @@ public class FileCertificateBatchRecognitionDTO implements Serializable { * 目录名称 */ private String dirName; + /** + * 磅单 obs key列表 + */ + private List poundUrls; + /** + * 车头 obs key列表 + */ + private List carFrontUrls; + /** + * 驾驶证 obs key列表 + */ + private List driverLicenseUrls; + /** + * 行驶证 obs key列表 + */ + private List vehicleLicenseUrls; public FileCertificateBatchRecognitionDTO(List objectKeys) { this.objectKeys = objectKeys; } + public FileCertificateBatchRecognitionDTO( + List objectKeys, String projectAbbreviation, String code, String dirName) { + this.objectKeys = objectKeys; + this.projectAbbreviation = projectAbbreviation; + this.code = code; + this.dirName = dirName; + } + } diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/LoadingManage.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/LoadingManage.java new file mode 100644 index 0000000..2488c5f --- /dev/null +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/LoadingManage.java @@ -0,0 +1,143 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + */ +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; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; + +/** + * 配载管理实体类 + * + * @author Chill + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("blade_loading_manage") +@Schema(description = "配载管理") +public class LoadingManage extends TenantEntity { + + @Serial + private static final long serialVersionUID = 1L; + + @Schema(description = "配载单号") + private String loadingNo; + + @Schema(description = "配载子单号") + private String loadingSubNos; + + @Schema(description = "运单ID集合") + private String waybillIdsJson; + + @Schema(description = "项目ID") + private Long projectId; + + @Schema(description = "项目名称") + private String projectName; + + @Schema(description = "客户名称") + private String customerName; + + @Schema(description = "运输类型") + private String transportType; + + @Schema(description = "货物类型") + private String cargoType; + + @Schema(description = "货物名称") + private String cargoName; + + @Schema(description = "车/船/航班/班列") + private String vehicleNo; + + @Schema(description = "挂车车牌号") + private String trailerVehicleNo; + + @Schema(description = "司机") + private String driverName; + + @Schema(description = "联系电话") + private String driverPhone; + + @Schema(description = "押运人") + private String escortName; + + @Schema(description = "押运人手机号") + private String escortPhone; + + @Schema(description = "承运类型") + private String carrierType; + + @Schema(description = "承运商") + private String carrierName; + + @Schema(description = "发货地址") + private String departureAddress; + + @Schema(description = "途经地") + private String transitAddress; + + @Schema(description = "到货地") + private String arrivalAddress; + + @Schema(description = "原始单号") + private String originalNo; + + @Schema(description = "数据来源") + private String dataSource; + + @Schema(description = "开始日期") + private LocalDate startDate; + + @Schema(description = "结束日期") + private LocalDate endDate; + + @Schema(description = "计划名称") + private String planName; + + @Schema(description = "运单批次号") + private String batchNo; + + @Schema(description = "当前过程节点") + private String currentProcessNode; + + @Schema(description = "里程") + private BigDecimal mileage; + + @Schema(description = "预计发货日期") + private LocalDate estimatedStartDate; + + @Schema(description = "预计完成日期") + private LocalDate estimatedEndDate; + + @Schema(description = "任务备注") + private String taskRemark; + + @Schema(description = "货物信息") + private String goodsJson; + + @Schema(description = "路线信息") + private String routeJson; + + @Schema(description = "任务信息") + private String taskInfoJson; + + @Schema(description = "所属组织ID") + private Long deptId; + + @Schema(description = "所属组织") + private String deptName; + + @Schema(description = "业务状态") + private String businessStatus; + +} diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/Waybill.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/Waybill.java index 6b1dc87..a17c99d 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/Waybill.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/Waybill.java @@ -29,7 +29,9 @@ import lombok.EqualsAndHashCode; import org.springblade.core.tenant.mp.TenantEntity; import java.io.Serial; +import java.math.BigDecimal; import java.time.LocalDate; +import java.time.LocalDateTime; /** * 运单管理实体类 @@ -72,21 +74,102 @@ public class Waybill extends TenantEntity { @Schema(description = "货物类型") private String cargoType; + @Schema(description = "规格") + private String specification; + + @Schema(description = "型号") + private String model; + + @Schema(description = "数量") + private BigDecimal quantity; + + @Schema(description = "数量单位") + private String quantityUnit; + + @Schema(description = "发货地址ID") + private Long departureAddressId; + + @Schema(description = "发货地") + private String departureName; + @Schema(description = "发货地址") private String departureAddress; + @Schema(description = "发货联系人") + private String departureContact; + + @Schema(description = "发货联系方式") + private String departurePhone; + + @Schema(description = "收货地址ID") + private Long arrivalAddressId; + + @Schema(description = "收货地") + private String arrivalName; + @Schema(description = "收货地址") private String arrivalAddress; + @Schema(description = "收货联系人") + private String arrivalContact; + + @Schema(description = "收货联系方式") + private String arrivalPhone; + + @Schema(description = "任务录入模式") + private String taskEntryMode; + + @Schema(description = "承运类型") + private String carrierType; + + @Schema(description = "承运商ID") + private Long carrierId; + @Schema(description = "承运商名称") private String carrierName; + @Schema(description = "司机ID") + private Long driverId; + @Schema(description = "司机姓名") private String driverName; + @Schema(description = "司机手机号") + private String driverPhone; + @Schema(description = "车/船/航班/班列号") private String vehicleNo; + @Schema(description = "挂车车牌号") + private String trailerVehicleNo; + + @Schema(description = "押运人") + private String escortName; + + @Schema(description = "押运人手机号") + private String escortPhone; + + @Schema(description = "里程") + private BigDecimal mileage; + + @Schema(description = "预计发货时间") + private LocalDateTime estimatedStartTime; + + @Schema(description = "预计完成时间") + private LocalDateTime estimatedEndTime; + + @Schema(description = "单价") + private BigDecimal unitPrice; + + @Schema(description = "计价单位") + private String priceUnit; + + @Schema(description = "其他费用合计") + private BigDecimal otherFeeTotal; + + @Schema(description = "任务备注") + private String taskRemark; + @Schema(description = "原始单号") private String originalNo; @@ -129,6 +212,9 @@ public class Waybill extends TenantEntity { @Schema(description = "承运信息") private String carrierJson; + @Schema(description = "任务信息") + private String taskInfoJson; + @Schema(description = "过程节点") private String processJson; diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/LoadingManageVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/LoadingManageVO.java new file mode 100644 index 0000000..c2ce78f --- /dev/null +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/LoadingManageVO.java @@ -0,0 +1,69 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + */ +package org.springblade.transport.pojo.vo; + +import com.baomidou.mybatisplus.annotation.TableField; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.springblade.transport.pojo.entity.LoadingManage; + +import java.io.Serial; +import java.time.LocalDate; + +/** + * 配载管理视图实体类 + * + * @author Chill + */ +@Data +@EqualsAndHashCode(callSuper = true) +@Schema(description = "配载管理") +public class LoadingManageVO extends LoadingManage { + + @Serial + private static final long serialVersionUID = 1L; + + @TableField(exist = false) + @Schema(description = "是否查看全部组织") + private Integer allDept; + + @TableField(exist = false) + @Schema(description = "是否只读") + private Boolean readonly; + + @TableField(exist = false) + @Schema(description = "创建人姓名") + private String createUserName; + + @TableField(exist = false) + @Schema(description = "更新人姓名") + private String updateUserName; + + @TableField(exist = false) + @Schema(description = "业务状态名称") + private String businessStatusName; + + @TableField(exist = false) + @Schema(description = "运单号") + private String waybillNo; + + @TableField(exist = false) + @Schema(description = "开始日期起") + private LocalDate startDateStart; + + @TableField(exist = false) + @Schema(description = "开始日期止") + private LocalDate startDateEnd; + + @TableField(exist = false) + @Schema(description = "结束日期起") + private LocalDate endDateStart; + + @TableField(exist = false) + @Schema(description = "结束日期止") + private LocalDate endDateEnd; + +} diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/WaybillVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/WaybillVO.java index 5689093..590c804 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/WaybillVO.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/WaybillVO.java @@ -62,5 +62,8 @@ public class WaybillVO extends Waybill { @Schema(description = "业务状态名称") private String businessStatusName; + @TableField(exist = false) + @Schema(description = "是否仅查询未配载运单") + private Integer onlyUnassignedLoading; } diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/controller/FileController.java b/blade-service/blade-file/src/main/java/org/springblade/file/controller/FileController.java index dc2c499..37cab5d 100644 --- a/blade-service/blade-file/src/main/java/org/springblade/file/controller/FileController.java +++ b/blade-service/blade-file/src/main/java/org/springblade/file/controller/FileController.java @@ -29,7 +29,9 @@ import org.springframework.context.ApplicationEvent; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; +import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Optional; /** @@ -145,8 +147,94 @@ public class FileController extends BladeController { @ApiLog("OCR识别-识别车辆运输凭证(不知道类型)") @Operation(summary = "识别车辆运输凭证,不知道类型", description = "参数:objectKeylist") @PostMapping("/recognitionTransportCertificates") - public R recognitionTransportCertificates(@RequestBody List objectKeys, @RequestParam(value = "projectAbbreviation", required = false) String projectAbbreviation, @RequestParam(value = "code", required = false) String code) { - return R.data(ocrConvertService.recognitionTransportCertificate(new FileCertificateBatchRecognitionDTO(objectKeys, projectAbbreviation, code, null))); + public R recognitionTransportCertificates( + @RequestBody List attachments, + @RequestParam(value = "projectAbbreviation", required = false) String projectAbbreviation, + @RequestParam(value = "code", required = false) String code) { + return R.data(ocrConvertService.recognitionTransportCertificate( + buildCertificateBatchRecognitionDTO(attachments, projectAbbreviation, code) + )); + } + + private FileCertificateBatchRecognitionDTO buildCertificateBatchRecognitionDTO( + List attachments, String projectAbbreviation, String code) { + List objectKeys = new ArrayList<>(); + List poundUrls = new ArrayList<>(); + List carFrontUrls = new ArrayList<>(); + List driverLicenseUrls = new ArrayList<>(); + List vehicleLicenseUrls = new ArrayList<>(); + for (Object attachment : Optional.ofNullable(attachments).orElse(List.of())) { + if (attachment instanceof String objectKey) { + if (StringUtil.isNotBlank(objectKey)) { + objectKeys.add(objectKey); + } + continue; + } + if (attachment instanceof Map attachmentMap) { + String objectKey = getAttachmentObjectKey(attachmentMap); + if (StringUtil.isBlank(objectKey)) { + continue; + } + objectKeys.add(objectKey); + addTypedObjectKeys(attachmentMap, poundUrls, carFrontUrls, driverLicenseUrls, vehicleLicenseUrls); + } + } + FileCertificateBatchRecognitionDTO dto = new FileCertificateBatchRecognitionDTO( + objectKeys, projectAbbreviation, code, null + ); + dto.setPoundUrls(poundUrls); + dto.setCarFrontUrls(carFrontUrls); + dto.setDriverLicenseUrls(driverLicenseUrls); + dto.setVehicleLicenseUrls(vehicleLicenseUrls); + return dto; + } + + private String getAttachmentObjectKey(Map map) { + String objectKey = getMapString(map, "objectKey"); + if (StringUtil.isNotBlank(objectKey)) { + return objectKey; + } + String url = getMapString(map, "url"); + if (StringUtil.isNotBlank(url)) { + return url; + } + String poundUrl = getMapString(map, "poundUrl"); + if (StringUtil.isNotBlank(poundUrl)) { + return poundUrl; + } + String carFrontUrl = getMapString(map, "carFrontUrl"); + if (StringUtil.isNotBlank(carFrontUrl)) { + return carFrontUrl; + } + String driverLicenseUrl = getMapString(map, "driverLicenseUrl"); + if (StringUtil.isNotBlank(driverLicenseUrl)) { + return driverLicenseUrl; + } + return getMapString(map, "vehicleLicenseUrl"); + } + + private void addTypedObjectKeys( + Map map, + List poundUrls, + List carFrontUrls, + List driverLicenseUrls, + List vehicleLicenseUrls) { + addMapString(map, "poundUrl", poundUrls); + addMapString(map, "carFrontUrl", carFrontUrls); + addMapString(map, "driverLicenseUrl", driverLicenseUrls); + addMapString(map, "vehicleLicenseUrl", vehicleLicenseUrls); + } + + private void addMapString(Map map, String key, List values) { + String value = getMapString(map, key); + if (StringUtil.isNotBlank(value)) { + values.add(value); + } + } + + private String getMapString(Map map, String key) { + Object value = map.get(key); + return value == null ? "" : String.valueOf(value); } @ApiLog("OCR识别-识别车辆运输凭证(知道类型)") diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/OCRConvertServiceImpl.java b/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/OCRConvertServiceImpl.java index 1f5fa9c..fe94531 100644 --- a/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/OCRConvertServiceImpl.java +++ b/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/OCRConvertServiceImpl.java @@ -37,12 +37,36 @@ public class OCRConvertServiceImpl implements IOCRConvertService { throw new ServiceException("图像obs key不能为空"); } CertificateBatchRecognitionDTO ocrParam = converter.dto2ocr(param); - // 转换文件链接 - List fileUrls = param.getObjectKeys().stream() + ocrParam.setPoundUrls(resolveFileUrls(param.getPoundUrls())); + ocrParam.setCarFrontUrls(resolveFileUrls(param.getCarFrontUrls())); + ocrParam.setDriverLicenseUrls(resolveFileUrls(param.getDriverLicenseUrls())); + ocrParam.setVehicleLicenseUrls(resolveFileUrls(param.getVehicleLicenseUrls())); + if (!hasTypedObjectKeys(param)) { + // 旧格式字符串数组仍按通用 urls 传递。 + List fileUrls = param.getObjectKeys().stream() + .map(this::resolveFileUrl) + .toList(); + ocrParam.setUrls(fileUrls); + } else { + ocrParam.setUrls(null); + } + return ocrService.recognitionTransportCertificate(ocrParam); + } + + private boolean hasTypedObjectKeys(FileCertificateBatchRecognitionDTO param) { + return CollectionUtil.isNotEmpty(param.getPoundUrls()) + || CollectionUtil.isNotEmpty(param.getCarFrontUrls()) + || CollectionUtil.isNotEmpty(param.getDriverLicenseUrls()) + || CollectionUtil.isNotEmpty(param.getVehicleLicenseUrls()); + } + + private List resolveFileUrls(List objectKeys) { + if (CollectionUtil.isEmpty(objectKeys)) { + return null; + } + return objectKeys.stream() .map(this::resolveFileUrl) .toList(); - ocrParam.setUrls(fileUrls); - return ocrService.recognitionTransportCertificate(ocrParam); } private String resolveFileUrl(String objectKey) { diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/MenuServiceImpl.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/MenuServiceImpl.java index e4058e4..e04feb2 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/MenuServiceImpl.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/MenuServiceImpl.java @@ -293,14 +293,19 @@ public class MenuServiceImpl extends ServiceImpl implements IM @Override public boolean submit(Menu menu) { boolean isNewMenu = menu.getId() == null; + Menu oldMenu = isNewMenu ? null : getById(menu.getId()); // 验证code和name唯一性 - LambdaQueryWrapper menuQueryWrapper = Wrappers.lambdaQuery() - .and(!isNewMenu, w -> w.ne(Menu::getId, menu.getId())) - .and(w -> w.eq(Menu::getCode, menu.getCode()) - .or(o -> o.eq(Menu::getName, menu.getName()).eq(Menu::getCategory, MENU_CATEGORY))); - - if (baseMapper.selectCount(menuQueryWrapper) > 0L) { + boolean codeChanged = isNewMenu || oldMenu == null || !Objects.equals(oldMenu.getCode(), menu.getCode()); + boolean nameChanged = isNewMenu || oldMenu == null || !Objects.equals(oldMenu.getName(), menu.getName()) || !Objects.equals(oldMenu.getCategory(), menu.getCategory()); + boolean duplicateCode = codeChanged && Func.isNotBlank(menu.getCode()) && baseMapper.selectCount(Wrappers.lambdaQuery() + .ne(!isNewMenu, Menu::getId, menu.getId()) + .eq(Menu::getCode, menu.getCode())) > 0L; + boolean duplicateName = nameChanged && Objects.equals(menu.getCategory(), MENU_CATEGORY) && Func.isNotBlank(menu.getName()) && baseMapper.selectCount(Wrappers.lambdaQuery() + .ne(!isNewMenu, Menu::getId, menu.getId()) + .eq(Menu::getName, menu.getName()) + .eq(Menu::getCategory, MENU_CATEGORY)) > 0L; + if (duplicateCode || duplicateName) { throw new ServiceException("菜单名或编号已存在!"); } diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/LoadingManageController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/LoadingManageController.java new file mode 100644 index 0000000..5e8dc1d --- /dev/null +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/LoadingManageController.java @@ -0,0 +1,128 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + */ +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.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; +import org.springblade.core.mp.support.Query; +import org.springblade.core.secure.annotation.PreAuth; +import org.springblade.core.tool.api.R; +import org.springblade.core.tool.utils.DateUtil; +import org.springblade.transport.excel.LoadingManageExcel; +import org.springblade.transport.pojo.entity.LoadingManage; +import org.springblade.transport.pojo.vo.BusinessRemoveResultVO; +import org.springblade.transport.pojo.vo.LoadingManageVO; +import org.springblade.transport.service.ILoadingManageService; +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; + +/** + * 配载管理 控制器 + * + * @author Chill + */ +@RestController +@AllArgsConstructor +@PreAuth(menu = "loading_manage") +@RequestMapping("/loading-manage") +@Tag(name = "配载管理", description = "配载管理") +public class LoadingManageController extends BladeController { + + private final ILoadingManageService loadingManageService; + + @GetMapping("/detail") + @ApiOperationSupport(order = 1) + @Operation(summary = "详情", description = "传入id") + public R detail(@Parameter(description = "主键", required = true) @RequestParam Long id) { + return R.data(loadingManageService.detail(id)); + } + + @GetMapping("/list") + @ApiOperationSupport(order = 2) + @Operation(summary = "分页", description = "传入loadingManage") + public R> list(LoadingManageVO loadingManage, Query query) { + return R.data(loadingManageService.selectLoadingManagePage(Condition.getPage(query), loadingManage)); + } + + @PostMapping("/save-draft") + @ApiOperationSupport(order = 3) + @Operation(summary = "保存草稿", description = "传入loadingManage") + public R saveDraft(@RequestBody LoadingManage loadingManage) { + loadingManageService.saveDraft(loadingManage); + return R.data(loadingManageService.detail(loadingManage.getId())); + } + + @PostMapping("/submit") + @ApiOperationSupport(order = 4) + @Operation(summary = "新增或修改", description = "传入loadingManage") + public R submit(@RequestBody LoadingManage loadingManage) { + return R.status(loadingManageService.submit(loadingManage)); + } + + @PostMapping("/remove") + @ApiOperationSupport(order = 5) + @Operation(summary = "逻辑删除", description = "传入ids") + public R remove(@Parameter(description = "主键集合", required = true) @RequestParam String ids) { + return R.data(loadingManageService.removeLoadingManage(ids)); + } + + @GetMapping("/export-loading-manage") + @ApiOperationSupport(order = 6) + @Operation(summary = "导出配载管理") + public void exportLoadingManage(LoadingManageVO loadingManage, @RequestParam(required = false) String ids, HttpServletResponse response) { + List list = loadingManageService.exportLoadingManage(loadingManage, ids); + ExcelUtil.export(response, "配载管理" + DateUtil.time(), "配载管理", list, LoadingManageExcel.class); + } + + @PostMapping("/copy") + @ApiOperationSupport(order = 7) + @Operation(summary = "复制", description = "传入id") + public R copy(@Parameter(description = "主键", required = true) @RequestParam Long id) { + return R.data(loadingManageService.copy(id)); + } + + @PostMapping("/reassign") + @ApiOperationSupport(order = 8) + @Operation(summary = "重新派单", description = "传入loadingManage") + public R reassign(@RequestBody LoadingManage loadingManage) { + return R.status(loadingManageService.reassign(loadingManage)); + } + + @PostMapping("/cancel") + @ApiOperationSupport(order = 9) + @Operation(summary = "取消", description = "传入id") + public R cancel(@Parameter(description = "主键", required = true) @RequestParam Long id) { + return R.status(loadingManageService.cancel(id)); + } + + @PostMapping("/complete") + @ApiOperationSupport(order = 10) + @Operation(summary = "完成", description = "传入id") + public R complete(@Parameter(description = "主键", required = true) @RequestParam Long id) { + return R.status(loadingManageService.complete(id)); + } + + @PostMapping("/batch-complete") + @ApiOperationSupport(order = 11) + @Operation(summary = "批量完成", description = "传入ids") + public R batchComplete(@Parameter(description = "主键集合", required = true) @RequestParam String ids) { + return R.data(loadingManageService.batchComplete(ids)); + } + +} diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/WaybillController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/WaybillController.java index 5fec4a9..142c317 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/WaybillController.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/WaybillController.java @@ -29,6 +29,7 @@ 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.common.excel.ImportFailureExcelUtil; import org.springblade.core.boot.ctrl.BladeController; import org.springblade.core.excel.util.ExcelUtil; import org.springblade.core.mp.support.Condition; @@ -48,6 +49,7 @@ 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 org.springframework.web.multipart.MultipartFile; import java.util.ArrayList; import java.util.List; @@ -102,36 +104,55 @@ public class WaybillController extends BladeController { ExcelUtil.export(response, "运单管理" + DateUtil.time(), "运单管理", list, WaybillExcel.class); } - @PostMapping("/copy") + @PostMapping("/import-waybill-manage") @ApiOperationSupport(order = 6) + @Operation(summary = "导入运单管理", description = "传入excel") + public R importWaybill(MultipartFile file, HttpServletResponse response) { + List failureList = waybillService.importWaybill(ExcelUtil.read(file, WaybillExcel.class)); + if (Func.isNotEmpty(failureList)) { + ImportFailureExcelUtil.export(response, "运单管理导入失败明细" + DateUtil.time(), "导入失败明细", failureList, WaybillExcel.class); + return null; + } + return R.success("导入数据成功"); + } + + @GetMapping("/export-template") + @ApiOperationSupport(order = 7) + @Operation(summary = "导出模板") + public void exportTemplate(HttpServletResponse response) { + ExcelUtil.export(response, "运单管理模板", "运单管理导入模板", new ArrayList(), WaybillExcel.class); + } + + @PostMapping("/copy") + @ApiOperationSupport(order = 8) @Operation(summary = "复制", description = "传入id") public R copy(@Parameter(description = "主键", required = true) @RequestParam Long id) { return R.data(waybillService.copy(id)); } @PostMapping("/cancel") - @ApiOperationSupport(order = 7) + @ApiOperationSupport(order = 9) @Operation(summary = "取消", description = "传入id") public R cancel(@Parameter(description = "主键", required = true) @RequestParam Long id) { return R.status(waybillService.cancel(id)); } @PostMapping("/reassign") - @ApiOperationSupport(order = 8) + @ApiOperationSupport(order = 10) @Operation(summary = "重新派单", description = "传入id") public R reassign(@Parameter(description = "主键", required = true) @RequestParam Long id) { return R.status(waybillService.reassign(id)); } @PostMapping("/complete") - @ApiOperationSupport(order = 9) + @ApiOperationSupport(order = 11) @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 = 10) + @ApiOperationSupport(order = 12) @Operation(summary = "批量完成", description = "传入ids") public R batchComplete(@Parameter(description = "主键集合", required = true) @RequestParam String ids) { return R.data(waybillService.batchComplete(ids)); diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/LoadingManageExcel.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/LoadingManageExcel.java new file mode 100644 index 0000000..dfcb61b --- /dev/null +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/LoadingManageExcel.java @@ -0,0 +1,101 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + */ +package org.springblade.transport.excel; + +import cn.idev.excel.annotation.ExcelIgnore; +import cn.idev.excel.annotation.ExcelProperty; +import cn.idev.excel.annotation.write.style.ColumnWidth; +import cn.idev.excel.annotation.write.style.ContentRowHeight; +import cn.idev.excel.annotation.write.style.HeadRowHeight; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDate; +import java.time.LocalDateTime; + +/** + * 配载管理 Excel + * + * @author Chill + */ +@Data +@ColumnWidth(22) +@HeadRowHeight(20) +@ContentRowHeight(18) +public class LoadingManageExcel implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @ExcelProperty("配载单号") + private String loadingNo; + @ExcelProperty("配载子单号") + private String loadingSubNos; + @ExcelProperty("项目名称") + private String projectName; + @ExcelProperty("客户名称") + private String customerName; + @ExcelProperty("运输类型") + private String transportType; + @ExcelProperty("货物类型") + private String cargoType; + @ExcelProperty("货物名称") + private String cargoName; + @ExcelProperty("车/船/航班/班列") + private String vehicleNo; + @ExcelProperty("司机") + private String driverName; + @ExcelProperty("联系电话") + private String driverPhone; + @ExcelProperty("承运类型") + private String carrierType; + @ExcelProperty("承运商") + private String carrierName; + @ExcelProperty("发货地址") + private String departureAddress; + @ExcelProperty("途经地") + private String transitAddress; + @ExcelProperty("到货地") + private String arrivalAddress; + @ExcelProperty("原始单号") + private String originalNo; + @ExcelProperty("数据来源") + private String dataSource; + @ExcelProperty("开始日期") + private LocalDate startDate; + @ExcelProperty("结束日期") + private LocalDate endDate; + @ExcelProperty("计划名称") + private String planName; + @ExcelProperty("运单批次号") + private String batchNo; + @ExcelProperty("当前过程节点") + private String currentProcessNode; + @ExcelProperty("里程") + private String mileage; + @ExcelProperty("预计发货日期") + private LocalDate estimatedStartDate; + @ExcelProperty("预计完成日期") + private LocalDate estimatedEndDate; + @ExcelProperty("任务备注") + private String taskRemark; + @ExcelProperty("业务状态") + private String businessStatus; + @ExcelProperty("所属组织") + private String deptName; + @ExcelProperty("创建人") + private String createUserName; + @ExcelProperty("更新人") + private String updateUserName; + @ExcelProperty("创建时间") + private LocalDateTime createTime; + @ExcelProperty("更新时间") + private LocalDateTime updateTime; + + @ExcelIgnore + private String errorMessage; + +} diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/WaybillExcel.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/WaybillExcel.java index 602a51a..5f9e605 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/WaybillExcel.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/WaybillExcel.java @@ -23,6 +23,7 @@ package org.springblade.transport.excel; import cn.idev.excel.annotation.ExcelProperty; +import cn.idev.excel.annotation.ExcelIgnore; import cn.idev.excel.annotation.write.style.ColumnWidth; import cn.idev.excel.annotation.write.style.ContentRowHeight; import cn.idev.excel.annotation.write.style.HeadRowHeight; @@ -30,6 +31,7 @@ import lombok.Data; import java.io.Serial; import java.io.Serializable; +import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; @@ -61,16 +63,62 @@ public class WaybillExcel implements Serializable { private String cargoName; @ExcelProperty("货物类型") private String cargoType; + @ExcelProperty("规格") + private String specification; + @ExcelProperty("型号") + private String model; + @ExcelProperty("数量") + private BigDecimal quantity; + @ExcelProperty("数量单位") + private String quantityUnit; + @ExcelProperty("发货地") + private String departureName; @ExcelProperty("发货地址") private String departureAddress; + @ExcelProperty("发货联系人") + private String departureContact; + @ExcelProperty("发货联系方式") + private String departurePhone; + @ExcelProperty("收货地") + private String arrivalName; @ExcelProperty("收货地址") private String arrivalAddress; + @ExcelProperty("收货联系人") + private String arrivalContact; + @ExcelProperty("收货联系方式") + private String arrivalPhone; + @ExcelProperty("任务录入模式") + private String taskEntryMode; + @ExcelProperty("承运类型") + private String carrierType; @ExcelProperty("承运商名称") private String carrierName; @ExcelProperty("司机姓名") private String driverName; + @ExcelProperty("司机手机号") + private String driverPhone; @ExcelProperty("车/船/航班/班列号") private String vehicleNo; + @ExcelProperty("挂车车牌号") + private String trailerVehicleNo; + @ExcelProperty("押运人") + private String escortName; + @ExcelProperty("押运人手机号") + private String escortPhone; + @ExcelProperty("里程(km)") + private BigDecimal mileage; + @ExcelProperty("预计发货时间") + private LocalDateTime estimatedStartTime; + @ExcelProperty("预计完成时间") + private LocalDateTime estimatedEndTime; + @ExcelProperty("单价") + private BigDecimal unitPrice; + @ExcelProperty("计价单位") + private String priceUnit; + @ExcelProperty("其他费用合计") + private BigDecimal otherFeeTotal; + @ExcelProperty("任务备注") + private String taskRemark; @ExcelProperty("原始单号") private String originalNo; @ExcelProperty("业务状态") @@ -106,4 +154,7 @@ public class WaybillExcel implements Serializable { @ExcelProperty("更新时间") private LocalDateTime updateTime; + @ExcelIgnore + private String errorMessage; + } diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/LoadingManageMapper.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/LoadingManageMapper.java new file mode 100644 index 0000000..58ac156 --- /dev/null +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/LoadingManageMapper.java @@ -0,0 +1,17 @@ +/** + * 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.springblade.transport.pojo.entity.LoadingManage; + +/** + * 配载管理 Mapper 接口 + * + * @author Chill + */ +public interface LoadingManageMapper extends BaseMapper { + +} diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/ILoadingManageService.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/ILoadingManageService.java new file mode 100644 index 0000000..e6ac98c --- /dev/null +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/ILoadingManageService.java @@ -0,0 +1,45 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + */ +package org.springblade.transport.service; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import org.springblade.core.mp.base.BaseService; +import org.springblade.transport.excel.LoadingManageExcel; +import org.springblade.transport.pojo.entity.LoadingManage; +import org.springblade.transport.pojo.vo.BusinessRemoveResultVO; +import org.springblade.transport.pojo.vo.LoadingManageVO; + +import java.util.List; + +/** + * 配载管理 服务类 + * + * @author Chill + */ +public interface ILoadingManageService extends BaseService { + + IPage selectLoadingManagePage(IPage page, LoadingManageVO loadingManage); + + LoadingManageVO detail(Long id); + + boolean saveDraft(LoadingManage loadingManage); + + boolean submit(LoadingManage loadingManage); + + BusinessRemoveResultVO removeLoadingManage(String ids); + + List exportLoadingManage(LoadingManageVO loadingManage, String ids); + + LoadingManageVO copy(Long id); + + boolean reassign(LoadingManage loadingManage); + + boolean cancel(Long id); + + boolean complete(Long id); + + BusinessRemoveResultVO batchComplete(String ids); + +} diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IWaybillService.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IWaybillService.java index 12a4be9..8fa5ae4 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IWaybillService.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IWaybillService.java @@ -43,6 +43,7 @@ public interface IWaybillService extends BaseService { boolean submit(Waybill waybill); BusinessRemoveResultVO removeWaybill(String ids); List exportWaybill(WaybillVO waybill, String ids); + List importWaybill(List data); WaybillVO copy(Long id); boolean cancel(Long id); boolean reassign(Long id); diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/LoadingManageServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/LoadingManageServiceImpl.java new file mode 100644 index 0000000..6ca498d --- /dev/null +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/LoadingManageServiceImpl.java @@ -0,0 +1,600 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + */ +package org.springblade.transport.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import jakarta.annotation.Resource; +import org.springblade.core.log.exception.ServiceException; +import org.springblade.core.mp.base.BaseServiceImpl; +import org.springblade.core.tool.jackson.JsonUtil; +import org.springblade.core.tool.utils.BeanUtil; +import org.springblade.core.tool.utils.Func; +import org.springblade.system.cache.UserCache; +import org.springblade.system.pojo.entity.Dept; +import org.springblade.transport.excel.LoadingManageExcel; +import org.springblade.transport.mapper.LoadingManageMapper; +import org.springblade.transport.mapper.WaybillMapper; +import org.springblade.transport.pojo.entity.LoadingManage; +import org.springblade.transport.pojo.entity.Waybill; +import org.springblade.transport.pojo.vo.BusinessRemoveResultVO; +import org.springblade.transport.pojo.vo.LoadingManageVO; +import org.springblade.transport.service.ILoadingManageService; +import org.springblade.transport.support.TransportBusinessSupport; +import org.springblade.transport.wrapper.LoadingManageWrapper; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * 配载管理 服务实现类 + * + * @author Chill + */ +@Service +public class LoadingManageServiceImpl extends BaseServiceImpl implements ILoadingManageService { + + private static final String STATUS_DRAFT = "draft"; + private static final String STATUS_PENDING = "pending"; + private static final String STATUS_RUNNING = "running"; + private static final String STATUS_COMPLETED = "completed"; + private static final String STATUS_CANCELLED = "cancelled"; + private static final String CARRIER_SELF = "自运"; + private static final String CARRIER_COMPANY = "承运商"; + private static final String CARRIER_PLATFORM = "网签平台"; + + @Resource + private WaybillMapper waybillMapper; + + @Override + public IPage selectLoadingManagePage(IPage page, LoadingManageVO loadingManage) { + IPage entityPage = page(page, buildQuery(loadingManage)); + IPage voPage = LoadingManageWrapper.build().pageVO(entityPage); + voPage.getRecords().forEach(this::fillReadonly); + return voPage; + } + + @Override + public LoadingManageVO detail(Long id) { + LoadingManageVO loadingManageVO = LoadingManageWrapper.build().entityVO(loadExists(id)); + fillReadonly(loadingManageVO); + return loadingManageVO; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public boolean saveDraft(LoadingManage loadingManage) { + prepare(loadingManage); + validate(loadingManage, true); + if (Func.isNotEmpty(loadingManage.getId())) { + LoadingManage oldRecord = loadEditable(loadingManage.getId(), true); + loadingManage.setLoadingNo(oldRecord.getLoadingNo()); + loadingManage.setDeptId(oldRecord.getDeptId()); + loadingManage.setDeptName(oldRecord.getDeptName()); + } + prepareCreateOrUpdate(loadingManage); + loadingManage.setBusinessStatus(STATUS_DRAFT); + if (Func.isEmpty(loadingManage.getLoadingNo())) { + loadingManage.setLoadingNo(nextCode()); + } + return saveOrUpdate(loadingManage); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public boolean submit(LoadingManage loadingManage) { + prepare(loadingManage); + validate(loadingManage, false); + List oldWaybillIds = new ArrayList<>(); + if (Func.isNotEmpty(loadingManage.getId())) { + LoadingManage oldRecord = loadEditable(loadingManage.getId(), true); + oldWaybillIds = waybillIds(oldRecord.getWaybillIdsJson()); + loadingManage.setLoadingNo(oldRecord.getLoadingNo()); + loadingManage.setDeptId(oldRecord.getDeptId()); + loadingManage.setDeptName(oldRecord.getDeptName()); + } + prepareCreateOrUpdate(loadingManage); + loadingManage.setBusinessStatus(STATUS_PENDING); + if (Func.isEmpty(loadingManage.getLoadingNo())) { + loadingManage.setLoadingNo(nextCode()); + } + validateWaybillsAvailable(loadingManage, waybillIds(loadingManage.getWaybillIdsJson())); + boolean result = saveOrUpdate(loadingManage); + syncAssociatedWaybills(loadingManage, STATUS_PENDING, oldWaybillIds); + return result; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public BusinessRemoveResultVO removeLoadingManage(String ids) { + List idList = Func.toLongList(ids); + if (Func.isEmpty(idList)) { + throw new ServiceException("请选择需要删除的数据"); + } + BusinessRemoveResultVO result = new BusinessRemoveResultVO(); + List deleteIdList = new ArrayList<>(); + for (LoadingManage loadingManage : listByIds(idList)) { + TransportBusinessSupport.assertCurrentDept(loadingManage.getDeptId(), "配载管理"); + if (!Objects.equals(loadingManage.getBusinessStatus(), STATUS_DRAFT)) { + result.setSkippedCount(result.getSkippedCount() + 1); + result.getSkippedCodes().add(loadingManage.getLoadingNo()); + continue; + } + deleteIdList.add(loadingManage.getId()); + } + if (Func.isNotEmpty(deleteIdList)) { + deleteLogic(deleteIdList); + } + result.setSuccessCount(deleteIdList.size()); + return result; + } + + @Override + public List exportLoadingManage(LoadingManageVO loadingManage, String ids) { + LambdaQueryWrapper queryWrapper = buildQuery(loadingManage); + if (Func.isNotEmpty(ids)) { + queryWrapper.in(LoadingManage::getId, Func.toLongList(ids)); + } + return list(queryWrapper).stream().map(record -> { + LoadingManageExcel excel = new LoadingManageExcel(); + BeanUtil.copyProperties(record, excel); + excel.setCreateUserName(UserCache.getUserRealName(record.getCreateUser())); + excel.setUpdateUserName(UserCache.getUserRealName(record.getUpdateUser())); + excel.setMileage(formatMileage(record.getMileage())); + return excel; + }).toList(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public LoadingManageVO copy(Long id) { + LoadingManage source = loadEditable(id, true); + LoadingManage target = new LoadingManage(); + target.setLoadingSubNos(source.getLoadingSubNos()); + target.setWaybillIdsJson(source.getWaybillIdsJson()); + target.setProjectId(source.getProjectId()); + target.setProjectName(source.getProjectName()); + target.setCustomerName(source.getCustomerName()); + target.setTransportType(source.getTransportType()); + target.setCargoType(source.getCargoType()); + target.setCargoName(source.getCargoName()); + target.setVehicleNo(source.getVehicleNo()); + target.setTrailerVehicleNo(source.getTrailerVehicleNo()); + target.setDriverName(source.getDriverName()); + target.setDriverPhone(source.getDriverPhone()); + target.setEscortName(source.getEscortName()); + target.setEscortPhone(source.getEscortPhone()); + target.setCarrierType(source.getCarrierType()); + target.setCarrierName(source.getCarrierName()); + target.setDepartureAddress(source.getDepartureAddress()); + target.setTransitAddress(source.getTransitAddress()); + target.setArrivalAddress(source.getArrivalAddress()); + target.setOriginalNo(source.getOriginalNo()); + target.setDataSource(source.getDataSource()); + target.setStartDate(source.getStartDate()); + target.setEndDate(source.getEndDate()); + target.setPlanName(source.getPlanName()); + target.setBatchNo(source.getBatchNo()); + target.setCurrentProcessNode(source.getCurrentProcessNode()); + target.setMileage(source.getMileage()); + target.setEstimatedStartDate(source.getEstimatedStartDate()); + target.setEstimatedEndDate(source.getEstimatedEndDate()); + target.setTaskRemark(source.getTaskRemark()); + target.setGoodsJson(source.getGoodsJson()); + target.setRouteJson(source.getRouteJson()); + target.setTaskInfoJson(source.getTaskInfoJson()); + target.setDeptId(source.getDeptId()); + target.setDeptName(source.getDeptName()); + target.setBusinessStatus(STATUS_DRAFT); + target.setLoadingNo(nextCode()); + prepare(target); + validate(target, true); + save(target); + return detail(target.getId()); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public boolean reassign(LoadingManage loadingManage) { + LoadingManage oldRecord = loadEditable(loadingManage.getId(), true); + if (!Objects.equals(oldRecord.getBusinessStatus(), STATUS_PENDING)) { + throw new ServiceException("仅待执行状态允许重新派单"); + } + prepare(loadingManage); + validate(loadingManage, false); + List oldWaybillIds = waybillIds(oldRecord.getWaybillIdsJson()); + loadingManage.setLoadingNo(oldRecord.getLoadingNo()); + loadingManage.setDeptId(oldRecord.getDeptId()); + loadingManage.setDeptName(oldRecord.getDeptName()); + loadingManage.setBusinessStatus(STATUS_PENDING); + validateWaybillsAvailable(loadingManage, waybillIds(loadingManage.getWaybillIdsJson())); + boolean result = updateById(loadingManage); + syncAssociatedWaybills(loadingManage, STATUS_PENDING, oldWaybillIds); + return result; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public boolean cancel(Long id) { + LoadingManage loadingManage = loadEditable(id, true); + if (!Objects.equals(loadingManage.getBusinessStatus(), STATUS_PENDING)) { + throw new ServiceException("仅待执行状态允许取消"); + } + loadingManage.setBusinessStatus(STATUS_CANCELLED); + boolean result = updateById(loadingManage); + releaseAssociatedWaybills(waybillIds(loadingManage.getWaybillIdsJson()), STATUS_PENDING); + return result; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public boolean complete(Long id) { + LoadingManage loadingManage = loadEditable(id, true); + if (!Objects.equals(loadingManage.getBusinessStatus(), STATUS_RUNNING)) { + throw new ServiceException("仅进行中状态允许完成"); + } + loadingManage.setBusinessStatus(STATUS_COMPLETED); + boolean result = updateById(loadingManage); + syncAssociatedWaybills(loadingManage, STATUS_COMPLETED, new ArrayList<>()); + return result; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public BusinessRemoveResultVO batchComplete(String ids) { + List idList = Func.toLongList(ids); + if (Func.isEmpty(idList)) { + throw new ServiceException("请选择需要完成的数据"); + } + BusinessRemoveResultVO result = new BusinessRemoveResultVO(); + for (LoadingManage loadingManage : listByIds(idList)) { + try { + complete(loadingManage.getId()); + result.setSuccessCount(result.getSuccessCount() + 1); + } catch (Exception exception) { + result.setSkippedCount(result.getSkippedCount() + 1); + result.getSkippedCodes().add(loadingManage.getLoadingNo()); + } + } + return result; + } + + private LambdaQueryWrapper buildQuery(LoadingManageVO loadingManage) { + TransportBusinessSupport.validateAllDept(loadingManage.getAllDept(), "配载管理"); + LambdaQueryWrapper queryWrapper = Wrappers.lambdaQuery().eq(LoadingManage::getIsDeleted, 0); + if (!Objects.equals(loadingManage.getAllDept(), 1)) { + queryWrapper.eq(LoadingManage::getDeptId, TransportBusinessSupport.currentDeptId("配载管理")); + } else if (Func.isNotEmpty(loadingManage.getDeptId())) { + queryWrapper.eq(LoadingManage::getDeptId, loadingManage.getDeptId()); + } + if (Func.isNotEmpty(loadingManage.getLoadingNo())) { + queryWrapper.eq(LoadingManage::getLoadingNo, loadingManage.getLoadingNo()); + } + if (Func.isNotEmpty(loadingManage.getWaybillNo())) { + queryWrapper.like(LoadingManage::getLoadingSubNos, loadingManage.getWaybillNo()); + } + if (Func.isNotEmpty(loadingManage.getProjectName())) { + queryWrapper.like(LoadingManage::getProjectName, loadingManage.getProjectName()); + } + if (Func.isNotEmpty(loadingManage.getCustomerName())) { + queryWrapper.like(LoadingManage::getCustomerName, loadingManage.getCustomerName()); + } + if (Func.isNotEmpty(loadingManage.getTransportType())) { + queryWrapper.eq(LoadingManage::getTransportType, loadingManage.getTransportType()); + } + if (Func.isNotEmpty(loadingManage.getCargoType())) { + queryWrapper.like(LoadingManage::getCargoType, loadingManage.getCargoType()); + } + if (Func.isNotEmpty(loadingManage.getDepartureAddress())) { + queryWrapper.like(LoadingManage::getDepartureAddress, loadingManage.getDepartureAddress()); + } + if (Func.isNotEmpty(loadingManage.getArrivalAddress())) { + queryWrapper.like(LoadingManage::getArrivalAddress, loadingManage.getArrivalAddress()); + } + if (Func.isNotEmpty(loadingManage.getCargoName())) { + queryWrapper.like(LoadingManage::getCargoName, loadingManage.getCargoName()); + } + if (Func.isNotEmpty(loadingManage.getDriverName())) { + queryWrapper.like(LoadingManage::getDriverName, loadingManage.getDriverName()); + } + if (Func.isNotEmpty(loadingManage.getVehicleNo())) { + queryWrapper.like(LoadingManage::getVehicleNo, loadingManage.getVehicleNo()); + } + if (Func.isNotEmpty(loadingManage.getOriginalNo())) { + queryWrapper.like(LoadingManage::getOriginalNo, loadingManage.getOriginalNo()); + } + if (Func.isNotEmpty(loadingManage.getCarrierName())) { + queryWrapper.like(LoadingManage::getCarrierName, loadingManage.getCarrierName()); + } + if (Func.isNotEmpty(loadingManage.getDataSource())) { + queryWrapper.eq(LoadingManage::getDataSource, loadingManage.getDataSource()); + } + if (Func.isNotEmpty(loadingManage.getStartDateStart())) { + queryWrapper.ge(LoadingManage::getStartDate, loadingManage.getStartDateStart()); + } + if (Func.isNotEmpty(loadingManage.getStartDateEnd())) { + queryWrapper.le(LoadingManage::getStartDate, loadingManage.getStartDateEnd()); + } + if (Func.isNotEmpty(loadingManage.getEndDateStart())) { + queryWrapper.ge(LoadingManage::getEndDate, loadingManage.getEndDateStart()); + } + if (Func.isNotEmpty(loadingManage.getEndDateEnd())) { + queryWrapper.le(LoadingManage::getEndDate, loadingManage.getEndDateEnd()); + } + if (Func.isNotEmpty(loadingManage.getBusinessStatus())) { + queryWrapper.eq(LoadingManage::getBusinessStatus, loadingManage.getBusinessStatus()); + } + if (Func.isNotEmpty(loadingManage.getPlanName())) { + queryWrapper.like(LoadingManage::getPlanName, loadingManage.getPlanName()); + } + if (Func.isNotEmpty(loadingManage.getBatchNo())) { + queryWrapper.like(LoadingManage::getBatchNo, loadingManage.getBatchNo()); + } + if (Func.isNotEmpty(loadingManage.getCurrentProcessNode())) { + queryWrapper.like(LoadingManage::getCurrentProcessNode, loadingManage.getCurrentProcessNode()); + } + queryWrapper.orderByDesc(LoadingManage::getCreateTime); + return queryWrapper; + } + + private void prepare(LoadingManage loadingManage) { + loadingManage.setLoadingNo(TransportBusinessSupport.trimToNull(loadingManage.getLoadingNo())); + loadingManage.setLoadingSubNos(TransportBusinessSupport.trimToNull(loadingManage.getLoadingSubNos())); + loadingManage.setWaybillIdsJson(TransportBusinessSupport.trimToNull(loadingManage.getWaybillIdsJson())); + loadingManage.setProjectName(TransportBusinessSupport.trimToNull(loadingManage.getProjectName())); + loadingManage.setCustomerName(TransportBusinessSupport.trimToNull(loadingManage.getCustomerName())); + loadingManage.setTransportType(TransportBusinessSupport.trimToNull(loadingManage.getTransportType())); + loadingManage.setCargoType(TransportBusinessSupport.trimToNull(loadingManage.getCargoType())); + loadingManage.setCargoName(TransportBusinessSupport.trimToNull(loadingManage.getCargoName())); + loadingManage.setVehicleNo(TransportBusinessSupport.trimToNull(loadingManage.getVehicleNo())); + loadingManage.setTrailerVehicleNo(TransportBusinessSupport.trimToNull(loadingManage.getTrailerVehicleNo())); + loadingManage.setDriverName(TransportBusinessSupport.trimToNull(loadingManage.getDriverName())); + loadingManage.setDriverPhone(TransportBusinessSupport.trimToNull(loadingManage.getDriverPhone())); + loadingManage.setEscortName(TransportBusinessSupport.trimToNull(loadingManage.getEscortName())); + loadingManage.setEscortPhone(TransportBusinessSupport.trimToNull(loadingManage.getEscortPhone())); + loadingManage.setCarrierType(TransportBusinessSupport.trimToNull(loadingManage.getCarrierType())); + loadingManage.setCarrierName(TransportBusinessSupport.trimToNull(loadingManage.getCarrierName())); + loadingManage.setDepartureAddress(TransportBusinessSupport.trimToNull(loadingManage.getDepartureAddress())); + loadingManage.setTransitAddress(TransportBusinessSupport.trimToNull(loadingManage.getTransitAddress())); + loadingManage.setArrivalAddress(TransportBusinessSupport.trimToNull(loadingManage.getArrivalAddress())); + loadingManage.setOriginalNo(TransportBusinessSupport.trimToNull(loadingManage.getOriginalNo())); + loadingManage.setDataSource(TransportBusinessSupport.trimToNull(loadingManage.getDataSource())); + loadingManage.setPlanName(TransportBusinessSupport.trimToNull(loadingManage.getPlanName())); + loadingManage.setBatchNo(TransportBusinessSupport.trimToNull(loadingManage.getBatchNo())); + loadingManage.setCurrentProcessNode(TransportBusinessSupport.trimToNull(loadingManage.getCurrentProcessNode())); + loadingManage.setTaskRemark(TransportBusinessSupport.trimToNull(loadingManage.getTaskRemark())); + loadingManage.setGoodsJson(TransportBusinessSupport.trimToNull(loadingManage.getGoodsJson())); + loadingManage.setRouteJson(TransportBusinessSupport.trimToNull(loadingManage.getRouteJson())); + loadingManage.setTaskInfoJson(TransportBusinessSupport.trimToNull(loadingManage.getTaskInfoJson())); + loadingManage.setDeptName(TransportBusinessSupport.trimToNull(loadingManage.getDeptName())); + if (Func.isEmpty(loadingManage.getDeptId())) { + Dept dept = TransportBusinessSupport.currentDept("配载管理"); + loadingManage.setDeptId(dept.getId()); + loadingManage.setDeptName(dept.getDeptName()); + } + if (Func.isEmpty(loadingManage.getBusinessStatus())) { + loadingManage.setBusinessStatus(STATUS_DRAFT); + } + } + + private void validate(LoadingManage loadingManage, boolean draftMode) { + TransportBusinessSupport.validateRequired(loadingManage.getLoadingSubNos(), "配载子单号不能为空"); + TransportBusinessSupport.validateRequired(loadingManage.getTransportType(), "运输类型不能为空"); + if (draftMode) { + validateCommon(loadingManage); + return; + } + TransportBusinessSupport.validateRequired(loadingManage.getCarrierType(), "承运类型不能为空"); + TransportBusinessSupport.validateRequired(loadingManage.getVehicleNo(), "车/船/航班/班列不能为空"); + TransportBusinessSupport.validateRequired(loadingManage.getDepartureAddress(), "发货地不能为空"); + TransportBusinessSupport.validateRequired(loadingManage.getArrivalAddress(), "到货地不能为空"); + if (Objects.equals(loadingManage.getCarrierType(), CARRIER_SELF)) { + TransportBusinessSupport.validateRequired(loadingManage.getDriverName(), "司机不能为空"); + TransportBusinessSupport.validateRequired(loadingManage.getDriverPhone(), "联系电话不能为空"); + } else if (Objects.equals(loadingManage.getCarrierType(), CARRIER_COMPANY) || Objects.equals(loadingManage.getCarrierType(), CARRIER_PLATFORM)) { + TransportBusinessSupport.validateRequired(loadingManage.getCarrierName(), "承运商不能为空"); + } + TransportBusinessSupport.validateLength(loadingManage.getLoadingNo(), 100, "配载单号不能超过100字"); + TransportBusinessSupport.validateLength(loadingManage.getLoadingSubNos(), 200, "配载子单号不能超过200字"); + TransportBusinessSupport.validateLength(loadingManage.getProjectName(), 100, "项目名称不能超过100字"); + TransportBusinessSupport.validateLength(loadingManage.getCustomerName(), 100, "客户名称不能超过100字"); + TransportBusinessSupport.validateLength(loadingManage.getTransportType(), 100, "运输类型不能超过100字"); + TransportBusinessSupport.validateLength(loadingManage.getCargoType(), 100, "货物类型不能超过100字"); + TransportBusinessSupport.validateLength(loadingManage.getCargoName(), 100, "货物名称不能超过100字"); + TransportBusinessSupport.validateLength(loadingManage.getVehicleNo(), 100, "车/船/航班/班列不能超过100字"); + TransportBusinessSupport.validateLength(loadingManage.getDriverName(), 100, "司机不能超过100字"); + TransportBusinessSupport.validateLength(loadingManage.getDriverPhone(), 50, "联系电话不能超过50字"); + TransportBusinessSupport.validateLength(loadingManage.getTrailerVehicleNo(), 100, "挂车车牌号不能超过100字"); + TransportBusinessSupport.validateLength(loadingManage.getEscortName(), 100, "押运人不能超过100字"); + TransportBusinessSupport.validateLength(loadingManage.getEscortPhone(), 50, "押运人手机号不能超过50字"); + TransportBusinessSupport.validateLength(loadingManage.getCarrierType(), 50, "承运类型不能超过50字"); + TransportBusinessSupport.validateLength(loadingManage.getCarrierName(), 100, "承运商不能超过100字"); + TransportBusinessSupport.validateLength(loadingManage.getDepartureAddress(), 100, "发货地不能超过100字"); + TransportBusinessSupport.validateLength(loadingManage.getTransitAddress(), 200, "途经地不能超过200字"); + TransportBusinessSupport.validateLength(loadingManage.getArrivalAddress(), 100, "到货地不能超过100字"); + TransportBusinessSupport.validateLength(loadingManage.getOriginalNo(), 100, "原始单号不能超过100字"); + TransportBusinessSupport.validateLength(loadingManage.getDataSource(), 100, "数据来源不能超过100字"); + TransportBusinessSupport.validateLength(loadingManage.getPlanName(), 100, "计划名称不能超过100字"); + TransportBusinessSupport.validateLength(loadingManage.getBatchNo(), 100, "运单批次号不能超过100字"); + TransportBusinessSupport.validateLength(loadingManage.getCurrentProcessNode(), 100, "过程节点不能超过100字"); + TransportBusinessSupport.validateLength(loadingManage.getWaybillIdsJson(), 4000, "运单ID集合不能超过4000字"); + TransportBusinessSupport.validateLength(loadingManage.getGoodsJson(), 8000, "货物信息不能超过8000字"); + TransportBusinessSupport.validateLength(loadingManage.getRouteJson(), 8000, "路线信息不能超过8000字"); + TransportBusinessSupport.validateLength(loadingManage.getTaskInfoJson(), 8000, "任务信息不能超过8000字"); + TransportBusinessSupport.validateLength(loadingManage.getTaskRemark(), 500, "任务备注不能超过500字"); + TransportBusinessSupport.validatePhone(loadingManage.getDriverPhone(), "联系电话格式不正确"); + TransportBusinessSupport.validatePhone(loadingManage.getEscortPhone(), "押运人手机号格式不正确"); + TransportBusinessSupport.validateNonNegative(loadingManage.getMileage(), "里程"); + TransportBusinessSupport.validateDateRange(loadingManage.getStartDate(), loadingManage.getEndDate(), "开始日期不能晚于结束日期"); + if (loadingManage.getEstimatedStartDate() != null && loadingManage.getEstimatedEndDate() != null + && loadingManage.getEstimatedEndDate().isBefore(loadingManage.getEstimatedStartDate())) { + throw new ServiceException("预计完成日期不能早于预计发货日期"); + } + } + + private void validateCommon(LoadingManage loadingManage) { + TransportBusinessSupport.validateLength(loadingManage.getLoadingNo(), 100, "配载单号不能超过100字"); + TransportBusinessSupport.validateLength(loadingManage.getLoadingSubNos(), 200, "配载子单号不能超过200字"); + TransportBusinessSupport.validateLength(loadingManage.getProjectName(), 100, "项目名称不能超过100字"); + TransportBusinessSupport.validateLength(loadingManage.getCustomerName(), 100, "客户名称不能超过100字"); + TransportBusinessSupport.validateLength(loadingManage.getTransportType(), 100, "运输类型不能超过100字"); + TransportBusinessSupport.validateLength(loadingManage.getWaybillIdsJson(), 4000, "运单ID集合不能超过4000字"); + TransportBusinessSupport.validateLength(loadingManage.getGoodsJson(), 8000, "货物信息不能超过8000字"); + TransportBusinessSupport.validateLength(loadingManage.getRouteJson(), 8000, "路线信息不能超过8000字"); + TransportBusinessSupport.validateLength(loadingManage.getTaskInfoJson(), 8000, "任务信息不能超过8000字"); + TransportBusinessSupport.validateLength(loadingManage.getTaskRemark(), 500, "任务备注不能超过500字"); + } + + private void validateWaybillsAvailable(LoadingManage loadingManage, List waybillIds) { + if (Func.isEmpty(waybillIds)) { + throw new ServiceException("请选择待配载运单"); + } + List waybills = waybillMapper.selectList(Wrappers.lambdaQuery() + .eq(Waybill::getIsDeleted, 0) + .in(Waybill::getId, waybillIds)); + if (waybills.size() != waybillIds.size()) { + throw new ServiceException("待配载运单不存在或已删除"); + } + for (Waybill waybill : waybills) { + TransportBusinessSupport.assertCurrentDept(waybill.getDeptId(), "运单管理"); + if (Func.isNotEmpty(waybill.getLoadingNo()) && !Objects.equals(waybill.getLoadingNo(), loadingManage.getLoadingNo())) { + throw new ServiceException("运单" + waybill.getWaybillNo() + "已关联其他配载单"); + } + if ((Objects.equals(waybill.getBusinessStatus(), STATUS_COMPLETED) || Objects.equals(waybill.getBusinessStatus(), STATUS_CANCELLED)) + && !Objects.equals(waybill.getLoadingNo(), loadingManage.getLoadingNo())) { + throw new ServiceException("运单" + waybill.getWaybillNo() + "当前状态不允许配载"); + } + } + } + + private void syncAssociatedWaybills(LoadingManage loadingManage, String status, List oldWaybillIds) { + List newWaybillIds = waybillIds(loadingManage.getWaybillIdsJson()); + if (Func.isNotEmpty(oldWaybillIds)) { + List removedIds = new ArrayList<>(oldWaybillIds); + removedIds.removeAll(newWaybillIds); + releaseAssociatedWaybills(removedIds, STATUS_PENDING); + } + if (Func.isEmpty(newWaybillIds)) { + return; + } + waybillMapper.update(null, Wrappers.lambdaUpdate() + .in(Waybill::getId, newWaybillIds) + .set(Waybill::getLoadingNo, loadingManage.getLoadingNo()) + .set(Waybill::getBusinessStatus, status) + .set(Waybill::getCarrierType, loadingManage.getCarrierType()) + .set(Waybill::getCarrierName, loadingManage.getCarrierName()) + .set(Waybill::getDriverName, loadingManage.getDriverName()) + .set(Waybill::getDriverPhone, loadingManage.getDriverPhone()) + .set(Waybill::getVehicleNo, loadingManage.getVehicleNo()) + .set(Waybill::getTrailerVehicleNo, loadingManage.getTrailerVehicleNo()) + .set(Waybill::getEscortName, loadingManage.getEscortName()) + .set(Waybill::getEscortPhone, loadingManage.getEscortPhone()) + .set(Waybill::getMileage, loadingManage.getMileage()) + .set(Waybill::getEstimatedStartTime, toDateTime(loadingManage.getEstimatedStartDate())) + .set(Waybill::getEstimatedEndTime, toDateTime(loadingManage.getEstimatedEndDate())) + .set(Waybill::getTaskRemark, loadingManage.getTaskRemark()) + .set(Waybill::getTaskInfoJson, loadingManage.getTaskInfoJson()) + .set(Waybill::getCurrentProcessNode, loadingManage.getCurrentProcessNode())); + } + + private void releaseAssociatedWaybills(List waybillIds, String status) { + if (Func.isEmpty(waybillIds)) { + return; + } + waybillMapper.update(null, Wrappers.lambdaUpdate() + .in(Waybill::getId, waybillIds) + .set(Waybill::getLoadingNo, null) + .set(Waybill::getBusinessStatus, status)); + } + + private List waybillIds(String value) { + if (Func.isEmpty(value)) { + return new ArrayList<>(); + } + try { + Object parsed = JsonUtil.parse(value, List.class); + if (parsed instanceof List list) { + return list.stream() + .filter(Objects::nonNull) + .map(item -> Long.valueOf(String.valueOf(item))) + .toList(); + } + } catch (Exception ignored) { + // 兼容历史逗号分隔存储。 + } + return Func.toLongList(value); + } + + private LocalDateTime toDateTime(LocalDate date) { + return date == null ? null : date.atStartOfDay(); + } + + private LoadingManage loadExists(Long id) { + if (Func.isEmpty(id)) { + throw new ServiceException("主键不能为空"); + } + LoadingManage loadingManage = getById(id); + if (Func.isEmpty(loadingManage) || Objects.equals(loadingManage.getIsDeleted(), 1)) { + throw new ServiceException("配载管理不存在"); + } + return loadingManage; + } + + private LoadingManage loadEditable(Long id, boolean checkDept) { + LoadingManage loadingManage = loadExists(id); + if (checkDept) { + TransportBusinessSupport.assertCurrentDept(loadingManage.getDeptId(), "配载管理"); + } + if (Objects.equals(loadingManage.getBusinessStatus(), STATUS_COMPLETED) || Objects.equals(loadingManage.getBusinessStatus(), STATUS_CANCELLED)) { + throw new ServiceException("当前状态不允许修改"); + } + return loadingManage; + } + + private void prepareCreateOrUpdate(LoadingManage loadingManage) { + if (Func.isEmpty(loadingManage.getId())) { + loadingManage.setLoadingNo(TransportBusinessSupport.trimToNull(loadingManage.getLoadingNo())); + } + } + + private void fillReadonly(LoadingManageVO loadingManageVO) { + // 保持与现有业务模块一致,包装类已完成主字段映射,这里预留兼容入口。 + } + + private String formatMileage(BigDecimal mileage) { + if (mileage == null) { + return null; + } + BigDecimal value = mileage.setScale(2, RoundingMode.HALF_UP).stripTrailingZeros(); + return value.toPlainString(); + } + + private synchronized String nextCode() { + String prefix = "PZ" + LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE) + "-"; + List latestList = list(Wrappers.lambdaQuery() + .select(LoadingManage::getLoadingNo) + .likeRight(LoadingManage::getLoadingNo, prefix) + .orderByDesc(LoadingManage::getLoadingNo) + .last("LIMIT 1")); + int next = 1; + if (Func.isNotEmpty(latestList) && Func.isNotEmpty(latestList.get(0).getLoadingNo())) { + String serial = latestList.get(0).getLoadingNo().substring(prefix.length()); + if (serial.chars().allMatch(Character::isDigit)) { + next = Integer.parseInt(serial) + 1; + } + } + return prefix + String.format("%03d", next); + } + +} diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillServiceImpl.java index 3c4a7be..8f2b9fa 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillServiceImpl.java @@ -27,6 +27,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage; 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.tool.jackson.JsonUtil; import org.springblade.core.tool.utils.BeanUtil; import org.springblade.core.tool.utils.Func; import org.springblade.system.cache.UserCache; @@ -45,7 +46,9 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.Objects; /** @@ -125,6 +128,28 @@ public class WaybillServiceImpl extends BaseServiceImpl }).toList(); } + @Override + @Transactional(rollbackFor = Exception.class) + public List importWaybill(List data) { + if (Func.isEmpty(data)) { + throw new ServiceException("导入数据不能为空"); + } + List errorList = new ArrayList<>(); + for (int index = 0; index < data.size(); index++) { + WaybillExcel excel = data.get(index); + try { + Waybill waybill = Objects.requireNonNull(BeanUtil.copyProperties(excel, Waybill.class)); + waybill.setDataSource("批量导入"); + waybill.setCarrierJson(buildImportCarrierJson(waybill)); + submit(waybill); + } catch (Exception exception) { + excel.setErrorMessage("第" + (index + 2) + "行:" + exception.getMessage()); + errorList.add(excel); + } + } + return errorList; + } + @Override @Transactional(rollbackFor = Exception.class) public WaybillVO copy(Long id) { @@ -138,11 +163,38 @@ public class WaybillServiceImpl extends BaseServiceImpl target.setTransportType(source.getTransportType()); target.setCargoName(source.getCargoName()); target.setCargoType(source.getCargoType()); + target.setSpecification(source.getSpecification()); + target.setModel(source.getModel()); + target.setQuantity(source.getQuantity()); + target.setQuantityUnit(source.getQuantityUnit()); + target.setDepartureAddressId(source.getDepartureAddressId()); + target.setDepartureName(source.getDepartureName()); target.setDepartureAddress(source.getDepartureAddress()); + target.setDepartureContact(source.getDepartureContact()); + target.setDeparturePhone(source.getDeparturePhone()); + target.setArrivalAddressId(source.getArrivalAddressId()); + target.setArrivalName(source.getArrivalName()); target.setArrivalAddress(source.getArrivalAddress()); + target.setArrivalContact(source.getArrivalContact()); + target.setArrivalPhone(source.getArrivalPhone()); + target.setTaskEntryMode(source.getTaskEntryMode()); + target.setCarrierType(source.getCarrierType()); + target.setCarrierId(source.getCarrierId()); target.setCarrierName(source.getCarrierName()); + target.setDriverId(source.getDriverId()); target.setDriverName(source.getDriverName()); + target.setDriverPhone(source.getDriverPhone()); target.setVehicleNo(source.getVehicleNo()); + target.setTrailerVehicleNo(source.getTrailerVehicleNo()); + target.setEscortName(source.getEscortName()); + target.setEscortPhone(source.getEscortPhone()); + target.setMileage(source.getMileage()); + target.setEstimatedStartTime(source.getEstimatedStartTime()); + target.setEstimatedEndTime(source.getEstimatedEndTime()); + target.setUnitPrice(source.getUnitPrice()); + target.setPriceUnit(source.getPriceUnit()); + target.setOtherFeeTotal(source.getOtherFeeTotal()); + target.setTaskRemark(source.getTaskRemark()); target.setOriginalNo(source.getOriginalNo()); target.setBusinessStatus(source.getBusinessStatus()); target.setDataSource(source.getDataSource()); @@ -157,6 +209,7 @@ public class WaybillServiceImpl extends BaseServiceImpl target.setCurrentProcessNode(source.getCurrentProcessNode()); target.setGoodsJson(source.getGoodsJson()); target.setCarrierJson(source.getCarrierJson()); + target.setTaskInfoJson(source.getTaskInfoJson()); target.setProcessJson(source.getProcessJson()); target.setFreightJson(source.getFreightJson()); target.setAttachmentsJson(source.getAttachmentsJson()); @@ -284,6 +337,9 @@ public class WaybillServiceImpl extends BaseServiceImpl if (Func.isNotEmpty(waybill.getLoadingNo())) { queryWrapper.like(Waybill::getLoadingNo, waybill.getLoadingNo()); } + if (Objects.equals(waybill.getOnlyUnassignedLoading(), 1)) { + queryWrapper.and(wrapper -> wrapper.isNull(Waybill::getLoadingNo).or().eq(Waybill::getLoadingNo, "")); + } if (Func.isNotEmpty(waybill.getBatchNo())) { queryWrapper.like(Waybill::getBatchNo, waybill.getBatchNo()); } @@ -308,11 +364,28 @@ public class WaybillServiceImpl extends BaseServiceImpl waybill.setTransportType(TransportBusinessSupport.trimToNull(waybill.getTransportType())); waybill.setCargoName(TransportBusinessSupport.trimToNull(waybill.getCargoName())); waybill.setCargoType(TransportBusinessSupport.trimToNull(waybill.getCargoType())); + waybill.setSpecification(TransportBusinessSupport.trimToNull(waybill.getSpecification())); + waybill.setModel(TransportBusinessSupport.trimToNull(waybill.getModel())); + waybill.setQuantityUnit(TransportBusinessSupport.trimToNull(waybill.getQuantityUnit())); + waybill.setDepartureName(TransportBusinessSupport.trimToNull(waybill.getDepartureName())); waybill.setDepartureAddress(TransportBusinessSupport.trimToNull(waybill.getDepartureAddress())); + waybill.setDepartureContact(TransportBusinessSupport.trimToNull(waybill.getDepartureContact())); + waybill.setDeparturePhone(TransportBusinessSupport.trimToNull(waybill.getDeparturePhone())); + waybill.setArrivalName(TransportBusinessSupport.trimToNull(waybill.getArrivalName())); waybill.setArrivalAddress(TransportBusinessSupport.trimToNull(waybill.getArrivalAddress())); + waybill.setArrivalContact(TransportBusinessSupport.trimToNull(waybill.getArrivalContact())); + waybill.setArrivalPhone(TransportBusinessSupport.trimToNull(waybill.getArrivalPhone())); + waybill.setTaskEntryMode(TransportBusinessSupport.trimToNull(waybill.getTaskEntryMode())); + waybill.setCarrierType(TransportBusinessSupport.trimToNull(waybill.getCarrierType())); waybill.setCarrierName(TransportBusinessSupport.trimToNull(waybill.getCarrierName())); waybill.setDriverName(TransportBusinessSupport.trimToNull(waybill.getDriverName())); + waybill.setDriverPhone(TransportBusinessSupport.trimToNull(waybill.getDriverPhone())); waybill.setVehicleNo(TransportBusinessSupport.trimToNull(waybill.getVehicleNo())); + waybill.setTrailerVehicleNo(TransportBusinessSupport.trimToNull(waybill.getTrailerVehicleNo())); + waybill.setEscortName(TransportBusinessSupport.trimToNull(waybill.getEscortName())); + waybill.setEscortPhone(TransportBusinessSupport.trimToNull(waybill.getEscortPhone())); + waybill.setPriceUnit(TransportBusinessSupport.trimToNull(waybill.getPriceUnit())); + waybill.setTaskRemark(TransportBusinessSupport.trimToNull(waybill.getTaskRemark())); waybill.setOriginalNo(TransportBusinessSupport.trimToNull(waybill.getOriginalNo())); waybill.setBusinessStatus(TransportBusinessSupport.trimToNull(waybill.getBusinessStatus())); waybill.setDataSource(TransportBusinessSupport.trimToNull(waybill.getDataSource())); @@ -324,11 +397,13 @@ public class WaybillServiceImpl extends BaseServiceImpl waybill.setCurrentProcessNode(TransportBusinessSupport.trimToNull(waybill.getCurrentProcessNode())); waybill.setGoodsJson(TransportBusinessSupport.trimToNull(waybill.getGoodsJson())); waybill.setCarrierJson(TransportBusinessSupport.trimToNull(waybill.getCarrierJson())); + waybill.setTaskInfoJson(TransportBusinessSupport.trimToNull(waybill.getTaskInfoJson())); waybill.setProcessJson(TransportBusinessSupport.trimToNull(waybill.getProcessJson())); waybill.setFreightJson(TransportBusinessSupport.trimToNull(waybill.getFreightJson())); waybill.setAttachmentsJson(TransportBusinessSupport.trimToNull(waybill.getAttachmentsJson())); waybill.setDeptName(TransportBusinessSupport.trimToNull(waybill.getDeptName())); waybill.setRemark(TransportBusinessSupport.trimToNull(waybill.getRemark())); + fillJsonFromFlatFields(waybill); if (Func.isEmpty(waybill.getDeptId())) { Dept dept = TransportBusinessSupport.currentDept("运单管理"); waybill.setDeptId(dept.getId()); @@ -344,7 +419,25 @@ public class WaybillServiceImpl extends BaseServiceImpl TransportBusinessSupport.validateRequired(waybill.getTransportType(), "运输类型不能为空"); TransportBusinessSupport.validateRequired(waybill.getCargoName(), "请输入货物名称"); TransportBusinessSupport.validateRequired(waybill.getCargoType(), "货物类型不能为空"); + TransportBusinessSupport.validateRequired(waybill.getQuantityUnit(), "数量单位不能为空"); + TransportBusinessSupport.validateRequired(waybill.getCarrierType(), "承运类型不能为空"); + TransportBusinessSupport.validateRequired(waybill.getVehicleNo(), "车牌号不能为空"); + if ("承运商".equals(waybill.getCarrierType())) { + TransportBusinessSupport.validateRequired(waybill.getCarrierName(), "承运商不能为空"); + } else { + TransportBusinessSupport.validateRequired(waybill.getDriverName(), "司机不能为空"); + TransportBusinessSupport.validateRequired(waybill.getDriverPhone(), "司机手机号不能为空"); + TransportBusinessSupport.validateRequired(waybill.getTrailerVehicleNo(), "挂车车牌号不能为空"); + TransportBusinessSupport.validateRequired(waybill.getEscortName(), "押运人不能为空"); + TransportBusinessSupport.validateRequired(waybill.getEscortPhone(), "押运人手机号不能为空"); + if (Func.isEmpty(waybill.getMileage())) { + throw new ServiceException("里程不能为空"); + } + } TransportBusinessSupport.validateRequired(waybill.getCarrierJson(), "承运信息不能为空"); + if (Func.isEmpty(waybill.getQuantity())) { + throw new ServiceException("数量不能为空"); + } TransportBusinessSupport.validateLength(waybill.getWaybillNo(), 255, "运单号不能超过255字"); TransportBusinessSupport.validateLength(waybill.getProjectName(), 255, "项目不能超过255字"); TransportBusinessSupport.validateLength(waybill.getContractName(), 255, "客户合同不能超过255字"); @@ -352,11 +445,28 @@ public class WaybillServiceImpl extends BaseServiceImpl TransportBusinessSupport.validateLength(waybill.getTransportType(), 255, "运输类型不能超过255字"); TransportBusinessSupport.validateLength(waybill.getCargoName(), 255, "货物名称不能超过255字"); TransportBusinessSupport.validateLength(waybill.getCargoType(), 255, "货物类型不能超过255字"); + TransportBusinessSupport.validateLength(waybill.getSpecification(), 255, "规格不能超过255字"); + TransportBusinessSupport.validateLength(waybill.getModel(), 255, "型号不能超过255字"); + TransportBusinessSupport.validateLength(waybill.getQuantityUnit(), 50, "数量单位不能超过50字"); + TransportBusinessSupport.validateLength(waybill.getDepartureName(), 255, "发货地不能超过255字"); TransportBusinessSupport.validateLength(waybill.getDepartureAddress(), 255, "发货地址不能超过255字"); + TransportBusinessSupport.validateLength(waybill.getDepartureContact(), 255, "发货联系人不能超过255字"); + TransportBusinessSupport.validateLength(waybill.getDeparturePhone(), 50, "发货联系方式不能超过50字"); + TransportBusinessSupport.validateLength(waybill.getArrivalName(), 255, "收货地不能超过255字"); TransportBusinessSupport.validateLength(waybill.getArrivalAddress(), 255, "收货地址不能超过255字"); + TransportBusinessSupport.validateLength(waybill.getArrivalContact(), 255, "收货联系人不能超过255字"); + TransportBusinessSupport.validateLength(waybill.getArrivalPhone(), 50, "收货联系方式不能超过50字"); + TransportBusinessSupport.validateLength(waybill.getTaskEntryMode(), 50, "任务录入模式不能超过50字"); + TransportBusinessSupport.validateLength(waybill.getCarrierType(), 50, "承运类型不能超过50字"); TransportBusinessSupport.validateLength(waybill.getCarrierName(), 255, "承运商名称不能超过255字"); TransportBusinessSupport.validateLength(waybill.getDriverName(), 255, "司机姓名不能超过255字"); + TransportBusinessSupport.validateLength(waybill.getDriverPhone(), 50, "司机手机号不能超过50字"); TransportBusinessSupport.validateLength(waybill.getVehicleNo(), 255, "车/船/航班/班列号不能超过255字"); + TransportBusinessSupport.validateLength(waybill.getTrailerVehicleNo(), 255, "挂车车牌号不能超过255字"); + TransportBusinessSupport.validateLength(waybill.getEscortName(), 255, "押运人不能超过255字"); + TransportBusinessSupport.validateLength(waybill.getEscortPhone(), 50, "押运人手机号不能超过50字"); + TransportBusinessSupport.validateLength(waybill.getPriceUnit(), 50, "计价单位不能超过50字"); + TransportBusinessSupport.validateLength(waybill.getTaskRemark(), 200, "任务备注不能超过200字"); TransportBusinessSupport.validateLength(waybill.getOriginalNo(), 255, "原始单号不能超过255字"); TransportBusinessSupport.validateLength(waybill.getBusinessStatus(), 255, "业务状态不能超过255字"); TransportBusinessSupport.validateLength(waybill.getDataSource(), 255, "数据来源不能超过255字"); @@ -368,12 +478,26 @@ public class WaybillServiceImpl extends BaseServiceImpl TransportBusinessSupport.validateLength(waybill.getCurrentProcessNode(), 255, "当前过程节点不能超过255字"); TransportBusinessSupport.validateLength(waybill.getGoodsJson(), 8000, "货物信息不能超过8000字"); TransportBusinessSupport.validateLength(waybill.getCarrierJson(), 8000, "承运信息不能超过8000字"); + TransportBusinessSupport.validateLength(waybill.getTaskInfoJson(), 8000, "任务信息不能超过8000字"); TransportBusinessSupport.validateLength(waybill.getProcessJson(), 8000, "过程节点不能超过8000字"); TransportBusinessSupport.validateLength(waybill.getFreightJson(), 8000, "费用信息不能超过8000字"); TransportBusinessSupport.validateLength(waybill.getAttachmentsJson(), 8000, "附件不能超过8000字"); TransportBusinessSupport.validateLength(waybill.getDeptName(), 255, "所属组织不能超过255字"); - TransportBusinessSupport.validateLength(waybill.getRemark(), 500, "备注不能超过500字"); + TransportBusinessSupport.validateLength(waybill.getRemark(), 200, "备注不能超过200字"); + TransportBusinessSupport.validatePhone(waybill.getDeparturePhone(), "发货联系方式格式不正确"); + TransportBusinessSupport.validatePhone(waybill.getArrivalPhone(), "收货联系方式格式不正确"); + TransportBusinessSupport.validatePhone(waybill.getDriverPhone(), "司机手机号格式不正确"); + TransportBusinessSupport.validatePhone(waybill.getEscortPhone(), "押运人手机号格式不正确"); + TransportBusinessSupport.validateNonNegative(waybill.getQuantity(), "数量"); + TransportBusinessSupport.validateNonNegative(waybill.getMileage(), "里程"); + TransportBusinessSupport.validateNonNegative(waybill.getUnitPrice(), "单价"); + TransportBusinessSupport.validateNonNegative(waybill.getOtherFeeTotal(), "其他费用合计"); TransportBusinessSupport.validateDateRange(waybill.getStartDate(), waybill.getEndDate(), "开始日期不能晚于结束日期"); + if (waybill.getEstimatedStartTime() != null + && waybill.getEstimatedEndTime() != null + && waybill.getEstimatedEndTime().isBefore(waybill.getEstimatedStartTime())) { + throw new ServiceException("预计完成时间不能早于预计发货时间"); + } } private Waybill loadEditable(Long id, boolean checkDept) { @@ -394,6 +518,154 @@ public class WaybillServiceImpl extends BaseServiceImpl return false; } + private void fillJsonFromFlatFields(Waybill waybill) { + if (Func.isEmpty(waybill.getTaskEntryMode())) { + waybill.setTaskEntryMode("simple"); + } + if (Func.isEmpty(waybill.getCarrierJson())) { + waybill.setCarrierJson(buildImportCarrierJson(waybill)); + } + if (Func.isEmpty(waybill.getTaskInfoJson())) { + waybill.setTaskInfoJson(buildTaskInfoJson(waybill)); + } + if (Func.isEmpty(waybill.getGoodsJson())) { + waybill.setGoodsJson(buildGoodsJson(waybill)); + } + if (Func.isEmpty(waybill.getFreightJson())) { + waybill.setFreightJson(buildFreightJson(waybill)); + } + } + + private String buildImportCarrierJson(Waybill waybill) { + if (!hasAnyValue( + waybill.getCarrierType(), + waybill.getCarrierId(), + waybill.getCarrierName(), + waybill.getDriverId(), + waybill.getDriverName(), + waybill.getDriverPhone(), + waybill.getVehicleNo(), + waybill.getTrailerVehicleNo(), + waybill.getEscortName(), + waybill.getEscortPhone(), + waybill.getMileage() + )) { + return null; + } + Map carrier = new LinkedHashMap<>(); + putIfNotEmpty(carrier, "carrierType", waybill.getCarrierType()); + putIfNotEmpty(carrier, "carrierId", waybill.getCarrierId()); + putIfNotEmpty(carrier, "carrier", waybill.getCarrierName()); + putIfNotEmpty(carrier, "carrierName", waybill.getCarrierName()); + putIfNotEmpty(carrier, "driverId", waybill.getDriverId()); + putIfNotEmpty(carrier, "driverName", waybill.getDriverName()); + putIfNotEmpty(carrier, "driverPhone", waybill.getDriverPhone()); + putIfNotEmpty(carrier, "vehicleNo", waybill.getVehicleNo()); + putIfNotEmpty(carrier, "trailerVehicleNo", waybill.getTrailerVehicleNo()); + putIfNotEmpty(carrier, "escortName", waybill.getEscortName()); + putIfNotEmpty(carrier, "escortPhone", waybill.getEscortPhone()); + putIfNotEmpty(carrier, "mileage", waybill.getMileage()); + putIfNotEmpty(carrier, "taskEntryMode", waybill.getTaskEntryMode()); + return JsonUtil.toJson(List.of(carrier)); + } + + private String buildTaskInfoJson(Waybill waybill) { + if (!hasAnyValue( + waybill.getTaskEntryMode(), + waybill.getCarrierType(), + waybill.getCarrierId(), + waybill.getCarrierName(), + waybill.getDriverId(), + waybill.getDriverName(), + waybill.getDriverPhone(), + waybill.getVehicleNo(), + waybill.getTrailerVehicleNo(), + waybill.getEscortName(), + waybill.getEscortPhone(), + waybill.getMileage(), + waybill.getEstimatedStartTime(), + waybill.getEstimatedEndTime(), + waybill.getUnitPrice(), + waybill.getPriceUnit(), + waybill.getOtherFeeTotal(), + waybill.getTaskRemark() + )) { + return null; + } + Map taskInfo = new LinkedHashMap<>(); + putIfNotEmpty(taskInfo, "taskEntryMode", waybill.getTaskEntryMode()); + putIfNotEmpty(taskInfo, "carrierType", waybill.getCarrierType()); + putIfNotEmpty(taskInfo, "carrierId", waybill.getCarrierId()); + putIfNotEmpty(taskInfo, "carrierName", waybill.getCarrierName()); + putIfNotEmpty(taskInfo, "driverId", waybill.getDriverId()); + putIfNotEmpty(taskInfo, "driverName", waybill.getDriverName()); + putIfNotEmpty(taskInfo, "driverPhone", waybill.getDriverPhone()); + putIfNotEmpty(taskInfo, "vehicleNo", waybill.getVehicleNo()); + putIfNotEmpty(taskInfo, "trailerVehicleNo", waybill.getTrailerVehicleNo()); + putIfNotEmpty(taskInfo, "escortName", waybill.getEscortName()); + putIfNotEmpty(taskInfo, "escortPhone", waybill.getEscortPhone()); + putIfNotEmpty(taskInfo, "mileage", waybill.getMileage()); + putIfNotEmpty(taskInfo, "estimatedStartTime", waybill.getEstimatedStartTime()); + putIfNotEmpty(taskInfo, "estimatedEndTime", waybill.getEstimatedEndTime()); + putIfNotEmpty(taskInfo, "unitPrice", waybill.getUnitPrice()); + putIfNotEmpty(taskInfo, "priceUnit", waybill.getPriceUnit()); + putIfNotEmpty(taskInfo, "otherFeeTotal", waybill.getOtherFeeTotal()); + putIfNotEmpty(taskInfo, "taskRemark", waybill.getTaskRemark()); + return JsonUtil.toJson(taskInfo); + } + + private String buildGoodsJson(Waybill waybill) { + if (!hasAnyValue( + waybill.getCargoName(), + waybill.getCargoType(), + waybill.getSpecification(), + waybill.getModel(), + waybill.getQuantity(), + waybill.getQuantityUnit() + )) { + return null; + } + Map goods = new LinkedHashMap<>(); + putIfNotEmpty(goods, "cargoName", waybill.getCargoName()); + putIfNotEmpty(goods, "cargoType", waybill.getCargoType()); + putIfNotEmpty(goods, "specification", waybill.getSpecification()); + putIfNotEmpty(goods, "model", waybill.getModel()); + putIfNotEmpty(goods, "quantity", waybill.getQuantity()); + putIfNotEmpty(goods, "quantityUnit", waybill.getQuantityUnit()); + putIfNotEmpty(goods, "mileage", waybill.getMileage()); + putIfNotEmpty(goods, "unitPrice", waybill.getUnitPrice()); + putIfNotEmpty(goods, "priceUnit", waybill.getPriceUnit()); + putIfNotEmpty(goods, "otherFeeTotal", waybill.getOtherFeeTotal()); + putIfNotEmpty(goods, "remark", waybill.getTaskRemark()); + return JsonUtil.toJson(List.of(goods)); + } + + private String buildFreightJson(Waybill waybill) { + if (!hasAnyValue(waybill.getUnitPrice(), waybill.getPriceUnit(), waybill.getOtherFeeTotal())) { + return null; + } + Map freight = new LinkedHashMap<>(); + putIfNotEmpty(freight, "unitPrice", waybill.getUnitPrice()); + putIfNotEmpty(freight, "priceUnit", waybill.getPriceUnit()); + putIfNotEmpty(freight, "otherFeeTotal", waybill.getOtherFeeTotal()); + return JsonUtil.toJson(List.of(freight)); + } + + private boolean hasAnyValue(Object... values) { + for (Object value : values) { + if (Func.isNotEmpty(value)) { + return true; + } + } + return false; + } + + private void putIfNotEmpty(Map map, String key, Object value) { + if (Func.isNotEmpty(value)) { + map.put(key, value); + } + } + private synchronized String nextCode() { String prefix = "YD" + LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE); List latestList = list(Wrappers.lambdaQuery() diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/wrapper/LoadingManageWrapper.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/wrapper/LoadingManageWrapper.java new file mode 100644 index 0000000..3a56ffb --- /dev/null +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/wrapper/LoadingManageWrapper.java @@ -0,0 +1,53 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + */ +package org.springblade.transport.wrapper; + +import org.springblade.core.mp.support.BaseEntityWrapper; +import org.springblade.core.secure.utils.AuthUtil; +import org.springblade.core.tool.utils.BeanUtil; +import org.springblade.core.tool.utils.Func; +import org.springblade.system.cache.UserCache; +import org.springblade.transport.pojo.entity.LoadingManage; +import org.springblade.transport.pojo.vo.LoadingManageVO; + +import java.util.Objects; + +/** + * 配载管理包装类 + * + * @author Chill + */ +public class LoadingManageWrapper extends BaseEntityWrapper { + + public static LoadingManageWrapper build() { + return new LoadingManageWrapper(); + } + + @Override + public LoadingManageVO entityVO(LoadingManage loadingManage) { + LoadingManageVO loadingManageVO = Objects.requireNonNull(BeanUtil.copyProperties(loadingManage, LoadingManageVO.class)); + loadingManageVO.setCreateUserName(UserCache.getUserRealName(loadingManage.getCreateUser())); + loadingManageVO.setUpdateUserName(UserCache.getUserRealName(loadingManage.getUpdateUser())); + Long currentDeptId = Func.firstLong(AuthUtil.getDeptId()); + loadingManageVO.setReadonly(currentDeptId != null && !Objects.equals(loadingManage.getDeptId(), currentDeptId)); + loadingManageVO.setBusinessStatusName(businessStatusName(loadingManage.getBusinessStatus())); + return loadingManageVO; + } + + private String businessStatusName(String status) { + if (status == null) { + return "未知"; + } + return switch (status) { + case "draft" -> "草稿"; + case "pending" -> "待执行"; + case "running" -> "进行中"; + case "completed" -> "已完成"; + case "cancelled" -> "已取消"; + default -> "未知"; + }; + } + +} diff --git a/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/pojo/dto/CertificateBatchRecognitionDTO.java b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/pojo/dto/CertificateBatchRecognitionDTO.java index 54eb4f9..b6b0bb8 100644 --- a/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/pojo/dto/CertificateBatchRecognitionDTO.java +++ b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/pojo/dto/CertificateBatchRecognitionDTO.java @@ -31,4 +31,20 @@ public class CertificateBatchRecognitionDTO implements Serializable { * 目录名称 */ private String dirName; + /** + * 磅单url列表 + */ + private List poundUrls; + /** + * 车头url列表 + */ + private List carFrontUrls; + /** + * 驾驶证url列表 + */ + private List driverLicenseUrls; + /** + * 行驶证url列表 + */ + private List vehicleLicenseUrls; } diff --git a/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/service/impl/OCRServiceImpl.java b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/service/impl/OCRServiceImpl.java index 1efee09..842c273 100644 --- a/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/service/impl/OCRServiceImpl.java +++ b/blade-third-party-api/blade-ocr-api/src/main/java/org/springblade/thirdparty/ocr/service/impl/OCRServiceImpl.java @@ -47,10 +47,26 @@ public class OCRServiceImpl implements IOCRService { @Override public TransportCertificateVO recognitionTransportCertificate(CertificateBatchRecognitionDTO param) { log.info("ocr 批量识别参数:{}", JSONUtil.toJsonStr(param)); - if (param == null || CollectionUtils.isEmpty(param.getUrls())) { + if (param == null) { throw new OCRException("图像链接不能为空"); } - List> result = this.getCardResult(() -> client.batchRecognition(new OCRBigCommonCardBatchDTO(param.getUrls(), param.getProjectAbbreviation(), param.getDirName()), param.getCode()), "批量磅单卡证识别"); + if (hasTypedUrls(param)) { + return convertTransportCertificate(recognitionTransportCertificateWithTypedUrls(param)); + } + if (CollectionUtils.isEmpty(param.getUrls())) { + throw new OCRException("图像链接不能为空"); + } + List> result = this.getCardResult( + () -> client.batchRecognition( + new OCRBigCommonCardBatchDTO( + param.getUrls(), + param.getProjectAbbreviation(), + param.getDirName() + ), + param.getCode() + ), + "批量磅单卡证识别" + ); List cards = result.stream() .map(OCRResultVO::getResult) .toList(); @@ -134,6 +150,108 @@ public class OCRServiceImpl implements IOCRService { } } + private List recognitionTransportCertificateWithTypedUrls(CertificateBatchRecognitionDTO param) { + List cards = new ArrayList<>(4); + addPoundRecognitionCards(cards, param); + addDriverLicenseRecognitionCards(cards, param); + addVehicleLicenseRecognitionCards(cards, param); + addCarFrontRecognitionCards(cards, param); + return cards; + } + + private void addPoundRecognitionCards(List cards, CertificateBatchRecognitionDTO param) { + for (String url : Optional.ofNullable(param.getPoundUrls()).orElse(List.of())) { + if (StringUtil.isBlank(url)) { + continue; + } + OCRBigCommonCardVO card = this.getCardResult( + () -> client.poundRecognition( + new OCRSingleDTO(url, param.getProjectAbbreviation()), param.getCode() + ), + "磅单单独识别" + ); + addCard(cards, card); + } + } + + private void addDriverLicenseRecognitionCards(List cards, CertificateBatchRecognitionDTO param) { + List driverLicenseUrls = Optional.ofNullable(param.getDriverLicenseUrls()).orElse(List.of()); + for (String url : driverLicenseUrls) { + if (StringUtil.isBlank(url)) { + continue; + } + OCRBigCommonCardVO card = this.getCardResult( + () -> client.singleRecognition( + new OCRBigCommonCardDTO( + url, + param.getProjectAbbreviation(), + List.of( + OCRImageType.DRIVER_LICENSE_MAIN.getId(), + OCRImageType.DRIVER_LICENSE_SIDE.getId(), + OCRImageType.DRIVER_LICENSE_ELEC.getId() + ) + ), + param.getCode() + ), + "驾驶证识别" + ); + addCard(cards, card); + } + } + + private void addVehicleLicenseRecognitionCards(List cards, CertificateBatchRecognitionDTO param) { + List vehicleLicenseUrls = Optional.ofNullable(param.getVehicleLicenseUrls()).orElse(List.of()); + for (String url : vehicleLicenseUrls) { + if (StringUtil.isBlank(url)) { + continue; + } + OCRBigCommonCardVO card = this.getCardResult( + () -> client.singleRecognition( + new OCRBigCommonCardDTO( + url, + param.getProjectAbbreviation(), + List.of( + OCRImageType.VEHICLE_LICENSE_MAIN_GUA.getId(), + OCRImageType.VEHICLE_LICENSE_MAIN.getId(), + OCRImageType.VEHICLE_LICENSE_SIDE.getId() + ) + ), + param.getCode() + ), + "行驶证识别" + ); + addCard(cards, card); + } + } + + private void addCarFrontRecognitionCards(List cards, CertificateBatchRecognitionDTO param) { + for (String url : Optional.ofNullable(param.getCarFrontUrls()).orElse(List.of())) { + if (StringUtil.isBlank(url)) { + continue; + } + OCRBigCommonCardVO card = this.getCardResult( + () -> client.plateNoRecognition( + new OCRSingleDTO(url, param.getProjectAbbreviation()), param.getCode() + ), + "⻋牌单独识别" + ); + addCard(cards, card); + } + } + + private static boolean hasTypedUrls(CertificateBatchRecognitionDTO param) { + return CollectionUtil.isNotEmpty(param.getPoundUrls()) + || CollectionUtil.isNotEmpty(param.getCarFrontUrls()) + || CollectionUtil.isNotEmpty(param.getDriverLicenseUrls()) + || CollectionUtil.isNotEmpty(param.getVehicleLicenseUrls()); + } + + private static void addCard(List cards, OCRBigCommonCardVO card) { + if (card != null) { + cards.add(card); + } + } + /** * 转换车辆凭证 * @param cards diff --git a/doc/sql/transport/blade_loading_manage_20260804.sql b/doc/sql/transport/blade_loading_manage_20260804.sql new file mode 100644 index 0000000..3096e6d --- /dev/null +++ b/doc/sql/transport/blade_loading_manage_20260804.sql @@ -0,0 +1,65 @@ +CREATE TABLE IF NOT EXISTS `blade_loading_manage` ( + `id` bigint(20) NOT NULL COMMENT '主键', + `tenant_id` varchar(12) DEFAULT '000000' COMMENT '租户ID', + `create_user` bigint(20) DEFAULT NULL COMMENT '创建人', + `create_dept` bigint(20) DEFAULT NULL COMMENT '创建部门', + `create_time` datetime DEFAULT NULL COMMENT '创建时间', + `update_user` bigint(20) DEFAULT NULL COMMENT '修改人', + `update_time` datetime DEFAULT NULL COMMENT '修改时间', + `status` int(11) DEFAULT '1' COMMENT '状态', + `is_deleted` int(11) DEFAULT '0' COMMENT '是否已删除', + `loading_no` varchar(100) DEFAULT NULL COMMENT '配载单号', + `loading_sub_nos` varchar(200) DEFAULT NULL COMMENT '配载子单号', + `waybill_ids_json` varchar(4000) DEFAULT NULL COMMENT '运单ID集合', + `project_id` bigint(20) DEFAULT NULL COMMENT '项目ID', + `project_name` varchar(100) DEFAULT NULL COMMENT '项目名称', + `customer_name` varchar(100) DEFAULT NULL COMMENT '客户名称', + `transport_type` varchar(100) DEFAULT NULL COMMENT '运输类型', + `cargo_type` varchar(100) DEFAULT NULL COMMENT '货物类型', + `cargo_name` varchar(100) DEFAULT NULL COMMENT '货物名称', + `vehicle_no` varchar(100) DEFAULT NULL COMMENT '车/船/航班/班列', + `trailer_vehicle_no` varchar(100) DEFAULT NULL COMMENT '挂车车牌号', + `driver_name` varchar(100) DEFAULT NULL COMMENT '司机', + `driver_phone` varchar(50) DEFAULT NULL COMMENT '联系电话', + `escort_name` varchar(100) DEFAULT NULL COMMENT '押运人', + `escort_phone` varchar(50) DEFAULT NULL COMMENT '押运人手机号', + `carrier_type` varchar(50) DEFAULT NULL COMMENT '承运类型', + `carrier_name` varchar(100) DEFAULT NULL COMMENT '承运商', + `departure_address` varchar(100) DEFAULT NULL COMMENT '发货地', + `transit_address` varchar(200) DEFAULT NULL COMMENT '途经地', + `arrival_address` varchar(100) DEFAULT NULL COMMENT '到货地', + `original_no` varchar(100) DEFAULT NULL COMMENT '原始单号', + `data_source` varchar(100) DEFAULT NULL COMMENT '数据来源', + `start_date` date DEFAULT NULL COMMENT '开始日期', + `end_date` date DEFAULT NULL COMMENT '结束日期', + `plan_name` varchar(100) DEFAULT NULL COMMENT '计划名称', + `batch_no` varchar(100) DEFAULT NULL COMMENT '运单批次号', + `current_process_node` varchar(100) DEFAULT NULL COMMENT '当前过程节点', + `mileage` decimal(18,2) DEFAULT NULL COMMENT '里程', + `estimated_start_date` date DEFAULT NULL COMMENT '预计发货日期', + `estimated_end_date` date DEFAULT NULL COMMENT '预计完成日期', + `task_remark` varchar(500) DEFAULT NULL COMMENT '任务备注', + `goods_json` text DEFAULT NULL COMMENT '货物信息', + `route_json` text DEFAULT NULL COMMENT '路线信息', + `task_info_json` text DEFAULT NULL COMMENT '任务信息', + `dept_id` bigint(20) DEFAULT NULL COMMENT '所属组织ID', + `dept_name` varchar(100) DEFAULT NULL COMMENT '所属组织', + `business_status` varchar(100) DEFAULT NULL COMMENT '业务状态', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE KEY `uk_loading_manage_no` (`loading_no`) USING BTREE, + KEY `idx_loading_manage_dept` (`dept_id`) USING BTREE, + KEY `idx_loading_manage_create_time` (`create_time`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='配载管理'; + +INSERT IGNORE INTO `blade_menu` (`id`, `parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `component`, `remark`, `is_deleted`) VALUES +(2090000000000000700, 2090000000000000000, 'loading_manage', '配载管理', 'loading_manage', '/business/loading-manage', 'iconfont icon-caidanguanli', 70, 1, 0, 1, NULL, '', 0), +(2090000000000000701, 2090000000000000700, 'loading_manage_view', '查看', 'loading_manage_view', '', '', 1, 2, 0, 1, NULL, '', 0), +(2090000000000000702, 2090000000000000700, 'loading_manage_add', '新增', 'loading_manage_add', '', '', 2, 2, 0, 1, NULL, '', 0), +(2090000000000000703, 2090000000000000700, 'loading_manage_edit', '编辑', 'loading_manage_edit', '', '', 3, 2, 0, 1, NULL, '', 0), +(2090000000000000704, 2090000000000000700, 'loading_manage_delete', '删除', 'loading_manage_delete', '', '', 4, 2, 0, 1, NULL, '', 0), +(2090000000000000705, 2090000000000000700, 'loading_manage_export', '批量导出', 'loading_manage_export', '', '', 5, 2, 0, 1, NULL, '', 0), +(2090000000000000706, 2090000000000000700, 'loading_manage_copy', '复制', 'loading_manage_copy', '', '', 6, 2, 0, 1, NULL, '', 0), +(2090000000000000707, 2090000000000000700, 'loading_manage_reassign', '重新派单', 'loading_manage_reassign', '', '', 7, 2, 0, 1, NULL, '', 0), +(2090000000000000708, 2090000000000000700, 'loading_manage_cancel', '取消', 'loading_manage_cancel', '', '', 8, 2, 0, 1, NULL, '', 0), +(2090000000000000709, 2090000000000000700, 'loading_manage_complete', '完成', 'loading_manage_complete', '', '', 9, 2, 0, 1, NULL, '', 0), +(2090000000000000710, 2090000000000000700, 'loading_manage_batch_complete', '批量完成', 'loading_manage_batch_complete', '', '', 10, 2, 0, 1, NULL, '', 0); diff --git a/doc/sql/transport/blade_tms_business.sql b/doc/sql/transport/blade_tms_business.sql index a1ab831..e8d557f 100644 --- a/doc/sql/transport/blade_tms_business.sql +++ b/doc/sql/transport/blade_tms_business.sql @@ -219,11 +219,38 @@ CREATE TABLE `blade_waybill` ( `transport_type` varchar(100) DEFAULT NULL COMMENT '运输类型', `cargo_name` varchar(100) DEFAULT NULL COMMENT '货物名称', `cargo_type` varchar(100) DEFAULT NULL COMMENT '货物类型', + `specification` varchar(255) DEFAULT NULL COMMENT '规格', + `model` varchar(255) DEFAULT NULL COMMENT '型号', + `quantity` decimal(18,2) DEFAULT NULL COMMENT '数量', + `quantity_unit` varchar(50) DEFAULT NULL COMMENT '数量单位', + `departure_address_id` bigint(20) DEFAULT NULL COMMENT '发货地址ID', + `departure_name` varchar(100) DEFAULT NULL COMMENT '发货地', `departure_address` varchar(255) DEFAULT NULL COMMENT '发货地址', + `departure_contact` varchar(100) DEFAULT NULL COMMENT '发货联系人', + `departure_phone` varchar(50) DEFAULT NULL COMMENT '发货联系方式', + `arrival_address_id` bigint(20) DEFAULT NULL COMMENT '收货地址ID', + `arrival_name` varchar(100) DEFAULT NULL COMMENT '收货地', `arrival_address` varchar(255) DEFAULT NULL COMMENT '收货地址', + `arrival_contact` varchar(100) DEFAULT NULL COMMENT '收货联系人', + `arrival_phone` varchar(50) DEFAULT NULL COMMENT '收货联系方式', + `task_entry_mode` varchar(50) DEFAULT NULL COMMENT '任务录入模式', + `carrier_type` varchar(50) DEFAULT NULL COMMENT '承运类型', + `carrier_id` bigint(20) DEFAULT NULL COMMENT '承运商ID', `carrier_name` varchar(100) DEFAULT NULL COMMENT '承运商名称', + `driver_id` bigint(20) DEFAULT NULL COMMENT '司机ID', `driver_name` varchar(100) DEFAULT NULL COMMENT '司机姓名', + `driver_phone` varchar(50) DEFAULT NULL COMMENT '司机手机号', `vehicle_no` varchar(100) DEFAULT NULL COMMENT '车/船/航班/班列号', + `trailer_vehicle_no` varchar(100) DEFAULT NULL COMMENT '挂车车牌号', + `escort_name` varchar(100) DEFAULT NULL COMMENT '押运人', + `escort_phone` varchar(50) DEFAULT NULL COMMENT '押运人手机号', + `mileage` decimal(18,2) DEFAULT NULL COMMENT '里程', + `estimated_start_time` datetime DEFAULT NULL COMMENT '预计发货时间', + `estimated_end_time` datetime DEFAULT NULL COMMENT '预计完成时间', + `unit_price` decimal(18,2) DEFAULT NULL COMMENT '单价', + `price_unit` varchar(50) DEFAULT NULL COMMENT '计价单位', + `other_fee_total` decimal(18,2) DEFAULT NULL COMMENT '其他费用合计', + `task_remark` varchar(200) DEFAULT NULL COMMENT '任务备注', `original_no` varchar(100) DEFAULT NULL COMMENT '原始单号', `business_status` varchar(100) DEFAULT NULL COMMENT '业务状态', `data_source` varchar(100) DEFAULT NULL COMMENT '数据来源', @@ -238,17 +265,75 @@ CREATE TABLE `blade_waybill` ( `current_process_node` varchar(100) DEFAULT NULL COMMENT '当前过程节点', `goods_json` text DEFAULT NULL COMMENT '货物信息', `carrier_json` text DEFAULT NULL COMMENT '承运信息', + `task_info_json` text DEFAULT NULL COMMENT '任务信息', `process_json` text DEFAULT NULL COMMENT '过程节点', `freight_json` text DEFAULT NULL COMMENT '费用信息', `attachments_json` text DEFAULT NULL COMMENT '附件', `dept_id` bigint(20) DEFAULT NULL COMMENT '所属组织ID', `dept_name` varchar(100) DEFAULT NULL COMMENT '所属组织', - `remark` varchar(500) DEFAULT NULL COMMENT '备注', + `remark` varchar(200) DEFAULT NULL COMMENT '备注', PRIMARY KEY (`id`) USING BTREE, KEY `idx_waybill_dept` (`dept_id`) USING BTREE, KEY `idx_waybill_create_time` (`create_time`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='运单管理'; +-- ---------------------------- +-- Table structure for blade_loading_manage +-- ---------------------------- +DROP TABLE IF EXISTS `blade_loading_manage`; +CREATE TABLE `blade_loading_manage` ( + `id` bigint(20) NOT NULL COMMENT '主键', + `tenant_id` varchar(12) DEFAULT '000000' COMMENT '租户ID', + `create_user` bigint(20) DEFAULT NULL COMMENT '创建人', + `create_dept` bigint(20) DEFAULT NULL COMMENT '创建部门', + `create_time` datetime DEFAULT NULL COMMENT '创建时间', + `update_user` bigint(20) DEFAULT NULL COMMENT '修改人', + `update_time` datetime DEFAULT NULL COMMENT '修改时间', + `status` int(11) DEFAULT '1' COMMENT '状态', + `is_deleted` int(11) DEFAULT '0' COMMENT '是否已删除', + `loading_no` varchar(100) DEFAULT NULL COMMENT '配载单号', + `loading_sub_nos` varchar(200) DEFAULT NULL COMMENT '配载子单号', + `waybill_ids_json` varchar(4000) DEFAULT NULL COMMENT '运单ID集合', + `project_id` bigint(20) DEFAULT NULL COMMENT '项目ID', + `project_name` varchar(100) DEFAULT NULL COMMENT '项目名称', + `customer_name` varchar(100) DEFAULT NULL COMMENT '客户名称', + `transport_type` varchar(100) DEFAULT NULL COMMENT '运输类型', + `cargo_type` varchar(100) DEFAULT NULL COMMENT '货物类型', + `cargo_name` varchar(100) DEFAULT NULL COMMENT '货物名称', + `vehicle_no` varchar(100) DEFAULT NULL COMMENT '车/船/航班/班列', + `trailer_vehicle_no` varchar(100) DEFAULT NULL COMMENT '挂车车牌号', + `driver_name` varchar(100) DEFAULT NULL COMMENT '司机', + `driver_phone` varchar(50) DEFAULT NULL COMMENT '联系电话', + `escort_name` varchar(100) DEFAULT NULL COMMENT '押运人', + `escort_phone` varchar(50) DEFAULT NULL COMMENT '押运人手机号', + `carrier_type` varchar(50) DEFAULT NULL COMMENT '承运类型', + `carrier_name` varchar(100) DEFAULT NULL COMMENT '承运商', + `departure_address` varchar(100) DEFAULT NULL COMMENT '发货地', + `transit_address` varchar(200) DEFAULT NULL COMMENT '途经地', + `arrival_address` varchar(100) DEFAULT NULL COMMENT '到货地', + `original_no` varchar(100) DEFAULT NULL COMMENT '原始单号', + `data_source` varchar(100) DEFAULT NULL COMMENT '数据来源', + `start_date` date DEFAULT NULL COMMENT '开始日期', + `end_date` date DEFAULT NULL COMMENT '结束日期', + `plan_name` varchar(100) DEFAULT NULL COMMENT '计划名称', + `batch_no` varchar(100) DEFAULT NULL COMMENT '运单批次号', + `current_process_node` varchar(100) DEFAULT NULL COMMENT '当前过程节点', + `mileage` decimal(18,2) DEFAULT NULL COMMENT '里程', + `estimated_start_date` date DEFAULT NULL COMMENT '预计发货日期', + `estimated_end_date` date DEFAULT NULL COMMENT '预计完成日期', + `task_remark` varchar(500) DEFAULT NULL COMMENT '任务备注', + `goods_json` text DEFAULT NULL COMMENT '货物信息', + `route_json` text DEFAULT NULL COMMENT '路线信息', + `task_info_json` text DEFAULT NULL COMMENT '任务信息', + `dept_id` bigint(20) DEFAULT NULL COMMENT '所属组织ID', + `dept_name` varchar(100) DEFAULT NULL COMMENT '所属组织', + `business_status` varchar(100) DEFAULT NULL COMMENT '业务状态', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE KEY `uk_loading_manage_no` (`loading_no`) USING BTREE, + KEY `idx_loading_manage_dept` (`dept_id`) USING BTREE, + KEY `idx_loading_manage_create_time` (`create_time`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='配载管理'; + -- ---------------------------- -- Menu data for business management -- ---------------------------- @@ -303,4 +388,18 @@ INSERT IGNORE INTO `blade_menu` (`id`, `parent_id`, `code`, `name`, `alias`, `pa (2090000000000000607, 2090000000000000600, 'waybill_manage_cancel', '取消', 'waybill_manage_cancel', '', '', 7, 2, 0, 1, NULL, '', 0), (2090000000000000608, 2090000000000000600, 'waybill_manage_reassign', '重新派单', 'waybill_manage_reassign', '', '', 8, 2, 0, 1, NULL, '', 0), (2090000000000000609, 2090000000000000600, 'waybill_manage_complete', '完成', 'waybill_manage_complete', '', '', 9, 2, 0, 1, NULL, '', 0), -(2090000000000000610, 2090000000000000600, 'waybill_manage_batch_complete', '批量完成', 'waybill_manage_batch_complete', '', '', 10, 2, 0, 1, NULL, '', 0); +(2090000000000000610, 2090000000000000600, 'waybill_manage_batch_complete', '批量完成', 'waybill_manage_batch_complete', '', '', 10, 2, 0, 1, NULL, '', 0), +(2090000000000000611, 2090000000000000600, 'waybill_manage_import', '导入运单', 'waybill_manage_import', '', '', 11, 2, 0, 1, NULL, '', 0), +(2090000000000000612, 2090000000000000600, 'waybill_manage_template', '下载模板', 'waybill_manage_template', '', '', 12, 2, 0, 1, NULL, '', 0), +(2090000000000000613, 2090000000000000600, 'waybill_manage_road_loading', '公路配载', 'waybill_manage_road_loading', '', '', 13, 2, 0, 1, NULL, '', 0), +(2090000000000000700, 2090000000000000000, 'loading_manage', '配载管理', 'loading_manage', '/business/loading-manage', 'iconfont icon-caidanguanli', 70, 1, 0, 1, NULL, '', 0), +(2090000000000000701, 2090000000000000700, 'loading_manage_view', '查看', 'loading_manage_view', '', '', 1, 2, 0, 1, NULL, '', 0), +(2090000000000000702, 2090000000000000700, 'loading_manage_add', '新增', 'loading_manage_add', '', '', 2, 2, 0, 1, NULL, '', 0), +(2090000000000000703, 2090000000000000700, 'loading_manage_edit', '编辑', 'loading_manage_edit', '', '', 3, 2, 0, 1, NULL, '', 0), +(2090000000000000704, 2090000000000000700, 'loading_manage_delete', '删除', 'loading_manage_delete', '', '', 4, 2, 0, 1, NULL, '', 0), +(2090000000000000705, 2090000000000000700, 'loading_manage_export', '批量导出', 'loading_manage_export', '', '', 5, 2, 0, 1, NULL, '', 0), +(2090000000000000706, 2090000000000000700, 'loading_manage_copy', '复制', 'loading_manage_copy', '', '', 6, 2, 0, 1, NULL, '', 0), +(2090000000000000707, 2090000000000000700, 'loading_manage_reassign', '重新派单', 'loading_manage_reassign', '', '', 7, 2, 0, 1, NULL, '', 0), +(2090000000000000708, 2090000000000000700, 'loading_manage_cancel', '取消', 'loading_manage_cancel', '', '', 8, 2, 0, 1, NULL, '', 0), +(2090000000000000709, 2090000000000000700, 'loading_manage_complete', '完成', 'loading_manage_complete', '', '', 9, 2, 0, 1, NULL, '', 0), +(2090000000000000710, 2090000000000000700, 'loading_manage_batch_complete', '批量完成', 'loading_manage_batch_complete', '', '', 10, 2, 0, 1, NULL, '', 0); diff --git a/doc/sql/transport/blade_waybill_patch_20260804.sql b/doc/sql/transport/blade_waybill_patch_20260804.sql new file mode 100644 index 0000000..d75703f --- /dev/null +++ b/doc/sql/transport/blade_waybill_patch_20260804.sql @@ -0,0 +1,30 @@ +ALTER TABLE `blade_waybill` + ADD COLUMN `specification` varchar(255) DEFAULT NULL COMMENT '规格' AFTER `cargo_type`, + ADD COLUMN `model` varchar(255) DEFAULT NULL COMMENT '型号' AFTER `specification`, + ADD COLUMN `quantity` decimal(18,2) DEFAULT NULL COMMENT '数量' AFTER `model`, + ADD COLUMN `quantity_unit` varchar(50) DEFAULT NULL COMMENT '数量单位' AFTER `quantity`, + ADD COLUMN `departure_address_id` bigint(20) DEFAULT NULL COMMENT '发货地址ID' AFTER `quantity_unit`, + ADD COLUMN `departure_name` varchar(100) DEFAULT NULL COMMENT '发货地' AFTER `departure_address_id`, + ADD COLUMN `departure_contact` varchar(100) DEFAULT NULL COMMENT '发货联系人' AFTER `departure_address`, + ADD COLUMN `departure_phone` varchar(50) DEFAULT NULL COMMENT '发货联系方式' AFTER `departure_contact`, + ADD COLUMN `arrival_address_id` bigint(20) DEFAULT NULL COMMENT '收货地址ID' AFTER `departure_phone`, + ADD COLUMN `arrival_name` varchar(100) DEFAULT NULL COMMENT '收货地' AFTER `arrival_address_id`, + ADD COLUMN `arrival_contact` varchar(100) DEFAULT NULL COMMENT '收货联系人' AFTER `arrival_address`, + ADD COLUMN `arrival_phone` varchar(50) DEFAULT NULL COMMENT '收货联系方式' AFTER `arrival_contact`, + ADD COLUMN `task_entry_mode` varchar(50) DEFAULT NULL COMMENT '任务录入模式' AFTER `arrival_phone`, + ADD COLUMN `carrier_type` varchar(50) DEFAULT NULL COMMENT '承运类型' AFTER `task_entry_mode`, + ADD COLUMN `carrier_id` bigint(20) DEFAULT NULL COMMENT '承运商ID' AFTER `carrier_type`, + ADD COLUMN `driver_id` bigint(20) DEFAULT NULL COMMENT '司机ID' AFTER `carrier_name`, + ADD COLUMN `driver_phone` varchar(50) DEFAULT NULL COMMENT '司机手机号' AFTER `driver_name`, + ADD COLUMN `trailer_vehicle_no` varchar(100) DEFAULT NULL COMMENT '挂车车牌号' AFTER `vehicle_no`, + ADD COLUMN `escort_name` varchar(100) DEFAULT NULL COMMENT '押运人' AFTER `trailer_vehicle_no`, + ADD COLUMN `escort_phone` varchar(50) DEFAULT NULL COMMENT '押运人手机号' AFTER `escort_name`, + ADD COLUMN `mileage` decimal(18,2) DEFAULT NULL COMMENT '里程' AFTER `escort_phone`, + ADD COLUMN `estimated_start_time` datetime DEFAULT NULL COMMENT '预计发货时间' AFTER `mileage`, + ADD COLUMN `estimated_end_time` datetime DEFAULT NULL COMMENT '预计完成时间' AFTER `estimated_start_time`, + ADD COLUMN `unit_price` decimal(18,2) DEFAULT NULL COMMENT '单价' AFTER `estimated_end_time`, + ADD COLUMN `price_unit` varchar(50) DEFAULT NULL COMMENT '计价单位' AFTER `unit_price`, + ADD COLUMN `other_fee_total` decimal(18,2) DEFAULT NULL COMMENT '其他费用合计' AFTER `price_unit`, + ADD COLUMN `task_remark` varchar(200) DEFAULT NULL COMMENT '任务备注' AFTER `other_fee_total`, + ADD COLUMN `task_info_json` text DEFAULT NULL COMMENT '任务信息' AFTER `carrier_json`, + MODIFY COLUMN `remark` varchar(200) DEFAULT NULL COMMENT '备注';