This commit is contained in:
dzc committed 2026-04-10 14:37:22 +08:00
commit df32a862af
20 files changed
+711 -5

No files matched your search

@@ -24,6 +24,7 @@ import com.bsd.pjcloud.canteen.dto.CanteenSpuUpdateStockReqDTO;
import com.bsd.pjcloud.canteen.entity.CanteenOnlineSpuEntity;
import com.bsd.pjcloud.canteen.vo.APPOnlineSpuVO;
import com.bsd.pjcloud.canteen.vo.app.CanteenOnlineSpuAppVO;
import com.bsd.pjcloud.canteen.vo.app.OnlineSpuChoiceGroupsVO;
import com.bsd.pjcloud.common.core.constant.ServiceNameConstants;
import com.bsd.pjcloud.common.core.util.R;
import org.springframework.cloud.openfeign.FeignClient;
@@ -51,6 +52,14 @@ public interface RemoteOnlineCanteenSpuService {
R<List<APPOnlineSpuVO>> getCanteenSpuList(@RequestParam(value = "categoryId", required = false) String categoryId,
@RequestParam(value = "merchantId") String merchantId);
/**
* 下单前获取分组规则(聚合)
*
* @param merchantId 商户 id
*/
@GetMapping("/canteenOnlineSpu/app/getChoiceGroups")
R<OnlineSpuChoiceGroupsVO> getChoiceGroups(@RequestParam(value = "merchantId") String merchantId);
/**
@@ -42,6 +42,15 @@ public class CanteenOnlineSpuSaveItemDto {
@Schema(description="商品状态: 0 上架(默认/开启) 1 下架(禁用) -1 回收")
private Integer status;
/**
* 是否必选:0 否 1 是
*/
@Schema(description = "是否必选:0否 1是,默认 0")
private Integer requiredChoice;
@Schema(description = "择一/组合分组 id,须已存在 canteen_online_spu_choice_group")
private Long choiceGroupId;
/**
* 商品价格,单位使用:分
*/
@@ -0,0 +1,93 @@
package com.bsd.pjcloud.canteen.entity;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import com.bsd.pjcloud.common.core.util.TenantTable;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
/**
* 在线点餐 SPU 择一/组合规则组(同组商品共享 min/max 规则)
*/
@Data
@TenantTable
@TableName("canteen_online_spu_choice_group")
@EqualsAndHashCode(callSuper = true)
@Schema(description = "在线点餐 SPU 择一/组合规则组")
public class CanteenOnlineSpuChoiceGroupEntity extends Model<CanteenOnlineSpuChoiceGroupEntity> {
@TableId(type = IdType.ASSIGN_ID)
@Schema(description = "主键")
private Long id;
@Schema(description = "分组名称,如:主食、饮料")
@TableField("name")
private String name;
@Schema(description = "商户 id")
@TableField("merchant_id")
private Long merchantId;
@Schema(description = "部门 id")
@TableField("dept_id")
private Long deptId;
/**
* 购物车内该组中「数量&gt;0 的不同 SPU 种数」下限;0 表示整组可不选(与单品 required_choice 独立)
*/
@Schema(description = "至少几种 SPU 要有数量;0 表示整组可选")
@TableField("min_kinds")
private Integer minKinds;
/**
* 至多几种;0 表示不限制种数上限
*/
@Schema(description = "至多几种;0 表示不限制")
@TableField("max_kinds")
private Integer maxKinds;
@Schema(description = "组内合计件数下限,0 不校验")
@TableField("min_total_qty")
private Integer minTotalQty;
@Schema(description = "组内合计件数上限,0 不限制")
@TableField("max_total_qty")
private Integer maxTotalQty;
@Schema(description = "排序")
@TableField("sort")
private Integer sort;
@Schema(description = "创建人")
@TableField(value = "create_by", fill = FieldFill.INSERT)
private String createBy;
@Schema(description = "创建时间")
@TableField(value = "create_time", fill = FieldFill.INSERT)
private LocalDateTime createTime;
@Schema(description = "更新人")
@TableField(value = "update_by", fill = FieldFill.INSERT_UPDATE)
private String updateBy;
@Schema(description = "更新时间")
@TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
@TableLogic
@Schema(description = "删除标记")
@TableField(value = "del_flag", fill = FieldFill.INSERT)
private String delFlag;
@Schema(description = "租户")
@TableField("tenant_id")
private Long tenantId;
}
@@ -83,6 +83,18 @@ public class CanteenOnlineSpuEntity extends Model<CanteenOnlineSpuEntity> {
@Schema(description="商品状态: 0 上架(默认/开启) 1 下架(禁用) -1 回收")
private Integer status;
/**
* 是否必选:0 否 1 是(前端下单需包含该品,如米饭)
*/
@Schema(description = "是否必选:0否 1是")
private Integer requiredChoice;
/**
* 择一/组合分组 id,与同组 SPU 共享 {@link CanteenOnlineSpuChoiceGroupEntity} 规则;null 表示不按组校验
*/
@Schema(description = "择一/组合分组 id,同组商品一起参与 minKinds/maxKinds 等校验")
private Long choiceGroupId;
/**
* 商品价格,单位使用:分
*/
@@ -1,5 +1,6 @@
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;
@@ -102,6 +103,18 @@ public class APPOnlineSpuVO {
@Schema(description = "商品状态: 0 上架 1 下架")
private Integer status;
/**
* 是否必选:0 否 1 是
*/
@Schema(description = "是否必选:0否 1是")
private Integer requiredChoice;
@Schema(description = "择一/组合分组 id,可与列表中同 id 的其它 SPU 归并展示;null 仅看 requiredChoice")
private Long choiceGroupId;
@Schema(description = "分组规则(有 choiceGroupId 时由服务端填充,含 minKinds/maxKinds 等)")
private OnlineSpuChoiceGroupRuleVO choiceGroup;
/**
* 是否售罄(库存为空或≤0)
*/
@@ -92,6 +92,18 @@ public class CanteenOnlineSpuAppVO {
@Schema(description="库存")
private Integer stock;
/**
* 是否必选:0 否 1 是
*/
@Schema(description = "是否必选:0否 1是")
private Integer requiredChoice;
@Schema(description = "择一/组合分组 id")
private Long choiceGroupId;
@Schema(description = "分组规则")
private OnlineSpuChoiceGroupRuleVO choiceGroup;
/**
* 赠送积分
*/
@@ -0,0 +1,45 @@
package com.bsd.pjcloud.canteen.vo.app;
import com.bsd.pjcloud.canteen.entity.CanteenOnlineSpuChoiceGroupEntity;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* App 端展示的分组规则(挂载在 SPU 上,同组 id 相同)
*/
@Data
@Schema(description = "择一/组合分组规则:同组 SPU 共享;校验见各字段说明")
public class OnlineSpuChoiceGroupRuleVO {
@Schema(description = "分组 id")
private Long id;
@Schema(description = "分组名称")
private String name;
@Schema(description = "至少几种 SPU 要有数量;0 表示整组可选")
private Integer minKinds;
@Schema(description = "至多几种;0 表示不限制")
private Integer maxKinds;
@Schema(description = "组内合计件数下限,0 不校验")
private Integer minTotalQty;
@Schema(description = "组内合计件数上限,0 不限制")
private Integer maxTotalQty;
public static OnlineSpuChoiceGroupRuleVO fromEntity(CanteenOnlineSpuChoiceGroupEntity e) {
if (e == null) {
return null;
}
OnlineSpuChoiceGroupRuleVO v = new OnlineSpuChoiceGroupRuleVO();
v.setId(e.getId());
v.setName(e.getName());
v.setMinKinds(e.getMinKinds());
v.setMaxKinds(e.getMaxKinds());
v.setMinTotalQty(e.getMinTotalQty());
v.setMaxTotalQty(e.getMaxTotalQty());
return v;
}
}
@@ -0,0 +1,81 @@
package com.bsd.pjcloud.canteen.vo.app;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
/**
* 移动端下单前:在线点餐 SPU 的分组(择一/组合)聚合结构
*/
@Data
@Schema(description = "在线点餐分组聚合(用于移动端必选/多选校验)")
public class OnlineSpuChoiceGroupsVO {
@Schema(description = "商户 id")
@JsonSerialize(using = ToStringSerializer.class)
private Long merchantId;
@Schema(description = "分组列表(choiceGroupId 维度聚合)")
private List<Group> groups;
@Schema(description = "单品必选列表(requiredChoice=1 且不属于任何分组)")
private List<RequiredSpu> requiredSpus;
@Data
@Schema(description = "分组")
public static class Group {
@Schema(description = "分组 id")
@JsonSerialize(using = ToStringSerializer.class)
private Long groupId;
@Schema(description = "分组名称")
private String groupName;
@Schema(description = "是否必选组:true 表示该组需要满足 minSelect/minTotalQty 约束")
private Boolean required;
@Schema(description = "至少选择几种 SPU(按种数计)")
private Integer minSelect;
@Schema(description = "最多选择几种 SPU(0 表示不限制)")
private Integer maxSelect;
@Schema(description = "组内合计件数下限(0 不校验)")
private Integer minTotalQty;
@Schema(description = "组内合计件数上限(0 不限制)")
private Integer maxTotalQty;
@Schema(description = "组内可选项(SPU 列表)")
private List<Item> items;
}
@Data
@Schema(description = "分组内可选项(SPU)")
public static class Item {
@Schema(description = "spuId")
@JsonSerialize(using = ToStringSerializer.class)
private Long spuId;
@Schema(description = "spuName")
private String spuName;
@Schema(description = "价格(分)")
private Integer price;
}
@Data
@Schema(description = "单品必选 SPU")
public static class RequiredSpu {
@Schema(description = "spuId")
@JsonSerialize(using = ToStringSerializer.class)
private Long spuId;
@Schema(description = "spuName")
private String spuName;
}
}
@@ -18,4 +18,10 @@ public class CanteenOnlineSpuSimpleVO {
@Schema(description = "关联线下菜品id")
private Long linkSpuId;
@Schema(description = "是否必选:0否 1是")
private Integer requiredChoice;
@Schema(description = "择一/组合分组 id")
private Long choiceGroupId;
}
@@ -84,14 +84,16 @@ public class CanteenDeliveryOrderController {
@SysLog("取餐拍照")
@PostMapping("/app/pickup")
@Operation(summary = "骑手端-到店取餐拍照")
@Operation(summary = "骑手端-到店取餐拍照",
description = "本地状态改为配送中后,会 Feign 调用硬件服务 POST /hard-order/update-take-out-status,status=TAKE_OUT_ING(sourceOrderId 即硬件订单 id)。")
public R<Void> pickup(@Valid @RequestBody CanteenDeliveryPhotoDTO dto) {
return canteenDeliveryOrderService.pickup(SecurityUtils.getUser().getId(), dto);
}
@SysLog("送达拍照")
@PostMapping("/app/deliver")
@Operation(summary = "骑手端-送达拍照")
@Operation(summary = "骑手端-送达拍照",
description = "本地状态改为已完成后,会 Feign 调用硬件服务 POST /hard-order/update-take-out-status,status=DONE。")
public R<Void> deliver(@Valid @RequestBody CanteenDeliveryPhotoDTO dto) {
return canteenDeliveryOrderService.deliver(SecurityUtils.getUser().getId(), dto);
}
@@ -0,0 +1,160 @@
package com.bsd.pjcloud.canteen.controller;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.bsd.pjcloud.canteen.entity.CanteenOnlineSpuChoiceGroupEntity;
import com.bsd.pjcloud.canteen.service.CanteenOnlineSpuChoiceGroupService;
import com.bsd.pjcloud.common.core.util.R;
import com.bsd.pjcloud.common.core.constant.enums.MerchantTypeEnum;
import com.bsd.pjcloud.common.log.annotation.SysLog;
import com.bsd.pjcloud.common.security.util.SecurityUtils;
import com.bsd.pjcloud.wallet.api.feign.RemoteMchService;
import com.bsd.pjcloud.wallet.api.vo.MchManageVo;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpHeaders;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 在线点餐 SPU 择一/组合规则组(管理端维护,SPU 上填 choiceGroupId 关联)
*/
@RestController
@RequiredArgsConstructor
@RequestMapping("/canteenOnlineSpuChoiceGroup")
@Tag(name = "在线点餐择一/组合分组", description = "minKinds/maxKinds 控制几种 SPU;minTotalQty/maxTotalQty 控制件数")
@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)
public class CanteenOnlineSpuChoiceGroupController {
private final CanteenOnlineSpuChoiceGroupService choiceGroupService;
private final RemoteMchService remoteMchService;
@GetMapping("/page")
@Operation(summary = "分页查询分组", description = "走数据权限 selectPageByScope;merchantId 不传则按数据权限返回可见范围内的全部分组,传了则按商户筛选")
@PreAuthorize("@pms.hasPermission('canteen_canteenOnlineSpu_view')")
public R<Page<CanteenOnlineSpuChoiceGroupEntity>> page(@RequestParam(required = false) Long merchantId,
@RequestParam(required = false) String name,
Page page) {
LambdaQueryWrapper<CanteenOnlineSpuChoiceGroupEntity> wrapper = Wrappers.lambdaQuery();
wrapper.eq(merchantId != null, CanteenOnlineSpuChoiceGroupEntity::getMerchantId, merchantId)
.like(name != null && !name.trim().isEmpty(), CanteenOnlineSpuChoiceGroupEntity::getName, name.trim())
.orderByAsc(CanteenOnlineSpuChoiceGroupEntity::getSort)
.orderByDesc(CanteenOnlineSpuChoiceGroupEntity::getId);
return R.ok(choiceGroupService.pageByScope(page, wrapper));
}
@GetMapping("/list")
@Operation(summary = "列出分组", description = "merchantId 不传则默认当前登录单位对应的食堂商户")
@PreAuthorize("@pms.hasPermission('canteen_canteenOnlineSpu_view')")
public R<List<CanteenOnlineSpuChoiceGroupEntity>> list(@RequestParam(required = false) Long merchantId) {
MchManageVo mch = resolveMchOrNull(merchantId);
if (mch == null) {
return R.failed("商户不存在");
}
List<CanteenOnlineSpuChoiceGroupEntity> list = choiceGroupService.listByScope(
Wrappers.<CanteenOnlineSpuChoiceGroupEntity>lambdaQuery()
.eq(CanteenOnlineSpuChoiceGroupEntity::getMerchantId, mch.getId())
.orderByAsc(CanteenOnlineSpuChoiceGroupEntity::getSort)
.orderByDesc(CanteenOnlineSpuChoiceGroupEntity::getId));
return R.ok(list);
}
@Operation(summary = "新增分组")
@SysLog("新增在线点餐择一分组")
@PostMapping
@PreAuthorize("@pms.hasPermission('canteen_canteenOnlineSpu_add')")
public R<Boolean> save(@RequestBody CanteenOnlineSpuChoiceGroupEntity body) {
MchManageVo mch = resolveMchOrNull(body.getMerchantId());
if (mch == null) {
return R.failed("商户不存在");
}
String err = validate(body);
if (err != null) {
return R.failed(err);
}
normalizeDefaults(body);
body.setMerchantId(mch.getId());
// deptId 用商户绑定的部门,便于按部门做数据权限隔离
body.setDeptId(mch.getDeptId());
return R.ok(choiceGroupService.save(body));
}
@Operation(summary = "修改分组")
@SysLog("修改在线点餐择一分组")
@PutMapping
@PreAuthorize("@pms.hasPermission('canteen_canteenOnlineSpu_edit')")
public R<Boolean> update(@RequestBody CanteenOnlineSpuChoiceGroupEntity body) {
MchManageVo mch = resolveMchOrNull(body.getMerchantId());
if (mch == null) {
return R.failed("商户不存在");
}
String err = validate(body);
if (err != null) {
return R.failed(err);
}
normalizeDefaults(body);
body.setMerchantId(mch.getId());
body.setDeptId(mch.getDeptId());
return R.ok(choiceGroupService.updateById(body));
}
@Operation(summary = "删除分组")
@SysLog("删除在线点餐择一分组")
@DeleteMapping
@PreAuthorize("@pms.hasPermission('canteen_canteenOnlineSpu_del')")
public R<Boolean> remove(@RequestBody Long[] ids) {
return R.ok(choiceGroupService.removeBatchByIds(CollUtil.toList(ids)));
}
private static void normalizeDefaults(CanteenOnlineSpuChoiceGroupEntity e) {
if (e.getMinKinds() == null) {
e.setMinKinds(0);
}
if (e.getMaxKinds() == null) {
e.setMaxKinds(0);
}
if (e.getMinTotalQty() == null) {
e.setMinTotalQty(0);
}
if (e.getMaxTotalQty() == null) {
e.setMaxTotalQty(0);
}
if (e.getSort() == null) {
e.setSort(0);
}
}
private static String validate(CanteenOnlineSpuChoiceGroupEntity e) {
int minK = e.getMinKinds() == null ? 0 : e.getMinKinds();
int maxK = e.getMaxKinds() == null ? 0 : e.getMaxKinds();
if (maxK > 0 && minK > maxK) {
return "maxKinds 不能小于 minKinds";
}
int minQ = e.getMinTotalQty() == null ? 0 : e.getMinTotalQty();
int maxQ = e.getMaxTotalQty() == null ? 0 : e.getMaxTotalQty();
if (maxQ > 0 && minQ > maxQ) {
return "maxTotalQty 不能小于 minTotalQty";
}
return null;
}
private MchManageVo resolveMchOrNull(Long merchantId) {
R<MchManageVo> resp;
if (merchantId != null) {
resp = remoteMchService.getById(merchantId);
} else {
Long deptId = SecurityUtils.getUser().getDeptId();
resp = remoteMchService.getByDeptId(deptId, MerchantTypeEnum.CANTEEN);
}
if (resp == null || !resp.isOk() || resp.getData() == null) {
return null;
}
return resp.getData();
}
}
@@ -12,10 +12,14 @@ import com.bsd.pjcloud.canteen.dto.canteenonline.CanteenOnlineSpuSaveDto;
import com.bsd.pjcloud.canteen.vo.APPOnlineSpuVO;
import com.bsd.pjcloud.canteen.vo.app.CanteenOnlineSpuAppVO;
import com.bsd.pjcloud.canteen.vo.app.MchSimpleAppVO;
import com.bsd.pjcloud.canteen.vo.app.OnlineSpuChoiceGroupsVO;
import com.bsd.pjcloud.canteen.vo.app.OnlineSpuChoiceGroupRuleVO;
import com.bsd.pjcloud.common.core.util.BeanUtils;
import com.bsd.pjcloud.common.core.util.R;
import com.bsd.pjcloud.common.log.annotation.SysLog;
import com.bsd.pjcloud.canteen.entity.CanteenOnlineSpuEntity;
import com.bsd.pjcloud.canteen.entity.CanteenOnlineSpuChoiceGroupEntity;
import com.bsd.pjcloud.canteen.service.CanteenOnlineSpuChoiceGroupService;
import com.bsd.pjcloud.canteen.service.CanteenOnlineSpuService;
import com.bsd.pjcloud.common.security.util.SecurityUtils;
import com.bsd.pjcloud.common.core.constant.enums.MerchantTypeEnum;
@@ -33,8 +37,11 @@ import lombok.RequiredArgsConstructor;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* 在线点餐spu
@@ -50,6 +57,7 @@ import java.util.Objects;
public class CanteenOnlineSpuController {
private final CanteenOnlineSpuService canteenOnlineSpuService;
private final CanteenOnlineSpuChoiceGroupService canteenOnlineSpuChoiceGroupService;
private final RemoteMchService remoteMchService;
/**
@@ -125,6 +133,12 @@ public class CanteenOnlineSpuController {
@PostMapping
@PreAuthorize("@pms.hasPermission('canteen_canteenOnlineSpu_add')")
public R save(@RequestBody CanteenOnlineSpuEntity canteenOnlineSpu) {
if (canteenOnlineSpu.getChoiceGroupId() != null) {
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));
}
@@ -148,6 +162,12 @@ public class CanteenOnlineSpuController {
@PutMapping
@PreAuthorize("@pms.hasPermission('canteen_canteenOnlineSpu_edit')")
public R updateById(@RequestBody CanteenOnlineSpuEntity canteenOnlineSpu) {
if (canteenOnlineSpu.getChoiceGroupId() != null) {
CanteenOnlineSpuChoiceGroupEntity g = canteenOnlineSpuChoiceGroupService.getById(canteenOnlineSpu.getChoiceGroupId());
if (g == null) {
return R.failed("择一分组不存在(可能是前端 long 精度丢失导致 id 被圆整),请重新选择分组");
}
}
return R.ok(canteenOnlineSpuService.updateById(canteenOnlineSpu));
}
@@ -217,7 +237,7 @@ public class CanteenOnlineSpuController {
* @param priceOrder 按价格排序:asc 升序 desc 降序,不传则按 sort、id
*/
@GetMapping("/app/getCanteenSpuList")
@Operation(summary = "获取在线点餐的菜品列表", description = "商户 id 必传;支持价格区间、上架状态、是否售罄筛选及按价格排序;返回含 status、soldOut")
@Operation(summary = "获取在线点餐的菜品列表", description = "商户 id 必传;返回含 status、requiredChoice、choiceGroupId+choiceGroup(择一/组合规则)、soldOut")
public R<List<APPOnlineSpuVO>> getCanteenSpuList(
@RequestParam(value = "categoryId", required = false) @Parameter(description = "分类 id,可选") String categoryId,
@RequestParam(value = "merchantId") @Parameter(description = "商户 id,必填") String merchantId,
@@ -240,10 +260,92 @@ public class CanteenOnlineSpuController {
if (bean != null && canteenOnlineSpuEntity != null) {
Integer st = canteenOnlineSpuEntity.getStock();
bean.setSoldOut(st == null || st <= 0);
canteenOnlineSpuService.fillAppOnlineSpuChoiceGroups(Collections.singletonList(bean),
Collections.singletonList(canteenOnlineSpuEntity));
}
return R.ok(bean);
}
@GetMapping("/app/getChoiceGroups")
@Operation(summary = "下单前获取分组规则(聚合)",
description = "按 choiceGroupId 聚合组内 SPU 列表;并返回 requiredChoice=1 且未进组的单品必选列表。")
public R<OnlineSpuChoiceGroupsVO> getChoiceGroups(@RequestParam("merchantId") Long merchantId,
@RequestParam(value = "status", required = false) Integer status) {
if (status == null) {
status = 0;
}
LambdaQueryWrapper<CanteenOnlineSpuEntity> w = Wrappers.lambdaQuery();
w.eq(CanteenOnlineSpuEntity::getMerchantId, merchantId);
// 默认仅返回上架菜,避免下单规则里混入下架项
w.eq(CanteenOnlineSpuEntity::getStatus, status);
w.orderByAsc(CanteenOnlineSpuEntity::getSort).orderByDesc(CanteenOnlineSpuEntity::getId);
List<CanteenOnlineSpuEntity> spus = canteenOnlineSpuService.list(w);
// 1) requiredChoice=1 且未进组
List<OnlineSpuChoiceGroupsVO.RequiredSpu> requiredSpus = spus.stream()
.filter(s -> Objects.equals(s.getRequiredChoice(), 1) && s.getChoiceGroupId() == null)
.map(s -> {
OnlineSpuChoiceGroupsVO.RequiredSpu r = new OnlineSpuChoiceGroupsVO.RequiredSpu();
r.setSpuId(s.getId());
r.setSpuName(s.getName());
return r;
}).collect(Collectors.toList());
// 2) 分组聚合:choiceGroupId -> items
Map<Long, List<CanteenOnlineSpuEntity>> byGroup = spus.stream()
.filter(s -> s.getChoiceGroupId() != null)
.collect(Collectors.groupingBy(CanteenOnlineSpuEntity::getChoiceGroupId));
// 3) 加载分组规则:直接按 groupId 查分组表,避免间接映射导致规则丢失
final Map<Long, OnlineSpuChoiceGroupRuleVO> ruleMap;
if (CollUtil.isEmpty(byGroup)) {
ruleMap = Collections.emptyMap();
} else {
List<CanteenOnlineSpuChoiceGroupEntity> groups = canteenOnlineSpuChoiceGroupService.listByIds(byGroup.keySet());
ruleMap = CollUtil.isEmpty(groups)
? Collections.emptyMap()
: groups.stream().collect(Collectors.toMap(CanteenOnlineSpuChoiceGroupEntity::getId, OnlineSpuChoiceGroupRuleVO::fromEntity, (a, b) -> a));
}
List<OnlineSpuChoiceGroupsVO.Group> groups = byGroup.entrySet().stream()
.map(e -> {
Long gid = e.getKey();
OnlineSpuChoiceGroupRuleVO rule = ruleMap.get(gid);
OnlineSpuChoiceGroupsVO.Group g = new OnlineSpuChoiceGroupsVO.Group();
g.setGroupId(gid);
g.setGroupName(rule != null ? rule.getName() : null);
Integer min = rule != null ? rule.getMinKinds() : 0;
Integer minQty = rule != null ? rule.getMinTotalQty() : 0;
g.setRequired((min != null && min > 0) || (minQty != null && minQty > 0));
g.setMinSelect(rule != null ? rule.getMinKinds() : 0);
g.setMaxSelect(rule != null ? rule.getMaxKinds() : 0);
g.setMinTotalQty(rule != null ? rule.getMinTotalQty() : 0);
g.setMaxTotalQty(rule != null ? rule.getMaxTotalQty() : 0);
List<OnlineSpuChoiceGroupsVO.Item> items = e.getValue().stream().map(s -> {
OnlineSpuChoiceGroupsVO.Item it = new OnlineSpuChoiceGroupsVO.Item();
it.setSpuId(s.getId());
it.setSpuName(s.getName());
it.setPrice(s.getPrice());
return it;
}).collect(Collectors.toList());
g.setItems(items);
return g;
})
.sorted((a, b) -> {
if (a.getGroupId() == null && b.getGroupId() == null) return 0;
if (a.getGroupId() == null) return 1;
if (b.getGroupId() == null) return -1;
return a.getGroupId().compareTo(b.getGroupId());
})
.collect(Collectors.toList());
OnlineSpuChoiceGroupsVO vo = new OnlineSpuChoiceGroupsVO();
vo.setMerchantId(merchantId);
vo.setGroups(groups);
vo.setRequiredSpus(requiredSpus);
return R.ok(vo);
}
@GetMapping("/app/checkSpuStock")
@SysLog("购物车效验库存")
@@ -310,7 +412,10 @@ public class CanteenOnlineSpuController {
// @PreAuthorize("@pms.hasPermission('canteen_canteenSpu_view')")
public R<List<CanteenOnlineSpuAppVO>> getByIds(@RequestBody List<Long> ids) {
List<CanteenOnlineSpuEntity> list = canteenOnlineSpuService.list(new LambdaQueryWrapper<CanteenOnlineSpuEntity>().in(CanteenOnlineSpuEntity::getId, ids));
List<CanteenOnlineSpuAppVO> canteenOnlineSpuAppVOS = BeanUtils.toBean(list, CanteenOnlineSpuAppVO.class);
Map<Long, CanteenOnlineSpuEntity> byId = list.stream().collect(Collectors.toMap(CanteenOnlineSpuEntity::getId, e -> e, (a, b) -> a));
List<CanteenOnlineSpuEntity> ordered = ids.stream().map(byId::get).filter(Objects::nonNull).collect(Collectors.toList());
List<CanteenOnlineSpuAppVO> canteenOnlineSpuAppVOS = BeanUtils.toBean(ordered, CanteenOnlineSpuAppVO.class);
canteenOnlineSpuService.fillOnlineSpuAppChoiceGroups(canteenOnlineSpuAppVOS, ordered);
return R.ok(canteenOnlineSpuAppVOS);
}
@@ -0,0 +1,9 @@
package com.bsd.pjcloud.canteen.mapper;
import com.bsd.pjcloud.canteen.entity.CanteenOnlineSpuChoiceGroupEntity;
import com.bsd.pjcloud.common.data.datascope.PjcloudBaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface CanteenOnlineSpuChoiceGroupMapper extends PjcloudBaseMapper<CanteenOnlineSpuChoiceGroupEntity> {
}
@@ -0,0 +1,15 @@
package com.bsd.pjcloud.canteen.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.bsd.pjcloud.canteen.entity.CanteenOnlineSpuChoiceGroupEntity;
import java.util.List;
public interface CanteenOnlineSpuChoiceGroupService extends IService<CanteenOnlineSpuChoiceGroupEntity> {
List<CanteenOnlineSpuChoiceGroupEntity> listByScope(LambdaQueryWrapper<CanteenOnlineSpuChoiceGroupEntity> wrapper);
Page<CanteenOnlineSpuChoiceGroupEntity> pageByScope(Page page, LambdaQueryWrapper<CanteenOnlineSpuChoiceGroupEntity> wrapper);
}
@@ -8,6 +8,7 @@ import com.bsd.pjcloud.canteen.dto.CanteenOnlineSpuUpdateStockReqDTO;
import com.bsd.pjcloud.canteen.dto.canteenonline.CanteenOnlineSpuSaveDto;
import com.bsd.pjcloud.canteen.entity.CanteenOnlineSpuEntity;
import com.bsd.pjcloud.canteen.vo.APPOnlineSpuVO;
import com.bsd.pjcloud.canteen.vo.app.CanteenOnlineSpuAppVO;
import com.bsd.pjcloud.canteen.vo.erp.CanteenOnlineSpuSimpleVO;
import com.bsd.pjcloud.common.core.util.R;
@@ -37,6 +38,12 @@ public interface CanteenOnlineSpuService extends IService<CanteenOnlineSpuEntity
Integer minPrice, Integer maxPrice, Integer status,
String stockFilter, String priceOrder);
/**
* 为 App 列表/详情填充 choiceGroup 规则对象(与 entities 按序一一对应)
*/
void fillAppOnlineSpuChoiceGroups(List<APPOnlineSpuVO> vos, List<CanteenOnlineSpuEntity> entities);
void fillOnlineSpuAppChoiceGroups(List<CanteenOnlineSpuAppVO> vos, List<CanteenOnlineSpuEntity> entities);
/**
* 修改商品库存
@@ -26,6 +26,8 @@ import com.bsd.pjcloud.canteen.service.CanteenDeliveryOrderService;
import com.bsd.pjcloud.canteen.vo.app.CanteenDeliveryOrderAppVO;
import com.bsd.pjcloud.common.core.util.BeanUtils;
import com.bsd.pjcloud.common.core.util.R;
import com.bsd.pjcloud.hard.api.dto.order.remote.HardUpdateTakeOutStatusDto;
import com.bsd.pjcloud.hard.api.feign.RemoteHardOrderService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -51,6 +53,7 @@ public class CanteenDeliveryOrderServiceImpl extends ServiceImpl<CanteenDelivery
private final RemoteDeptService remoteDeptService;
private final ObjectMapper objectMapper;
private final CanteenDeliveryRiderPushPublisher riderPushPublisher;
private final RemoteHardOrderService remoteHardOrderService;
@Override
@Transactional(rollbackFor = Exception.class)
@@ -310,6 +313,7 @@ public class CanteenDeliveryOrderServiceImpl extends ServiceImpl<CanteenDelivery
e.setPickupPhotoUrl(dto.getPhotoUrl());
e.setStatus(CanteenDeliveryOrderStatus.DELIVERING);
updateById(e);
syncHardTakeOutStatusOrThrow(e.getSourceOrderId(), HardUpdateTakeOutStatusDto.TakeOutStatus.TAKE_OUT_ING);
return R.ok();
}
@@ -326,6 +330,31 @@ public class CanteenDeliveryOrderServiceImpl extends ServiceImpl<CanteenDelivery
e.setDeliveryPhotoUrl(dto.getPhotoUrl());
e.setStatus(CanteenDeliveryOrderStatus.COMPLETED);
updateById(e);
syncHardTakeOutStatusOrThrow(e.getSourceOrderId(), HardUpdateTakeOutStatusDto.TakeOutStatus.DONE);
return R.ok();
}
/**
* 来源订单号即硬件外卖订单主键(与同步时 dto.id 一致)。非数字则跳过(非硬件单)。
* 同步失败抛异常,触发事务回滚,避免本地状态与硬件订单不一致。
*/
private void syncHardTakeOutStatusOrThrow(String sourceOrderId, HardUpdateTakeOutStatusDto.TakeOutStatus status) {
if (StrUtil.isBlank(sourceOrderId)) {
return;
}
long hardOrderId;
try {
hardOrderId = Long.parseLong(sourceOrderId.trim());
} catch (NumberFormatException ex) {
return;
}
HardUpdateTakeOutStatusDto dto = new HardUpdateTakeOutStatusDto();
dto.setOrderId(hardOrderId);
dto.setStatus(status);
R<Boolean> r = remoteHardOrderService.updateTakeOutStatus(dto);
if (r == null || !r.isOk() || !Boolean.TRUE.equals(r.getData())) {
String msg = r == null ? "无响应" : r.getMsg();
throw new IllegalStateException("同步硬件外卖订单状态失败: " + msg);
}
}
}
@@ -0,0 +1,28 @@
package com.bsd.pjcloud.canteen.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bsd.pjcloud.canteen.entity.CanteenOnlineSpuChoiceGroupEntity;
import com.bsd.pjcloud.canteen.mapper.CanteenOnlineSpuChoiceGroupMapper;
import com.bsd.pjcloud.canteen.service.CanteenOnlineSpuChoiceGroupService;
import com.bsd.pjcloud.common.data.datascope.DataScope;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class CanteenOnlineSpuChoiceGroupServiceImpl
extends ServiceImpl<CanteenOnlineSpuChoiceGroupMapper, CanteenOnlineSpuChoiceGroupEntity>
implements CanteenOnlineSpuChoiceGroupService {
@Override
public List<CanteenOnlineSpuChoiceGroupEntity> listByScope(LambdaQueryWrapper<CanteenOnlineSpuChoiceGroupEntity> wrapper) {
return baseMapper.selectListByScope(wrapper, DataScope.of());
}
@Override
public Page<CanteenOnlineSpuChoiceGroupEntity> pageByScope(Page page, LambdaQueryWrapper<CanteenOnlineSpuChoiceGroupEntity> wrapper) {
return baseMapper.selectPageByScope(page, wrapper, DataScope.of());
}
}
@@ -11,12 +11,16 @@ 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.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.CanteenOnlineSpuMapper;
import com.bsd.pjcloud.canteen.service.CanteenOnlineSpuChoiceGroupService;
import com.bsd.pjcloud.canteen.service.CanteenOnlineSpuService;
import com.bsd.pjcloud.canteen.vo.app.CanteenOnlineSpuAppVO;
import com.bsd.pjcloud.canteen.vo.app.OnlineSpuChoiceGroupRuleVO;
import com.bsd.pjcloud.canteen.vo.APPOnlineSpuVO;
import com.bsd.pjcloud.canteen.vo.erp.CanteenOnlineSpuSimpleVO;
import com.bsd.pjcloud.common.core.util.BeanUtils;
@@ -30,11 +34,12 @@ import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.Objects;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
@@ -55,6 +60,8 @@ public class CanteenOnlineSpuServiceImpl extends ServiceImpl<CanteenOnlineSpuMap
private final CanteenDailyMenuMapper canteenDailyMenuMapper;
private final CanteenOnlineSpuMapper baseMapper;
private final CanteenOnlineSpuChoiceGroupService canteenOnlineSpuChoiceGroupService;
@Override
public R<List<APPOnlineSpuVO>> getCanteenOnlineSpuList(String categoryId, String merchantId, String time,
Integer minPrice, Integer maxPrice, Integer status,
@@ -109,10 +116,50 @@ public class CanteenOnlineSpuServiceImpl extends ServiceImpl<CanteenOnlineSpuMap
Integer st = finallyCanteenSpuEntities.get(i).getStock();
bean.get(i).setSoldOut(st == null || st <= 0);
}
fillAppOnlineSpuChoiceGroups(bean, finallyCanteenSpuEntities);
return R.ok(bean);
}
@Override
public void fillAppOnlineSpuChoiceGroups(List<APPOnlineSpuVO> vos, List<CanteenOnlineSpuEntity> entities) {
if (CollUtil.isEmpty(vos) || CollUtil.isEmpty(entities) || vos.size() != entities.size()) {
return;
}
Map<Long, OnlineSpuChoiceGroupRuleVO> ruleMap = loadChoiceGroupRuleMap(
entities.stream().map(CanteenOnlineSpuEntity::getChoiceGroupId).filter(Objects::nonNull).collect(Collectors.toSet()));
for (int i = 0; i < vos.size(); i++) {
Long gid = entities.get(i).getChoiceGroupId();
vos.get(i).setChoiceGroupId(gid);
vos.get(i).setChoiceGroup(gid == null ? null : ruleMap.get(gid));
}
}
@Override
public void fillOnlineSpuAppChoiceGroups(List<CanteenOnlineSpuAppVO> vos, List<CanteenOnlineSpuEntity> entities) {
if (CollUtil.isEmpty(vos) || CollUtil.isEmpty(entities) || vos.size() != entities.size()) {
return;
}
Map<Long, OnlineSpuChoiceGroupRuleVO> ruleMap = loadChoiceGroupRuleMap(
entities.stream().map(CanteenOnlineSpuEntity::getChoiceGroupId).filter(Objects::nonNull).collect(Collectors.toSet()));
for (int i = 0; i < vos.size(); i++) {
Long gid = entities.get(i).getChoiceGroupId();
vos.get(i).setChoiceGroupId(gid);
vos.get(i).setChoiceGroup(gid == null ? null : ruleMap.get(gid));
}
}
private Map<Long, OnlineSpuChoiceGroupRuleVO> loadChoiceGroupRuleMap(Set<Long> groupIds) {
if (CollUtil.isEmpty(groupIds)) {
return Collections.emptyMap();
}
List<CanteenOnlineSpuChoiceGroupEntity> groups = canteenOnlineSpuChoiceGroupService.listByIds(groupIds);
if (CollUtil.isEmpty(groups)) {
return Collections.emptyMap();
}
return groups.stream().collect(Collectors.toMap(CanteenOnlineSpuChoiceGroupEntity::getId, OnlineSpuChoiceGroupRuleVO::fromEntity));
}
@Override
public R updateOnlineSpuSkuStock(CanteenOnlineSpuUpdateStockReqDTO canteenOnlineSpuUpdateStockReqDTO) {
// 下单等场景:库存与销量联动(incrCount<0 视为售出)
@@ -208,7 +255,18 @@ public class CanteenOnlineSpuServiceImpl extends ServiceImpl<CanteenOnlineSpuMap
.collect(Collectors.toMap(CanteenCategoryEntity::getId, c -> c));
Long deptId = SecurityUtils.getUser().getDeptId();
for (CanteenOnlineSpuEntity c : canteenSpuEntities) {
if (c.getChoiceGroupId() != null) {
CanteenOnlineSpuChoiceGroupEntity g = canteenOnlineSpuChoiceGroupService.getById(c.getChoiceGroupId());
if (g == null || !Objects.equals(g.getMerchantId(), merchantId)) {
return R.failed("择一分组不存在或不属于当前商户:" + StrUtil.blankToDefault(c.getName(), String.valueOf(c.getChoiceGroupId())));
}
}
}
canteenSpuEntities.forEach(c -> {
if (c.getRequiredChoice() == null) {
c.setRequiredChoice(0);
}
CanteenCategoryEntity canteenCategoryEntity = canteenCategoryEntityMap.get(c.getCategoryId());
if (canteenCategoryEntity != null) {
String name = canteenCategoryEntity.getName();
@@ -13,6 +13,8 @@
<result property="sliderPicUrls" column="slider_pic_urls"/>
<result property="sort" column="sort"/>
<result property="status" column="status"/>
<result property="requiredChoice" column="required_choice"/>
<result property="choiceGroupId" column="choice_group_id"/>
<result property="price" column="price"/>
<result property="marketPrice" column="market_price"/>
<result property="stock" column="stock"/>
@@ -22,6 +22,7 @@ import com.bsd.pjcloud.canteen.api.feign.RemoteOnlineCanteenSpuService;
import com.bsd.pjcloud.canteen.dto.CanteenSpuUpdateStockReqDTO;
import com.bsd.pjcloud.canteen.dto.delivery.CanteenDeliveryCartItemDTO;
import com.bsd.pjcloud.canteen.dto.delivery.CanteenDeliveryOrderSyncDTO;
import com.bsd.pjcloud.canteen.vo.app.OnlineSpuChoiceGroupsVO;
import com.bsd.pjcloud.common.core.constant.CacheConstants;
import com.bsd.pjcloud.common.core.constant.CommonConstants;
import com.bsd.pjcloud.common.core.constant.CommonRoleConstant;
@@ -150,6 +151,7 @@ public class HardOrderServiceImpl extends ServiceImpl<HardOrderMapper, HardOrder
private final MiniAlipayService miniAlipayService;
private final HardOrderAddressService hardOrderAddressService;
private final RemoteOnlineCanteenSpuService remoteOnlineCanteenSpuService;
private final RemoteMchService remoteMchService;
private final RemoteMchPickUpService remoteMchPickUpService;
private final RemoteCodeUseService remoteCodeUseService;
@@ -601,6 +603,7 @@ public class HardOrderServiceImpl extends ServiceImpl<HardOrderMapper, HardOrder
// 查询用户
UserInfo userDetail = userService.getInnerByUserId(dto.getUserId()).getData();
if (ObjectUtil.isEmpty(userDetail)) throw new RuntimeException("用户不存在不存在");
// 如果是预约判断是否在预约时间段内
boolean inReserveTime = isInReserveTime(dto.getApptId());
if (!inReserveTime) throw new RuntimeException("不在预约时间段内");
@@ -613,12 +616,20 @@ public class HardOrderServiceImpl extends ServiceImpl<HardOrderMapper, HardOrder
// 查询父订单
HardParentCarEntity parentCar = hardCarService.getParentCarById(dto.getCarParentId());
if (ObjectUtil.isEmpty(parentCar)) throw new RuntimeException("购物车不存在");
//效验是否有选择必选品
R<OnlineSpuChoiceGroupsVO> choiceGroups = remoteOnlineCanteenSpuService.getChoiceGroups(String.valueOf(parentCar.getMerchantId()));
if (!choiceGroups.isOk()){
throw new RuntimeException("未选择必选菜品");
}
// 查询商户
MchManageVo hardManager = remoteMchService.getById(parentCar.getMerchantId()).getData();
if (ObjectUtil.isEmpty(hardManager)) throw new RuntimeException("商户不存在");
// 查询父购物车
List<HardCarEntity> carList = hardCarService.getByParentCarId(dto.getCarParentId());
if (ObjectUtil.isEmpty(carList)) throw new RuntimeException("购物车不存在");
Integer balance = wallet.getBalance();
int carPrice = (int) carList.stream().mapToLong(HardCarEntity::getTotalPrice).sum();
int totalPrice = carPrice;