fix:修改用户管理样式,能展示用户身份信息。

fix:修改树状查询让他更紧凑。
fix:修改app微信登录,先检测用户手机上没有有装微信,没有就隐藏微信登录。
fix:修改商户管理"商户功能"样式
feat:增加自动退款模块
This commit is contained in:
dzc committed 2026-08-14 15:03:58 +08:00
1 parent 1044306558
commit 6e1af7c84d
64 files changed
+4748 -134

No files matched your search

@@ -1,26 +1,56 @@
package com.bsd.pjcloud.hard.api.constant.refund;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 硬件售后操作类型的枚举
*/
@AllArgsConstructor
@Getter
public enum HardAfterSaleOperateTypeEnum {
/** 会员主动提交退款申请。 */
MEMBER_CREATE("10", "会员申请退款"),
/** 商户审核同意退款申请。 */
ADMIN_AGREE_APPLY("11", "商家同意退款"),
/** 商户审核拒绝退款申请。 */
ADMIN_DISAGREE_APPLY("12", "商家拒绝退款"),
/** 会员补充退货物流信息。 */
MEMBER_DELIVERY("20", "会员填写退货物流信息,快递公司:{deliveryName},快递单号:{logisticsNo}"),
/** 商户确认收到退货商品。 */
ADMIN_AGREE_RECEIVE("21", "商家收货"),
/** 商户拒绝接收退货商品。 */
ADMIN_DISAGREE_RECEIVE("22", "商家拒绝收货,原因:{reason}"),
/** 商户或后台执行资金退款。 */
ADMIN_REFUND("30", "商家退款"),
/** 系统根据订单快照和商户配置免审完成退款。 */
SYSTEM_AUTO_REFUND("31", "系统自动退款"),
/** 会员取消尚未审核的退款申请。 */
MEMBER_CANCEL("40", "会员取消退款"),
;
/** 售后日志持久化的稳定操作类型编码。 */
private final String type;
/** 售后进度展示内容,部分人工操作允许包含格式化占位符。 */
private final String content;
/**
* 构造售后操作类型协议项。
*
* @param type 售后日志持久化编码
* @param content 售后进度展示内容
*/
HardAfterSaleOperateTypeEnum(String type, String content) {
this.type = type;
this.content = content;
}
}
@@ -0,0 +1,51 @@
package com.bsd.pjcloud.hard.api.constant.refund;
import lombok.Getter;
/**
* 自动退款单次尝试结果。日志先以执行中写入同一事务,最终持久化为成功、失败或开关关闭跳过。
*/
@Getter
public enum HardAutoRefundAttemptResultEnum {
/**
* 资金调用开始但尚未完成的临时状态;正常提交后不会长期停留。
*/
EXECUTING("10", "执行中"),
/**
* 钱包到账及 Hardware 状态更新均已提交。
*/
SUCCESS("20", "成功"),
/**
* 本次实际资金调用失败,已安排重试或进入耗尽状态。
*/
FAILED("30", "失败"),
/**
* 执行前发现当前开关关闭,未调用资金接口且不消耗自动尝试额度。
*/
SWITCH_DISABLED("40", "因开关关闭跳过");
/**
* 数据库存储的稳定结果编码。
*/
private final String status;
/**
* 管理端展示名称。
*/
private final String description;
/**
* 构造一次尝试结果协议项。
*
* @param status 数据库存储的稳定结果编码
* @param description 管理端展示名称
*/
HardAutoRefundAttemptResultEnum(String status, String description) {
this.status = status;
this.description = description;
}
}
@@ -0,0 +1,73 @@
package com.bsd.pjcloud.hard.api.constant.refund;
import lombok.Getter;
import java.util.Arrays;
/**
* 自动退款任务状态。编码持久化到 hard_auto_refund_task.status,并由 PC、App 共同展示。
*/
@Getter
public enum HardAutoRefundTaskStatusEnum {
/**
* 售后申请事务已经创建任务,等待事务提交后的首次同步执行。
*/
PENDING("10", "待执行"),
/**
* 上次资金调用失败,等待 next_retry_time 到期后自动重试。
*/
WAITING_RETRY("20", "等待重试"),
/**
* 商户已关闭自动退款,任务保留并等待重新开启。
*/
SWITCH_DISABLED("30", "开关关闭暂停"),
/**
* 配置缺失、规则不一致或配置服务异常,任务保留并等待配置恢复。
*/
CONFIG_UNAVAILABLE("31", "配置不可用暂停"),
/**
* 首次执行和七次自动重试均失败,只允许技术人员额外手工触发。
*/
RETRY_EXHAUSTED("40", "重试耗尽"),
/**
* 钱包已幂等到账,售后和订单退款聚合状态均已完成更新。
*/
SUCCESS("50", "成功");
/**
* 数据库和接口使用的稳定状态编码。
*/
private final String status;
/**
* 管理端展示名称。
*/
private final String description;
/**
* 构造自动退款任务状态协议项。
*
* @param status 数据库和接口使用的稳定状态编码
* @param description 管理端展示名称
*/
HardAutoRefundTaskStatusEnum(String status, String description) {
this.status = status;
this.description = description;
}
/**
* 按持久化编码解析任务状态,未知值返回 {@code null}。
*
* @param status 数据库存储编码
* @return 匹配状态;未知时返回 {@code null}
*/
public static HardAutoRefundTaskStatusEnum valueOfStatus(String status) {
return Arrays.stream(values()).filter(item -> item.status.equals(status)).findFirst().orElse(null);
}
}
@@ -0,0 +1,53 @@
package com.bsd.pjcloud.hard.api.constant.refund;
import lombok.Getter;
/**
* 自动退款尝试触发来源。编码持久化到尝试日志,属于管理端审计协议。
*/
@Getter
public enum HardAutoRefundTriggerTypeEnum {
/**
* 售后申请本地事务提交后的首次同步执行。
*/
INITIAL("10", "首次同步执行", true),
/**
* 每 30 秒调度器触发的到期自动重试。
*/
SCHEDULED("20", "定时重试", true),
/**
* 管理端技术人员对耗尽任务执行的一次额外尝试。
*/
MANUAL("30", "人工技术重试", false);
/**
* 数据库存储的稳定触发编码。
*/
private final String type;
/**
* 管理端展示名称。
*/
private final String description;
/**
* 是否计入八次自动尝试额度;人工技术重试只增加总尝试次数。
*/
private final boolean autoAttempt;
/**
* 构造尝试触发来源协议项。
*
* @param type 数据库存储的稳定触发编码
* @param description 管理端展示名称
* @param autoAttempt 是否消耗八次自动尝试额度
*/
HardAutoRefundTriggerTypeEnum(String type, String description, boolean autoAttempt) {
this.type = type;
this.description = description;
this.autoAttempt = autoAttempt;
}
}
@@ -0,0 +1,57 @@
package com.bsd.pjcloud.hard.api.dto.order;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
/**
* 自动退款任务管理分页筛选条件。
*/
@Data
@Schema(description = "自动退款任务分页筛选")
public class HardAutoRefundTaskPageDto {
/**
* 精确任务主键,管理端通常不传。
*/
@Schema(description = "任务主键")
private Long id;
/**
* 精确售后单 ID。
*/
@Schema(description = "售后单ID")
private Long afterSaleId;
/**
* 精确原订单 ID。
*/
@Schema(description = "原订单ID")
private Long orderId;
/**
* 精确商户 ID;必须同时受服务端数据范围约束。
*/
@Schema(description = "商户ID")
private Long mchId;
/**
* 任务状态编码。
*/
@Schema(description = "任务状态")
private String status;
/**
* 任务创建时间闭区间,数组长度应为 2。
*/
@Schema(description = "创建时间范围")
private LocalDateTime[] createTimeList;
/**
* 当前管理用户可访问的商户 ID 列表,由 Controller 根据数据范围注入,不接受客户端放宽。
*/
@Schema(hidden = true)
private List<Long> mchIds;
}
@@ -14,7 +14,7 @@ import java.util.List;
* App 每日菜谱多日期、多餐段批量订餐请求。
*
* <p>一个分组对应一个父购物车和一个预约日期餐段,服务端会为每个分组下的每位实际用餐人
* 创建独立订单,并统一从当前付款人选择的钱包扣款。</p>
* 创建独立订单,并从各实际用餐人本人已经开通的 EAT 饭卡扣款。</p>
*/
@Data
public class HardAppCreateDailyMenuReserveOrdersDto {
@@ -34,9 +34,9 @@ public class HardAppCreateDailyMenuReserveOrdersDto {
private String payType;
/**
* 付款人本次选择的钱包类型 ID,服务端会按当前登录用户再次校验钱包归属和余额。
* 旧客户端兼容字段。服务端不再使用该值选择钱包,实际扣款钱包固定按每位用餐人的 EAT 饭卡解析;
* 新客户端可以不传,旧客户端继续传入也不会改变结算目标。
*/
@NotNull(message = "钱包类型不能为空")
private Long walletTypeId;
/**
@@ -2,6 +2,7 @@ package com.bsd.pjcloud.hard.api.dto.order.app;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import lombok.Data;
import java.time.LocalDate;
@@ -25,6 +26,12 @@ public class HardAppCreateReserveOrdersDto {
@NotEmpty(message = "用餐人不能为空")
private List<Long> dinerUserIds;
/**
* App 支付平台类型。新客户端固定提交钱包支付编码 30;旧客户端未传时按 30 兼容处理,其他编码拒绝进入饭卡直扣。
*/
@Pattern(regexp = "30", message = "批量订餐暂仅支持钱包支付")
private String payType;
private Long apptId;
private Long apptRuleId;
@@ -42,7 +42,7 @@ public class HardAppRefundApplyDto {
/**
* 用户填写的问题描述,便于商户审核本次所选商品。
*/
@NotBlank(message = "相关描述不能为空")
// @NotBlank(message = "相关描述不能为空")
@Schema(description = "相关描述")
private String applyDescription;
@@ -0,0 +1,141 @@
package com.bsd.pjcloud.hard.api.entity.order;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import com.bsd.pjcloud.common.core.util.TenantTable;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
/**
* 自动退款单次尝试只读日志。每个任务的 attempt_no 严格递增且由唯一索引防止重复写入。
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TenantTable
@TableName("hard_auto_refund_attempt_log")
@EqualsAndHashCode(callSuper = true)
@Schema(description = "自动退款尝试日志")
public class HardAutoRefundAttemptLogEntity extends Model<HardAutoRefundAttemptLogEntity> {
/**
* 日志主键,使用 MyBatis-Plus 雪花 ID。
*/
@TableId(type = IdType.ASSIGN_ID)
@Schema(description = "日志主键")
private Long id;
/**
* 所属自动退款任务 ID。
*/
@Schema(description = "任务ID")
private Long taskId;
/**
* 所属售后单 ID,便于不经过任务表直接查询完整尝试历史。
*/
@Schema(description = "售后单ID")
private Long afterSaleId;
/**
* 任务内递增尝试序号,包含首次开关关闭跳过记录。
*/
@Schema(description = "尝试序号")
private Integer attemptNo;
/**
* 触发来源编码,对应 HardAutoRefundTriggerTypeEnum。
*/
@Schema(description = "触发来源")
private String triggerType;
/**
* 尝试结果编码,对应 HardAutoRefundAttemptResultEnum。
*/
@Schema(description = "尝试结果")
private String resultStatus;
/**
* 稳定钱包退款业务号,格式为 hard_{orderId}_{afterSaleId}。
*/
@Schema(description = "钱包退款业务号")
private String refundCode;
/**
* 人工技术重试的操作用户 ID;自动触发时为空。
*/
@Schema(description = "操作用户ID")
private Long operatorUserId;
/**
* 本次尝试开始时间,使用服务器默认时区。
*/
@Schema(description = "开始时间")
private LocalDateTime startedTime;
/**
* 本次尝试完成时间;执行中临时记录允许为空。
*/
@Schema(description = "完成时间")
private LocalDateTime finishedTime;
/**
* 失败诊断信息,最多 2000 字符;成功和开关跳过时为空。
*/
@Schema(description = "错误信息")
private String errorMessage;
/**
* 创建人账号,由公共字段填充器写入。
*/
@TableField(fill = FieldFill.INSERT)
@Schema(description = "创建人")
private String createBy;
/**
* 日志创建时间,使用服务器和数据库连接默认时区。
*/
@TableField(fill = FieldFill.INSERT)
@Schema(description = "创建时间")
private LocalDateTime createTime;
/**
* 最后修改人账号;日志仅在同一尝试事务内从执行中更新为最终结果。
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
@Schema(description = "修改人")
private String updateBy;
/**
* 最后修改时间;日志最终完成后不再允许业务修改。
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
@Schema(description = "修改时间")
private LocalDateTime updateTime;
/**
* 逻辑删除标记;尝试日志不提供修改或删除接口。
*/
@TableLogic
@TableField(fill = FieldFill.INSERT)
@Schema(description = "逻辑删除标记")
private String delFlag;
/**
* 日志所属租户 ID,用于查询隔离。
*/
@Schema(description = "租户ID")
private Long tenantId;
}
@@ -0,0 +1,159 @@
package com.bsd.pjcloud.hard.api.entity.order;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import com.bsd.pjcloud.common.core.util.TenantTable;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
/**
* Hardware 自动退款持久化任务。一个售后单最多创建一条任务,失败后由该记录持续驱动恢复。
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TenantTable
@TableName("hard_auto_refund_task")
@EqualsAndHashCode(callSuper = true)
@Schema(description = "自动退款任务")
public class HardAutoRefundTaskEntity extends Model<HardAutoRefundTaskEntity> {
/**
* 任务主键,使用 MyBatis-Plus 雪花 ID。
*/
@TableId(type = IdType.ASSIGN_ID)
@Schema(description = "任务主键")
private Long id;
/**
* 售后单 ID,是任务唯一业务键和钱包退款幂等键。
*/
@Schema(description = "售后单ID")
private Long afterSaleId;
/**
* 原 Hardware 订单 ID,用于订单级分布式锁和退款状态聚合。
*/
@Schema(description = "原订单ID")
private Long orderId;
/**
* 订单所属商户 ID,用于管理端数据范围和配置实时复核。
*/
@Schema(description = "商户ID")
private Long mchId;
/**
* 下单时快照引用的 Wallet 自动退款配置主键。
*/
@Schema(description = "配置主键")
private Long configId;
/**
* 下单时快照的规则名称,重试期间不跟随配置规则切换而改变。
*/
@Schema(description = "自动退款规则快照")
private String ruleType;
/**
* 用户申请免审退款的闭区间截止时间;任务创建后跨过该时间仍继续重试。
*/
@Schema(description = "免审资格截止时间")
private LocalDateTime eligibilityDeadline;
/**
* 当前任务状态编码,对应 HardAutoRefundTaskStatusEnum。
*/
@Schema(description = "任务状态")
private String status;
/**
* 已消耗的自动尝试次数,包含首次同步执行,最大为 8;开关跳过和人工重试不增加。
*/
@Schema(description = "自动尝试次数")
private Integer autoAttemptCount;
/**
* 全部审计尝试序号计数,包含自动、人工和首次开关关闭跳过,用于生成唯一 attempt_no。
*/
@Schema(description = "总尝试次数")
private Integer totalAttemptCount;
/**
* 等待重试任务的下一次到期时间;暂停、耗尽和成功状态允许为空。
*/
@Schema(description = "下次重试时间")
private LocalDateTime nextRetryTime;
/**
* 最近一次实际尝试或开关跳过的开始时间。
*/
@Schema(description = "末次尝试时间")
private LocalDateTime lastAttemptTime;
/**
* 钱包到账及 Hardware 状态全部提交成功的时间。
*/
@Schema(description = "成功时间")
private LocalDateTime successTime;
/**
* 最近一次失败的诊断信息,最多保存 2000 字符,不包含凭证或敏感资金数据。
*/
@Schema(description = "末次错误")
private String lastError;
/**
* 创建人账号,由公共字段填充器写入。
*/
@TableField(fill = FieldFill.INSERT)
@Schema(description = "创建人")
private String createBy;
/**
* 任务创建时间,使用服务器和数据库连接默认时区。
*/
@TableField(fill = FieldFill.INSERT)
@Schema(description = "创建时间")
private LocalDateTime createTime;
/**
* 最后修改人账号,由公共字段填充器维护。
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
@Schema(description = "修改人")
private String updateBy;
/**
* 最后状态更新时间,使用服务器和数据库连接默认时区。
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
@Schema(description = "修改时间")
private LocalDateTime updateTime;
/**
* 逻辑删除标记;任务管理不提供删除接口。
*/
@TableLogic
@TableField(fill = FieldFill.INSERT)
@Schema(description = "逻辑删除标记")
private String delFlag;
/**
* 任务所属租户 ID,调度器会按该值恢复租户上下文后执行。
*/
@Schema(description = "租户ID")
private Long tenantId;
}
@@ -141,6 +141,24 @@ public class HardOrderEntity extends Model<HardOrderEntity> {
@Schema(description = "预计使用日期,食堂预约订单取预约目标日期,其他订单取下单当天,精确到天")
private LocalDate apptUseDate;
/**
* 下单时命中的商户自动退款配置主键快照;为空表示订单不具备免审资格。
*/
@Schema(description = "自动退款配置主键快照")
private Long autoRefundConfigId;
/**
* 下单时保存的自动退款规则名称;退款申请时不重新按新规则计算截止时间。
*/
@Schema(description = "自动退款规则快照")
private String autoRefundRuleType;
/**
* 免审退款申请截止时间,使用服务器默认时区并按 {@code now <= deadline} 判断。
*/
@Schema(description = "自动退款申请截止时间")
private LocalDateTime autoRefundDeadline;
/**
* 订单设备类型
*/
@@ -10,6 +10,11 @@ import java.time.LocalDateTime;
@Data
public class HardAfterSaleVo extends HardAfterSaleEntity implements BaseUserInterface, BaseDeptInterface, BaseOrderInterface {
/**
* 自动退款任务状态;为空表示当前售后仍走人工审核,非空时前端不得展示审核或取消操作。
*/
private String autoRefundTaskStatus;
/**
* 用户相关
*/
@@ -0,0 +1,45 @@
package com.bsd.pjcloud.hard.api.vo.order;
import com.bsd.pjcloud.hard.api.entity.order.HardAutoRefundTaskEntity;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 自动退款任务管理响应,补充售后金额、用户和商户展示信息。
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "自动退款任务管理响应")
public class HardAutoRefundTaskVo extends HardAutoRefundTaskEntity {
/**
* 售后申请用户 ID。
*/
@Schema(description = "用户ID")
private Long userId;
/**
* 售后申请用户展示名称,远程用户服务异常时为空但不影响任务查询。
*/
@Schema(description = "用户名称")
private String userName;
/**
* 商户展示名称,远程商户服务异常时为空但保留商户 ID。
*/
@Schema(description = "商户名称")
private String mchName;
/**
* 售后单服务端核定退款金额,单位为分。
*/
@Schema(description = "退款金额,单位为分")
private Integer refundPrice;
/**
* 售后单当前状态,用于区分处理中和资金已完成。
*/
@Schema(description = "售后状态")
private String afterSaleStatus;
}
@@ -0,0 +1,44 @@
package com.bsd.pjcloud.hard.api.vo.order.app;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
/**
* App 自动退款处理状态,只暴露用户理解进度所需字段,不返回内部错误详情。
*/
@Data
@Schema(description = "App自动退款处理状态")
public class HardAppAutoRefundTaskVo {
/**
* 售后单 ID,供退款进度页确认当前查询对象。
*/
@Schema(description = "售后单ID")
private Long afterSaleId;
/**
* 自动退款任务状态编码;不存在自动任务时接口返回空数据。
*/
@Schema(description = "自动退款任务状态")
private String status;
/**
* 自动尝试次数,开关暂停和人工技术重试不计入该值。
*/
@Schema(description = "自动尝试次数")
private Integer autoAttemptCount;
/**
* 等待重试状态下的下一次计划时间,其他状态允许为空。
*/
@Schema(description = "下次重试时间")
private LocalDateTime nextRetryTime;
/**
* 自动退款全部完成时间;处理中和暂停状态为空。
*/
@Schema(description = "完成时间")
private LocalDateTime successTime;
}
@@ -55,6 +55,12 @@ public class HardAppRefundApplyVo {
@Schema(description = "是否可以继续在线申请")
private Boolean canApply;
/**
* 是否退回实际用餐人的饭卡。仅食堂预约钱包订单为 {@code true},其他订单仍按原支付渠道原路退款。
*/
@Schema(description = "是否退回实际用餐人的饭卡")
private Boolean refundToDinerWallet;
/**
* 最新售后单 ID;没有历史申请时为空。
*/
@@ -85,6 +91,36 @@ public class HardAppRefundApplyVo {
@Schema(description = "最新状态更新时间")
private LocalDateTime latestUpdateTime;
/**
* 当前订单是否可在本次提交后进入免审自动退款,按订单快照、闭区间截止和实时开关共同判断。
*/
@Schema(description = "是否具备自动退款资格")
private Boolean autoRefundEligible;
/**
* 下单时快照的自动退款规则名称;历史无快照订单为空。
*/
@Schema(description = "自动退款规则")
private String autoRefundRuleType;
/**
* 下单时快照的免审申请截止时间;历史无快照订单为空。
*/
@Schema(description = "自动退款截止时间")
private LocalDateTime autoRefundDeadline;
/**
* 最新售后单关联的自动退款任务状态;人工审核售后为空。
*/
@Schema(description = "自动退款任务状态")
private String autoRefundTaskStatus;
/**
* 最新售后单是否允许用户取消;存在自动退款任务时始终为 {@code false}。
*/
@Schema(description = "最新售后单是否允许取消")
private Boolean latestAfterSaleCancelable;
/**
* 按原订单商品 ID 聚合后的退款商品列表。
*/