会员积分代码提交

This commit is contained in:
Tankaikai 2025-02-25 19:02:51 +08:00
parent 978f7f1bdb
commit eb748897cf
43 changed files with 2480 additions and 23 deletions

View File

@ -0,0 +1,39 @@
package com.czg.controller.admin;
import com.czg.account.entity.MemberPoints;
import com.czg.account.param.MemberPointsParam;
import com.czg.account.service.MemberPointsService;
import com.czg.log.annotation.OperationLog;
import com.czg.resp.CzgResult;
import com.mybatisflex.core.paginate.Page;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 会员积分
*
* @author Tankaikai tankaikai@aliyun.com
* @since 1.0 2025-02-25
*/
@AllArgsConstructor
@RestController
@RequestMapping("/admin/points/member-points")
public class MemberPointsController {
private final MemberPointsService memberPointsService;
/**
* 分页
*/
@GetMapping("page")
@OperationLog("会员积分-分页")
//@SaAdminCheckPermission("memberPoints:page")
public CzgResult<Page<MemberPoints>> getMemberPointsPage(MemberPointsParam param) {
Page<MemberPoints> page = memberPointsService.getMemberPointsPage(param);
return CzgResult.success(page);
}
}

View File

@ -0,0 +1,37 @@
package com.czg.controller.admin;
import com.czg.account.dto.points.MemberPointsLogDTO;
import com.czg.account.service.MemberPointsLogService;
import com.czg.log.annotation.OperationLog;
import com.czg.resp.CzgResult;
import com.mybatisflex.core.paginate.Page;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 会员积分变动记录
*
* @author Tankaikai tankaikai@aliyun.com
* @since 1.0 2025-02-25
*/
@AllArgsConstructor
@RestController
@RequestMapping("/admin/points/member-points-log")
public class MemberPointsLogController {
private final MemberPointsLogService memberPointsLogService;
/**
* 分页
*/
@GetMapping("page")
@OperationLog("会员积分变动记录-分页")
//@SaAdminCheckPermission("memberPointsLog:page")
public CzgResult<Page<MemberPointsLogDTO>> getMemberPointsLogPage(MemberPointsLogDTO param) {
Page<MemberPointsLogDTO> page = memberPointsLogService.getMemberPointsLogPage(param);
return CzgResult.success(page);
}
}

View File

@ -0,0 +1,59 @@
package com.czg.controller.admin;
import cn.hutool.core.bean.BeanUtil;
import com.czg.account.dto.points.PointsBasicSettingDTO;
import com.czg.account.entity.PointsBasicSetting;
import com.czg.account.service.PointsBasicSettingService;
import com.czg.log.annotation.OperationLog;
import com.czg.resp.CzgResult;
import com.czg.sa.StpKit;
import com.czg.validator.ValidatorUtil;
import com.czg.validator.group.DefaultGroup;
import com.czg.validator.group.InsertGroup;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;
/**
* 积分基本设置
*
* @author Tankaikai tankaikai@aliyun.com
* @since 1.0 2025-02-25
*/
@AllArgsConstructor
@RestController
@RequestMapping("/admin/points/basic-setting")
public class PointsBasicSettingController {
private final PointsBasicSettingService pointsBasicSettingService;
/**
* 详情
*/
@GetMapping()
@OperationLog("积分基本设置-详情")
//@SaAdminCheckPermission("basicSetting:info")
public CzgResult<PointsBasicSettingDTO> getPointsBasicSettingByShopId() {
Long shopId = StpKit.USER.getShopId(0L);
PointsBasicSetting entity = pointsBasicSettingService.getById(shopId);
PointsBasicSettingDTO data = BeanUtil.copyProperties(entity, PointsBasicSettingDTO.class);
return CzgResult.success(data);
}
/**
* 保存
*/
@PostMapping
@OperationLog("积分基本设置-保存")
//@SaAdminCheckPermission("basicSetting:save")
public CzgResult<Void> savePointsBasicSetting(@RequestBody PointsBasicSettingDTO dto) {
Long shopId = StpKit.USER.getShopId(0L);
dto.setShopId(shopId);
ValidatorUtil.validateEntity(dto, InsertGroup.class, DefaultGroup.class);
PointsBasicSetting entity = BeanUtil.copyProperties(dto, PointsBasicSetting.class);
pointsBasicSettingService.saveOrUpdate(entity);
return CzgResult.success();
}
}

View File

@ -0,0 +1,98 @@
package com.czg.controller.admin;
import com.czg.account.dto.points.PointsExchangeRecordDTO;
import com.czg.account.param.PointsExchangeCfParam;
import com.czg.account.param.PointsExchangeRecordParam;
import com.czg.account.service.PointsExchangeRecordService;
import com.czg.account.vo.PointsExchangeSummaryVo;
import com.czg.log.annotation.OperationLog;
import com.czg.resp.CzgResult;
import com.mybatisflex.core.paginate.Page;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;
/**
* 积分兑换
*
* @author Tankaikai tankaikai@aliyun.com
* @since 1.0 2025-02-25
*/
@AllArgsConstructor
@RestController
@RequestMapping("/admin/points/exchange-record")
public class PointsExchangeRecordController {
private final PointsExchangeRecordService pointsExchangeRecordService;
/**
* 分页
*/
@GetMapping("page")
@OperationLog("积分兑换-分页")
//@SaAdminCheckPermission("pointsExchangeRecord:page")
public CzgResult<Page<PointsExchangeRecordDTO>> getPointsExchangeRecordPage(PointsExchangeRecordParam param) {
Page<PointsExchangeRecordDTO> page = pointsExchangeRecordService.getPointsExchangeRecordPage(param);
return CzgResult.success(page);
}
/**
* 详情
*
* @param id 兑换id
*/
@GetMapping("{id}")
@OperationLog("积分兑换-详情")
//@SaAdminCheckPermission("pointsExchangeRecord:info")
public CzgResult<PointsExchangeRecordDTO> getPointsExchangeRecordById(@PathVariable("id") Long id) {
PointsExchangeRecordDTO data = pointsExchangeRecordService.getPointsExchangeRecordById(id);
return CzgResult.success(data);
}
/**
* 核销
*
* @param couponCode 兑换码
*/
@PostMapping("checkout")
@OperationLog("积分兑换-核销")
//@SaAdminCheckPermission("pointsExchangeRecord:checkout")
public CzgResult<Void> checkout(@RequestParam String couponCode) {
pointsExchangeRecordService.checkout(couponCode);
return CzgResult.success();
}
/**
* 取消
*/
@PostMapping("cancel")
@OperationLog("积分兑换-取消")
//@SaAdminCheckPermission("pointsExchangeRecord:cancel")
public CzgResult<Void> cancel(@RequestBody PointsExchangeCfParam param) {
pointsExchangeRecordService.cancel(param);
return CzgResult.success();
}
/**
* 退单
*/
@PostMapping("refund")
@OperationLog("积分兑换-退单")
//@SaAdminCheckPermission("pointsExchangeRecord:refund")
public CzgResult<Void> refund(@RequestBody PointsExchangeCfParam param) {
pointsExchangeRecordService.refund(param);
return CzgResult.success();
}
/**
* 统计
*/
@GetMapping("total")
@OperationLog("积分兑换-统计")
//@SaAdminCheckPermission("pointsExchangeRecord:total")
public CzgResult<PointsExchangeSummaryVo> total(PointsExchangeRecordParam param) {
PointsExchangeSummaryVo data = pointsExchangeRecordService.total(param);
return CzgResult.success(data);
}
}

View File

@ -0,0 +1,100 @@
package com.czg.controller.admin;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import com.czg.account.dto.points.PointsGoodsSettingDTO;
import com.czg.account.entity.PointsGoodsSetting;
import com.czg.account.service.PointsGoodsSettingService;
import com.czg.enums.DeleteEnum;
import com.czg.log.annotation.OperationLog;
import com.czg.resp.CzgResult;
import com.czg.sa.StpKit;
import com.czg.validator.ValidatorUtil;
import com.czg.validator.group.DefaultGroup;
import com.czg.validator.group.InsertGroup;
import com.czg.validator.group.UpdateGroup;
import com.mybatisflex.core.paginate.Page;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;
/**
* 积分商品设置
*
* @author Tankaikai tankaikai@aliyun.com
* @since 1.0 2025-02-25
*/
@AllArgsConstructor
@RestController
@RequestMapping("/admin/points/goods-setting")
public class PointsGoodsSettingController {
private final PointsGoodsSettingService pointsGoodsSettingService;
/**
* 分页
*/
@GetMapping("page")
@OperationLog("积分商品设置-分页")
//@SaAdminCheckPermission("pointsGoodsSetting:page")
public CzgResult<Page<PointsGoodsSettingDTO>> getPointsGoodsSettingPage(PointsGoodsSettingDTO param) {
Page<PointsGoodsSettingDTO> data = pointsGoodsSettingService.getPointsGoodsSettingPage(param);
return CzgResult.success(data);
}
/**
* 详情
*/
@GetMapping("{id}")
@OperationLog("积分商品设置-详情")
//@SaAdminCheckPermission("pointsGoodsSetting:info")
public CzgResult<PointsGoodsSettingDTO> getPointsGoodsSettingById(@PathVariable("id") Long id) {
PointsGoodsSetting entity = pointsGoodsSettingService.getById(id);
PointsGoodsSettingDTO data = BeanUtil.copyProperties(entity, PointsGoodsSettingDTO.class);
return CzgResult.success(data);
}
/**
* 新增
*/
@PostMapping
@OperationLog("积分商品设置-新增")
//@SaAdminCheckPermission("pointsGoodsSetting:add")
public CzgResult<Boolean> addPointsGoodsSetting(@RequestBody PointsGoodsSettingDTO dto) {
Long shopId = StpKit.USER.getShopId(0L);
dto.setShopId(shopId);
ValidatorUtil.validateEntity(dto, InsertGroup.class, DefaultGroup.class);
PointsGoodsSetting entity = BeanUtil.copyProperties(dto, PointsGoodsSetting.class);
boolean ret = pointsGoodsSettingService.save(entity);
return CzgResult.success(ret);
}
/**
* 修改
*/
@PutMapping
@OperationLog("积分商品设置-修改")
//@SaAdminCheckPermission("pointsGoodsSetting:update")
public CzgResult<Boolean> updatePointsGoodsSetting(@RequestBody PointsGoodsSettingDTO dto) {
Long shopId = StpKit.USER.getShopId(0L);
dto.setShopId(shopId);
ValidatorUtil.validateEntity(dto, UpdateGroup.class, DefaultGroup.class);
PointsGoodsSetting entity = new PointsGoodsSetting();
BeanUtil.copyProperties(dto, entity, CopyOptions.create().setIgnoreNullValue(false));
boolean ret = pointsGoodsSettingService.updateById(entity);
return CzgResult.success(ret);
}
/**
* 删除
*/
@DeleteMapping("{id}")
@OperationLog("积分商品设置-删除")
//@SaAdminCheckPermission("pointsGoodsSetting:delete")
public CzgResult<Void> deletePointsGoodsSetting(@PathVariable("id") Long id) {
PointsGoodsSetting entity = pointsGoodsSettingService.getById(id);
entity.setDelFlag(DeleteEnum.DELETED.value());
pointsGoodsSettingService.updateById(entity);
return CzgResult.success();
}
}

