From 1198def17e343e64c75e3e5f53805deb6b97baf5 Mon Sep 17 00:00:00 2001 From: zjf <1799650030@qq.com> Date: Tue, 21 Oct 2025 16:52:53 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=87=87=E8=B4=AD?= =?UTF-8?q?=E5=85=A5=E5=BA=93=E4=B9=8B=E5=90=8E=E6=9B=B4=E6=96=B0=E5=BA=93?= =?UTF-8?q?=E5=AD=98=E5=95=86=E5=93=81=E5=86=85=E8=A3=85=E6=95=B0=EF=BC=8C?= =?UTF-8?q?=E5=86=8D=E5=8E=BB=E6=9B=B4=E6=96=B0=E9=87=87=E8=B4=AD=E5=85=A5?= =?UTF-8?q?=E5=BA=93=EF=BC=8C=E5=95=86=E5=93=81=E6=80=BB=E5=BA=93=E5=AD=98?= =?UTF-8?q?=E6=B2=A1=E6=9C=89=E5=8F=98=E5=8C=96=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../InventoryRecordEditItemDTO.java | 1 + .../InventoryRecordSaveItemDTO.java | 1 + .../SmErpInventoryRecordsItemEntity.java | 6 ++ .../SmErpInventoryRecordsController.java | 10 ++- .../SmErpInventoryRecordsServiceImpl.java | 78 ++++++++++--------- 5 files changed, 58 insertions(+), 38 deletions(-) diff --git a/pjcloud-supermarket/pjcloud-supermarket-api/src/main/java/com/bsd/pjcloud/supermarket/dto/inventoryrecords/InventoryRecordEditItemDTO.java b/pjcloud-supermarket/pjcloud-supermarket-api/src/main/java/com/bsd/pjcloud/supermarket/dto/inventoryrecords/InventoryRecordEditItemDTO.java index 34c0d6cbf..8d0845edf 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-api/src/main/java/com/bsd/pjcloud/supermarket/dto/inventoryrecords/InventoryRecordEditItemDTO.java +++ b/pjcloud-supermarket/pjcloud-supermarket-api/src/main/java/com/bsd/pjcloud/supermarket/dto/inventoryrecords/InventoryRecordEditItemDTO.java @@ -96,5 +96,6 @@ public class InventoryRecordEditItemDTO { @Schema(description="包装单位id") private Long unitId; + // 注意:packageNum 不需要前端传递,由后端根据 unitId 自动查询并设置 } \ No newline at end of file diff --git a/pjcloud-supermarket/pjcloud-supermarket-api/src/main/java/com/bsd/pjcloud/supermarket/dto/inventoryrecords/InventoryRecordSaveItemDTO.java b/pjcloud-supermarket/pjcloud-supermarket-api/src/main/java/com/bsd/pjcloud/supermarket/dto/inventoryrecords/InventoryRecordSaveItemDTO.java index e4507037f..e4dd6f994 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-api/src/main/java/com/bsd/pjcloud/supermarket/dto/inventoryrecords/InventoryRecordSaveItemDTO.java +++ b/pjcloud-supermarket/pjcloud-supermarket-api/src/main/java/com/bsd/pjcloud/supermarket/dto/inventoryrecords/InventoryRecordSaveItemDTO.java @@ -55,5 +55,6 @@ public class InventoryRecordSaveItemDTO { @Schema(description="实际进价") private Integer realPrice; + // 注意:packageNum 不需要前端传递,由后端根据 unitId 自动查询并设置 } \ No newline at end of file diff --git a/pjcloud-supermarket/pjcloud-supermarket-api/src/main/java/com/bsd/pjcloud/supermarket/entity/SmErpInventoryRecordsItemEntity.java b/pjcloud-supermarket/pjcloud-supermarket-api/src/main/java/com/bsd/pjcloud/supermarket/entity/SmErpInventoryRecordsItemEntity.java index 2596741f8..89c0c36e4 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-api/src/main/java/com/bsd/pjcloud/supermarket/entity/SmErpInventoryRecordsItemEntity.java +++ b/pjcloud-supermarket/pjcloud-supermarket-api/src/main/java/com/bsd/pjcloud/supermarket/entity/SmErpInventoryRecordsItemEntity.java @@ -166,4 +166,10 @@ public class SmErpInventoryRecordsItemEntity extends Model userIds = records.stream() .map(SmErpInventoryRecordsEntity::getHandlerUserId) + .filter(Objects::nonNull) .collect(Collectors.toSet()); //收集supplierIds Set supplierIds = records.stream() .map(SmErpInventoryRecordsEntity::getSupplierId) .collect(Collectors.toSet()); //请求得到所有的userIds - Map userMap = remoteUserService.getUserMap(userIds).getData(); + Map userMap; + if (!CollectionUtils.isEmpty(userIds)){ + userMap = remoteUserService.getUserMap(userIds).getData(); + } else { + userMap = new HashMap<>(); + } // List processInstanceIds= records.stream().map(SmErpInventoryRecordsEntity::getProcessInstanceId).filter(Objects::nonNull).collect(Collectors.toList()); LambdaQueryWrapper smErpCgPlanItemEntityLambdaQueryWrapper = new LambdaQueryWrapper<>(); @@ -248,7 +256,6 @@ public class SmErpInventoryRecordsServiceImpl extends ServiceImpl smErpInventoryRecordsItemEntityList = smerpInventoryRecordsItemEntityMap.get(record.getId()); // record.setTotalAmount(totalAmount); - //todo 这里有一个问题就是出入库编辑的时候,没有更新主表的totalAmount,所以一直用的子表的totalAmount,子表的totalAmount又存在为空的情况,涉及到多个业务 //存在脏数据,判断下,防止空数据查询报错 if (!CollectionUtils.isEmpty(smErpInventoryRecordsItemEntityList)) { record.setTotalAmount(smErpInventoryRecordsItemEntityList.stream().mapToInt(item -> item.getTotalAmount()==null?0:item.getTotalAmount()).sum()); @@ -279,19 +286,6 @@ public class SmErpInventoryRecordsServiceImpl extends ServiceImpl> pageR = remoteFlowApiFlowService.queryMineTask(taskQueryParamDto); -// Page data = pageR.getData(); -// -// final Map processInstanceIdMap = (data != null && !ObjectUtil.isEmpty(data.getRecords())) -// ? data.getRecords().stream() -// .collect(Collectors.toMap(TaskDto::getProcessInstanceId, taskDto -> taskDto)) -// : Collections.emptyMap(); - // 设置商品名称到主表实体 records.forEach(plan -> { //移动端自动扣款入库,无需经办人 @@ -304,15 +298,6 @@ public class SmErpInventoryRecordsServiceImpl extends ServiceImpl dataBaseSmErpInventoryRecordsItemEntityList = smErpInventoryRecordsItemMapper .selectList(new LambdaQueryWrapper().eq(SmErpInventoryRecordsItemEntity::getInventoryId, inventoryRecordEditDTO.getId())); - - Set unitsRemoved = dataBaseSmErpInventoryRecordsItemEntityList.stream().map(s -> s.getUnitId()).collect(Collectors.toSet()); - List smErpUnitEntitiesRemoved = smErpUnitMapper.selectList(new LambdaQueryWrapper().in(SmErpUnitEntity::getId, unitsRemoved)); - Map unitMapRemoved = smErpUnitEntitiesRemoved.stream().collect(Collectors.toMap(SmErpUnitEntity::getId, unit -> unit)); - //这部分是数据库原本添加的库存数据,需要全部减掉,重新添加更新的库存数据进去 dataBaseSmErpInventoryRecordsItemEntityList.forEach(item -> { - Integer packageNum = unitMapRemoved.get(item.getUnitId()).getPackageNum(); + // 使用保存时的内装数,而不是查询最新的内装数,这样即使内装数改变了也能正确计算 + Integer packageNum = item.getPackageNum(); + // 如果之前的数据没有保存内装数(旧数据),则查询当前的内装数作为兜底 + if (packageNum == null) { + SmErpUnitEntity unit = smErpUnitMapper.selectById(item.getUnitId()); + packageNum = unit != null ? unit.getPackageNum() : 1; + } UpdateStockDTO updateStockDTO = new UpdateStockDTO(); updateStockDTO.setId(item.getErpSpuId()); if (item.getOrderType().equals(OrderTypeEnum.RK.getType())){ @@ -555,8 +543,14 @@ public class SmErpInventoryRecordsServiceImpl extends ServiceImpl smErpInventoryRecordsItemMapper.updateById(item)); + } + // 批量插入新增的明细 + if (!CollectionUtils.isEmpty(insertItems)) { + smErpInventoryRecordsItemMapper.insert(insertItems); + } List existingCodeIds = smErpInventoryRecordsItemMapper.selectList( new LambdaQueryWrapper() @@ -631,11 +625,17 @@ public class SmErpInventoryRecordsServiceImpl extends ServiceImpl smErpInventoryRecordsItemMapper.updateById(item)); + } + // 批量插入新增的明细 + if (!CollectionUtils.isEmpty(insertItems)) { + smErpInventoryRecordsItemMapper.insert(insertItems); + } List existingCodeIds = smErpInventoryRecordsItemMapper.selectList( new LambdaQueryWrapper() From a3d8df9ad86d5e9e041fec57be2c9219d3073436 Mon Sep 17 00:00:00 2001 From: zjf <1799650030@qq.com> Date: Tue, 21 Oct 2025 18:23:47 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=88=90=E6=9C=AC?= =?UTF-8?q?=E5=8D=95=E4=BB=B7=E7=9A=84=E8=AE=A1=E7=AE=97=E6=96=B9=E5=BC=8F?= =?UTF-8?q?=E4=B8=BA-=E6=96=B0=E6=88=90=E6=9C=AC=E4=BB=B7=20=3D=20(?= =?UTF-8?q?=E5=89=A9=E4=BD=99=E5=95=86=E5=93=81=E8=BF=9B=E8=B4=A7=E4=BB=B7?= =?UTF-8?q?=20=C3=97=20=E5=89=A9=E4=BD=99=E6=95=B0=E9=87=8F=20+=20?= =?UTF-8?q?=E6=96=B0=E8=BF=9B=E5=95=86=E5=93=81=E8=BF=9B=E8=B4=A7=E4=BB=B7?= =?UTF-8?q?=20=C3=97=20=E6=96=B0=E8=BF=9B=E6=95=B0=E9=87=8F)=20/=20(?= =?UTF-8?q?=E5=89=A9=E4=BD=99=E6=95=B0=E9=87=8F=20+=20=E6=96=B0=E8=BF=9B?= =?UTF-8?q?=E6=95=B0=E9=87=8F)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SmErpInventoryRecordsServiceImpl.java | 262 ++++++++++++++++-- 1 file changed, 237 insertions(+), 25 deletions(-) diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/SmErpInventoryRecordsServiceImpl.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/SmErpInventoryRecordsServiceImpl.java index b1d7c768c..037c9ae79 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/SmErpInventoryRecordsServiceImpl.java +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/SmErpInventoryRecordsServiceImpl.java @@ -68,7 +68,6 @@ public class SmErpInventoryRecordsServiceImpl extends ServiceImpl currentSpuIds = cgPlanItemEntities.stream() .map(SmErpInventoryRecordsItemEntity::getErpSpuId) + .distinct() .collect(Collectors.toList()); - List itemEntityList = smErpInventoryRecordsItemMapper.getRecordsItemListBySpuIdsAndInventoryType( - currentSpuIds + // 获取当前库存信息和商品信息 + List stockList = smErpStockService.list( + new LambdaQueryWrapper().in(SmErpStockEntity::getErpSpuId, currentSpuIds) ); - Map> costPriceMap = itemEntityList.stream() - .collect(Collectors.groupingBy(SmErpCostPriceVo::getErpSpuId)); + Map stockMap = stockList.stream() + .collect(Collectors.toMap(SmErpStockEntity::getErpSpuId, stock -> stock)); - // 计算并更新成本价 - cgPlanItemEntities.forEach(item -> { - Long erpSpuId = item.getErpSpuId(); - List smErpInventoryRecordsItemEntityList = costPriceMap.get(erpSpuId); - Double totalNum = smErpInventoryRecordsItemEntityList.stream().filter(Objects::nonNull).mapToDouble(itemEntity -> itemEntity.getPackageNum() * itemEntity.getQuantity()).sum(); + List spuList = smErpSpuMapper.selectList( + new LambdaQueryWrapper().in(SmErpSpuEntity::getId, currentSpuIds) + ); + Map spuMap = spuList.stream() + .collect(Collectors.toMap(SmErpSpuEntity::getId, spu -> spu)); - int totalRealRrice = smErpInventoryRecordsItemEntityList.stream().filter(Objects::nonNull).mapToInt(SmErpCostPriceVo::getTotalAmount).sum(); - double costPrice = totalRealRrice / totalNum; + // 按商品分组,计算每个商品的加权平均成本价 + Map> itemsBySpuId = cgPlanItemEntities.stream() + .collect(Collectors.groupingBy(SmErpInventoryRecordsItemEntity::getErpSpuId)); - SmErpSpuEntity smErpSpuEntity = new SmErpSpuEntity(); - smErpSpuEntity.setCostPrice(costPrice); - smErpSpuEntity.setId(erpSpuId); - // 更新库存成本价 - smErpSpuMapper.updateById(smErpSpuEntity); + itemsBySpuId.forEach((erpSpuId, spuItems) -> { + // 获取剩余库存数量(入库前的库存) + SmErpStockEntity stock = stockMap.get(erpSpuId); + double remainingQuantity = 0; + double remainingPurchasePrice = 0; + + if (stock != null) { + // 获取本次入库数量 + double newInQuantity = spuItems.stream() + .mapToDouble(item -> item.getPackageNum() * item.getQuantity()) + .sum(); + // 剩余数量 = 当前库存(已更新) - 本次入库数量 = 入库前的库存 + remainingQuantity = stock.getCurrentStock() - newInQuantity; + + // 如果剩余数量小于0,说明是新商品或库存异常,设为0 + if (remainingQuantity < 0) { + remainingQuantity = 0; + } + } + // 获取剩余商品的进货价(从基础单位的采购价获取,单位:分) + LambdaQueryWrapper baseUnitWrapper = new LambdaQueryWrapper<>(); + baseUnitWrapper.eq(SmErpUnitEntity::getSpuId, erpSpuId); + baseUnitWrapper.eq(SmErpUnitEntity::getIsBase, true); + SmErpUnitEntity baseUnit = smErpUnitMapper.selectOne(baseUnitWrapper); + if (baseUnit != null && baseUnit.getPurchasePrice() != null) { + remainingPurchasePrice = baseUnit.getPurchasePrice(); + } - //更新实际进价到商品包装单位里 - SmErpUnitEntity smErpUnitEntity = new SmErpUnitEntity(); - smErpUnitEntity.setPurchasePrice(item.getRealPrice()); - smErpUnitEntity.setId(item.getUnitId()); - smErpUnitMapper.updateById(smErpUnitEntity); + // 计算新进商品的总金额和总数量 + double newInTotalAmount = 0; + double newInTotalQuantity = 0; + + for (SmErpInventoryRecordsItemEntity item : spuItems) { + // 新进数量 = 包装数量(箱数) * 内装数(个/箱) + double quantity = item.getPackageNum() * item.getQuantity(); + newInTotalQuantity += quantity; + + // 新进商品总金额 = 实际进价(分/个) * 数量(个) + if (item.getRealPrice() != null) { + newInTotalAmount += item.getRealPrice() * quantity; + } + + //更新实际进价到商品包装单位里 + SmErpUnitEntity smErpUnitEntity = new SmErpUnitEntity(); + smErpUnitEntity.setPurchasePrice(item.getRealPrice()); + smErpUnitEntity.setId(item.getUnitId()); + smErpUnitMapper.updateById(smErpUnitEntity); + } + + // 计算加权平均成本价 = (剩余商品进货价*剩余数量 + 新进商品进货价*新进数量) / (剩余数量 + 新进数量) + double newCostPrice = 0; + double totalQuantity = remainingQuantity + newInTotalQuantity; + + if (totalQuantity > 0) { + double totalAmount = remainingPurchasePrice * remainingQuantity + newInTotalAmount; + newCostPrice = totalAmount / totalQuantity; + + // 调试日志:打印成本价计算过程 +// System.out.println("========== 成本价计算详情 =========="); +// System.out.println("商品ID: " + erpSpuId); +// System.out.println("剩余数量: " + remainingQuantity); +// System.out.println("剩余商品进货价(基础单位采购价): " + remainingPurchasePrice + " 分/个"); +// System.out.println("剩余总金额: " + (remainingPurchasePrice * remainingQuantity) + " 分"); +// System.out.println("新进数量: " + newInTotalQuantity); +// System.out.println("新进商品进货价(realPrice): " + (newInTotalQuantity > 0 ? (newInTotalAmount / newInTotalQuantity) : 0) + " 分/个"); +// System.out.println("新进总金额: " + newInTotalAmount + " 分"); +// System.out.println("总数量: " + totalQuantity); +// System.out.println("总金额: " + totalAmount + " 分"); +// System.out.println("新成本价: " + newCostPrice + " 分/个"); +// System.out.println("新成本价(保留2位): " + String.format("%.2f", newCostPrice) + " 分/个"); +// System.out.println("===================================="); + } else { + // 如果总数量为0,使用新进商品的平均价格 + if (newInTotalQuantity > 0) { + newCostPrice = newInTotalAmount / newInTotalQuantity; + } + } + + // 更新商品成本价(double类型会自动保留精度) + SmErpSpuEntity updateSpu = new SmErpSpuEntity(); + updateSpu.setId(erpSpuId); + updateSpu.setCostPrice(newCostPrice); + smErpSpuMapper.updateById(updateSpu); }); - - } return insert; } @@ -594,6 +666,146 @@ public class SmErpInventoryRecordsServiceImpl extends ServiceImpl savedItems = smErpInventoryRecordsItemMapper.selectList( + new LambdaQueryWrapper() + .eq(SmErpInventoryRecordsItemEntity::getInventoryId, inventoryRecordEditDTO.getId()) + ); + + // 获取涉及的商品ID + List currentSpuIds = savedItems.stream() + .map(SmErpInventoryRecordsItemEntity::getErpSpuId) + .distinct() + .collect(Collectors.toList()); + + if (!CollectionUtils.isEmpty(currentSpuIds)) { + // 获取当前库存信息和商品信息 + List stockList = smErpStockService.list( + new LambdaQueryWrapper().in(SmErpStockEntity::getErpSpuId, currentSpuIds) + ); + Map stockMap = stockList.stream() + .collect(Collectors.toMap(SmErpStockEntity::getErpSpuId, stock -> stock)); + + List spuList = smErpSpuMapper.selectList( + new LambdaQueryWrapper().in(SmErpSpuEntity::getId, currentSpuIds) + ); + Map spuMap = spuList.stream() + .collect(Collectors.toMap(SmErpSpuEntity::getId, spu -> spu)); + + // 按商品分组,计算每个商品的加权平均成本价 + Map> itemsBySpuId = savedItems.stream() + .collect(Collectors.groupingBy(SmErpInventoryRecordsItemEntity::getErpSpuId)); + + itemsBySpuId.forEach((erpSpuId, spuItems) -> { + // 获取当前库存数量(已经更新后的库存) + SmErpStockEntity stock = stockMap.get(erpSpuId); + double currentStockQuantity = 0; + + if (stock != null) { + currentStockQuantity = stock.getCurrentStock(); + } + + // 计算本次编辑的入库数量 + double editInQuantity = spuItems.stream() + .mapToDouble(item -> { + Integer pkgNum = item.getPackageNum(); + if (pkgNum == null) { + SmErpUnitEntity unit = smErpUnitMapper.selectById(item.getUnitId()); + pkgNum = unit != null ? unit.getPackageNum() : 1; + } + return pkgNum * item.getQuantity(); + }) + .sum(); + + // 剩余数量 = 当前库存 - 本次入库数量 + double remainingQuantity = currentStockQuantity - editInQuantity; + if (remainingQuantity < 0) { + remainingQuantity = 0; + } + + // 获取剩余商品的进货价(从基础单位的采购价获取,单位:分) + double remainingPurchasePrice = 0; + LambdaQueryWrapper baseUnitWrapper = new LambdaQueryWrapper<>(); + baseUnitWrapper.eq(SmErpUnitEntity::getSpuId, erpSpuId); + baseUnitWrapper.eq(SmErpUnitEntity::getIsBase, true); + SmErpUnitEntity baseUnit = smErpUnitMapper.selectOne(baseUnitWrapper); + if (baseUnit != null && baseUnit.getPurchasePrice() != null) { + remainingPurchasePrice = baseUnit.getPurchasePrice(); + } + + // 计算新进(编辑后)商品的总金额和总数量 + double newInTotalAmount = 0; + double newInTotalQuantity = 0; + + for (SmErpInventoryRecordsItemEntity item : spuItems) { + // 获取内装数 + Integer pkgNum = item.getPackageNum(); + if (pkgNum == null) { + SmErpUnitEntity unit = smErpUnitMapper.selectById(item.getUnitId()); + pkgNum = unit != null ? unit.getPackageNum() : 1; + } + + // 新进数量 = 包装数量 * 内装数 + double quantity = pkgNum * item.getQuantity(); + newInTotalQuantity += quantity; + + // 新进商品总金额 = 实际进价 * 数量 + if (item.getRealPrice() != null) { + newInTotalAmount += item.getRealPrice() * quantity; + } + + //更新实际进价到商品包装单位里 + if (item.getRealPrice() != null) { + SmErpUnitEntity smErpUnitEntity = new SmErpUnitEntity(); + smErpUnitEntity.setPurchasePrice(item.getRealPrice()); + smErpUnitEntity.setId(item.getUnitId()); + smErpUnitMapper.updateById(smErpUnitEntity); + } + } + + // 计算加权平均成本价 = (剩余商品进货价*剩余数量 + 新进商品进货价*新进数量) / (剩余数量 + 新进数量) + double newCostPrice = 0; + double totalQuantity = remainingQuantity + newInTotalQuantity; + + if (totalQuantity > 0) { + double totalAmount = remainingPurchasePrice * remainingQuantity + newInTotalAmount; + newCostPrice = totalAmount / totalQuantity; + + // 调试日志:打印成本价计算过程(编辑) + System.out.println("========== 成本价计算详情(编辑) =========="); + System.out.println("商品ID: " + erpSpuId); + System.out.println("剩余数量: " + remainingQuantity); + System.out.println("剩余商品进货价(基础单位采购价): " + remainingPurchasePrice + " 分/个"); + System.out.println("剩余总金额: " + (remainingPurchasePrice * remainingQuantity) + " 分"); + System.out.println("新进数量: " + newInTotalQuantity); + System.out.println("新进商品进货价(realPrice): " + (newInTotalQuantity > 0 ? (newInTotalAmount / newInTotalQuantity) : 0) + " 分/个"); + System.out.println("新进总金额: " + newInTotalAmount + " 分"); + System.out.println("总数量: " + totalQuantity); + System.out.println("总金额: " + totalAmount + " 分"); + System.out.println("新成本价: " + newCostPrice + " 分/个"); + System.out.println("新成本价(保留2位): " + String.format("%.2f", newCostPrice) + " 分/个"); + System.out.println("=========================================="); + } else { + // 如果总数量为0,使用新进商品的平均价格 + if (newInTotalQuantity > 0) { + newCostPrice = newInTotalAmount / newInTotalQuantity; + } + } + + // 更新商品成本价 + SmErpSpuEntity updateSpu = new SmErpSpuEntity(); + updateSpu.setId(erpSpuId); + updateSpu.setCostPrice(newCostPrice); + smErpSpuMapper.updateById(updateSpu); + }); + } + } + return i; } From 8647444e10ce462fc7b66962b171b0a4454b8fc0 Mon Sep 17 00:00:00 2001 From: zjf <1799650030@qq.com> Date: Wed, 22 Oct 2025 12:40:09 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E5=85=B1=E5=AF=8C=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/feign/RemoteMallProductService.java | 133 +++++++ .../dto/MallProductStockChangeDTO.java | 42 +++ .../supermarket/entity/MallProductEntity.java | 4 +- .../entity/MallProductSkuEntity.java | 0 .../vo/mall/MallProductDetailAppVo.java | 127 +++++++ .../vo/mall/MallProductListAppVo.java | 106 ++++++ .../vo/mall/MallProductSkuAppVo.java | 72 ++++ .../controller/MallProductController.java | 335 +++++++++++++++++- .../supermarket/dto/MallProductCreateDTO.java | 10 +- .../service/impl/MallProductServiceImpl.java | 30 +- 10 files changed, 844 insertions(+), 15 deletions(-) create mode 100644 pjcloud-supermarket/pjcloud-supermarket-api/src/main/java/com/bsd/pjcloud/supermarket/api/feign/RemoteMallProductService.java create mode 100644 pjcloud-supermarket/pjcloud-supermarket-api/src/main/java/com/bsd/pjcloud/supermarket/dto/MallProductStockChangeDTO.java rename pjcloud-supermarket/{pjcloud-supermarket-biz => pjcloud-supermarket-api}/src/main/java/com/bsd/pjcloud/supermarket/entity/MallProductEntity.java (97%) rename pjcloud-supermarket/{pjcloud-supermarket-biz => pjcloud-supermarket-api}/src/main/java/com/bsd/pjcloud/supermarket/entity/MallProductSkuEntity.java (100%) create mode 100644 pjcloud-supermarket/pjcloud-supermarket-api/src/main/java/com/bsd/pjcloud/supermarket/vo/mall/MallProductDetailAppVo.java create mode 100644 pjcloud-supermarket/pjcloud-supermarket-api/src/main/java/com/bsd/pjcloud/supermarket/vo/mall/MallProductListAppVo.java create mode 100644 pjcloud-supermarket/pjcloud-supermarket-api/src/main/java/com/bsd/pjcloud/supermarket/vo/mall/MallProductSkuAppVo.java diff --git a/pjcloud-supermarket/pjcloud-supermarket-api/src/main/java/com/bsd/pjcloud/supermarket/api/feign/RemoteMallProductService.java b/pjcloud-supermarket/pjcloud-supermarket-api/src/main/java/com/bsd/pjcloud/supermarket/api/feign/RemoteMallProductService.java new file mode 100644 index 000000000..020269ac3 --- /dev/null +++ b/pjcloud-supermarket/pjcloud-supermarket-api/src/main/java/com/bsd/pjcloud/supermarket/api/feign/RemoteMallProductService.java @@ -0,0 +1,133 @@ +/* + * + * Copyright (c) 2018-2025, pjcloud All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * Neither the name of the pig4cloud.com developer nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * Author: pjcloud + * + */ + +package com.bsd.pjcloud.supermarket.api.feign; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.bsd.pjcloud.common.core.constant.ServiceNameConstants; +import com.bsd.pjcloud.common.core.util.R; +import com.bsd.pjcloud.supermarket.dto.MallProductStockChangeDTO; +import com.bsd.pjcloud.supermarket.entity.MallProductEntity; +import com.bsd.pjcloud.supermarket.vo.mall.MallCategoryTreeVo; +import com.bsd.pjcloud.supermarket.vo.mall.MallProductDetailAppVo; +import com.bsd.pjcloud.supermarket.vo.mall.MallProductListAppVo; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; + +import java.util.List; + +/** + * 共富商城商品远程调用服务 + * + * @author zjf + * @date 2025-01-27 + */ +@FeignClient(contextId = "remoteMallProductService", value = ServiceNameConstants.SUPERMARKET_SERVICE) +public interface RemoteMallProductService { + + /** + * 批量扣减/增加库存(正数增加,负数扣减) + * + * @param changes 库存变更DTO + * @return 是否成功 + */ + @PostMapping("/mallProduct/deductStock") + R deductStock(@RequestBody MallProductStockChangeDTO changes); + + /** + * 购物车校验库存 + * + * @param productId 商品ID(多SKU模式传SKU ID,单SPU模式传SPU ID) + * @param productType 商品类型(1=SKU, 2=SPU) + * @param incrCount 扣减数量(正数或负数) + * @return 库存是否充足 + */ + @GetMapping("/mallProduct/checkStock") + R checkStock(@RequestParam("productId") Long productId, + @RequestParam("productType") Integer productType, + @RequestParam("incrCount") Double incrCount); + + /** + * 查询商城商品分类树 + * + * @param merchantId 商户ID(可选) + * @return 分类树列表 + */ + @GetMapping("/mallCategory/tree") + R> getCategoryTree(@RequestParam(value = "merchantId", required = false) Long merchantId); + + /** + * 分页查询商品列表 + * + * @param page 分页对象 + * @param categoryId 分类ID(可选) + * @param keyword 关键词(可选) + * @param merchantId 商户ID(可选) + * @param status 状态(可选) + * @return 分页结果 + */ + @GetMapping("/mallProduct/page/vo") + R> page(@RequestParam Page page, + @RequestParam(value = "categoryId", required = false) Long categoryId, + @RequestParam(value = "keyword", required = false) String keyword, + @RequestParam(value = "merchantId", required = false) Long merchantId, + @RequestParam(value = "status", required = false) String status); + + /** + * 查询所有商品(不分页) + * + * @param merchantId 商户ID(可选) + * @param status 状态(可选) + * @return 商品列表 + */ + @GetMapping("/mallProduct/list") + R> list(@RequestParam(value = "merchantId", required = false) Long merchantId, + @RequestParam(value = "status", required = false) String status); + + /** + * 根据商品ID查询详情 + * + * @param id 商品ID + * @return 商品详情(包含SKU) + */ + @GetMapping("/mallProduct/detail") + R getDetailById(@RequestParam("id") Long id); + + /** + * 根据商品ID列表批量查询详情 + * + * @param ids 商品ID列表 + * @return 商品详情列表(包含SKU) + */ + @GetMapping("/mallProduct/detailByIds") + R> getDetailByIds(@RequestParam("ids") List ids); + + /** + * 根据商品ID列表批量查询基本信息 + * + * @param ids 商品ID列表 + * @return 商品基本信息列表 + */ + @GetMapping("/mallProduct/listByIds") + R> getListByIds(@RequestParam("ids") List ids); +} + diff --git a/pjcloud-supermarket/pjcloud-supermarket-api/src/main/java/com/bsd/pjcloud/supermarket/dto/MallProductStockChangeDTO.java b/pjcloud-supermarket/pjcloud-supermarket-api/src/main/java/com/bsd/pjcloud/supermarket/dto/MallProductStockChangeDTO.java new file mode 100644 index 000000000..17f941714 --- /dev/null +++ b/pjcloud-supermarket/pjcloud-supermarket-api/src/main/java/com/bsd/pjcloud/supermarket/dto/MallProductStockChangeDTO.java @@ -0,0 +1,42 @@ +package com.bsd.pjcloud.supermarket.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.List; + +/** + * 共富商城商品库存变更DTO + * + * @author zjf + * @date 2025-01-27 + */ +@Data +@Schema(description = "共富商城商品库存变更DTO") +public class MallProductStockChangeDTO { + + @NotNull(message = "商品列表不能为空") + @Schema(description = "商品列表") + private List items; + + @Schema(description = "下单设备类型,0默认安卓机,1移动端") + private Integer deviceType; + + @Data + @Schema(description = "库存变更明细") + public static class Item { + + @NotNull(message = "商品ID不能为空(SPU或SKU ID)") + @Schema(description = "商品ID(多SKU模式传SKU ID,单SPU模式传SPU ID)") + private Long id; + + @NotNull(message = "库存变化数量不能为空") + @Schema(description = "库存变化数量(正数增加,负数扣减)") + private Double incrCount; + + @Schema(description = "商品类型(1=SKU, 2=SPU)") + private Integer productType; + } +} + diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/entity/MallProductEntity.java b/pjcloud-supermarket/pjcloud-supermarket-api/src/main/java/com/bsd/pjcloud/supermarket/entity/MallProductEntity.java similarity index 97% rename from pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/entity/MallProductEntity.java rename to pjcloud-supermarket/pjcloud-supermarket-api/src/main/java/com/bsd/pjcloud/supermarket/entity/MallProductEntity.java index 51cdbfff5..639535578 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/entity/MallProductEntity.java +++ b/pjcloud-supermarket/pjcloud-supermarket-api/src/main/java/com/bsd/pjcloud/supermarket/entity/MallProductEntity.java @@ -99,8 +99,8 @@ public class MallProductEntity extends Model { /** * 计量单位 */ - @Schema(description="计量单位") - private String unit; + @Schema(description="商品性质") + private String productNature; /** * 状态(1上架 0下架) diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/entity/MallProductSkuEntity.java b/pjcloud-supermarket/pjcloud-supermarket-api/src/main/java/com/bsd/pjcloud/supermarket/entity/MallProductSkuEntity.java similarity index 100% rename from pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/entity/MallProductSkuEntity.java rename to pjcloud-supermarket/pjcloud-supermarket-api/src/main/java/com/bsd/pjcloud/supermarket/entity/MallProductSkuEntity.java diff --git a/pjcloud-supermarket/pjcloud-supermarket-api/src/main/java/com/bsd/pjcloud/supermarket/vo/mall/MallProductDetailAppVo.java b/pjcloud-supermarket/pjcloud-supermarket-api/src/main/java/com/bsd/pjcloud/supermarket/vo/mall/MallProductDetailAppVo.java new file mode 100644 index 000000000..60ea9f35c --- /dev/null +++ b/pjcloud-supermarket/pjcloud-supermarket-api/src/main/java/com/bsd/pjcloud/supermarket/vo/mall/MallProductDetailAppVo.java @@ -0,0 +1,127 @@ +package com.bsd.pjcloud.supermarket.vo.mall; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 共富商城移动端商品详情VO + * + * @author zjf + * @date 2025-01-27 + */ +@Data +@Schema(description = "共富商城移动端商品详情VO") +public class MallProductDetailAppVo { + + /** + * 商品ID + */ + @Schema(description = "商品ID") + private Long id; + + /** + * 商户ID + */ + @Schema(description = "商户ID") + private Long merchantId; + + /** + * 类目ID + */ + @Schema(description = "类目ID") + private Long categoryId; + + /** + * 商品标题 + */ + @Schema(description = "商品标题") + private String title; + + /** + * 副标题 + */ + @Schema(description = "副标题") + private String subTitle; + + /** + * 品牌 + */ + @Schema(description = "品牌") + private String brand; + + /** + * 主图 + */ + @Schema(description = "主图") + private String coverImage; + + /** + * 轮播图 + */ + @Schema(description = "轮播图") + private String carouselImages; + + /** + * 定价方式(1多SKU 2单价SPU) + */ + @Schema(description = "定价方式") + private Integer priceType; + + /** + * SPU售价(单位:分) + */ + @Schema(description = "SPU售价(单位:分)") + private Integer spuPrice; + + /** + * 划线价(单位:分) + */ + @Schema(description = "划线价(单位:分)") + private Integer originalPrice; + + /** + * 总库存 + */ + @Schema(description = "总库存") + private Double totalStock; + + /** + * 商品性质 + */ + @Schema(description = "商品性质") + private String productNature; + + /** + * 状态 + */ + @Schema(description = "状态") + private String status; + + /** + * 开售时间 + */ + @Schema(description = "开售时间") + private LocalDateTime saleStartTime; + + /** + * 停售时间 + */ + @Schema(description = "停售时间") + private LocalDateTime saleEndTime; + + /** + * 详情描述 + */ + @Schema(description = "详情描述") + private String description; + + /** + * SKU列表 + */ + @Schema(description = "SKU列表") + private List skuList; +} + diff --git a/pjcloud-supermarket/pjcloud-supermarket-api/src/main/java/com/bsd/pjcloud/supermarket/vo/mall/MallProductListAppVo.java b/pjcloud-supermarket/pjcloud-supermarket-api/src/main/java/com/bsd/pjcloud/supermarket/vo/mall/MallProductListAppVo.java new file mode 100644 index 000000000..dbb2045db --- /dev/null +++ b/pjcloud-supermarket/pjcloud-supermarket-api/src/main/java/com/bsd/pjcloud/supermarket/vo/mall/MallProductListAppVo.java @@ -0,0 +1,106 @@ +package com.bsd.pjcloud.supermarket.vo.mall; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 共富商城移动端商品列表VO + * + * @author zjf + * @date 2025-01-27 + */ +@Data +@Schema(description = "共富商城移动端商品列表VO") +public class MallProductListAppVo { + + /** + * 商品ID + */ + @Schema(description = "商品ID") + private Long id; + + /** + * 商户ID + */ + @Schema(description = "商户ID") + private Long merchantId; + + /** + * 类目ID + */ + @Schema(description = "类目ID") + private Long categoryId; + + /** + * 商品标题 + */ + @Schema(description = "商品标题") + private String title; + + /** + * 副标题 + */ + @Schema(description = "副标题") + private String subTitle; + + /** + * 品牌 + */ + @Schema(description = "品牌") + private String brand; + + /** + * 主图 + */ + @Schema(description = "主图") + private String coverImage; + + /** + * 轮播图 + */ + @Schema(description = "轮播图") + private String carouselImages; + + /** + * 定价方式(1多SKU 2单价SPU) + */ + @Schema(description = "定价方式") + private Integer priceType; + + /** + * SPU售价(单位:分) + */ + @Schema(description = "SPU售价(单位:分)") + private Integer spuPrice; + + /** + * 划线价(单位:分) + */ + @Schema(description = "划线价(单位:分)") + private Integer originalPrice; + + /** + * 总库存 + */ + @Schema(description = "总库存") + private Double totalStock; + + /** + * 商品性质 + */ + @Schema(description = "商品性质") + private String productNature; + + /** + * 状态 + */ + @Schema(description = "状态") + private String status; + + /** + * 最低价格(从SKU中计算,单位:分) + */ + @Schema(description = "最低价格(单位:分)") + private Integer minPrice; +} + diff --git a/pjcloud-supermarket/pjcloud-supermarket-api/src/main/java/com/bsd/pjcloud/supermarket/vo/mall/MallProductSkuAppVo.java b/pjcloud-supermarket/pjcloud-supermarket-api/src/main/java/com/bsd/pjcloud/supermarket/vo/mall/MallProductSkuAppVo.java new file mode 100644 index 000000000..43c8b20e7 --- /dev/null +++ b/pjcloud-supermarket/pjcloud-supermarket-api/src/main/java/com/bsd/pjcloud/supermarket/vo/mall/MallProductSkuAppVo.java @@ -0,0 +1,72 @@ +package com.bsd.pjcloud.supermarket.vo.mall; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 共富商城移动端商品SKU VO + * + * @author zjf + * @date 2025-01-27 + */ +@Data +@Schema(description = "共富商城移动端商品SKU VO") +public class MallProductSkuAppVo { + + /** + * SKU ID + */ + @Schema(description = "SKU ID") + private Long id; + + /** + * SPU ID + */ + @Schema(description = "SPU ID") + private Long productId; + + /** + * SKU编码 + */ + @Schema(description = "SKU编码") + private String skuCode; + + /** + * SKU名称(规格组合) + */ + @Schema(description = "SKU名称(规格组合)") + private String skuName; + + /** + * 售价(单位:分) + */ + @Schema(description = "售价(单位:分)") + private Integer price; + + + /** + * 库存 + */ + @Schema(description = "库存") + private Double stock; + + /** + * 条码 + */ + @Schema(description = "条码") + private String barcode; + + /** + * 重量(克) + */ + @Schema(description = "重量(克)") + private Integer weightG; + + + /** + * 状态(1启用 0停用) + */ + @Schema(description = "状态(1启用 0停用)") + private String status; +} + diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/MallProductController.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/MallProductController.java index 88e2a5c41..81de84093 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/MallProductController.java +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/MallProductController.java @@ -9,8 +9,14 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.bsd.pjcloud.common.core.util.R; import com.bsd.pjcloud.common.log.annotation.SysLog; import com.bsd.pjcloud.supermarket.entity.MallProductEntity; +import com.bsd.pjcloud.supermarket.entity.MallProductSkuEntity; import com.bsd.pjcloud.supermarket.service.MallProductService; +import com.bsd.pjcloud.supermarket.service.MallProductSkuService; import com.bsd.pjcloud.supermarket.dto.MallProductCreateDTO; +import com.bsd.pjcloud.supermarket.dto.MallProductStockChangeDTO; +import com.bsd.pjcloud.supermarket.vo.mall.MallProductDetailAppVo; +import com.bsd.pjcloud.supermarket.vo.mall.MallProductListAppVo; +import com.bsd.pjcloud.supermarket.vo.mall.MallProductSkuAppVo; import jakarta.validation.Valid; import org.springframework.security.access.prepost.PreAuthorize; import com.bsd.pjcloud.common.excel.annotation.ResponseExcel; @@ -39,21 +45,75 @@ import java.util.Objects; public class MallProductController { private final MallProductService mallProductService; + private final MallProductSkuService mallProductSkuService; /** - * 分页查询 + * 分页查询(管理端) * @param page 分页对象 * @param mallProduct 共富商城商品信息 * @return */ - @Operation(summary = "分页查询" , description = "分页查询" ) + @Operation(summary = "分页查询(管理端)" , description = "分页查询" ) @GetMapping("/page" ) @PreAuthorize("@pms.hasPermission('supermarket_mallProduct_view')" ) public R getMallProductPage(@ParameterObject Page page, @ParameterObject MallProductEntity mallProduct) { LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); wrapper.eq(Objects.nonNull(mallProduct.getCategoryId()),MallProductEntity::getCategoryId,mallProduct.getCategoryId()); wrapper.like(StrUtil.isNotBlank(mallProduct.getTitle()),MallProductEntity::getTitle,mallProduct.getTitle()); - return R.ok(mallProductService.page(page, wrapper)); + wrapper.eq(Objects.nonNull(mallProduct.getMerchantId()),MallProductEntity::getMerchantId,mallProduct.getMerchantId()); + wrapper.eq(StrUtil.isNotBlank(mallProduct.getStatus()),MallProductEntity::getStatus,mallProduct.getStatus()); + + // 如果是 Feign 调用,返回 MallProductListAppVo 格式 + Page resultPage = mallProductService.page(page, wrapper); + return R.ok(resultPage); + } + + /** + * 分页查询商品列表(Feign专用 - 返回VO格式) + * @param page 分页对象 + * @param categoryId 分类ID + * @param keyword 关键词 + * @param merchantId 商户ID + * @param status 状态 + * @return + */ + @Operation(summary = "分页查询商品列表(Feign)" , description = "分页查询商品列表,返回VO格式" ) + @GetMapping("/page/vo" ) + public R> getProductPageVo( + @ParameterObject Page page, + @RequestParam(value = "categoryId", required = false) Long categoryId, + @RequestParam(value = "keyword", required = false) String keyword, + @RequestParam(value = "merchantId", required = false) Long merchantId, + @RequestParam(value = "status", required = false) String status) { + try { + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.eq(categoryId != null, MallProductEntity::getCategoryId, categoryId); + wrapper.eq(merchantId != null, MallProductEntity::getMerchantId, merchantId); + wrapper.eq(StrUtil.isNotBlank(status), MallProductEntity::getStatus, status); + + // 关键词搜索:标题或副标题 + if (StrUtil.isNotBlank(keyword)) { + wrapper.and(w -> w.like(MallProductEntity::getTitle, keyword) + .or() + .like(MallProductEntity::getSubTitle, keyword)); + } + + // 排序:按创建时间倒序 + wrapper.orderByDesc(MallProductEntity::getCreateTime); + + Page resultPage = mallProductService.page(page, wrapper); + + // 转换为 VO 格式 + Page voPage = new Page<>(resultPage.getCurrent(), resultPage.getSize(), resultPage.getTotal()); + List voList = resultPage.getRecords().stream() + .map(this::convertToListVo) + .collect(java.util.stream.Collectors.toList()); + voPage.setRecords(voList); + + return R.ok(voPage); + } catch (Exception e) { + return R.failed("分页查询商品失败:" + e.getMessage()); + } } @@ -149,4 +209,273 @@ public class MallProductController { public List export(MallProductEntity mallProduct,Long[] ids) { return mallProductService.list(Wrappers.lambdaQuery(mallProduct).in(ArrayUtil.isNotEmpty(ids), MallProductEntity::getId, ids)); } + + // ==================== Feign 接口方法 ==================== + + /** + * 批量扣减/增加库存(正数增加,负数扣减) + */ + @Operation(summary = "批量扣减/增加库存", description = "批量扣减/增加库存") + @PostMapping("/deductStock") + public R deductStock(@Valid @RequestBody MallProductStockChangeDTO changes) { + try { + if (CollUtil.isEmpty(changes.getItems())) { + return R.failed("库存变更项不能为空"); + } + + for (MallProductStockChangeDTO.Item item : changes.getItems()) { + if (item.getProductType() == null) { + return R.failed("商品类型不能为空"); + } + + if (item.getProductType() == 1) { + // SKU 模式:更新 SKU 库存 + MallProductSkuEntity sku = mallProductSkuService.getById(item.getId()); + if (sku == null) { + return R.failed("SKU不存在:" + item.getId()); + } + + Double newStock = (sku.getStock() != null ? sku.getStock() : 0D) + item.getIncrCount(); + if (newStock < 0) { + return R.failed("SKU库存不足:" + sku.getSkuName()); + } + + sku.setStock(newStock); + mallProductSkuService.updateById(sku); + + // 更新 SPU 总库存 + updateSpuTotalStock(sku.getProductId()); + + } else if (item.getProductType() == 2) { + // SPU 模式:直接更新 SPU 总库存 + MallProductEntity product = mallProductService.getById(item.getId()); + if (product == null) { + return R.failed("商品不存在:" + item.getId()); + } + + Double newStock = (product.getTotalStock() != null ? product.getTotalStock() : 0D) + item.getIncrCount(); + if (newStock < 0) { + return R.failed("商品库存不足:" + product.getTitle()); + } + + product.setTotalStock(newStock); + mallProductService.updateById(product); + } + } + + return R.ok(true); + } catch (Exception e) { + return R.failed("库存变更失败:" + e.getMessage()); + } + } + + /** + * 购物车校验库存 + */ + @Operation(summary = "购物车校验库存", description = "购物车校验库存") + @GetMapping("/checkStock") + public R checkStock(@RequestParam("productId") Long productId, + @RequestParam("productType") Integer productType, + @RequestParam("incrCount") Double incrCount) { + try { + if (productType == 1) { + // SKU 模式 + MallProductSkuEntity sku = mallProductSkuService.getById(productId); + if (sku == null) { + return R.ok(false); + } + + Double currentStock = sku.getStock() != null ? sku.getStock() : 0D; + return R.ok(currentStock + incrCount >= 0); + + } else if (productType == 2) { + // SPU 模式 + MallProductEntity product = mallProductService.getById(productId); + if (product == null) { + return R.ok(false); + } + + Double currentStock = product.getTotalStock() != null ? product.getTotalStock() : 0D; + return R.ok(currentStock + incrCount >= 0); + } + + return R.failed("无效的商品类型"); + } catch (Exception e) { + return R.failed("校验库存失败:" + e.getMessage()); + } + } + + /** + * 查询商品列表(不分页) + */ + @Operation(summary = "查询商品列表", description = "查询商品列表(不分页)") + @GetMapping("/list") + public R> list(@RequestParam(value = "merchantId", required = false) Long merchantId, + @RequestParam(value = "status", required = false) String status) { + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.eq(merchantId != null, MallProductEntity::getMerchantId, merchantId); + wrapper.eq(StrUtil.isNotBlank(status), MallProductEntity::getStatus, status); + return R.ok(mallProductService.list(wrapper)); + } + + /** + * 根据商品ID查询详情 + */ + @Operation(summary = "根据商品ID查询详情", description = "根据商品ID查询详情(包含SKU)") + @GetMapping("/detail") + public R getDetailById(@RequestParam("id") Long id) { + try { + MallProductEntity product = mallProductService.getById(id); + if (product == null) { + return R.failed("商品不存在"); + } + + MallProductDetailAppVo vo = convertToDetailVo(product); + return R.ok(vo); + } catch (Exception e) { + return R.failed("查询商品详情失败:" + e.getMessage()); + } + } + + /** + * 根据商品ID列表批量查询详情 + */ + @Operation(summary = "根据商品ID列表批量查询详情", description = "根据商品ID列表批量查询详情(包含SKU)") + @GetMapping("/detailByIds") + public R> getDetailByIds(@RequestParam("ids") List ids) { + try { + List products = mallProductService.listByIds(ids); + List voList = products.stream() + .map(this::convertToDetailVo) + .collect(java.util.stream.Collectors.toList()); + return R.ok(voList); + } catch (Exception e) { + return R.failed("批量查询商品详情失败:" + e.getMessage()); + } + } + + /** + * 根据商品ID列表批量查询基本信息 + */ + @Operation(summary = "根据商品ID列表批量查询基本信息", description = "根据商品ID列表批量查询基本信息") + @GetMapping("/listByIds") + public R> getListByIds(@RequestParam("ids") List ids) { + return R.ok(mallProductService.listByIds(ids)); + } + + // ==================== 私有辅助方法 ==================== + + /** + * 更新SPU总库存(从所有SKU汇总) + */ + private void updateSpuTotalStock(Long productId) { + List skuList = mallProductSkuService.list( + Wrappers.lambdaQuery() + .eq(MallProductSkuEntity::getProductId, productId) + ); + + Double totalStock = skuList.stream() + .mapToDouble(sku -> sku.getStock() != null ? sku.getStock() : 0D) + .sum(); + + MallProductEntity product = new MallProductEntity(); + product.setId(productId); + product.setTotalStock(totalStock); + mallProductService.updateById(product); + } + + /** + * 转换为列表VO + */ + private MallProductListAppVo convertToListVo(MallProductEntity product) { + MallProductListAppVo vo = new MallProductListAppVo(); + vo.setId(product.getId()); + vo.setMerchantId(product.getMerchantId()); + vo.setCategoryId(product.getCategoryId()); + vo.setTitle(product.getTitle()); + vo.setSubTitle(product.getSubTitle()); + vo.setBrand(product.getBrand()); + vo.setCoverImage(product.getCoverImage()); + vo.setCarouselImages(product.getCarouselImages()); + vo.setPriceType(product.getPriceType()); + vo.setSpuPrice(product.getSpuPrice()); + vo.setOriginalPrice(product.getOriginalPrice()); + vo.setTotalStock(product.getTotalStock()); + vo.setProductNature(product.getProductNature()); + vo.setStatus(product.getStatus()); + + // 如果是多SKU模式,计算最低价格 + if (product.getPriceType() != null && product.getPriceType() == 1) { + List skuList = mallProductSkuService.list( + Wrappers.lambdaQuery() + .eq(MallProductSkuEntity::getProductId, product.getId()) + .orderByAsc(MallProductSkuEntity::getPrice) + ); + + if (CollUtil.isNotEmpty(skuList)) { + vo.setMinPrice(skuList.get(0).getPrice()); + } + } else { + // 单SPU模式,最低价格就是SPU价格 + vo.setMinPrice(product.getSpuPrice()); + } + + return vo; + } + + /** + * 转换为详情VO + */ + private MallProductDetailAppVo convertToDetailVo(MallProductEntity product) { + MallProductDetailAppVo vo = new MallProductDetailAppVo(); + vo.setId(product.getId()); + vo.setMerchantId(product.getMerchantId()); + vo.setCategoryId(product.getCategoryId()); + vo.setTitle(product.getTitle()); + vo.setSubTitle(product.getSubTitle()); + vo.setBrand(product.getBrand()); + vo.setCoverImage(product.getCoverImage()); + vo.setCarouselImages(product.getCarouselImages()); + vo.setPriceType(product.getPriceType()); + vo.setSpuPrice(product.getSpuPrice()); + vo.setOriginalPrice(product.getOriginalPrice()); + vo.setTotalStock(product.getTotalStock()); + vo.setProductNature(product.getProductNature()); + vo.setStatus(product.getStatus()); + vo.setSaleStartTime(product.getSaleStartTime()); + vo.setSaleEndTime(product.getSaleEndTime()); + vo.setDescription(product.getDescription()); + + // 如果是多SKU模式,查询SKU列表 + if (product.getPriceType() != null && product.getPriceType() == 1) { + List skuList = mallProductSkuService.list( + Wrappers.lambdaQuery() + .eq(MallProductSkuEntity::getProductId, product.getId()) + ); + + List skuVoList = skuList.stream() + .map(this::convertToSkuVo) + .collect(java.util.stream.Collectors.toList()); + vo.setSkuList(skuVoList); + } + + return vo; + } + + /** + * 转换为SKU VO + */ + private MallProductSkuAppVo convertToSkuVo(MallProductSkuEntity sku) { + MallProductSkuAppVo vo = new MallProductSkuAppVo(); + vo.setId(sku.getId()); + vo.setProductId(sku.getProductId()); + vo.setSkuCode(sku.getSkuCode()); + vo.setSkuName(sku.getSkuName()); + vo.setPrice(sku.getPrice()); + vo.setStock(sku.getStock()); + vo.setBarcode(sku.getBarcode()); + vo.setWeightG(sku.getWeightG()); + vo.setStatus(sku.getStatus()); + return vo; + } } \ No newline at end of file diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/dto/MallProductCreateDTO.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/dto/MallProductCreateDTO.java index 6908008e1..934a94a6e 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/dto/MallProductCreateDTO.java +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/dto/MallProductCreateDTO.java @@ -91,11 +91,17 @@ public class MallProductCreateDTO { @Schema(description = "划线价") private Integer originalPrice; + /** + * 总库存(冗余) + */ + @Schema(description = "总库存(冗余)") + private Double totalStock; + /** * 计量单位 */ - @Schema(description = "计量单位") - private String unit; + @Schema(description = "商品性质") + private String productNature; /** * 状态(1上架 0下架) diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/MallProductServiceImpl.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/MallProductServiceImpl.java index 10dbb1724..45f5db9ed 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/MallProductServiceImpl.java +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/MallProductServiceImpl.java @@ -50,7 +50,7 @@ public class MallProductServiceImpl extends ServiceImpl