diff --git a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/entity/Dept.java b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/entity/Dept.java index 15a3e94a..e5958c76 100644 --- a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/entity/Dept.java +++ b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/entity/Dept.java @@ -150,6 +150,13 @@ public class Dept extends TenantEntity { @Schema(description = "合同编码前缀") private String contractCodePrefix; + /** + * 合同初始流水号(该部门当年首个合同时从此号起编,范围1-999) + */ + @Schema(description = "合同初始流水号") + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private Integer contractInitialSerial; + /** * 组织简称 */ diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/PaymentApplicationSaveRequest.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/PaymentApplicationSaveRequest.java index d608d7cb..d69690f8 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/PaymentApplicationSaveRequest.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/PaymentApplicationSaveRequest.java @@ -36,6 +36,8 @@ public class PaymentApplicationSaveRequest implements Serializable { private List settlementIds; private Long preSettlementId; private List preSettlementIds; + /** 预结算单号。驳回重提时用于在主键失真后找回预结算单 */ + private String preSettlementNo; private Long projectId; private String projectName; private Long deptId; diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/PersonCertSubmitRequest.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/PersonCertSubmitRequest.java index c33d030f..01cc4f53 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/PersonCertSubmitRequest.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/PersonCertSubmitRequest.java @@ -51,6 +51,12 @@ public class PersonCertSubmitRequest implements Serializable { private String realName; @Schema(description = "身份证号") private String idCardNo; + @Schema(description = "出生年月") + private LocalDate birthday; + @Schema(description = "住址-区划:省市区名称拼接串") + private String addressRegion; + @Schema(description = "住址-详细地址") + private String address; @Schema(description = "身份证正面照") private String idCardFrontUrl; @Schema(description = "身份证反面照") diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/AdminWaybillCardVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/AdminWaybillCardVO.java index 9e63df31..b0dca1a4 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/AdminWaybillCardVO.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/AdminWaybillCardVO.java @@ -92,4 +92,30 @@ public class AdminWaybillCardVO implements Serializable { @Schema(description = "采购方是否已付款(预留)") private Boolean buyerPaid; + /* ===== 运次(一趟车)字段:列表按运次下发,卡片的展示单号与路线取这些 ===== */ + + @Schema(description = "运次主键:配载单主键或运单主键,按字符串下发(19 位雪花号按数字下发会被前端取整)") + private String tripId; + + @Schema(description = "配载单号,未配载为空") + private String loadingNo; + + @Schema(description = "展示单号:有配载取配载单号,否则取运单号") + private String tripNo; + + @Schema(description = "途经站点:整趟车中间各站按顺序以「 → 」连接,无中间站为空") + private String viaName; + + @Schema(description = "站点数(整趟车,含起终点)") + private Integer stationCount; + + @Schema(description = "货物汇总(整趟车,同名货物合并且单位一致时取数量合计)") + private String cargoSummary; + + @Schema(description = "合计重量(整趟车同单位时才有值,单位不一致为空)") + private String totalWeight; + + @Schema(description = "子运单数(可见口径;配载运次即整趟车的子单数)") + private Integer waybillCount; + } diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/AdminWaybillDetailVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/AdminWaybillDetailVO.java index ebbd9cce..ff601f14 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/AdminWaybillDetailVO.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/AdminWaybillDetailVO.java @@ -66,9 +66,12 @@ public class AdminWaybillDetailVO implements Serializable { @Schema(description = "卸货地址") private String unloadAddress; - @Schema(description = "货物名称") + @Schema(description = "货物名称:运单上录入的全部货名以「、」连接") private String cargoName; + @Schema(description = "货物名称清单:运单上录入的全部货物,同名的只留一次") + private List cargoNames; + @Schema(description = "货物数量(带单位)") private String cargoQuantity; @@ -135,6 +138,29 @@ public class AdminWaybillDetailVO implements Serializable { @Schema(description = "途打卡记录") private List enrouteRecords; + /* ===== 运次(一趟车)字段:详情按运次下发,与司机端接单页同一口径 ===== */ + + @Schema(description = "运次主键:配载单主键或运单主键,按字符串下发(19 位雪花号按数字下发会被前端取整)") + private String tripId; + + @Schema(description = "配载单号,未配载为空") + private String loadingNo; + + @Schema(description = "展示单号:有配载取配载单号,否则取运单号") + private String tripNo; + + @Schema(description = "途经站点:整趟车中间各站按顺序以「 → 」连接,无中间站为空") + private String viaName; + + @Schema(description = "站点数(整趟车,含起终点)") + private Integer stationCount; + + @Schema(description = "货物汇总(整趟车,同名货物合并且单位一致时取数量合计)") + private String cargoSummary; + + @Schema(description = "站点任务安排(整趟车按实际途经顺序,第 1 站即 ①;配载运次含每站装卸与货物条目)") + private List stations; + @Data @Schema(description = "装卸点") public static class AdminRoutePointVO implements Serializable { diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/CargoLineVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/CargoLineVO.java new file mode 100644 index 00000000..831e6313 --- /dev/null +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/CargoLineVO.java @@ -0,0 +1,48 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.transport.pojo.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 货物明细行:一条货物一行 + *

+ * 详情页逐行展示「货物名称 + 数量单位」,名称与数量分开给,前端不需要再从汇总串里拆。 + */ +@Data +@Schema(description = "货物明细行") +public class CargoLineVO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @Schema(description = "货物名称") + private String cargoName; + + @Schema(description = "数量(含单位,如 1000吨);录入时只填了名称则为空") + private String quantity; +} diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/DriverPunchNodeVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/DriverPunchNodeVO.java index 03f85510..eaeec543 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/DriverPunchNodeVO.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/DriverPunchNodeVO.java @@ -67,9 +67,15 @@ public class DriverPunchNodeVO implements Serializable { @Schema(description = "不可打卡的原因,供司机端展示") private String lockReason; + @Schema(description = "运单已结束(完成/取消):卡片仅展示、不可提交") + private Boolean ended; + @Schema(description = "是否展示该卡(在途可能因频次/时段隐藏)") private Boolean visible; + @Schema(description = "打这张卡即完成运单:该运单的末位可打卡节点,且过程配置不要求业务员确认回单") + private Boolean finishOnPunch; + @Schema(description = "是否默认展开(仅第一个可打卡节点)") private Boolean defaultExpanded; diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/DriverWaybillCardVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/DriverWaybillCardVO.java index 323db157..23432c6a 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/DriverWaybillCardVO.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/DriverWaybillCardVO.java @@ -67,6 +67,9 @@ public class DriverWaybillCardVO implements Serializable { @Schema(description = "货物类别") private String cargoCategory; + @Schema(description = "货物明细(详情返回):一条一行,同名同单位的货物已合并数量") + private List cargos; + @Schema(description = "重量(带单位)") private String weight; @@ -153,6 +156,26 @@ public class DriverWaybillCardVO implements Serializable { @Schema(description = "运输组织:common普通 / load配载") private String transportOrgType; + /* ===== 运次(一趟车)字段:运次接口返回,旧运单接口不填 ===== */ + + @Schema(description = "运次主键:配载单主键或运单主键,按字符串下发(19 位雪花号按数字下发会被前端取整)") + private String tripId; + + @Schema(description = "配载单号,未配载为空") + private String loadingNo; + + @Schema(description = "展示单号:有配载取配载单号,否则取运单号") + private String tripNo; + + @Schema(description = "途经站点:整趟车中间各站按顺序以「 → 」连接") + private String viaName; + + @Schema(description = "站点数(整趟车,含起终点)") + private Integer stationCount; + + @Schema(description = "货物汇总(整趟车,同名货物合并且单位一致时取数量合计)") + private String cargoSummary; + @Schema(description = "计划发货时间") private String planShipTime; @@ -168,6 +191,9 @@ public class DriverWaybillCardVO implements Serializable { @Schema(description = "装卸点(详情返回)") private List routePoints; + @Schema(description = "站点任务安排(详情返回,按实际途经顺序,第 1 站即 ①;有配载时含每站装卸与货物条目)") + private List stations; + @Schema(description = "过程配置 JSON(详情返回,供前端兜底推导打卡节点)") private String processJson; diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/ReceiptFlowVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/ReceiptFlowVO.java index f32a9491..3f105412 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/ReceiptFlowVO.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/ReceiptFlowVO.java @@ -39,6 +39,7 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; +import java.util.Map; /** * 收款流水视图实体类 @@ -91,4 +92,9 @@ public class ReceiptFlowVO extends KingdeeReceiptFlow { private BigDecimal receiptAmountEnd; @TableField(exist = false) private List claimRecords = new ArrayList<>(); + /** + * 本流水历史关联且仍有剩余可认领金额的应收正式结算单(继续认领时预填) + */ + @TableField(exist = false) + private List> relatedSettlements = new ArrayList<>(); } diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/TripQueryVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/TripQueryVO.java new file mode 100644 index 00000000..5027ada9 --- /dev/null +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/TripQueryVO.java @@ -0,0 +1,104 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.transport.pojo.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; +import java.util.List; +import java.util.Set; + +/** + * 运次查询条件 + *

+ * 司机端用 {@link #forDriver(String)} 按司机手机号收口;管理端用 {@link #forAdmin()} 不按司机收口。 + * 两者都不给时收口为空集——收口一旦缺省就等于把别人的运次发出去,故缺省不放行。 + */ +@Data +@Schema(description = "运次查询条件") +public class TripQueryVO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @Schema(description = "运次主键(详情按它取单个运次)") + private String tripId; + + @Schema(description = "司机手机号") + private String driverPhone; + + @Schema(description = "是否不按司机收口(管理端)") + private boolean allDrivers; + + @Schema(description = "可见组织 ID 集合:非空时只出这些组织的运单行,空集合表示不按组织过滤") + private List deptIds; + + @Schema(description = "状态:0待接单/1进行中/2已完成/3已取消,不传为全部(不含已取消)") + private Integer status; + + @Schema(description = "多档状态:同上取值,非空时优先于 status(待处理列表要待接单与进行中两档)") + private List statuses; + + @Schema(description = "关键字:运单号 / 配载单号 / 起终点 / 车牌") + private String keyword; + + @Schema(description = "运输组织类型:load 公路配载 / common 普通运输,不传为全部(按子单行筛选)") + private String transportOrgType; + + @Schema(description = "异常筛选:exception 只看命中异常的子单 / normal 只看未命中的,不传为全部(按子单行筛选)") + private String exception; + + @Schema(description = "有未完成异常的子运单主键集合,配合 exception 使用(按子单行筛选)") + private Set exceptionWaybillIds; + + @Schema(description = "创建时间下界,按子单行筛选") + private Date createdFrom; + + @Schema(description = "创建时间上界,按子单行筛选") + private Date createdTo; + + @Schema(description = "是否排除「有子单被拒且这趟车还没结束」的运次(待处理列表用)") + private boolean excludeRejectedTrips; + + @Schema(description = "当前页,从 1 开始") + private Integer current; + + @Schema(description = "每页条数(按运次,不是运单)") + private Integer size; + + public static TripQueryVO forDriver(String driverPhone) { + TripQueryVO query = new TripQueryVO(); + query.setDriverPhone(driverPhone); + return query; + } + + public static TripQueryVO forAdmin() { + TripQueryVO query = new TripQueryVO(); + query.setAllDrivers(true); + return query; + } + +} diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/TripStationVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/TripStationVO.java new file mode 100644 index 00000000..7b795898 --- /dev/null +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/TripStationVO.java @@ -0,0 +1,105 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.transport.pojo.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.List; + +/** + * 运次站点(任务安排) + *

+ * 有配载单时按配载单的路线信息给出实际途经顺序,第 1 站即 ①;站点上的「装N / 卸N」指向配载单运单集合里的第 N 张子运单, + * 同一站可以既卸又装(装卸)。没有路线信息时回退到子运单的起讫点去重成站。 + *

+ * 站点上的货物条目取子运单,不取配载单的货物信息:配载单的货物条数与子运单不一定同数,行序也对不上站点编号。 + */ +@Data +@Schema(description = "运次站点") +public class TripStationVO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @Schema(description = "站序,从 1 开始(展示为 ①②③)") + private Integer index; + + @Schema(description = "站名:展示地址优先,回退详细地址") + private String name; + + @Schema(description = "详细地址") + private String address; + + @Schema(description = "站点标记原文:装N / 卸N") + private List tags; + + @Schema(description = "操作类型:load 装货 / unload 卸货 / load_unload 装卸,无从判定时为空") + private String operationType; + + @Schema(description = "操作类型文案:装货 / 卸货 / 装卸") + private String operationTypeLabel; + + @Schema(description = "该站涉及的货物条目") + private List cargos; + + /** + * 站点货物条目 + */ + @Data + @Schema(description = "运次站点货物条目") + public static class TripCargoVO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @Schema(description = "所属子运单主键,按字符串下发") + private String waybillId; + + @Schema(description = "所属运单号") + private String waybillNo; + + @Schema(description = "货物名称") + private String cargoName; + + @Schema(description = "货主名称") + private String customerName; + + @Schema(description = "数量") + private BigDecimal quantity; + + @Schema(description = "数量单位") + private String quantityUnit; + + @Schema(description = "该站的操作:load 装货 / unload 卸货") + private String operation; + + @Schema(description = "项目名称") + private String projectName; + + } + +} diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/TripTabCountsVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/TripTabCountsVO.java new file mode 100644 index 00000000..944c4ded --- /dev/null +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/TripTabCountsVO.java @@ -0,0 +1,55 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.transport.pojo.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 运次 Tab 统计 + *

+ * 与司机端运单统计字段同名同义,但计数单位是运次(一卡一趟车),不是运单。 + */ +@Data +@Schema(description = "运次 Tab 统计") +public class TripTabCountsVO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @Schema(description = "全部(待接单+进行中+已完成,不含已取消)") + private long all; + + @Schema(description = "待接单") + private long pending; + + @Schema(description = "进行中") + private long doing; + + @Schema(description = "已完成") + private long done; + +} diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/TripVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/TripVO.java new file mode 100644 index 00000000..a6523dfb --- /dev/null +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/TripVO.java @@ -0,0 +1,236 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.transport.pojo.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.Date; +import java.util.List; + +/** + * 运次:一趟车的展示与操作单位 + *

+ * 有配载单时就是那张配载单连同它的全部子运单,没配载时就是一张运单;它没有自己的表。 + * 状态与接单需求都由子运单读时聚合,配载单上不落这两个字段。 + */ +@Data +@Schema(description = "运次") +public class TripVO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @Schema(description = "运次主键:配载单主键或运单主键,按字符串下发(19 位雪花号按数字下发会被前端取整)") + private String id; + + @Schema(description = "展示单号:有配载取配载单号,否则取运单号") + private String tripNo; + + @Schema(description = "配载单号,未配载为空") + private String loadingNo; + + @Schema(description = "运输组织类型:load 公路配载 / common 普通运输") + private String transportOrgType; + + @Schema(description = "状态:0待接单/1运输中/2已完成/3已取消,子单集合为空时为空") + private Integer status; + + @Schema(description = "排序键:运次内子运单 create_time 的最大值") + private Date sortTime; + + @Schema(description = "是否需要接单:任一子运单的项目有接单节点即为是") + private Boolean requireAccept; + + @Schema(description = "接单态:accepted 已接单 / pending 待接单,无从判定时为空") + private String acceptStatus; + + @Schema(description = "子运单数(可见口径:已排除本司机拒单的子单)") + private int waybillCount; + + @Schema(description = "司机姓名") + private String driverName; + + @Schema(description = "司机手机号") + private String driverPhone; + + @Schema(description = "车牌号") + private String vehicleNo; + + @Schema(description = "挂车车牌号") + private String trailerVehicleNo; + + @Schema(description = "项目名称") + private String projectName; + + @Schema(description = "客户名称") + private String customerName; + + @Schema(description = "运输类型") + private String transportType; + + @Schema(description = "货物名称:整趟车全部货名以「、」连接,一格多名的历史写法已摊平") + private String cargoName; + + @Schema(description = "货物名称清单:整趟车全部货物,同名的只留一次,保持子单与录入顺序") + private List cargoNames; + + @Schema(description = "货物类型:整趟车全部类型以「、」连接") + private String cargoType; + + @Schema(description = "货物类型清单:整趟车全部货物类型,同类型的只留一次") + private List cargoTypes; + + @Schema(description = "发货地址") + private String departureAddress; + + @Schema(description = "途经地") + private String transitAddress; + + @Schema(description = "到货地") + private String arrivalAddress; + + @Schema(description = "起点名称:整趟车首站,展示地址优先、回退详细地址") + private String departureName; + + @Schema(description = "途经站点:中间各站按顺序以「 → 」连接,无中间站为空") + private String transitName; + + @Schema(description = "终点名称:整趟车末站,展示地址优先、回退详细地址") + private String arrivalName; + + @Schema(description = "站点数(整趟车,含起终点)") + private int stationCount; + + @Schema(description = "货物汇总:按货物名合并数量,同名同单位取合计") + private String cargoSummary; + + @Schema(description = "货物明细:一条货物一行,同名同单位的货物已合并数量") + private List cargos; + + @Schema(description = "合计重量:全部子运单同单位时为数量合计,单位不一致时为空") + private BigDecimal totalQuantity; + + @Schema(description = "合计重量的单位,与 totalQuantity 同时为空或同时有值") + private String quantityUnit; + + @Schema(description = "计重合计:全部货物同单位时取该单位合计,单位不一致时只合计「吨」项(其余单位不参与);无可用合计时为空") + private BigDecimal weighingQuantity; + + @Schema(description = "计重合计的单位,与 weighingQuantity 同时为空或同时有值") + private String weighingUnit; + + @Schema(description = "计划发货日期(配载单,未配载为空)") + private LocalDate estimatedStartDate; + + @Schema(description = "计划完成日期(配载单,未配载为空)") + private LocalDate estimatedEndDate; + + @Schema(description = "任务备注(配载单的运输说明)") + private String taskRemark; + + @Schema(description = "路线信息,仅单个运次详情返回(列表不带,省流量)") + private String routeJson; + + @Schema(description = "货物信息,仅单个运次详情返回(列表不带,省流量)") + private String goodsJson; + + @Schema(description = "站点(任务安排,按实际途经顺序),仅单个运次详情返回;列表用起/经/终与站点数") + private List stations; + + @Schema(description = "子运单(可见子单,按创建时间升序)") + private List waybills; + + /** + * 运次下的子运单 + *

+ * 接单事实仍落在每张子运单上,运次只是把它们的需要接单与接单态聚合起来。 + */ + @Data + @Schema(description = "运次下的子运单") + public static class TripWaybillVO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @Schema(description = "运单主键,按字符串下发") + private String id; + + @Schema(description = "运单号") + private String waybillNo; + + @Schema(description = "业务状态") + private String businessStatus; + + @Schema(description = "接单状态:pending待接单/accepted已接单/rejected已拒绝") + private String acceptStatus; + + @Schema(description = "该运单是否需要接单确认") + private Boolean requireAccept; + + @Schema(description = "项目ID") + private Long projectId; + + @Schema(description = "项目名称") + private String projectName; + + @Schema(description = "货物名称:运单上录入的全部货名以「、」连接,一格多名的历史写法已摊平") + private String cargoName; + + @Schema(description = "货物名称清单:运单上录入的全部货物,同名的只留一次") + private List cargoNames; + + @Schema(description = "货物类型:主表首行的类型,完整清单见 cargoTypes") + private String cargoType; + + @Schema(description = "货物类型清单:运单上录入的全部货物类型,同类型的只留一次") + private List cargoTypes; + + @Schema(description = "数量") + private BigDecimal quantity; + + @Schema(description = "数量单位") + private String quantityUnit; + + @Schema(description = "发货地") + private String departureName; + + @Schema(description = "发货地址") + private String departureAddress; + + @Schema(description = "收货地") + private String arrivalName; + + @Schema(description = "收货地址") + private String arrivalAddress; + + @Schema(description = "创建时间") + private Date createTime; + + } + +} diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/WaybillImportBatchVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/WaybillImportBatchVO.java index 841236aa..b30c1973 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/WaybillImportBatchVO.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/WaybillImportBatchVO.java @@ -20,6 +20,12 @@ public class WaybillImportBatchVO extends WaybillImportBatch { private static final long serialVersionUID = 1L; private String importTypeName; private String statusName; + /** 结算状态:unsettled/partial/settled */ + @Schema(description = "结算状态") + private String settlementStatus; + /** 结算状态名称:未结算/部分结算/已结算 */ + @Schema(description = "结算状态名称") + private String settlementStatusName; private String createUserName; private String updateUserName; } 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 ebba5343..a2ecf024 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 @@ -29,6 +29,7 @@ import lombok.EqualsAndHashCode; import org.springblade.transport.pojo.entity.Waybill; import java.io.Serial; +import java.util.List; /** * 运单管理视图实体类 @@ -78,4 +79,12 @@ public class WaybillVO extends Waybill { @Schema(description = "是否仅查询未配载运单") private Integer onlyUnassignedLoading; + @TableField(exist = false) + @Schema(description = "是否仅查询可配载运单") + private Integer loadableOnly; + + @TableField(exist = false) + @Schema(description = "运单主键集合") + private List ids; + } diff --git a/blade-service-api/blade-user-api/src/main/java/org/springblade/system/pojo/entity/User.java b/blade-service-api/blade-user-api/src/main/java/org/springblade/system/pojo/entity/User.java index 398ba356..d20b5bb8 100644 --- a/blade-service-api/blade-user-api/src/main/java/org/springblade/system/pojo/entity/User.java +++ b/blade-service-api/blade-user-api/src/main/java/org/springblade/system/pojo/entity/User.java @@ -132,6 +132,10 @@ public class User extends TenantEntity implements IPhone { * 是否是oa的账号 */ private Integer isOa; + /** + * OA登录名 + */ + private String oaLoginId; /** * OA上次更新时间(OA 的 modified 字段),仅同步比对使用 */ diff --git a/blade-service/blade-system/src/main/java/org/springblade/process/controller/BusinessProcessController.java b/blade-service/blade-system/src/main/java/org/springblade/process/controller/BusinessProcessController.java index f65cc45e..2cf6ad79 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/process/controller/BusinessProcessController.java +++ b/blade-service/blade-system/src/main/java/org/springblade/process/controller/BusinessProcessController.java @@ -11,7 +11,6 @@ import lombok.AllArgsConstructor; import org.springblade.core.boot.ctrl.BladeController; import org.springblade.core.mp.support.Condition; import org.springblade.core.mp.support.Query; -import org.springblade.core.secure.utils.AuthUtil; import org.springblade.core.tool.api.R; import org.springblade.core.tool.utils.StringUtil; import org.springblade.process.pojo.dto.ApprovalDTO; @@ -49,7 +48,6 @@ public class BusinessProcessController extends BladeController { if (param == null) { param = new ApprovalDTO(); } - param.setLoginName(AuthUtil.getUserAccount()); IPage pages = businessProcessService.queryMkApprovalList(Condition.getPage(query), param); return R.data(pages); } diff --git a/blade-service/blade-system/src/main/java/org/springblade/process/service/IBusinessProcessService.java b/blade-service/blade-system/src/main/java/org/springblade/process/service/IBusinessProcessService.java index 36b15ce0..ae11c22d 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/process/service/IBusinessProcessService.java +++ b/blade-service/blade-system/src/main/java/org/springblade/process/service/IBusinessProcessService.java @@ -71,7 +71,7 @@ public interface IBusinessProcessService extends IService { * 获取流程当前节点详情 * * @param processInstanceId 流程实例id - * @param loginName MK登录名(手机号),可为空,为空时按流程发起人解析 + * @param loginName MK登录名(OA登录名),可为空,为空时按流程发起人解析 * @return 当前节点列表 */ List getCurrentNodes(String processInstanceId, String loginName); @@ -80,7 +80,7 @@ public interface IBusinessProcessService extends IService { * 获取流程实例详情 * * @param processInstanceId 流程实例id - * @param loginName MK登录名(手机号),可为空,为空时按流程发起人解析 + * @param loginName MK登录名(OA登录名),可为空,为空时按流程发起人解析 * @return 流程实例详情 */ Object getProcessInfo(String processInstanceId, String loginName); diff --git a/blade-service/blade-system/src/main/java/org/springblade/process/service/impl/BusinessProcessServiceImpl.java b/blade-service/blade-system/src/main/java/org/springblade/process/service/impl/BusinessProcessServiceImpl.java index fed83a70..42326194 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/process/service/impl/BusinessProcessServiceImpl.java +++ b/blade-service/blade-system/src/main/java/org/springblade/process/service/impl/BusinessProcessServiceImpl.java @@ -132,9 +132,9 @@ public class BusinessProcessServiceImpl extends ServiceImpl queryMkApprovalList(IPage page, ApprovalDTO param) { + String loginName = resolveCurrentUserOaLoginId(); + AssertUtils.notBlank(loginName, "当前用户OA登录名为空,无法查询审核列表"); + param.setLoginName(loginName); if (MKDoc.RELATED.getCode().equals(param.getDocType())) { // 我参与的,调用流程列表接口 MKProcessDTO processParam = approvalConvert.dto2mk(param); diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/convert/DeptConvert.java b/blade-service/blade-system/src/main/java/org/springblade/system/convert/DeptConvert.java index ba5bdf43..dc045868 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/convert/DeptConvert.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/convert/DeptConvert.java @@ -49,8 +49,8 @@ public interface DeptConvert { @Mapping(source = "modified", target = "oaModified") Dept baseConvert(OACompanyResponse company); - @Mapping(source = "departmentname", target = "deptName") - @Mapping(source = "departmentmark", target = "fullName") + @Mapping(source = "departmentmark", target = "deptName") + @Mapping(source = "departmentname", target = "fullName") @Mapping(source = "supdepid", target = "parentCode") @Mapping(source = "subcompanyid1", target = "belongCompanyCode") @Mapping(source = "created", target = "createTime", dateFormat = "yyyy-MM-dd HH:mm:ss") diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/convert/UserConvert.java b/blade-service/blade-system/src/main/java/org/springblade/system/convert/UserConvert.java index 00b2e576..2dabbf04 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/convert/UserConvert.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/convert/UserConvert.java @@ -70,6 +70,7 @@ public interface UserConvert { @Mapping(source = "lastname", target = "realName") @Mapping(source = "mobile", target = "phone") @Mapping(source = "email", target = "email") + @Mapping(source = "loginid", target = "oaLoginId") @Mapping(source = "modified", target = "oaModified") User baseConvert(OAPersonResponse person); @@ -147,7 +148,7 @@ public interface UserConvert { /** * oa人员转本系统用户部门 * @param person - * @param userMap 所有用户手机号与id map + * @param userMap OA登录名与用户id * @return */ default UserDept person2userDept(OAPersonResponse person, Map userMap) { @@ -161,8 +162,9 @@ public interface UserConvert { userDept.setStatus(status); // 排序 userDept.setSort(OAUtils.parseInt(person.getDsporder())); - // 用户id - userDept.setUserId(userMap.get(resolveAccount(person))); + // 用户id,按 OA 登录名关联本批已新增或更新的用户 + String oaLoginId = person == null ? null : StringUtils.trimToNull(person.getLoginid()); + userDept.setUserId(oaLoginId == null ? null : userMap.get(oaLoginId)); userDept.setSyncTime(new Date()); return userDept; } diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/mapper/DeptMapper.xml b/blade-service/blade-system/src/main/java/org/springblade/system/mapper/DeptMapper.xml index f45626dd..bfbaf61f 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/mapper/DeptMapper.xml +++ b/blade-service/blade-system/src/main/java/org/springblade/system/mapper/DeptMapper.xml @@ -14,6 +14,7 @@ + @@ -36,6 +37,7 @@ + diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/mapper/UserMapper.xml b/blade-service/blade-system/src/main/java/org/springblade/system/mapper/UserMapper.xml index 1cdec59f..6921159c 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/mapper/UserMapper.xml +++ b/blade-service/blade-system/src/main/java/org/springblade/system/mapper/UserMapper.xml @@ -26,6 +26,7 @@ + diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/DeptServiceImpl.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/DeptServiceImpl.java index 3dda7348..dd24564f 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/DeptServiceImpl.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/DeptServiceImpl.java @@ -687,6 +687,7 @@ public class DeptServiceImpl extends ServiceImpl implements ID } validateDeptCategory(dept); validateDeptCode(dept, parent); + validateContractInitialSerial(dept); return saveOrUpdate(dept); } @@ -874,6 +875,16 @@ public class DeptServiceImpl extends ServiceImpl implements ID } } + private void validateContractInitialSerial(Dept dept) { + Integer serial = dept.getContractInitialSerial(); + if (serial == null) { + return; + } + if (serial < 1 || serial > 999) { + throw new ServiceException("合同初始流水号范围为1到999"); + } + } + private void validateDeptCode(Dept dept, Dept parent) { String deptCode = dept.getDeptCode(); // 组织编码非必填,为空时存 null,避免唯一索引冲突 diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/OASyncServiceImpl.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/OASyncServiceImpl.java index e133b6c5..90588373 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/OASyncServiceImpl.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/OASyncServiceImpl.java @@ -761,7 +761,7 @@ public class OASyncServiceImpl implements IOASyncService { .filter(oaDept -> syncAll || rootCompanyId == null || !rootCompanyId.equals(oaDept.getSubcompanyid1()) - || (OAConstant.ROOT_COMPANY_ID.equals(oaDept.getSupdepid()) && rootCompanyDeptNames.contains(oaDept.getDepartmentname()))) + || (OAConstant.ROOT_COMPANY_ID.equals(oaDept.getSupdepid()) && rootCompanyDeptNames.contains(oaDept.getDepartmentmark()))) .map(dept -> deptConvert.dept2dept(dept, companyByOaId)) .toList(); int filteredSkipCount = oaDepts.size() - allParam.size(); @@ -921,8 +921,9 @@ public class OASyncServiceImpl implements IOASyncService { .eq(Dept::getDeptCategory, deptCategory.getCode()) .isNotNull(Dept::getOaId) .ne(Dept::getOaId, "") - .select(Dept::getId, Dept::getDeptCode, Dept::getOaModified, Dept::getParentId, - Dept::getOaId, Dept::getBelongCompanyCode, Dept::getParentCode, Dept::getOaSupSubComId)); + .select(Dept::getId, Dept::getDeptCode, Dept::getDeptName, Dept::getFullName, + Dept::getOaModified, Dept::getParentId, Dept::getOaId, Dept::getBelongCompanyCode, + Dept::getParentCode, Dept::getOaSupSubComId)); Map byOaId = new HashMap<>(); for (Dept dept : deptList) { if (StringUtils.isNotBlank(dept.getOaId())) { @@ -1047,7 +1048,7 @@ public class OASyncServiceImpl implements IOASyncService { /** * oaModified 一致则跳过更新;任一侧为空无法确认一致,执行更新以补齐字段。 - * 组织编码已按新规则重算、或父子挂载与库中不一致时,即使 modified 相同也要更新。 + * 组织编码已按新规则重算、父子挂载不一致、或名称字段与当前映射不一致时,即使 modified 相同也要更新。 */ private boolean shouldSkipOaUpdate(Dept existing, Dept incoming) { if (existing == null) { @@ -1066,6 +1067,14 @@ public class OASyncServiceImpl implements IOASyncService { StringUtils.trimToEmpty(incoming.getParentCode()))) { return false; } + if (incoming != null && !StringUtils.equals(StringUtils.trimToEmpty(existing.getDeptName()), + StringUtils.trimToEmpty(incoming.getDeptName()))) { + return false; + } + if (incoming != null && !StringUtils.equals(StringUtils.trimToEmpty(existing.getFullName()), + StringUtils.trimToEmpty(incoming.getFullName()))) { + return false; + } String localModified = StringUtils.trimToEmpty(existing.getOaModified()); String remoteModified = StringUtils.trimToEmpty(incoming == null ? null : incoming.getOaModified()); if (localModified.isEmpty() || remoteModified.isEmpty()) { diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/OaUserListSyncHelper.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/OaUserListSyncHelper.java index 87e3cd50..46f8af2b 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/OaUserListSyncHelper.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/OaUserListSyncHelper.java @@ -403,70 +403,37 @@ public class OaUserListSyncHelper { Map uniquePersonMap = new LinkedHashMap<>(); int skippedCount = 0; for (OAPersonResponse oaPerson : oaPersons) { - String account = userConvert.resolveAccount(oaPerson); - if (StringUtils.isBlank(account)) { + String oaLoginId = resolveOaLoginId(oaPerson); + if (StringUtils.isBlank(oaLoginId)) { skippedCount++; continue; } - uniquePersonMap.putIfAbsent(account, oaPerson); + uniquePersonMap.put(oaLoginId, oaPerson); } if (uniquePersonMap.isEmpty()) { - ComposeLogUtil.getLastLog().warn("OA人员均缺少loginid/工号/手机号,跳过人员同步"); + ComposeLogUtil.getLastLog().warn("OA人员均缺少登录名,跳过人员同步"); return new PersonSyncCount(0, skippedCount); } String tenantId = resolveTenantId(); String defaultPassword = DigestUtil.encrypt(ParamCache.getValue(DEFAULT_PARAM_PASSWORD)); - List users = uniquePersonMap.values().stream() - .map(person -> { - User user = userConvert.person2user(person, defaultPassword); + List users = uniquePersonMap.entrySet().stream() + .map(entry -> { + User user = userConvert.person2user(entry.getValue(), defaultPassword); user.setTenantId(tenantId); + user.setOaLoginId(entry.getKey()); + user.setAccount(entry.getKey()); return user; }) .toList(); - List accounts = users.stream() - .map(User::getAccount) - .filter(StringUtils::isNotBlank) - .distinct() - .toList(); - List phones = users.stream() - .map(User::getPhone) - .filter(StringUtils::isNotBlank) - .distinct() - .toList(); - Map existingByAccount = new HashMap<>(); - Map existingByPhone = new HashMap<>(); - userService.list(Wrappers.lambdaQuery() - .and(wrapper -> { - wrapper.in(User::getAccount, accounts); - if (CollectionUtil.isNotEmpty(phones)) { - wrapper.or().in(User::getPhone, phones); - } - }) - ).stream() - .peek(userService::decryptPhone) - .forEach(user -> { - if (StringUtils.isNotBlank(user.getAccount())) { - existingByAccount.put(user.getAccount(), user); - } - if (StringUtils.isNotBlank(user.getPhone())) { - existingByPhone.put(user.getPhone(), user); - } - }); + Map existingByOaLoginId = this.loadUsersByOaLoginId(tenantId, uniquePersonMap.keySet()); Map userMap = new HashMap<>(); Set existsUserIds = new HashSet<>(); - Set unchangedUserIds = new HashSet<>(); users.forEach(user -> { - User existingUser = existingByAccount.get(user.getAccount()); - if (existingUser == null && StringUtils.isNotBlank(user.getPhone())) { - existingUser = existingByPhone.get(user.getPhone()); - } + User existingUser = existingByOaLoginId.get(user.getOaLoginId()); if (existingUser != null) { user.setId(existingUser.getId()); user.setPassword(null); existsUserIds.add(existingUser.getId()); - if (shouldSkipOaUpdate(existingUser, user)) { - unchangedUserIds.add(existingUser.getId()); - } } else { user.setId(DefaultIdentifierGenerator.getInstance().nextId(null)); user.setPostId("-1"); @@ -474,9 +441,9 @@ public class OaUserListSyncHelper { user.setDataScopeRange(1); user.setDataLevelRange(1); user.setIncludeNewCustomer(0); - userService.encryptPhone(user); } - userMap.put(user.getAccount(), user.getId()); + userService.encryptPhone(user); + userMap.put(user.getOaLoginId(), user.getId()); }); List addUsers = users.stream() @@ -488,18 +455,13 @@ public class OaUserListSyncHelper { } List updateUsers = users.stream() .filter(user -> existsUserIds.contains(user.getId())) - .filter(user -> !unchangedUserIds.contains(user.getId())) .toList(); if (CollectionUtil.isNotEmpty(updateUsers)) { ComposeLogUtil.getLastLog().info("批量修改用户:{}", updateUsers.size()); userService.updateBatchById(updateUsers); } - if (!unchangedUserIds.isEmpty()) { - ComposeLogUtil.getLastLog().info("跳过未变更用户:{}", unchangedUserIds.size()); - } - ComposeLogUtil.getLastLog().info("缺少账号已跳过的人员:{}", skippedCount); + ComposeLogUtil.getLastLog().info("缺少OA登录名已跳过的人员:{}", skippedCount); int syncedCount = addUsers.size() + updateUsers.size(); - skippedCount += unchangedUserIds.size(); if (userMap.isEmpty()) { return new PersonSyncCount(0, skippedCount); } @@ -511,11 +473,8 @@ public class OaUserListSyncHelper { Set existsUserDeptIds = new HashSet<>(userDeptMap.values()); List userDeptList = oaPersons.stream() .filter(oaPerson -> { - String account = userConvert.resolveAccount(oaPerson); - if (!userMap.containsKey(account)) { - return false; - } - return !unchangedUserIds.contains(userMap.get(account)); + String oaLoginId = resolveOaLoginId(oaPerson); + return StringUtils.isNotBlank(oaLoginId) && userMap.containsKey(oaLoginId); }) .map(oaPerson -> this.buildUserDept(oaPerson, userMap, orgIndex)) .filter(Objects::nonNull) @@ -554,7 +513,6 @@ public class OaUserListSyncHelper { .collect(Collectors.toSet()); String defaultRoleId = getDefaultRoleId(); List updateUserParams = list.stream() - .filter(userDeptIds -> !unchangedUserIds.contains(userDeptIds.getUserId())) .filter(userDeptIds -> { if (StringUtils.isBlank(userDeptIds.getDeptIds())) { return false; @@ -586,18 +544,51 @@ public class OaUserListSyncHelper { } /** - * oaModified 一致则跳过更新;任一侧为空无法确认一致,执行更新以补齐字段 + * 按 OA 登录名加载已有用户。同一登录名有多条时保留较早的一条,后续同步都更新这一条。 */ - private boolean shouldSkipOaUpdate(User existing, User incoming) { - if (existing == null) { - return false; + private Map loadUsersByOaLoginId(String tenantId, Collection oaLoginIds) { + Map existingByOaLoginId = new HashMap<>(); + if (CollectionUtil.isEmpty(oaLoginIds)) { + return existingByOaLoginId; } - String localModified = StringUtils.trimToEmpty(existing.getOaModified()); - String remoteModified = StringUtils.trimToEmpty(incoming == null ? null : incoming.getOaModified()); - if (localModified.isEmpty() || remoteModified.isEmpty()) { - return false; + List oaLoginIdList = oaLoginIds.stream() + .filter(StringUtils::isNotBlank) + .distinct() + .toList(); + int batchSize = 500; + for (int fromIndex = 0; fromIndex < oaLoginIdList.size(); fromIndex += batchSize) { + List oaLoginIdBatch = oaLoginIdList.subList(fromIndex, Math.min(fromIndex + batchSize, oaLoginIdList.size())); + userService.list(Wrappers.lambdaQuery() + .eq(User::getTenantId, tenantId) + .in(User::getOaLoginId, oaLoginIdBatch)) + .forEach(user -> { + String oaLoginId = StringUtils.trimToNull(user.getOaLoginId()); + if (oaLoginId == null) { + return; + } + User previous = existingByOaLoginId.get(oaLoginId); + if (previous == null) { + existingByOaLoginId.put(oaLoginId, user); + return; + } + if (user.getId() != null && previous.getId() != null && user.getId() < previous.getId()) { + ComposeLogUtil.getLastLog().warn("OA登录名重复,保留较早用户 oaLoginId={} keepUserId={} dropUserId={}", + oaLoginId, user.getId(), previous.getId()); + existingByOaLoginId.put(oaLoginId, user); + return; + } + ComposeLogUtil.getLastLog().warn("OA登录名重复,保留较早用户 oaLoginId={} keepUserId={} dropUserId={}", + oaLoginId, previous.getId(), user.getId()); + }); } - return localModified.equals(remoteModified); + return existingByOaLoginId; + } + + private String resolveOaLoginId(OAPersonResponse oaPerson) { + if (oaPerson == null) { + return null; + } + return StringUtils.trimToNull(oaPerson.getLoginid()); } private boolean isPersonBypassCode(String deptCode, DeptCategory deptCategory) { @@ -638,8 +629,8 @@ public class OaUserListSyncHelper { Dept department = orgIndex.findDept(oaPerson); if (department == null || department.getId() == null) { ComposeLogUtil.getLastLog().warn( - "人员未匹配到已有部门,请先同步组织架构 account={} departmentid={} departmentname={} subcompanyid1={}", - userConvert.resolveAccount(oaPerson), oaPerson.getDepartmentid(), oaPerson.getDepartmentname(), + "人员未匹配到已有部门,请先同步组织架构 oaLoginId={} departmentid={} departmentname={} subcompanyid1={}", + resolveOaLoginId(oaPerson), oaPerson.getDepartmentid(), oaPerson.getDepartmentname(), oaPerson.getSubcompanyid1()); return null; } diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/UserServiceImpl.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/UserServiceImpl.java index 648bedb7..499575eb 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/UserServiceImpl.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/UserServiceImpl.java @@ -366,6 +366,7 @@ public class UserServiceImpl extends BaseServiceImpl implement user.setPostId(null); user.setAccount(null); user.setIsLeader(null); + user.setOaLoginId(null); CacheUtil.clear(USER_CACHE); return submitUserInfo(user); } diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/BillLedgerController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/BillLedgerController.java index 24d695ca..7cce3ffc 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/BillLedgerController.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/BillLedgerController.java @@ -61,8 +61,9 @@ public class BillLedgerController extends BladeController { @ApiOperationSupport(order = 4) @Operation(summary = "付款申请可用汇票") public R> availableOptions(@RequestParam(required = false) String keyword, - @RequestParam(required = false) Long deptId, @RequestParam(required = false) Long selectedId) { - return R.data(billLedgerService.availableOptions(keyword, deptId, selectedId)); + @RequestParam(required = false) Long deptId, @RequestParam(required = false) Long selectedId, + @RequestParam(required = false) String payerName, @RequestParam(required = false) String payeeName) { + return R.data(billLedgerService.availableOptions(keyword, deptId, selectedId, payerName, payeeName)); } @GetMapping("/available-page") @@ -70,8 +71,10 @@ public class BillLedgerController extends BladeController { @Operation(summary = "付款申请可用汇票分页") public R> availablePage(Query pageQuery, @RequestParam(required = false) String keyword, @RequestParam(required = false) Long deptId, - @RequestParam(required = false) Long selectedId) { - return R.data(billLedgerService.availablePage(Condition.getPage(pageQuery), keyword, deptId, selectedId)); + @RequestParam(required = false) Long selectedId, @RequestParam(required = false) String payerName, + @RequestParam(required = false) String payeeName) { + return R.data(billLedgerService.availablePage(Condition.getPage(pageQuery), keyword, deptId, selectedId, + payerName, payeeName)); } @PostMapping("/submit") diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/DriverWaybillController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/DriverWaybillController.java index f966601d..bb9b4693 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/DriverWaybillController.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/DriverWaybillController.java @@ -105,6 +105,65 @@ public class DriverWaybillController extends BladeController { return R.data(driverWaybillService.detail(id)); } + @GetMapping("/task/current") + @ApiOperationSupport(order = 21) + @Operation(summary = "首页:当前运输中运次", description = "当前用户手机号可见的运输中运次(有配载单的一趟车算一个)最新一条") + public R tripCurrentTask() { + return R.data(driverWaybillService.tripCurrentTask()); + } + + @GetMapping("/task/pending-preview") + @ApiOperationSupport(order = 22) + @Operation(summary = "首页:待接运次预览", description = "当前用户手机号可见的待接运次预览与总数(按运次计)") + public R tripPendingPreview( + @Parameter(description = "预览条数,默认 2") @RequestParam(required = false) Integer size) { + return R.data(driverWaybillService.tripPendingPreview(size)); + } + + @GetMapping("/task/counts") + @ApiOperationSupport(order = 23) + @Operation(summary = "运次 Tab 统计", description = "按运次统计:全部 = 待接单 + 进行中 + 已完成(不含已取消)") + public R tripCounts() { + return R.data(driverWaybillService.tripTabCounts()); + } + + @GetMapping("/task/page") + @ApiOperationSupport(order = 24) + @Operation(summary = "运次分页列表", description = "一页即若干个运次;status:空=全部,0待接单,1运输中,2已完成;keyword 支持运单号/配载单号/起终点/车牌") + public R> tripPage( + @Parameter(description = "当前页") @RequestParam(required = false) Integer current, + @Parameter(description = "每页运次数") @RequestParam(required = false) Integer size, + @Parameter(description = "状态:0待接单/1运输中/2已完成,不传为全部") @RequestParam(required = false) String status, + @Parameter(description = "关键字:运单号/配载单号/起终点/车牌") @RequestParam(required = false) String keyword) { + Integer statusCode = parseStatus(status); + return R.data(driverWaybillService.tripPage(current, size, statusCode, keyword)); + } + + @GetMapping("/task/detail") + @ApiOperationSupport(order = 25) + @Operation(summary = "司机运次详情", description = "按运次主键(字符串)取整趟车;有配载单时下发站点 ①②③(stations)与每站货物条目") + public R tripDetail( + @Parameter(description = "运次主键(配载单主键或运单主键)", required = true) @RequestParam String id) { + return R.data(driverWaybillService.tripDetail(id)); + } + + @PostMapping("/task/accept") + @ApiOperationSupport(order = 26) + @Operation(summary = "运次确认接单", description = "一次接单把该运次全部子运单置为已接单;任一子单已接单即视为整单已接单") + public R tripAccept( + @Parameter(description = "运次主键(配载单主键或运单主键)", required = true) @RequestParam String id) { + return R.status(driverWaybillService.tripAccept(id)); + } + + @PostMapping("/task/reject") + @ApiOperationSupport(order = 27) + @Operation(summary = "运次拒绝接单", description = "整单拒绝:该运次全部未终态子运单一起置为已拒绝,此后该运次对本人不可见") + public R tripReject( + @Parameter(description = "运次主键(配载单主键或运单主键)", required = true) @RequestParam String id, + @Parameter(description = "拒绝原因") @RequestParam(required = false) String reason) { + return R.status(driverWaybillService.tripReject(id, reason)); + } + @PostMapping("/track") @ApiOperationSupport(order = 11) @Operation(summary = "司机端行驶轨迹", description = "按当前用户手机号校验归属后,根据打卡记录/计划时间自动推导起止日期并查询 LBS 轨迹(WGS-84)") diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/HcsMockController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/HcsMockController.java new file mode 100644 index 00000000..247f0e87 --- /dev/null +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/HcsMockController.java @@ -0,0 +1,123 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.transport.controller; + +import cn.hutool.core.util.StrUtil; +import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springblade.core.log.exception.ServiceException; +import org.springblade.core.tenant.annotation.TenantIgnore; +import org.springblade.core.tool.api.R; +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.RestController; + +import java.util.Map; +import java.util.Objects; + +/** + * HCS 合规回调 Mock 端点 + *

+ * 合同法务审批结果回调(D103)、付款法务审批结果回调(D104)、项目金蝶同步结果回调(D902)、 + * 合同金蝶同步结果回传接收(D903)四个入站回调的纯 mock 实现:仅校验来源系统标识后返回固定结果, + * 不读写业务数据、不联动状态。真实处理逻辑保留在对应 Support 中,联调时把端点切回 + * {@link } 并调用 Support 方法即可。 + * + * @author Chill + */ +@Slf4j +@RestController +@TenantIgnore +@RequestMapping("hcs") +@Tag(name = "HCS合规回调Mock", description = "HCS合规回调Mock端点(仅自测,返回固定 mock 数据)") +public class HcsMockController { + + /** + * 来源系统标识(合规方约定值,与 third-party.hcs.src-system 默认值一致) + */ + private static final String SOURCE_SYSTEM = "WLTMS"; + + /** + * 合同法务审批结果回调(D103) + */ + @PostMapping("/contractLegal/result") + @ApiOperationSupport(order = 15) + @Operation(summary = "合同法务审批结果回调Mock", description = "D103 合规推送合同特批(金蝶同步)结果,仅校验来源系统并返回 mock 数据") + public R contractLegalResult(@RequestBody(required = false) Map param) { + this.checkSourceSystem("合同法务审批结果", param); + return R.data("mock特批结果已处理:approved"); + } + + /** + * 付款法务审批结果回调(D104) + */ + @PostMapping("/paymentLegal/result") + @ApiOperationSupport(order = 16) + @Operation(summary = "付款法务审批结果回调Mock", description = "D104 合规推送付款特批结果,仅校验来源系统并返回 mock 数据") + public R paymentLegalResult(@RequestBody(required = false) Map param) { + this.checkSourceSystem("付款法务审批结果", param); + return R.data("mock付款特批结果接收已处理:approved"); + } + + /** + * 项目金蝶同步结果回调(D902) + */ + @PostMapping("/projectLegal/result") + @ApiOperationSupport(order = 18) + @Operation(summary = "项目金蝶同步结果回调Mock", description = "D902 合规推送项目金蝶同步结果,仅校验来源系统并返回 mock 数据") + public R projectLegalResult(@RequestBody(required = false) Map param) { + this.checkSourceSystem("项目金蝶同步结果", param); + return R.data("mock项目金蝶同步结果接收已处理:approved"); + } + + /** + * 合同金蝶同步结果回传接收(D903) + */ + @PostMapping("/contractKingdee/result") + @ApiOperationSupport(order = 19) + @Operation(summary = "合同金蝶同步结果回传接收Mock", description = "D903 合规推送合同金蝶最终同步结果,仅校验来源系统并返回 mock 数据") + public R contractKingdeeResult(@RequestBody(required = false) Map param) { + this.checkSourceSystem("合同金蝶同步结果", param); + return R.data("mock合同金蝶同步结果接收处理:approved"); + } + + /** + * 来源系统校验:仅接受合规方约定的来源标识,防伪造回传 + * + * @param bizName 业务名称(日志用) + * @param param 回调报文 + */ + private void checkSourceSystem(String bizName, Map param) { + String sourceSystem = param == null ? null : Objects.toString(param.get("sourceSystem"), null); + if (!StrUtil.equalsIgnoreCase(sourceSystem, SOURCE_SYSTEM)) { + log.warn("{}推送来源系统标识错误:sourceSystem={}", bizName, sourceSystem); + throw new ServiceException("来源系统标识错误"); + } + } +} diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ManageWaybillController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ManageWaybillController.java index 0b7dda41..67c2d15c 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ManageWaybillController.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ManageWaybillController.java @@ -39,6 +39,7 @@ import org.springblade.transport.pojo.vo.AdminVehicleOptionVO; import org.springblade.transport.pojo.vo.AdminWaybillCardVO; import org.springblade.transport.pojo.vo.AdminWaybillDetailVO; import org.springblade.transport.pojo.vo.WaybillPunchRecordsVO; +import org.springblade.transport.pojo.vo.WaybillTrackVO; import org.springblade.transport.service.IManageWaybillService; import org.springblade.transport.service.IWaybillService; import org.springframework.web.bind.annotation.GetMapping; @@ -69,7 +70,7 @@ public class ManageWaybillController extends BladeController { @GetMapping("/stats") @ApiOperationSupport(order = 1) - @Operation(summary = "运单状态统计", description = "待接单=pending,运输中=running,已完成=completed;租户内不过滤组织(小程序调度账号组织常与运单不一致);在途异常=异常处置状态≠已完成") + @Operation(summary = "运单状态统计", description = "待接单=pending,运输中=running,已完成=completed;只统计本组织的运单(超级管理员不过滤组织);在途异常=异常处置状态≠已完成") public R stats() { return R.data(manageWaybillService.stats()); } @@ -99,9 +100,9 @@ public class ManageWaybillController extends BladeController { @GetMapping("/detail") @ApiOperationSupport(order = 4) - @Operation(summary = "运单详情", description = "调度端查看运单详情(含 punchNodes / enrouteRecords),不校验司机归属与组织;字段对齐小程序 pages/waybill/detail") + @Operation(summary = "运次详情", description = "调度端查看整趟车详情(含 stations / punchNodes / enrouteRecords),不校验司机归属与组织;id 传运次主键字符串,已拒单的运单主键回落成单票视图") public R detail( - @Parameter(description = "运单ID", required = true) @RequestParam Long id) { + @Parameter(description = "运次主键(配载单主键或运单主键,字符串)", required = true) @RequestParam String id) { return R.data(manageWaybillService.detail(id)); } @@ -159,4 +160,14 @@ public class ManageWaybillController extends BladeController { return R.status(waybillService.savePunchRecord(request)); } + @PostMapping("/track") + @ApiOperationSupport(order = 11) + @Operation(summary = "车辆历史轨迹", description = "小程序调度端:仅需登录态,按运单绑定车牌和日期区间查询 LBS 轨迹") + public R track( + @Parameter(description = "运单ID", required = true) @RequestParam Long id, + @Parameter(description = "开始日期 YYYY-MM-DD", required = true) @RequestParam String startDate, + @Parameter(description = "结束日期 YYYY-MM-DD", required = true) @RequestParam String endDate) { + return R.data(waybillService.trackVehicle(id, startDate, endDate)); + } + } 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 82d39780..30af8573 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 @@ -373,6 +373,13 @@ public class WaybillController extends BladeController { return R.data(waybillService.batchComplete(ids)); } + @PostMapping("/confirm-return") + @ApiOperationSupport(order = 24) + @Operation(summary = "确认回单", description = "回单节点要求业务员确认时用:该运单的司机打卡节点全部已打卡且无未了结异常,才置为已完成") + public R confirmReturn(@Parameter(description = "主键", required = true) @RequestParam Long id) { + return R.status(waybillService.confirmReturnReceipt(id)); + } + @PostMapping("/road-loading") @ApiOperationSupport(order = 23) @Operation(summary = "公路配载", description = "传入ids") diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/SettlementImportFeeReader.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/SettlementImportFeeReader.java index cc43d6e1..12b44580 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/SettlementImportFeeReader.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/SettlementImportFeeReader.java @@ -54,7 +54,8 @@ import java.util.Set; /** * 导入结算单 Excel 读取器(Apache POI)。 *

- * 定位「*运输费」或「运输费」列,其后直到「结算金额」为动态费用列。 + * 定位「*运输费」或「运输费」列,其后直到日期/备注等固定列(兼容旧模板中的「结算金额」)为动态费用列。 + * 结算金额不由模板填写,解析时按「运输费 + 各费用项」自动合计。 * * @author Chill */ @@ -66,6 +67,14 @@ public final class SettlementImportFeeReader { "actualStartDate", "actualEndDate", "planStartDate", "planEndDate" ); private static final Set OPTIONAL_DECIMAL_FIELDS = Set.of("mileage", "unitPrice"); + /** 费用列结束边界:其后为固定业务列;含旧模板「结算金额」以便兼容历史文件 */ + private static final Set FEE_END_HEADERS = Set.of( + "结算金额", + "*实际发货时间", "实际发货时间", + "*实际完成时间", "实际完成时间", + "预计发货时间", "预计完成时间", + "备注", "同一运单标识号" + ); private SettlementImportFeeReader() { } @@ -119,15 +128,12 @@ public final class SettlementImportFeeReader { if (transportFeeColumn < 0) { throw new ServiceException("未找到「*运输费」或「运输费」列"); } - int settlementAmountColumn = findColumnExact(headers, "结算金额"); List feeColumns = new ArrayList<>(); List feeItemNames = new ArrayList<>(); - int endColumn = settlementAmountColumn > transportFeeColumn - ? settlementAmountColumn - : headers.keySet().stream().mapToInt(Integer::intValue).max().orElse(transportFeeColumn) + 1; + int endColumn = findFeeEndColumn(headers, transportFeeColumn); for (int column = transportFeeColumn + 1; column < endColumn; column++) { String name = headers.get(column); - if (name == null || name.isBlank() || "结算金额".equals(name)) { + if (name == null || name.isBlank() || FEE_END_HEADERS.contains(name)) { continue; } feeColumns.add(column); @@ -229,7 +235,8 @@ public final class SettlementImportFeeReader { } else if ("transportFee".equals(field) || "quantity".equals(field)) { mapped.put(field, decimal(cellString(poiCell), trimmedHeader, excelRowIndex)); } else if ("settlementAmount".equals(field)) { - mapped.put(field, optionalDecimal(cellString(poiCell), trimmedHeader, excelRowIndex)); + // 旧模板可能仍含结算金额列,读入后忽略,统一按运输费+费用项自动合计 + continue; } else if ("serialNumber".equals(field)) { mapped.put(field, intValue(cellString(poiCell))); } else { @@ -255,9 +262,7 @@ public final class SettlementImportFeeReader { BigDecimal feeItemTotal = feeItems.values().stream() .reduce(BigDecimal.ZERO, BigDecimal::add) .setScale(2, RoundingMode.HALF_UP); - BigDecimal settlementAmount = mapped.get("settlementAmount") == null - ? transportFee.add(feeItemTotal).setScale(2, RoundingMode.HALF_UP) - : toDecimal(mapped.get("settlementAmount")); + BigDecimal settlementAmount = transportFee.add(feeItemTotal).setScale(2, RoundingMode.HALF_UP); mapped.put("settlementAmount", settlementAmount); mapped.put("结算金额", settlementAmount); mapped.put("feeItems", feeItems); @@ -284,6 +289,21 @@ public final class SettlementImportFeeReader { return findColumnExact(headers, "运输费"); } + /** 费用列右边界:取运输费之后第一个固定结束表头列下标;无则取表头最大下标 + 1。 */ + private static int findFeeEndColumn(Map headers, int transportFeeColumn) { + int endColumn = headers.keySet().stream().mapToInt(Integer::intValue).max().orElse(transportFeeColumn) + 1; + for (Map.Entry entry : headers.entrySet()) { + int column = entry.getKey(); + if (column <= transportFeeColumn) { + continue; + } + if (FEE_END_HEADERS.contains(entry.getValue()) && column < endColumn) { + endColumn = column; + } + } + return endColumn; + } + private static int findColumnExact(Map headers, String header) { return headers.entrySet().stream() .filter(entry -> header.equals(entry.getValue())) diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/SettlementImportTemplateExcel.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/SettlementImportTemplateExcel.java index a060b15e..8ed3f35a 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/SettlementImportTemplateExcel.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/SettlementImportTemplateExcel.java @@ -37,7 +37,7 @@ import java.io.Serializable; import java.math.BigDecimal; /** - * 导入结算单 Excel 模板(固定列 + 运输单价 + *运输费 + 示例费用项) + * 导入结算单 Excel 固定列模型(失败明细兜底导出;费用项由用户在原模板中自行增列)。 * * @author Chill */ @@ -96,11 +96,8 @@ public class SettlementImportTemplateExcel implements Serializable { private BigDecimal unitPrice; @ExcelProperty("*运输费") private BigDecimal transportFee; - @ExcelProperty("装卸费") - private BigDecimal loadingFee; - @ExcelProperty("过路费") - private BigDecimal tollFee; - @ExcelProperty("结算金额") + /** 模板已去掉结算金额列;仅失败明细内部保留,不导出为表头 */ + @ExcelIgnore private BigDecimal settlementAmount; @ExcelProperty("*实际发货时间") private String actualStartDate; diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/SettlementImportTemplateExporter.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/SettlementImportTemplateExporter.java index a33dbb6c..cee7d476 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/SettlementImportTemplateExporter.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/SettlementImportTemplateExporter.java @@ -47,12 +47,12 @@ import java.io.IOException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.ArrayList; -import java.util.LinkedHashSet; import java.util.List; -import java.util.Set; /** - * 导入结算单模板导出:填写说明 + 数据表头 + 运输方式下拉 + 系统费用项列。 + * 导入结算单模板导出:填写说明 + 固定数据表头 + 运输方式下拉。 + *

+ * 模板不预置费用项列;用户可在「*运输费」与后续固定列之间自行插入系统已启用的费用项列。 * * @author Chill */ @@ -85,7 +85,6 @@ public final class SettlementImportTemplateExporter { ); private static final List FIXED_HEADERS_AFTER_FEE = List.of( - "结算金额", "*实际发货时间", "*实际完成时间", "预计发货时间", @@ -94,15 +93,18 @@ public final class SettlementImportTemplateExporter { "同一运单标识号" ); - private static final List DEFAULT_FEE_ITEMS = List.of("装卸费", "过路费"); - private SettlementImportTemplateExporter() { } public static void export(HttpServletResponse response, List transportTypeOptions, List feeItemNames) { - List headers = buildHeaders(feeItemNames); - List transportOptions = defaultTransportOptions(transportTypeOptions); + List headers = buildHeaders(); + // 运输方式由调用方传入业务字典 transport_type 的字典名称,禁止写死 + List transportOptions = transportTypeOptions == null ? List.of() : transportTypeOptions.stream() + .filter(item -> item != null && !item.isBlank()) + .map(String::trim) + .distinct() + .toList(); try (Workbook workbook = new XSSFWorkbook()) { writeInstructionSheet(workbook, transportOptions, feeItemNames); Sheet dataSheet = workbook.createSheet("导入结算单模板"); @@ -128,20 +130,9 @@ public final class SettlementImportTemplateExporter { } } - private static List buildHeaders(List feeItemNames) { + /** 固定列模板:不预置费用项列,由用户自行插入。 */ + private static List buildHeaders() { List headers = new ArrayList<>(FIXED_HEADERS_BEFORE_FEE); - Set feeNames = new LinkedHashSet<>(); - if (feeItemNames != null) { - for (String name : feeItemNames) { - if (name != null && !name.isBlank()) { - feeNames.add(name.trim()); - } - } - } - if (feeNames.isEmpty()) { - feeNames.addAll(DEFAULT_FEE_ITEMS); - } - headers.addAll(feeNames); headers.addAll(FIXED_HEADERS_AFTER_FEE); return headers; } @@ -155,18 +146,24 @@ public final class SettlementImportTemplateExporter { titleStyle.setFont(titleFont); List lines = new ArrayList<>(); lines.add("导入结算单填写说明"); - lines.add("1. 请不要修改「导入结算单模板」工作表的表头;带 * 号为必填项。"); - lines.add("2. 运输方式请从下拉项中选择,须与系统字典一致。"); + lines.add("1. 请不要修改「导入结算单模板」工作表的固定表头;带 * 号为必填项。"); + lines.add("2. 运输方式请从下拉项中选择,须与业务字典 transport_type 的字典名称一致。"); lines.add("3. 日期请填写 yyyy-MM-dd(如 2026-01-01);Excel 日期单元格也可直接选择。"); lines.add("4. 里程(km) 可为空;填写时须为正数。"); - lines.add("5. 「*运输费」之后、「结算金额」之前的列为费用项,列名须与系统费用项主数据名称完全一致;金额不为 0 时若不存在或已停用则无法导入。"); - lines.add("6. 结算金额 = 运输费 + 各费用项合计,系统会按此重算。"); - lines.add("7. 当前可选运输方式:" + String.join("、", transportOptions) + "。"); + lines.add("5. 「*运输费」之后、「*实际发货时间」之前可按需自行插入费用项列,列名须与系统已启用费用项名称完全一致;金额不为 0 时若不存在或已停用则无法导入。"); + lines.add("6. 模板不含结算金额列;导入后系统按「运输费 + 各费用项」自动合计结算金额。"); + if (transportOptions != null && !transportOptions.isEmpty()) { + lines.add("7. 当前可选运输方式(业务字典 transport_type 名称):" + + String.join("、", transportOptions) + "。"); + } else { + lines.add("7. 运输方式请从业务字典 transport_type 维护的名称中选择。"); + } if (feeItemNames != null && !feeItemNames.isEmpty()) { - lines.add("8. 当前启用费用项示例:" + String.join("、", feeItemNames.stream().limit(30).toList()) + lines.add("8. 费用项请按实际需要在模板中自行增列,列名须与系统已启用费用项一致;当前系统已启用费用项:" + + String.join("、", feeItemNames.stream().limit(30).toList()) + (feeItemNames.size() > 30 ? " 等" : "") + "。"); } else { - lines.add("8. 模板默认包含装卸费、过路费列,可按系统费用项主数据增删费用列。"); + lines.add("8. 费用项请按实际需要在模板中自行增列,列名须与系统已启用的费用项主数据名称完全一致。"); } for (int index = 0; index < lines.size(); index++) { Row row = sheet.createRow(index); @@ -202,10 +199,4 @@ public final class SettlementImportTemplateExporter { return style; } - private static List defaultTransportOptions(List transportTypeOptions) { - if (transportTypeOptions != null && !transportTypeOptions.isEmpty()) { - return transportTypeOptions; - } - return List.of("公路运输", "铁路运输", "水路运输", "航空运输"); - } } diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/ReceiptClaimMapper.xml b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/ReceiptClaimMapper.xml index b1337bbe..5dbc2be7 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/ReceiptClaimMapper.xml +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/ReceiptClaimMapper.xml @@ -102,7 +102,8 @@ FROM blade_receipt_claim c INNER JOIN blade_kingdee_receipt_flow f ON f.id = c.receipt_flow_id WHERE - ORDER BY c.create_time DESC + + ORDER BY c.claim_date DESC, c.id DESC