diff --git a/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/device/DeviceApi.java b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/device/DeviceApi.java new file mode 100644 index 0000000..5f232ad --- /dev/null +++ b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/device/DeviceApi.java @@ -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 getDeviceByDeviceIds(List deviceIds); + + int updateDeviceLastChannel(String deviceId, String cabinetChannel); +} diff --git a/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/device/dto/DeviceRespDto.java b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/device/dto/DeviceRespDto.java new file mode 100644 index 0000000..5d76cf6 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/device/dto/DeviceRespDto.java @@ -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; + +} \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/ProductSkuApi.java b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/ProductSkuApi.java index 3581fdb..d96b783 100644 --- a/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/ProductSkuApi.java +++ b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/ProductSkuApi.java @@ -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 getSkuStockSum(List barCodes); + + List getSkuListByBarCode(Collection barCodes); } diff --git a/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/dto/ProductSkuStockReqDTO.java b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/dto/ProductSkuStockReqDTO.java new file mode 100644 index 0000000..721977b --- /dev/null +++ b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/dto/ProductSkuStockReqDTO.java @@ -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; + + +} diff --git a/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/ErrorCodeConstants.java b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/ErrorCodeConstants.java index a81e0ce..9056456 100644 --- a/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/ErrorCodeConstants.java +++ b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/ErrorCodeConstants.java @@ -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, "商品汇总不存在"); } diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/device/DeviceApiImpl.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/device/DeviceApiImpl.java new file mode 100644 index 0000000..72a59a3 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/device/DeviceApiImpl.java @@ -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 getDeviceByDeviceIds(List deviceIds) { + return deviceService.getDeviceByDeviceIds(deviceIds); + } + + @Override + public int updateDeviceLastChannel(String deviceId, String cabinetChannel) { + return deviceService.updateDeviceLastChannel(deviceId, cabinetChannel); + } +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/sku/ProductSkuApiImpl.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/sku/ProductSkuApiImpl.java index ac7ac16..7f561e2 100644 --- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/sku/ProductSkuApiImpl.java +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/sku/ProductSkuApiImpl.java @@ -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 getSkuStockSum(List barCodes) { + return productSkuService.getSkuStockSum(barCodes); + } + + @Override + public List getSkuListByBarCode(Collection barCodes) { + return productSkuService.getSkuListByBarCode(barCodes); + } + } diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/device/vo/DevicePageReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/device/vo/DevicePageReqVO.java index be92acf..ab45d6e 100644 --- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/device/vo/DevicePageReqVO.java +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/device/vo/DevicePageReqVO.java @@ -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; + } \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/device/vo/DeviceRespVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/device/vo/DeviceRespVO.java index 8b1e15e..cf8f3b1 100644 --- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/device/vo/DeviceRespVO.java +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/device/vo/DeviceRespVO.java @@ -36,4 +36,9 @@ public class DeviceRespVO { @ExcelProperty("创建时间") private LocalDateTime createTime; + + @Schema(description = "当前使用的货道或柜门编号", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("当前使用的货道或柜门编号") + private String cabinetChannel; + } \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/device/vo/DeviceSaveReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/device/vo/DeviceSaveReqVO.java index 38848c5..5832e3b 100644 --- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/device/vo/DeviceSaveReqVO.java +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/device/vo/DeviceSaveReqVO.java @@ -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; + } \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/summary/SummaryController.http b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/summary/SummaryController.http new file mode 100644 index 0000000..207c7f1 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/summary/SummaryController.http @@ -0,0 +1,5 @@ + +### 获得商品汇总分页-库存求和 +GET {{baseUrl}}/product/summary/sumPage?pageNo=1&pageSize=10 +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/summary/SummaryController.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/summary/SummaryController.java new file mode 100644 index 0000000..f3b47b1 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/summary/SummaryController.java @@ -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 createSummary(@Valid @RequestBody SummarySaveReqVO createReqVO) { + return success(summaryService.createSummary(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新商品汇总") + @PreAuthorize("@ss.hasPermission('product:summary:update')") + public CommonResult 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 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 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> getSummaryPage(@Valid SummaryPageReqVO pageReqVO) { + PageResult pageResult = summaryService.getSummaryPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, SummaryRespVO.class)); + } + + @GetMapping("/sumPage") + @Operation(summary = "获得商品汇总分页-库存求和") + @PreAuthorize("@ss.hasPermission('product:summary:query')") + public CommonResult> getSummarySumPage(@Valid SummaryPageReqVO pageReqVO) { + PageResult 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 list = summaryService.getSummaryPage(pageReqVO).getList(); + // 导出 Excel + ExcelUtils.write(response, "商品汇总.xls", "数据", SummaryRespVO.class, + BeanUtils.toBean(list, SummaryRespVO.class)); + } + +} \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/summary/vo/SummaryPageReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/summary/vo/SummaryPageReqVO.java new file mode 100644 index 0000000..520203f --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/summary/vo/SummaryPageReqVO.java @@ -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; + +} \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/summary/vo/SummaryRespVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/summary/vo/SummaryRespVO.java new file mode 100644 index 0000000..5a535ba --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/summary/vo/SummaryRespVO.java @@ -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; + +} \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/summary/vo/SummarySaveReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/summary/vo/SummarySaveReqVO.java new file mode 100644 index 0000000..df0f915 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/summary/vo/SummarySaveReqVO.java @@ -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; + +} \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/summary/vo/SummarySumRespVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/summary/vo/SummarySumRespVO.java new file mode 100644 index 0000000..87fcc42 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/summary/vo/SummarySumRespVO.java @@ -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; + +} \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/device/DeviceDO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/device/DeviceDO.java index 7baabb3..8656af7 100644 --- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/device/DeviceDO.java +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/device/DeviceDO.java @@ -44,4 +44,9 @@ public class DeviceDO extends BaseDO { */ private String fileUrl; + /** + * 当前使用的货道或柜门编号 + */ + private String cabinetChannel; + } \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/summary/SummaryDO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/summary/SummaryDO.java new file mode 100644 index 0000000..3c38579 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/summary/SummaryDO.java @@ -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; + +} \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/device/DeviceMapper.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/device/DeviceMapper.java index 43215f4..651ad76 100644 --- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/device/DeviceMapper.java +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/device/DeviceMapper.java @@ -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 { .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 getDeviceByDeviceIds(List deviceIds){ + return selectList(DeviceDO::getDeviceId, deviceIds); + } + + default int updateDeviceLastChannel(String deviceId, String cabinetChannel){ + return update(new DeviceDO().setCabinetChannel(cabinetChannel), new LambdaQueryWrapperX() + .eq(DeviceDO::getDeviceId, deviceId)); + } } \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/sku/ProductSkuMapper.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/sku/ProductSkuMapper.java index 5e79582..3e64ceb 100644 --- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/sku/ProductSkuMapper.java +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/sku/ProductSkuMapper.java @@ -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 { 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("") + List getSkuStockSum(@Param("barCodes")List barCodes); + + default List getSkuListByBarCode(Collection barCodes){ + return selectList(ProductSkuDO::getBarCode, barCodes); + } } diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/summary/SummaryMapper.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/summary/SummaryMapper.java new file mode 100644 index 0000000..81447f9 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/summary/SummaryMapper.java @@ -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 { + + default PageResult selectPage(SummaryPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .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)); + } + +} \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/framework/web/config/ProductWebConfiguration.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/framework/web/config/ProductWebConfiguration.java index 9d1bf7d..4da0182 100644 --- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/framework/web/config/ProductWebConfiguration.java +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/framework/web/config/ProductWebConfiguration.java @@ -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"); + }; + } + } diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/device/DeviceService.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/device/DeviceService.java index 1ff3e79..6c52ca9 100644 --- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/device/DeviceService.java +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/device/DeviceService.java @@ -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 getDevicePage(DevicePageReqVO pageReqVO); + DeviceRespDto getDeviceByDeviceId(String deviceId); + + List getDeviceByDeviceIds(List deviceIds); + + int updateDeviceLastChannel(String deviceId, String cabinetChannel); } \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/device/DeviceServiceImpl.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/device/DeviceServiceImpl.java index bf90cec..be66145 100644 --- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/device/DeviceServiceImpl.java +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/device/DeviceServiceImpl.java @@ -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 getDeviceByDeviceIds(List deviceIds) { + List devices = deviceMapper.getDeviceByDeviceIds(deviceIds); + return BeanUtils.toBean(devices, DeviceRespDto.class); + } + + @Override + public int updateDeviceLastChannel(String deviceId, String cabinetChannel) { + return deviceMapper.updateDeviceLastChannel(deviceId, cabinetChannel); + } + } \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuService.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuService.java index a6d3f02..c2733cf 100644 --- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuService.java +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuService.java @@ -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 getSkuStockSum(List barCodes); + + List getSkuListByBarCode(Collection barCodes); } diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuServiceImpl.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuServiceImpl.java index 3632ae1..883437d 100644 --- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuServiceImpl.java +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuServiceImpl.java @@ -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 getSkuStockSum(List barCodes) { + return productSkuMapper.getSkuStockSum(barCodes); + } + + @Override + public List getSkuListByBarCode(Collection barCodes) { + List productSkuDOS = productSkuMapper.getSkuListByBarCode(barCodes); + return BeanUtils.toBean(productSkuDOS, ProductSkuRespDTO.class); + } + @Override @Transactional(rollbackFor = Exception.class) public void updateSkuList(Long spuId, List skus) { diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/summary/SummaryService.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/summary/SummaryService.java new file mode 100644 index 0000000..de83a59 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/summary/SummaryService.java @@ -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 getSummaryPage(SummaryPageReqVO pageReqVO); + + PageResult getSummarySumPage(SummaryPageReqVO pageReqVO); +} \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/summary/SummaryServiceImpl.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/summary/SummaryServiceImpl.java new file mode 100644 index 0000000..1849b59 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/summary/SummaryServiceImpl.java @@ -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 getSummaryPage(SummaryPageReqVO pageReqVO) { + return summaryMapper.selectPage(pageReqVO); + } + + @Override + public PageResult getSummarySumPage(SummaryPageReqVO pageReqVO) { + PageResult pageResult = summaryMapper.selectPage(pageReqVO); + if (CollUtil.isEmpty(pageResult.getList())) { + return PageResult.empty(); + } + // 获取所有不同的条形码 + List barCodes = CollUtil.distinct(convertList(pageResult.getList(), SummaryDO::getBarCode)); + + // 从商品SKU查询库存总和 + List productStockReqs = productSkuApi.getSkuStockSum(barCodes); + + // 创建条形码到库存信息的映射,便于快速查找 + Map stockReqMap = CollectionUtils.convertMap(productStockReqs, ProductSkuStockReqDTO::getBarCode); + + // 转换 SummaryDO 列表为 SummarySumRespVO 列表,并填充库存信息 + List 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()); + } + +} \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/AppTradeOrderController.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/AppTradeOrderController.java index f46e5f9..7484012 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/AppTradeOrderController.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/AppTradeOrderController.java @@ -87,6 +87,13 @@ public class AppTradeOrderController { return success(new AppTradeOrderCreateRespVO().setId(order.getId()).setPayOrderId(order.getPayOrderId())); } + @PostMapping("/create-summary") + @Operation(summary = "创建汇总商品订单") + public CommonResult 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 createGiftVoucherOrder(@Valid @RequestBody AppGiftVoucherOrderSettlementReqVO createReqVO) { diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppSummaryOrderSettlementReqVO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppSummaryOrderSettlementReqVO.java new file mode 100644 index 0000000..b55b9a3 --- /dev/null +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppSummaryOrderSettlementReqVO.java @@ -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 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; + } + + } + +} diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderConvert.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderConvert.java index cdf52d2..9b8400e 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderConvert.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderConvert.java @@ -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 convertList(TradeOrderDO tradeOrderDO, TradePriceCalculateRespBO calculateRespBO) { @@ -85,8 +103,21 @@ public interface TradeOrderConvert { }); } + default List 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 list) { List 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) { diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateService.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateService.java index 4577869..a3badfb 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateService.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateService.java @@ -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 giveCouponIds); TradeOrderDO createGiftVoucherOrder(Long userId, AppGiftVoucherOrderSettlementReqVO createReqVO); + + TradeOrderDO createSummaryOrder(Long userId, AppSummaryOrderSettlementReqVO createReqVO); } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java index bab577f..6fb0b4a 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java @@ -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 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 orderItems, AppSummaryOrderSettlementReqVO createReqVO) { + // 1. 执行订单创建后置处理器 + tradeOrderHandlers.forEach(handler -> handler.afterOrderCreate(order, orderItems)); + + // 2. 删除购物车商品 +// Set 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 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); + } + /** * 创建单个订单的评论 * diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeDeviceOrderHandler.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeDeviceOrderHandler.java index 8e80954..9293136 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeDeviceOrderHandler.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeDeviceOrderHandler.java @@ -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 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(); diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeGiftVoucherOrderHandler.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeGiftVoucherOrderHandler.java index 4eef2ca..77519c7 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeGiftVoucherOrderHandler.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeGiftVoucherOrderHandler.java @@ -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()); // 设置请求头 diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/TradePriceService.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/TradePriceService.java index 57713d5..59c9ce7 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/TradePriceService.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/TradePriceService.java @@ -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 calculateProductPrice(Long userId, List spuIds); + TradePriceCalculateSummaryRespBO calculateSummaryOrderPrice(TradePriceCalculateSummaryReqBO calculateReqBO); } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/TradePriceServiceImpl.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/TradePriceServiceImpl.java index ab03530..f937f88 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/TradePriceServiceImpl.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/TradePriceServiceImpl.java @@ -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 priceCalculators; @@ -152,4 +159,110 @@ public class TradePriceServiceImpl implements TradePriceService { }); } + @Override + public TradePriceCalculateSummaryRespBO calculateSummaryOrderPrice(TradePriceCalculateSummaryReqBO calculateReqBO) { + // 1.1 获得商品 SKU 数组 + List skuList = checkSummarySkuList(calculateReqBO); + // 1.2 获得商品 SPU 数组 + List spuList = checkSpuList(skuList); + + List deviceIds = calculateReqBO.getItems().stream() + .map(TradePriceCalculateSummaryReqBO.Item::getDeviceId) + .distinct() + .collect(Collectors.toList()); + List 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 checkSummarySkuList(TradePriceCalculateSummaryReqBO calculateReqBO) { + // 获得商品 SKU 数组 + //根据商品码获取sku列表 + List 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 barCodeCountMap = convertMap(calculateReqBO.getItems(), + TradePriceCalculateSummaryReqBO.Item::getBarCode, TradePriceCalculateSummaryReqBO.Item::getCount); + + // 按 barCode 分组并计算每组 stock 总和 + Map barCodeStockTotals = skus.stream() + .collect(Collectors.groupingBy( + ProductSkuRespDTO::getBarCode, + Collectors.reducing(0, ProductSkuRespDTO::getStock, Integer::sum) + )); + Map deviceCountMap = convertMap(calculateReqBO.getItems(), + TradePriceCalculateSummaryReqBO.Item::getBarCode, TradePriceCalculateSummaryReqBO.Item::getDeviceId); + // 校验商品 SKU +// List 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; + } + } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateSummaryReqBO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateSummaryReqBO.java new file mode 100644 index 0000000..2eda5d2 --- /dev/null +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateSummaryReqBO.java @@ -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 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; + + } +} diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateSummaryRespBO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateSummaryRespBO.java new file mode 100644 index 0000000..aad6ea1 --- /dev/null +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateSummaryRespBO.java @@ -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. 订单管理 + * 2. 常用订单金额说明 + * + * @author 芋道源码 + */ +@Data +public class TradePriceCalculateSummaryRespBO { + + /** + * 订单类型 + * + * 枚举 {@link TradeOrderTypeEnum} + */ + private Integer type; + + /** + * 订单价格 + */ + private Price price; + + /** + * 订单项数组 + */ + private List items; + + /** + * 营销活动数组 + * + * 只对应 {@link Price#items} 商品匹配的活动 + */ + private List promotions; + + /** + * 使用的优惠劵编号 + */ + private Long couponId; + /** + * 用户的优惠劵列表(可用 + 不可用) + */ + private List coupons; + + /** + * 会员剩余积分 + */ + private Integer totalPoint; + /** + * 使用的积分 + */ + private Integer usePoint; + + /** + * 赠送的积分 + */ + private Integer givePoint; + + /** + * 砍价活动编号 + */ + private Long bargainActivityId; + + /** + * 是否包邮 + */ + private Boolean freeDelivery; + + /** + * 赠送的优惠劵 + * + * key: 优惠劵模版编号 + * value:对应的优惠券数量 + * + * 目的:用于订单支付后赠送优惠券 + */ + private Map 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 deliveryTypes; + + /** + * 物流配置模板编号 + * + * 对应 TradeDeliveryExpressTemplateDO 的 id 编号 + */ + private Long deliveryTemplateId; + + // ========== 商品 SKU 信息 ========== + /** + * 商品重量,单位:kg 千克 + */ + private Double weight; + /** + * 商品体积,单位:m^3 平米 + */ + private Double volume; + + /** + * 商品属性数组 + */ + private List 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 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; + + } + + +} diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeBargainActivityPriceCalculator.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeBargainActivityPriceCalculator.java index 56b8e35..eb650ad 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeBargainActivityPriceCalculator.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeBargainActivityPriceCalculator.java @@ -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) { + + } + } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeCombinationActivityPriceCalculator.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeCombinationActivityPriceCalculator.java index 4021bbe..17b1ddb 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeCombinationActivityPriceCalculator.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeCombinationActivityPriceCalculator.java @@ -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) { + + } + } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeCouponPriceCalculator.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeCouponPriceCalculator.java index 2185b19..86f0f54 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeCouponPriceCalculator.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeCouponPriceCalculator.java @@ -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) { + + } + /** * 计算用户的优惠劵列表(可用 + 不可用) * diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculator.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculator.java index f056928..45c9bce 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculator.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculator.java @@ -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 selectedItem = filterList(result.getItems(), TradePriceCalculateSummaryRespBO.OrderItem::getSelected); + Set deliveryTemplateIds = convertSet(selectedItem, TradePriceCalculateSummaryRespBO.OrderItem::getDeliveryTemplateId); + Map 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 selectedSkus, Map expressTemplateMap, TradePriceCalculateSummaryRespBO result) { + // 按商品运费模板来计算商品的运费:相同的运费模板可能对应多条订单商品 SKU + Map> template2ItemMap = convertMultiMap(selectedSkus, TradePriceCalculateSummaryRespBO.OrderItem::getDeliveryTemplateId); + // 依次计算快递运费 + for (Map.Entry> entry : template2ItemMap.entrySet()) { + Long templateId = entry.getKey(); + List 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 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 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 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) { // 价格计算时,如果为空就不算~最终下单,会校验该字段不允许空 diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDiscountActivityPriceCalculator.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDiscountActivityPriceCalculator.java index 849735b..aba4cfc 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDiscountActivityPriceCalculator.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDiscountActivityPriceCalculator.java @@ -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 discountProducts = discountActivityApi.getMatchDiscountProductListBySkuIds( + convertSet(result.getItems(), TradePriceCalculateSummaryRespBO.OrderItem::getSkuId)); + Map 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; + } + /** * 获得用户的等级 * diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointActivityPriceCalculator.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointActivityPriceCalculator.java index 38ed74b..2cc1b7d 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointActivityPriceCalculator.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointActivityPriceCalculator.java @@ -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); diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointGiveCalculator.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointGiveCalculator.java index f3176e2..3177c45 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointGiveCalculator.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointGiveCalculator.java @@ -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 orderItems = filterList(result.getItems(), TradePriceCalculateSummaryRespBO.OrderItem::getSelected); + List 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); + } + } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointUsePriceCalculator.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointUsePriceCalculator.java index 2fe86f1..ca422ad 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointUsePriceCalculator.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointUsePriceCalculator.java @@ -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()) && // 积分功能是否启用 diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculator.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculator.java index a37048b..de0c977 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculator.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculator.java @@ -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); } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculatorHelper.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculatorHelper.java index 0b24e2e..b346eca 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculatorHelper.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculatorHelper.java @@ -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 spuList, List skuList) { // 创建 PriceCalculateRespDTO 对象 @@ -342,4 +353,252 @@ public class TradePriceCalculatorHelper { return String.format("%.2f", price / 100d); } + public static TradePriceCalculateSummaryRespBO buildCalculateSummaryResp(TradePriceCalculateSummaryReqBO param, List spuList, List skuList,List devices) { + // 创建 PriceCalculateRespDTO 对象 + TradePriceCalculateSummaryRespBO result = new TradePriceCalculateSummaryRespBO(); + result.setType(TradeOrderTypeEnum.NORMAL.getType()).setPromotions(new ArrayList<>()).setGiveCouponTemplateCounts(new LinkedHashMap<>()); + + // 创建它的 OrderItem 属性 + result.setItems(new ArrayList<>()); + Map spuMap = convertMap(spuList, ProductSpuRespDTO::getId); +// Map skuMap = convertMap(skuList, ProductSkuRespDTO::getId); + Map 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 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 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 orderItems, + Long id, String name, Integer type, String description, List discountPrices) { + // 创建营销明细 Item + List 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 dividePriceSummary(List orderItems, int price) { + Integer total = calculateTotalPayPriceSummary(orderItems); + assert total != null; + // 遍历每一个,进行分摊 + List 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 orderItems) { + return getSumValue(orderItems, + orderItem -> orderItem.getSelected() ? orderItem.getCount() : 0, // 未选中的情况下,不计算数量 + Integer::sum); + } + + public static void addNotMatchPromotionSummary(TradePriceCalculateSummaryRespBO result, List orderItems, + Long id, String name, Integer type, String description) { + // 创建营销明细 Item + List 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); + } } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculator.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculator.java index 1cce36d..27e450c 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculator.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculator.java @@ -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 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 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 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 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 entry : rule.getGiveCouponTemplateCounts().entrySet()) { + result.getGiveCouponTemplateCounts().put(entry.getKey(), + result.getGiveCouponTemplateCounts().getOrDefault(entry.getKey(), 0) + entry.getValue()); + } + } + } + + private RewardActivityMatchRespDTO.Rule getMaxMatchRewardActivityRuleSummary(RewardActivityMatchRespDTO rewardActivity, List 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 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 获得满减送的订单项(商品)列表 diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeSeckillActivityPriceCalculator.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeSeckillActivityPriceCalculator.java index 8ae8e27..06dbcae 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeSeckillActivityPriceCalculator.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeSeckillActivityPriceCalculator.java @@ -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); diff --git a/yudao-server/src/main/resources/application.yaml b/yudao-server/src/main/resources/application.yaml index a107521..71e83b0 100644 --- a/yudao-server/src/main/resources/application.yaml +++ b/yudao-server/src/main/resources/application.yaml @@ -367,4 +367,5 @@ pf4j: xr: cabinet: token: 'Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhcHAxIiwiaWF0IjoxNzQ5MTMzNzE5LCJleHAiOjIwNjAxNzM3MTl9.u2VUKxPxE2H8xldabMVRYSJKf6LN-XZfbcfyj1T7czI' - api_url: 'http://116.148.200.224:9527/ots/multiDispense' \ No newline at end of file + api_url: 'http://9527.asset-cloud.com.cn/ots/multiDispense' + api_device_url: 'http://9527.asset-cloud.com.cn/ots/deviceScreen' \ No newline at end of file