记得上下班打卡 | git大法好,push需谨慎

Commit 931332ff authored by wangyifan's avatar wangyifan

Merge branch 'dev-v1.7.3-pintuan' into 'master'

Dev v1.7.3 pintuan、归因

See merge request !438
parents 8d25b760 e3d3471d
This diff is collapsed.
-- 演出票退款原因配置表 + 退款单归因字段
CREATE TABLE IF NOT EXISTS `kylin_order_refund_reasons` (
`mid` int(11) NOT NULL AUTO_INCREMENT,
`reason_id` varchar(32) NOT NULL DEFAULT '' COMMENT '业务ID',
`name` varchar(100) NOT NULL DEFAULT '' COMMENT '退款理由名称',
`need_detail` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否需要二级补充输入 0否 1是',
`placeholder` varchar(200) NOT NULL DEFAULT '' COMMENT '二级输入placeholder',
`is_applied` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否已应用 0未应用 1应用中',
`sort` int(11) NOT NULL DEFAULT '0' COMMENT '排序,越大越靠前',
`is_deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否删除 0未删除 1已删除',
`created_at` datetime DEFAULT NULL,
`updated_at` datetime DEFAULT NULL,
PRIMARY KEY (`mid`),
UNIQUE KEY `uk_reason_id` (`reason_id`),
KEY `idx_is_applied_sort` (`is_applied`, `sort`),
KEY `idx_is_deleted` (`is_deleted`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='演出票退款原因配置';
-- 已建表环境补逻辑删除字段(若已存在可忽略报错)
ALTER TABLE `kylin_order_refund_reasons`
ADD COLUMN `is_deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否删除 0未删除 1已删除' AFTER `sort`;
ALTER TABLE `kylin_order_refund_reasons`
ADD KEY `idx_is_deleted` (`is_deleted`);
ALTER TABLE `kylin_order_refunds`
ADD COLUMN `reason_id` varchar(32) DEFAULT NULL COMMENT '退款原因配置ID' AFTER `reason`,
ADD COLUMN `reason_detail` varchar(200) DEFAULT NULL COMMENT '退款原因二级补充说明' AFTER `reason_id`;
-- 演出票退款原因管理 - 菜单与权限
-- 挂载在:正在映画 → 退款管理 下面(与「订单退款列表」「演出退款管理」同级)
SET @zhengzaiRootId = (
SELECT menu_id
FROM sys_menu
WHERE menu_name = '正在映画'
AND parent_id = 0
LIMIT 1
);
SET @refundManageId = (
SELECT menu_id
FROM sys_menu
WHERE menu_name = '退款管理'
AND parent_id = @zhengzaiRootId
LIMIT 1
);
INSERT INTO sys_menu (menu_name, parent_id, order_num, url, target, menu_type, visible, perms, icon, create_by, create_time, update_by, update_time, remark)
VALUES ('退款原因管理', @refundManageId, 2, '/kylin/refundReasons', 'menuItem', 'C', '0', 'kylin:refundReasons:list', '#', 'admin', NOW(), '', NULL, '演出票退款原因配置');
SET @menuId = LAST_INSERT_ID();
INSERT INTO sys_menu (menu_name, parent_id, order_num, url, target, menu_type, visible, perms, icon, create_by, create_time, update_by, update_time, remark)
VALUES
('退款原因查询', @menuId, 1, '#', '', 'F', '0', 'kylin:refundReasons:list', '#', 'admin', NOW(), '', NULL, ''),
('退款原因新增', @menuId, 2, '#', '', 'F', '0', 'kylin:refundReasons:create', '#', 'admin', NOW(), '', NULL, ''),
('退款原因修改', @menuId, 3, '#', '', 'F', '0', 'kylin:refundReasons:update', '#', 'admin', NOW(), '', NULL, ''),
('退款原因删除', @menuId, 4, '#', '', 'F', '0', 'kylin:refundReasons:delete', '#', 'admin', NOW(), '', NULL, ''),
('退款原因应用', @menuId, 5, '#', '', 'F', '0', 'kylin:refundReasons:apply', '#', 'admin', NOW(), '', NULL, '');
package com.liquidnet.service.kylin.constant;
/**
* 演出票拼团状态常量,覆盖活动、订单、成员三类状态机。
*/
public class GroupBuyStatusConst {
private GroupBuyStatusConst() {
}
/**
* 拼团活动状态:草稿 → 待启动 → 进行中 ⇄ 已关闭 → 已结束。
*/
public static class ActivityStatus {
/** 草稿(未提交) */
public static final Integer DRAFT = 0;
/** 待启动(手动/定时) */
public static final Integer PENDING = 1;
/** 进行中(用户可发起/加入拼团) */
public static final Integer ACTIVE = 2;
/** 已关闭(B端手动关停,可重新开启) */
public static final Integer CLOSED = 3;
/** 已结束(到结束时间,终态) */
public static final Integer ENDED = 4;
private ActivityStatus() {
}
}
/**
* 启动方式。
*/
public static class LaunchType {
/** 立即启动:保存后活动立即生效 */
public static final Integer IMMEDIATE = 1;
/** 手动启动:保存后状态=待启动,B端手动点启动 */
public static final Integer MANUAL = 2;
/** 定时启动:保存后状态=待启动,到定时时间自动激活 */
public static final Integer SCHEDULED = 3;
private LaunchType() {
}
}
/**
* 优惠类型。
*/
public static class DiscountType {
/** 固定价格(元) */
public static final Integer FIXED_PRICE = 1;
/** 折扣(1-100,如85表示85折) */
public static final Integer DISCOUNT = 2;
/** 满减(元) */
public static final Integer REDUCE = 3;
private DiscountType() {
}
}
/**
* 拼团订单状态:预占 → 拼单中 → 拼成 / 失败 / 已取消。
*/
public static class OrderStatus {
/** 待支付发起(团长已创建拼团但尚未下单) */
public static final Integer PREPARE = -1;
/** 拼单中(团长已支付,等待成员加入) */
public static final Integer PENDING = 0;
/** 拼成(达到目标人数) */
public static final Integer SUCCESS = 1;
/** 失败(24h超时未成团) */
public static final Integer FAILED = 2;
/** 已取消(团长手动取消) */
public static final Integer CANCELLED = 3;
private OrderStatus() {
}
}
/**
* 公开/私密标记。
*/
public static class PublicFlag {
/** 私密(不进公开轮播,仅通过 shareToken 加入) */
public static final Integer PRIVATE = 0;
/** 公开(进入轮播列表) */
public static final Integer PUBLIC = 1;
private PublicFlag() {
}
}
/**
* 拼团成员状态:下单后待支付 → 已加入 → 已退款。
*/
public static class MemberStatus {
/** 待支付(已下单但未支付/支付中) */
public static final Integer PENDING_PAY = -1;
/** 已加入(支付成功) */
public static final Integer JOINED = 0;
/** 已退款(拼单中退款/拼成后退款) */
public static final Integer REFUNDED = 1;
/** 已取消(支付超时/用户取消订单) */
public static final Integer CANCELLED = 2;
private MemberStatus() {
}
}
}
...@@ -36,6 +36,16 @@ public class KylinRedisConst { ...@@ -36,6 +36,16 @@ public class KylinRedisConst {
public static final String ORDER_REFUND_BY_ORDER_ID = "kylin:order:refund:orderId:"; public static final String ORDER_REFUND_BY_ORDER_ID = "kylin:order:refund:orderId:";
public static final String ORDER_REFUND = "kylin:order:refund:orderRefundsId:"; public static final String ORDER_REFUND = "kylin:order:refund:orderRefundsId:";
//拼团相关开始
public static final String GROUP_BUY_ACTIVITY = "kylin:groupBuy:activity:id:";
public static final String GROUP_BUY_ORDER = "kylin:groupBuy:order:id:";
public static final String GROUP_BUY_ORDER_LOCK = "kylin:groupBuy:lock:";
public static final String GROUP_BUY_ORDER_SHARE = "kylin:groupBuy:order:share:";
/** 拼团短信总开关:无 key 或非 0/false 则发送;SET 为 0/false 关停 */
public static final String GROUP_BUY_SMS_ENABLED = "kylin:groupBuy:sms:enabled";
/** 拼团 1h 提醒防重:key + groupOrderId */
public static final String GROUP_BUY_REMIND_1H = "kylin:groupBuy:remind1h:";
public static final String CAMERA_DEVICES = "kylin:cameraDevices:list"; public static final String CAMERA_DEVICES = "kylin:cameraDevices:list";
public static final String CAMERA_DEVICES_FIELD = "kylin:cameraDevices:fieldId:"; public static final String CAMERA_DEVICES_FIELD = "kylin:cameraDevices:fieldId:";
public static final String CAMERA_DEVICE_GB_INFO = "kylin:cameraDevices:gbId:"; public static final String CAMERA_DEVICE_GB_INFO = "kylin:cameraDevices:gbId:";
...@@ -96,6 +106,11 @@ public class KylinRedisConst { ...@@ -96,6 +106,11 @@ public class KylinRedisConst {
public static final String KYLIN_EXPRESS_MODULES = "kylin:express:modules"; public static final String KYLIN_EXPRESS_MODULES = "kylin:express:modules";
/**
* 已应用的演出票退款原因列表
*/
public static final String KYLIN_ORDER_REFUND_REASONS_APPLIED = "kylin:order:refund:reasons:applied";
public static final String REDIS_KEY_KYLIN_PERFORM_SUBSCRIBE = "kylin:performSubscribe:uid:"; public static final String REDIS_KEY_KYLIN_PERFORM_SUBSCRIBE = "kylin:performSubscribe:uid:";
public static final String REDIS_KEY_KYLIN_PERFORMANCE_SUBSCRIBE = "kylin:performance:subscribe:"; //演出预约 public static final String REDIS_KEY_KYLIN_PERFORMANCE_SUBSCRIBE = "kylin:performance:subscribe:"; //演出预约
......
package com.liquidnet.service.kylin.dto.param;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.List;
/**
* 创建拼团活动请求参数(不含 activityId)。
*/
@Data
@ApiModel(value = "KylinGroupBuyActivityCreateParam", description = "创建拼团活动")
public class KylinGroupBuyActivityCreateParam implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "活动名称", required = true)
@NotNull(message = "活动名称不能为空")
private String name;
@ApiModelProperty(value = "关联演出ID", required = true)
@NotNull(message = "关联演出ID不能为空")
private String performanceId;
@ApiModelProperty(value = "成团人数", required = true)
@NotNull(message = "成团人数不能为空")
private Integer minGroupSize;
@ApiModelProperty(value = "每人限购", required = true)
@NotNull(message = "每人限购不能为空")
private Integer maxBuyPerUser;
@ApiModelProperty(value = "活动开始时间,格式 yyyy-MM-dd HH:mm:ss", required = true, example = "2026-07-17 00:00:00")
@NotNull(message = "活动开始时间不能为空")
private String startTime;
@ApiModelProperty(value = "活动结束时间,格式 yyyy-MM-dd HH:mm:ss", required = true, example = "2026-12-31 23:59:59")
@NotNull(message = "活动结束时间不能为空")
private String endTime;
@ApiModelProperty(value = "启动方式 1立即 2手动 3定时", required = true)
@NotNull(message = "启动方式不能为空")
private Integer launchType;
@ApiModelProperty(value = "定时启动时间,格式 yyyy-MM-dd HH:mm:ss(launchType=3 时必填)", example = "2026-07-18 10:00:00")
private String scheduledTime;
@ApiModelProperty(value = "活动说明")
private String description;
@Valid
@NotEmpty(message = "票种优惠不能为空")
@ApiModelProperty(value = "票种优惠配置", required = true)
private List<DiscountParam> discounts;
@Data
@ApiModel(value = "KylinGroupBuyActivityDiscountParam", description = "拼团票种优惠")
public static class DiscountParam implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "场次ID", required = true)
@NotNull(message = "场次ID不能为空")
private String timeId;
@ApiModelProperty(value = "票种ID", required = true)
@NotNull(message = "票种ID不能为空")
private String ticketId;
@ApiModelProperty(value = "优惠类型 1固定价格 2折扣 3满减", required = true)
@NotNull(message = "优惠类型不能为空")
private Integer discountType;
@ApiModelProperty(value = "优惠值", required = true)
@NotNull(message = "优惠值不能为空")
private BigDecimal discountValue;
}
}
package com.liquidnet.service.kylin.dto.param;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.NotNull;
/**
* 编辑拼团活动请求参数(必传 activityId)。
*/
@Data
@EqualsAndHashCode(callSuper = true)
@ApiModel(value = "KylinGroupBuyActivityUpdateParam", description = "编辑拼团活动")
public class KylinGroupBuyActivityUpdateParam extends KylinGroupBuyActivityCreateParam {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "活动ID", required = true)
@NotNull(message = "活动ID不能为空")
private String activityId;
}
package com.liquidnet.service.kylin.dto.param;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.math.BigDecimal;
@Data
public class KylinGroupBuyCreateParam implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "拼团活动ID")
@NotNull(message = "拼团活动ID不能为空")
private String activityId;
@ApiModelProperty(value = "演出ID")
@NotNull(message = "演出ID不能为空")
private String performanceId;
@ApiModelProperty(value = "场次ID")
@NotNull(message = "场次ID不能为空")
private String timeId;
@ApiModelProperty(value = "票种ID")
@NotNull(message = "票种ID不能为空")
private String ticketId;
@ApiModelProperty(value = "购票张数")
@NotNull(message = "购票张数不能为空")
@Min(value = 1, message = "购票张数不能小于1")
private Integer ticketCount;
@ApiModelProperty(value = "是否公开 1公开 0私密,默认公开")
private Integer isPublic;
@ApiModelProperty(value = "前端展示拼团单价,仅用于服务端比对")
private BigDecimal groupBuyDisplayPrice;
}
package com.liquidnet.service.kylin.dto.param;
import com.liquidnet.service.kylin.entity.KylinOrderRefundReasons;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.beans.BeanUtils;
import java.time.LocalDateTime;
import java.util.List;
/**
* 退款原因配置参数
*/
@Data
@ApiModel("退款原因配置参数")
public class OrderRefundReasonParam {
@ApiModelProperty(value = "原因业务ID")
private String reasonId;
@ApiModelProperty(value = "退款理由名称")
private String name;
@ApiModelProperty(value = "是否需要二级补充输入 0否 1是")
private Integer needDetail;
@ApiModelProperty(value = "二级输入placeholder")
private String placeholder;
@ApiModelProperty(value = "是否已应用 0未应用 1应用中")
private Integer isApplied;
@ApiModelProperty(value = "排序")
private Integer sort;
@ApiModelProperty(value = "批量操作ID列表")
private List<String> ids;
public KylinOrderRefundReasons getFields(String reasonId, LocalDateTime createdAt) {
KylinOrderRefundReasons entity = new KylinOrderRefundReasons();
BeanUtils.copyProperties(this, entity);
if (reasonId != null) {
entity.setReasonId(reasonId);
}
if (createdAt != null) {
entity.setCreatedAt(createdAt);
}
if (entity.getNeedDetail() == null) {
entity.setNeedDetail(0);
}
if (entity.getPlaceholder() == null) {
entity.setPlaceholder("");
}
if (entity.getIsApplied() == null) {
entity.setIsApplied(0);
}
if (entity.getIsDeleted() == null) {
entity.setIsDeleted(0);
}
if (entity.getSort() == null) {
entity.setSort(0);
}
if (entity.getNeedDetail() != null && entity.getNeedDetail() == 0) {
entity.setPlaceholder("");
}
entity.setUpdatedAt(LocalDateTime.now());
return entity;
}
}
package com.liquidnet.service.kylin.dto.param;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* 退款原因配置搜索参数
*/
@Data
@ApiModel("退款原因配置搜索参数")
public class OrderRefundReasonSearchParam {
@ApiModelProperty(value = "退款理由名称")
private String name;
@ApiModelProperty(value = "是否已应用 0未应用 1应用中")
private Integer isApplied;
@ApiModelProperty(value = "页码")
private Integer pageNum = 1;
@ApiModelProperty(value = "每页数量")
private Integer pageSize = 10;
}
...@@ -71,4 +71,17 @@ public class PayOrderParam { ...@@ -71,4 +71,17 @@ public class PayOrderParam {
private String referrerApp; private String referrerApp;
@ApiModelProperty(value = "粉丝俱乐部侧用户id") @ApiModelProperty(value = "粉丝俱乐部侧用户id")
private String referrerUserId; private String referrerUserId;
@ApiModelProperty(value = "拼团订单ID,加入拼团时传")
private String groupOrderId;
@ApiModelProperty(value = "拼团活动ID,发起拼团时传")
private String groupBuyActivityId;
@ApiModelProperty(value = "是否公开拼团 1公开 0私密,发起时传,默认1")
private Integer isPublic;
@ApiModelProperty(value = "分享token,私密团加入时传")
private String shareToken;
} }
...@@ -23,6 +23,16 @@ public class RefundApplyParam implements Serializable { ...@@ -23,6 +23,16 @@ public class RefundApplyParam implements Serializable {
private String reason; private String reason;
/**
* 退款原因配置ID
*/
private String reasonId;
/**
* 退款原因二级补充说明
*/
private String reasonDetail;
private BigDecimal RefundPriceExpress; private BigDecimal RefundPriceExpress;
private List<String> ticketEntityIds; private List<String> ticketEntityIds;
......
...@@ -23,6 +23,16 @@ public class RefundBatchApplyParam implements Serializable { ...@@ -23,6 +23,16 @@ public class RefundBatchApplyParam implements Serializable {
private String reason; private String reason;
/**
* 退款原因配置ID
*/
private String reasonId;
/**
* 退款原因二级补充说明
*/
private String reasonDetail;
private Integer status; private Integer status;
private Integer refundStatus; private Integer refundStatus;
......
package com.liquidnet.service.kylin.dto.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* 退款原因配置 VO
*/
@Data
@ApiModel("退款原因配置")
public class OrderRefundReasonVo implements Serializable, Cloneable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "原因业务ID")
private String reasonId;
@ApiModelProperty(value = "退款理由名称")
private String name;
@ApiModelProperty(value = "是否需要二级补充输入 0否 1是")
private Integer needDetail;
@ApiModelProperty(value = "二级输入placeholder")
private String placeholder;
@ApiModelProperty(value = "是否已应用 0未应用 1应用中")
private Integer isApplied;
@ApiModelProperty(value = "应用状态名称")
private String isAppliedName;
@ApiModelProperty(value = "排序")
private Integer sort;
@ApiModelProperty(value = "创建时间")
private String createdAt;
@ApiModelProperty(value = "修改时间")
private String updatedAt;
private static final OrderRefundReasonVo OBJ = new OrderRefundReasonVo();
public static OrderRefundReasonVo getNew() {
try {
return (OrderRefundReasonVo) OBJ.clone();
} catch (CloneNotSupportedException e) {
return new OrderRefundReasonVo();
}
}
}
package com.liquidnet.service.kylin.dto.vo.groupbuy;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* 拼团成员简要信息(头像+ID)。
*/
@Data
public class GroupBuyMemberUserInfoVo implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "用户ID")
private String userId;
@ApiModelProperty(value = "头像URL")
private String avatar;
@ApiModelProperty(value = "昵称")
private String nickname;
@ApiModelProperty(value = "订单ID")
private String orderTicketsId;
}
package com.liquidnet.service.kylin.dto.vo.groupbuy;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
@Data
public class KylinGroupBuyActivityVo implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "活动ID")
private String activityId;
@ApiModelProperty(value = "活动名称")
private String name;
@ApiModelProperty(value = "演出ID")
private String performanceId;
@ApiModelProperty(value = "成团人数")
private Integer minGroupSize;
@ApiModelProperty(value = "每人限购")
private Integer maxBuyPerUser;
@ApiModelProperty(value = "活动开始时间")
private String startTime;
@ApiModelProperty(value = "活动结束时间")
private String endTime;
@ApiModelProperty(value = "启动方式 1立即 2手动 3定时")
private Integer launchType;
@ApiModelProperty(value = "定时启动时间")
private String scheduledTime;
@ApiModelProperty(value = "状态 0草稿 1待启动 2进行中 3已关闭 4已结束")
private Integer status;
@ApiModelProperty(value = "活动说明")
private String description;
@ApiModelProperty(value = "创建时间")
private String createdAt;
@ApiModelProperty(value = "更新时间")
private String updatedAt;
@ApiModelProperty(value = "优惠配置")
private List<KylinGroupBuyDiscountVo> discounts;
@ApiModelProperty(value = "关联演出名称(列表查询时填充)")
private String performanceName;
@ApiModelProperty(value = "拼单数量(列表查询时填充)")
private Long orderCount;
}
package com.liquidnet.service.kylin.dto.vo.groupbuy;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
@Data
public class KylinGroupBuyDiscountVo implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "优惠ID")
private String discountId;
@ApiModelProperty(value = "活动ID")
private String activityId;
@ApiModelProperty(value = "演出ID")
private String performanceId;
@ApiModelProperty(value = "场次ID")
private String timeId;
@ApiModelProperty(value = "票种ID")
private String ticketId;
@ApiModelProperty(value = "优惠类型 1固定价格 2折扣 3满减")
private Integer discountType;
@ApiModelProperty(value = "优惠值")
private BigDecimal discountValue;
@ApiModelProperty(value = "创建时间")
private String createdAt;
}
package com.liquidnet.service.kylin.dto.vo.groupbuy;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
@Data
public class KylinGroupBuyMemberVo implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "成员ID")
private String memberId;
@ApiModelProperty(value = "拼团订单ID")
private String groupOrderId;
@ApiModelProperty(value = "用户ID")
private String userId;
@ApiModelProperty(value = "订单ID")
private String orderTicketsId;
@ApiModelProperty(value = "购票张数")
private Integer ticketCount;
@ApiModelProperty(value = "状态 -1待支付 0已加入 1已退款")
private Integer status;
@ApiModelProperty(value = "加入时间")
private String joinTime;
@ApiModelProperty(value = "创建时间")
private String createdAt;
@ApiModelProperty(value = "昵称")
private String nickname;
@ApiModelProperty(value = "头像")
private String avatar;
}
package com.liquidnet.service.kylin.dto.vo.groupbuy;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
/**
* 我的拼团列表项(/my 用)。
*/
@Data
public class KylinGroupBuyMyGroupVo implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "拼团订单ID")
private String groupOrderId;
@ApiModelProperty(value = "活动ID")
private String activityId;
@ApiModelProperty(value = "团长用户ID")
private String leaderUserId;
@ApiModelProperty(value = "目标成团人数")
private Integer targetCount;
@ApiModelProperty(value = "当前参团人数")
private Integer currentCount;
@ApiModelProperty(value = "状态 0拼单中 1拼成 2失败 3已取消")
private Integer status;
@ApiModelProperty(value = "过期时间")
private String expiresAt;
@ApiModelProperty(value = "是否团长 1是 0否")
private Integer isLeader;
@ApiModelProperty(value = "演出标题")
private String performanceTitle;
@ApiModelProperty(value = "演出图片")
private String performanceImg;
@ApiModelProperty(value = "票种名称")
private String ticketTitle;
@ApiModelProperty(value = "场次名称")
private String timeTitle;
@ApiModelProperty(value = "成员头像列表")
private List<GroupBuyMemberUserInfoVo> memberBriefs;
}
package com.liquidnet.service.kylin.dto.vo.groupbuy;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* 拼单轮播卡片(/pending 用)。
*/
@Data
public class KylinGroupBuyOrderCardVo implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "拼团订单ID")
private String groupOrderId;
@ApiModelProperty(value = "活动ID")
private String activityId;
@ApiModelProperty(value = "团长用户ID")
private String leaderUserId;
@ApiModelProperty(value = "目标成团人数")
private Integer targetCount;
@ApiModelProperty(value = "当前参团人数")
private Integer currentCount;
@ApiModelProperty(value = "分享token")
private String shareToken;
}
package com.liquidnet.service.kylin.dto.vo.groupbuy;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.List;
/**
* 拼团详情(/detail 用)。
*/
@Data
public class KylinGroupBuyOrderDetailVo implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "拼团订单ID")
private String groupOrderId;
@ApiModelProperty(value = "活动ID")
private String activityId;
@ApiModelProperty(value = "团长用户ID")
private String leaderUserId;
@ApiModelProperty(value = "目标成团人数")
private Integer targetCount;
@ApiModelProperty(value = "当前参团人数")
private Integer currentCount;
@ApiModelProperty(value = "状态 0拼单中 1拼成 2失败 3已取消")
private Integer status;
@ApiModelProperty(value = "是否公开 1公开 0私密")
private Integer isPublic;
@ApiModelProperty(value = "分享token,分享/私密团加入时携带")
private String shareToken;
@ApiModelProperty(value = "过期时间")
private String expiresAt;
@ApiModelProperty(value = "创建时间")
private String createdAt;
@ApiModelProperty(value = "演出ID")
private String performanceId;
@ApiModelProperty(value = "演出名称")
private String performanceName;
@ApiModelProperty(value = "场次ID")
private String timeId;
@ApiModelProperty(value = "场次名称")
private String timeTitle;
@ApiModelProperty(value = "票种ID")
private String ticketId;
@ApiModelProperty(value = "票种名称")
private String ticketName;
@ApiModelProperty(value = "原价")
private BigDecimal originalPrice;
@ApiModelProperty(value = "拼团单价")
private BigDecimal groupBuyPrice;
@ApiModelProperty(value = "当前用户是否团长 1是 0否")
private Integer isLeader;
@ApiModelProperty(value = "拼团成员信息")
private List<GroupBuyMemberUserInfoVo> members;
}
package com.liquidnet.service.kylin.dto.vo.groupbuy;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
@Data
public class KylinGroupBuyOrderVo implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "拼团订单ID")
private String groupOrderId;
@ApiModelProperty(value = "活动ID")
private String activityId;
@ApiModelProperty(value = "团长用户ID")
private String leaderUserId;
@ApiModelProperty(value = "团长订单ID")
private String orderTicketsId;
@ApiModelProperty(value = "场次ID")
private String timeId;
@ApiModelProperty(value = "票种ID")
private String ticketId;
@ApiModelProperty(value = "目标成团人数")
private Integer targetCount;
@ApiModelProperty(value = "当前参团人数")
private Integer currentCount;
@ApiModelProperty(value = "状态 0拼单中 1拼成 2失败 3已取消")
private Integer status;
@ApiModelProperty(value = "是否公开 1公开 0私密")
private Integer isPublic;
@ApiModelProperty(value = "分享token")
private String shareToken;
@ApiModelProperty(value = "过期时间")
private String expiresAt;
@ApiModelProperty(value = "创建时间")
private String createdAt;
@ApiModelProperty(value = "更新时间")
private String updatedAt;
@ApiModelProperty(value = "是否已发送到期前1小时提醒 1已发 0/null未发")
private Integer remind1hSent;
@ApiModelProperty(value = "成员")
private List<KylinGroupBuyMemberVo> members;
}
package com.liquidnet.service.kylin.dto.vo.groupbuy;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.List;
/**
* 管理端拼团订单详情 VO(与列表 VO 分离)。
*/
@Data
public class KylinGroupBuyPartnerOrderDetailVo implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "拼团订单号")
private String groupOrderId;
@ApiModelProperty(value = "活动ID")
private String activityId;
@ApiModelProperty(value = "拼团活动名称")
private String activityName;
@ApiModelProperty(value = "订单状态 0拼团中 1拼团成功 2拼团失败 -1待支付发起 3已取消")
private Integer status;
@ApiModelProperty(value = "订单金额(团长票单实付)")
private BigDecimal orderAmount;
@ApiModelProperty(value = "创建时间")
private String createdAt;
@ApiModelProperty(value = "当前参团人数")
private Integer currentCount;
@ApiModelProperty(value = "成团目标人数")
private Integer targetCount;
@ApiModelProperty(value = "参团成员")
private List<KylinGroupBuyPartnerOrderMemberVo> members;
@ApiModelProperty(value = "发起人用户ID")
private String leaderUserId;
@ApiModelProperty(value = "发起人昵称")
private String leaderNickname;
@ApiModelProperty(value = "发起人头像")
private String leaderAvatar;
}
package com.liquidnet.service.kylin.dto.vo.groupbuy;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
/**
* 管理端拼团订单列表 VO(与详情 VO 分离)。
*/
@Data
public class KylinGroupBuyPartnerOrderListVo implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "拼团订单号 groupOrderId")
private String groupOrderId;
@ApiModelProperty(value = "拼团活动名称")
private String activityName;
@ApiModelProperty(value = "发起人用户ID")
private String leaderUserId;
@ApiModelProperty(value = "发起人昵称")
private String leaderNickname;
@ApiModelProperty(value = "发起人头像")
private String leaderAvatar;
@ApiModelProperty(value = "当前参团人数")
private Integer currentCount;
@ApiModelProperty(value = "成团目标人数")
private Integer targetCount;
@ApiModelProperty(value = "订单金额(团长票单实付)")
private BigDecimal orderAmount;
@ApiModelProperty(value = "创建时间 yyyy-MM-dd HH:mm:ss")
private String createdAt;
@ApiModelProperty(value = "状态 0拼团中 1拼团成功 2拼团失败;列表可能出现 -1待支付发起 3已取消")
private Integer status;
}
package com.liquidnet.service.kylin.dto.vo.groupbuy;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* 管理端拼团订单详情-成员项。
*/
@Data
public class KylinGroupBuyPartnerOrderMemberVo implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "用户ID")
private String userId;
@ApiModelProperty(value = "昵称")
private String nickname;
@ApiModelProperty(value = "头像")
private String avatar;
@ApiModelProperty(value = "是否团长 1是 0否")
private Integer isLeader;
@ApiModelProperty(value = "成员状态 -1待支付 0已加入 1已退款 2已取消")
private Integer status;
}
package com.liquidnet.service.kylin.dto.vo.groupbuy;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* 管理端拼团订单统计。
*/
@Data
public class KylinGroupBuyPartnerOrderStatsVo implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "订单总数(含待支付发起、已取消)")
private Long total;
@ApiModelProperty(value = "拼团中数量 status=0")
private Long pending;
@ApiModelProperty(value = "拼团成功数量 status=1")
private Long success;
@ApiModelProperty(value = "拼团失败数量 status=2")
private Long failed;
}
package com.liquidnet.service.kylin.dto.vo.groupbuy;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
@Data
public class KylinGroupBuyPriceVo implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "活动ID")
private String activityId;
@ApiModelProperty(value = "场次ID")
private String timeId;
@ApiModelProperty(value = "票种ID")
private String ticketId;
@ApiModelProperty(value = "原价")
private BigDecimal originalPrice;
@ApiModelProperty(value = "拼团单价")
private BigDecimal groupBuyPrice;
@ApiModelProperty(value = "优惠类型")
private Integer discountType;
@ApiModelProperty(value = "优惠值")
private BigDecimal discountValue;
}
package com.liquidnet.service.kylin.dto.vo.groupbuy;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* 刷新分享链接响应(/refreshShare 用)。
*/
@Data
public class KylinGroupBuyRefreshShareVo implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "拼团订单ID")
private String groupOrderId;
@ApiModelProperty(value = "新邀请token")
private String shareToken;
}
...@@ -49,6 +49,8 @@ public class KylinOrderTicketVo implements Serializable, Cloneable { ...@@ -49,6 +49,8 @@ public class KylinOrderTicketVo implements Serializable, Cloneable {
private String referrerApp; private String referrerApp;
@ApiModelProperty(position = 19, value = "粉丝俱乐部侧用户id") @ApiModelProperty(position = 19, value = "粉丝俱乐部侧用户id")
private String referrerUserId; private String referrerUserId;
@ApiModelProperty(position = 19, value = "拼团订单ID")
private String groupOrderId;
@ApiModelProperty(position = 20, value = "下单版本") @ApiModelProperty(position = 20, value = "下单版本")
private String orderVersion; private String orderVersion;
@ApiModelProperty(position = 20, value = "数量") @ApiModelProperty(position = 20, value = "数量")
......
...@@ -91,6 +91,12 @@ public class KylinOrderRefundsAdminVo implements Serializable,Cloneable { ...@@ -91,6 +91,12 @@ public class KylinOrderRefundsAdminVo implements Serializable,Cloneable {
@ApiModelProperty(value = "退款原因") @ApiModelProperty(value = "退款原因")
private String reason; private String reason;
@ApiModelProperty(value = "退款原因配置ID")
private String reasonId;
@ApiModelProperty(value = "退款原因二级补充说明")
private String reasonDetail;
@ApiModelProperty(value = "审核人id") @ApiModelProperty(value = "审核人id")
private String auditorId; private String auditorId;
......
...@@ -89,6 +89,12 @@ public class KylinOrderRefundsVo implements Serializable,Cloneable { ...@@ -89,6 +89,12 @@ public class KylinOrderRefundsVo implements Serializable,Cloneable {
@ApiModelProperty(value = "退款原因") @ApiModelProperty(value = "退款原因")
private String reason; private String reason;
@ApiModelProperty(value = "退款原因配置ID")
private String reasonId;
@ApiModelProperty(value = "退款原因二级补充说明")
private String reasonDetail;
@ApiModelProperty(value = "审核人id") @ApiModelProperty(value = "审核人id")
private String auditorId; private String auditorId;
......
package com.liquidnet.service.kylin.service;
/**
* 拼团短信通知(成功 / 超时失败 / 到期前 1h)。
* 主挂载点在 order(成功)与 platform(失败、1h);本接口供 kylin 侧复用。
*/
public interface GroupBuyNotifyService {
void notifySuccess(String groupOrderId);
void notifyFailed(String groupOrderId);
void notifyRemind1h(String groupOrderId);
}
package com.liquidnet.service.kylin.service;
import com.liquidnet.service.base.ResponseDto;
import com.liquidnet.service.kylin.dto.vo.groupbuy.KylinGroupBuyActivityVo;
import com.liquidnet.service.kylin.dto.vo.groupbuy.KylinGroupBuyPriceVo;
import java.util.List;
public interface IKylinGroupBuyActivityService {
ResponseDto<List<KylinGroupBuyActivityVo>> activities(String performanceId);
ResponseDto<KylinGroupBuyPriceVo> price(String activityId, String timeId, String ticketId);
}
package com.liquidnet.service.kylin.service;
import com.github.pagehelper.PageInfo;
import com.liquidnet.service.base.ResponseDto;
import com.liquidnet.service.kylin.dto.vo.groupbuy.KylinGroupBuyMyGroupVo;
import com.liquidnet.service.kylin.dto.vo.groupbuy.KylinGroupBuyOrderCardVo;
import com.liquidnet.service.kylin.dto.vo.groupbuy.KylinGroupBuyOrderDetailVo;
import com.liquidnet.service.kylin.dto.vo.groupbuy.KylinGroupBuyRefreshShareVo;
import java.util.List;
public interface IKylinGroupBuyOrderService {
ResponseDto<List<KylinGroupBuyOrderCardVo>> pending(String activityId, String timeId, String ticketId);
ResponseDto<PageInfo<KylinGroupBuyMyGroupVo>> my(Integer page, Integer size);
ResponseDto<KylinGroupBuyOrderDetailVo> detail(String groupOrderId, String shareToken);
ResponseDto<Boolean> cancel(String groupOrderId);
ResponseDto<KylinGroupBuyRefreshShareVo> refreshShare(String groupOrderId);
}
package com.liquidnet.service.kylin.service;
import com.liquidnet.service.kylin.dto.vo.OrderRefundReasonVo;
import java.util.List;
/**
* 退款原因(App/H5)
*/
public interface IKylinOrderRefundReasonService {
/**
* 已应用退款理由列表(Redis 缓存)
*/
List<OrderRefundReasonVo> listApplied();
/**
* 按 reasonId 从已应用列表取配置;不存在返回 null
*/
OrderRefundReasonVo getAppliedByReasonId(String reasonId);
}
package com.liquidnet.service.kylin.service.admin;
import com.github.pagehelper.PageInfo;
import com.liquidnet.service.kylin.dto.param.OrderRefundReasonParam;
import com.liquidnet.service.kylin.dto.param.OrderRefundReasonSearchParam;
import com.liquidnet.service.kylin.dto.vo.OrderRefundReasonVo;
import com.liquidnet.service.kylin.entity.KylinOrderRefundReasons;
import java.util.List;
/**
* 退款原因配置管理
*/
public interface IOrderRefundReasonAdminService {
Boolean create(OrderRefundReasonParam param);
Boolean update(OrderRefundReasonParam param);
OrderRefundReasonVo detail(String reasonId);
Boolean delete(List<String> reasonIds);
PageInfo<KylinOrderRefundReasons> reasonList(OrderRefundReasonSearchParam param);
/**
* 应用 / 取消应用
*
* @param reasonId 原因ID
* @param isApplied 1应用 0取消应用
*/
Boolean apply(String reasonId, Integer isApplied);
/**
* 已应用列表(读缓存)
*/
List<OrderRefundReasonVo> listApplied();
}
package com.liquidnet.service.kylin.service.partner;
import com.github.pagehelper.PageInfo;
import com.liquidnet.service.base.ResponseDto;
import com.liquidnet.service.kylin.dto.param.KylinGroupBuyActivityCreateParam;
import com.liquidnet.service.kylin.dto.param.KylinGroupBuyActivityUpdateParam;
import com.liquidnet.service.kylin.dto.vo.groupbuy.KylinGroupBuyActivityVo;
import com.liquidnet.service.kylin.dto.vo.groupbuy.KylinGroupBuyPartnerOrderDetailVo;
import com.liquidnet.service.kylin.dto.vo.groupbuy.KylinGroupBuyPartnerOrderListVo;
import com.liquidnet.service.kylin.dto.vo.groupbuy.KylinGroupBuyPartnerOrderStatsVo;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
public interface IKylinGroupBuyPartnerService {
ResponseDto<PageInfo<KylinGroupBuyActivityVo>> list(String name, Integer status, Integer page, Integer size);
ResponseDto<Map<String, Long>> stats();
ResponseDto<KylinGroupBuyActivityVo> detail(String activityId);
ResponseDto<KylinGroupBuyActivityVo> create(KylinGroupBuyActivityCreateParam param);
ResponseDto<KylinGroupBuyActivityVo> update(KylinGroupBuyActivityUpdateParam param);
ResponseDto<Boolean> delete(String activityId);
ResponseDto<Boolean> toggle(String activityId, Integer targetStatus);
/**
* 拼团订单顶部统计:总数 + 拼团中/成功/失败。
*/
ResponseDto<KylinGroupBuyPartnerOrderStatsVo> orderStats();
/**
* 拼团订单分页列表。status 仅允许 0/1/2 或空(空=全部含 -1/3)。
*/
ResponseDto<PageInfo<KylinGroupBuyPartnerOrderListVo>> orderList(String keyword, Integer status,
Integer page, Integer size);
/**
* 拼团订单详情。
*/
ResponseDto<KylinGroupBuyPartnerOrderDetailVo> orderDetail(String groupOrderId);
/**
* 导出拼团订单(列同列表,筛选同列表)。
*/
void exportOrders(String keyword, Integer status, HttpServletResponse response);
}
package com.liquidnet.service.kylin.utils;
import com.liquidnet.service.kylin.dto.vo.OrderRefundReasonVo;
import lombok.Getter;
import org.apache.commons.lang3.StringUtils;
import java.util.List;
/**
* 退款原因展示文案与校验工具
*/
public final class OrderRefundReasonUtils {
private OrderRefundReasonUtils() {
}
/**
* 校验已应用退款原因,并组装落库字段
*
* @param reasonId 原因配置 ID
* @param reasonDetail 二级补充说明
* @param appliedList 已应用原因列表(可从缓存取)
*/
public static ResolvedRefundReason resolve(String reasonId, String reasonDetail, List<OrderRefundReasonVo> appliedList) {
if (StringUtils.isBlank(reasonId)) {
return ResolvedRefundReason.fail("请选择退款原因");
}
OrderRefundReasonVo matched = findByReasonId(reasonId, appliedList);
if (matched == null) {
return ResolvedRefundReason.fail("退款原因无效或未应用");
}
return resolve(matched, reasonDetail);
}
/**
* 已拿到配置项时的校验与组装
*/
public static ResolvedRefundReason resolve(OrderRefundReasonVo matched, String reasonDetail) {
if (matched == null) {
return ResolvedRefundReason.fail("退款原因无效或未应用");
}
String detail = StringUtils.trimToEmpty(reasonDetail);
boolean needDetail = matched.getNeedDetail() != null && matched.getNeedDetail() == 1;
if (needDetail && StringUtils.isBlank(detail)) {
return ResolvedRefundReason.fail("请填写补充说明");
}
if (!needDetail) {
detail = null;
} else {
detail = truncate(detail, 200);
}
String snapshot = buildReasonSnapshot(matched.getName(), detail);
return ResolvedRefundReason.ok(matched.getReasonId(), detail, snapshot);
}
public static OrderRefundReasonVo findByReasonId(String reasonId, List<OrderRefundReasonVo> appliedList) {
if (StringUtils.isBlank(reasonId) || appliedList == null) {
return null;
}
for (OrderRefundReasonVo item : appliedList) {
if (reasonId.equals(item.getReasonId())) {
return item;
}
}
return null;
}
/**
* 组装 reason 快照:无二级写 name;有二级写 name:detail,截断至 200
*/
public static String buildReasonSnapshot(String name, String reasonDetail) {
String base = StringUtils.defaultString(name, "");
if (StringUtils.isBlank(reasonDetail)) {
return truncate(base, 200);
}
return truncate(base + ":" + reasonDetail.trim(), 200);
}
public static String truncate(String value, int maxLen) {
if (value == null) {
return "";
}
if (value.length() <= maxLen) {
return value;
}
return value.substring(0, maxLen);
}
@Getter
public static final class ResolvedRefundReason {
private final boolean success;
private final String errorMsg;
private final String reasonId;
private final String reasonDetail;
private final String reasonSnapshot;
private ResolvedRefundReason(boolean success, String errorMsg, String reasonId, String reasonDetail, String reasonSnapshot) {
this.success = success;
this.errorMsg = errorMsg;
this.reasonId = reasonId;
this.reasonDetail = reasonDetail;
this.reasonSnapshot = reasonSnapshot;
}
public static ResolvedRefundReason ok(String reasonId, String reasonDetail, String reasonSnapshot) {
return new ResolvedRefundReason(true, null, reasonId, reasonDetail, reasonSnapshot);
}
public static ResolvedRefundReason fail(String errorMsg) {
return new ResolvedRefundReason(false, errorMsg, null, null, null);
}
}
}
...@@ -10,7 +10,8 @@ public class SlimeAuthorizationConst { ...@@ -10,7 +10,8 @@ public class SlimeAuthorizationConst {
SALES("4", "销售统计"), SALES("4", "销售统计"),
CHECK("5", "验票"), CHECK("5", "验票"),
GRANT("6", "授权"), GRANT("6", "授权"),
AUDIT("7", "审核"); AUDIT("7", "审核"),
GROUP_BUY("8", "拼团管理");
private final String id; private final String id;
private final String name; private final String name;
......
package com.liquidnet.client.admin.web.controller.zhengzai.kylin;
import com.github.pagehelper.PageInfo;
import com.liquidnet.client.admin.common.annotation.Log;
import com.liquidnet.client.admin.common.core.controller.BaseController;
import com.liquidnet.client.admin.common.core.domain.AjaxResult;
import com.liquidnet.client.admin.common.core.page.TableDataInfo;
import com.liquidnet.client.admin.common.enums.BusinessType;
import com.liquidnet.client.admin.zhengzai.kylin.service.impl.KylinOrderRefundReasonAdminServiceImpl;
import com.liquidnet.service.kylin.dto.param.OrderRefundReasonParam;
import com.liquidnet.service.kylin.dto.param.OrderRefundReasonSearchParam;
import com.liquidnet.service.kylin.dto.vo.OrderRefundReasonVo;
import com.liquidnet.service.kylin.entity.KylinOrderRefundReasons;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 退款原因管理
*/
@Controller
@RequestMapping("/kylin/refundReasons")
public class KylinOrderRefundReasonsAdminController extends BaseController {
private String prefix = "zhengzai/kylin/refundReasons";
@Autowired
private KylinOrderRefundReasonAdminServiceImpl refundReasonAdminService;
@RequiresPermissions("kylin:refundReasons:list")
@GetMapping()
public String list() {
return prefix + "/refundReasons";
}
@RequiresPermissions("kylin:refundReasons:list")
@PostMapping("list")
@ResponseBody
public TableDataInfo listData(OrderRefundReasonSearchParam param) {
PageInfo<KylinOrderRefundReasons> result = refundReasonAdminService.reasonList(param);
return getDataTable(result.getList());
}
@GetMapping("/create")
public String create() {
return prefix + "/create";
}
@Log(title = "创建退款原因", businessType = BusinessType.INSERT)
@RequiresPermissions("kylin:refundReasons:create")
@PostMapping("create")
@ResponseBody
public AjaxResult createSave(OrderRefundReasonParam param) {
Boolean res = refundReasonAdminService.create(param);
return res ? success() : error("添加退款原因失败");
}
@GetMapping("/update/{reasonId}")
public String update(@PathVariable("reasonId") String reasonId, ModelMap mmap) {
OrderRefundReasonVo vo = refundReasonAdminService.detail(reasonId);
if (vo == null || (vo.getIsApplied() != null && vo.getIsApplied() == 1)) {
mmap.put("errorMsg", "应用中的退款原因不可编辑,请先取消应用");
}
mmap.put("RefundReasonVo", vo);
return prefix + "/update";
}
@Log(title = "修改退款原因", businessType = BusinessType.UPDATE)
@RequiresPermissions("kylin:refundReasons:update")
@PostMapping("update")
@ResponseBody
public AjaxResult updateSave(OrderRefundReasonParam param) {
Boolean res = refundReasonAdminService.update(param);
return res ? success() : error("修改失败(应用中不可编辑,或数据不存在)");
}
@Log(title = "删除退款原因", businessType = BusinessType.DELETE)
@RequiresPermissions("kylin:refundReasons:delete")
@PostMapping("delete")
@ResponseBody
public AjaxResult delete(OrderRefundReasonParam param) {
List<String> ids = param.getIds();
Boolean result = refundReasonAdminService.delete(ids);
return result ? success("删除成功") : error("删除失败(应用中的原因需先取消应用,或数据不存在)");
}
@Log(title = "应用/取消应用退款原因", businessType = BusinessType.UPDATE)
@RequiresPermissions("kylin:refundReasons:apply")
@PutMapping("apply")
@ResponseBody
public AjaxResult apply(@RequestParam("reasonId") String reasonId,
@RequestParam("isApplied") Integer isApplied) {
Boolean result = refundReasonAdminService.apply(reasonId, isApplied);
return result ? success() : error("操作失败");
}
/**
* 已应用列表(供申请页下拉,走缓存)
*/
@RequiresPermissions(value = {"kylin:refund:apply", "kylin:refundBatch:apply", "kylin:refundReasons:list"}, logical = Logical.OR)
@GetMapping("appliedList")
@ResponseBody
public AjaxResult appliedList() {
List<OrderRefundReasonVo> list = refundReasonAdminService.listApplied();
return AjaxResult.success(list);
}
}
...@@ -34,9 +34,17 @@ ...@@ -34,9 +34,17 @@
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="col-sm-2 control-label layui-required is-required">备注</label> <label class="col-sm-2 control-label layui-required is-required">退款原因</label>
<div class="col-sm-10"> <div class="col-sm-10">
<textarea name="reason" maxlength="500" class="form-control layui-required" rows="3"></textarea> <select name="reasonId" id="reasonId" class="form-control layui-required" required>
<option value="">请选择退款原因</option>
</select>
</div>
</div>
<div class="form-group" id="reasonDetailGroup" style="display:none;">
<label class="col-sm-2 control-label layui-required is-required">补充说明:</label>
<div class="col-sm-10">
<textarea name="reasonDetail" id="reasonDetail" maxlength="200" class="form-control" rows="3"></textarea>
</div> </div>
</div> </div>
</form> </form>
...@@ -44,6 +52,38 @@ ...@@ -44,6 +52,38 @@
<th:block th:include="include :: footer"/> <th:block th:include="include :: footer"/>
<script type="text/javascript"> <script type="text/javascript">
var prefix = ctx + "kylin/refund"; var prefix = ctx + "kylin/refund";
var reasonPrefix = ctx + "kylin/refundReasons";
function loadAppliedReasons() {
$.ajax({
url: reasonPrefix + "/appliedList",
type: "get",
dataType: "json",
success: function (result) {
if (result.code == 0 && result.data) {
var $sel = $("#reasonId");
$sel.find("option:not(:first)").remove();
for (var i = 0; i < result.data.length; i++) {
var item = result.data[i];
$sel.append('<option value="' + item.reasonId + '" data-need-detail="' + item.needDetail + '" data-placeholder="' + (item.placeholder || '') + '">' + item.name + '</option>');
}
}
}
});
}
function onReasonChange() {
var $opt = $("#reasonId option:selected");
var needDetail = $opt.data("need-detail");
var placeholder = $opt.data("placeholder") || "请填写补充说明";
if (needDetail == 1) {
$("#reasonDetailGroup").show();
$("#reasonDetail").attr("placeholder", placeholder);
} else {
$("#reasonDetailGroup").hide();
$("#reasonDetail").val("");
}
}
$("#form-post-add").validate({ $("#form-post-add").validate({
onkeyup: false, onkeyup: false,
...@@ -174,7 +214,8 @@ ...@@ -174,7 +214,8 @@
}); });
function submitHandler() { function submitHandler() {
var reason = $('#form-post-add').find("[name='reason']").val(); var reasonId = $('#form-post-add').find("[name='reasonId']").val();
var reasonDetail = $('#form-post-add').find("[name='reasonDetail']").val();
var orderTicketsId = $('#form-post-add').find("[name='orderTicketsId']").val(); var orderTicketsId = $('#form-post-add').find("[name='orderTicketsId']").val();
var rows = $.common.isEmpty(table.options.uniqueId) ? $.table.selectFirstColumns() : $.table.selectColumns(table.options.uniqueId); var rows = $.common.isEmpty(table.options.uniqueId) ? $.table.selectFirstColumns() : $.table.selectColumns(table.options.uniqueId);
if (!orderTicketsId) { if (!orderTicketsId) {
...@@ -185,8 +226,13 @@ ...@@ -185,8 +226,13 @@
layer.msg("请选择需要退款的入场人订单"); layer.msg("请选择需要退款的入场人订单");
return false; return false;
} }
if (!reason) { if (!reasonId) {
layer.msg("请填写备注"); layer.msg("请选择退款原因");
return false;
}
var $opt = $("#reasonId option:selected");
if ($opt.data("need-detail") == 1 && !$.common.trim(reasonDetail)) {
layer.msg("请填写补充说明");
return false; return false;
} }
if ($.validate.form()) { if ($.validate.form()) {
...@@ -204,6 +250,11 @@ ...@@ -204,6 +250,11 @@
} }
} }
$(function () {
loadAppliedReasons();
$("#reasonId").on("change", onReasonChange);
});
</script> </script>
</body> </body>
</html> </html>
...@@ -147,7 +147,7 @@ ...@@ -147,7 +147,7 @@
<div class="form-control-static" th:text="${KylinOrderRefundsAdminVo.applicantName}"></div> <div class="form-control-static" th:text="${KylinOrderRefundsAdminVo.applicantName}"></div>
<label class="col-sm-2 control-label">申请时间:</label> <label class="col-sm-2 control-label">申请时间:</label>
<div class="form-control-static" th:text="${KylinOrderRefundsAdminVo.applicantAt}"></div> <div class="form-control-static" th:text="${KylinOrderRefundsAdminVo.applicantAt}"></div>
<label class="col-sm-2 control-label">申请备注</label> <label class="col-sm-2 control-label">退款原因</label>
<div class="form-control-static" th:text="${KylinOrderRefundsAdminVo.reason}"></div> <div class="form-control-static" th:text="${KylinOrderRefundsAdminVo.reason}"></div>
</div> </div>
<div class="form-group"> <div class="form-group">
......
...@@ -368,6 +368,19 @@ ...@@ -368,6 +368,19 @@
field: 'priceExpress', field: 'priceExpress',
title: '退快递费金额' title: '退快递费金额'
}, },
{
field: 'reason',
title: '退款原因',
formatter: function (value) {
if (!value) {
return '';
}
if (value.length > 30) {
return '<span title="' + value.replace(/"/g, '&quot;') + '">' + value.substring(0, 30) + '...</span>';
}
return value;
}
},
{ {
field: 'statusName', field: 'statusName',
title: '退款状态' title: '退款状态'
......
...@@ -27,9 +27,17 @@ ...@@ -27,9 +27,17 @@
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="col-sm-3 control-label layui-required is-required">备注</label> <label class="col-sm-3 control-label layui-required is-required">退款原因</label>
<div class="col-sm-8"> <div class="col-sm-8">
<textarea name="reason" maxlength="500" class="form-control layui-required" rows="3"></textarea> <select name="reasonId" id="reasonId" class="form-control layui-required" required>
<option value="">请选择退款原因</option>
</select>
</div>
</div>
<div class="form-group" id="reasonDetailGroup" style="display:none;">
<label class="col-sm-3 control-label layui-required is-required">补充说明:</label>
<div class="col-sm-8">
<textarea name="reasonDetail" id="reasonDetail" maxlength="200" class="form-control" rows="3"></textarea>
</div> </div>
</div> </div>
</form> </form>
...@@ -37,7 +45,38 @@ ...@@ -37,7 +45,38 @@
<th:block th:include="include :: footer" /> <th:block th:include="include :: footer" />
<script type="text/javascript"> <script type="text/javascript">
var prefix = ctx + "kylin/refundBatch"; var prefix = ctx + "kylin/refundBatch";
var reasonPrefix = ctx + "kylin/refundReasons";
function loadAppliedReasons() {
$.ajax({
url: reasonPrefix + "/appliedList",
type: "get",
dataType: "json",
success: function (result) {
if (result.code == 0 && result.data) {
var $sel = $("#reasonId");
$sel.find("option:not(:first)").remove();
for (var i = 0; i < result.data.length; i++) {
var item = result.data[i];
$sel.append('<option value="' + item.reasonId + '" data-need-detail="' + item.needDetail + '" data-placeholder="' + (item.placeholder || '') + '">' + item.name + '</option>');
}
}
}
});
}
function onReasonChange() {
var $opt = $("#reasonId option:selected");
var needDetail = $opt.data("need-detail");
var placeholder = $opt.data("placeholder") || "请填写补充说明";
if (needDetail == 1) {
$("#reasonDetailGroup").show();
$("#reasonDetail").attr("placeholder", placeholder);
} else {
$("#reasonDetailGroup").hide();
$("#reasonDetail").val("");
}
}
$("#form-post-add").validate({ $("#form-post-add").validate({
onkeyup: false, onkeyup: false,
rules:{ rules:{
...@@ -136,14 +175,20 @@ ...@@ -136,14 +175,20 @@
}); });
function submitHandler() { function submitHandler() {
var reason = $('#form-post-add').find("[name='reason']").val(); var reasonId = $('#form-post-add').find("[name='reasonId']").val();
var reasonDetail = $('#form-post-add').find("[name='reasonDetail']").val();
var targetId = $('#form-post-add').find("[name='targetId']").val(); var targetId = $('#form-post-add').find("[name='targetId']").val();
if (!targetId) { if (!targetId) {
layer.msg("演出id有误"); layer.msg("演出id有误");
return false; return false;
} }
if (!reason) { if (!reasonId) {
layer.msg("请填写备注"); layer.msg("请选择退款原因");
return false;
}
var $opt = $("#reasonId option:selected");
if ($opt.data("need-detail") == 1 && !$.common.trim(reasonDetail)) {
layer.msg("请填写补充说明");
return false; return false;
} }
if ($.validate.form()) { if ($.validate.form()) {
...@@ -151,6 +196,11 @@ ...@@ -151,6 +196,11 @@
$.operate.save(prefix + "/apply", data); $.operate.save(prefix + "/apply", data);
} }
} }
$(function () {
loadAppliedReasons();
$("#reasonId").on("change", onReasonChange);
});
</script> </script>
</body> </body>
</html> </html>
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org">
<head>
<th:block th:include="include :: header('添加退款原因')"/>
</head>
<body class="white-bg">
<div class="wrapper wrapper-content animated fadeInRight ibox-content">
<form class="form-horizontal m" id="form-reason-add">
<div class="form-group">
<label class="col-sm-3 control-label is-required">退款理由名称:</label>
<div class="col-sm-8">
<input class="form-control" type="text" name="name" id="name"
maxlength="100" placeholder="展示给用户的文案" required>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label is-required">是否需补充说明:</label>
<div class="col-sm-8">
<div class="radio check-box">
<label>
<input type="radio" value="0" name="needDetail" checked> 否(仅选择)
</label>
</div>
<div class="radio check-box">
<label>
<input type="radio" value="1" name="needDetail"> 是(选择后需输入)
</label>
</div>
</div>
</div>
<div class="form-group" id="placeholderGroup" style="display:none;">
<label class="col-sm-3 control-label is-required">二级输入 placeholder:</label>
<div class="col-sm-8">
<input class="form-control" type="text" name="placeholder" id="placeholder"
maxlength="200" placeholder="请输入补充说明的提示文案">
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">排序:</label>
<div class="col-sm-8">
<input class="form-control" type="number" name="sort" id="sort" value="0">
<span class="help-block m-b-none"><i class="fa fa-info-circle"></i> 数值越大越靠前</span>
</div>
</div>
</form>
</div>
<th:block th:include="include :: footer"/>
<script type="text/javascript">
var prefix = ctx + "kylin/refundReasons";
function togglePlaceholderGroup() {
if ($('input[name="needDetail"]:checked').val() == '1') {
$('#placeholderGroup').show();
} else {
$('#placeholderGroup').hide();
$('#placeholder').val('');
}
}
function submitHandler() {
var name = $('#name').val().trim();
if (!name) {
$.modal.alertWarning("请填写退款理由名称");
return false;
}
var needDetail = $('input[name="needDetail"]:checked').val();
if (needDetail == '1' && !$('#placeholder').val().trim()) {
$.modal.alertWarning("请填写二级输入 placeholder");
return false;
}
if ($.validate.form()) {
var data = $('#form-reason-add').serializeArray();
$.operate.save(prefix + "/create", data);
}
}
$(function () {
// 本页 radio 走 iCheck,必须用 ifChecked,否则选「是」不会展示 placeholder
$('input[name="needDetail"]').on('ifChecked change', togglePlaceholderGroup);
togglePlaceholderGroup();
});
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
<th:block th:include="include :: header('退款原因列表')"/>
<style>
.btn-disabled-edit {
color: #ccc !important;
cursor: not-allowed !important;
pointer-events: none;
}
</style>
</head>
<body class="gray-bg">
<div class="container-div">
<div class="row">
<div class="col-sm-12 search-collapse">
<form id="formId">
<div class="select-list">
<ul>
<li>
<label>名称:</label>
<input type="text" name="name"/>
</li>
<li>
<label>应用状态:</label>
<select name="isApplied">
<option value="">全部</option>
<option value="1">应用中</option>
<option value="0">未应用</option>
</select>
</li>
<li>
<a class="btn btn-primary btn-rounded btn-sm" onclick="$.table.search()"><i
class="fa fa-search"></i>&nbsp;搜索</a>
<a class="btn btn-warning btn-rounded btn-sm" onclick="$.form.reset()"><i
class="fa fa-refresh"></i>&nbsp;重置</a>
</li>
</ul>
</div>
</form>
</div>
<div class="btn-group-sm" id="toolbar" role="group">
<a class="btn btn-success" onclick="$.operate.add()" shiro:hasPermission="kylin:refundReasons:create">
<i class="fa fa-plus"></i> 添加
</a>
</div>
<div class="col-sm-12 select-table table-bordered">
<table id="bootstrap-table"></table>
</div>
</div>
</div>
<th:block th:include="include :: footer"/>
<script th:inline="javascript">
var updateFlag = [[${@permission.hasPermi('kylin:refundReasons:update')}]];
var removeFlag = [[${@permission.hasPermi('kylin:refundReasons:delete')}]];
var applyFlag = [[${@permission.hasPermi('kylin:refundReasons:apply')}]];
var prefix = ctx + "kylin/refundReasons";
$(function () {
var options = {
url: prefix + "/list",
updateUrl: prefix + "/update/{id}",
createUrl: prefix + "/create",
removeUrl: prefix + "/delete",
modalName: "退款原因",
columns: [
{
field: 'name',
title: '退款理由'
},
{
field: 'needDetail',
title: '需补充说明',
formatter: function (value) {
return value == 1 ? '是' : '否';
}
},
{
field: 'placeholder',
title: '二级输入placeholder'
},
{
field: 'isApplied',
title: '应用状态',
formatter: function (value) {
if (value == 1) {
return '<span class="badge badge-primary">应用中</span>';
}
return '<span class="badge badge-warning">未应用</span>';
}
},
{
field: 'sort',
title: '排序'
},
{
field: 'updatedAt',
title: '更新时间',
formatter: function (value) {
if (!value) {
return '';
}
var date = new Date(value);
return date.getFullYear() + '-' +
String(date.getMonth() + 1).padStart(2, '0') + '-' +
String(date.getDate()).padStart(2, '0') + ' ' +
String(date.getHours()).padStart(2, '0') + ':' +
String(date.getMinutes()).padStart(2, '0') + ':' +
String(date.getSeconds()).padStart(2, '0');
}
},
{
title: '操作',
align: 'center',
formatter: function (value, row) {
var actions = [];
if (row.isApplied == 1) {
actions.push('<a class="btn btn-success btn-xs btn-disabled-edit" href="javascript:void(0)" title="请先取消应用"><i class="fa fa-edit"></i>编辑</a> ');
actions.push('<a class="btn btn-warning btn-xs ' + applyFlag + '" href="javascript:void(0)" onclick="toggleApply(\'' + row.reasonId + '\', 0)"><i class="fa fa-ban"></i>取消应用</a> ');
} else {
actions.push('<a class="btn btn-success btn-xs ' + updateFlag + '" href="javascript:void(0)" onclick="$.operate.edit(\'' + row.reasonId + '\')"><i class="fa fa-edit"></i>编辑</a> ');
actions.push('<a class="btn btn-primary btn-xs ' + applyFlag + '" href="javascript:void(0)" onclick="toggleApply(\'' + row.reasonId + '\', 1)"><i class="fa fa-check"></i>应用</a> ');
actions.push('<a class="btn btn-danger btn-xs ' + removeFlag + '" href="javascript:void(0)" onclick="removeReason(\'' + row.reasonId + '\')"><i class="fa fa-remove"></i>删除</a> ');
}
return actions.join('');
}
}]
};
$.table.init(options);
});
function removeReason(id) {
$.operate.remove(id, "确认删除该退款原因吗?");
}
function toggleApply(reasonId, isApplied) {
var title = isApplied == 1 ? '应用' : '取消应用';
$.modal.confirm('确定要' + title + '该退款原因吗?', function () {
$.ajax({
type: 'put',
url: prefix + '/apply',
data: {reasonId: reasonId, isApplied: isApplied},
success: function (e) {
if (e.code == 0) {
$.modal.msgSuccess(title + '成功');
$("button[name=refresh]").click();
} else {
$.modal.alertError(e.msg || (title + '失败'));
}
}
});
});
}
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org">
<head>
<th:block th:include="include :: header('修改退款原因')"/>
</head>
<body class="white-bg">
<div class="wrapper wrapper-content animated fadeInRight ibox-content">
<div th:if="${errorMsg != null}" class="alert alert-warning" th:text="${errorMsg}"></div>
<form class="form-horizontal m" id="form-reason-edit" th:if="${RefundReasonVo != null and RefundReasonVo.isApplied != 1}">
<input type="hidden" name="reasonId" th:value="${RefundReasonVo.reasonId}">
<div class="form-group">
<label class="col-sm-3 control-label is-required">退款理由名称:</label>
<div class="col-sm-8">
<input class="form-control" type="text" name="name" id="name"
maxlength="100" placeholder="展示给用户的文案"
th:value="${RefundReasonVo.name}" required>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label is-required">是否需补充说明:</label>
<div class="col-sm-8">
<div class="radio check-box">
<label>
<input type="radio" value="0" name="needDetail"
th:checked="${RefundReasonVo.needDetail == null or RefundReasonVo.needDetail == 0}"> 否(仅选择)
</label>
</div>
<div class="radio check-box">
<label>
<input type="radio" value="1" name="needDetail"
th:checked="${RefundReasonVo.needDetail == 1}"> 是(选择后需输入)
</label>
</div>
</div>
</div>
<div class="form-group" id="placeholderGroup"
th:style="${RefundReasonVo.needDetail == 1} ? '' : 'display:none;'">
<label class="col-sm-3 control-label is-required">二级输入 placeholder:</label>
<div class="col-sm-8">
<input class="form-control" type="text" name="placeholder" id="placeholder"
maxlength="200" placeholder="请输入补充说明的提示文案"
th:value="${RefundReasonVo.placeholder}">
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">排序:</label>
<div class="col-sm-8">
<input class="form-control" type="number" name="sort" id="sort"
th:value="${RefundReasonVo.sort}">
<span class="help-block m-b-none"><i class="fa fa-info-circle"></i> 数值越大越靠前</span>
</div>
</div>
</form>
</div>
<th:block th:include="include :: footer"/>
<script type="text/javascript">
var prefix = ctx + "kylin/refundReasons";
function togglePlaceholderGroup() {
if ($('input[name="needDetail"]:checked').val() == '1') {
$('#placeholderGroup').show();
} else {
$('#placeholderGroup').hide();
$('#placeholder').val('');
}
}
function submitHandler() {
if ($('#form-reason-edit').length === 0) {
$.modal.alertWarning("应用中的退款原因不可编辑");
return false;
}
var name = $('#name').val().trim();
if (!name) {
$.modal.alertWarning("请填写退款理由名称");
return false;
}
var needDetail = $('input[name="needDetail"]:checked').val();
if (needDetail == '1' && !$('#placeholder').val().trim()) {
$.modal.alertWarning("请填写二级输入 placeholder");
return false;
}
if ($.validate.form()) {
var data = $('#form-reason-edit').serializeArray();
$.operate.save(prefix + "/update", data);
}
}
$(function () {
// 本页 radio 走 iCheck,必须用 ifChecked,否则选「是」不会展示 placeholder
$('input[name="needDetail"]').on('ifChecked change', togglePlaceholderGroup);
togglePlaceholderGroup();
});
</script>
</body>
</html>
package com.liquidnet.client.admin.zhengzai.kylin.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.liquidnet.client.admin.zhengzai.kylin.utils.DataUtils;
import com.liquidnet.commons.lang.util.BeanUtil;
import com.liquidnet.commons.lang.util.DateUtil;
import com.liquidnet.commons.lang.util.IDGenerator;
import com.liquidnet.service.kylin.dto.param.OrderRefundReasonParam;
import com.liquidnet.service.kylin.dto.param.OrderRefundReasonSearchParam;
import com.liquidnet.service.kylin.dto.vo.OrderRefundReasonVo;
import com.liquidnet.service.kylin.entity.KylinOrderRefundReasons;
import com.liquidnet.service.kylin.mapper.KylinOrderRefundReasonsMapper;
import com.liquidnet.service.kylin.service.admin.IOrderRefundReasonAdminService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* 退款原因配置管理
*/
@Slf4j
@Service
public class KylinOrderRefundReasonAdminServiceImpl
extends ServiceImpl<KylinOrderRefundReasonsMapper, KylinOrderRefundReasons>
implements IOrderRefundReasonAdminService {
@Autowired
private KylinOrderRefundReasonsMapper reasonsMapper;
@Autowired
private DataUtils dataUtils;
@Override
public Boolean create(OrderRefundReasonParam param) {
try {
if (StringUtils.isBlank(param.getName())) {
return false;
}
String reasonId = IDGenerator.nextSnowId();
LocalDateTime now = LocalDateTime.now();
KylinOrderRefundReasons entity = param.getFields(reasonId, now);
// 新建默认未应用、未删除
entity.setIsApplied(0);
entity.setIsDeleted(0);
reasonsMapper.insert(entity);
refreshAppliedCache();
return true;
} catch (Exception e) {
log.error("创建退款原因失败", e);
return false;
}
}
@Override
public Boolean update(OrderRefundReasonParam param) {
try {
if (StringUtils.isBlank(param.getReasonId()) || StringUtils.isBlank(param.getName())) {
return false;
}
KylinOrderRefundReasons existing = reasonsMapper.selectOne(
new QueryWrapper<KylinOrderRefundReasons>()
.eq("reason_id", param.getReasonId())
.eq("is_deleted", 0)
);
if (existing == null) {
return false;
}
// 应用中不可编辑
if (existing.getIsApplied() != null && existing.getIsApplied() == 1) {
return false;
}
KylinOrderRefundReasons entity = param.getFields(null, null);
// 不允许通过 update 直接改应用态/删除态
entity.setIsApplied(null);
entity.setIsDeleted(null);
entity.setReasonId(null);
entity.setCreatedAt(null);
entity.setUpdatedAt(LocalDateTime.now());
reasonsMapper.update(entity, new UpdateWrapper<KylinOrderRefundReasons>()
.eq("reason_id", param.getReasonId())
.eq("is_deleted", 0));
refreshAppliedCache();
return true;
} catch (Exception e) {
log.error("修改退款原因失败", e);
return false;
}
}
@Override
public OrderRefundReasonVo detail(String reasonId) {
KylinOrderRefundReasons data = reasonsMapper.selectOne(
new QueryWrapper<KylinOrderRefundReasons>()
.eq("reason_id", reasonId)
.eq("is_deleted", 0)
);
if (data == null) {
return null;
}
return toVo(data);
}
@Override
public PageInfo<KylinOrderRefundReasons> reasonList(OrderRefundReasonSearchParam param) {
try {
PageHelper.startPage(param.getPageNum(), param.getPageSize());
Map<String, Object> paramMap = BeanUtil.convertBeanToMap(param);
// convertBeanToMap(isAll) 会把 null 变成 "",isApplied=0 需保留为整型
if (param.getIsApplied() != null) {
paramMap.put("isApplied", param.getIsApplied());
} else {
paramMap.remove("isApplied");
}
List<KylinOrderRefundReasons> list = reasonsMapper.searchList(paramMap);
if (list != null) {
for (KylinOrderRefundReasons item : list) {
item.setIsAppliedName(formatAppliedName(item.getIsApplied()));
}
}
return new PageInfo<>(list);
} catch (Exception e) {
log.error("获取退款原因列表失败", e);
return new PageInfo<>();
}
}
@Override
public Boolean delete(List<String> reasonIds) {
try {
if (reasonIds == null || reasonIds.isEmpty()) {
return false;
}
List<KylinOrderRefundReasons> list = reasonsMapper.selectList(
new QueryWrapper<KylinOrderRefundReasons>()
.in("reason_id", reasonIds)
.eq("is_deleted", 0)
);
if (list == null || list.isEmpty()) {
return false;
}
for (KylinOrderRefundReasons item : list) {
if (item.getIsApplied() != null && item.getIsApplied() == 1) {
return false;
}
}
// 逻辑删除,保留 reason_id 供历史退款单关联
KylinOrderRefundReasons update = new KylinOrderRefundReasons();
update.setIsDeleted(1);
update.setUpdatedAt(LocalDateTime.now());
reasonsMapper.update(update, new UpdateWrapper<KylinOrderRefundReasons>()
.in("reason_id", reasonIds)
.eq("is_deleted", 0));
refreshAppliedCache();
return true;
} catch (Exception e) {
log.error("删除退款原因失败", e);
return false;
}
}
@Override
public Boolean apply(String reasonId, Integer isApplied) {
try {
if (StringUtils.isBlank(reasonId) || isApplied == null || (isApplied != 0 && isApplied != 1)) {
return false;
}
KylinOrderRefundReasons existing = reasonsMapper.selectOne(
new QueryWrapper<KylinOrderRefundReasons>()
.eq("reason_id", reasonId)
.eq("is_deleted", 0)
);
if (existing == null) {
return false;
}
KylinOrderRefundReasons update = new KylinOrderRefundReasons();
update.setIsApplied(isApplied);
update.setUpdatedAt(LocalDateTime.now());
reasonsMapper.update(update, new UpdateWrapper<KylinOrderRefundReasons>()
.eq("reason_id", reasonId)
.eq("is_deleted", 0));
refreshAppliedCache();
return true;
} catch (Exception e) {
log.error("应用/取消应用退款原因失败", e);
return false;
}
}
@Override
public List<OrderRefundReasonVo> listApplied() {
List<OrderRefundReasonVo> cached = dataUtils.getOrderRefundReasonsApplied();
if (cached != null) {
return cached;
}
return refreshAppliedCache();
}
private List<OrderRefundReasonVo> refreshAppliedCache() {
List<KylinOrderRefundReasons> list = reasonsMapper.listApplied();
List<OrderRefundReasonVo> voList = new ArrayList<>();
if (list != null) {
for (KylinOrderRefundReasons item : list) {
voList.add(toVo(item));
}
}
dataUtils.setOrderRefundReasonsApplied(voList);
return voList;
}
private OrderRefundReasonVo toVo(KylinOrderRefundReasons data) {
OrderRefundReasonVo vo = new OrderRefundReasonVo();
BeanUtils.copyProperties(data, vo);
vo.setIsAppliedName(formatAppliedName(data.getIsApplied()));
if (data.getCreatedAt() != null) {
vo.setCreatedAt(DateUtil.Formatter.yyyyMMddHHmmss.format(data.getCreatedAt()));
}
if (data.getUpdatedAt() != null) {
vo.setUpdatedAt(DateUtil.Formatter.yyyyMMddHHmmss.format(data.getUpdatedAt()));
}
return vo;
}
private String formatAppliedName(Integer isApplied) {
if (isApplied != null && isApplied == 1) {
return "应用中";
}
return "未应用";
}
}
...@@ -31,6 +31,7 @@ import com.liquidnet.service.kylin.dto.vo.returns.RefundOrderDetailsVo; ...@@ -31,6 +31,7 @@ import com.liquidnet.service.kylin.dto.vo.returns.RefundOrderDetailsVo;
import com.liquidnet.service.kylin.entity.*; import com.liquidnet.service.kylin.entity.*;
import com.liquidnet.service.kylin.mapper.*; import com.liquidnet.service.kylin.mapper.*;
import com.liquidnet.service.kylin.service.IKylinOrderRefundsService; import com.liquidnet.service.kylin.service.IKylinOrderRefundsService;
import com.liquidnet.service.kylin.utils.OrderRefundReasonUtils;
import com.mongodb.BasicDBObject; import com.mongodb.BasicDBObject;
import com.mongodb.client.result.UpdateResult; import com.mongodb.client.result.UpdateResult;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
...@@ -89,6 +90,9 @@ public class KylinOrderRefundsServiceImpl extends ServiceImpl<KylinOrderRefundsM ...@@ -89,6 +90,9 @@ public class KylinOrderRefundsServiceImpl extends ServiceImpl<KylinOrderRefundsM
@Autowired @Autowired
private KylinOrderRefundPicMapper kylinOrderRefundPicMapper; private KylinOrderRefundPicMapper kylinOrderRefundPicMapper;
@Autowired
private KylinOrderRefundReasonAdminServiceImpl refundReasonAdminService;
@Autowired @Autowired
MongoTemplate mongoTemplate; MongoTemplate mongoTemplate;
@Autowired @Autowired
...@@ -96,6 +100,10 @@ public class KylinOrderRefundsServiceImpl extends ServiceImpl<KylinOrderRefundsM ...@@ -96,6 +100,10 @@ public class KylinOrderRefundsServiceImpl extends ServiceImpl<KylinOrderRefundsM
public ResponseDto refundApply(RefundApplyParam refundApplyParam, String authId, String authName) { public ResponseDto refundApply(RefundApplyParam refundApplyParam, String authId, String authName) {
try { try {
ResponseDto reasonCheck = fillRefundReason(refundApplyParam);
if (reasonCheck != null) {
return reasonCheck;
}
String orderTicketsId = refundApplyParam.getOrderTicketsId(); String orderTicketsId = refundApplyParam.getOrderTicketsId();
List<String> ticketEntityIds = refundApplyParam.getTicketEntityIds(); List<String> ticketEntityIds = refundApplyParam.getTicketEntityIds();
List<BigDecimal> entitiesPrice = refundApplyParam.getEntitiesPrice(); List<BigDecimal> entitiesPrice = refundApplyParam.getEntitiesPrice();
...@@ -574,4 +582,21 @@ public class KylinOrderRefundsServiceImpl extends ServiceImpl<KylinOrderRefundsM ...@@ -574,4 +582,21 @@ public class KylinOrderRefundsServiceImpl extends ServiceImpl<KylinOrderRefundsM
} }
return orderCode; return orderCode;
} }
/**
* 校验并填充退款原因快照(从已应用配置缓存)
*
* @return 失败时返回 ResponseDto;成功返回 null
*/
private ResponseDto fillRefundReason(RefundApplyParam param) {
OrderRefundReasonUtils.ResolvedRefundReason resolved = OrderRefundReasonUtils.resolve(
param.getReasonId(), param.getReasonDetail(), refundReasonAdminService.listApplied());
if (!resolved.isSuccess()) {
return ResponseDto.failure(resolved.getErrorMsg());
}
param.setReasonId(resolved.getReasonId());
param.setReasonDetail(resolved.getReasonDetail());
param.setReason(resolved.getReasonSnapshot());
return null;
}
} }
...@@ -52,7 +52,7 @@ public class KylinRefundExecuteServiceImpl { ...@@ -52,7 +52,7 @@ public class KylinRefundExecuteServiceImpl {
private KylinOrderRefundsServiceImpl kylinOrderRefundsServiceImpl; private KylinOrderRefundsServiceImpl kylinOrderRefundsServiceImpl;
@Async @Async
public void refundBatchApply(String targetId, String refundBatchId, String reason, String authId, String authName) { public void refundBatchApply(String targetId, String refundBatchId, String reason, String reasonId, String reasonDetail, String authId, String authName) {
RefundApplyParam refundApplyParam = new RefundApplyParam(); RefundApplyParam refundApplyParam = new RefundApplyParam();
int count; int count;
int limitNum = 10; int limitNum = 10;
...@@ -70,6 +70,8 @@ public class KylinRefundExecuteServiceImpl { ...@@ -70,6 +70,8 @@ public class KylinRefundExecuteServiceImpl {
refundApplyParam.setOrderRefundBatchesId(refundBatchId); refundApplyParam.setOrderRefundBatchesId(refundBatchId);
refundApplyParam.setReason(reason); refundApplyParam.setReason(reason);
refundApplyParam.setReasonId(reasonId);
refundApplyParam.setReasonDetail(reasonDetail);
refundApplyParam.setOrderTicketsId(order.getOrderTicketsId()); refundApplyParam.setOrderTicketsId(order.getOrderTicketsId());
refundApplyParam.setTicketEntityIds(ticketEntityIds); refundApplyParam.setTicketEntityIds(ticketEntityIds);
if (null != order.getPriceExpress()) { if (null != order.getPriceExpress()) {
......
...@@ -21,6 +21,7 @@ import com.liquidnet.service.kylin.mapper.KylinOrderRefundBatchesMapper; ...@@ -21,6 +21,7 @@ import com.liquidnet.service.kylin.mapper.KylinOrderRefundBatchesMapper;
import com.liquidnet.service.kylin.mapper.KylinOrderRefundsMapper; import com.liquidnet.service.kylin.mapper.KylinOrderRefundsMapper;
import com.liquidnet.service.kylin.mapper.KylinOrderTicketsMapper; import com.liquidnet.service.kylin.mapper.KylinOrderTicketsMapper;
import com.liquidnet.service.kylin.mapper.KylinPerformancesMapper; import com.liquidnet.service.kylin.mapper.KylinPerformancesMapper;
import com.liquidnet.service.kylin.utils.OrderRefundReasonUtils;
import com.liquidnet.service.dragon.support.WepayAppletPaySupport; import com.liquidnet.service.dragon.support.WepayAppletPaySupport;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
...@@ -65,7 +66,21 @@ public class KylinRefundPerformancesAdminServiceImpl { ...@@ -65,7 +66,21 @@ public class KylinRefundPerformancesAdminServiceImpl {
@Autowired @Autowired
RedisDataSourceUtil redisDataSourceUtil; RedisDataSourceUtil redisDataSourceUtil;
@Autowired
private KylinOrderRefundReasonAdminServiceImpl refundReasonAdminService;
public ResponseDto refundBatchApply(RefundBatchApplyParam refundBatchApplyParam) { public ResponseDto refundBatchApply(RefundBatchApplyParam refundBatchApplyParam) {
OrderRefundReasonUtils.ResolvedRefundReason resolved = OrderRefundReasonUtils.resolve(
refundBatchApplyParam.getReasonId(),
refundBatchApplyParam.getReasonDetail(),
refundReasonAdminService.listApplied());
if (!resolved.isSuccess()) {
return ResponseDto.failure(resolved.getErrorMsg());
}
refundBatchApplyParam.setReasonId(resolved.getReasonId());
refundBatchApplyParam.setReasonDetail(resolved.getReasonDetail());
refundBatchApplyParam.setReason(resolved.getReasonSnapshot());
String targetId = refundBatchApplyParam.getTargetId(); String targetId = refundBatchApplyParam.getTargetId();
// 查询需要退的金额和数量 // 查询需要退的金额和数量
String[] paymentTypeAlipay = WepayAppletPaySupport.alipayPaymentTypesForRefundStatis(); String[] paymentTypeAlipay = WepayAppletPaySupport.alipayPaymentTypesForRefundStatis();
...@@ -122,7 +137,8 @@ public class KylinRefundPerformancesAdminServiceImpl { ...@@ -122,7 +137,8 @@ public class KylinRefundPerformancesAdminServiceImpl {
// 开始执行批量提交审核 分批处理退款申请 // 开始执行批量提交审核 分批处理退款申请
String authId = ShiroUtils.getUserId().toString(); String authId = ShiroUtils.getUserId().toString();
String authName = ShiroUtils.getLoginName(); String authName = ShiroUtils.getLoginName();
kylinRefundExecuteServiceImpl.refundBatchApply(targetId, refundBatchId, refundBatchApplyParam.getReason(), authId, authName); kylinRefundExecuteServiceImpl.refundBatchApply(targetId, refundBatchId, refundBatchApplyParam.getReason(),
refundBatchApplyParam.getReasonId(), refundBatchApplyParam.getReasonDetail(), authId, authName);
return ResponseDto.success(); return ResponseDto.success();
} else { } else {
return ResponseDto.failure("操作失败"); return ResponseDto.failure("操作失败");
......
...@@ -172,6 +172,8 @@ public class KylinRefundsStatusServiceImpl { ...@@ -172,6 +172,8 @@ public class KylinRefundsStatusServiceImpl {
kylinOrderRefunds.setApplicantName(authName); kylinOrderRefunds.setApplicantName(authName);
kylinOrderRefunds.setApplicantAt(LocalDateTime.now()); kylinOrderRefunds.setApplicantAt(LocalDateTime.now());
kylinOrderRefunds.setReason(reason); kylinOrderRefunds.setReason(reason);
kylinOrderRefunds.setReasonId(refundApplyParam.getReasonId());
kylinOrderRefunds.setReasonDetail(refundApplyParam.getReasonDetail());
kylinOrderRefunds.setCreatedAt(LocalDateTime.now()); kylinOrderRefunds.setCreatedAt(LocalDateTime.now());
int rows = kylinOrderRefundsMapper.insert(kylinOrderRefunds); int rows = kylinOrderRefundsMapper.insert(kylinOrderRefunds);
// 添加缓存 // 添加缓存
......
...@@ -10,6 +10,7 @@ import com.liquidnet.service.base.constant.RedisKeyExpireConst; ...@@ -10,6 +10,7 @@ import com.liquidnet.service.base.constant.RedisKeyExpireConst;
import com.liquidnet.service.candy.param.BackCouponParam; import com.liquidnet.service.candy.param.BackCouponParam;
import com.liquidnet.service.kylin.constant.KylinRedisConst; import com.liquidnet.service.kylin.constant.KylinRedisConst;
import com.liquidnet.service.kylin.dto.vo.KylinExpressModuleVo; import com.liquidnet.service.kylin.dto.vo.KylinExpressModuleVo;
import com.liquidnet.service.kylin.dto.vo.OrderRefundReasonVo;
import com.liquidnet.service.kylin.dto.vo.admin.OrderRefundAddress; import com.liquidnet.service.kylin.dto.vo.admin.OrderRefundAddress;
import com.liquidnet.service.kylin.dto.vo.admin.OrderRefundPoundage; import com.liquidnet.service.kylin.dto.vo.admin.OrderRefundPoundage;
import com.liquidnet.service.kylin.dto.vo.express.KylinOrderExpressVo; import com.liquidnet.service.kylin.dto.vo.express.KylinOrderExpressVo;
...@@ -464,6 +465,23 @@ public class DataUtils { ...@@ -464,6 +465,23 @@ public class DataUtils {
redisDataSourceUtil.getRedisKylinUtil().set(redisKey, voList); redisDataSourceUtil.getRedisKylinUtil().set(redisKey, voList);
} }
public void setOrderRefundReasonsApplied(List<OrderRefundReasonVo> voList) {
redisDataSourceUtil.getRedisKylinUtil().set(KylinRedisConst.KYLIN_ORDER_REFUND_REASONS_APPLIED, voList);
}
@SuppressWarnings("unchecked")
public List<OrderRefundReasonVo> getOrderRefundReasonsApplied() {
Object obj = redisDataSourceUtil.getRedisKylinUtil().get(KylinRedisConst.KYLIN_ORDER_REFUND_REASONS_APPLIED);
if (obj == null) {
return null;
}
return (List<OrderRefundReasonVo>) obj;
}
public void delOrderRefundReasonsApplied() {
redisDataSourceUtil.getRedisKylinUtil().del(KylinRedisConst.KYLIN_ORDER_REFUND_REASONS_APPLIED);
}
/** /**
* 获取 票种快递票模板关联vo [第三方非线上] * 获取 票种快递票模板关联vo [第三方非线上]
* *
......
...@@ -77,6 +77,12 @@ public class SmsMessage implements Serializable, Cloneable { ...@@ -77,6 +77,12 @@ public class SmsMessage implements Serializable, Cloneable {
return this; return this;
} }
/** 无变量模板时传空对象 {},避免 templateParam 为 null */
public SmsMessage setEmptyTemplateParam() {
this.templateParam = JsonUtils.OM().createObjectNode();
return this;
}
public String toJson() { public String toJson() {
return JsonUtils.toJson(this); return JsonUtils.toJson(this);
} }
......
...@@ -17,6 +17,9 @@ public class RedisKeyExpireConst { ...@@ -17,6 +17,9 @@ public class RedisKeyExpireConst {
// kylin:order:list:userId 过期时间为30天 // kylin:order:list:userId 过期时间为30天
public static final long KYLIN_ORDER_LIST_USERID_EXPIRE = 30 * 24 * 60 * 60; public static final long KYLIN_ORDER_LIST_USERID_EXPIRE = 30 * 24 * 60 * 60;
// kylin:groupBuy 过期时间为30天
public static final long KYLIN_GROUP_BUY_EXPIRE = 30 * 24 * 60 * 60;
// adam:info:enters 过期时间为30天 // adam:info:enters 过期时间为30天
public static final long ADAM_INFO_ENTERS_EXPIRE = 30 * 24 * 60 * 60; public static final long ADAM_INFO_ENTERS_EXPIRE = 30 * 24 * 60 * 60;
......
...@@ -82,6 +82,24 @@ public class SmsEnum { ...@@ -82,6 +82,24 @@ public class SmsEnum {
* - 模版内容:欢迎加入登登登VIP大家庭。请微信关注“摩登天空服务号”,回复VIP,获取专属小助理客服服务了,回T退订 * - 模版内容:欢迎加入登登登VIP大家庭。请微信关注“摩登天空服务号”,回复VIP,获取专属小助理客服服务了,回T退订
*/ */
SMS_232908714, SMS_232908714,
/**
* 拼团成功
* - 恭喜拼团成功,您购买的是「${name}」演出的「${time}」场次「${ticket}」票种,祝您观演愉快
* - {"name":"...","time":"...","ticket":"..."}
*/
SMS_510540063,
/**
* 拼团失败/超时未成团
* - 实在抱歉,您的拼团订单在「${hours}」小时内无人拼单,拼团失败,已支付金额会在 1-3 个工作日原路退回,请注意查收。
* - {"hours":"24"}
*/
SMS_510615071,
/**
* 拼团倒计时剩 1 小时提醒
* - 您的拼团订单还有 1 小时到期,快转发朋友一起拼单吧,逾期未成团,订单将自动取消。
* - {}
*/
SMS_510285138,
; ;
} }
......
...@@ -163,6 +163,9 @@ global-auth: ...@@ -163,6 +163,9 @@ global-auth:
- ${liquidnet.info.context}/performance/artists/** - ${liquidnet.info.context}/performance/artists/**
# 艺人详情 C 端(无需登录) # 艺人详情 C 端(无需登录)
- ${liquidnet.info.context}/artist/** - ${liquidnet.info.context}/artist/**
# 拼团
- ${liquidnet.info.context}/groupBuy/detail
- ${liquidnet.info.context}/groupBuy/activities
oncheck-url-pattern: oncheck-url-pattern:
- ${liquidnet.info.context}/order/details - ${liquidnet.info.context}/order/details
- ${liquidnet.info.context}/order/transfer* - ${liquidnet.info.context}/order/transfer*
......
package com.liquidnet.service.kylin.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
import java.time.LocalDateTime;
@Data
@EqualsAndHashCode(callSuper = false)
public class KylinGroupBuyActivity implements Serializable, Cloneable {
private static final long serialVersionUID = 1L;
@TableId(value = "mid", type = IdType.AUTO)
private Integer mid;
private String activityId;
private String name;
private String performanceId;
private Integer minGroupSize;
private Integer maxBuyPerUser;
private LocalDateTime startTime;
private LocalDateTime endTime;
private Integer launchType;
private LocalDateTime scheduledTime;
private Integer status;
private String description;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private static final KylinGroupBuyActivity obj = new KylinGroupBuyActivity();
public static KylinGroupBuyActivity getNew() {
try {
return (KylinGroupBuyActivity) obj.clone();
} catch (CloneNotSupportedException e) {
return new KylinGroupBuyActivity();
}
}
}
package com.liquidnet.service.kylin.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Data
@EqualsAndHashCode(callSuper = false)
public class KylinGroupBuyDiscount implements Serializable, Cloneable {
private static final long serialVersionUID = 1L;
@TableId(value = "mid", type = IdType.AUTO)
private Integer mid;
private String discountId;
private String activityId;
private String performanceId;
private String timeId;
private String ticketId;
private Integer discountType;
private BigDecimal discountValue;
private LocalDateTime createdAt;
private static final KylinGroupBuyDiscount obj = new KylinGroupBuyDiscount();
public static KylinGroupBuyDiscount getNew() {
try {
return (KylinGroupBuyDiscount) obj.clone();
} catch (CloneNotSupportedException e) {
return new KylinGroupBuyDiscount();
}
}
}
package com.liquidnet.service.kylin.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
import java.time.LocalDateTime;
@Data
@EqualsAndHashCode(callSuper = false)
public class KylinGroupBuyMember implements Serializable, Cloneable {
private static final long serialVersionUID = 1L;
@TableId(value = "mid", type = IdType.AUTO)
private Integer mid;
private String memberId;
private String groupOrderId;
private String userId;
private String orderTicketsId;
private Integer ticketCount;
private Integer status;
private LocalDateTime joinTime;
private LocalDateTime createdAt;
private static final KylinGroupBuyMember obj = new KylinGroupBuyMember();
public static KylinGroupBuyMember getNew() {
try {
return (KylinGroupBuyMember) obj.clone();
} catch (CloneNotSupportedException e) {
return new KylinGroupBuyMember();
}
}
}
package com.liquidnet.service.kylin.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
import java.time.LocalDateTime;
@Data
@EqualsAndHashCode(callSuper = false)
public class KylinGroupBuyOrder implements Serializable, Cloneable {
private static final long serialVersionUID = 1L;
@TableId(value = "mid", type = IdType.AUTO)
private Integer mid;
private String groupOrderId;
private String activityId;
private String leaderUserId;
private String orderTicketsId;
private Integer targetCount;
private Integer currentCount;
private Integer status;
private Integer isPublic;
private String shareToken;
private LocalDateTime expiresAt;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private static final KylinGroupBuyOrder obj = new KylinGroupBuyOrder();
public static KylinGroupBuyOrder getNew() {
try {
return (KylinGroupBuyOrder) obj.clone();
} catch (CloneNotSupportedException e) {
return new KylinGroupBuyOrder();
}
}
}
package com.liquidnet.service.kylin.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 演出票退款原因配置表
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class KylinOrderRefundReasons implements Serializable, Cloneable {
private static final long serialVersionUID = 1L;
@TableId(value = "mid", type = IdType.AUTO)
private Integer mid;
/**
* 业务ID
*/
private String reasonId;
/**
* 退款理由名称
*/
private String name;
/**
* 是否需要二级补充输入 0否 1是
*/
private Integer needDetail;
/**
* 二级输入 placeholder
*/
private String placeholder;
/**
* 是否已应用 0未应用 1应用中
*/
private Integer isApplied;
/**
* 是否删除 0未删除 1已删除
*/
private Integer isDeleted;
/**
* 排序,越大越靠前
*/
private Integer sort;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
@TableField(exist = false)
private String isAppliedName;
private static final KylinOrderRefundReasons OBJ = new KylinOrderRefundReasons();
public static KylinOrderRefundReasons getNew() {
try {
return (KylinOrderRefundReasons) OBJ.clone();
} catch (CloneNotSupportedException e) {
return new KylinOrderRefundReasons();
}
}
}
...@@ -106,10 +106,20 @@ public class KylinOrderRefunds implements Serializable, Cloneable{ ...@@ -106,10 +106,20 @@ public class KylinOrderRefunds implements Serializable, Cloneable{
private LocalDateTime applicantAt; private LocalDateTime applicantAt;
/** /**
* 退款原因 * 退款原因(展示快照)
*/ */
private String reason; private String reason;
/**
* 退款原因配置ID
*/
private String reasonId;
/**
* 退款原因二级补充说明
*/
private String reasonDetail;
/** /**
* 审核人id * 审核人id
*/ */
......
package com.liquidnet.service.kylin.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.liquidnet.service.kylin.entity.KylinGroupBuyActivity;
public interface KylinGroupBuyActivityMapper extends BaseMapper<KylinGroupBuyActivity> {
}
package com.liquidnet.service.kylin.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.liquidnet.service.kylin.entity.KylinGroupBuyDiscount;
public interface KylinGroupBuyDiscountMapper extends BaseMapper<KylinGroupBuyDiscount> {
}
package com.liquidnet.service.kylin.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.liquidnet.service.kylin.entity.KylinGroupBuyMember;
public interface KylinGroupBuyMemberMapper extends BaseMapper<KylinGroupBuyMember> {
}
package com.liquidnet.service.kylin.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.liquidnet.service.kylin.entity.KylinGroupBuyOrder;
public interface KylinGroupBuyOrderMapper extends BaseMapper<KylinGroupBuyOrder> {
}
package com.liquidnet.service.kylin.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.liquidnet.service.kylin.entity.KylinOrderRefundReasons;
import java.util.List;
import java.util.Map;
/**
* 演出票退款原因配置 Mapper
*/
public interface KylinOrderRefundReasonsMapper extends BaseMapper<KylinOrderRefundReasons> {
List<KylinOrderRefundReasons> searchList(Map<String, Object> params);
List<KylinOrderRefundReasons> listApplied();
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.liquidnet.service.kylin.mapper.KylinOrderRefundReasonsMapper">
<select id="searchList" parameterType="java.util.Map" resultType="com.liquidnet.service.kylin.entity.KylinOrderRefundReasons">
SELECT
mid,
reason_id,
name,
need_detail,
placeholder,
is_applied,
sort,
is_deleted,
created_at,
updated_at
FROM kylin_order_refund_reasons
<where>
AND is_deleted = 0
<if test="name != null and name != ''">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
<!-- isApplied=0 时不能写 != '',OGNL 会把 0 当成空 -->
<if test="isApplied != null">
AND is_applied = #{isApplied}
</if>
</where>
ORDER BY sort DESC, mid DESC
</select>
<select id="listApplied" resultType="com.liquidnet.service.kylin.entity.KylinOrderRefundReasons">
SELECT
mid,
reason_id,
name,
need_detail,
placeholder,
is_applied,
sort,
is_deleted,
created_at,
updated_at
FROM kylin_order_refund_reasons
WHERE is_applied = 1
AND is_deleted = 0
ORDER BY sort DESC, mid DESC
</select>
</mapper>
...@@ -54,4 +54,13 @@ public interface FeignPlatformTaskClient { ...@@ -54,4 +54,13 @@ public interface FeignPlatformTaskClient {
@GetMapping("platform/refund/failRefund") @GetMapping("platform/refund/failRefund")
ResponseDto<Boolean> failRefund(); ResponseDto<Boolean> failRefund();
@PostMapping("platform/groupBuy/timeout")
ResponseDto<Boolean> groupBuyTimeout();
@PostMapping("platform/groupBuy/activitySchedule")
ResponseDto<Boolean> groupBuyActivitySchedule();
@PostMapping("platform/groupBuy/remind1h")
ResponseDto<Boolean> groupBuyRemind1h();
} }
...@@ -9,13 +9,18 @@ import com.liquidnet.commons.lang.util.HttpUtil; ...@@ -9,13 +9,18 @@ import com.liquidnet.commons.lang.util.HttpUtil;
import com.liquidnet.commons.lang.util.JsonUtils; import com.liquidnet.commons.lang.util.JsonUtils;
import com.liquidnet.service.base.SqlMapping; import com.liquidnet.service.base.SqlMapping;
import com.liquidnet.service.base.constant.MQConst; import com.liquidnet.service.base.constant.MQConst;
import com.liquidnet.service.base.constant.RedisKeyExpireConst;
import com.liquidnet.service.consumer.order.utils.GoblinNftUtils; import com.liquidnet.service.consumer.order.utils.GoblinNftUtils;
import com.liquidnet.service.consumer.order.utils.KylinOrderUtils; import com.liquidnet.service.consumer.order.utils.KylinOrderUtils;
import com.liquidnet.service.goblin.constant.GoblinRedisConst; import com.liquidnet.service.goblin.constant.GoblinRedisConst;
import com.liquidnet.service.goblin.constant.GoblinStatusConst; import com.liquidnet.service.goblin.constant.GoblinStatusConst;
import com.liquidnet.service.goblin.dto.vo.*; import com.liquidnet.service.goblin.dto.vo.*;
import com.liquidnet.service.goblin.param.BackCouponParam; import com.liquidnet.service.goblin.param.BackCouponParam;
import com.liquidnet.service.kylin.constant.GroupBuyStatusConst;
import com.liquidnet.service.kylin.constant.KylinRedisConst;
import com.liquidnet.service.kylin.constant.KylinTableStatusConst; import com.liquidnet.service.kylin.constant.KylinTableStatusConst;
import com.liquidnet.service.kylin.dto.vo.groupbuy.KylinGroupBuyMemberVo;
import com.liquidnet.service.kylin.dto.vo.groupbuy.KylinGroupBuyOrderVo;
import com.liquidnet.service.kylin.dto.vo.mongo.KylinOrderTicketEntitiesVo; import com.liquidnet.service.kylin.dto.vo.mongo.KylinOrderTicketEntitiesVo;
import com.liquidnet.service.kylin.dto.vo.mongo.KylinOrderTicketVo; import com.liquidnet.service.kylin.dto.vo.mongo.KylinOrderTicketVo;
import com.liquidnet.service.kylin.entity.KylinOrderCoupons; import com.liquidnet.service.kylin.entity.KylinOrderCoupons;
...@@ -386,8 +391,17 @@ public abstract class AbstractOrderCloseReceiver implements StreamListener<Strin ...@@ -386,8 +391,17 @@ public abstract class AbstractOrderCloseReceiver implements StreamListener<Strin
sqlDataOrderRelation.add(new Object[]{ sqlDataOrderRelation.add(new Object[]{
now, kylinOrderTicketVo.getOrderTicketsId(), now, kylinOrderTicketVo.getOrderTicketsId(),
}); });
sendMsgByRedis(MQConst.KylinQueue.SQL_ORDER_PAY.getKey(), // 拼团:Mongo + Redis + MySQL(MQ) 回收未支付占坑
SqlMapping.gets(sqls, sqlDataOrder, sqlDataOrderStatus, sqlDataOrderRelation)); LinkedList<Object[]> gbMember = CollectionUtil.linkedListObjectArr();
LinkedList<Object[]> gbOrder = CollectionUtil.linkedListObjectArr();
boolean hasGroupBuySql = appendGroupBuyCloseSql(kylinOrderTicketVo, now, sqls, gbMember, gbOrder);
if (hasGroupBuySql) {
sendMsgByRedis(MQConst.KylinQueue.SQL_ORDER_PAY.getKey(),
SqlMapping.gets(sqls, sqlDataOrder, sqlDataOrderStatus, sqlDataOrderRelation, gbMember, gbOrder));
} else {
sendMsgByRedis(MQConst.KylinQueue.SQL_ORDER_PAY.getKey(),
SqlMapping.gets(sqls, sqlDataOrder, sqlDataOrderStatus, sqlDataOrderRelation));
}
} }
return true; return true;
} catch (Exception e) { } catch (Exception e) {
...@@ -396,6 +410,107 @@ public abstract class AbstractOrderCloseReceiver implements StreamListener<Strin ...@@ -396,6 +410,107 @@ public abstract class AbstractOrderCloseReceiver implements StreamListener<Strin
} }
} }
/**
* 关单回收拼团占坑:先改 Mongo/Redis,再组装 MySQL SQL。
* @return 是否追加了拼团 SQL
*/
private boolean appendGroupBuyCloseSql(KylinOrderTicketVo ticket, LocalDateTime now,
LinkedList<String> sqls,
LinkedList<Object[]> gbMember,
LinkedList<Object[]> gbOrder) {
KylinGroupBuyOrderVo group = findGroupBuyForClose(ticket);
if (group == null || CollectionUtil.isEmpty(group.getMembers())) {
return false;
}
String lockKey = KylinRedisConst.GROUP_BUY_ORDER_LOCK.concat(group.getGroupOrderId());
if (!redisDataSourceUtil.getRedisKylinUtil().lock(lockKey, 1, 15)) {
log.warn("关单拼团获取锁失败 orderTicketsId={}, groupOrderId={}",
ticket.getOrderTicketsId(), group.getGroupOrderId());
return false;
}
try {
group = mongoTemplate.findOne(
Query.query(Criteria.where("groupOrderId").is(group.getGroupOrderId())),
KylinGroupBuyOrderVo.class, KylinGroupBuyOrderVo.class.getSimpleName());
if (group == null || CollectionUtil.isEmpty(group.getMembers())) {
return false;
}
KylinGroupBuyMemberVo member = null;
for (KylinGroupBuyMemberVo m : group.getMembers()) {
if (ticket.getOrderTicketsId().equals(m.getOrderTicketsId())) {
member = m;
break;
}
}
if (member == null || !GroupBuyStatusConst.MemberStatus.PENDING_PAY.equals(member.getStatus())) {
return false;
}
boolean isLeaderPrepare = GroupBuyStatusConst.OrderStatus.PREPARE.equals(group.getStatus())
&& Objects.equals(group.getLeaderUserId(), ticket.getUserId());
member.setStatus(GroupBuyStatusConst.MemberStatus.CANCELLED);
String nowStr = DateUtil.Formatter.yyyyMMddHHmmss.format(now);
if (isLeaderPrepare) {
group.setStatus(GroupBuyStatusConst.OrderStatus.CANCELLED);
group.setCurrentCount(0);
} else if (GroupBuyStatusConst.OrderStatus.PENDING.equals(group.getStatus())
|| GroupBuyStatusConst.OrderStatus.PREPARE.equals(group.getStatus())) {
int count = group.getCurrentCount() == null ? 0 : group.getCurrentCount();
group.setCurrentCount(Math.max(0, count - 1));
}
group.setUpdatedAt(nowStr);
mongoTemplate.remove(Query.query(Criteria.where("groupOrderId").is(group.getGroupOrderId())),
KylinGroupBuyOrderVo.class, KylinGroupBuyOrderVo.class.getSimpleName());
mongoTemplate.insert(group, KylinGroupBuyOrderVo.class.getSimpleName());
if (isLeaderPrepare) {
redisDataSourceUtil.getRedisKylinUtil().del(KylinRedisConst.GROUP_BUY_ORDER.concat(group.getGroupOrderId()));
if (StringUtils.isNotBlank(group.getShareToken())) {
redisDataSourceUtil.getRedisKylinUtil().del(KylinRedisConst.GROUP_BUY_ORDER_SHARE.concat(group.getShareToken()));
}
} else {
redisDataSourceUtil.getRedisKylinUtil().set(KylinRedisConst.GROUP_BUY_ORDER.concat(group.getGroupOrderId()),
group, RedisKeyExpireConst.KYLIN_GROUP_BUY_EXPIRE);
if (StringUtils.isNotBlank(group.getShareToken())) {
redisDataSourceUtil.getRedisKylinUtil().set(KylinRedisConst.GROUP_BUY_ORDER_SHARE.concat(group.getShareToken()),
group, RedisKeyExpireConst.KYLIN_GROUP_BUY_EXPIRE);
}
}
sqls.add(SqlMapping.get("kylin_group_buy_member.cancel_by_order_tickets"));
gbMember.add(new Object[]{ticket.getOrderTicketsId()});
if (isLeaderPrepare) {
sqls.add(SqlMapping.get("kylin_group_buy_order.cancel_if_prepare"));
gbOrder.add(new Object[]{now, group.getGroupOrderId()});
} else {
sqls.add(SqlMapping.get("kylin_group_buy_order.decr_count_if_pending"));
gbOrder.add(new Object[]{now, group.getGroupOrderId()});
}
return true;
} finally {
redisDataSourceUtil.getRedisKylinUtil().uLock(lockKey);
}
}
private KylinGroupBuyOrderVo findGroupBuyForClose(KylinOrderTicketVo ticket) {
if (StringUtils.isNotBlank(ticket.getGroupOrderId())) {
Object cached = redisDataSourceUtil.getRedisKylinUtil()
.get(KylinRedisConst.GROUP_BUY_ORDER.concat(ticket.getGroupOrderId()));
if (cached instanceof KylinGroupBuyOrderVo) {
return (KylinGroupBuyOrderVo) cached;
}
KylinGroupBuyOrderVo byId = mongoTemplate.findOne(
Query.query(Criteria.where("groupOrderId").is(ticket.getGroupOrderId())),
KylinGroupBuyOrderVo.class, KylinGroupBuyOrderVo.class.getSimpleName());
if (byId != null) {
return byId;
}
}
return mongoTemplate.findOne(
Query.query(Criteria.where("members.orderTicketsId").is(ticket.getOrderTicketsId())),
KylinGroupBuyOrderVo.class, KylinGroupBuyOrderVo.class.getSimpleName());
}
private UpdateResult updateGoblinStoreOrderVo(String orderId, GoblinStoreOrderVo data) { private UpdateResult updateGoblinStoreOrderVo(String orderId, GoblinStoreOrderVo data) {
BasicDBObject object = cloneBasicDBObject().append("$set", mongoConverter.convertToMongoType(data)); BasicDBObject object = cloneBasicDBObject().append("$set", mongoConverter.convertToMongoType(data));
return mongoTemplate.getCollection(GoblinStoreOrderVo.class.getSimpleName()).updateOne( return mongoTemplate.getCollection(GoblinStoreOrderVo.class.getSimpleName()).updateOne(
......
...@@ -9,4 +9,13 @@ goblin_activity.code.admin=UPDATE goblin_nft_ex_code SET admin_uid = ? where co ...@@ -9,4 +9,13 @@ goblin_activity.code.admin=UPDATE goblin_nft_ex_code SET admin_uid = ? where co
#---- 关闭票务订单 #---- 关闭票务订单
kylin_order_tickets.close=UPDATE kylin_order_tickets SET updated_at = ? where order_tickets_id = ? kylin_order_tickets.close=UPDATE kylin_order_tickets SET updated_at = ? where order_tickets_id = ?
kylin_order_ticket_status.close=UPDATE kylin_order_ticket_status SET status = ? , updated_at = ? where order_id = ? kylin_order_ticket_status.close=UPDATE kylin_order_ticket_status SET status = ? , updated_at = ? where order_id = ?
kylin_order_ticket_relations.close=UPDATE kylin_order_ticket_relations SET updated_at = ? where order_id = ? kylin_order_ticket_relations.close=UPDATE kylin_order_ticket_relations SET updated_at = ? where order_id = ?
\ No newline at end of file
# ------------------------演出票拼团-未支付关单----------------------------
kylin_group_buy_member.cancel_by_order_tickets=UPDATE kylin_group_buy_member SET status=2 WHERE order_tickets_id=? AND status=-1
kylin_group_buy_order.cancel_if_prepare=UPDATE kylin_group_buy_order SET status=3, current_count=0, updated_at=? WHERE group_order_id=? AND status=-1
kylin_group_buy_order.decr_count_if_pending=UPDATE kylin_group_buy_order SET current_count=current_count-1, updated_at=? WHERE group_order_id=? AND status=0 AND current_count>0
kylin_group_buy_order.delete=DELETE FROM kylin_group_buy_order WHERE group_order_id = ?
kylin_group_buy_order.insert=INSERT INTO kylin_group_buy_order (group_order_id,activity_id,leader_user_id,order_tickets_id,target_count,current_count,status,is_public,share_token,expires_at,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)
kylin_group_buy_member.delete_by_group=DELETE FROM kylin_group_buy_member WHERE group_order_id = ?
kylin_group_buy_member.insert=INSERT INTO kylin_group_buy_member (member_id,group_order_id,user_id,order_tickets_id,ticket_count,status,join_time,created_at) VALUES (?,?,?,?,?,?,?,?)
\ No newline at end of file
...@@ -122,4 +122,34 @@ public class KylinTaskHandler { ...@@ -122,4 +122,34 @@ public class KylinTaskHandler {
} }
} }
@XxlJob(value = "sev-platform:groupBuyTimeout")
public void groupBuyTimeout() {
try {
XxlJobHelper.handleSuccess("结果:" + feignPlatformTaskClient.groupBuyTimeout().getData());
} catch (Exception e) {
XxlJobHelper.log(e);
XxlJobHelper.handleFail();
}
}
@XxlJob(value = "sev-platform:groupBuyActivitySchedule")
public void groupBuyActivitySchedule() {
try {
XxlJobHelper.handleSuccess("结果:" + feignPlatformTaskClient.groupBuyActivitySchedule().getData());
} catch (Exception e) {
XxlJobHelper.log(e);
XxlJobHelper.handleFail();
}
}
@XxlJob(value = "sev-platform:groupBuyRemind1h")
public void groupBuyRemind1h() {
try {
XxlJobHelper.handleSuccess("结果:" + feignPlatformTaskClient.groupBuyRemind1h().getData());
} catch (Exception e) {
XxlJobHelper.log(e);
XxlJobHelper.handleFail();
}
}
} }
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for kylin_group_buy_activity
-- ----------------------------
CREATE TABLE IF NOT EXISTS `kylin_group_buy_activity` (
`mid` int NOT NULL AUTO_INCREMENT,
`activity_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '拼团活动ID',
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '活动名称',
`performance_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '演出ID',
`min_group_size` int NOT NULL COMMENT '成团人数',
`max_buy_per_user` int NOT NULL COMMENT '每人限购',
`start_time` datetime NOT NULL COMMENT '活动开始时间',
`end_time` datetime NOT NULL COMMENT '活动结束时间',
`launch_type` tinyint NOT NULL COMMENT '启动方式 1立即 2手动 3定时',
`scheduled_time` datetime DEFAULT NULL COMMENT '定时启动时间',
`status` tinyint NOT NULL COMMENT '状态 0草稿 1待启动 2进行中 3已关闭 4已结束',
`description` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '活动说明',
`created_at` datetime NOT NULL COMMENT '创建时间',
`updated_at` datetime NOT NULL COMMENT '更新时间',
PRIMARY KEY (`mid`) USING BTREE,
UNIQUE KEY `uk_activity_id` (`activity_id`) USING BTREE,
KEY `idx_performance_status` (`performance_id`, `status`) USING BTREE,
KEY `idx_status_schedule` (`status`, `launch_type`, `scheduled_time`) USING BTREE,
KEY `idx_status_end_time` (`status`, `end_time`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=Dynamic COMMENT='演出票拼团活动';
-- ----------------------------
-- Table structure for kylin_group_buy_discount
-- ----------------------------
CREATE TABLE IF NOT EXISTS `kylin_group_buy_discount` (
`mid` int NOT NULL AUTO_INCREMENT,
`discount_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '优惠ID',
`activity_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '拼团活动ID',
`performance_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '演出ID',
`time_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '场次ID',
`ticket_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '票种ID',
`discount_type` tinyint NOT NULL COMMENT '优惠类型 1固定价格 2折扣 3满减',
`discount_value` decimal(10,2) NOT NULL COMMENT '优惠值',
`created_at` datetime NOT NULL COMMENT '创建时间',
PRIMARY KEY (`mid`) USING BTREE,
UNIQUE KEY `uk_discount_id` (`discount_id`) USING BTREE,
UNIQUE KEY `uk_activity_ticket` (`activity_id`, `time_id`, `ticket_id`) USING BTREE,
KEY `idx_performance_ticket` (`performance_id`, `time_id`, `ticket_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=Dynamic COMMENT='演出票拼团优惠配置';
-- ----------------------------
-- Table structure for kylin_group_buy_order
-- ----------------------------
CREATE TABLE IF NOT EXISTS `kylin_group_buy_order` (
`mid` int NOT NULL AUTO_INCREMENT,
`group_order_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '拼团订单ID',
`activity_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '拼团活动ID',
`leader_user_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '团长用户ID',
`order_tickets_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '团长票务订单ID',
`target_count` int NOT NULL COMMENT '目标成团人数',
`current_count` int NOT NULL DEFAULT 0 COMMENT '当前参团人数',
`status` tinyint NOT NULL COMMENT '状态 -1待支付发起单 0拼单中 1拼成 2失败 3已取消',
`is_public` tinyint NOT NULL DEFAULT 1 COMMENT '是否公开 1公开 0私密',
`share_token` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '分享token',
`expires_at` datetime DEFAULT NULL COMMENT '拼团过期时间',
`created_at` datetime NOT NULL COMMENT '创建时间',
`updated_at` datetime NOT NULL COMMENT '更新时间',
PRIMARY KEY (`mid`) USING BTREE,
UNIQUE KEY `uk_group_order_id` (`group_order_id`) USING BTREE,
UNIQUE KEY `uk_share_token` (`share_token`) USING BTREE,
KEY `idx_activity_status` (`activity_id`, `status`) USING BTREE,
KEY `idx_status_expires` (`status`, `expires_at`) USING BTREE,
KEY `idx_leader_status` (`leader_user_id`, `status`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=Dynamic COMMENT='演出票拼团订单';
-- ----------------------------
-- Table structure for kylin_group_buy_member
-- ----------------------------
CREATE TABLE IF NOT EXISTS `kylin_group_buy_member` (
`mid` int NOT NULL AUTO_INCREMENT,
`member_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '成员ID',
`group_order_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '拼团订单ID',
`user_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '用户ID',
`order_tickets_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '票务订单ID',
`ticket_count` int NOT NULL COMMENT '购票张数',
`status` tinyint NOT NULL DEFAULT -1 COMMENT '状态 -1待支付 0已加入 1已退款 2已取消',
`join_time` datetime NOT NULL COMMENT '加入时间',
`created_at` datetime NOT NULL COMMENT '创建时间',
PRIMARY KEY (`mid`) USING BTREE,
UNIQUE KEY `uk_member_id` (`member_id`) USING BTREE,
UNIQUE KEY `uk_order_tickets_id` (`order_tickets_id`) USING BTREE,
KEY `idx_group_order_id` (`group_order_id`) USING BTREE,
KEY `idx_user_id` (`user_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=Dynamic COMMENT='演出票拼团成员';
-- 条件状态迁移示例:
-- UPDATE kylin_group_buy_order SET status = 1, updated_at = NOW()
-- WHERE group_order_id = ? AND status = 0 AND current_count >= target_count;
-- UPDATE kylin_group_buy_order SET status = 2, updated_at = NOW()
-- WHERE group_order_id = ? AND status = 0 AND expires_at < NOW();
-- UPDATE kylin_group_buy_order SET status = 3, updated_at = NOW()
-- WHERE group_order_id = ? AND status = 0;
SET FOREIGN_KEY_CHECKS = 1;
...@@ -793,6 +793,28 @@ alter table kylin_order_refunds add ticket_audit_name varchar(100) NOT NULL DE ...@@ -793,6 +793,28 @@ alter table kylin_order_refunds add ticket_audit_name varchar(100) NOT NULL DE
alter table kylin_order_refunds add ticket_audit_at timestamp NULL DEFAULT NULL COMMENT '票务审核时间' after ticket_audit_name; alter table kylin_order_refunds add ticket_audit_at timestamp NULL DEFAULT NULL COMMENT '票务审核时间' after ticket_audit_name;
alter table kylin_order_refunds add ticket_audit_remark varchar(200) NOT NULL DEFAULT '' COMMENT '票务审核备至' after ticket_audit_at; alter table kylin_order_refunds add ticket_audit_remark varchar(200) NOT NULL DEFAULT '' COMMENT '票务审核备至' after ticket_audit_at;
alter table kylin_order_refunds add reason_id varchar(32) DEFAULT NULL COMMENT '退款原因配置ID' after reason;
alter table kylin_order_refunds add reason_detail varchar(200) DEFAULT NULL COMMENT '退款原因二级补充说明' after reason_id;
# 演出票退款原因配置表
drop TABLE if exists `kylin_order_refund_reasons`;
CREATE TABLE `kylin_order_refund_reasons`
(
`mid` int(11) NOT NULL AUTO_INCREMENT,
`reason_id` varchar(32) NOT NULL DEFAULT '' COMMENT '业务ID',
`name` varchar(100) NOT NULL DEFAULT '' COMMENT '退款理由名称',
`need_detail` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否需要二级补充输入 0否 1是',
`placeholder` varchar(200) NOT NULL DEFAULT '' COMMENT '二级输入placeholder',
`is_applied` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否已应用 0未应用 1应用中',
`sort` int(11) NOT NULL DEFAULT '0' COMMENT '排序,越大越靠前',
`created_at` datetime DEFAULT NULL,
`updated_at` datetime DEFAULT NULL,
PRIMARY KEY (`mid`),
UNIQUE KEY `uk_reason_id` (`reason_id`),
KEY `idx_is_applied_sort` (`is_applied`, `sort`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT ='演出票退款原因配置';
drop TABLE if exists `kylin_order_refund_entities`; drop TABLE if exists `kylin_order_refund_entities`;
CREATE TABLE `kylin_order_refund_entities` CREATE TABLE `kylin_order_refund_entities`
( (
......
...@@ -55,6 +55,13 @@ ...@@ -55,6 +55,13 @@
<version>1.0-SNAPSHOT</version> <version>1.0-SNAPSHOT</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<!-- SmsProcessor / SmsEnum:拼团短信 -->
<dependency>
<groupId>com.liquidnet</groupId>
<artifactId>liquidnet-common-sms</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>
......
...@@ -8,6 +8,7 @@ import com.liquidnet.service.adam.dto.vo.AdamUserMemberVo; ...@@ -8,6 +8,7 @@ import com.liquidnet.service.adam.dto.vo.AdamUserMemberVo;
import com.liquidnet.service.base.ResponseDto; import com.liquidnet.service.base.ResponseDto;
import com.liquidnet.service.kylin.service.IKylinConsmerService; import com.liquidnet.service.kylin.service.IKylinConsmerService;
import com.liquidnet.service.kylin.service.IKylinPerformancesService; import com.liquidnet.service.kylin.service.IKylinPerformancesService;
import com.liquidnet.service.kylin.service.impl.KylinOrderTicketsRefundServiceImpl;
import com.liquidnet.service.kylin.service.impl.KylinPerformancesServiceImpl; import com.liquidnet.service.kylin.service.impl.KylinPerformancesServiceImpl;
import com.liquidnet.service.kylin.utils.DataUtils; import com.liquidnet.service.kylin.utils.DataUtils;
import io.swagger.annotations.Api; import io.swagger.annotations.Api;
...@@ -39,6 +40,9 @@ public class InnerController { ...@@ -39,6 +40,9 @@ public class InnerController {
@Autowired @Autowired
private KylinPerformancesServiceImpl kylinPerformancesService; private KylinPerformancesServiceImpl kylinPerformancesService;
@Autowired
private KylinOrderTicketsRefundServiceImpl kylinOrderTicketsRefundService;
@GetMapping("cache/member/{uid}") @GetMapping("cache/member/{uid}")
@ApiOperation("获取会员相关数据") @ApiOperation("获取会员相关数据")
public void cacheMember(@NotBlank @PathVariable(value = "uid") String uid) { public void cacheMember(@NotBlank @PathVariable(value = "uid") String uid) {
...@@ -81,4 +85,17 @@ public class InnerController { ...@@ -81,4 +85,17 @@ public class InnerController {
return ResponseDto.success(map); return ResponseDto.success(map);
} }
/**
* 系统全额退款(拼团超时等平台任务调用)。走 /inner/**,无需登录态。
*/
@PostMapping("orderRefund/systemFullRefund")
@ApiOperation("系统全额退款")
public ResponseDto systemFullRefund(
@RequestParam("orderId") @NotBlank(message = "订单ID不能为空") String orderId,
@RequestParam("orderTicketEntitiesId") @NotBlank(message = "票单ID不能为空") String orderTicketEntitiesId,
@RequestParam(value = "reason", required = false) String reason
) {
return kylinOrderTicketsRefundService.systemFullRefund(orderId, orderTicketEntitiesId, reason);
}
} }
package com.liquidnet.service.kylin.controller;
import com.github.pagehelper.PageInfo;
import com.liquidnet.service.base.ResponseDto;
import com.liquidnet.service.kylin.dto.vo.groupbuy.*;
import com.liquidnet.service.kylin.service.IKylinGroupBuyActivityService;
import com.liquidnet.service.kylin.service.IKylinGroupBuyOrderService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.constraints.NotNull;
import java.util.List;
@Api(tags = "前端-拼团")
@RestController
@RequestMapping("/groupBuy")
@Validated
public class KylinGroupBuyController {
@Autowired
private IKylinGroupBuyActivityService activityService;
@Autowired
private IKylinGroupBuyOrderService orderService;
@GetMapping("/activities")
@ApiOperation("演出下进行中的拼团活动")
public ResponseDto<List<KylinGroupBuyActivityVo>> activities(
@RequestParam("performanceId") @NotNull(message = "演出ID不能为空") String performanceId) {
return activityService.activities(performanceId);
}
@GetMapping("/orders/pending")
@ApiOperation("待成团公开拼单")
public ResponseDto<List<KylinGroupBuyOrderCardVo>> pending(
@RequestParam("activityId") @NotNull(message = "活动ID不能为空") String activityId,
@RequestParam("timeId") @NotNull(message = "场次ID不能为空") String timeId,
@RequestParam("ticketId") @NotNull(message = "票种ID不能为空") String ticketId) {
return orderService.pending(activityId, timeId, ticketId);
}
@GetMapping("/my")
@ApiOperation("我的拼团列表")
public ResponseDto<PageInfo<KylinGroupBuyMyGroupVo>> my(
@RequestParam(value = "page", required = false, defaultValue = "1") Integer page,
@RequestParam(value = "size", required = false, defaultValue = "20") Integer size) {
return orderService.my(page, size);
}
@GetMapping("/detail")
@ApiOperation("拼团详情")
public ResponseDto<KylinGroupBuyOrderDetailVo> detail(@RequestParam(value = "groupOrderId", required = false) String groupOrderId,
@RequestParam(value = "shareToken", required = false) String shareToken) {
return orderService.detail(groupOrderId, shareToken);
}
@PostMapping("/cancel")
@ApiOperation("团长取消拼团")
public ResponseDto<Boolean> cancel(@RequestParam("groupOrderId") @NotNull(message = "拼团订单ID不能为空") String groupOrderId) {
return orderService.cancel(groupOrderId);
}
@PostMapping("/refreshShare")
@ApiOperation("刷新邀请链接")
public ResponseDto<KylinGroupBuyRefreshShareVo> refreshShare(
@RequestParam("groupOrderId") @NotNull(message = "拼团订单ID不能为空") String groupOrderId) {
return orderService.refreshShare(groupOrderId);
}
@GetMapping("/price")
@ApiOperation("查询拼团价")
public ResponseDto<KylinGroupBuyPriceVo> price(
@RequestParam("activityId") @NotNull(message = "活动ID不能为空") String activityId,
@RequestParam("timeId") @NotNull(message = "场次ID不能为空") String timeId,
@RequestParam("ticketId") @NotNull(message = "票种ID不能为空") String ticketId) {
return activityService.price(activityId, timeId, ticketId);
}
}
package com.liquidnet.service.kylin.controller;
import com.liquidnet.service.base.ResponseDto;
import com.liquidnet.service.kylin.dto.vo.OrderRefundReasonVo;
import com.liquidnet.service.kylin.service.impl.KylinOrderRefundReasonServiceImpl;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* 退款原因(App/H5)
*/
@Api(tags = "前端-退款原因")
@RestController
@RequestMapping("orderRefundReason")
@Validated
public class KylinOrderRefundReasonController {
@Autowired
private KylinOrderRefundReasonServiceImpl refundReasonService;
@GetMapping("appliedList")
@ApiOperation("已应用退款理由列表")
@ApiResponse(code = 200, message = "接口返回对象参数")
public ResponseDto<List<OrderRefundReasonVo>> appliedList() {
return ResponseDto.success(refundReasonService.listApplied());
}
}
...@@ -63,7 +63,9 @@ public class KylinOrderTicketsRefundController { ...@@ -63,7 +63,9 @@ public class KylinOrderTicketsRefundController {
@ApiImplicitParams({ @ApiImplicitParams({
@ApiImplicitParam(type = "form", dataType = "String", name = "orderId", value = "订单ID", required = true), @ApiImplicitParam(type = "form", dataType = "String", name = "orderId", value = "订单ID", required = true),
@ApiImplicitParam(type = "form", dataType = "String", name = "orderTicketEntitiesId", value = "订单入场人ID", required = true), @ApiImplicitParam(type = "form", dataType = "String", name = "orderTicketEntitiesId", value = "订单入场人ID", required = true),
@ApiImplicitParam(type = "form", dataType = "String", name = "reason", value = "退款申请备注", required = false), @ApiImplicitParam(type = "form", dataType = "String", name = "reasonId", value = "退款原因配置ID", required = true),
@ApiImplicitParam(type = "form", dataType = "String", name = "reasonDetail", value = "退款原因补充说明"),
@ApiImplicitParam(type = "form", dataType = "String", name = "reason", value = "退款申请备注(兼容旧版,优先使用reasonId)", required = false),
@ApiImplicitParam(type = "form", dataType = "String", name = "picList", value = "图片"), @ApiImplicitParam(type = "form", dataType = "String", name = "picList", value = "图片"),
@ApiImplicitParam(type = "form", dataType = "String", name = "ticketNum", value = "纸质票号"), @ApiImplicitParam(type = "form", dataType = "String", name = "ticketNum", value = "纸质票号"),
@ApiImplicitParam(type = "form", dataType = "Integer", name = "sendExpressType", value = "快递方式 1上门取件 2自主发货"), @ApiImplicitParam(type = "form", dataType = "Integer", name = "sendExpressType", value = "快递方式 1上门取件 2自主发货"),
...@@ -71,12 +73,16 @@ public class KylinOrderTicketsRefundController { ...@@ -71,12 +73,16 @@ public class KylinOrderTicketsRefundController {
@ApiImplicitParam(type = "form", dataType = "String", name = "expressContacts", value = "发货人姓名"), @ApiImplicitParam(type = "form", dataType = "String", name = "expressContacts", value = "发货人姓名"),
@ApiImplicitParam(type = "form", dataType = "String", name = "expressPhone", value = "发货人联系方式"), @ApiImplicitParam(type = "form", dataType = "String", name = "expressPhone", value = "发货人联系方式"),
@ApiImplicitParam(type = "form", dataType = "String", name = "sendExpressAddress", value = "取件地址"), @ApiImplicitParam(type = "form", dataType = "String", name = "sendExpressAddress", value = "取件地址"),
@ApiImplicitParam(type = "form", dataType = "String", name = "appointmentTime", value = "预约取件时间") @ApiImplicitParam(type = "form", dataType = "String", name = "appointmentTime", value = "预约取件时间"),
@ApiImplicitParam(type = "form", dataType = "Boolean", name = "skipCharges", value = "系统退款免手续费"),
@ApiImplicitParam(type = "form", dataType = "Boolean", name = "autoApproved", value = "系统退款自动审批")
}) })
public ResponseDto sendOrderRefund( public ResponseDto sendOrderRefund(
@RequestParam(value = "orderId", required = true) @NotNull(message = "订单ID不能为空") String orderId, @RequestParam(value = "orderId", required = true) @NotNull(message = "订单ID不能为空") String orderId,
@RequestParam(value = "orderTicketEntitiesId", required = true) @NotNull(message = "票单ID不能为空") String orderTicketEntitiesId, @RequestParam(value = "orderTicketEntitiesId", required = true) @NotNull(message = "票单ID不能为空") String orderTicketEntitiesId,
@RequestParam(value = "reason", required = false) @NotNull(message = "退款原因不能为空") String reason, @RequestParam(value = "reasonId", required = false) String reasonId,
@RequestParam(value = "reasonDetail", required = false) String reasonDetail,
@RequestParam(value = "reason", required = false) String reason,
@RequestParam(value = "picList", required = false) String picList, @RequestParam(value = "picList", required = false) String picList,
@RequestParam(value = "ticketNum", required = false) String ticketNum, @RequestParam(value = "ticketNum", required = false) String ticketNum,
...@@ -86,11 +92,16 @@ public class KylinOrderTicketsRefundController { ...@@ -86,11 +92,16 @@ public class KylinOrderTicketsRefundController {
@RequestParam(value = "expressContacts", required = false) String expressContacts, @RequestParam(value = "expressContacts", required = false) String expressContacts,
@RequestParam(value = "expressPhone", required = false) String expressPhone, @RequestParam(value = "expressPhone", required = false) String expressPhone,
@RequestParam(value = "sendExpressAddress", required = false) String sendExpressAddress, @RequestParam(value = "sendExpressAddress", required = false) String sendExpressAddress,
@RequestParam(value = "appointmentTime", required = false) String appointmentTime @RequestParam(value = "appointmentTime", required = false) String appointmentTime,
@RequestParam(value = "skipCharges", required = false) Boolean skipCharges,
@RequestParam(value = "autoApproved", required = false) Boolean autoApproved
) { ) {
// 退款 boolean systemRefund = Boolean.TRUE.equals(skipCharges) && Boolean.TRUE.equals(autoApproved);
if (systemRefund) {
return kylinOrderTicketsRefundService.systemFullRefund(orderId, orderTicketEntitiesId, reason);
}
return kylinOrderTicketsRefundService.sendOrderRefund( return kylinOrderTicketsRefundService.sendOrderRefund(
orderId, orderTicketEntitiesId, reason, picList, ticketNum, orderId, orderTicketEntitiesId, reasonId, reasonDetail, reason, picList, ticketNum,
sendExpressType, expressNumber, expressContacts, expressPhone, sendExpressAddress, appointmentTime sendExpressType, expressNumber, expressContacts, expressPhone, sendExpressAddress, appointmentTime
); );
} }
......
package com.liquidnet.service.kylin.service.impl;
import com.liquidnet.service.kylin.dto.vo.groupbuy.KylinGroupBuyOrderVo;
import com.liquidnet.service.kylin.service.GroupBuyNotifyService;
import com.liquidnet.service.kylin.utils.GroupBuySmsSender;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class GroupBuyNotifyServiceImpl implements GroupBuyNotifyService {
@Autowired
private MongoTemplate mongoTemplate;
@Autowired
private GroupBuySmsSender groupBuySmsSender;
@Override
public void notifySuccess(String groupOrderId) {
KylinGroupBuyOrderVo group = load(groupOrderId);
if (group == null) {
return;
}
groupBuySmsSender.notifySuccess(group);
}
@Override
public void notifyFailed(String groupOrderId) {
KylinGroupBuyOrderVo group = load(groupOrderId);
if (group == null) {
return;
}
groupBuySmsSender.notifyFailed(group);
}
@Override
public void notifyRemind1h(String groupOrderId) {
KylinGroupBuyOrderVo group = load(groupOrderId);
if (group == null) {
return;
}
groupBuySmsSender.notifyRemind1h(group);
}
private KylinGroupBuyOrderVo load(String groupOrderId) {
if (groupOrderId == null) {
return null;
}
return mongoTemplate.findOne(
Query.query(Criteria.where("groupOrderId").is(groupOrderId)),
KylinGroupBuyOrderVo.class, KylinGroupBuyOrderVo.class.getSimpleName());
}
}
package com.liquidnet.service.kylin.service.impl;
import com.liquidnet.common.cache.redis.util.RedisUtil;
import com.liquidnet.service.base.ErrorMapping;
import com.liquidnet.service.base.ResponseDto;
import com.liquidnet.service.base.constant.RedisKeyExpireConst;
import com.liquidnet.service.kylin.constant.GroupBuyStatusConst;
import com.liquidnet.service.kylin.constant.KylinRedisConst;
import com.liquidnet.service.kylin.dto.vo.groupbuy.KylinGroupBuyActivityVo;
import com.liquidnet.service.kylin.dto.vo.groupbuy.KylinGroupBuyDiscountVo;
import com.liquidnet.service.kylin.dto.vo.groupbuy.KylinGroupBuyPriceVo;
import com.liquidnet.service.kylin.dto.vo.middle.KylinTicketTimesVo;
import com.liquidnet.service.kylin.dto.vo.middle.KylinTicketVo;
import com.liquidnet.service.kylin.dto.vo.mongo.KylinPerformanceVo;
import com.liquidnet.service.kylin.service.IKylinGroupBuyActivityService;
import com.liquidnet.service.kylin.utils.DataUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.util.List;
@Service
@Slf4j
public class KylinGroupBuyActivityServiceImpl implements IKylinGroupBuyActivityService {
@Autowired
private MongoTemplate mongoTemplate;
@Autowired
private DataUtils dataUtils;
@Autowired
private RedisUtil redisUtil;
@Override
public ResponseDto<List<KylinGroupBuyActivityVo>> activities(String performanceId) {
Query query = Query.query(Criteria.where("performanceId").is(performanceId)
.and("status").is(GroupBuyStatusConst.ActivityStatus.ACTIVE));
List<KylinGroupBuyActivityVo> list = mongoTemplate.find(query,
KylinGroupBuyActivityVo.class, KylinGroupBuyActivityVo.class.getSimpleName());
return ResponseDto.success(list);
}
@Override
public ResponseDto<KylinGroupBuyPriceVo> price(String activityId, String timeId, String ticketId) {
KylinGroupBuyActivityVo activity = getActiveActivity(activityId);
if (activity == null) {
return ResponseDto.failure("拼团活动不存在或未进行中");
}
KylinGroupBuyDiscountVo discount = findDiscount(activity, timeId, ticketId);
if (discount == null) {
return ResponseDto.failure("该票档不支持拼团");
}
BigDecimal originalPrice = getOriginalPrice(activity.getPerformanceId(), timeId, ticketId);
if (originalPrice == null) {
return ResponseDto.failure(ErrorMapping.get("20004"));
}
KylinGroupBuyPriceVo vo = new KylinGroupBuyPriceVo();
vo.setActivityId(activityId);
vo.setTimeId(timeId);
vo.setTicketId(ticketId);
vo.setOriginalPrice(originalPrice);
vo.setDiscountType(discount.getDiscountType());
vo.setDiscountValue(discount.getDiscountValue());
vo.setGroupBuyPrice(discount.getDiscountValue());
return ResponseDto.success(vo);
}
public KylinGroupBuyActivityVo getActiveActivity(String activityId) {
if (activityId == null) {
return null;
}
String redisKey = KylinRedisConst.GROUP_BUY_ACTIVITY.concat(activityId);
Object obj = redisUtil.get(redisKey);
if (obj != null) {
KylinGroupBuyActivityVo activity = (KylinGroupBuyActivityVo) obj;
return GroupBuyStatusConst.ActivityStatus.ACTIVE.equals(activity.getStatus()) ? activity : null;
}
Query query = Query.query(Criteria.where("activityId").is(activityId)
.and("status").is(GroupBuyStatusConst.ActivityStatus.ACTIVE));
KylinGroupBuyActivityVo activity = mongoTemplate.findOne(query, KylinGroupBuyActivityVo.class,
KylinGroupBuyActivityVo.class.getSimpleName());
if (activity != null) {
redisUtil.set(redisKey, activity, RedisKeyExpireConst.KYLIN_GROUP_BUY_EXPIRE);
}
return activity;
}
public KylinGroupBuyDiscountVo findDiscount(KylinGroupBuyActivityVo activity, String timeId, String ticketId) {
if (activity == null || activity.getDiscounts() == null) {
return null;
}
for (KylinGroupBuyDiscountVo discount : activity.getDiscounts()) {
if (timeId.equals(discount.getTimeId()) && ticketId.equals(discount.getTicketId())) {
return discount;
}
}
return null;
}
public BigDecimal getOriginalPrice(String performanceId, String timeId, String ticketId) {
KylinPerformanceVo performanceVo = dataUtils.getPerformanceVo(performanceId);
if (performanceVo == null || performanceVo.getTicketTimeList() == null) {
return null;
}
for (KylinTicketTimesVo timeVo : performanceVo.getTicketTimeList()) {
if (!timeId.equals(timeVo.getTimeId()) || timeVo.getTicketList() == null) {
continue;
}
for (KylinTicketVo ticketVo : timeVo.getTicketList()) {
if (ticketId.equals(ticketVo.getTicketsId())) {
return ticketVo.getPrice();
}
}
}
return null;
}
}
package com.liquidnet.service.kylin.service.impl;
import com.liquidnet.common.cache.redis.util.RedisUtil;
import com.liquidnet.service.kylin.constant.KylinRedisConst;
import com.liquidnet.service.kylin.dto.vo.OrderRefundReasonVo;
import com.liquidnet.service.kylin.entity.KylinOrderRefundReasons;
import com.liquidnet.service.kylin.mapper.KylinOrderRefundReasonsMapper;
import com.liquidnet.service.kylin.service.IKylinOrderRefundReasonService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
/**
* 退款原因(App/H5)
*/
@Slf4j
@Service
public class KylinOrderRefundReasonServiceImpl implements IKylinOrderRefundReasonService {
@Autowired
private RedisUtil redisUtil;
@Autowired
private KylinOrderRefundReasonsMapper reasonsMapper;
@Override
@SuppressWarnings("unchecked")
public List<OrderRefundReasonVo> listApplied() {
Object cached = redisUtil.get(KylinRedisConst.KYLIN_ORDER_REFUND_REASONS_APPLIED);
if (cached != null) {
return (List<OrderRefundReasonVo>) cached;
}
List<KylinOrderRefundReasons> list = reasonsMapper.listApplied();
List<OrderRefundReasonVo> voList = new ArrayList<>();
if (list != null) {
for (KylinOrderRefundReasons item : list) {
OrderRefundReasonVo vo = new OrderRefundReasonVo();
BeanUtils.copyProperties(item, vo);
voList.add(vo);
}
}
redisUtil.set(KylinRedisConst.KYLIN_ORDER_REFUND_REASONS_APPLIED, voList);
return voList;
}
@Override
public OrderRefundReasonVo getAppliedByReasonId(String reasonId) {
if (StringUtils.isBlank(reasonId)) {
return null;
}
List<OrderRefundReasonVo> list = listApplied();
if (list == null) {
return null;
}
for (OrderRefundReasonVo item : list) {
if (reasonId.equals(item.getReasonId())) {
return item;
}
}
return null;
}
}
...@@ -64,6 +64,8 @@ public class KylinOrderTicketsRefundServiceImpl { ...@@ -64,6 +64,8 @@ public class KylinOrderTicketsRefundServiceImpl {
private QueueUtils queueUtils; private QueueUtils queueUtils;
@Autowired @Autowired
private KylinRefundsStatusServiceImpl refundsStatusService; private KylinRefundsStatusServiceImpl refundsStatusService;
@Autowired
private KylinOrderRefundReasonServiceImpl refundReasonService;
@Value("${liquidnet.express.shunfeng.custid}") @Value("${liquidnet.express.shunfeng.custid}")
private String custid; private String custid;
...@@ -170,7 +172,22 @@ public class KylinOrderTicketsRefundServiceImpl { ...@@ -170,7 +172,22 @@ public class KylinOrderTicketsRefundServiceImpl {
} }
} }
public ResponseDto sendOrderRefund(String orderId, String orderTicketEntitiesId, String reason, String picList, String ticketNum, Integer sendExpressType, String expressNumber, String expressContacts, String expressPhone, String sendExpressAddress, String appointmentTime) { public ResponseDto sendOrderRefund(String orderId, String orderTicketEntitiesId, String reasonId, String reasonDetail, String reason, String picList, String ticketNum, Integer sendExpressType, String expressNumber, String expressContacts, String expressPhone, String sendExpressAddress, String appointmentTime) {
return sendOrderRefund(orderId, orderTicketEntitiesId, reasonId, reasonDetail, reason, picList, ticketNum,
sendExpressType, expressNumber, expressContacts, expressPhone, sendExpressAddress, appointmentTime,
false, false);
}
/**
* 系统全额退(拼团散团/超时):跳过票主登录态与退票窗口,免手续费并触发自动退款。
* 原因按自由文本兼容写入(无 reasonId)。
*/
public ResponseDto systemFullRefund(String orderId, String orderTicketEntitiesId, String reason) {
return sendOrderRefund(orderId, orderTicketEntitiesId, null, null, reason, "", "",
0, "", "", "", "", "", true, true);
}
public ResponseDto sendOrderRefund(String orderId, String orderTicketEntitiesId, String reasonId, String reasonDetail, String reason, String picList, String ticketNum, Integer sendExpressType, String expressNumber, String expressContacts, String expressPhone, String sendExpressAddress, String appointmentTime, boolean skipCharges, boolean autoApproved) {
boolean isLock = dataUtils.setRefundLock(orderTicketEntitiesId); boolean isLock = dataUtils.setRefundLock(orderTicketEntitiesId);
if (!isLock) { if (!isLock) {
return ResponseDto.failure("正在处理,请勿重复操作~"); return ResponseDto.failure("正在处理,请勿重复操作~");
...@@ -178,11 +195,39 @@ public class KylinOrderTicketsRefundServiceImpl { ...@@ -178,11 +195,39 @@ public class KylinOrderTicketsRefundServiceImpl {
KylinOrderTicketEntitiesVo orderTicketEntitiesVo = null; KylinOrderTicketEntitiesVo orderTicketEntitiesVo = null;
try { try {
String uid = CurrentUtil.getCurrentUid(); String resolvedReason;
String resolvedReasonId = null;
String resolvedReasonDetail = null;
if (StringUtils.isNotBlank(reasonId)) {
OrderRefundReasonUtils.ResolvedRefundReason resolved = OrderRefundReasonUtils.resolve(
refundReasonService.getAppliedByReasonId(reasonId), reasonDetail);
if (!resolved.isSuccess()) {
return ResponseDto.failure(resolved.getErrorMsg());
}
resolvedReasonId = resolved.getReasonId();
resolvedReasonDetail = resolved.getReasonDetail();
resolvedReason = resolved.getReasonSnapshot();
} else if (StringUtils.isNotBlank(reason)) {
// 兼容旧版自由文本 / 系统退款文案
resolvedReason = OrderRefundReasonUtils.truncate(reason, 200);
} else {
return ResponseDto.failure("请选择退款原因");
}
String uid = null;
try {
uid = CurrentUtil.getCurrentUid();
} catch (Exception ignore) {
// 系统退款可能无登录态
}
KylinOrderTicketVo orderTicketVo = dataUtils.getOrderTicketVo(orderId); KylinOrderTicketVo orderTicketVo = dataUtils.getOrderTicketVo(orderId);
List<KylinOrderRefundsVo> kylinOrderRefundsVoBaseList = dataUtils.getOrderRefundVoByOrderId(orderTicketVo.getOrderTicketsId()); List<KylinOrderRefundsVo> kylinOrderRefundsVoBaseList = dataUtils.getOrderRefundVoByOrderId(orderTicketVo.getOrderTicketsId());
if (!SuperAccountUtils.isSuperAccount(uid, orderTicketVo.getUserId())) { if (!autoApproved) {
return ResponseDto.failure(ErrorMapping.get("20003")); if (uid == null || !SuperAccountUtils.isSuperAccount(uid, orderTicketVo.getUserId())) {
return ResponseDto.failure(ErrorMapping.get("20003"));
}
} else if (uid == null) {
uid = "system";
} }
// if (uid.equals("809406") || uid.equals("773650")) { // if (uid.equals("809406") || uid.equals("773650")) {
// } else { // } else {
...@@ -209,18 +254,20 @@ public class KylinOrderTicketsRefundServiceImpl { ...@@ -209,18 +254,20 @@ public class KylinOrderTicketsRefundServiceImpl {
} }
} }
if (null != performanceVo.getIsCanRefund() && performanceVo.getIsCanRefund() == 1) { if (!autoApproved) {
LocalDateTime refundOpenDate = DateUtil.Formatter.yyyyMMddHHmmss.parse(performanceVo.getRefundOpenTime()); if (null != performanceVo.getIsCanRefund() && performanceVo.getIsCanRefund() == 1) {
LocalDateTime refundCloseDate = DateUtil.Formatter.yyyyMMddHHmmss.parse(performanceVo.getRefundCloseTime()); LocalDateTime refundOpenDate = DateUtil.Formatter.yyyyMMddHHmmss.parse(performanceVo.getRefundOpenTime());
if (LocalDateTime.now().isAfter(refundOpenDate) && LocalDateTime.now().isBefore(refundCloseDate) LocalDateTime refundCloseDate = DateUtil.Formatter.yyyyMMddHHmmss.parse(performanceVo.getRefundCloseTime());
//&& ticketVo.getCounts() == 1 if (LocalDateTime.now().isAfter(refundOpenDate) && LocalDateTime.now().isBefore(refundCloseDate)
) { //&& ticketVo.getCounts() == 1
) {
} else {
return ResponseDto.failure("暂不支持退款");
}
} else { } else {
return ResponseDto.failure("暂不支持退款"); return ResponseDto.failure("暂不支持退款");
} }
} else {
return ResponseDto.failure("暂不支持退款");
} }
List<KylinOrderTicketEntitiesVo> entitiesVos = orderTicketVo.getEntitiesVoList(); List<KylinOrderTicketEntitiesVo> entitiesVos = orderTicketVo.getEntitiesVoList();
...@@ -240,9 +287,12 @@ public class KylinOrderTicketsRefundServiceImpl { ...@@ -240,9 +287,12 @@ public class KylinOrderTicketsRefundServiceImpl {
BigDecimal singlePrice = orderTicketVo.getPriceActual().subtract(orderTicketVo.getPriceExpress()).divide(BigDecimal.valueOf(orderTicketVo.getNumber()), 2, BigDecimal.ROUND_HALF_UP); BigDecimal singlePrice = orderTicketVo.getPriceActual().subtract(orderTicketVo.getPriceExpress()).divide(BigDecimal.valueOf(orderTicketVo.getNumber()), 2, BigDecimal.ROUND_HALF_UP);
// 手续费处理 TODO 修改了手续费根据票单算 // 手续费处理 TODO 修改了手续费根据票单算
BigDecimal chargesRatio = getChargesRatio(orderTicketVo.getUseStart(), performanceVo.getIsRefundPoundage(dataUtils.getRefundPoundage(1, performanceVo.getPerformancesId()).size()), performanceVo.getPerformancesId()); BigDecimal priceCharges = BigDecimal.ZERO;
BigDecimal priceCharges = singlePrice.multiply(chargesRatio).setScale(2, BigDecimal.ROUND_HALF_UP); if (!skipCharges) {
refundSinglePrice = refundSinglePrice.subtract(priceCharges); BigDecimal chargesRatio = getChargesRatio(orderTicketVo.getUseStart(), performanceVo.getIsRefundPoundage(dataUtils.getRefundPoundage(1, performanceVo.getPerformancesId()).size()), performanceVo.getPerformancesId());
priceCharges = singlePrice.multiply(chargesRatio).setScale(2, BigDecimal.ROUND_HALF_UP);
refundSinglePrice = refundSinglePrice.subtract(priceCharges);
}
if (refundSinglePrice.compareTo(BigDecimal.ZERO) <= 0) { if (refundSinglePrice.compareTo(BigDecimal.ZERO) <= 0) {
return ResponseDto.failure("申请金额不得小于0哦~"); return ResponseDto.failure("申请金额不得小于0哦~");
} }
...@@ -265,16 +315,20 @@ public class KylinOrderTicketsRefundServiceImpl { ...@@ -265,16 +315,20 @@ public class KylinOrderTicketsRefundServiceImpl {
} }
} }
Map token = CurrentUtil.getTokenClaims(); String username = "system";
String username = StringUtils.defaultString(((String) token.get("nickname")), ""); if (!autoApproved) {
String orderRefundId = refundsStatusService.userOrderTicketRefunding(orderTicketVo, refundSinglePrice, priceExpress, priceCharges, orderTicketEntitiesId, reason, picList, ticketNum, uid, username, kylinOrderRefundsVoBaseList.size(), sendExpressType); Map token = CurrentUtil.getTokenClaims();
username = StringUtils.defaultString(((String) token.get("nickname")), "");
}
Integer expressTypeParam = sendExpressType == null ? 0 : sendExpressType;
String orderRefundId = refundsStatusService.userOrderTicketRefunding(orderTicketVo, refundSinglePrice, priceExpress, priceCharges, orderTicketEntitiesId, resolvedReason, resolvedReasonId, resolvedReasonDetail, picList, ticketNum, uid, username, kylinOrderRefundsVoBaseList.size(), expressTypeParam);
if (!orderRefundId.isEmpty()) { if (!orderRefundId.isEmpty()) {
// 退款申请成功 异步去快递下单 // 退款申请成功 异步去快递下单
if (orderTicketVo.getGetTicketType().equals("express") && sendExpressType > 0) {// 快递票 if (orderTicketVo.getGetTicketType().equals("express") && expressTypeParam > 0) {// 快递票
expressPlace(sendExpressType, expressNumber, expressContacts, expressPhone, sendExpressAddress, appointmentTime, orderTicketVo, performanceVo, orderRefundId); expressPlace(expressTypeParam, expressNumber, expressContacts, expressPhone, sendExpressAddress, appointmentTime, orderTicketVo, performanceVo, orderRefundId);
} }
//电子票自动退款 //电子票自动退款;系统退款一律触发自动审批
if (orderTicketVo.getGetTicketType().equals("electronic")){ if (autoApproved || orderTicketVo.getGetTicketType().equals("electronic")){
try { try {
MultiValueMap<String, String> params = new LinkedMultiValueMap(); MultiValueMap<String, String> params = new LinkedMultiValueMap();
params.add("orderRefundsId", orderRefundId); params.add("orderRefundsId", orderRefundId);
......
...@@ -56,6 +56,8 @@ public class KylinRefundsStatusServiceImpl { ...@@ -56,6 +56,8 @@ public class KylinRefundsStatusServiceImpl {
BigDecimal priceCharges, BigDecimal priceCharges,
String orderEntitiesId, String orderEntitiesId,
String reason, String reason,
String reasonId,
String reasonDetail,
String picList, String picList,
String ticketNum, String ticketNum,
String uid, String uid,
...@@ -120,6 +122,8 @@ public class KylinRefundsStatusServiceImpl { ...@@ -120,6 +122,8 @@ public class KylinRefundsStatusServiceImpl {
kylinOrderRefunds.setApplicantName(username); kylinOrderRefunds.setApplicantName(username);
kylinOrderRefunds.setApplicantAt(time); kylinOrderRefunds.setApplicantAt(time);
kylinOrderRefunds.setReason(reason); kylinOrderRefunds.setReason(reason);
kylinOrderRefunds.setReasonId(reasonId);
kylinOrderRefunds.setReasonDetail(reasonDetail);
kylinOrderRefunds.setCreatedAt(time); kylinOrderRefunds.setCreatedAt(time);
KylinOrderRefundsVo orderRefundsVo = KylinOrderRefundsVo.getNew(); KylinOrderRefundsVo orderRefundsVo = KylinOrderRefundsVo.getNew();
...@@ -182,7 +186,7 @@ public class KylinRefundsStatusServiceImpl { ...@@ -182,7 +186,7 @@ public class KylinRefundsStatusServiceImpl {
kylinOrderRefunds.getOrderRefundsId(), kylinOrderRefunds.getOrderTicketsId(), kylinOrderRefunds.getOrderRefundCode(), kylinOrderRefunds.getOrderRefundsId(), kylinOrderRefunds.getOrderTicketsId(), kylinOrderRefunds.getOrderRefundCode(),
kylinOrderRefunds.getPrice(), kylinOrderRefunds.getPriceExpress(), kylinOrderRefunds.getPriceCharges(), ticketNum, kylinOrderRefunds.getStatus(), kylinOrderRefunds.getPrice(), kylinOrderRefunds.getPriceExpress(), kylinOrderRefunds.getPriceCharges(), ticketNum, kylinOrderRefunds.getStatus(),
kylinOrderRefunds.getType(), kylinOrderRefunds.getApplicantId(), kylinOrderRefunds.getApplicantName(), kylinOrderRefunds.getType(), kylinOrderRefunds.getApplicantId(), kylinOrderRefunds.getApplicantName(),
kylinOrderRefunds.getApplicantAt(), kylinOrderRefunds.getReason(), kylinOrderRefunds.getCreatedAt() kylinOrderRefunds.getApplicantAt(), kylinOrderRefunds.getReason(), kylinOrderRefunds.getReasonId(), kylinOrderRefunds.getReasonDetail(), kylinOrderRefunds.getCreatedAt()
}); });
sqlsDataD.add(new Object[]{ sqlsDataD.add(new Object[]{
kylinOrderRefundEntities.getOrderRefundsEntitiesId(), kylinOrderRefundEntities.getOrderRefundsId(), kylinOrderRefundEntities.getRefundPrice(), kylinOrderRefundEntities.getOrderRefundsEntitiesId(), kylinOrderRefundEntities.getOrderRefundsId(), kylinOrderRefundEntities.getRefundPrice(),
...@@ -332,9 +336,9 @@ public class KylinRefundsStatusServiceImpl { ...@@ -332,9 +336,9 @@ public class KylinRefundsStatusServiceImpl {
}); });
sqlsDataC.add(new Object[]{ sqlsDataC.add(new Object[]{
kylinOrderRefunds.getOrderRefundsId(), kylinOrderRefunds.getOrderTicketsId(), kylinOrderRefunds.getOrderRefundCode(), kylinOrderRefunds.getOrderRefundsId(), kylinOrderRefunds.getOrderTicketsId(), kylinOrderRefunds.getOrderRefundCode(),
kylinOrderRefunds.getPrice(), kylinOrderRefunds.getPriceExpress(), 0, kylinOrderRefunds.getStatus(), kylinOrderRefunds.getPrice(), kylinOrderRefunds.getPriceExpress(), 0, null, kylinOrderRefunds.getStatus(),
kylinOrderRefunds.getType(), kylinOrderRefunds.getApplicantId(), kylinOrderRefunds.getApplicantName(), kylinOrderRefunds.getType(), kylinOrderRefunds.getApplicantId(), kylinOrderRefunds.getApplicantName(),
kylinOrderRefunds.getApplicantAt(), kylinOrderRefunds.getReason(), kylinOrderRefunds.getCreatedAt() kylinOrderRefunds.getApplicantAt(), kylinOrderRefunds.getReason(), null, null, kylinOrderRefunds.getCreatedAt()
}); });
sqlsDataD.add(new Object[]{ sqlsDataD.add(new Object[]{
kylinOrderRefundEntities.getOrderRefundsEntitiesId(), kylinOrderRefundEntities.getOrderRefundsId(), kylinOrderRefundEntities.getRefundPrice(), kylinOrderRefundEntities.getOrderRefundsEntitiesId(), kylinOrderRefundEntities.getOrderRefundsId(), kylinOrderRefundEntities.getRefundPrice(),
......
...@@ -26,6 +26,11 @@ ...@@ -26,6 +26,11 @@
<groupId>com.liquidnet</groupId> <groupId>com.liquidnet</groupId>
<artifactId>liquidnet-common-web</artifactId> <artifactId>liquidnet-common-web</artifactId>
</dependency> </dependency>
<dependency>
<groupId>com.liquidnet</groupId>
<artifactId>liquidnet-common-sms</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies> </dependencies>
</project> </project>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment