From 52e2996a339a725cb0421818518a06a0c9e7fa71 Mon Sep 17 00:00:00 2001 From: zjf <1799650030@qq.com> Date: Wed, 22 Jul 2026 15:06:57 +0800 Subject: [PATCH] =?UTF-8?q?=E5=9C=A8=E7=BA=BF=E7=82=B9=E9=A4=90=E5=A5=97?= =?UTF-8?q?=E9=A4=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../entity/CanteenOnlineComboItemEntity.java | 48 ++++ .../entity/CanteenOnlineSpuEntity.java | 17 +- .../pjcloud/canteen/vo/APPOnlineSpuVO.java | 16 +- .../canteen/vo/app/OnlineSpuComboItemVO.java | 27 ++ .../vo/erp/CanteenOnlineSpuSimpleVO.java | 7 +- .../CanteenOnlineSpuController.java | 66 +++-- .../mapper/CanteenOnlineComboItemMapper.java | 9 + .../service/CanteenOnlineSpuService.java | 23 ++ .../impl/CanteenOnlineSpuServiceImpl.java | 239 +++++++++++++++++- .../mapper/CanteenOnlineSpuMapper.xml | 1 + .../bsd/pjcloud/admin/api/dto/UserDTO.java | 5 - .../service/impl/SysUserServiceImpl.java | 55 ++-- .../wallet/mapper/WalletManageMapper.java | 1 + 13 files changed, 450 insertions(+), 64 deletions(-) create mode 100644 pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/entity/CanteenOnlineComboItemEntity.java create mode 100644 pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/vo/app/OnlineSpuComboItemVO.java create mode 100644 pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/mapper/CanteenOnlineComboItemMapper.java diff --git a/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/entity/CanteenOnlineComboItemEntity.java b/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/entity/CanteenOnlineComboItemEntity.java new file mode 100644 index 000000000..77305fe26 --- /dev/null +++ b/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/entity/CanteenOnlineComboItemEntity.java @@ -0,0 +1,48 @@ +package com.bsd.pjcloud.canteen.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.activerecord.Model; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +@Data +@TableName("canteen_online_combo_item") +@EqualsAndHashCode(callSuper = true) +@Schema(description = "线上点餐套餐组成明细") +public class CanteenOnlineComboItemEntity extends Model { + + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + @Schema(description = "套餐 SPU id") + private Long comboSpuId; + + @Schema(description = "组成菜品 SPU id") + private Long childSpuId; + + @Schema(description = "份数") + private Integer quantity; + + @Schema(description = "排序") + private Integer sortOrder; + + private LocalDateTime createTime; + + @TableField(exist = false) + @Schema(description = "组成菜品名称") + private String childSpuName; + + @TableField(exist = false) + @Schema(description = "组成菜品封面图") + private String childSpuPicUrl; + + @TableField(exist = false) + @Schema(description = "组成菜品价格(分)") + private Integer childSpuPrice; +} diff --git a/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/entity/CanteenOnlineSpuEntity.java b/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/entity/CanteenOnlineSpuEntity.java index a505347a8..c8d37e36d 100644 --- a/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/entity/CanteenOnlineSpuEntity.java +++ b/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/entity/CanteenOnlineSpuEntity.java @@ -10,7 +10,9 @@ import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; import com.bsd.pjcloud.common.core.util.TenantTable; + import java.time.LocalDateTime; +import java.util.List; /** * 在线点餐spu @@ -80,9 +82,15 @@ public class CanteenOnlineSpuEntity extends Model { /** * 商品状态: 0 上架(默认/开启) 1 下架(禁用) -1 回收 */ - @Schema(description="商品状态: 0 上架(默认/开启) 1 下架(禁用) -1 回收") + @Schema(description="商品状态: 0 上架(默认/开启) 1 下架(禁用) -1 回收") private Integer status; + /** + * 商品类型:1单品 2套餐 + */ + @Schema(description = "商品类型:1单品 2套餐") + private Integer spuType; + /** * 是否必选:0 否 1 是(前端下单需包含该品,如米饭) */ @@ -212,4 +220,11 @@ public class CanteenOnlineSpuEntity extends Model { @Schema(description="菜品类型名称") @TableField(exist = false) private String dishTypeName; + + /** + * 套餐组成(仅 spuType=2 时使用) + */ + @Schema(description = "套餐组成明细") + @TableField(exist = false) + private List comboItems; } \ No newline at end of file diff --git a/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/vo/APPOnlineSpuVO.java b/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/vo/APPOnlineSpuVO.java index 086e1f395..fbf4aea2d 100644 --- a/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/vo/APPOnlineSpuVO.java +++ b/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/vo/APPOnlineSpuVO.java @@ -1,16 +1,12 @@ package com.bsd.pjcloud.canteen.vo; import com.bsd.pjcloud.canteen.vo.app.OnlineSpuChoiceGroupRuleVO; -import com.baomidou.mybatisplus.annotation.*; -import com.bsd.pjcloud.common.core.util.MoneyDeserializer; -import com.bsd.pjcloud.common.core.util.MoneySerializer; -import com.bsd.pjcloud.common.core.util.TenantTable; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.bsd.pjcloud.canteen.vo.app.OnlineSpuComboItemVO; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.time.LocalDateTime; +import java.util.List; /** * 在线点餐spu @@ -19,7 +15,6 @@ import java.time.LocalDateTime; * @date 2024-12-16 23:42:19 */ @Data -@TenantTable @Schema(description = "在线点餐vo") public class APPOnlineSpuVO { @@ -109,6 +104,12 @@ public class APPOnlineSpuVO { @Schema(description = "商品状态: 0 上架 1 下架") private Integer status; + @Schema(description = "商品类型:1单品 2套餐") + private Integer spuType; + + @Schema(description = "套餐组成(spuType=2 时返回)") + private List comboItems; + /** * 是否必选:0 否 1 是 */ @@ -150,7 +151,6 @@ public class APPOnlineSpuVO { /** * 创建时间 */ - @TableField(fill = FieldFill.INSERT) @Schema(description="创建时间") private LocalDateTime createTime; diff --git a/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/vo/app/OnlineSpuComboItemVO.java b/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/vo/app/OnlineSpuComboItemVO.java new file mode 100644 index 000000000..4517706df --- /dev/null +++ b/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/vo/app/OnlineSpuComboItemVO.java @@ -0,0 +1,27 @@ +package com.bsd.pjcloud.canteen.vo.app; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@Schema(description = "套餐组成项(App)") +public class OnlineSpuComboItemVO { + + @Schema(description = "组成菜品 SPU id") + private Long childSpuId; + + @Schema(description = "组成菜品名称") + private String name; + + @Schema(description = "组成菜品封面") + private String picUrl; + + @Schema(description = "份数") + private Integer quantity; + + @Schema(description = "排序") + private Integer sortOrder; + + @Schema(description = "组成菜品单价(分,仅展示)") + private Integer price; +} diff --git a/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/vo/erp/CanteenOnlineSpuSimpleVO.java b/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/vo/erp/CanteenOnlineSpuSimpleVO.java index b84a77831..d0fdc6429 100644 --- a/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/vo/erp/CanteenOnlineSpuSimpleVO.java +++ b/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/vo/erp/CanteenOnlineSpuSimpleVO.java @@ -10,12 +10,15 @@ public class CanteenOnlineSpuSimpleVO { @Schema(description = "id") private Long id; - @Schema(description = "菜品名称") + @Schema(description = "商品名称") private String name; - @Schema(description = "菜品封面图") + @Schema(description = "商品封面图") private String picUrl; + @Schema(description = "商品价格,单位:分") + private Integer price; + @Schema(description = "关联线下菜品id") private Long linkSpuId; diff --git a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/controller/CanteenOnlineSpuController.java b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/controller/CanteenOnlineSpuController.java index 4c9ff7f37..59698fed1 100644 --- a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/controller/CanteenOnlineSpuController.java +++ b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/controller/CanteenOnlineSpuController.java @@ -94,6 +94,7 @@ public class CanteenOnlineSpuController { LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); wrapper.like(StrUtil.isNotBlank(canteenOnlineSpu.getName()), CanteenOnlineSpuEntity::getName, canteenOnlineSpu.getName()); wrapper.eq(Objects.nonNull(canteenOnlineSpu.getCategoryId()), CanteenOnlineSpuEntity::getCategoryId, canteenOnlineSpu.getCategoryId()); + wrapper.eq(Objects.nonNull(canteenOnlineSpu.getSpuType()), CanteenOnlineSpuEntity::getSpuType, canteenOnlineSpu.getSpuType()); // 根据菜品类型筛选 if (Objects.nonNull(dishType)) { @@ -105,7 +106,11 @@ public class CanteenOnlineSpuController { wrapper.isNotNull(CanteenOnlineSpuEntity::getLinkSpuId); } } - + + page.orders().clear(); + wrapper.orderByDesc(CanteenOnlineSpuEntity::getCreateTime) + .orderByDesc(CanteenOnlineSpuEntity::getId); + return R.ok(canteenOnlineSpuService.pageScope(page, wrapper)); } @@ -116,11 +121,11 @@ public class CanteenOnlineSpuController { * @param id id * @return R */ - @Operation(summary = "通过id查询", description = "通过id查询") + @Operation(summary = "通过id查询", description = "通过id查询(含套餐组成)") @GetMapping("/{id}") @PreAuthorize("@pms.hasPermission('canteen_canteenOnlineSpu_view')") public R getById(@PathVariable("id") Long id) { - return R.ok(canteenOnlineSpuService.getById(id)); + return R.ok(canteenOnlineSpuService.getDetail(id)); } /** @@ -129,20 +134,26 @@ public class CanteenOnlineSpuController { * @param canteenOnlineSpu 在线点餐spu * @return R */ - @Operation(summary = "新增在线点餐spu", description = "新增在线点餐spu") + @Operation(summary = "新增在线点餐spu", description = "新增在线点餐spu;套餐时传 spuType=2 与 comboItems") @SysLog("新增在线点餐spu") @ApiCryptoAes @PostMapping @PreAuthorize("@pms.hasPermission('canteen_canteenOnlineSpu_add')") public R save(@RequestBody CanteenOnlineSpuEntity canteenOnlineSpu) { - if (canteenOnlineSpu.getChoiceGroupId() != null) { + if (canteenOnlineSpu.getChoiceGroupId() != null + && !Objects.equals(canteenOnlineSpu.getSpuType(), 2)) { CanteenOnlineSpuChoiceGroupEntity g = canteenOnlineSpuChoiceGroupService.getById(canteenOnlineSpu.getChoiceGroupId()); if (g == null) { return R.failed("择一分组不存在(可能是前端 long 精度丢失导致 id 被圆整),请重新选择分组"); } } canteenOnlineSpu.setDeptId(SecurityUtils.getUser().getDeptId()); - return R.ok(canteenOnlineSpuService.save(canteenOnlineSpu)); + try { + return R.ok(canteenOnlineSpuService.saveDeep(canteenOnlineSpu)); + } + catch (IllegalArgumentException ex) { + return R.failed(ex.getMessage()); + } } @Operation(summary = "批量新增关联每日菜谱", description = "批量新增关联每日菜谱") @@ -160,19 +171,25 @@ public class CanteenOnlineSpuController { * @param canteenOnlineSpu 在线点餐spu * @return R */ - @Operation(summary = "修改在线点餐spu", description = "修改在线点餐spu") + @Operation(summary = "修改在线点餐spu", description = "修改在线点餐spu;套餐时传 spuType=2 与 comboItems") @SysLog("修改在线点餐spu") @ApiCryptoAes @PutMapping @PreAuthorize("@pms.hasPermission('canteen_canteenOnlineSpu_edit')") public R updateById(@RequestBody CanteenOnlineSpuEntity canteenOnlineSpu) { - if (canteenOnlineSpu.getChoiceGroupId() != null) { + if (canteenOnlineSpu.getChoiceGroupId() != null + && !Objects.equals(canteenOnlineSpu.getSpuType(), 2)) { CanteenOnlineSpuChoiceGroupEntity g = canteenOnlineSpuChoiceGroupService.getById(canteenOnlineSpu.getChoiceGroupId()); if (g == null) { return R.failed("择一分组不存在(可能是前端 long 精度丢失导致 id 被圆整),请重新选择分组"); } } - return R.ok(canteenOnlineSpuService.updateById(canteenOnlineSpu)); + try { + return R.ok(canteenOnlineSpuService.updateDeep(canteenOnlineSpu)); + } + catch (IllegalArgumentException ex) { + return R.failed(ex.getMessage()); + } } /** @@ -185,8 +202,14 @@ public class CanteenOnlineSpuController { @SysLog("通过id删除在线点餐spu") @DeleteMapping @PreAuthorize("@pms.hasPermission('canteen_canteenOnlineSpu_del')") + @Transactional(rollbackFor = Exception.class) public R removeById(@RequestBody Long[] ids) { - return R.ok(canteenOnlineSpuService.removeBatchByIds(CollUtil.toList(ids))); + List idList = CollUtil.toList(ids); + boolean ok = canteenOnlineSpuService.removeBatchByIds(idList); + if (ok && CollUtil.isNotEmpty(idList)) { + canteenOnlineSpuService.removeComboItemsByComboIds(idList); + } + return R.ok(ok); } @@ -243,7 +266,7 @@ public class CanteenOnlineSpuController { */ @ApiCryptoAes @GetMapping("/app/getCanteenSpuList") - @Operation(summary = "获取在线点餐的菜品列表", description = "商户 id 必传;返回含 status、requiredChoice、choiceGroupId+choiceGroup(择一/组合规则)、soldOut") + @Operation(summary = "获取在线点餐的菜品列表", description = "商户 id 必传;返回含 status、spuType、comboItems(套餐组成)、requiredChoice、choiceGroup、soldOut") public R> getCanteenSpuList( @RequestParam(value = "categoryId", required = false) @Parameter(description = "分类 id,可选") String categoryId, @RequestParam(value = "merchantId") @Parameter(description = "商户 id,必填") String merchantId, @@ -261,16 +284,21 @@ public class CanteenOnlineSpuController { @ApiCryptoAes @GetMapping("/app/getCanteenSpuDetails") - @Operation(summary = "获取详情接口", description = "通过spu的id") + @Operation(summary = "获取详情接口", description = "通过spu的id;套餐含 comboItems 组成") public R getCanteenSpuDetails(@RequestParam(value = "id") Long id) { - CanteenOnlineSpuEntity canteenOnlineSpuEntity = canteenOnlineSpuService.getById(id); + // 走 getDetail,保证套餐组成从明细表装载 + CanteenOnlineSpuEntity canteenOnlineSpuEntity = canteenOnlineSpuService.getDetail(id); + if (canteenOnlineSpuEntity == null) { + return R.ok(null); + } APPOnlineSpuVO bean = BeanUtils.toBean(canteenOnlineSpuEntity, APPOnlineSpuVO.class); - if (bean != null && canteenOnlineSpuEntity != null) { - Integer st = canteenOnlineSpuEntity.getStock(); - bean.setSoldOut(st == null || st <= 0); - canteenOnlineSpuService.fillAppOnlineSpuChoiceGroups(Collections.singletonList(bean), - Collections.singletonList(canteenOnlineSpuEntity)); - } + Integer st = canteenOnlineSpuEntity.getStock(); + bean.setSoldOut(st == null || st <= 0); + canteenOnlineSpuService.fillAppOnlineSpuChoiceGroups(Collections.singletonList(bean), + Collections.singletonList(canteenOnlineSpuEntity)); + // BeanUtils 无法正确把 Entity 明细转到 App VO,统一用 fill 覆盖 + canteenOnlineSpuService.fillAppComboItems(Collections.singletonList(bean), + Collections.singletonList(canteenOnlineSpuEntity)); return R.ok(bean); } diff --git a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/mapper/CanteenOnlineComboItemMapper.java b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/mapper/CanteenOnlineComboItemMapper.java new file mode 100644 index 000000000..598f5cc0f --- /dev/null +++ b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/mapper/CanteenOnlineComboItemMapper.java @@ -0,0 +1,9 @@ +package com.bsd.pjcloud.canteen.mapper; + +import com.bsd.pjcloud.canteen.entity.CanteenOnlineComboItemEntity; +import com.bsd.pjcloud.common.data.datascope.PjcloudBaseMapper; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface CanteenOnlineComboItemMapper extends PjcloudBaseMapper { +} diff --git a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/CanteenOnlineSpuService.java b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/CanteenOnlineSpuService.java index ef318afe1..ff5982b98 100644 --- a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/CanteenOnlineSpuService.java +++ b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/CanteenOnlineSpuService.java @@ -16,6 +16,29 @@ import java.util.List; public interface CanteenOnlineSpuService extends IService { + /** + * 详情(含套餐组成) + */ + CanteenOnlineSpuEntity getDetail(Long id); + + /** + * 新增(套餐时同步组成) + */ + Boolean saveDeep(CanteenOnlineSpuEntity entity); + + /** + * 修改(套餐时同步组成) + */ + Boolean updateDeep(CanteenOnlineSpuEntity entity); + + /** + * 为 App VO 批量填充套餐组成 + */ + void fillAppComboItems(List vos, List entities); + + /** 删除套餐组成明细 */ + void removeComboItemsByComboIds(List comboIds); + /** * 模板选择线上菜品分页(简化数据,用于选择弹窗) */ diff --git a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/impl/CanteenOnlineSpuServiceImpl.java b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/impl/CanteenOnlineSpuServiceImpl.java index 1f82c7fb0..0ab47078a 100644 --- a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/impl/CanteenOnlineSpuServiceImpl.java +++ b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/impl/CanteenOnlineSpuServiceImpl.java @@ -2,6 +2,7 @@ package com.bsd.pjcloud.canteen.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.bsd.pjcloud.canteen.dto.CanteenOnlineSpuShelfReqDTO; @@ -11,17 +12,20 @@ import com.bsd.pjcloud.canteen.dto.canteenonline.CanteenOnlineSpuSaveItemDto; import com.bsd.pjcloud.canteen.entity.CanteenCategoryEntity; import com.bsd.pjcloud.canteen.entity.CanteenDailyMenuEntity; import com.bsd.pjcloud.canteen.entity.CanteenOnlineCategoryEntity; +import com.bsd.pjcloud.canteen.entity.CanteenOnlineComboItemEntity; import com.bsd.pjcloud.canteen.entity.CanteenOnlineSpuChoiceGroupEntity; import com.bsd.pjcloud.canteen.entity.CanteenOnlineSpuEntity; import com.bsd.pjcloud.canteen.mapper.CanteenCategoryMapper; import com.bsd.pjcloud.canteen.mapper.CanteenDailyMenuMapper; import com.bsd.pjcloud.canteen.mapper.CanteenOnlineCategoryMapper; +import com.bsd.pjcloud.canteen.mapper.CanteenOnlineComboItemMapper; import com.bsd.pjcloud.canteen.mapper.CanteenOnlineSpuMapper; import com.bsd.pjcloud.canteen.service.CanteenOnlineSpuChoiceGroupService; import com.bsd.pjcloud.canteen.service.CanteenOnlineSpuService; import com.bsd.pjcloud.canteen.util.MealSegmentCategoryUtils; import com.bsd.pjcloud.canteen.vo.app.CanteenOnlineSpuAppVO; import com.bsd.pjcloud.canteen.vo.app.OnlineSpuChoiceGroupRuleVO; +import com.bsd.pjcloud.canteen.vo.app.OnlineSpuComboItemVO; import com.bsd.pjcloud.canteen.vo.APPOnlineSpuVO; import com.bsd.pjcloud.canteen.vo.erp.CanteenOnlineSpuSimpleVO; import com.bsd.pjcloud.common.core.util.BeanUtils; @@ -34,8 +38,12 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDate; +import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; +import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -63,6 +71,214 @@ public class CanteenOnlineSpuServiceImpl extends ServiceImpllambdaQuery() + .eq(CanteenOnlineComboItemEntity::getComboSpuId, entity.getId())); + saveComboItems(entity.getId(), entity.getSpuType(), entity.getComboItems()); + return Boolean.TRUE; + } + + @Override + public void removeComboItemsByComboIds(List comboIds) { + if (CollUtil.isEmpty(comboIds)) { + return; + } + comboItemMapper.delete(Wrappers.lambdaQuery() + .in(CanteenOnlineComboItemEntity::getComboSpuId, comboIds)); + } + + @Override + public void fillAppComboItems(List vos, List entities) { + if (CollUtil.isEmpty(vos) || CollUtil.isEmpty(entities)) { + return; + } + List comboIds = new ArrayList<>(); + for (CanteenOnlineSpuEntity e : entities) { + if (resolveSpuType(e) == 2 && e.getId() != null) { + comboIds.add(e.getId()); + } + } + Map> comboMap = loadComboItems(comboIds); + for (int i = 0; i < vos.size() && i < entities.size(); i++) { + CanteenOnlineSpuEntity entity = entities.get(i); + int type = resolveSpuType(entity); + vos.get(i).setSpuType(type); + if (type == 2) { + List lines = comboMap.getOrDefault(entity.getId(), Collections.emptyList()); + // getDetail 已装载时直接用,避免 map 再查空 + if (CollUtil.isEmpty(lines) && CollUtil.isNotEmpty(entity.getComboItems())) { + lines = entity.getComboItems(); + } + vos.get(i).setComboItems(toAppComboVos(lines)); + } + else { + vos.get(i).setComboItems(Collections.emptyList()); + } + } + } + + private int resolveSpuType(CanteenOnlineSpuEntity entity) { + Integer type = entity.getSpuType(); + if (type == null) { + return 1; + } + int v = type.intValue(); + return (v == 2) ? 2 : 1; + } + + private void normalizeSpuType(CanteenOnlineSpuEntity entity) { + if (entity.getSpuType() == null || (entity.getSpuType() != 1 && entity.getSpuType() != 2)) { + entity.setSpuType(1); + } + if (Objects.equals(entity.getSpuType(), 2)) { + // 套餐不参与择一/单品必选 + entity.setRequiredChoice(0); + entity.setChoiceGroupId(null); + entity.setLinkSpuId(null); + } + else if (entity.getRequiredChoice() == null) { + entity.setRequiredChoice(0); + } + } + + private void validateCombo(CanteenOnlineSpuEntity entity, Long selfId) { + if (!Objects.equals(entity.getSpuType(), 2)) { + return; + } + List items = entity.getComboItems(); + if (CollUtil.isEmpty(items) || items.stream().filter(i -> i.getChildSpuId() != null).count() < 2) { + throw new IllegalArgumentException("套餐至少选择 2 个组成菜品"); + } + Set childIds = new HashSet<>(); + for (CanteenOnlineComboItemEntity item : items) { + if (item.getChildSpuId() == null) { + continue; + } + if (!childIds.add(item.getChildSpuId())) { + throw new IllegalArgumentException("套餐组成菜品不能重复"); + } + if (selfId != null && Objects.equals(item.getChildSpuId(), selfId)) { + throw new IllegalArgumentException("套餐不能包含自身"); + } + } + List children = baseMapper.selectBatchIds(childIds); + if (children.size() != childIds.size()) { + throw new IllegalArgumentException("套餐组成菜品不存在或已删除"); + } + for (CanteenOnlineSpuEntity child : children) { + if (entity.getMerchantId() != null && !Objects.equals(child.getMerchantId(), entity.getMerchantId())) { + throw new IllegalArgumentException("套餐组成菜品必须属于同一商户:" + child.getName()); + } + Integer childType = child.getSpuType() == null ? 1 : child.getSpuType(); + if (Objects.equals(childType, 2)) { + throw new IllegalArgumentException("套餐组成不能再包含套餐:" + child.getName()); + } + } + } + + private void saveComboItems(Long comboSpuId, Integer spuType, List items) { + if (!Objects.equals(spuType, 2) || CollUtil.isEmpty(items)) { + return; + } + int sort = 0; + LocalDateTime now = LocalDateTime.now(); + for (CanteenOnlineComboItemEntity item : items) { + if (item.getChildSpuId() == null) { + continue; + } + CanteenOnlineComboItemEntity line = new CanteenOnlineComboItemEntity(); + line.setComboSpuId(comboSpuId); + line.setChildSpuId(item.getChildSpuId()); + line.setQuantity(item.getQuantity() == null || item.getQuantity() < 1 ? 1 : item.getQuantity()); + line.setSortOrder(item.getSortOrder() != null ? item.getSortOrder() : sort++); + line.setCreateTime(now); + comboItemMapper.insert(line); + } + } + + private Map> loadComboItems(List comboIds) { + if (CollUtil.isEmpty(comboIds)) { + return Collections.emptyMap(); + } + List lines = comboItemMapper.selectList( + Wrappers.lambdaQuery() + .in(CanteenOnlineComboItemEntity::getComboSpuId, comboIds) + .orderByAsc(CanteenOnlineComboItemEntity::getSortOrder) + .orderByAsc(CanteenOnlineComboItemEntity::getId)); + if (CollUtil.isEmpty(lines)) { + return Collections.emptyMap(); + } + Set childIds = lines.stream().map(CanteenOnlineComboItemEntity::getChildSpuId) + .filter(Objects::nonNull).collect(Collectors.toSet()); + Map childMap = childIds.isEmpty() ? Collections.emptyMap() + : baseMapper.selectBatchIds(childIds).stream() + .collect(Collectors.toMap(CanteenOnlineSpuEntity::getId, c -> c, (a, b) -> a)); + for (CanteenOnlineComboItemEntity line : lines) { + CanteenOnlineSpuEntity child = childMap.get(line.getChildSpuId()); + if (child != null) { + line.setChildSpuName(child.getName()); + line.setChildSpuPicUrl(child.getPicUrl()); + line.setChildSpuPrice(child.getPrice()); + } + } + return lines.stream().collect(Collectors.groupingBy(CanteenOnlineComboItemEntity::getComboSpuId, + LinkedHashMap::new, Collectors.toList())); + } + + private List toAppComboVos(List lines) { + if (CollUtil.isEmpty(lines)) { + return Collections.emptyList(); + } + List list = new ArrayList<>(lines.size()); + for (CanteenOnlineComboItemEntity line : lines) { + OnlineSpuComboItemVO vo = new OnlineSpuComboItemVO(); + vo.setChildSpuId(line.getChildSpuId()); + vo.setName(line.getChildSpuName()); + vo.setPicUrl(line.getChildSpuPicUrl()); + vo.setQuantity(line.getQuantity()); + vo.setSortOrder(line.getSortOrder()); + vo.setPrice(line.getChildSpuPrice()); + list.add(vo); + } + return list; + } + @Override public R> getCanteenOnlineSpuList(String categoryId, String merchantId, String time, Integer minPrice, Integer maxPrice, Integer status, @@ -130,6 +346,7 @@ public class CanteenOnlineSpuServiceImpl extends ServiceImpl wrapper = new LambdaQueryWrapper<>(); wrapper.like(StrUtil.isNotBlank(canteenOnlineSpu.getName()), CanteenOnlineSpuEntity::getName, canteenOnlineSpu.getName()) .eq(Objects.nonNull(canteenOnlineSpu.getCategoryId()), CanteenOnlineSpuEntity::getCategoryId, canteenOnlineSpu.getCategoryId()) + .eq(Objects.nonNull(canteenOnlineSpu.getMerchantId()), CanteenOnlineSpuEntity::getMerchantId, canteenOnlineSpu.getMerchantId()) + // 套餐组成只可选单品(spu_type 为空或 1) + .and(w -> w.isNull(CanteenOnlineSpuEntity::getSpuType).or().eq(CanteenOnlineSpuEntity::getSpuType, 1)) + .eq(CanteenOnlineSpuEntity::getStatus, 0) .orderByAsc(CanteenOnlineSpuEntity::getSort).orderByDesc(CanteenOnlineSpuEntity::getId); Page pageData = baseMapper.selectPageByScope(page, wrapper, DataScope.of()); - List voList = BeanUtils.toBean(pageData.getRecords(), CanteenOnlineSpuSimpleVO.class); + List voList = new ArrayList<>(); + if (CollUtil.isNotEmpty(pageData.getRecords())) { + for (CanteenOnlineSpuEntity record : pageData.getRecords()) { + CanteenOnlineSpuSimpleVO vo = new CanteenOnlineSpuSimpleVO(); + vo.setId(record.getId()); + vo.setName(record.getName()); + vo.setPicUrl(record.getPicUrl()); + vo.setPrice(record.getPrice()); + vo.setLinkSpuId(record.getLinkSpuId()); + vo.setRequiredChoice(record.getRequiredChoice()); + vo.setChoiceGroupId(record.getChoiceGroupId()); + voList.add(vo); + } + } Page resultPage = new Page<>(); resultPage.setRecords(voList); resultPage.setCurrent(pageData.getCurrent()); @@ -357,6 +591,9 @@ public class CanteenOnlineSpuServiceImpl extends ServiceImpl { + if (record.getSpuType() == null) { + record.setSpuType(1); + } if (record.getLinkSpuId() == null) { record.setDishTypeName("常驻菜品"); record.setDishType(1); diff --git a/pjcloud-canteen/pjcloud-canteen-biz/src/main/resources/mapper/CanteenOnlineSpuMapper.xml b/pjcloud-canteen/pjcloud-canteen-biz/src/main/resources/mapper/CanteenOnlineSpuMapper.xml index 620a48806..a1ca589f8 100644 --- a/pjcloud-canteen/pjcloud-canteen-biz/src/main/resources/mapper/CanteenOnlineSpuMapper.xml +++ b/pjcloud-canteen/pjcloud-canteen-biz/src/main/resources/mapper/CanteenOnlineSpuMapper.xml @@ -13,6 +13,7 @@ + diff --git a/pjcloud-upms/pjcloud-upms-api/src/main/java/com/bsd/pjcloud/admin/api/dto/UserDTO.java b/pjcloud-upms/pjcloud-upms-api/src/main/java/com/bsd/pjcloud/admin/api/dto/UserDTO.java index fdeff9f0c..10a4427b0 100644 --- a/pjcloud-upms/pjcloud-upms-api/src/main/java/com/bsd/pjcloud/admin/api/dto/UserDTO.java +++ b/pjcloud-upms/pjcloud-upms-api/src/main/java/com/bsd/pjcloud/admin/api/dto/UserDTO.java @@ -78,11 +78,6 @@ public class UserDTO extends SysUser implements BaseIsIncludeTreeInterface { */ private String nameOrPhone; - /** - * 用户id - */ - private Long userId; - private List userIds; /** diff --git a/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/service/impl/SysUserServiceImpl.java b/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/service/impl/SysUserServiceImpl.java index 25a968911..675bde987 100644 --- a/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/service/impl/SysUserServiceImpl.java +++ b/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/service/impl/SysUserServiceImpl.java @@ -444,6 +444,12 @@ public class SysUserServiceImpl extends ServiceImpl impl if (userId == null) { throw new RuntimeException("用户ID不能为空"); } + SysUser dbUser = this.getById(userId); + if (dbUser == null) { + throw new RuntimeException("用户不存在或用户ID无效,请刷新后重试"); + } + // 以库中主键为准,避免前端 Long 精度丢失后 ne 条件排除失败 + userId = dbUser.getUserId(); userDto.setUserId(userId); sysUserIdentityService.validateDto(userDto); // 更新用户表信息 @@ -460,12 +466,12 @@ public class SysUserServiceImpl extends ServiceImpl impl // 脱敏手机号不参与更新,避免把 138****1234 写回库 if (StrUtil.isNotBlank(userDto.getPhone()) && userDto.getPhone().contains("*")) { - SysUser dbUser = this.getById(userId); - sysUser.setPhone(dbUser != null ? dbUser.getPhone() : null); + sysUser.setPhone(dbUser.getPhone()); } - // 判断用户username是否重复 - if (existUsername(userId, userDto.getUsername())) { + // 用户名未变更则不校验;变更时排除自身 + if (!StrUtil.equals(dbUser.getUsername(), userDto.getUsername()) + && existUsername(userId, userDto.getUsername())) { throw new RuntimeException("该用户名已存在"); } // 判断手机号是否重复 @@ -1214,48 +1220,41 @@ public class SysUserServiceImpl extends ServiceImpl impl return com.bsd.pjcloud.common.core.util.BeanUtils.toBean(sysUsers, SimpleUserListVO.class); } + /** + * 用户名是否已被占用。 + * 编辑时必须传入正确的 userId,用 ne 排除自身;若 userId 因前端 Long 精度丢失而不等于库中值, + * 会误把当前用户算作“他人占用”。调用方应先 getById 校验并回填真实主键。 + */ private boolean existUsername(Long userId, String username) { - Long count = baseMapper.selectCount(new LambdaQueryWrapper() - .eq(ObjectUtil.isNotEmpty(username), SysUser::getUsername, username) - .ne(ObjectUtil.isNotEmpty(userId), SysUser::getUserId, userId) + if (StrUtil.isBlank(username)) { + return false; + } + Long count = baseMapper.selectCount(Wrappers.lambdaQuery() + .eq(SysUser::getUsername, username.trim()) + .ne(userId != null, SysUser::getUserId, userId) ); - if (ObjectUtil.isNotEmpty(userId)) { - return count >= 1; - } - return count > 0; + return count != null && count > 0; } private boolean existPhone(Long userId, String phone) { if (StrUtil.isBlank(phone) || phone.contains("*")) { return false; } - Long count = baseMapper.selectCount(new LambdaQueryWrapper() + Long count = baseMapper.selectCount(Wrappers.lambdaQuery() .eq(SysUser::getPhone, phone) - .ne(ObjectUtil.isNotEmpty(userId), SysUser::getUserId, userId) + .ne(userId != null, SysUser::getUserId, userId) ); - if (ObjectUtil.isNotEmpty(userId)) { - return count >= 1; - } - return count > 0; + return count != null && count > 0; } /** - * UserDTO 子类重复声明了 userId,Jackson 可能只写入父类字段,需兼容读取。 + * 读取更新用的用户ID(兼容历史请求体)。 + * 注意:UserDTO 曾重复声明 userId,可能导致 Jackson 只写入父类字段。 */ private Long resolveUserIdForUpdate(UserDTO userDto) { if (userDto.getUserId() != null) { return userDto.getUserId(); } - try { - java.lang.reflect.Field field = SysUser.class.getDeclaredField("userId"); - field.setAccessible(true); - Object value = field.get(userDto); - if (value instanceof Long) { - return (Long) value; - } - } catch (ReflectiveOperationException ignored) { - // ignore - } return null; } } diff --git a/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/mapper/WalletManageMapper.java b/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/mapper/WalletManageMapper.java index a331969cc..0dcf2127a 100644 --- a/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/mapper/WalletManageMapper.java +++ b/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/mapper/WalletManageMapper.java @@ -32,6 +32,7 @@ public interface WalletManageMapper extends PjcloudBaseMapper