View File

@ -0,0 +1,96 @@
package com.czg.controller.user;
import com.czg.account.dto.points.OrderDeductionPointsDTO;
import com.czg.account.entity.MemberPoints;
import com.czg.account.param.ConsumeAwardPointsParam;
import com.czg.account.param.PayedDeductPointsParam;
import com.czg.account.service.MemberPointsService;
import com.czg.log.annotation.OperationLog;
import com.czg.resp.CzgResult;
import com.czg.sa.StpKit;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.math.BigDecimal;
/**
* 会员积分
*
* @author Tankaikai tankaikai@aliyun.com
* @since 1.0 2025-02-25
*/
@AllArgsConstructor
@RestController
@RequestMapping("/user/points/member-points")
public class UMemberPointsController {
private final MemberPointsService memberPointsService;
/**
* 获取会员积分等信息
*/
@GetMapping("my-points")
@OperationLog("会员积分-获取会员积分等信息")
public CzgResult<MemberPoints> getMemberPoints() {
long userId = StpKit.USER.getLoginIdAsLong();
MemberPoints data = memberPointsService.getMemberPoints(userId);
return CzgResult.success(data);
}
/**
* 获取订单可用积分及抵扣金额
*
* @param userId 用户id
* @param orderAmount 订单金额
*/
@GetMapping("calc-usable-points")
@OperationLog("获取订单可用积分及抵扣金额")
public CzgResult<OrderDeductionPointsDTO> getMemberUsablePoints(@RequestParam Long userId, @RequestParam BigDecimal orderAmount) {
OrderDeductionPointsDTO usablePoints = memberPointsService.getMemberUsablePoints(userId, orderAmount);
return CzgResult.success(usablePoints);
}
/**
* 根据抵扣金额计算所需积分
*
* @param userId 用户id
* @param orderAmount 订单金额
* @param deductionAmount 抵扣金额
*/
@GetMapping("calc-used-points")
@OperationLog("根据抵扣金额计算所需积分")
public CzgResult<Integer> calcUsedPoints(@RequestParam Long userId, @RequestParam BigDecimal orderAmount, @RequestParam BigDecimal deductionAmount) {
int points = memberPointsService.calcUsedPoints(userId, orderAmount, deductionAmount);
return CzgResult.success(points);
}
/**
* 根据积分计算可抵扣金额
*/
@GetMapping("calc-deduction-amount")
@OperationLog("根据积分计算可抵扣金额")
public CzgResult<BigDecimal> calcDeductionAmount(@RequestParam Long memberId, @RequestParam BigDecimal orderAmount, @RequestParam Integer points) {
BigDecimal deductionAmount = memberPointsService.calcDeductionAmount(memberId, orderAmount, points);
return CzgResult.success(deductionAmount);
}
/**
* 支付完成扣减积分支付成功回调中使用
*/
@PostMapping("payed-deduct-points")
@OperationLog("支付完成扣减积分(支付成功回调中使用)")
public CzgResult<Boolean> deductPoints(@RequestBody PayedDeductPointsParam param) {
boolean ret = memberPointsService.deductPoints(param.getUserId(), param.getPoints(), param.getContent(), param.getOrderId());
return CzgResult.success(ret);
}
/**
* 消费赠送积分支付成功回调中使用
*/
@PostMapping("consume-award-points")
@OperationLog("消费赠送积分(支付成功回调中使用)")
public CzgResult<Void> consumeAwardPoints(@RequestBody ConsumeAwardPointsParam param) {
memberPointsService.consumeAwardPoints(param.getUserId(), param.getOrderId());
return CzgResult.success();
}
}

View File

