新增商品汇总信息,订单

This commit is contained in:
zhangji committed 2025-08-12 13:44:03 +08:00
1 parent 5c62b26dc3
commit 6cfa240cdb
52 files changed
+2571 -19

No files matched your search

@@ -0,0 +1,25 @@
package cn.iocoder.yudao.module.product.api.device;
import cn.iocoder.yudao.module.product.api.device.dto.DeviceRespDto;
import java.util.Collection;
import java.util.List;
/**
* 贩卖机信息 API 接口
*
* @author owen
*/
public interface DeviceApi {
/**
* 根据设备编号,获得设备信息
* @param deviceId
* @return
*/
DeviceRespDto getDeviceByDeviceId(String deviceId);
List<DeviceRespDto> getDeviceByDeviceIds(List<String> deviceIds);
int updateDeviceLastChannel(String deviceId, String cabinetChannel);
}
@@ -0,0 +1,39 @@
package cn.iocoder.yudao.module.product.api.device.dto;
import lombok.Data;
/**
* 贩卖机信息 DO
*
* @author 小如之家
*/
@Data
public class DeviceRespDto{
/**
* 表编号,自增
*/
private Long id;
/**
* 设备名称
*/
private String name;
/**
* 设备IMEI
*/
private String deviceId;
/**
* 访问商品地址
*/
private String productUrl;
/**
* 文件地址
*/
private String fileUrl;
/**
* 当前使用的货道或柜门编号
*/
private String cabinetChannel;
}
@@ -1,10 +1,13 @@
package cn.iocoder.yudao.module.product.api.sku;
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuStockReqDTO;
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
/**
* 商品 SKU API 接口
@@ -45,4 +48,7 @@ public interface ProductSkuApi {
*/
void updateSkuStock(ProductSkuUpdateStockReqDTO updateStockReqDTO);
List<ProductSkuStockReqDTO> getSkuStockSum(List<String> barCodes);
List<ProductSkuRespDTO> getSkuListByBarCode(Collection<String> barCodes);
}
@@ -0,0 +1,37 @@
package cn.iocoder.yudao.module.product.api.sku.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotNull;
import java.util.List;
/**
* 商品 SKU 库存合计 Request DTO
*
* @author LeeYan9
* @since 2022-08-26
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ProductSkuStockReqDTO {
/**
* SKU 的条形码
*/
private String barCode;
/**
* 库存
*/
private Integer stock;
/**
* 销量
*/
private Integer salesCount;
}
@@ -55,5 +55,9 @@ public interface ErrorCodeConstants {
// ========== 贩卖机信息 1-008-009-000 ==========
ErrorCode DEVICE_NOT_EXISTS = new ErrorCode(1_008_009_000, "贩卖机信息不存在");
ErrorCode DEVICE_NOT_EXISTS_INFO = new ErrorCode(1_008_009_001, "贩卖机【{}】不存在");
// ========== 商品汇总 1-008-010-000 ==========
ErrorCode SUMMARY_NOT_EXISTS = new ErrorCode(1_008_010_000, "商品汇总不存在");
}
@@ -0,0 +1,41 @@
package cn.iocoder.yudao.module.product.api.device;
import cn.iocoder.yudao.module.product.api.category.ProductCategoryApi;
import cn.iocoder.yudao.module.product.api.device.dto.DeviceRespDto;
import cn.iocoder.yudao.module.product.service.category.ProductCategoryService;
import cn.iocoder.yudao.module.product.service.device.DeviceService;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.Collection;
import java.util.List;
/**
* 商品分类 API 接口实现类
*
* @author owen
*/
@Service
@Validated
public class DeviceApiImpl implements DeviceApi {
@Resource
private DeviceService deviceService;
@Override
public DeviceRespDto getDeviceByDeviceId(String deviceId) {
return deviceService.getDeviceByDeviceId(deviceId);
}
@Override
public List<DeviceRespDto> getDeviceByDeviceIds(List<String> deviceIds) {
return deviceService.getDeviceByDeviceIds(deviceIds);
}
@Override
public int updateDeviceLastChannel(String deviceId, String cabinetChannel) {
return deviceService.updateDeviceLastChannel(deviceId, cabinetChannel);
}
}
@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.product.api.sku;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuStockReqDTO;
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO;
import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
import cn.iocoder.yudao.module.product.service.sku.ProductSkuService;
@@ -10,6 +11,7 @@ import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
@@ -48,4 +50,14 @@ public class ProductSkuApiImpl implements ProductSkuApi {
productSkuService.updateSkuStock(updateStockReqDTO);
}
@Override
public List<ProductSkuStockReqDTO> getSkuStockSum(List<String> barCodes) {
return productSkuService.getSkuStockSum(barCodes);
}
@Override
public List<ProductSkuRespDTO> getSkuListByBarCode(Collection<String> barCodes) {
return productSkuService.getSkuListByBarCode(barCodes);
}
}
@@ -31,4 +31,7 @@ public class DevicePageReqVO extends PageParam {
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
@Schema(description = "当前使用的货道或柜门编号")
private String cabinetChannel;
}
@@ -36,4 +36,9 @@ public class DeviceRespVO {
@ExcelProperty("创建时间")
private LocalDateTime createTime;
@Schema(description = "当前使用的货道或柜门编号", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("当前使用的货道或柜门编号")
private String cabinetChannel;
}
@@ -26,4 +26,8 @@ public class DeviceSaveReqVO {
@Schema(description = "文件地址", example = "https://www.iocoder.cn")
private String fileUrl;
@Schema(description = "当前使用的货道或柜门编号", requiredMode = Schema.RequiredMode.REQUIRED)
@NotEmpty(message = "当前使用的货道或柜门编号不能为空")
private String cabinetChannel;
}
@@ -0,0 +1,5 @@
### 获得商品汇总分页-库存求和
GET {{baseUrl}}/product/summary/sumPage?pageNo=1&pageSize=10
Authorization: Bearer {{token}}
tenant-id: {{adminTenantId}}
@@ -0,0 +1,103 @@
package cn.iocoder.yudao.module.product.controller.admin.summary;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.security.access.prepost.PreAuthorize;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Operation;
import javax.validation.constraints.*;
import javax.validation.*;
import javax.servlet.http.*;
import java.util.*;
import java.io.IOException;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.*;
import cn.iocoder.yudao.module.product.controller.admin.summary.vo.*;
import cn.iocoder.yudao.module.product.dal.dataobject.summary.SummaryDO;
import cn.iocoder.yudao.module.product.service.summary.SummaryService;
@Tag(name = "管理后台 - 商品汇总")
@RestController
@RequestMapping("/product/summary")
@Validated
public class SummaryController {
@Resource
private SummaryService summaryService;
@PostMapping("/create")
@Operation(summary = "创建商品汇总")
@PreAuthorize("@ss.hasPermission('product:summary:create')")
public CommonResult<Long> createSummary(@Valid @RequestBody SummarySaveReqVO createReqVO) {
return success(summaryService.createSummary(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新商品汇总")
@PreAuthorize("@ss.hasPermission('product:summary:update')")
public CommonResult<Boolean> updateSummary(@Valid @RequestBody SummarySaveReqVO updateReqVO) {
summaryService.updateSummary(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除商品汇总")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('product:summary:delete')")
public CommonResult<Boolean> deleteSummary(@RequestParam("id") Long id) {
summaryService.deleteSummary(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得商品汇总")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('product:summary:query')")
public CommonResult<SummaryRespVO> getSummary(@RequestParam("id") Long id) {
SummaryDO summary = summaryService.getSummary(id);
return success(BeanUtils.toBean(summary, SummaryRespVO.class));
}
@GetMapping("/page")
@Operation(summary = "获得商品汇总分页")
@PreAuthorize("@ss.hasPermission('product:summary:query')")
public CommonResult<PageResult<SummaryRespVO>> getSummaryPage(@Valid SummaryPageReqVO pageReqVO) {
PageResult<SummaryDO> pageResult = summaryService.getSummaryPage(pageReqVO);
return success(BeanUtils.toBean(pageResult, SummaryRespVO.class));
}
@GetMapping("/sumPage")
@Operation(summary = "获得商品汇总分页-库存求和")
@PreAuthorize("@ss.hasPermission('product:summary:query')")
public CommonResult<PageResult<SummarySumRespVO>> getSummarySumPage(@Valid SummaryPageReqVO pageReqVO) {
PageResult<SummarySumRespVO> pageResult = summaryService.getSummarySumPage(pageReqVO);
return success(pageResult);
}
@GetMapping("/export-excel")
@Operation(summary = "导出商品汇总 Excel")
@PreAuthorize("@ss.hasPermission('product:summary:export')")
@ApiAccessLog(operateType = EXPORT)
public void exportSummaryExcel(@Valid SummaryPageReqVO pageReqVO,
HttpServletResponse response) throws IOException {
pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
List<SummaryDO> list = summaryService.getSummaryPage(pageReqVO).getList();
// 导出 Excel
ExcelUtils.write(response, "商品汇总.xls", "数据", SummaryRespVO.class,
BeanUtils.toBean(list, SummaryRespVO.class));
}
}
@@ -0,0 +1,64 @@
package cn.iocoder.yudao.module.product.controller.admin.summary.vo;
import lombok.*;
import java.util.*;
import io.swagger.v3.oas.annotations.media.Schema;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 商品汇总分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class SummaryPageReqVO extends PageParam {
@Schema(description = "商品名称", example = "芋艿")
private String name;
@Schema(description = "设备IMEI", example = "9908")
private String deviceId;
@Schema(description = "SKU 的条形码")
private String barCode;
@Schema(description = "关键字")
private String keyword;
@Schema(description = "商品简介")
private String introduction;
@Schema(description = "商品详情", example = "你猜")
private String description;
@Schema(description = "商品分类编号", example = "20893")
private Long categoryId;
@Schema(description = "商品品牌编号", example = "29100")
private Integer brandId;
@Schema(description = "商品封面图", example = "https://www.iocoder.cn")
private String picUrl;
@Schema(description = "商品轮播图地址数组,以逗号分隔 最多上传15张")
private String sliderPicUrls;
@Schema(description = "排序字段")
private Integer sort;
@Schema(description = "商品价格,单位使用:分", example = "11271")
private Integer price;
@Schema(description = "市场价,单位使用:分", example = "27021")
private Integer marketPrice;
@Schema(description = "成本价,单位: 分", example = "17204")
private Integer costPrice;
@Schema(description = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
}
@@ -0,0 +1,79 @@
package cn.iocoder.yudao.module.product.controller.admin.summary.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.util.*;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import com.alibaba.excel.annotation.*;
@Schema(description = "管理后台 - 商品汇总 Response VO")
@Data
@ExcelIgnoreUnannotated
public class SummaryRespVO {
@Schema(description = "商品编号,自增", requiredMode = Schema.RequiredMode.REQUIRED, example = "14448")
@ExcelProperty("商品编号,自增")
private Long id;
@Schema(description = "商品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
@ExcelProperty("商品名称")
private String name;
@Schema(description = "设备IMEI", requiredMode = Schema.RequiredMode.REQUIRED, example = "9908")
@ExcelProperty("设备IMEI")
private String deviceId;
@Schema(description = "SKU 的条形码", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("SKU 的条形码")
private String barCode;
@Schema(description = "关键字")
@ExcelProperty("关键字")
private String keyword;
@Schema(description = "商品简介")
@ExcelProperty("商品简介")
private String introduction;
@Schema(description = "商品详情", example = "你猜")
@ExcelProperty("商品详情")
private String description;
@Schema(description = "商品分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20893")
@ExcelProperty("商品分类编号")
private Long categoryId;
@Schema(description = "商品品牌编号", example = "29100")
@ExcelProperty("商品品牌编号")
private Integer brandId;
@Schema(description = "商品封面图", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn")
@ExcelProperty("商品封面图")
private String picUrl;
@Schema(description = "商品轮播图地址数组,以逗号分隔 最多上传15张")
@ExcelProperty("商品轮播图地址数组,以逗号分隔 最多上传15张")
private String sliderPicUrls;
@Schema(description = "排序字段", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("排序字段")
private Integer sort;
@Schema(description = "商品价格,单位使用:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "11271")
@ExcelProperty("商品价格,单位使用:分")
private Integer price;
@Schema(description = "市场价,单位使用:分", example = "27021")
@ExcelProperty("市场价,单位使用:分")
private Integer marketPrice;
@Schema(description = "成本价,单位: 分", requiredMode = Schema.RequiredMode.REQUIRED, example = "17204")
@ExcelProperty("成本价,单位: 分")
private Integer costPrice;
@Schema(description = "创建时间")
@ExcelProperty("创建时间")
private LocalDateTime createTime;
}
@@ -0,0 +1,65 @@
package cn.iocoder.yudao.module.product.controller.admin.summary.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.util.*;
import javax.validation.constraints.*;
@Schema(description = "管理后台 - 商品汇总新增/修改 Request VO")
@Data
public class SummarySaveReqVO {
@Schema(description = "商品编号,自增", requiredMode = Schema.RequiredMode.REQUIRED, example = "14448")
private Long id;
@Schema(description = "商品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
@NotEmpty(message = "商品名称不能为空")
private String name;
@Schema(description = "设备IMEI", requiredMode = Schema.RequiredMode.REQUIRED, example = "9908")
@NotEmpty(message = "设备IMEI不能为空")
private String deviceId;
@Schema(description = "SKU 的条形码", requiredMode = Schema.RequiredMode.REQUIRED)
@NotEmpty(message = "SKU 的条形码不能为空")
private String barCode;
@Schema(description = "关键字")
private String keyword;
@Schema(description = "商品简介")
private String introduction;
@Schema(description = "商品详情", example = "你猜")
private String description;
@Schema(description = "商品分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20893")
@NotNull(message = "商品分类编号不能为空")
private Long categoryId;
@Schema(description = "商品品牌编号", example = "29100")
private Integer brandId;
@Schema(description = "商品封面图", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn")
@NotEmpty(message = "商品封面图不能为空")
private String picUrl;
@Schema(description = "商品轮播图地址数组,以逗号分隔 最多上传15张")
private String sliderPicUrls;
@Schema(description = "排序字段", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "排序字段不能为空")
private Integer sort;
@Schema(description = "商品价格,单位使用:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "11271")
@NotNull(message = "商品价格,单位使用:分不能为空")
private Integer price;
@Schema(description = "市场价,单位使用:分", example = "27021")
private Integer marketPrice;
@Schema(description = "成本价,单位: 分", requiredMode = Schema.RequiredMode.REQUIRED, example = "17204")
@NotNull(message = "成本价,单位: 分不能为空")
private Integer costPrice;
}
@@ -0,0 +1,87 @@
package cn.iocoder.yudao.module.product.controller.admin.summary.vo;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 商品汇总 Response VO")
@Data
@ExcelIgnoreUnannotated
public class SummarySumRespVO {
@Schema(description = "商品编号,自增", requiredMode = Schema.RequiredMode.REQUIRED, example = "14448")
@ExcelProperty("商品编号,自增")
private Long id;
@Schema(description = "商品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
@ExcelProperty("商品名称")
private String name;
@Schema(description = "设备IMEI", requiredMode = Schema.RequiredMode.REQUIRED, example = "9908")
@ExcelProperty("设备IMEI")
private String deviceId;
@Schema(description = "SKU 的条形码", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("SKU 的条形码")
private String barCode;
@Schema(description = "关键字")
@ExcelProperty("关键字")
private String keyword;
@Schema(description = "商品简介")
@ExcelProperty("商品简介")
private String introduction;
@Schema(description = "商品详情", example = "你猜")
@ExcelProperty("商品详情")
private String description;
@Schema(description = "商品分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20893")
@ExcelProperty("商品分类编号")
private Long categoryId;
@Schema(description = "商品品牌编号", example = "29100")
@ExcelProperty("商品品牌编号")
private Integer brandId;
@Schema(description = "商品封面图", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn")
@ExcelProperty("商品封面图")
private String picUrl;
@Schema(description = "商品轮播图地址数组,以逗号分隔 最多上传15张")
@ExcelProperty("商品轮播图地址数组,以逗号分隔 最多上传15张")
private String sliderPicUrls;
@Schema(description = "排序字段", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("排序字段")
private Integer sort;
@Schema(description = "商品价格,单位使用:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "11271")
@ExcelProperty("商品价格,单位使用:分")
private Integer price;
@Schema(description = "市场价,单位使用:分", example = "27021")
@ExcelProperty("市场价,单位使用:分")
private Integer marketPrice;
@Schema(description = "成本价,单位: 分", requiredMode = Schema.RequiredMode.REQUIRED, example = "17204")
@ExcelProperty("成本价,单位: 分")
private Integer costPrice;
@Schema(description = "创建时间")
@ExcelProperty("创建时间")
private LocalDateTime createTime;
@Schema(description = "库存", requiredMode = Schema.RequiredMode.REQUIRED, example = "666")
@ExcelProperty("库存")
private Integer stock;
@Schema(description = "商品销量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@ExcelProperty("商品销量")
private Integer salesCount;
}
@@ -44,4 +44,9 @@ public class DeviceDO extends BaseDO {
*/
private String fileUrl;
/**
* 当前使用的货道或柜门编号
*/
private String cabinetChannel;
}
@@ -0,0 +1,87 @@
package cn.iocoder.yudao.module.product.dal.dataobject.summary;
import lombok.*;
import java.util.*;
import java.time.LocalDateTime;
import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.*;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
/**
* 商品汇总 DO
*
* @author 小如之家
*/
@TableName("product_summary")
@KeySequence("product_summary_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SummaryDO extends BaseDO {
/**
* 商品编号,自增
*/
@TableId
private Long id;
/**
* 设备IMEI
*/
private String deviceId;
/**
* SKU 的条形码
*/
private String barCode;
/**
* 商品名称
*/
private String name;
/**
* 关键字
*/
private String keyword;
/**
* 商品简介
*/
private String introduction;
/**
* 商品详情
*/
private String description;
/**
* 商品分类编号
*/
private Long categoryId;
/**
* 商品品牌编号
*/
private Integer brandId;
/**
* 商品封面图
*/
private String picUrl;
/**
* 商品轮播图地址数组,以逗号分隔最多上传15张
*/
private String sliderPicUrls;
/**
* 排序字段
*/
private Integer sort;
/**
* 商品价格,单位使用:分
*/
private Integer price;
/**
* 市场价,单位使用:分
*/
private Integer marketPrice;
/**
* 成本价,单位: 分
*/
private Integer costPrice;
}
@@ -5,6 +5,7 @@ import java.util.*;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.product.api.device.dto.DeviceRespDto;
import cn.iocoder.yudao.module.product.dal.dataobject.device.DeviceDO;
import org.apache.ibatis.annotations.Mapper;
import cn.iocoder.yudao.module.product.controller.admin.device.vo.*;
@@ -24,7 +25,20 @@ public interface DeviceMapper extends BaseMapperX<DeviceDO> {
.eqIfPresent(DeviceDO::getProductUrl, reqVO.getProductUrl())
.eqIfPresent(DeviceDO::getFileUrl, reqVO.getFileUrl())
.betweenIfPresent(DeviceDO::getCreateTime, reqVO.getCreateTime())
.eqIfPresent(DeviceDO::getCabinetChannel, reqVO.getCabinetChannel())
.orderByDesc(DeviceDO::getId));
}
default DeviceDO getDeviceByDeviceId(String deviceId){
return selectOne(DeviceDO::getDeviceId, deviceId);
}
default List<DeviceDO> getDeviceByDeviceIds(List<String> deviceIds){
return selectList(DeviceDO::getDeviceId, deviceIds);
}
default int updateDeviceLastChannel(String deviceId, String cabinetChannel){
return update(new DeviceDO().setCabinetChannel(cabinetChannel), new LambdaQueryWrapperX<DeviceDO>()
.eq(DeviceDO::getDeviceId, deviceId));
}
}
@@ -1,11 +1,19 @@
package cn.iocoder.yudao.module.product.dal.mysql.sku;
import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuStockReqDTO;
import cn.iocoder.yudao.module.product.controller.admin.summary.vo.SummaryRespVO;
import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.Collection;
import java.util.List;
@@ -58,4 +66,19 @@ public interface ProductSkuMapper extends BaseMapperX<ProductSkuDO> {
return update(null, updateWrapper);
}
// @Select("select bar_code as barCode,sum(stock) as stock,sum(sales_count) as salesCount from product_sku where bar_code in #{barCodes}")
@Select("<script>" +
"SELECT bar_code AS barCode, SUM(stock) AS stock, SUM(sales_count) AS salesCount " +
"FROM product_sku " +
"WHERE bar_code IN " +
"<foreach item='barCode' collection='barCodes' open='(' separator=',' close=')'>" +
"#{barCode}" +
"</foreach>" +
"GROUP BY bar_code" +
"</script>")
List<ProductSkuStockReqDTO> getSkuStockSum(@Param("barCodes")List<String> barCodes);
default List<ProductSkuDO> getSkuListByBarCode(Collection<String> barCodes){
return selectList(ProductSkuDO::getBarCode, barCodes);
}
}
@@ -0,0 +1,40 @@
package cn.iocoder.yudao.module.product.dal.mysql.summary;
import java.util.*;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.product.dal.dataobject.summary.SummaryDO;
import org.apache.ibatis.annotations.Mapper;
import cn.iocoder.yudao.module.product.controller.admin.summary.vo.*;
/**
* 商品汇总 Mapper
*
* @author 小如之家
*/
@Mapper
public interface SummaryMapper extends BaseMapperX<SummaryDO> {
default PageResult<SummaryDO> selectPage(SummaryPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<SummaryDO>()
.likeIfPresent(SummaryDO::getName, reqVO.getName())
.eqIfPresent(SummaryDO::getDeviceId, reqVO.getDeviceId())
.eqIfPresent(SummaryDO::getBarCode, reqVO.getBarCode())
.eqIfPresent(SummaryDO::getKeyword, reqVO.getKeyword())
.eqIfPresent(SummaryDO::getIntroduction, reqVO.getIntroduction())
.eqIfPresent(SummaryDO::getDescription, reqVO.getDescription())
.eqIfPresent(SummaryDO::getCategoryId, reqVO.getCategoryId())
.eqIfPresent(SummaryDO::getBrandId, reqVO.getBrandId())
.eqIfPresent(SummaryDO::getPicUrl, reqVO.getPicUrl())
.eqIfPresent(SummaryDO::getSliderPicUrls, reqVO.getSliderPicUrls())
.eqIfPresent(SummaryDO::getSort, reqVO.getSort())
.eqIfPresent(SummaryDO::getPrice, reqVO.getPrice())
.eqIfPresent(SummaryDO::getMarketPrice, reqVO.getMarketPrice())
.eqIfPresent(SummaryDO::getCostPrice, reqVO.getCostPrice())
.betweenIfPresent(SummaryDO::getCreateTime, reqVO.getCreateTime())
.orderByDesc(SummaryDO::getId));
}
}
@@ -1,6 +1,9 @@
package cn.iocoder.yudao.module.product.framework.web.config;
import cn.iocoder.yudao.framework.datapermission.core.rule.dept.DeptDataPermissionRuleCustomizer;
import cn.iocoder.yudao.framework.swagger.config.YudaoSwaggerAutoConfiguration;
import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
import org.springdoc.core.GroupedOpenApi;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -21,4 +24,15 @@ public class ProductWebConfiguration {
return YudaoSwaggerAutoConfiguration.buildGroupedOpenApi("product");
}
@Bean
public DeptDataPermissionRuleCustomizer productDeptDataPermissionRuleCustomizer() {
return rule -> {
// dept
rule.addDeptColumn(AdminUserDO.class);
rule.addDeptColumn(DeptDO.class, "id");
// user
rule.addUserColumn(AdminUserDO.class, "id");
};
}
}
@@ -2,6 +2,8 @@ package cn.iocoder.yudao.module.product.service.device;
import java.util.*;
import javax.validation.*;
import cn.iocoder.yudao.module.product.api.device.dto.DeviceRespDto;
import cn.iocoder.yudao.module.product.controller.admin.device.vo.*;
import cn.iocoder.yudao.module.product.dal.dataobject.device.DeviceDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
@@ -52,4 +54,9 @@ public interface DeviceService {
*/
PageResult<DeviceDO> getDevicePage(DevicePageReqVO pageReqVO);
DeviceRespDto getDeviceByDeviceId(String deviceId);
List<DeviceRespDto> getDeviceByDeviceIds(List<String> deviceIds);
int updateDeviceLastChannel(String deviceId, String cabinetChannel);
}
@@ -1,5 +1,7 @@
package cn.iocoder.yudao.module.product.service.device;
import cn.iocoder.yudao.module.product.api.device.dto.DeviceRespDto;
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
import cn.iocoder.yudao.module.product.service.device.bo.DispenseRequestBo;
import cn.iocoder.yudao.module.product.service.device.bo.DispenseResponseBo;
import lombok.extern.slf4j.Slf4j;
@@ -42,7 +44,7 @@ public class DeviceServiceImpl implements DeviceService {
private String token;
//API_URL
@Value("${xr.cabinet.api_url}")
@Value("${xr.cabinet.api_device_url}")
private String api_url;
@Override
@@ -161,4 +163,21 @@ public class DeviceServiceImpl implements DeviceService {
return deviceMapper.selectPage(pageReqVO);
}
@Override
public DeviceRespDto getDeviceByDeviceId(String deviceId) {
DeviceDO device = deviceMapper.getDeviceByDeviceId(deviceId);
return BeanUtils.toBean(device, DeviceRespDto.class);
}
@Override
public List<DeviceRespDto> getDeviceByDeviceIds(List<String> deviceIds) {
List<DeviceDO> devices = deviceMapper.getDeviceByDeviceIds(deviceIds);
return BeanUtils.toBean(devices, DeviceRespDto.class);
}
@Override
public int updateDeviceLastChannel(String deviceId, String cabinetChannel) {
return deviceMapper.updateDeviceLastChannel(deviceId, cabinetChannel);
}
}
@@ -1,5 +1,7 @@
package cn.iocoder.yudao.module.product.service.sku;
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuStockReqDTO;
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO;
import cn.iocoder.yudao.module.product.controller.admin.spu.vo.ProductSkuSaveReqVO;
import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
@@ -110,4 +112,7 @@ public interface ProductSkuService {
*/
int updateSkuPropertyValue(Long propertyValueId, String propertyValueName);
List<ProductSkuStockReqDTO> getSkuStockSum(List<String> barCodes);
List<ProductSkuRespDTO> getSkuListByBarCode(Collection<String> barCodes);
}
@@ -5,6 +5,8 @@ import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuStockReqDTO;
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO;
import cn.iocoder.yudao.module.product.controller.admin.spu.vo.ProductSkuSaveReqVO;
import cn.iocoder.yudao.module.product.convert.sku.ProductSkuConvert;
@@ -209,6 +211,17 @@ public class ProductSkuServiceImpl implements ProductSkuService {
return updateSkus.size();
}
@Override
public List<ProductSkuStockReqDTO> getSkuStockSum(List<String> barCodes) {
return productSkuMapper.getSkuStockSum(barCodes);
}
@Override
public List<ProductSkuRespDTO> getSkuListByBarCode(Collection<String> barCodes) {
List<ProductSkuDO> productSkuDOS = productSkuMapper.getSkuListByBarCode(barCodes);
return BeanUtils.toBean(productSkuDOS, ProductSkuRespDTO.class);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateSkuList(Long spuId, List<ProductSkuSaveReqVO> skus) {
@@ -0,0 +1,56 @@
package cn.iocoder.yudao.module.product.service.summary;
import java.util.*;
import javax.validation.*;
import cn.iocoder.yudao.module.product.controller.admin.summary.vo.*;
import cn.iocoder.yudao.module.product.dal.dataobject.summary.SummaryDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
/**
* 商品汇总 Service 接口
*
* @author 小如之家
*/
public interface SummaryService {
/**
* 创建商品汇总
*
* @param createReqVO 创建信息
* @return 编号
*/
Long createSummary(@Valid SummarySaveReqVO createReqVO);
/**
* 更新商品汇总
*
* @param updateReqVO 更新信息
*/
void updateSummary(@Valid SummarySaveReqVO updateReqVO);
/**
* 删除商品汇总
*
* @param id 编号
*/
void deleteSummary(Long id);
/**
* 获得商品汇总
*
* @param id 编号
* @return 商品汇总
*/
SummaryDO getSummary(Long id);
/**
* 获得商品汇总分页
*
* @param pageReqVO 分页查询
* @return 商品汇总分页
*/
PageResult<SummaryDO> getSummaryPage(SummaryPageReqVO pageReqVO);
PageResult<SummarySumRespVO> getSummarySumPage(SummaryPageReqVO pageReqVO);
}
@@ -0,0 +1,113 @@
package cn.iocoder.yudao.module.product.service.summary;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuStockReqDTO;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import cn.iocoder.yudao.module.product.controller.admin.summary.vo.*;
import cn.iocoder.yudao.module.product.dal.dataobject.summary.SummaryDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.product.dal.mysql.summary.SummaryMapper;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.*;
/**
* 商品汇总 Service 实现类
*
* @author 小如之家
*/
@Service
@Validated
public class SummaryServiceImpl implements SummaryService {
@Resource
private SummaryMapper summaryMapper;
@Resource
private ProductSkuApi productSkuApi;
@Override
public Long createSummary(SummarySaveReqVO createReqVO) {
// 插入
SummaryDO summary = BeanUtils.toBean(createReqVO, SummaryDO.class);
summaryMapper.insert(summary);
// 返回
return summary.getId();
}
@Override
public void updateSummary(SummarySaveReqVO updateReqVO) {
// 校验存在
validateSummaryExists(updateReqVO.getId());
// 更新
SummaryDO updateObj = BeanUtils.toBean(updateReqVO, SummaryDO.class);
summaryMapper.updateById(updateObj);
}
@Override
public void deleteSummary(Long id) {
// 校验存在
validateSummaryExists(id);
// 删除
summaryMapper.deleteById(id);
}
private void validateSummaryExists(Long id) {
if (summaryMapper.selectById(id) == null) {
throw exception(SUMMARY_NOT_EXISTS);
}
}
@Override
public SummaryDO getSummary(Long id) {
return summaryMapper.selectById(id);
}
@Override
public PageResult<SummaryDO> getSummaryPage(SummaryPageReqVO pageReqVO) {
return summaryMapper.selectPage(pageReqVO);
}
@Override
public PageResult<SummarySumRespVO> getSummarySumPage(SummaryPageReqVO pageReqVO) {
PageResult<SummaryDO> pageResult = summaryMapper.selectPage(pageReqVO);
if (CollUtil.isEmpty(pageResult.getList())) {
return PageResult.empty();
}
// 获取所有不同的条形码
List<String> barCodes = CollUtil.distinct(convertList(pageResult.getList(), SummaryDO::getBarCode));
// 从商品SKU查询库存总和
List<ProductSkuStockReqDTO> productStockReqs = productSkuApi.getSkuStockSum(barCodes);
// 创建条形码到库存信息的映射,便于快速查找
Map<String, ProductSkuStockReqDTO> stockReqMap = CollectionUtils.convertMap(productStockReqs, ProductSkuStockReqDTO::getBarCode);
// 转换 SummaryDO 列表为 SummarySumRespVO 列表,并填充库存信息
List<SummarySumRespVO> list = CollectionUtils.convertList(pageResult.getList(), summary -> {
SummarySumRespVO respVO = BeanUtils.toBean(summary, SummarySumRespVO.class);
ProductSkuStockReqDTO stockReq = stockReqMap.get(summary.getBarCode());
if (stockReq != null) {
respVO.setStock(stockReq.getStock());
respVO.setSalesCount(stockReq.getSalesCount());
}
return respVO;
});
// 返回包含合并数据的分页结果
return new PageResult<>(list, pageResult.getTotal());
}
}
@@ -87,6 +87,13 @@ public class AppTradeOrderController {
return success(new AppTradeOrderCreateRespVO().setId(order.getId()).setPayOrderId(order.getPayOrderId()));
}
@PostMapping("/create-summary")
@Operation(summary = "创建汇总商品订单")
public CommonResult<AppTradeOrderCreateRespVO> createSummaryOrder(@Valid @RequestBody AppSummaryOrderSettlementReqVO createReqVO) {
TradeOrderDO order = tradeOrderUpdateService.createSummaryOrder(getLoginUserId(), createReqVO);
return success(new AppTradeOrderCreateRespVO().setId(order.getId()).setPayOrderId(order.getPayOrderId()));
}
@PostMapping("/create-giftvoucher")
@Operation(summary = "创建提货码订单")
public CommonResult<Long> createGiftVoucherOrder(@Valid @RequestBody AppGiftVoucherOrderSettlementReqVO createReqVO) {
@@ -0,0 +1,91 @@
package cn.iocoder.yudao.module.trade.controller.app.order.vo;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.framework.common.validation.Mobile;
import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.Valid;
import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.List;
@Schema(description = "用户 App - 交易订单结算 Request VO")
@Data
@Valid
public class AppSummaryOrderSettlementReqVO {
@Schema(description = "商品项数组", requiredMode = Schema.RequiredMode.REQUIRED)
@NotEmpty(message = "商品不能为空")
private List<Item> items;
@Schema(description = "优惠劵编号", example = "1024")
private Long couponId;
@Schema(description = "是否使用积分", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
@NotNull(message = "是否使用积分不能为空")
private Boolean pointStatus;
// ========== 配送相关相关字段 ==========
@Schema(description = "配送方式", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@InEnum(value = DeliveryTypeEnum.class, message = "配送方式不正确")
private Integer deliveryType;
@Schema(description = "收件地址编号", example = "1")
private Long addressId;
@Schema(description = "自提门店编号", example = "1088")
private Long pickUpStoreId;
@Schema(description = "收件人名称", example = "芋艿") // 选择门店自提时,该字段为联系人名
private String receiverName;
@Schema(description = "收件人手机", example = "15601691300") // 选择门店自提时,该字段为联系人手机
@Mobile(message = "收件人手机格式不正确")
private String receiverMobile;
@Schema(description = "备注", example = "这个是我的订单哟")
private String remark;
@AssertTrue(message = "配送方式不能为空")
@JsonIgnore
public boolean isDeliveryTypeNotNull() {
return deliveryType != null;
}
/* @AssertTrue(message = "提货商品每次只能购买一种规格")
@JsonIgnore
public boolean isValidActivityItems() {
// 校验订单项是否超出
return items.size() == 1;
}*/
@Data
@Schema(description = "用户 App - 商品项")
@Valid
public static class Item {
@Schema(description = "商品 SKU 条形码", example = "2048")
@NotNull(message = "商品 SKU 条形码不能为空")
private String barCode;
@Schema(description = "设备IMEI", example = "2048")
@NotNull(message = "设备IMEI不能为空")
private String deviceId;
@Schema(description = "购买数量", example = "1")
@Min(value = 1, message = "购买数量最小值为 {value}")
private Integer count;
@AssertTrue(message = "商品不正确")
@JsonIgnore
public boolean isValid() {
// 组合一:deviceId + skuId + count 使用商品 SKU
return deviceId!=null && barCode != null && count != null;
}
}
}
@@ -37,6 +37,8 @@ import cn.iocoder.yudao.module.trade.service.brokerage.bo.BrokerageAddReqBO;
import cn.iocoder.yudao.module.trade.service.order.bo.GiftVoucherCalculateRespBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateSummaryReqBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateSummaryRespBO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
@@ -60,8 +62,8 @@ public interface TradeOrderConvert {
@Mapping(target = "id", ignore = true),
@Mapping(source = "userId", target = "userId"),
@Mapping(source = "createReqVO.couponId", target = "couponId"),
@Mapping(target = "remark", ignore = true),
@Mapping(source = "createReqVO.remark", target = "userRemark"),
@Mapping(target = "remark", ignore = true),
@Mapping(source = "calculateRespBO.price.totalPrice", target = "totalPrice"),
@Mapping(source = "calculateRespBO.price.discountPrice", target = "discountPrice"),
@Mapping(source = "calculateRespBO.price.deliveryPrice", target = "deliveryPrice"),
@@ -72,6 +74,22 @@ public interface TradeOrderConvert {
})
TradeOrderDO convert(Long userId, AppTradeOrderCreateReqVO createReqVO, TradePriceCalculateRespBO calculateRespBO);
@Mappings({
@Mapping(target = "id", ignore = true),
@Mapping(source = "userId", target = "userId"),
@Mapping(source = "createReqVO.couponId", target = "couponId"),
@Mapping(source = "createReqVO.remark", target = "userRemark"),
@Mapping(target = "remark", ignore = true),
@Mapping(source = "calculateRespBO.price.totalPrice", target = "totalPrice"),
@Mapping(source = "calculateRespBO.price.discountPrice", target = "discountPrice"),
@Mapping(source = "calculateRespBO.price.deliveryPrice", target = "deliveryPrice"),
@Mapping(source = "calculateRespBO.price.couponPrice", target = "couponPrice"),
@Mapping(source = "calculateRespBO.price.pointPrice", target = "pointPrice"),
@Mapping(source = "calculateRespBO.price.vipPrice", target = "vipPrice"),
@Mapping(source = "calculateRespBO.price.payPrice", target = "payPrice")
})
TradeOrderDO convert(Long userId, AppSummaryOrderSettlementReqVO createReqVO, TradePriceCalculateSummaryRespBO calculateRespBO);
TradeOrderRespDTO convert(TradeOrderDO orderDO);
default List<TradeOrderItemDO> convertList(TradeOrderDO tradeOrderDO, TradePriceCalculateRespBO calculateRespBO) {
@@ -85,8 +103,21 @@ public interface TradeOrderConvert {
});
}
default List<TradeOrderItemDO> convertList(TradeOrderDO tradeOrderDO, TradePriceCalculateSummaryRespBO calculateRespBO) {
return CollectionUtils.convertList(calculateRespBO.getItems(), item -> {
TradeOrderItemDO orderItem = convert(item);
orderItem.setOrderId(tradeOrderDO.getId());
orderItem.setUserId(tradeOrderDO.getUserId());
orderItem.setAfterSaleStatus(TradeOrderItemAfterSaleStatusEnum.NONE.getStatus());
orderItem.setCommentStatus(false);
return orderItem;
});
}
TradeOrderItemDO convert(TradePriceCalculateRespBO.OrderItem item);
TradeOrderItemDO convert(TradePriceCalculateSummaryRespBO.OrderItem item);
default ProductSkuUpdateStockReqDTO convert(List<TradeOrderItemDO> list) {
List<ProductSkuUpdateStockReqDTO.Item> items = CollectionUtils.convertList(list, item ->
new ProductSkuUpdateStockReqDTO.Item().setId(item.getSkuId()).setIncrCount(item.getCount()));
@@ -242,6 +273,23 @@ public interface TradeOrderConvert {
return reqBO;
}
default TradePriceCalculateSummaryReqBO convert(Long userId, AppSummaryOrderSettlementReqVO createReqVO){
TradePriceCalculateSummaryReqBO reqBO = new TradePriceCalculateSummaryReqBO().setUserId(userId)
.setItems(new ArrayList<>(createReqVO.getItems().size()))
.setCouponId(createReqVO.getCouponId()).setPointStatus(createReqVO.getPointStatus())
// 物流信息
.setDeliveryType(createReqVO.getDeliveryType()).setAddressId(createReqVO.getAddressId())
.setPickUpStoreId(createReqVO.getPickUpStoreId());
for (AppSummaryOrderSettlementReqVO.Item item : createReqVO.getItems()) {
// 情况一:skuId + count
if (item.getBarCode() != null) {
reqBO.getItems().add(new TradePriceCalculateSummaryReqBO.Item().setBarCode(item.getBarCode()).setDeviceId(item.getDeviceId()).setCount(item.getCount())
.setSelected(true)); // true 的原因,下单一定选中
}
}
return reqBO;
}
default AppTradeOrderSettlementRespVO convert(TradePriceCalculateRespBO calculate, MemberAddressRespDTO address) {
AppTradeOrderSettlementRespVO respVO = convert0(calculate, address);
if (address != null) {
@@ -4,10 +4,7 @@ import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderDeliver
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderRemarkReqVO;
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderUpdateAddressReqVO;
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderUpdatePriceReqVO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppGiftVoucherOrderSettlementReqVO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderCreateReqVO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderSettlementReqVO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderSettlementRespVO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.*;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.item.AppTradeOrderItemCommentCreateReqVO;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
@@ -222,4 +219,6 @@ public interface TradeOrderUpdateService {
void updateOrderGiveCouponIds(Long userId, Long orderId, List<Long> giveCouponIds);
TradeOrderDO createGiftVoucherOrder(Long userId, AppGiftVoucherOrderSettlementReqVO createReqVO);
TradeOrderDO createSummaryOrder(Long userId, AppSummaryOrderSettlementReqVO createReqVO);
}
@@ -39,10 +39,7 @@ import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderDeliver
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderRemarkReqVO;
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderUpdateAddressReqVO;
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderUpdatePriceReqVO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppGiftVoucherOrderSettlementReqVO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderCreateReqVO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderSettlementReqVO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderSettlementRespVO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.*;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.item.AppTradeOrderItemCommentCreateReqVO;
import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert;
import cn.iocoder.yudao.module.trade.dal.dataobject.cart.CartDO;
@@ -68,6 +65,8 @@ import cn.iocoder.yudao.module.trade.service.order.handler.TradeOrderHandler;
import cn.iocoder.yudao.module.trade.service.price.TradePriceService;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateSummaryReqBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateSummaryRespBO;
import cn.iocoder.yudao.module.trade.service.price.calculator.TradePriceCalculatorHelper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.extern.slf4j.Slf4j;
@@ -1124,6 +1123,88 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
return order;
}
@Override
public TradeOrderDO createSummaryOrder(Long userId, AppSummaryOrderSettlementReqVO createReqVO) {
// 1.1 价格计算
TradePriceCalculateSummaryRespBO calculateRespBO = calculatePriceSummary(userId, createReqVO);
// 1.2 构建订单
TradeOrderDO order = buildTradeOrderSummary(userId, createReqVO, calculateRespBO);
List<TradeOrderItemDO> orderItems = buildTradeOrderItemsSummary(order, calculateRespBO);
// 2. 订单创建前的逻辑
tradeOrderHandlers.forEach(handler -> handler.beforeOrderCreate(order, orderItems));
// 3. 保存订单
tradeOrderMapper.insert(order);
orderItems.forEach(orderItem -> orderItem.setOrderId(order.getId()));
tradeOrderItemMapper.insertBatch(orderItems);
// 4. 订单创建后的逻辑
afterCreateTradeOrderSummary(order, orderItems, createReqVO);
return order;
}
private void afterCreateTradeOrderSummary(TradeOrderDO order, List<TradeOrderItemDO> orderItems, AppSummaryOrderSettlementReqVO createReqVO) {
// 1. 执行订单创建后置处理器
tradeOrderHandlers.forEach(handler -> handler.afterOrderCreate(order, orderItems));
// 2. 删除购物车商品
// Set<Long> cartIds = convertSet(createReqVO.getItems(), AppSummaryOrderSettlementReqVO.Item::getCartId);
// if (CollUtil.isNotEmpty(cartIds)) {
// cartService.deleteCart(order.getUserId(), cartIds);
// }
// 3. 生成预支付
// 特殊情况:积分兑换时,可能支付金额为零
// TODO 特殊需求,价格可以为0
if (order.getPayPrice() >= 0) {
createPayOrder(order, orderItems);
}
// 4. 插入订单日志
TradeOrderLogUtils.setOrderInfo(order.getId(), null, order.getStatus());
// TODO @LeeYan9: 是可以思考下, 订单的营销优惠记录, 应该记录在哪里, 微信讨论起来!
}
private List<TradeOrderItemDO> buildTradeOrderItemsSummary(TradeOrderDO order, TradePriceCalculateSummaryRespBO calculateRespBO) {
return TradeOrderConvert.INSTANCE.convertList(order, calculateRespBO);
}
private TradeOrderDO buildTradeOrderSummary(Long userId, AppSummaryOrderSettlementReqVO createReqVO, TradePriceCalculateSummaryRespBO calculateRespBO) {
TradeOrderDO order = TradeOrderConvert.INSTANCE.convert(userId, createReqVO, calculateRespBO);
order.setType(calculateRespBO.getType());
order.setNo(tradeNoRedisDAO.generate(TradeNoRedisDAO.TRADE_ORDER_NO_PREFIX));
order.setStatus(TradeOrderStatusEnum.UNPAID.getStatus());
order.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus());
order.setProductCount(getSumValue(calculateRespBO.getItems(), TradePriceCalculateSummaryRespBO.OrderItem::getCount, Integer::sum));
order.setUserIp(getClientIP()).setTerminal(getTerminal());
// 使用 + 赠送优惠券
order.setGiveCouponTemplateCounts(calculateRespBO.getGiveCouponTemplateCounts());
// 支付 + 退款信息
order.setAdjustPrice(0).setPayStatus(false);
order.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus()).setRefundPrice(0);
// 物流信息
order.setDeliveryType(createReqVO.getDeliveryType());
if (Objects.equals(createReqVO.getDeliveryType(), DeliveryTypeEnum.EXPRESS.getType())) {
MemberAddressRespDTO address = addressApi.getAddress(createReqVO.getAddressId(), userId);
Assert.notNull(address, "地址({}) 不能为空", createReqVO.getAddressId()); // 价格计算时,已经计算
order.setReceiverName(address.getName()).setReceiverMobile(address.getMobile())
.setReceiverAreaId(address.getAreaId()).setReceiverDetailAddress(address.getDetailAddress());
} else if (Objects.equals(createReqVO.getDeliveryType(), DeliveryTypeEnum.PICK_UP.getType())) {
order.setReceiverName(createReqVO.getReceiverName()).setReceiverMobile(createReqVO.getReceiverMobile());
order.setPickUpVerifyCode(RandomUtil.randomNumbers(8)); // 随机一个核销码,长度为 8 位
}
return order;
}
private TradePriceCalculateSummaryRespBO calculatePriceSummary(Long userId, AppSummaryOrderSettlementReqVO createReqVO) {
TradePriceCalculateSummaryReqBO calculateReqBO = TradeOrderConvert.INSTANCE.convert(userId, createReqVO);
calculateReqBO.getItems().forEach(item -> Assert.isTrue(item.getSelected(), // 防御性编程,保证都是选中的
"商品({}) 未设置为选中", item.getBarCode()));
return tradePriceService.calculateSummaryOrderPrice(calculateReqBO);
}
/**
* 创建单个订单的评论
*
@@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.trade.service.order.handler;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.module.product.api.device.DeviceApi;
import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
@@ -58,6 +59,9 @@ public class TradeDeviceOrderHandler implements TradeOrderHandler {
@Resource
private TradeOrderMapper tradeOrderMapper;
@Resource
private DeviceApi deviceApi;
// TOKEN
@Value("${xr.cabinet.token}")
private String token;
@@ -113,9 +117,9 @@ public class TradeDeviceOrderHandler implements TradeOrderHandler {
List<ProductSkuRespDTO> skuList = productSkuApi.getSkuList(skuIds);
//判断是不是有不同通道类型的订单
boolean isSingleCabinetType = skuList.stream()
.map(ProductSkuRespDTO::getCabinetType) //获取每个商品的Creator
.distinct() //去重Creator
.count() == 1; // 判断是否只有一个唯一的创建人
.map(ProductSkuRespDTO::getCabinetType) //获取每个商品的通道类型
.distinct() //去重
.count() == 1; // 判断是否只有一个唯一的通道类型
if (isSingleCabinetType) {
DispenseRequestBo request = new DispenseRequestBo();
request.setDeviceId(user.getDeviceId());
@@ -128,6 +132,10 @@ public class TradeDeviceOrderHandler implements TradeOrderHandler {
if (i != orderItems.size() - 1) {
message.append(",");
}
//记录最后一次出货的货道
if (i == orderItems.size() - 1) {
int d = deviceApi.updateDeviceLastChannel(user.getDeviceId(), sku.getCabinetChannel());
}
}
request.setMessage(message.toString());
// 设置请求头
@@ -188,11 +196,11 @@ public class TradeDeviceOrderHandler implements TradeOrderHandler {
}
public static void main(String[] args) {
String API_URL = "http://116.148.200.224:9527/ots/multiDispense";
String API_URL = "http://9527.asset-cloud.com.cn/ots/multiDispense";
String TOKEN = "Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhcHAxIiwiaWF0IjoxNzQ5MTMzNzE5LCJleHAiOjIwNjAxNzM3MTl9.u2VUKxPxE2H8xldabMVRYSJKf6LN-XZfbcfyj1T7czI";
// 创建请求对象
DispenseRequestBo request = new DispenseRequestBo("869667070337387", "1,11&1,12&1,13&1");
DispenseRequestBo request = new DispenseRequestBo("869667070337387", "1,17&1,18&1");
// 设置请求头
HttpHeaders headers = new HttpHeaders();
@@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.trade.service.order.handler;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.module.product.api.device.DeviceApi;
import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
@@ -54,6 +55,9 @@ public class TradeGiftVoucherOrderHandler implements TradeOrderHandler {
@Resource
private TradeOrderMapper tradeOrderMapper;
@Resource
private DeviceApi deviceApi;
// TOKEN
@Value("${xr.cabinet.token}")
private String token;
@@ -126,6 +130,10 @@ public class TradeGiftVoucherOrderHandler implements TradeOrderHandler {
if (i != orderItems.size() - 1) {
message.append(",");
}
//记录最后一次出货的货道
if (i == orderItems.size() - 1) {
int d = deviceApi.updateDeviceLastChannel(user.getDeviceId(), sku.getCabinetChannel());
}
}
request.setMessage(message.toString());
// 设置请求头
@@ -3,6 +3,8 @@ package cn.iocoder.yudao.module.trade.service.price;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeProductSettlementRespVO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateSummaryReqBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateSummaryRespBO;
import javax.validation.Valid;
import java.util.List;
@@ -31,4 +33,5 @@ public interface TradePriceService {
*/
List<AppTradeProductSettlementRespVO> calculateProductPrice(Long userId, List<Long> spuIds);
TradePriceCalculateSummaryRespBO calculateSummaryOrderPrice(TradePriceCalculateSummaryReqBO calculateReqBO);
}
@@ -3,6 +3,8 @@ package cn.iocoder.yudao.module.trade.service.price;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.member.api.level.dto.MemberLevelRespDTO;
import cn.iocoder.yudao.module.product.api.device.DeviceApi;
import cn.iocoder.yudao.module.product.api.device.dto.DeviceRespDto;
import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
@@ -15,6 +17,8 @@ import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeProductSettlementRespVO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateSummaryReqBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateSummaryRespBO;
import cn.iocoder.yudao.module.trade.service.price.calculator.TradeDiscountActivityPriceCalculator;
import cn.iocoder.yudao.module.trade.service.price.calculator.TradePriceCalculator;
import cn.iocoder.yudao.module.trade.service.price.calculator.TradePriceCalculatorHelper;
@@ -23,14 +27,15 @@ import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_NOT_EXISTS;
import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_STOCK_NOT_ENOUGH;
import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRICE_CALCULATE_PAY_PRICE_ILLEGAL;
import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.*;
/**
* 价格计算 Service 实现类
@@ -50,6 +55,8 @@ public class TradePriceServiceImpl implements TradePriceService {
private DiscountActivityApi discountActivityApi;
@Resource
private RewardActivityApi rewardActivityApi;
@Resource
private DeviceApi deviceApi;
@Resource
private List<TradePriceCalculator> priceCalculators;
@@ -152,4 +159,110 @@ public class TradePriceServiceImpl implements TradePriceService {
});
}
@Override
public TradePriceCalculateSummaryRespBO calculateSummaryOrderPrice(TradePriceCalculateSummaryReqBO calculateReqBO) {
// 1.1 获得商品 SKU 数组
List<ProductSkuRespDTO> skuList = checkSummarySkuList(calculateReqBO);
// 1.2 获得商品 SPU 数组
List<ProductSpuRespDTO> spuList = checkSpuList(skuList);
List<String> deviceIds = calculateReqBO.getItems().stream()
.map(TradePriceCalculateSummaryReqBO.Item::getDeviceId)
.distinct()
.collect(Collectors.toList());
List<DeviceRespDto> devices =deviceApi.getDeviceByDeviceIds(deviceIds);
// 2.1 计算价格
TradePriceCalculateSummaryRespBO calculateRespBO = TradePriceCalculatorHelper
.buildCalculateSummaryResp(calculateReqBO, spuList, skuList,devices);
priceCalculators.forEach(calculator -> calculator.summaryCalculate(calculateReqBO, calculateRespBO));
// 2.2 如果最终支付金额小于等于 0,则抛出业务异常
/*if (calculateReqBO.getPointActivityId() == null // 积分订单,允许支付金额为 0
&& calculateRespBO.getPrice().getPayPrice() <= 0) {
log.error("[calculatePrice][价格计算不正确,请求 calculateReqDTO({}),结果 priceCalculate({})]",
calculateReqBO, calculateRespBO);
throw exception(PRICE_CALCULATE_PAY_PRICE_ILLEGAL);
}*/
return calculateRespBO;
}
private List<ProductSkuRespDTO> checkSummarySkuList(TradePriceCalculateSummaryReqBO calculateReqBO) {
// 获得商品 SKU 数组
//根据商品码获取sku列表
List<ProductSkuRespDTO> skus = productSkuApi.getSkuListByBarCode(convertSet(calculateReqBO.getItems(), TradePriceCalculateSummaryReqBO.Item::getBarCode))
.stream()
.filter(sku -> sku.getStock() > 0)
.sorted(Comparator.comparing(sku -> {
try {
// 按照 cabinetChannel 的数值大小排序
return Integer.parseInt(sku.getCabinetChannel());
} catch (NumberFormatException e) {
// 如果无法解析为数字,则放到最后
return Integer.MAX_VALUE;
}
}))
.collect(Collectors.toList());
//查询需要的数量
Map<String, Integer> barCodeCountMap = convertMap(calculateReqBO.getItems(),
TradePriceCalculateSummaryReqBO.Item::getBarCode, TradePriceCalculateSummaryReqBO.Item::getCount);
// 按 barCode 分组并计算每组 stock 总和
Map<String, Integer> barCodeStockTotals = skus.stream()
.collect(Collectors.groupingBy(
ProductSkuRespDTO::getBarCode,
Collectors.reducing(0, ProductSkuRespDTO::getStock, Integer::sum)
));
Map<String, String> deviceCountMap = convertMap(calculateReqBO.getItems(),
TradePriceCalculateSummaryReqBO.Item::getBarCode, TradePriceCalculateSummaryReqBO.Item::getDeviceId);
// 校验商品 SKU
// List<ProductSkuRespDTO> skus2=new ArrayList<>();
barCodeCountMap.forEach((barCode, count) -> {
Integer stock = barCodeStockTotals.get(barCode);
if (stock == null) {
throw exception(SKU_NOT_EXISTS);
}
if (stock < count) {
throw exception(SKU_STOCK_NOT_ENOUGH);
}
DeviceRespDto device =deviceApi.getDeviceByDeviceId(deviceCountMap.get(barCode));
if (device == null) {
throw exception(DEVICE_NOT_EXISTS_INFO, deviceCountMap.get(barCode));
}
/*int num=count;
for (ProductSkuRespDTO productSkuRespDTO : skus) {
if (num<=0){
break;
}
//从机器当前使用的货道或柜门编号的下一个开始
if (Integer.parseInt(productSkuRespDTO.getCabinetChannel())> Integer.parseInt(device.getCabinetChannel())
&&productSkuRespDTO.getBarCode().equals(barCode)&&productSkuRespDTO.getStock()>0){
skus2.add(productSkuRespDTO);
num--;
}
}
while (num>0){
boolean found = false; // 标志位
for (ProductSkuRespDTO productSkuRespDTO : skus) {
if (num<=0){
break;
}
if (productSkuRespDTO.getBarCode().equals(barCode)&&productSkuRespDTO.getStock()>0){
skus2.add(productSkuRespDTO);
num--;
found = true; // 标记找到了商品
}
}
// 如果一轮遍历后没有找到任何商品,跳出 while 循环
if (!found) {
break;
}
if (num<=0){
break;
}
}*/
});
return skus;
}
}
@@ -0,0 +1,122 @@
package cn.iocoder.yudao.module.trade.service.price.bo;
import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.Valid;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import java.util.List;
/**
* 价格计算 Request BO
*
* @author yudao源码
*/
@Data
public class TradePriceCalculateSummaryReqBO {
/**
* 用户编号
*
* 对应 MemberUserDO 的 id 编号
*/
private Long userId;
/**
* 优惠劵编号
*
* 对应 CouponDO 的 id 编号
*/
private Long couponId;
/**
* 是否使用积分
*/
@NotNull(message = "是否使用积分不能为空")
private Boolean pointStatus;
/**
* 配送方式
*
* 枚举 {@link DeliveryTypeEnum}
*/
private Integer deliveryType;
/**
* 收货地址编号
*
* 对应 MemberAddressDO 的 id 编号
*/
private Long addressId;
/**
* 自提门店编号
*
* 对应 PickUpStoreDO 的 id 编号
*/
private Long pickUpStoreId;
/**
* 商品 SKU 数组
*/
@NotNull(message = "商品数组不能为空")
private List<Item> items;
// ========== 秒杀活动相关字段 ==========
/**
* 秒杀活动编号
*/
private Long seckillActivityId;
// ========== 拼团活动相关字段 ==========
/**
* 拼团活动编号
*/
private Long combinationActivityId;
/**
* 拼团团长编号
*/
private Long combinationHeadId;
// ========== 砍价活动相关字段 ==========
/**
* 砍价记录编号
*/
private Long bargainRecordId;
// ========== 积分商城活动相关字段 ==========
/**
* 积分商城活动编号
*/
private Long pointActivityId;
/**
* 商品 SKU
*/
@Data
@Valid
public static class Item {
@Schema(description = "商品 SKU 条形码", example = "2048")
@NotNull(message = "商品 SKU 条形码不能为空")
private String barCode;
@Schema(description = "设备IMEI", example = "2048")
@NotNull(message = "设备IMEI不能为空")
private String deviceId;
/**
* SKU 数量
*/
@NotNull(message = "商品 SKU 数量不能为空")
@Min(value = 0L, message = "商品 SKU 数量必须大于等于 0")
private Integer count;
/**
* 是否选中
*/
@NotNull(message = "是否选中不能为空")
private Boolean selected;
}
}
@@ -0,0 +1,405 @@
package cn.iocoder.yudao.module.trade.service.price.bo;
import cn.iocoder.yudao.module.product.api.property.dto.ProductPropertyValueDetailRespDTO;
import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
/**
* 价格计算 Response BO
*
* 整体设计,参考 taobao 的技术文档:
* 1. <a href="https://developer.alibaba.com/docs/doc.htm?treeId=1&articleId=1029&docType=1">订单管理</a>
* 2. <a href="https://open.taobao.com/docV3.htm?docId=108471&docType=1">常用订单金额说明</a>
*
* @author 芋道源码
*/
@Data
public class TradePriceCalculateSummaryRespBO {
/**
* 订单类型
*
* 枚举 {@link TradeOrderTypeEnum}
*/
private Integer type;
/**
* 订单价格
*/
private Price price;
/**
* 订单项数组
*/
private List<OrderItem> items;
/**
* 营销活动数组
*
* 只对应 {@link Price#items} 商品匹配的活动
*/
private List<Promotion> promotions;
/**
* 使用的优惠劵编号
*/
private Long couponId;
/**
* 用户的优惠劵列表(可用 + 不可用)
*/
private List<Coupon> coupons;
/**
* 会员剩余积分
*/
private Integer totalPoint;
/**
* 使用的积分
*/
private Integer usePoint;
/**
* 赠送的积分
*/
private Integer givePoint;
/**
* 砍价活动编号
*/
private Long bargainActivityId;
/**
* 是否包邮
*/
private Boolean freeDelivery;
/**
* 赠送的优惠劵
*
* key: 优惠劵模版编号
* value:对应的优惠券数量
*
* 目的:用于订单支付后赠送优惠券
*/
private Map<Long, Integer> giveCouponTemplateCounts;
/**
* 订单价格
*/
@Data
public static class Price {
/**
* 商品原价(总),单位:分
*
* 基于 {@link OrderItem#getPrice()} * {@link OrderItem#getCount()} 求和
*
* 对应 taobao 的 trade.total_fee 字段
*/
private Integer totalPrice;
/**
* 订单优惠(总),单位:分
*
* 对应 taobao 的 order.discount_fee 字段
*/
private Integer discountPrice;
/**
* 运费金额,单位:分
*/
private Integer deliveryPrice;
/**
* 优惠劵减免金额(总),单位:分
*
* 对应 taobao 的 trade.coupon_fee 字段
*/
private Integer couponPrice;
/**
* 积分抵扣的金额,单位:分
*
* 对应 taobao 的 trade.point_fee 字段
*/
private Integer pointPrice;
/**
* VIP 减免金额,单位:分
*/
private Integer vipPrice;
/**
* 最终购买金额(总),单位:分
*
* = {@link #totalPrice}
* - {@link #couponPrice}
* - {@link #pointPrice}
* - {@link #discountPrice}
* + {@link #deliveryPrice}
* - {@link #vipPrice}
*/
private Integer payPrice;
}
/**
* 订单商品 SKU
*/
@Data
public static class OrderItem {
/**
* SPU 编号
*/
private Long spuId;
/**
* SKU 编号
*/
private Long skuId;
/**
* 购买数量
*/
private Integer count;
/**
* 购物车项的编号
*/
private Long cartId;
/**
* 是否选中
*/
private Boolean selected;
/**
* 商品原价(单),单位:分
*
* 对应 ProductSkuDO 的 price 字段
* 对应 taobao 的 order.price 字段
*/
private Integer price;
/**
* 优惠金额(总),单位:分
*
* 对应 taobao 的 order.discount_fee 字段
*/
private Integer discountPrice;
/**
* 运费金额(总),单位:分
*/
private Integer deliveryPrice;
/**
* 优惠劵减免金额,单位:分
*
* 对应 taobao 的 trade.coupon_fee 字段
*/
private Integer couponPrice;
/**
* 积分抵扣的金额,单位:分
*
* 对应 taobao 的 trade.point_fee 字段
*/
private Integer pointPrice;
/**
* 使用的积分
*/
private Integer usePoint;
/**
* VIP 减免金额,单位:分
*/
private Integer vipPrice;
/**
* 应付金额(总),单位:分
*
* = {@link #price} * {@link #count}
* - {@link #couponPrice}
* - {@link #pointPrice}
* - {@link #discountPrice}
* + {@link #deliveryPrice}
* - {@link #vipPrice}
*/
private Integer payPrice;
// ========== 商品 SPU 信息 ==========
/**
* 商品名
*/
private String spuName;
/**
* 商品图片
*
* 优先级:SKU.picUrl > SPU.picUrl
*/
private String picUrl;
/**
* 分类编号
*/
private Long categoryId;
// ========== 物流相关字段 =========
/**
* 配送方式数组
*
* 对应 DeliveryTypeEnum 枚举
*/
private List<Integer> deliveryTypes;
/**
* 物流配置模板编号
*
* 对应 TradeDeliveryExpressTemplateDO 的 id 编号
*/
private Long deliveryTemplateId;
// ========== 商品 SKU 信息 ==========
/**
* 商品重量,单位:kg 千克
*/
private Double weight;
/**
* 商品体积,单位:m^3 平米
*/
private Double volume;
/**
* 商品属性数组
*/
private List<ProductPropertyValueDetailRespDTO> properties;
/**
* 赠送的积分
*/
private Integer givePoint;
}
/**
* 营销明细
*/
@Data
public static class Promotion {
/**
* 营销编号
*
* 例如说:营销活动的编号、优惠劵的编号
*/
private Long id;
/**
* 营销名字
*/
private String name;
/**
* 营销类型
*
* 枚举 {@link PromotionTypeEnum}
*/
private Integer type;
/**
* 计算时的原价(总),单位:分
*/
private Integer totalPrice;
/**
* 计算时的优惠(总),单位:分
*/
private Integer discountPrice;
/**
* 匹配的商品 SKU 数组
*/
private List<PromotionItem> items;
// ========== 匹配情况 ==========
/**
* 是否满足优惠条件
*/
private Boolean match;
/**
* 满足条件的提示
*
* 如果 {@link #match} = true 满足,则提示“圣诞价:省 150.00 元”
* 如果 {@link #match} = false 不满足,则提示“购满 85 元,可减 40 元”
*/
private String description;
}
/**
* 营销匹配的商品 SKU
*/
@Data
public static class PromotionItem {
/**
* 商品 SKU 编号
*/
private Long skuId;
/**
* 计算时的原价(总),单位:分
*/
private Integer totalPrice;
/**
* 计算时的优惠(总),单位:分
*/
private Integer discountPrice;
}
/**
* 优惠劵信息
*/
@Data
public static class Coupon {
/**
* 优惠劵编号
*/
private Long id;
/**
* 优惠劵名
*/
private String name;
/**
* 是否设置满多少金额可用,单位:分
*/
private Integer usePrice;
/**
* 生效开始时间
*/
private LocalDateTime validStartTime;
/**
* 生效结束时间
*/
private LocalDateTime validEndTime;
/**
* 优惠类型
*/
private Integer discountType;
/**
* 折扣百分比
*/
private Integer discountPercent;
/**
* 优惠金额,单位:分
*/
private Integer discountPrice;
/**
* 折扣上限,单位:分
*/
private Integer discountLimitPrice;
/**
* 是否匹配
*/
private Boolean match;
/**
* 不匹配的原因
*/
private String mismatchReason;
}
}
@@ -9,6 +9,8 @@ import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateSummaryReqBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateSummaryRespBO;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@@ -55,4 +57,9 @@ public class TradeBargainActivityPriceCalculator implements TradePriceCalculator
result.setBargainActivityId(bargainActivity.getActivityId());
}
@Override
public void summaryCalculate(TradePriceCalculateSummaryReqBO calculateReqBO, TradePriceCalculateSummaryRespBO calculateRespBO) {
}
}
@@ -7,6 +7,8 @@ import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationValidate
import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateSummaryReqBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateSummaryRespBO;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@@ -51,4 +53,9 @@ public class TradeCombinationActivityPriceCalculator implements TradePriceCalcul
TradePriceCalculatorHelper.recountAllPrice(result);
}
@Override
public void summaryCalculate(TradePriceCalculateSummaryReqBO calculateReqBO, TradePriceCalculateSummaryRespBO calculateRespBO) {
}
}
@@ -14,6 +14,8 @@ import cn.iocoder.yudao.module.promotion.enums.coupon.CouponStatusEnum;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateSummaryReqBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateSummaryRespBO;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@@ -91,6 +93,11 @@ public class TradeCouponPriceCalculator implements TradePriceCalculator {
TradePriceCalculatorHelper.recountAllPrice(result);
}
@Override
public void summaryCalculate(TradePriceCalculateSummaryReqBO calculateReqBO, TradePriceCalculateSummaryRespBO calculateRespBO) {
}
/**
* 计算用户的优惠劵列表(可用 + 不可用)
*
@@ -18,6 +18,8 @@ import cn.iocoder.yudao.module.trade.service.delivery.bo.DeliveryExpressTemplate
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO.OrderItem;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateSummaryReqBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateSummaryRespBO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@@ -70,6 +72,172 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
}
}
@Override
public void summaryCalculate(TradePriceCalculateSummaryReqBO param, TradePriceCalculateSummaryRespBO result) {
if (param.getDeliveryType() == null) {
return;
}
// 校验是不是存在商品不能门店自提,或者不能快递发货的情况。就是说,配送方式不匹配哈
if (CollectionUtils.anyMatch(result.getItems(), item -> !item.getDeliveryTypes().contains(param.getDeliveryType()))) {
throw exception(PRICE_CALCULATE_DELIVERY_PRICE_TYPE_ILLEGAL);
}
if (DeliveryTypeEnum.PICK_UP.getType().equals(param.getDeliveryType())) {
summaryCalculateByPickUp(param);
} else if (DeliveryTypeEnum.EXPRESS.getType().equals(param.getDeliveryType())) {
summaryCalculateExpress(param, result);
} else if (DeliveryTypeEnum.DOOR.getType().equals(param.getDeliveryType())) {
}
}
private void summaryCalculateExpress(TradePriceCalculateSummaryReqBO param, TradePriceCalculateSummaryRespBO result) {
// 0. 得到收件地址区域
if (param.getAddressId() == null) {
// 价格计算时,如果为空就不算~最终下单,会校验该字段不允许空
return;
}
MemberAddressRespDTO address = addressApi.getAddress(param.getAddressId(), param.getUserId());
Assert.notNull(address, "收件人({})的地址,不能为空", param.getUserId());
// 情况一:全局包邮
if (isGlobalExpressFreeSummary(result)) {
return;
}
// 情况二:活动包邮
if (Boolean.TRUE.equals(result.getFreeDelivery())) {
return;
}
// 情况三:快递模版
// 2.1 过滤出已选中的商品 SKU
List<TradePriceCalculateSummaryRespBO.OrderItem> selectedItem = filterList(result.getItems(), TradePriceCalculateSummaryRespBO.OrderItem::getSelected);
Set<Long> deliveryTemplateIds = convertSet(selectedItem, TradePriceCalculateSummaryRespBO.OrderItem::getDeliveryTemplateId);
Map<Long, DeliveryExpressTemplateRespBO> expressTemplateMap =
deliveryExpressTemplateService.getExpressTemplateMapByIdsAndArea(deliveryTemplateIds, address.getAreaId());
// 2.2 计算配送费用
if (CollUtil.isEmpty(expressTemplateMap)) {
log.error("[calculate][找不到商品 templateIds {} areaId{} 对应的运费模板]", deliveryTemplateIds, address.getAreaId());
throw exception(PRICE_CALCULATE_DELIVERY_PRICE_TEMPLATE_NOT_FOUND);
}
calculateDeliveryPriceSummary(selectedItem, expressTemplateMap, result);
}
private void calculateDeliveryPriceSummary(List<TradePriceCalculateSummaryRespBO.OrderItem> selectedSkus, Map<Long, DeliveryExpressTemplateRespBO> expressTemplateMap, TradePriceCalculateSummaryRespBO result) {
// 按商品运费模板来计算商品的运费:相同的运费模板可能对应多条订单商品 SKU
Map<Long, List<TradePriceCalculateSummaryRespBO.OrderItem>> template2ItemMap = convertMultiMap(selectedSkus, TradePriceCalculateSummaryRespBO.OrderItem::getDeliveryTemplateId);
// 依次计算快递运费
for (Map.Entry<Long, List<TradePriceCalculateSummaryRespBO.OrderItem>> entry : template2ItemMap.entrySet()) {
Long templateId = entry.getKey();
List<TradePriceCalculateSummaryRespBO.OrderItem> orderItems = entry.getValue();
DeliveryExpressTemplateRespBO templateBO = expressTemplateMap.get(templateId);
if (templateBO == null) {
log.error("[calculateDeliveryPrice][不能计算快递运费,找不到 templateId({}) 对应的运费模板配置]", templateId);
continue;
}
// 1. 优先判断是否包邮。如果包邮不计算快递运费
if (isExpressTemplateFreeSummary(orderItems, templateBO.getChargeMode(), templateBO.getFree())) {
continue;
}
// 2. 计算快递运费
calculateExpressFeeByChargeModeSummary(orderItems, templateBO.getChargeMode(), templateBO.getCharge());
}
TradePriceCalculatorHelper.recountAllPriceSummary(result);
}
private void calculateExpressFeeByChargeModeSummary(List<TradePriceCalculateSummaryRespBO.OrderItem> orderItems,
Integer chargeMode, DeliveryExpressTemplateRespBO.Charge templateCharge) {
if (templateCharge == null) {
log.error("[calculateExpressFeeByChargeMode][计算快递运费时,找不到 SKU({}) 对应的运费模版]", orderItems);
return;
}
double totalChargeValue = getTotalChargeValueSummary(orderItems, chargeMode);
// 1. 计算 SKU 商品快递费用
int deliveryPrice;
if (totalChargeValue <= templateCharge.getStartCount()) {
deliveryPrice = templateCharge.getStartPrice();
} else {
double remainWeight = totalChargeValue - templateCharge.getStartCount();
// 剩余重量/ 续件 = 续件的次数. 向上取整
int extraNum = (int) Math.ceil(remainWeight / templateCharge.getExtraCount());
int extraPrice = templateCharge.getExtraPrice() * extraNum;
deliveryPrice = templateCharge.getStartPrice() + extraPrice;
}
// 2. 分摊快递费用到 SKU. 退费的时候,可能按照 SKU 考虑退费金额
int remainPrice = deliveryPrice;
for (int i = 0; i < orderItems.size(); i++) {
TradePriceCalculateSummaryRespBO.OrderItem item = orderItems.get(i);
int partPrice;
double chargeValue = getChargeValueSummary(item, chargeMode);
if (i < orderItems.size() - 1) { // 减一的原因,是因为拆分时,如果按照比例,可能会出现.所以最后一个,使用反减
partPrice = (int) (deliveryPrice * (chargeValue / totalChargeValue));
remainPrice -= partPrice;
} else {
partPrice = remainPrice;
}
Assert.isTrue(partPrice >= 0, "分摊金额必须大于等于 0");
// 更新快递运费
item.setDeliveryPrice(partPrice);
TradePriceCalculatorHelper.recountPayPriceSummary(item);
}
}
private boolean isExpressTemplateFreeSummary(List<TradePriceCalculateSummaryRespBO.OrderItem> orderItems,
Integer chargeMode, DeliveryExpressTemplateRespBO.Free free) {
if (free == null) {
return false;
}
double totalChargeValue = getTotalChargeValueSummary(orderItems, chargeMode);
double totalPrice = TradePriceCalculatorHelper.calculateTotalPayPriceSummary(orderItems);
return totalChargeValue >= free.getFreeCount() && totalPrice >= free.getFreePrice();
}
private double getTotalChargeValueSummary(List<TradePriceCalculateSummaryRespBO.OrderItem> orderItems, Integer chargeMode) {
double total = 0;
for (TradePriceCalculateSummaryRespBO.OrderItem orderItem : orderItems) {
total += getChargeValueSummary(orderItem, chargeMode);
}
return total;
}
private double getChargeValueSummary(TradePriceCalculateSummaryRespBO.OrderItem orderItem, Integer chargeMode) {
DeliveryExpressChargeModeEnum chargeModeEnum = DeliveryExpressChargeModeEnum.valueOf(chargeMode);
switch (chargeModeEnum) {
case COUNT:
return orderItem.getCount();
case WEIGHT:
return orderItem.getWeight() != null ? orderItem.getWeight() * orderItem.getCount() : 0;
case VOLUME:
return orderItem.getVolume() != null ? orderItem.getVolume() * orderItem.getCount() : 0;
default:
throw new IllegalArgumentException(StrUtil.format("未知的计费模式({})", chargeMode));
}
}
private boolean isGlobalExpressFreeSummary(TradePriceCalculateSummaryRespBO result) {
TradeConfigDO config = tradeConfigService.getTradeConfig();
// 情况一:交易中心配置不存在默认不包邮
if (config == null) {
return false;
}
// 情况二:开启了全局包邮 && 满足包邮金额
return Boolean.TRUE.equals(config.getDeliveryExpressFreeEnabled()) &&
result.getPrice().getPayPrice() >= config.getDeliveryExpressFreePrice();
}
private void summaryCalculateByPickUp(TradePriceCalculateSummaryReqBO param) {
if (param.getPickUpStoreId() == null) {
// 价格计算时,如果为空就不算~最终下单,会校验该字段不允许空
return;
}
DeliveryPickUpStoreDO pickUpStore = deliveryPickUpStoreService.getDeliveryPickUpStore(param.getPickUpStoreId());
if (pickUpStore == null || CommonStatusEnum.DISABLE.getStatus().equals(pickUpStore.getStatus())) {
throw exception(PICK_UP_STORE_NOT_EXISTS);
}
}
private void calculateByPickUp(TradePriceCalculateReqBO param) {
if (param.getPickUpStoreId() == null) {
// 价格计算时,如果为空就不算~最终下单,会校验该字段不允许空
@@ -14,6 +14,8 @@ import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateSummaryReqBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateSummaryRespBO;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@@ -96,6 +98,83 @@ public class TradeDiscountActivityPriceCalculator implements TradePriceCalculato
});
}
@Override
public void summaryCalculate(TradePriceCalculateSummaryReqBO param, TradePriceCalculateSummaryRespBO result) {
// 0. 只有【普通】订单,才计算该优惠
if (ObjectUtil.notEqual(result.getType(), TradeOrderTypeEnum.NORMAL.getType())) {
return;
}
// 1.1 获得 SKU 对应的限时折扣活动
List<DiscountProductRespDTO> discountProducts = discountActivityApi.getMatchDiscountProductListBySkuIds(
convertSet(result.getItems(), TradePriceCalculateSummaryRespBO.OrderItem::getSkuId));
Map<Long, DiscountProductRespDTO> discountProductMap = convertMap(discountProducts, DiscountProductRespDTO::getSkuId);
// 1.2 获得会员等级
MemberLevelRespDTO level = getMemberLevel(param.getUserId());
// 2. 计算每个 SKU 的优惠金额
result.getItems().forEach(orderItem -> {
if (!orderItem.getSelected()) {
return;
}
// 2.1 计算限时折扣的优惠金额
DiscountProductRespDTO discountProduct = discountProductMap.get(orderItem.getSkuId());
Integer discountPrice = calculateActivityPriceSummary(discountProduct, orderItem);
// 2.2 计算 VIP 优惠金额
Integer vipPrice = calculateVipPriceSummary(level, orderItem);
if (discountPrice <= 0 && vipPrice <= 0) {
return;
}
// 3. 选择优惠金额多的
if (discountPrice > vipPrice) {
TradePriceCalculatorHelper.addPromotionSummary(result, orderItem,
discountProduct.getActivityId(), discountProduct.getActivityName(), PromotionTypeEnum.DISCOUNT_ACTIVITY.getType(),
StrUtil.format("限时折扣:省 {} 元", formatPrice(discountPrice)),
discountPrice);
// 更新 SKU 优惠金额
orderItem.setDiscountPrice(orderItem.getDiscountPrice() + discountPrice);
} else {
assert level != null;
TradePriceCalculatorHelper.addPromotionSummary(result, orderItem,
level.getId(), level.getName(), PromotionTypeEnum.MEMBER_LEVEL.getType(),
String.format("会员等级折扣:省 %s 元", formatPrice(vipPrice)),
vipPrice);
// 更新 SKU 的优惠金额
orderItem.setVipPrice(vipPrice);
}
// 4. 分摊优惠
TradePriceCalculatorHelper.recountPayPriceSummary(orderItem);
TradePriceCalculatorHelper.recountAllPriceSummary(result);
});
}
private Integer calculateVipPriceSummary(MemberLevelRespDTO level, TradePriceCalculateSummaryRespBO.OrderItem orderItem) {
if (level == null
|| CommonStatusEnum.isDisable(level.getStatus())
|| level.getDiscountPercent() == null) {
return 0;
}
Integer newPrice = calculateRatePrice(orderItem.getPayPrice(), level.getDiscountPercent().doubleValue());
return orderItem.getPayPrice() - newPrice;
}
private Integer calculateActivityPriceSummary(DiscountProductRespDTO discount, TradePriceCalculateSummaryRespBO.OrderItem orderItem) {
if (discount == null) {
return 0;
}
Integer newPrice = orderItem.getPayPrice();
if (PromotionDiscountTypeEnum.PRICE.getType().equals(discount.getDiscountType())) { // 减价
newPrice -= discount.getDiscountPrice() * orderItem.getCount();
} else if (PromotionDiscountTypeEnum.PERCENT.getType().equals(discount.getDiscountType())) { // 打折
newPrice = calculateRatePrice(orderItem.getPayPrice(), discount.getDiscountPercent() / 100.0);
} else {
throw new IllegalArgumentException(String.format("优惠活动的商品(%s) 的优惠类型不正确", discount));
}
return orderItem.getPayPrice() - newPrice;
}
/**
* 获得用户的等级
*
@@ -12,6 +12,8 @@ import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
import cn.iocoder.yudao.module.trade.service.order.TradeOrderQueryService;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateSummaryReqBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateSummaryRespBO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@@ -81,6 +83,11 @@ public class TradePointActivityPriceCalculator implements TradePriceCalculator {
TradePriceCalculatorHelper.recountAllPrice(result);
}
@Override
public void summaryCalculate(TradePriceCalculateSummaryReqBO calculateReqBO, TradePriceCalculateSummaryRespBO calculateRespBO) {
}
private PointValidateJoinRespDTO validateJoinPointActivity(Long userId, Long activityId, Long skuId, Integer count) {
// 1. 校验是否可以参与积分商城活动
PointValidateJoinRespDTO pointValidateJoinRespDTO = pointActivityApi.validateJoinPointActivity(activityId, skuId, count);
@@ -6,6 +6,8 @@ import cn.iocoder.yudao.module.member.api.config.MemberConfigApi;
import cn.iocoder.yudao.module.member.api.config.dto.MemberConfigRespDTO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateSummaryReqBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateSummaryRespBO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@@ -60,4 +62,35 @@ public class TradePointGiveCalculator implements TradePriceCalculator {
TradePriceCalculatorHelper.recountAllGivePoint(result);
}
@Override
public void summaryCalculate(TradePriceCalculateSummaryReqBO calculateReqBO, TradePriceCalculateSummaryRespBO result) {
// 1.1 校验积分功能是否开启
int givePointPerYuan = Optional.ofNullable(memberConfigApi.getConfig())
.filter(config -> BooleanUtil.isTrue(config.getPointTradeDeductEnable()))
.map(MemberConfigRespDTO::getPointTradeGivePoint)
.orElse(0);
if (givePointPerYuan <= 0) {
return;
}
// 1.2 校验支付金额
if (result.getPrice().getPayPrice() <= 0) {
return;
}
// 2.1 计算赠送积分
int givePoint = MoneyUtils.calculateRatePriceFloor(result.getPrice().getPayPrice(), (double) givePointPerYuan);
// 2.2 计算分摊的赠送积分
List<TradePriceCalculateSummaryRespBO.OrderItem> orderItems = filterList(result.getItems(), TradePriceCalculateSummaryRespBO.OrderItem::getSelected);
List<Integer> dividePoints = TradePriceCalculatorHelper.dividePriceSummary(orderItems, givePoint);
// 3.2 更新 SKU 赠送积分
for (int i = 0; i < orderItems.size(); i++) {
TradePriceCalculateSummaryRespBO.OrderItem orderItem = orderItems.get(i);
// 商品可能赠送了积分,所以这里要加上
orderItem.setGivePoint(orderItem.getGivePoint() + dividePoints.get(i));
}
// 3.3 更新订单赠送积分
TradePriceCalculatorHelper.recountAllGivePointSummary(result);
}
}
@@ -11,6 +11,8 @@ import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateSummaryReqBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateSummaryRespBO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@@ -83,6 +85,11 @@ public class TradePointUsePriceCalculator implements TradePriceCalculator {
TradePriceCalculatorHelper.recountAllPrice(result);
}
@Override
public void summaryCalculate(TradePriceCalculateSummaryReqBO calculateReqBO, TradePriceCalculateSummaryRespBO calculateRespBO) {
}
private boolean isDeductPointEnable(MemberConfigRespDTO config) {
return config != null &&
BooleanUtil.isTrue(config.getPointTradeDeductEnable()) && // 积分功能是否启用
@@ -2,6 +2,8 @@ package cn.iocoder.yudao.module.trade.service.price.calculator;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateSummaryReqBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateSummaryRespBO;
/**
* 价格计算的计算器接口
@@ -37,4 +39,5 @@ public interface TradePriceCalculator {
void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result);
void summaryCalculate(TradePriceCalculateSummaryReqBO calculateReqBO, TradePriceCalculateSummaryRespBO calculateRespBO);
}
@@ -3,20 +3,28 @@ package cn.iocoder.yudao.module.trade.service.price.calculator;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.module.product.api.device.DeviceApi;
import cn.iocoder.yudao.module.product.api.device.dto.DeviceRespDto;
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateSummaryReqBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateSummaryRespBO;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getSumValue;
import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.DEVICE_NOT_EXISTS_INFO;
import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_STOCK_NOT_ENOUGH;
import static java.util.Collections.singletonList;
/**
@@ -28,6 +36,9 @@ import static java.util.Collections.singletonList;
*/
public class TradePriceCalculatorHelper {
@Resource
private DeviceApi deviceApi;
public static TradePriceCalculateRespBO buildCalculateResp(TradePriceCalculateReqBO param,
List<ProductSpuRespDTO> spuList, List<ProductSkuRespDTO> skuList) {
// 创建 PriceCalculateRespDTO 对象
@@ -342,4 +353,252 @@ public class TradePriceCalculatorHelper {
return String.format("%.2f", price / 100d);
}
public static TradePriceCalculateSummaryRespBO buildCalculateSummaryResp(TradePriceCalculateSummaryReqBO param, List<ProductSpuRespDTO> spuList, List<ProductSkuRespDTO> skuList,List<DeviceRespDto> devices) {
// 创建 PriceCalculateRespDTO 对象
TradePriceCalculateSummaryRespBO result = new TradePriceCalculateSummaryRespBO();
result.setType(TradeOrderTypeEnum.NORMAL.getType()).setPromotions(new ArrayList<>()).setGiveCouponTemplateCounts(new LinkedHashMap<>());
// 创建它的 OrderItem 属性
result.setItems(new ArrayList<>());
Map<Long, ProductSpuRespDTO> spuMap = convertMap(spuList, ProductSpuRespDTO::getId);
// Map<Long, ProductSkuRespDTO> skuMap = convertMap(skuList, ProductSkuRespDTO::getId);
Map<String, DeviceRespDto> deviceMap = convertMap(devices, DeviceRespDto::getDeviceId);
param.getItems().forEach(item -> {
DeviceRespDto device =deviceMap.get(item.getDeviceId());
if (device == null) {
throw exception(DEVICE_NOT_EXISTS_INFO, item.getDeviceId());
}
int num = item.getCount();
for (ProductSkuRespDTO sku : skuList) {
if (num<=0){
break;
}
ProductSpuRespDTO spu = spuMap.get(sku.getSpuId());
if (spu == null) {
return;
}
//从机器当前使用的货道或柜门编号的下一个开始
if (Integer.parseInt(sku.getCabinetChannel())> Integer.parseInt(device.getCabinetChannel())
&&sku.getBarCode().equals(item.getBarCode())&&sku.getStock()>0){
// 商品项
TradePriceCalculateSummaryRespBO.OrderItem orderItem = new TradePriceCalculateSummaryRespBO.OrderItem();
result.getItems().add(orderItem);
orderItem.setSpuId(sku.getSpuId()).setSkuId(sku.getId())
.setCount(1).setSelected(item.getSelected());
// sku 价格
orderItem.setPrice(sku.getPrice()).setPayPrice(sku.getPrice() )
.setDiscountPrice(0).setDeliveryPrice(0).setCouponPrice(0).setPointPrice(0).setVipPrice(0);
// sku 信息
orderItem.setPicUrl(sku.getPicUrl()).setProperties(sku.getProperties())
.setWeight(sku.getWeight()).setVolume(sku.getVolume());
// spu 信息
orderItem.setSpuName(spu.getName()).setCategoryId(spu.getCategoryId())
.setDeliveryTypes(spu.getDeliveryTypes()).setDeliveryTemplateId(spu.getDeliveryTemplateId())
.setGivePoint(spu.getGiveIntegral()).setUsePoint(0);
if (StrUtil.isBlank(orderItem.getPicUrl())) {
orderItem.setPicUrl(spu.getPicUrl());
}
num--;
}
}
while (num>0){
boolean found = false; // 标志位
for (ProductSkuRespDTO sku : skuList) {
if (num<=0){
break;
}
ProductSpuRespDTO spu = spuMap.get(sku.getSpuId());
if (spu == null) {
return;
}
//判断sku是否已存在
Map<Long, TradePriceCalculateSummaryRespBO.OrderItem> map = convertMap(result.getItems(), TradePriceCalculateSummaryRespBO.OrderItem::getSkuId);
if (map.containsKey(sku.getId())){
// 获取已存在的订单项并增加数量
TradePriceCalculateSummaryRespBO.OrderItem existingItem = map.get(sku.getId());
if (sku.getStock()>=existingItem.getCount()+1){
existingItem.setCount(existingItem.getCount() + 1);
// 重新计算价格
existingItem.setPayPrice(existingItem.getPrice() *existingItem.getCount());
num--;
found = true;
}
}else {
//判断是否是需要的商品和库存
if (sku.getBarCode().equals(item.getBarCode())&&sku.getStock()>0){
// 商品项
TradePriceCalculateSummaryRespBO.OrderItem orderItem = new TradePriceCalculateSummaryRespBO.OrderItem();
result.getItems().add(orderItem);
orderItem.setSpuId(sku.getSpuId()).setSkuId(sku.getId())
.setCount(1).setSelected(item.getSelected());
// sku 价格
orderItem.setPrice(sku.getPrice()).setPayPrice(sku.getPrice() )
.setDiscountPrice(0).setDeliveryPrice(0).setCouponPrice(0).setPointPrice(0).setVipPrice(0);
// sku 信息
orderItem.setPicUrl(sku.getPicUrl()).setProperties(sku.getProperties())
.setWeight(sku.getWeight()).setVolume(sku.getVolume());
// spu 信息
orderItem.setSpuName(spu.getName()).setCategoryId(spu.getCategoryId())
.setDeliveryTypes(spu.getDeliveryTypes()).setDeliveryTemplateId(spu.getDeliveryTemplateId())
.setGivePoint(spu.getGiveIntegral()).setUsePoint(0);
if (StrUtil.isBlank(orderItem.getPicUrl())) {
orderItem.setPicUrl(spu.getPicUrl());
}
num--;
found = true; // 标记找到了商品
}
}
}
// 如果一轮遍历后没有找到任何商品,跳出 while 循环
if (!found) {
throw exception(SKU_STOCK_NOT_ENOUGH);
}
if (num<=0){
break;
}
}
});
// 创建它的 Price 属性
result.setPrice(new TradePriceCalculateSummaryRespBO.Price());
recountSummaryAllPrice(result);
recountSummaryAllGivePoint(result);
return result;
}
private static void recountSummaryAllGivePoint(TradePriceCalculateSummaryRespBO result) {
result.setGivePoint(getSumValue(result.getItems(), item -> item.getSelected() ? item.getGivePoint() : 0, Integer::sum));
}
private static void recountSummaryAllPrice(TradePriceCalculateSummaryRespBO result) {
// 先重置
TradePriceCalculateSummaryRespBO.Price price = result.getPrice();
price.setTotalPrice(0).setDiscountPrice(0).setDeliveryPrice(0)
.setCouponPrice(0).setPointPrice(0).setVipPrice(0).setPayPrice(0);
// 再合计 item
result.getItems().forEach(item -> {
if (!item.getSelected()) {
return;
}
price.setTotalPrice(price.getTotalPrice() + item.getPrice() * item.getCount());
price.setDiscountPrice(price.getDiscountPrice() + item.getDiscountPrice());
price.setDeliveryPrice(price.getDeliveryPrice() + item.getDeliveryPrice());
price.setCouponPrice(price.getCouponPrice() + item.getCouponPrice());
price.setPointPrice(price.getPointPrice() + item.getPointPrice());
price.setVipPrice(price.getVipPrice() + item.getVipPrice());
price.setPayPrice(price.getPayPrice() + item.getPayPrice());
});
}
public static Integer calculateTotalPayPriceSummary(List<TradePriceCalculateSummaryRespBO.OrderItem> orderItems) {
return getSumValue(orderItems,
orderItem -> orderItem.getSelected() ? orderItem.getPayPrice() : 0, // 未选中的情况下,不计算支付金额
Integer::sum);
}
public static void recountPayPriceSummary(TradePriceCalculateSummaryRespBO.OrderItem orderItem) {
orderItem.setPayPrice(orderItem.getPrice() * orderItem.getCount()
- orderItem.getDiscountPrice()
+ orderItem.getDeliveryPrice()
- orderItem.getCouponPrice()
- orderItem.getPointPrice()
- orderItem.getVipPrice()
);
}
public static void recountAllPriceSummary(TradePriceCalculateSummaryRespBO result) {
// 先重置
TradePriceCalculateSummaryRespBO.Price price = result.getPrice();
price.setTotalPrice(0).setDiscountPrice(0).setDeliveryPrice(0)
.setCouponPrice(0).setPointPrice(0).setVipPrice(0).setPayPrice(0);
// 再合计 item
result.getItems().forEach(item -> {
if (!item.getSelected()) {
return;
}
price.setTotalPrice(price.getTotalPrice() + item.getPrice() * item.getCount());
price.setDiscountPrice(price.getDiscountPrice() + item.getDiscountPrice());
price.setDeliveryPrice(price.getDeliveryPrice() + item.getDeliveryPrice());
price.setCouponPrice(price.getCouponPrice() + item.getCouponPrice());
price.setPointPrice(price.getPointPrice() + item.getPointPrice());
price.setVipPrice(price.getVipPrice() + item.getVipPrice());
price.setPayPrice(price.getPayPrice() + item.getPayPrice());
});
}
public static void addPromotionSummary(TradePriceCalculateSummaryRespBO result, TradePriceCalculateSummaryRespBO.OrderItem orderItem,
Long id, String name, Integer type, String description, Integer discountPrice) {
addPromotionSummary(result, singletonList(orderItem), id, name, type, description, singletonList(discountPrice));
}
public static void addPromotionSummary(TradePriceCalculateSummaryRespBO result, List<TradePriceCalculateSummaryRespBO.OrderItem> orderItems,
Long id, String name, Integer type, String description, List<Integer> discountPrices) {
// 创建营销明细 Item
List<TradePriceCalculateSummaryRespBO.PromotionItem> promotionItems = new ArrayList<>(discountPrices.size());
for (int i = 0; i < orderItems.size(); i++) {
TradePriceCalculateSummaryRespBO.OrderItem orderItem = orderItems.get(i);
promotionItems.add(new TradePriceCalculateSummaryRespBO.PromotionItem().setSkuId(orderItem.getSkuId())
.setTotalPrice(orderItem.getPayPrice()).setDiscountPrice(discountPrices.get(i)));
}
// 创建营销明细
TradePriceCalculateSummaryRespBO.Promotion promotion = new TradePriceCalculateSummaryRespBO.Promotion()
.setId(id).setName(name).setType(type)
.setTotalPrice(calculateTotalPayPriceSummary(orderItems))
.setDiscountPrice(getSumValue(discountPrices, value -> value, Integer::sum))
.setItems(promotionItems).setMatch(true).setDescription(description);
result.getPromotions().add(promotion);
}
public static List<Integer> dividePriceSummary(List<TradePriceCalculateSummaryRespBO.OrderItem> orderItems, int price) {
Integer total = calculateTotalPayPriceSummary(orderItems);
assert total != null;
// 遍历每一个,进行分摊
List<Integer> prices = new ArrayList<>(orderItems.size());
int remainPrice = price;
for (int i = 0; i < orderItems.size(); i++) {
TradePriceCalculateSummaryRespBO.OrderItem orderItem = orderItems.get(i);
// 1. 如果是未选中,则分摊为 0
if (!orderItem.getSelected()) {
prices.add(0);
continue;
}
// 2. 如果选中,则按照百分比,进行分摊
int partPrice;
if (i < orderItems.size() - 1) { // 减一的原因,是因为拆分时,如果按照比例,可能会出现.所以最后一个,使用反减
partPrice = (int) (price * (1.0D * orderItem.getPayPrice() / total));
remainPrice -= partPrice;
} else {
partPrice = remainPrice;
}
Assert.isTrue(partPrice >= 0, "分摊金额必须大于等于 0");
prices.add(partPrice);
}
return prices;
}
public static void recountAllGivePointSummary(TradePriceCalculateSummaryRespBO result) {
result.setGivePoint(getSumValue(result.getItems(), item -> item.getSelected() ? item.getGivePoint() : 0, Integer::sum));
}
public static Integer calculateTotalCountSummary(List<TradePriceCalculateSummaryRespBO.OrderItem> orderItems) {
return getSumValue(orderItems,
orderItem -> orderItem.getSelected() ? orderItem.getCount() : 0, // 未选中的情况下,不计算数量
Integer::sum);
}
public static void addNotMatchPromotionSummary(TradePriceCalculateSummaryRespBO result, List<TradePriceCalculateSummaryRespBO.OrderItem> orderItems,
Long id, String name, Integer type, String description) {
// 创建营销明细 Item
List<TradePriceCalculateSummaryRespBO.PromotionItem> promotionItems = CollectionUtils.convertList(orderItems,
orderItem -> new TradePriceCalculateSummaryRespBO.PromotionItem().setSkuId(orderItem.getSkuId())
.setTotalPrice(orderItem.getPayPrice()).setDiscountPrice(0));
// 创建营销明细
TradePriceCalculateSummaryRespBO.Promotion promotion = new TradePriceCalculateSummaryRespBO.Promotion()
.setId(id).setName(name).setType(type)
.setTotalPrice(calculateTotalPayPriceSummary(orderItems))
.setDiscountPrice(0)
.setItems(promotionItems).setMatch(false).setDescription(description);
result.getPromotions().add(promotion);
}
}
@@ -10,6 +10,8 @@ import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateSummaryReqBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateSummaryRespBO;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@@ -53,6 +55,111 @@ public class TradeRewardActivityPriceCalculator implements TradePriceCalculator
}
}
@Override
public void summaryCalculate(TradePriceCalculateSummaryReqBO param, TradePriceCalculateSummaryRespBO result) {
// 0. 只有【普通】订单,才计算该优惠
if (ObjectUtil.notEqual(result.getType(), TradeOrderTypeEnum.NORMAL.getType())) {
return;
}
// 获得 SKU 对应的满减送活动
List<RewardActivityMatchRespDTO> rewardActivities = rewardActivityApi.getMatchRewardActivityListBySpuIds(
convertSet(result.getItems(), TradePriceCalculateSummaryRespBO.OrderItem::getSpuId));
if (CollUtil.isEmpty(rewardActivities)) {
return;
}
// 处理最新的满减送活动
if (!rewardActivities.isEmpty()) {
calculateSummary(param, result, rewardActivities.get(0));
}
}
private void calculateSummary(TradePriceCalculateSummaryReqBO param, TradePriceCalculateSummaryRespBO result, RewardActivityMatchRespDTO rewardActivity) {
// 1.1 获得满减送的订单项(商品)列表
List<TradePriceCalculateSummaryRespBO.OrderItem> orderItems = filterMatchActivityOrderItemsSummary(result, rewardActivity);
if (CollUtil.isEmpty(orderItems)) {
return;
}
// 1.2 获得最大匹配的满减送活动的规则
RewardActivityMatchRespDTO.Rule rule = getMaxMatchRewardActivityRuleSummary(rewardActivity, orderItems);
if (rule == null) {
TradePriceCalculatorHelper.addNotMatchPromotionSummary(result, orderItems,
rewardActivity.getId(), rewardActivity.getName(), PromotionTypeEnum.REWARD_ACTIVITY.getType(),
"满减送:" + rewardActivity.getRules().get(0).getDescription());
return;
}
// 2.1 计算可以优惠的金额
Integer newDiscountPrice = rule.getDiscountPrice();
// 2.2 计算分摊的优惠金额
List<Integer> divideDiscountPrices = TradePriceCalculatorHelper.dividePriceSummary(orderItems, newDiscountPrice);
// 2.3 计算是否包邮
if (Boolean.TRUE.equals(rule.getFreeDelivery())) {
result.setFreeDelivery(true);
}
// 3.1 记录使用的优惠劵
result.setCouponId(param.getCouponId());
// 3.2 记录优惠明细
TradePriceCalculatorHelper.addPromotionSummary(result, orderItems,
rewardActivity.getId(), rewardActivity.getName(), PromotionTypeEnum.REWARD_ACTIVITY.getType(),
StrUtil.format("满减送:省 {} 元", formatPrice(rule.getDiscountPrice())),
divideDiscountPrices);
// 3.3 更新 SKU 优惠金额
for (int i = 0; i < orderItems.size(); i++) {
TradePriceCalculateSummaryRespBO.OrderItem orderItem = orderItems.get(i);
orderItem.setDiscountPrice(orderItem.getDiscountPrice() + divideDiscountPrices.get(i));
TradePriceCalculatorHelper.recountPayPriceSummary(orderItem);
}
TradePriceCalculatorHelper.recountAllPriceSummary(result);
// 4.1 记录赠送的积分
if (rule.getPoint() != null && rule.getPoint() > 0) {
List<Integer> dividePoints = TradePriceCalculatorHelper.dividePriceSummary(orderItems, rule.getPoint());
for (int i = 0; i < orderItems.size(); i++) {
// 商品可能赠送了积分,所以这里要加上
TradePriceCalculateSummaryRespBO.OrderItem orderItem = orderItems.get(i);
orderItem.setGivePoint(orderItem.getGivePoint() + dividePoints.get(i));
}
}
// 4.2 记录订单是否包邮
if (Boolean.TRUE.equals(rule.getFreeDelivery())) {
// 只要满足一个活动包邮那么这单就包邮
result.setFreeDelivery(true);
}
// 4.3 记录赠送的优惠券
if (CollUtil.isNotEmpty(rule.getGiveCouponTemplateCounts())) {
for (Map.Entry<Long, Integer> entry : rule.getGiveCouponTemplateCounts().entrySet()) {
result.getGiveCouponTemplateCounts().put(entry.getKey(),
result.getGiveCouponTemplateCounts().getOrDefault(entry.getKey(), 0) + entry.getValue());
}
}
}
private RewardActivityMatchRespDTO.Rule getMaxMatchRewardActivityRuleSummary(RewardActivityMatchRespDTO rewardActivity, List<TradePriceCalculateSummaryRespBO.OrderItem> orderItems) {
// 1. 计算数量和价格
Integer count = TradePriceCalculatorHelper.calculateTotalCountSummary(orderItems);
Integer price = TradePriceCalculatorHelper.calculateTotalPayPriceSummary(orderItems);
assert count != null && price != null;
// 2. 倒序找一个最大优惠的规则
for (int i = rewardActivity.getRules().size() - 1; i >= 0; i--) {
RewardActivityMatchRespDTO.Rule rule = rewardActivity.getRules().get(i);
if (PromotionConditionTypeEnum.PRICE.getType().equals(rewardActivity.getConditionType())
&& price >= rule.getLimit()) {
return rule;
}
if (PromotionConditionTypeEnum.COUNT.getType().equals(rewardActivity.getConditionType())
&& count >= rule.getLimit()) {
return rule;
}
}
return null;
}
private List<TradePriceCalculateSummaryRespBO.OrderItem> filterMatchActivityOrderItemsSummary(TradePriceCalculateSummaryRespBO result, RewardActivityMatchRespDTO rewardActivity) {
return filterList(result.getItems(), orderItem -> CollUtil.contains(rewardActivity.getSpuIds(), orderItem.getSpuId()));
}
private void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result,
RewardActivityMatchRespDTO rewardActivity) {
// 1.1 获得满减送的订单项(商品)列表
@@ -9,6 +9,8 @@ import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
import cn.iocoder.yudao.module.trade.service.order.TradeOrderQueryService;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateSummaryReqBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateSummaryRespBO;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@@ -58,6 +60,11 @@ public class TradeSeckillActivityPriceCalculator implements TradePriceCalculator
TradePriceCalculatorHelper.recountAllPrice(result);
}
@Override
public void summaryCalculate(TradePriceCalculateSummaryReqBO calculateReqBO, TradePriceCalculateSummaryRespBO calculateRespBO) {
}
private SeckillValidateJoinRespDTO validateJoinSeckill(Long userId, Long activityId, Long skuId, Integer count) {
// 1. 校验是否可以参与秒杀
SeckillValidateJoinRespDTO seckillActivity = seckillActivityApi.validateJoinSeckill(activityId, skuId, count);
@@ -367,4 +367,5 @@ pf4j:
xr:
cabinet:
token: 'Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhcHAxIiwiaWF0IjoxNzQ5MTMzNzE5LCJleHAiOjIwNjAxNzM3MTl9.u2VUKxPxE2H8xldabMVRYSJKf6LN-XZfbcfyj1T7czI'
api_url: 'http://116.148.200.224:9527/ots/multiDispense'
api_url: 'http://9527.asset-cloud.com.cn/ots/multiDispense'
api_device_url: 'http://9527.asset-cloud.com.cn/ots/deviceScreen'