新增商品汇总信息,订单
This commit is contained in:
1 parent
5c62b26dc3
commit
6cfa240cdb
52 files changed
+2571
-19
No files matched your search
+25
@@ -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);
|
||||
}
|
||||
+39
@@ -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;
|
||||
|
||||
}
|
||||
+6
@@ -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);
|
||||
}
|
||||
+37
@@ -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;
|
||||
|
||||
|
||||
}
|
||||
+4
@@ -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, "商品汇总不存在");
|
||||
|
||||
}
|
||||
+41
@@ -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);
|
||||
}
|
||||
}
|
||||
+12
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+3
@@ -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;
|
||||
|
||||
}
|
||||
+5
@@ -36,4 +36,9 @@ public class DeviceRespVO {
|
||||
@ExcelProperty("创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
|
||||
@Schema(description = "当前使用的货道或柜门编号", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@ExcelProperty("当前使用的货道或柜门编号")
|
||||
private String cabinetChannel;
|
||||
|
||||
}
|
||||
+4
@@ -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;
|
||||
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
|
||||
### 获得商品汇总分页-库存求和
|
||||
GET {{baseUrl}}/product/summary/sumPage?pageNo=1&pageSize=10
|
||||
Authorization: Bearer {{token}}
|
||||
tenant-id: {{adminTenantId}}
|
||||
+103
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
+64
@@ -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;
|
||||
|
||||
}
|
||||
+79
@@ -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;
|
||||
|
||||
}
|
||||
+65
@@ -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;
|
||||
|
||||
}
|
||||
+87
@@ -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;
|
||||
|
||||
}
|
||||
+5
@@ -44,4 +44,9 @@ public class DeviceDO extends BaseDO {
|
||||
*/
|
||||
private String fileUrl;
|
||||
|
||||
/**
|
||||
* 当前使用的货道或柜门编号
|
||||
*/
|
||||
private String cabinetChannel;
|
||||
|
||||
}
|
||||
+87
@@ -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;
|
||||
|
||||
}
|
||||
+14
@@ -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));
|
||||
}
|
||||
}
|
||||
+23
@@ -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);
|
||||
}
|
||||
}
|
||||
+40
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
+14
@@ -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");
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
+7
@@ -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);
|
||||
}
|
||||
+20
-1
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+5
@@ -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);
|
||||
}
|
||||
+13
@@ -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) {
|
||||
|
||||
+56
@@ -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);
|
||||
}
|
||||
+113
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
+7
@@ -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) {
|
||||
|
||||
+91
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+49
-1
@@ -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) {
|
||||
|
||||
+3
-4
@@ -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);
|
||||
}
|
||||
+85
-4
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建单个订单的评论
|
||||
*
|
||||
|
||||
+13
-5
@@ -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();
|
||||
|
||||
+8
@@ -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
@@ -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);
|
||||
}
|
||||
+116
-3
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+122
@@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
+405
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+7
@@ -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
@@ -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) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+7
@@ -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) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算用户的优惠劵列表(可用 + 不可用)
|
||||
*
|
||||
|
||||
+168
@@ -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) {
|
||||
// 价格计算时,如果为空就不算~最终下单,会校验该字段不允许空
|
||||
|
||||
+79
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得用户的等级
|
||||
*
|
||||
|
||||
+7
@@ -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);
|
||||
|
||||
+33
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+7
@@ -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()) && // 积分功能是否启用
|
||||
|
||||
+3
@@ -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);
|
||||
}
|
||||
+259
@@ -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);
|
||||
}
|
||||
}
|
||||
+107
@@ -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 获得满减送的订单项(商品)列表
|
||||
|
||||
+7
@@ -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'
|
||||
Reference in new issue
Block a user