@ -30,7 +30,7 @@ public class ShopProdSpecController {
@GetMapping("list")
@OperationLog("商品规格-列表")
//@SaAdminCheckPermission("shopProductSpec:list")
public CzgResult<List<ShopProdSpecDTO>> list(ShopProdSpecDTO param) {
public CzgResult<List<ShopProdSpecDTO>> getShopProdSpecList(ShopProdSpecDTO param) {
List<ShopProdSpecDTO> data = shopProdSpecService.getShopProdSpecList(param);
return CzgResult.success(data);
}
@ -38,7 +38,7 @@ public class ShopProdSpecController {
@GetMapping("{id}")
@OperationLog("商品规格-详情")
//@SaAdminCheckPermission("shopProductSpec:info")
public CzgResult<ShopProdSpecDTO> get(@PathVariable("id") Long id) {
public CzgResult<ShopProdSpecDTO> getShopProdSpecById(@PathVariable("id") Long id) {
AssertUtil.isNull(id, "{}不能为空", "id");
ShopProdSpecDTO data = shopProdSpecService.getShopProdSpecById(id);
return CzgResult.success(data);
@ -47,7 +47,7 @@ public class ShopProdSpecController {
@PostMapping
@OperationLog("商品规格-新增")
//@SaAdminCheckPermission("shopProductSpec:add")
public CzgResult<Void> save(@RequestBody ShopProdSpecDTO dto) {
public CzgResult<Void> addShopProdSpec(@RequestBody ShopProdSpecDTO dto) {
//效验数据
ValidatorUtil.validateEntity(dto, InsertGroup.class, DefaultGroup.class);
@ -59,7 +59,7 @@ public class ShopProdSpecController {
@PutMapping
@OperationLog("商品规格-修改")
//@SaAdminCheckPermission("shopProductSpec:update")
public CzgResult<Void> update(@RequestBody ShopProdSpecDTO dto) {
public CzgResult<Void> updateShopProdSpec(@RequestBody ShopProdSpecDTO dto) {
//效验数据
ValidatorUtil.validateEntity(dto, UpdateGroup.class, DefaultGroup.class);
@ -71,7 +71,7 @@ public class ShopProdSpecController {
@DeleteMapping("{id}")
@OperationLog("商品规格-删除")
//@SaAdminCheckPermission("shopProductSpec:delete")
public CzgResult<Void> delete(@PathVariable("id") Long id) {
public CzgResult<Void> deleteShopProdSpec(@PathVariable("id") Long id) {
//效验数据
AssertUtil.isNull(id, "{}不能为空", "id");
shopProdSpecService.deleteShopProdSpec(id);
@ -81,7 +81,7 @@ public class ShopProdSpecController {
@PostMapping("disable/{id}")
@OperationLog("商品规格-禁用")
//@SaAdminCheckPermission("shopProductSpec:able")
public CzgResult<Void> disable(@PathVariable("id") Long id) {
public CzgResult<Void> disableShopProdSpec(@PathVariable("id") Long id) {
//效验数据
AssertUtil.isNull(id, "{}不能为空", "id");
shopProdSpecService.disableShopProdSpec(id);
@ -91,7 +91,7 @@ public class ShopProdSpecController {
@PostMapping("enable/{id}")
@OperationLog("商品规格-启用")
//@SaAdminCheckPermission("shopProductSpec:able")
public CzgResult<Void> enable(@PathVariable("id") Long id) {
public CzgResult<Void> enableShopProdSpec(@PathVariable("id") Long id) {
//效验数据
AssertUtil.isNull(id, "{}不能为空", "id");
shopProdSpecService.enableShopProdSpec(id);
@ -101,7 +101,7 @@ public class ShopProdSpecController {
@PostMapping("quickAdd")
@OperationLog("商品规格-快捷添加")
//@SaAdminCheckPermission("shopProductSpec:add")
public CzgResult<Void> quickAdd(@RequestBody ShopProdSpecDTO dto) {
public CzgResult<Void> quickAddShopProdSpec(@RequestBody ShopProdSpecDTO dto) {
shopProdSpecService.quickAddShopProdSpec(dto);
return CzgResult.success();
}
@ -109,7 +109,7 @@ public class ShopProdSpecController {
@GetMapping("/tree/{id}")
@OperationLog("商品规格-快捷编辑详情")
//@SaAdminCheckPermission("shopProductSpec:info")
public CzgResult<ShopProdSpecDTO> getTree(@PathVariable("id") Long id) {
public CzgResult<ShopProdSpecDTO> getShopProdSpecTreeById(@PathVariable("id") Long id) {
AssertUtil.isNull(id, "{}不能为空", "id");
ShopProdSpecDTO data = shopProdSpecService.getShopProdSpecTreeById(id);
return CzgResult.success(data);
@ -118,7 +118,7 @@ public class ShopProdSpecController {
@PutMapping("quickUpdate")
@OperationLog("商品规格-快捷更新")
//@SaAdminCheckPermission("shopProductSpec:update")
public CzgResult<Void> quickUpdate(@RequestBody ShopProdSpecDTO dto) {
public CzgResult<Void> quickUpdateShopProdSpec(@RequestBody ShopProdSpecDTO dto) {
shopProdSpecService.quickUpdateShopProdSpec(dto);
return CzgResult.success();
}

View File

@ -0,0 +1,75 @@
package com.czg.account.dto.points;
import com.alibaba.fastjson2.annotation.JSONField;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 会员积分变动记录
*
* @author Tankaikai tankaikai@aliyun.com
* @since 1.0 2025-02-25
*/
@Data
public class MemberPointsLogDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* id
*/
private Long id;
/**
* 店铺id
*/
private Long shopId;
/**
* 用户id
*/
private Long userId;
/**
* 摘要信息兑换某个商品/消费多少钱/充值多少钱/新会员赠送积分等
*/
private String content;
/**
* 订单编号
*/
private String orderNo;
/**
* 浮动类型 add-累加 subtract-扣减
*/
private String floatType;
/**
* 浮动积分(非0正负数)
*/
private Integer floatPoints;
/**
* 创建时间
*/
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
/**
* 用户昵称
*/
private String nickName;
/**
* 用户头像
*/
private String headImg;
/**
* 用户手机号码
*/
private String phone;
/**
* 查询开始日期 yyyy-MM-dd
*/
private String beginDate;
/**
* 查询结束日期 yyyy-MM-dd
*/
private String endDate;
}

View File

@ -0,0 +1,71 @@
package com.czg.account.dto.points;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
/**
* 获取会员可用积分
*
* @author tankaikai
* @since 2024-10-26 11:40
*/
@Data
public class OrderDeductionPointsDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 本单最多可抵扣多少积分
*/
private Integer maxUsablePoints;
/**
* 根据策略计算出的最少可以抵扣的金额
*/
private BigDecimal minDeductionAmount;
/**
* 根据策略计算出的最多可以抵扣的金额
*/
private BigDecimal maxDeductionAmount;
/**
* 下单实付抵扣门槛(实付金额不低于这个值)
*/
private BigDecimal minPaymentAmount;
/**
* 下单积分抵扣门槛(每次使用不低于这个值)
*/
private Integer minDeductionPoints;
/**
* 会员账户积分
*/
private Integer accountPoints;
/**
* 订单金额 (扣除各类折扣)
*/
private BigDecimal orderAmount;
/**
* 使用的积分数量
*/
private Integer usedPoints;
/**
* 实际抵扣的金额
*/
private Integer deductionAmount;
/**
* 下单抵扣积分比例 1元=?积分
*/
private Integer equivalentPoints;
/**
* 不可抵扣原因
*/
private String unusableReason;
/**
* 是否可用 1-可用 0-不可用
*/
private Boolean usable;
}

View File

@ -0,0 +1,93 @@
package com.czg.account.dto.points;
import com.alibaba.fastjson2.annotation.JSONField;
import com.czg.validator.group.DefaultGroup;
import com.czg.validator.group.InsertGroup;
import jakarta.validation.constraints.DecimalMax;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 积分基本设置
*
* @author Tankaikai tankaikai@aliyun.com
* @since 1.0 2025-02-25
*/
@Data
public class PointsBasicSettingDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 店铺id
*/
@NotNull(message = "店铺id不能为空", groups = InsertGroup.class)
private Long shopId;
/**
* 开启消费赠送积分 1-开启 0-关闭
*/
@NotNull(message = "开启消费赠送积分不能为空", groups = DefaultGroup.class)
private Integer enableRewards;
/**
* 赠积分适用群体 all-全部 vip-仅会员
*/
@NotNull(message = "赠积分适用群体不能为空", groups = DefaultGroup.class)
private String rewardsGroup;
/**
* 每消费xx元赠送1积分
*/
@NotNull(message = "每消费xx元赠送1积分不能为空", groups = DefaultGroup.class)
private BigDecimal consumeAmount;
/**
* 开启下单积分抵扣 1-开启 0-关闭
*/
@NotNull(message = "开启下单积分抵扣不能为空", groups = DefaultGroup.class)
private Integer enableDeduction;
/**
* 抵扣适用群体 all-全部 vip-仅会员
*/
@NotBlank(message = "抵扣适用群体不能为空", groups = DefaultGroup.class)
private String deductionGroup;
/**
* 下单实付抵扣门槛
*/
@NotNull(message = "下单实付抵扣门槛不能为空", groups = DefaultGroup.class)
@DecimalMin(value = "0", inclusive = false, message = "下单实付抵扣门槛不能小于0")
private BigDecimal minPaymentAmount;
/**
* 下单最高抵扣比例
*/
@NotNull(message = "下单最高抵扣比例不能为空", groups = DefaultGroup.class)
@DecimalMin(value = "0", inclusive = false, message = "下单最高抵扣比例不能小于等于0")
@DecimalMax(value = "100", message = "下单最高抵扣比例不能大于100")
private BigDecimal maxDeductionRatio;
/**
* 下单抵扣积分比例 1元=?积分
*/
@NotNull(message = "下单抵扣积分比例不能为空", groups = DefaultGroup.class)
private Integer equivalentPoints;
/**
* 开启积分商城
*/
@NotNull(message = "开启积分商城不能为空", groups = DefaultGroup.class)
private Integer enablePointsMall;
/**
* 浏览模式 list-列表 grid-宫格
*/
@NotBlank(message = "浏览模式不能为空", groups = DefaultGroup.class)
private String browseMode;
/**
* 创建时间
*/
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
}

View File

@ -0,0 +1,124 @@
package com.czg.account.dto.points;
import com.alibaba.fastjson2.annotation.JSONField;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 积分兑换记录
*
* @author Tankaikai tankaikai@aliyun.com
* @since 1.0 2025-02-25
*/
@Data
public class PointsExchangeRecordDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* id
*/
private Long id;
/**
* 订单编号
*/
private String orderNo;
/**
* 店铺id
*/
private Long shopId;
/**
* 积分商品id
*/
private Long pointsGoodsId;
/**
* 积分商品名称
*/
private String pointsGoodsName;
/**
* 商品图片URL
*/
private String goodsImageUrl;
/**
* 领取方式 self-自取 post-邮寄
*/
private String pickupMethod;
/**
* 用户id
*/
private Long userId;
/**
* 用户昵称
*/
private String nickName;
/**
* 手机号码
*/
private String mobile;
/**
* 会员头像
*/
private String avatarUrl;
/**
* 消耗积分
*/
private Integer spendPoints;
/**
* 额外支付
*/
private BigDecimal extraPaymentAmount;
/**
* 兑换券券码
*/
private String couponCode;
/**
* 支付平台订单号
*/
private String payOrderId;
/**
* 渠道订单号(微信/支付宝订单号)
*/
private String channelTradeNo;
/**
* 支付方式 积分支付/积分+微信/积分+支付宝
*/
private String payMethod;
/**
* 支付类型 POINTS-积分 WECHAT-微信 ALIPAY-支付宝 UNIONPAY-银联云闪付
*/
private String payType;
/**
* 状态 unpaid-待支付 waiting-待自取 done-已完成 cancel-已取消
*/
private String status;
/**
* 取消/退款原因
*/
private String cancelOrRefundReason;
/**
* 取消/退款时间
*/
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime cancelOrRefundTime;
/**
* 实际支付时间
*/
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime payTime;
/**
* 创建时间下单时间
*/
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
/**
* 更新时间核销时间
*/
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime;
}

View File

@ -0,0 +1,102 @@
package com.czg.account.dto.points;
import com.alibaba.fastjson2.annotation.JSONField;
import com.czg.validator.group.DefaultGroup;
import com.czg.validator.group.InsertGroup;
import com.czg.validator.group.UpdateGroup;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Null;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 积分商品设置
*
* @author Tankaikai tankaikai@aliyun.com
* @since 1.0 2025-02-25
*/
@Data
public class PointsGoodsSettingDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* id
*/
@Null(message = "ID必须为空", groups = InsertGroup.class)
@NotNull(message = "ID不能为空", groups = UpdateGroup.class)
private Long id;
/**
* 店铺id
*/
@NotNull(message = "店铺id不能为空", groups = DefaultGroup.class)
private Long shopId;
/**
* 商品类型 physical-实物 coupon-优惠劵
*/
@NotBlank(message = "商品类型不能为空", groups = DefaultGroup.class)
private String goodsCategory;
/**
* 商品名称
*/
@NotBlank(message = "商品名称不能为空", groups = DefaultGroup.class)
private String goodsName;
/**
* 商品图片URL
*/
private String goodsImageUrl;
/**
* 所需积分
*/
@NotNull(message = "所需积分不能为空", groups = DefaultGroup.class)
private Integer requiredPoints;
/**
* 额外价格
*/
@NotNull(message = "额外价格不能为空", groups = DefaultGroup.class)
private BigDecimal extraPrice;
/**
* 排序(权重)数字越高显示约靠前
*/
@NotNull(message = "排序(权重)不能为空", groups = DefaultGroup.class)
private Integer sort;
/**
* 数量
*/
@NotNull(message = "数量不能为空", groups = DefaultGroup.class)
private Integer quantity;
/**
* 商品详情
*/
private String goodsDescription;
/**
* 累计兑换数量
*/
private Integer totalExchangeCount;
/**
* 是否上架 1- 0-
*/
@NotNull(message = "是否上架不能为空", groups = DefaultGroup.class)
private Integer status;
/**
* 创建时间
*/
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
/**
* 更新时间
*/
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime;
/**
* 逻辑删除标志 1- 0-
*/
private Integer delFlag;
}

View File

@ -0,0 +1,77 @@
package com.czg.account.entity;
import com.alibaba.fastjson2.annotation.JSONField;
import com.mybatisflex.annotation.Column;
import com.mybatisflex.annotation.Id;
import com.mybatisflex.annotation.KeyType;
import com.mybatisflex.annotation.Table;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 会员积分
*
* @author Tankaikai tankaikai@aliyun.com
* @since 2.0 2024-10-25
*/
@Data
@Table("tb_shop_user")
public class MemberPoints implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* id
*/
@Id(keyType = KeyType.Auto)
private Long id;
/**
* 店铺id
*/
private Long shopId;
/**
* 会员id
*/
@Column(value = "id")
private Long userId;
/**
* 会员名称
*/
@Column("nick_name")
private String nickName;
/**
* 会员头像
*/
@Column("head_img")
private String headImg;
/**
* 手机号码
*/
@Column("phone")
private String phone;
/**
* 账户积分
*/
@Column("account_points")
private Integer accountPoints;
/**
* 最近一次积分变动时间
*/
@Column("last_points_change_time")
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime lastPointsChangeTime;
/**
* 最近一次浮动积分
*/
@Column("last_float_points")
private Integer lastFloatPoints;
/**
* 是否会员 1- 0-
*/
@Column("is_vip")
private Integer vip;
}

View File

@ -0,0 +1,60 @@
package com.czg.account.entity;
import com.mybatisflex.annotation.Column;
import com.mybatisflex.annotation.Id;
import com.mybatisflex.annotation.KeyType;
import com.mybatisflex.annotation.Table;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 会员积分变动记录
*
* @author Tankaikai tankaikai@aliyun.com
* @since 1.0 2025-02-25
*/
@Data
@Table("tb_member_points_log")
public class MemberPointsLog implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* id
*/
@Id(keyType = KeyType.Auto)
private Long id;
/**
* 店铺id
*/
private Long shopId;
/**
* 用户id
*/
private Long userId;
/**
* 摘要信息兑换某个商品/消费多少钱/充值多少钱/新会员赠送积分等
*/
private String content;
/**
* 订单编号
*/
private String orderNo;
/**
* 浮动类型 add-累加 subtract-扣减
*/
private String floatType;
/**
* 浮动积分(非0正负数)
*/
private Integer floatPoints;
/**
* 创建时间
*/
@Column(onInsertValue = "now()")
private LocalDateTime createTime;
}

View File

@ -4,17 +4,16 @@ import com.mybatisflex.annotation.Column;
import com.mybatisflex.annotation.Id;
import com.mybatisflex.annotation.KeyType;
import com.mybatisflex.annotation.Table;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.io.Serial;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 积分基本设置 实体类
*
@ -34,7 +33,7 @@ public class PointsBasicSetting implements Serializable {
/**
* 店铺id
*/
@Id(keyType = KeyType.Auto)
@Id(keyType = KeyType.None)
private Long shopId;
/**
@ -90,7 +89,7 @@ public class PointsBasicSetting implements Serializable {
/**
* 创建时间
*/
@Column(onInsertValue = "now()")
@Column(onInsertValue = "now()", onUpdateValue = "now()")
private LocalDateTime createTime;
}

View File

@ -0,0 +1,137 @@
package com.czg.account.entity;
import com.mybatisflex.annotation.Column;
import com.mybatisflex.annotation.Id;
import com.mybatisflex.annotation.KeyType;
import com.mybatisflex.annotation.Table;
import com.mybatisflex.core.keygen.KeyGenerators;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 积分兑换记录
*
* @author Tankaikai tankaikai@aliyun.com
* @since 1.0 2025-02-25
*/
@Data
@Table("tb_points_exchange_record")
public class PointsExchangeRecord implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* id
*/
@Id(keyType = KeyType.Generator, value = KeyGenerators.snowFlakeId)
private Long id;
/**
* 订单编号
*/
private String orderNo;
/**
* 店铺id
*/
private Long shopId;
/**
* 积分商品id
*/
private Long pointsGoodsId;
/**
* 积分商品名称
*/
private String pointsGoodsName;
/**
* 商品图片URL
*/
private String goodsImageUrl;
/**
* 领取方式 self-自取 post-邮寄
*/
private String pickupMethod;
/**
* 用户id
*/
private Long userId;
/**
* 用户昵称
*/
private String nickName;
/**
* 手机号码
*/
private String mobile;
/**
* 会员头像
*/
private String avatarUrl;
/**
* 消耗积分
*/
private Integer spendPoints;
/**
* 额外支付
*/
private BigDecimal extraPaymentAmount;
/**
* 兑换券券码
*/
private String couponCode;
/**
* 支付平台订单号
*/
private String payOrderId;
/**
* 渠道订单号(微信/支付宝订单号)
*/
private String channelTradeNo;
/**
* 支付方式 积分支付/积分+微信/积分+支付宝
*/
private String payMethod;
/**
* 支付类型 POINTS-积分 WECHAT-微信 ALIPAY-支付宝 UNIONPAY-银联云闪付
*/
private String payType;
/**
* 状态 unpaid-待支付 waiting-待自取 done-已完成 cancel-已取消
*/
private String status;
/**
* 取消/退款原因
*/
private String cancelOrRefundReason;
/**
* 取消/退款时间
*/
private LocalDateTime cancelOrRefundTime;
/**
* 实际支付时间
*/
private LocalDateTime payTime;
/**
* 创建时间下单时间
*/
@Column(onInsertValue = "now()")
private LocalDateTime createTime;
/**
* 更新时间核销时间
*/
@Column(onInsertValue = "now()", onUpdateValue = "now()")
private LocalDateTime updateTime;
/**
* 次数
*/
@Column(ignore = true)
private Long count;
/**
* 总额
*/
@Column(ignore = true)
private BigDecimal totalAmount;
}

View File

@ -0,0 +1,92 @@
package com.czg.account.entity;
import com.mybatisflex.annotation.Column;
import com.mybatisflex.annotation.Id;
import com.mybatisflex.annotation.KeyType;
import com.mybatisflex.annotation.Table;
import com.mybatisflex.core.keygen.KeyGenerators;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 积分商品设置
*
* @author Tankaikai tankaikai@aliyun.com
* @since 1.0 2025-02-25
*/
@Data
@Table("tb_points_goods_setting")
public class PointsGoodsSetting implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* id
*/
@Id(keyType = KeyType.Generator, value = KeyGenerators.snowFlakeId)
private Long id;
/**
* 店铺id
*/
private Long shopId;
/**
* 商品类型 physical-实物 coupon-优惠劵
*/
private String goodsCategory;
/**
* 商品名称
*/
private String goodsName;
/**
* 商品图片URL
*/
private String goodsImageUrl;
/**
* 所需积分
*/
private Integer requiredPoints;
/**
* 额外价格
*/
private BigDecimal extraPrice;
/**
* 排序(权重)数字越高显示约靠前
*/
private Integer sort;
/**
* 数量
*/
private Integer quantity;
/**
* 商品详情
*/
private String goodsDescription;
/**
* 累计兑换数量
*/
private Integer totalExchangeCount;
/**
* 是否上架 1- 0-
*/
private Integer status;
/**
* 创建时间
*/
@Column(onInsertValue = "now()")
private LocalDateTime createTime;
/**
* 更新时间
*/
@Column(onInsertValue = "now()", onUpdateValue = "now()")
private LocalDateTime updateTime;
/**
* 逻辑删除标志 1- 0-
*/
@Column(onInsertValue = "0")
private Integer delFlag;
}

View File

@ -0,0 +1,29 @@
package com.czg.account.enums;
/**
* 抵扣适用群体枚举
*
* @author tankaikai
* @since 2025-02-25 17:50
*/
public enum PointUserGroupEnum {
/**
* 全部用户
*/
ALL("all"),
/**
* 仅会员
*/
VIP("vip");
private String value;
PointUserGroupEnum(String value) {
this.value = value;
}
public String value() {
return this.value;
}
}

View File

@ -0,0 +1,36 @@
package com.czg.account.enums;
/**
* 积分兑换状态枚举
*
* @author tankaikai
* @since 2025-02-25 15:37
*/
public enum PointsExchangeStatusEnum {
/**
* 待支付
*/
UNPAID("unpaid"),
/**
* 待自取
*/
WAITING("waiting"),
/**
* 已完成
*/
DONE("done"),
/**
* 已取消
*/
CANCEL("cancel");
private String value;
PointsExchangeStatusEnum(String value) {
this.value = value;
}
public String value() {
return this.value;
}
}

View File

@ -0,0 +1,28 @@
package com.czg.account.param;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 消费赠送积分入参
*
* @author tankaikai
* @since 2025-02-25 18:20
*/
@Data
public class ConsumeAwardPointsParam implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 用户ID
*/
private Long userId;
/**
* 订单ID
*/
private Long orderId;
}

View File

@ -0,0 +1,28 @@
package com.czg.account.param;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 会员积分查询入参
*
* @author tankaikai
* @since 2025-02-25 17:18
*/
@Data
public class MemberPointsParam implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 用户昵称
*/
private String nickName;
/**
* 手机号
*/
private String phone;
}

View File

@ -0,0 +1,36 @@
package com.czg.account.param;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 支付完成扣减积分入参
*
* @author tankaikai
* @since 2025-02-25 18:07
*/
@Data
public class PayedDeductPointsParam implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 用户ID
*/
private Long userId;
/**
* 消耗积分
*/
private Integer points;
/**
* 摘要信息兑换某个商品/消费多少钱/充值多少钱/新会员赠送积分等
*/
private String content;
/**
* 订单ID可为空
*/
private Long orderId;
}

View File

@ -0,0 +1,28 @@
package com.czg.account.param;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 积分兑换 - 取消/退款入参
*
* @author tankaikai
* @since 2025-02-25 15:29
*/
@Data
public class PointsExchangeCfParam implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* id
*/
private Long id;
/**
* 取消或退款原因
*/
private String cancelOrRefundReason;
}

View File

@ -0,0 +1,44 @@
package com.czg.account.param;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 兑换记录查询入侵
*
* @author tankaikai
* @since 2025-02-25 14:08
*/
@Data
public class PointsExchangeRecordParam implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 查询关键字
*/
private String keywords;
/**
* 查询开始日期 格式 yyyy-MM-dd
*/
private String beginDate;
/**
* 查询结束日期 格式 yyyy-MM-dd
*/
private String endDate;
/**
* 领取方式 self-自取 post-邮寄
*/
private String pickupMethod;
/**
* 状态 unpaid-待支付 waiting-待自取 done-已完成 cancel-已取消
*/
private String status;
/**
* 用户id
*/
private Long userId;
}

View File

@ -0,0 +1,18 @@
package com.czg.account.service;
import com.czg.account.dto.points.MemberPointsLogDTO;
import com.czg.account.entity.MemberPointsLog;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.service.IService;
/**
* 会员积分变动记录
*
* @author Tankaikai tankaikai@aliyun.com
* @since 1.0 2025-02-25
*/
public interface MemberPointsLogService extends IService<MemberPointsLog> {
Page<MemberPointsLogDTO> getMemberPointsLogPage(MemberPointsLogDTO param);
}

View File

@ -0,0 +1,99 @@
package com.czg.account.service;
import com.czg.account.dto.points.OrderDeductionPointsDTO;
import com.czg.account.entity.MemberPoints;
import com.czg.account.param.MemberPointsParam;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.service.IService;
import java.math.BigDecimal;
/**
* 会员积分
*
* @author Tankaikai tankaikai@aliyun.com
* @since 1.0 2025-02-25
*/
public interface MemberPointsService extends IService<MemberPoints> {
/**
* 会员积分分页
*
* @param param 查询参数
* @return 分页结果
*/
Page<MemberPoints> getMemberPointsPage(MemberPointsParam param);
/**
* 初始化会员积分
*
* @param userId 用户id
* @return 会员积分等信息
*/
MemberPoints initMemberPoints(Long userId);
/**
* 获取会员积分等信息
*
* @param userId 用户id
* @return 会员积分等信息
*/
MemberPoints getMemberPoints(Long userId);
/**
* 根据用户id及订单金额计算可抵扣积分及可抵扣金额
*
* @param userId 用户id
* @param orderAmount 订单金额
* @return 积分及金额
*/
OrderDeductionPointsDTO getMemberUsablePoints(Long userId, BigDecimal orderAmount);
/**
* 根据抵扣金额计算抵扣积分
*
* @param userId 用户id
* @param orderAmount 订单金额
* @param deductionAmount 抵扣金额
*/
int calcUsedPoints(Long userId, BigDecimal orderAmount, BigDecimal deductionAmount);
/**
* 根据抵扣积分计算抵扣金额
*
* @param userId 用户id
* @param orderAmount 订单金额
* @param points 抵扣积分
*/
BigDecimal calcDeductionAmount(Long userId, BigDecimal orderAmount, int points);
/**
* 扣除积分
*
* @param userId 用户id
* @param points 积分
* @param content 摘要信息兑换积分商品/积分抵扣账单/消费赠送积分/新会员送积分/储值赠送积分
* @param orderId 订单id可以为空
*/
boolean deductPoints(Long userId, int points, String content, Long orderId);
/**
* 追加积分
*
* @param userId 用户id
* @param points 积分
* @param content 摘要信息兑换积分商品/积分抵扣账单/消费赠送积分/新会员送积分/储值赠送积分
* @param orderId 订单id可以为空
* @return 成功/失败
*/
boolean addPoints(Long userId, int points, String content, Long orderId);
/**
* 消费赠送积分
*
* @param userId 用户id
* @param orderId 订单id
*/
void consumeAwardPoints(Long userId, Long orderId);
}

View File

@ -0,0 +1,69 @@
package com.czg.account.service;
import com.czg.account.dto.points.PointsExchangeRecordDTO;
import com.czg.account.entity.PointsExchangeRecord;
import com.czg.account.param.PointsExchangeCfParam;
import com.czg.account.param.PointsExchangeRecordParam;
import com.czg.account.vo.PointsExchangeSummaryVo;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.service.IService;
/**
* 积分兑换记录
*
* @author Tankaikai tankaikai@aliyun.com
* @since 1.0 2025-02-25
*/
public interface PointsExchangeRecordService extends IService<PointsExchangeRecord> {
/**
* 获取积分兑换记录分页
*
* @param param 查询参数
* @return 分页数据
*/
Page<PointsExchangeRecordDTO> getPointsExchangeRecordPage(PointsExchangeRecordParam param);
/**
* 获取积分兑换记录详情
*
* @param id 兑换记录id
* @return 兑换记录详情
*/
PointsExchangeRecordDTO getPointsExchangeRecordById(Long id);
/**
* 核销券码
*
* @param couponCode 券码
*/
void checkout(String couponCode);
/**
* 取消
*
* @param param 兑换记录
*/
void cancel(PointsExchangeCfParam param);
/**
* 退款
*
* @param param 兑换记录
*/
void refund(PointsExchangeCfParam param);
/**
* 统计兑换记录
*
* @param param 查询参数
* @return 统计结果
*/
PointsExchangeSummaryVo total(PointsExchangeRecordParam param);
/**
* 自动取消
*/
void authCancel();
}

View File

@ -0,0 +1,22 @@
package com.czg.account.service;
import com.czg.account.dto.points.PointsGoodsSettingDTO;
import com.czg.account.entity.PointsGoodsSetting;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.service.IService;
/**
* 积分商品设置
*
* @author Tankaikai tankaikai@aliyun.com
* @since 1.0 2025-02-25
*/
public interface PointsGoodsSettingService extends IService<PointsGoodsSetting> {
/**
* 获取积分商品设置分页
*
* @param param 查询参数
* @return 分页结果
*/
Page<PointsGoodsSettingDTO> getPointsGoodsSettingPage(PointsGoodsSettingDTO param);
}

View File

@ -0,0 +1,29 @@
package com.czg.account.vo;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
/**
* 积分兑换汇总VO
*
* @author tankaikai
* @since 2025-02-25 14:05
*/
@Data
public class PointsExchangeSummaryVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 次数
*/
private Long count;
/**
* 金额
*/
private BigDecimal totalAmount;
}

View File

@ -0,0 +1,16 @@
package com.czg.service.account.mapper;
import com.czg.account.entity.MemberPointsLog;
import com.mybatisflex.core.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
* 会员积分变动记录
*
* @author Tankaikai tankaikai@aliyun.com
* @since 1.0 2025-02-25
*/
@Mapper
public interface MemberPointsLogMapper extends BaseMapper<MemberPointsLog> {
}

View File

@ -0,0 +1,16 @@
package com.czg.service.account.mapper;
import com.czg.account.entity.MemberPoints;
import com.mybatisflex.core.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
* 会员积分
*
* @author Tankaikai tankaikai@aliyun.com
* @since 1.0 2025-02-25
*/
@Mapper
public interface MemberPointsMapper extends BaseMapper<MemberPoints> {
}

View File

@ -1,7 +1,8 @@
package com.czg.service.account.mapper;
import com.mybatisflex.core.BaseMapper;
import com.czg.account.entity.PointsBasicSetting;
import com.mybatisflex.core.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
* 积分基本设置 映射层
@ -9,6 +10,7 @@ import com.czg.account.entity.PointsBasicSetting;
* @author ww
* @since 2025-02-20
*/
@Mapper
public interface PointsBasicSettingMapper extends BaseMapper<PointsBasicSetting> {
}

View File

@ -0,0 +1,16 @@
package com.czg.service.account.mapper;
import com.czg.account.entity.PointsExchangeRecord;
import com.mybatisflex.core.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
* 积分兑换记录
*
* @author Tankaikai tankaikai@aliyun.com
* @since 1.0 2025-02-25
*/
@Mapper
public interface PointsExchangeRecordMapper extends BaseMapper<PointsExchangeRecord> {
}

View File

@ -0,0 +1,16 @@
package com.czg.service.account.mapper;
import com.czg.account.entity.PointsGoodsSetting;
import com.mybatisflex.core.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
* 积分商品设置
*
* @author Tankaikai tankaikai@aliyun.com
* @since 1.0 2025-02-25
*/
@Mapper
public interface PointsGoodsSettingMapper extends BaseMapper<PointsGoodsSetting> {
}

View File

@ -0,0 +1,76 @@
package com.czg.service.account.service.impl;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import com.czg.account.dto.points.MemberPointsLogDTO;
import com.czg.account.entity.MemberPointsLog;
import com.czg.account.entity.ShopUser;
import com.czg.account.service.MemberPointsLogService;
import com.czg.sa.StpKit;
import com.czg.service.account.mapper.MemberPointsLogMapper;
import com.czg.utils.PageUtil;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import static com.czg.account.entity.table.MemberPointsLogTableDef.MEMBER_POINTS_LOG;
import static com.czg.account.entity.table.MemberPointsTableDef.MEMBER_POINTS;
import static com.czg.account.entity.table.ShopUserTableDef.SHOP_USER;
/**
* 会员积分变动记录
*
* @author Tankaikai tankaikai@aliyun.com
* @since 1.0 2025-02-25
*/
@Service
public class MemberPointsLogServiceImpl extends ServiceImpl<MemberPointsLogMapper, MemberPointsLog> implements MemberPointsLogService {
private QueryWrapper buildQueryWrapper(MemberPointsLogDTO param) {
QueryWrapper queryWrapper = PageUtil.buildSortQueryWrapper();
if (ObjUtil.isNotNull(param.getUserId())) {
queryWrapper.eq(MemberPointsLog::getUserId, param.getUserId());
}
if (StrUtil.isNotBlank(param.getNickName())) {
queryWrapper.like(ShopUser::getNickName, param.getNickName());
}
if (StrUtil.isNotBlank(param.getPhone())) {
queryWrapper.like(ShopUser::getPhone, param.getPhone());
}
if (StrUtil.isNotBlank(param.getContent())) {
queryWrapper.like(MemberPointsLog::getContent, param.getContent());
}
if (StrUtil.isNotBlank(param.getFloatType())) {
queryWrapper.eq(MemberPointsLog::getFloatType, param.getFloatType());
}
if (StrUtil.isNotBlank(param.getOrderNo())) {
queryWrapper.eq(MemberPointsLog::getOrderNo, param.getOrderNo());
}
if (StrUtil.isNotEmpty(param.getBeginDate())) {
queryWrapper.ge(MemberPointsLog::getCreateTime, param.getBeginDate() + " 00:00:00");
}
if (StrUtil.isNotEmpty(param.getEndDate())) {
queryWrapper.le(MemberPointsLog::getCreateTime, param.getEndDate() + " 23:59:59");
}
Long shopId = StpKit.USER.getShopId(0L);
queryWrapper.eq(MemberPointsLog::getShopId, shopId);
queryWrapper.orderBy(MemberPointsLog::getId, false);
return queryWrapper;
}
private QueryWrapper buildFullQueryWrapper(MemberPointsLogDTO param) {
QueryWrapper queryWrapper = buildQueryWrapper(param);
queryWrapper.select(MEMBER_POINTS.DEFAULT_COLUMNS)
.select(SHOP_USER.NICK_NAME.as(MemberPointsLogDTO::getNickName), SHOP_USER.HEAD_IMG.as(MemberPointsLogDTO::getHeadImg), SHOP_USER.PHONE.as(MemberPointsLogDTO::getPhone))
.from(MEMBER_POINTS_LOG)
.leftJoin(SHOP_USER).on(SHOP_USER.ID.eq(MEMBER_POINTS.USER_ID));
return queryWrapper;
}
@Override
public Page<MemberPointsLogDTO> getMemberPointsLogPage(MemberPointsLogDTO param) {
QueryWrapper queryWrapper = buildFullQueryWrapper(param);
return super.pageAs(PageUtil.buildPage(), queryWrapper, MemberPointsLogDTO.class);
}
}

View File

@ -0,0 +1,287 @@
package com.czg.service.account.service.impl;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.czg.account.dto.points.OrderDeductionPointsDTO;
import com.czg.account.entity.MemberPoints;
import com.czg.account.entity.MemberPointsLog;
import com.czg.account.entity.PointsBasicSetting;
import com.czg.account.enums.PointUserGroupEnum;
import com.czg.account.param.MemberPointsParam;
import com.czg.account.service.MemberPointsService;
import com.czg.enums.YesNoEnum;
import com.czg.exception.CzgException;
import com.czg.order.entity.OrderInfo;
import com.czg.order.service.OrderInfoService;
import com.czg.sa.StpKit;
import com.czg.service.account.mapper.MemberPointsLogMapper;
import com.czg.service.account.mapper.MemberPointsMapper;
import com.czg.service.account.mapper.PointsBasicSettingMapper;
import com.czg.utils.PageUtil;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import jakarta.annotation.Resource;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
/**
* 会员积分变动记录
*
* @author Tankaikai tankaikai@aliyun.com
* @since 1.0 2025-02-25
*/
@Service
public class MemberPointsServiceImpl extends ServiceImpl<MemberPointsMapper, MemberPoints> implements MemberPointsService {
@Resource
private PointsBasicSettingMapper pointsBasicSettingMapper;
@Resource
private MemberPointsLogMapper memberPointsLogMapper;
@DubboReference
private OrderInfoService orderInfoService;
private QueryWrapper buildQueryWrapper(MemberPointsParam param) {
QueryWrapper queryWrapper = PageUtil.buildSortQueryWrapper();
if (StrUtil.isNotBlank(param.getNickName())) {
queryWrapper.like(MemberPoints::getNickName, param.getNickName());
}
if (StrUtil.isNotBlank(param.getPhone())) {
queryWrapper.like(MemberPoints::getPhone, param.getPhone());
}
Long shopId = StpKit.USER.getShopId(0L);
queryWrapper.eq(MemberPoints::getShopId, shopId);
queryWrapper.orderBy(MemberPoints::getId, false);
return queryWrapper;
}
@Override
public Page<MemberPoints> getMemberPointsPage(MemberPointsParam param) {
QueryWrapper queryWrapper = buildQueryWrapper(param);
return super.page(PageUtil.buildPage(), queryWrapper);
}
@Override
public MemberPoints initMemberPoints(Long userId) {
MemberPoints entity = super.getOne(query().eq(MemberPoints::getUserId, userId));
if (entity == null) {
throw new CzgException("会员信息不存在");
}
if (entity.getAccountPoints() == null) {
entity.setAccountPoints(0);
super.updateById(entity);
}
return entity;
}
@Override
public MemberPoints getMemberPoints(Long userId) {
return initMemberPoints(userId);
}
@Override
public OrderDeductionPointsDTO getMemberUsablePoints(Long userId, BigDecimal orderAmount) {
MemberPoints entity = initMemberPoints(userId);
Long shopId = entity.getShopId();
Integer accountPoints = entity.getAccountPoints();
OrderDeductionPointsDTO dto = new OrderDeductionPointsDTO();
dto.setAccountPoints(accountPoints);
dto.setUsable(false);
dto.setMaxDeductionAmount(BigDecimal.ZERO);
dto.setMinDeductionAmount(BigDecimal.ZERO);
PointsBasicSetting basic = pointsBasicSettingMapper.selectOneById(shopId);
if (basic == null) {
dto.setUnusableReason("商家未启用积分抵扣功能");
return dto;
}
if (basic.getEnableDeduction() == 0) {
dto.setUnusableReason("商家未启用积分抵扣功能");
return dto;
}
if (PointUserGroupEnum.VIP.value().equals(basic.getDeductionGroup()) && ObjectUtil.defaultIfNull(entity.getVip(), YesNoEnum.NO.value()) != YesNoEnum.YES.value()) {
dto.setUnusableReason("仅VIP用户可用");
return dto;
}
dto.setMinPaymentAmount(basic.getMinPaymentAmount());
dto.setOrderAmount(orderAmount);
if (NumberUtil.isLess(orderAmount, basic.getMinPaymentAmount())) {
dto.setUnusableReason(StrUtil.format("实付金额不足¥{}元,无法进行抵扣", basic.getMinPaymentAmount()));
return dto;
}
dto.setEquivalentPoints(basic.getEquivalentPoints());
// 最低抵扣积分 = 1元等值积分 = 1.0元
dto.setMinDeductionPoints(basic.getEquivalentPoints());
// 计算抵扣门槛=?
dto.setMinDeductionAmount(BigDecimal.ONE);
if (accountPoints == 0 || accountPoints < dto.getMinDeductionPoints()) {
dto.setUnusableReason("积分不足或小于最低使用门槛" + dto.getMinDeductionPoints());
return dto;
}
// 下单抵扣积分比例 1元=?积分
Integer equivalentPoints = basic.getEquivalentPoints();
// 计算账户积分=?
BigDecimal accountYuan = NumberUtil.div(accountPoints, equivalentPoints);
// 下单最高抵扣比例
BigDecimal maxDeductionRatio = basic.getMaxDeductionRatio();
// 计算订单最多可以抵扣多少元向下取整
BigDecimal orderYuan = NumberUtil.roundDown(NumberUtil.mul(orderAmount, NumberUtil.div(maxDeductionRatio, new BigDecimal("100"))), 0);
// 积分余额足够
if (NumberUtil.isGreaterOrEqual(accountYuan, orderYuan)) {
dto.setMaxDeductionAmount(orderYuan);
} else {
dto.setMaxDeductionAmount(NumberUtil.roundDown(accountYuan, 0));
}
if (NumberUtil.isLess(dto.getMaxDeductionAmount(), BigDecimal.ONE)) {
dto.setUnusableReason("积分不足1元无法进行抵扣");
return dto;
}
dto.setUsable(true);
// 计算最多可抵扣的积分
BigDecimal mul = NumberUtil.mul(dto.getMaxDeductionAmount(), equivalentPoints);
BigDecimal round = NumberUtil.round(mul, 0, RoundingMode.CEILING);
dto.setMaxUsablePoints(round.intValue());
return dto;
}
@Override
public int calcUsedPoints(Long userId, BigDecimal orderAmount, BigDecimal deductionAmount) {
OrderDeductionPointsDTO core = getMemberUsablePoints(userId, orderAmount);
if (!core.getUsable()) {
throw new CzgException(core.getUnusableReason());
}
if (NumberUtil.isGreater(deductionAmount, core.getMaxDeductionAmount())) {
throw new CzgException(StrUtil.format("抵扣金额不能超过最大抵扣金额{}元", core.getMaxDeductionAmount()));
}
if (NumberUtil.isGreater(deductionAmount, orderAmount)) {
throw new CzgException(StrUtil.format("抵扣金额不能超过订单金额{}元", orderAmount));
}
// 计算可抵扣的积分
BigDecimal mul = NumberUtil.mul(deductionAmount, core.getEquivalentPoints());
BigDecimal round = NumberUtil.round(mul, 0, RoundingMode.CEILING);
return round.intValue();
}
@Override
public BigDecimal calcDeductionAmount(Long userId, BigDecimal orderAmount, int points) {
OrderDeductionPointsDTO core = getMemberUsablePoints(userId, orderAmount);
if (!core.getUsable()) {
throw new CzgException(core.getUnusableReason());
}
if (points < core.getMinDeductionPoints()) {
throw new CzgException(StrUtil.format("使用积分不能低于使用门槛(每次最少使用{}积分)", core.getMinDeductionPoints()));
}
if (points > core.getMaxUsablePoints()) {
throw new CzgException(StrUtil.format("使用积分不能超过最大使用限制{}", core.getMaxUsablePoints()));
}
BigDecimal money = NumberUtil.mul(points, NumberUtil.div(BigDecimal.ONE, core.getEquivalentPoints()));
BigDecimal maxDeductionAmount = NumberUtil.roundDown(money, 0);
if (NumberUtil.isGreater(maxDeductionAmount, core.getMaxDeductionAmount())) {
return core.getMaxDeductionAmount();
}
return maxDeductionAmount;
}
@Override
public boolean deductPoints(Long userId, int points, String content, Long orderId) {
MemberPoints entity = initMemberPoints(userId);
// 扣除账户积分
entity.setAccountPoints(entity.getAccountPoints() - points);
entity.setLastPointsChangeTime(LocalDateTime.now());
entity.setLastFloatPoints(-points);
// 记录积分变动记录
MemberPointsLog log = new MemberPointsLog();
log.setShopId(entity.getShopId());
log.setUserId(entity.getUserId());
log.setContent(content);
log.setFloatType("subtract");
log.setFloatPoints(-points);
// 有关联订单的需要回置订单表的相关积分使用字段
if (orderId != null) {
OrderInfo orderInfo = orderInfoService.getById(orderId);
if (orderInfo != null) {
log.setOrderNo(orderInfo.getOrderNo());
// TODO 是否需要回执使用的积分数量(points_num)积分抵扣金额(points_discount_amount),目前不清楚是创建订单的时候置入还是支付完成后回调时置入需要商议后决定
}
}
super.updateById(entity);
memberPointsLogMapper.insert(log);
return true;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean addPoints(Long memberId, int points, String content, Long orderId) {
MemberPoints entity = initMemberPoints(memberId);
// 增加账户积分
entity.setAccountPoints(entity.getAccountPoints() + points);
entity.setLastPointsChangeTime(LocalDateTime.now());
entity.setLastFloatPoints(points);
// 记录积分变动记录
MemberPointsLog log = new MemberPointsLog();
log.setShopId(entity.getShopId());
log.setUserId(entity.getUserId());
log.setContent(content);
log.setFloatType("add");
log.setFloatPoints(points);
// 有关联订单的需要回置订单表的相关积分使用字段
if (orderId != null) {
OrderInfo orderInfo = orderInfoService.getById(orderId);
if (orderInfo != null) {
log.setOrderNo(orderInfo.getOrderNo());
}
}
super.updateById(entity);
memberPointsLogMapper.insert(log);
return true;
}
@Override
@Transactional(rollbackFor = Exception.class)
public void consumeAwardPoints(Long memberId, Long orderId) {
OrderInfo orderInfo = orderInfoService.getById(orderId);
if (orderInfo == null) {
throw new CzgException("订单不存在");
}
BigDecimal payAmount = orderInfo.getPayAmount();
if (NumberUtil.isLessOrEqual(payAmount, BigDecimal.ZERO)) {
return;
}
PointsBasicSetting basicSetting = pointsBasicSettingMapper.selectOneById(orderInfo.getShopId());
if (basicSetting == null) {
return;
}
Integer enableRewards = basicSetting.getEnableRewards();
if (enableRewards == 0) {
return;
}
String rewardsGroup = basicSetting.getRewardsGroup();
MemberPoints entity;
try {
entity = initMemberPoints(memberId);
} catch (Exception e) {
return;
}
Integer vip = entity.getVip();
if (PointUserGroupEnum.VIP.value().equals(rewardsGroup) && ObjectUtil.defaultIfNull(vip, 0) != 1) {
return;
}
BigDecimal consumeAmount = basicSetting.getConsumeAmount();
if (consumeAmount == null) {
return;
}
if (NumberUtil.isLessOrEqual(consumeAmount, BigDecimal.ZERO)) {
return;
}
BigDecimal awardPoints = NumberUtil.roundDown(NumberUtil.div(payAmount, consumeAmount), 0);
addPoints(memberId, awardPoints.intValue(), StrUtil.format("消费¥{}送{}积分", payAmount, awardPoints.intValue()), orderId);
}
}

View File

@ -5,6 +5,7 @@ import com.czg.account.service.PointsBasicSettingService;
import com.czg.service.account.mapper.PointsBasicSettingMapper;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.stereotype.Service;
/**
* 积分基本设置 服务层实现
@ -12,6 +13,7 @@ import org.apache.dubbo.config.annotation.DubboService;
* @author ww
* @since 2025-02-20
*/
@Service
@DubboService
public class PointsBasicSettingServiceImpl extends ServiceImpl<PointsBasicSettingMapper, PointsBasicSetting> implements PointsBasicSettingService{

View File

@ -0,0 +1,234 @@
package com.czg.service.account.service.impl;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import com.czg.account.dto.points.PointsExchangeRecordDTO;
import com.czg.account.entity.*;
import com.czg.account.enums.PointsExchangeStatusEnum;
import com.czg.account.param.PointsExchangeCfParam;
import com.czg.account.param.PointsExchangeRecordParam;
import com.czg.account.service.PointsExchangeRecordService;
import com.czg.account.vo.PointsExchangeSummaryVo;
import com.czg.exception.CzgException;
import com.czg.sa.StpKit;
import com.czg.service.account.mapper.*;
import com.czg.utils.PageUtil;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryColumn;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.core.update.UpdateChain;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Date;
/**
* 积分兑换记录
*
* @author Tankaikai tankaikai@aliyun.com
* @since 1.0 2025-02-25
*/
@AllArgsConstructor
@Service
public class PointsExchangeRecordServiceImpl extends ServiceImpl<PointsExchangeRecordMapper, PointsExchangeRecord> implements PointsExchangeRecordService {
private final PointsGoodsSettingMapper pointsGoodsSettingMapper;
private final MemberPointsMapper memberPointsMapper;
private final MemberPointsLogMapper memberPointsLogMapper;
private final ShopInfoMapper shopInfoMapper;
private final ShopMerchantMapper shopMerchantMapper;
private QueryWrapper buildQueryWrapper(PointsExchangeRecordParam param) {
QueryWrapper queryWrapper = PageUtil.buildSortQueryWrapper();
if (StrUtil.isNotEmpty(param.getPickupMethod())) {
queryWrapper.eq(PointsExchangeRecord::getPickupMethod, param.getPickupMethod());
}
if (StrUtil.isNotEmpty(param.getStatus())) {
queryWrapper.eq(PointsExchangeRecord::getStatus, param.getStatus());
}
if (ObjUtil.isNotNull(param.getUserId())) {
queryWrapper.eq(PointsExchangeRecord::getUserId, param.getUserId());
}
if (StrUtil.isNotEmpty(param.getKeywords())) {
queryWrapper.and(q -> {
q.like(PointsExchangeRecord::getOrderNo, param.getKeywords()).or(r -> {
r.like(PointsExchangeRecord::getCouponCode, param.getKeywords());
});
});
}
if (StrUtil.isNotEmpty(param.getBeginDate())) {
queryWrapper.ge(PointsExchangeRecord::getCreateTime, param.getBeginDate() + " 00:00:00");
}
if (StrUtil.isNotEmpty(param.getEndDate())) {
queryWrapper.le(PointsExchangeRecord::getCreateTime, param.getEndDate() + " 23:59:59");
}
Long shopId = StpKit.USER.getShopId(0L);
queryWrapper.eq(PointsExchangeRecord::getShopId, shopId);
queryWrapper.orderBy(PointsExchangeRecord::getId, false);
return queryWrapper;
}
@Override
public Page<PointsExchangeRecordDTO> getPointsExchangeRecordPage(PointsExchangeRecordParam param) {
QueryWrapper queryWrapper = buildQueryWrapper(param);
return super.pageAs(PageUtil.buildPage(), queryWrapper, PointsExchangeRecordDTO.class);
}
@Override
public PointsExchangeRecordDTO getPointsExchangeRecordById(Long id) {
Long shopId = StpKit.USER.getShopId(0L);
return super.mapper.selectOneByQueryAs(query().eq(PointsExchangeRecord::getShopId, shopId).eq(PointsExchangeRecord::getId, id), PointsExchangeRecordDTO.class);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void checkout(String couponCode) {
if (StrUtil.isBlank(couponCode)) {
throw new CzgException("兑换券券码不能为空");
}
PointsExchangeRecord entity = super.getOne(query().eq(PointsExchangeRecord::getCouponCode, couponCode));
if (entity == null) {
throw new CzgException("兑换券券码无效");
}
entity.setStatus(PointsExchangeStatusEnum.DONE.value());
super.updateById(entity);
PointsGoodsSetting goods = pointsGoodsSettingMapper.selectOneById(entity.getPointsGoodsId());
if (goods == null) {
throw new CzgException("积分商品不存在");
}
// 更新累计兑换数量
goods.setTotalExchangeCount(goods.getTotalExchangeCount() + 1);
pointsGoodsSettingMapper.update(goods);
}
@Override
public void cancel(PointsExchangeCfParam param) {
if (param.getId() == null) {
throw new CzgException("订单ID不能为空");
}
PointsExchangeRecord entity = super.getById(param.getId());
if (entity == null) {
throw new CzgException("订单不存在");
}
if (!PointsExchangeStatusEnum.UNPAID.value().equals(entity.getStatus())) {
throw new CzgException("当前订单状态不支持取消");
}
entity.setStatus(PointsExchangeStatusEnum.CANCEL.value());
entity.setCancelOrRefundReason(param.getCancelOrRefundReason());
entity.setCancelOrRefundTime(LocalDateTime.now());
super.updateById(entity);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void refund(PointsExchangeCfParam param) {
if (param.getId() == null) {
throw new CzgException("订单ID不能为空");
}
PointsExchangeRecord entity = super.getById(param.getId());
if (entity == null) {
throw new CzgException("订单不存在");
}
if (!PointsExchangeStatusEnum.WAITING.value().equals(entity.getStatus())) {
throw new CzgException("当前订单状态不支持退款");
}
// 先退积分
entity.setStatus(PointsExchangeStatusEnum.CANCEL.value());
entity.setCancelOrRefundReason(param.getCancelOrRefundReason());
entity.setCancelOrRefundTime(LocalDateTime.now());
super.updateById(entity);
MemberPoints memberPoints = memberPointsMapper.selectOneByQuery(query().eq(MemberPoints::getUserId, entity.getUserId()));
if (memberPoints == null) {
throw new CzgException("会员信息不存在");
}
memberPoints.setAccountPoints(memberPoints.getAccountPoints() + entity.getSpendPoints());
memberPoints.setLastPointsChangeTime(LocalDateTime.now());
memberPoints.setLastFloatPoints(entity.getSpendPoints());
memberPointsMapper.update(memberPoints);
// 回滚库存
PointsGoodsSetting goods = pointsGoodsSettingMapper.selectOneById(entity.getPointsGoodsId());
if (goods == null) {
throw new CzgException("积分商品不存在");
}
goods.setQuantity(goods.getQuantity() + 1);
pointsGoodsSettingMapper.update(goods);
// 记录积分浮动流水
MemberPointsLog log = new MemberPointsLog();
log.setShopId(entity.getShopId());
log.setUserId(entity.getUserId());
log.setContent(StrUtil.format("(退单)兑换商品:{} * {}", entity.getPointsGoodsName(), "1"));
log.setFloatType("add");
log.setFloatPoints(+entity.getSpendPoints());
log.setCreateTime(LocalDateTime.now());
memberPointsLogMapper.insert(log);
// 如果额外付款了则需要退款
if (NumberUtil.equals(entity.getExtraPaymentAmount(), BigDecimal.ZERO)) {
return;
}
// 需要额外支付
ShopInfo shopInfo = shopInfoMapper.selectOneById(entity.getShopId());
if (shopInfo == null) {
throw new CzgException("店铺信息不存在");
}
ShopMerchant shopMerchant = shopMerchantMapper.selectOneById(shopInfo.getId());
if (shopMerchant == null) {
throw new CzgException("支付通道不存在");
}
if ("alipay".equalsIgnoreCase(entity.getPayType()) && StrUtil.isBlank(shopMerchant.getAlipaySmallAppid())) {
throw new CzgException("店铺未配置支付宝小程序appId");
}
// 准备退款参数
// 应用ID
// TODO 对接退款接口
/* String appId = thirdApply.getAppId();
String appToken = thirdApply.getAppToken();
// 交易金额 单位分 100 表示1元
long amount = NumberUtil.mul(record.getExtraPaymentAmount(), new BigDecimal("100")).longValue();
// 退款订单号
String mchRefundNo = entity.getOrderNo();
PublicResp<OrderReturnResp> publicResp;
try {
publicResp = thirdPayService.returnOrder(thirdUrl, appId, mchRefundNo, entity.getPayOrderId(), null, entity.getCancelOrRefundReason(), amount, null, null, appToken);
} catch (Exception e) {
super.log.error("发起退款失败:", e);
throw new CzgException(StrUtil.format("发起退款失败:{}", e.getMessage()));
}
if (publicResp == null) {
throw new CzgException("发起退款失败:无响应");
}
if (!"000000".equals(publicResp.getCode())) {
throw new CzgException(publicResp.getMsg());
}
OrderReturnResp returnResp = publicResp.getObjData();
if (!"SUCCESS".equals(returnResp.getState())) {
throw new CzgException(StrUtil.format("退款失败原因:{}", returnResp.getState()));
}*/
}
@Override
public PointsExchangeSummaryVo total(PointsExchangeRecordParam param) {
QueryWrapper queryWrapper = buildQueryWrapper(param);
queryWrapper.select(new QueryColumn("count(*) as count"), new QueryColumn("sum(extra_payment_amount) as totalAmount"));
PointsExchangeRecord summary = super.getOne(queryWrapper);
PointsExchangeSummaryVo result = new PointsExchangeSummaryVo();
result.setCount(summary.getCount());
result.setTotalAmount(NumberUtil.nullToZero(summary.getTotalAmount()));
return result;
}
@Override
public void authCancel() {
UpdateChain.of(PointsExchangeRecord.class)
.set(PointsExchangeRecord::getStatus, PointsExchangeStatusEnum.CANCEL.value())
.set(PointsExchangeRecord::getCancelOrRefundTime, new Date())
.set(PointsExchangeRecord::getCancelOrRefundReason, "超时未支付,系统自动取消订单")
.eq(PointsExchangeRecord::getStatus, PointsExchangeStatusEnum.UNPAID.value())
.ge("TIMESTAMPDIFF(MINUTE, create_time, NOW())", 5);
}
}

View File

@ -0,0 +1,50 @@
package com.czg.service.account.service.impl;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import com.czg.account.dto.points.PointsGoodsSettingDTO;
import com.czg.account.entity.PointsGoodsSetting;
import com.czg.account.service.PointsGoodsSettingService;
import com.czg.enums.DeleteEnum;
import com.czg.sa.StpKit;
import com.czg.service.account.mapper.PointsGoodsSettingMapper;
import com.czg.utils.PageUtil;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
* 积分商品设置
*
* @author Tankaikai tankaikai@aliyun.com
* @since 1.0 2025-02-25
*/
@Service
public class PointsGoodsSettingServiceImpl extends ServiceImpl<PointsGoodsSettingMapper, PointsGoodsSetting> implements PointsGoodsSettingService {
private QueryWrapper buildQueryWrapper(PointsGoodsSettingDTO param) {
QueryWrapper queryWrapper = PageUtil.buildSortQueryWrapper();
if (StrUtil.isNotEmpty(param.getGoodsName())) {
queryWrapper.like(PointsGoodsSetting::getGoodsName, param.getGoodsName());
}
if (StrUtil.isNotEmpty(param.getGoodsCategory())) {
queryWrapper.eq(PointsGoodsSetting::getGoodsCategory, param.getGoodsCategory());
}
if (ObjUtil.isNotNull(param.getStatus())) {
queryWrapper.eq(PointsGoodsSetting::getStatus, param.getStatus());
}
Long shopId = StpKit.USER.getShopId(0L);
queryWrapper.eq(PointsGoodsSetting::getDelFlag, DeleteEnum.NORMAL.value());
queryWrapper.eq(PointsGoodsSetting::getShopId, shopId);
queryWrapper.orderBy(PointsGoodsSetting::getSort, false);
queryWrapper.orderBy(PointsGoodsSetting::getId, false);
return queryWrapper;
}
@Override
public Page<PointsGoodsSettingDTO> getPointsGoodsSettingPage(PointsGoodsSettingDTO param) {
QueryWrapper queryWrapper = buildQueryWrapper(param);
return super.pageAs(PageUtil.buildPage(), queryWrapper, PointsGoodsSettingDTO.class);
}
}

View File

@ -0,0 +1,6 @@
<?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.czg.service.account.mapper.MemberPointsLogMapper">
</mapper>

View File

@ -1,7 +1,6 @@
<?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">
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.czg.service.account.mapper.PointsBasicSettingMapper">
</mapper>
</mapper>

View File

@ -0,0 +1,6 @@
<?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.czg.service.account.mapper.PointsExchangeRecordMapper">
</mapper>

View File

@ -0,0 +1,6 @@
<?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.czg.service.account.mapper.PointsGoodsSettingMapper">
</mapper>