每日订餐问题更新

This commit is contained in:
zjf committed 2026-10-14 10:16:50 +08:00
1 parent 4c9bdf073a
commit cbf28e8d7e
10 files changed
+113 -19

No files matched your search

@@ -116,7 +116,7 @@ public class DailyMenuAppVO {
private Integer remainOfflineStock;
/**
* 商品销量,仅用于详情页展示。
* 当日移动端已订份数(取 online_sold),供 App 详情「已订」展示;不是 canteen_spu 历史累计销量。
*/
private Integer salesCount;
@@ -300,17 +300,19 @@ public class CanteenDailyMenuController {
* 按硬件订单净销量回填当日线上/线下 sold(配额不动)。
* 用于历史 sold 未累计、或怀疑销量漂移时的运维纠偏。
*/
@Operation(summary = "按订单重算当日分池销量", description = "覆盖 online_sold/offline_sold,不改配额")
@Operation(summary = "按订单重算分池销量", description = "覆盖 online_sold/offline_sold,不改配额;传 dailyMenuId 时只重算该行")
@SysLog("按订单重算每日菜谱销量")
@PutMapping("/syncChannelSold")
@PreAuthorize("@pms.hasPermission('canteen_canteenDailyMenu_edit')")
public R<Integer> syncChannelSold(@RequestParam("merchantId") Long merchantId,
@RequestParam("mealDate") String mealDate) {
@RequestParam("mealDate") String mealDate,
@RequestParam(value = "dailyMenuId", required = false) Long dailyMenuId) {
if (merchantId == null || StrUtil.isBlank(mealDate)) {
return R.failed("商户和用餐日不能为空");
}
try {
return canteenDailyMenuService.syncChannelSoldFromOrders(merchantId, LocalDate.parse(mealDate.trim()));
return canteenDailyMenuService.syncChannelSoldFromOrders(merchantId, LocalDate.parse(mealDate.trim()),
dailyMenuId);
}
catch (Exception ex) {
return R.failed("用餐日格式应为 yyyy-MM-dd");
@@ -469,6 +469,15 @@ public class CanteenSpuController {
return R.ok(canteenSpuService.loadComboItemVos(spuIds));
}
/**
* 反查「含该组成单品」的套餐及每份含本菜数量,供每日菜谱相关订单并入套餐单。
*/
@GetMapping("/comboParentsByChild")
@PreAuthorize("@pms.hasPermission('canteen_canteenDailyMenu_view')")
public R<Map<Long, Integer>> comboParentsByChild(@RequestParam("childSpuId") Long childSpuId) {
return R.ok(canteenSpuService.mapComboQtyByChildSpuId(childSpuId));
}
/**
* 为无用户令牌的 Hardware 异步打印任务提供套餐组成查询,复用管理端相同的数据装配逻辑。
*
@@ -116,4 +116,11 @@ public interface CanteenDailyMenuService extends IService<CanteenDailyMenuEntity
* @return 实际改写行数
*/
R<Integer> syncChannelSoldFromOrders(Long merchantId, LocalDate mealDate);
/**
* 同 {@link #syncChannelSoldFromOrders(Long, LocalDate)};{@code dailyMenuId} 非空时只重算该菜谱行。
*
* @param dailyMenuId 每日菜谱行 ID,空则重算当日全部
*/
R<Integer> syncChannelSoldFromOrders(Long merchantId, LocalDate mealDate, Long dailyMenuId);
}
@@ -129,6 +129,14 @@ public interface CanteenSpuService extends IService<CanteenSpuEntity> {
*/
Map<Long, List<OnlineSpuComboItemVO>> loadComboItemVos(List<Long> comboSpuIds);
/**
* 查出「组成中含该单品」的套餐及其每份套餐含本菜份数,供相关订单并入套餐单并折算本菜份数。
*
* @param childSpuId 组成单品 SPU ID
* @return key=套餐 SPU ID,value=每份套餐含本菜数量;无则空 Map
*/
Map<Long, Integer> mapComboQtyByChildSpuId(Long childSpuId);
/**
* 按购物车商品 ID 批量加载打印凭证名称(套餐名优先、商品名兜底)。
*
@@ -506,16 +506,39 @@ public class CanteenDailyMenuServiceImpl extends ServiceImpl<CanteenDailyMenuMap
@Override
@Transactional(rollbackFor = Exception.class)
public R<Integer> syncChannelSoldFromOrders(Long merchantId, LocalDate mealDate) {
return syncChannelSoldFromOrders(merchantId, mealDate, null);
}
@Override
@Transactional(rollbackFor = Exception.class)
public R<Integer> syncChannelSoldFromOrders(Long merchantId, LocalDate mealDate, Long dailyMenuId) {
if (merchantId == null || mealDate == null) {
return R.failed("商户和用餐日不能为空");
}
List<CanteenDailyMenuEntity> menus = list(Wrappers.<CanteenDailyMenuEntity>lambdaQuery()
.eq(CanteenDailyMenuEntity::getMerchantId, merchantId)
.eq(CanteenDailyMenuEntity::getStartTime, mealDate));
List<CanteenDailyMenuEntity> menus;
if (dailyMenuId != null) {
CanteenDailyMenuEntity one = getById(dailyMenuId);
if (one == null) {
return R.failed("菜谱行不存在");
}
if (!Objects.equals(one.getMerchantId(), merchantId)) {
return R.failed("菜谱行不属于当前商户");
}
if (one.getStartTime() == null || !mealDate.equals(one.getStartTime())) {
return R.failed("菜谱行用餐日与所选日期不一致");
}
menus = List.of(one);
}
else {
menus = list(Wrappers.<CanteenDailyMenuEntity>lambdaQuery()
.eq(CanteenDailyMenuEntity::getMerchantId, merchantId)
.eq(CanteenDailyMenuEntity::getStartTime, mealDate));
}
if (CollectionUtils.isEmpty(menus)) {
return R.ok(0, "当日无菜谱记录");
}
// 单行重算也要拉当日全量净销量:组成单品需叠加「含本菜」套餐单的摊销量
DailyMenuNetSoldQueryDTO query = new DailyMenuNetSoldQueryDTO();
query.setMerchantId(merchantId);
query.setMealDate(mealDate);
@@ -543,12 +566,16 @@ public class CanteenDailyMenuServiceImpl extends ServiceImpl<CanteenDailyMenuMap
}
}
// 套餐订单销量摊到同日同餐段已排菜的组成单品(与实时扣减联动一致)
Set<Long> menuSpuIds = menus.stream().map(CanteenDailyMenuEntity::getSpuId).filter(Objects::nonNull)
// 套餐联动范围:单行时仍按「当日全部菜谱键」判断组成是否已排菜,与全量重算口径一致
List<CanteenDailyMenuEntity> dayMenus = dailyMenuId == null ? menus
: list(Wrappers.<CanteenDailyMenuEntity>lambdaQuery()
.eq(CanteenDailyMenuEntity::getMerchantId, merchantId)
.eq(CanteenDailyMenuEntity::getStartTime, mealDate));
Set<Long> menuSpuIds = dayMenus.stream().map(CanteenDailyMenuEntity::getSpuId).filter(Objects::nonNull)
.collect(Collectors.toSet());
Map<Long, List<com.bsd.pjcloud.canteen.vo.app.OnlineSpuComboItemVO>> comboMap =
canteenSpuService.loadComboItemVos(new ArrayList<>(menuSpuIds));
Set<String> menuKeys = menus.stream()
Set<String> menuKeys = dayMenus.stream()
.filter(m -> m.getHourType() != null && m.getSpuId() != null)
.map(m -> m.getHourType() + "|" + m.getSpuId())
.collect(Collectors.toSet());
@@ -612,9 +639,10 @@ public class CanteenDailyMenuServiceImpl extends ServiceImpl<CanteenDailyMenuMap
if (CollUtil.isNotEmpty(toUpdate)) {
updateBatchById(toUpdate);
}
log.info("每日菜谱按订单重算销量 merchantId={} mealDate={} menuRows={} updated={}",
merchantId, mealDate, menus.size(), toUpdate.size());
return R.ok(toUpdate.size(), "已按订单重算 " + toUpdate.size() + " 条销量(共 " + menus.size() + " 条菜谱)");
log.info("每日菜谱按订单重算销量 merchantId={} mealDate={} dailyMenuId={} menuRows={} updated={}",
merchantId, mealDate, dailyMenuId, menus.size(), toUpdate.size());
String scope = dailyMenuId == null ? ("共 " + menus.size() + " 条菜谱") : "本菜 1 条";
return R.ok(toUpdate.size(), "已按订单重算 " + toUpdate.size() + " 条销量(" + scope + ")");
}
private static String weekdayLabel(DayOfWeek dayOfWeek) {
@@ -895,6 +895,29 @@ public class CanteenSpuServiceImpl extends ServiceImpl<CanteenSpuMapper, Canteen
return result;
}
@Override
public Map<Long, Integer> mapComboQtyByChildSpuId(Long childSpuId) {
if (childSpuId == null) {
return Collections.emptyMap();
}
List<CanteenSpuComboItemEntity> lines = comboItemMapper.selectList(
Wrappers.<CanteenSpuComboItemEntity>lambdaQuery()
.select(CanteenSpuComboItemEntity::getComboSpuId, CanteenSpuComboItemEntity::getQuantity)
.eq(CanteenSpuComboItemEntity::getChildSpuId, childSpuId));
if (CollUtil.isEmpty(lines)) {
return Collections.emptyMap();
}
Map<Long, Integer> result = new LinkedHashMap<>();
for (CanteenSpuComboItemEntity line : lines) {
if (line == null || line.getComboSpuId() == null) {
continue;
}
int qty = line.getQuantity() == null || line.getQuantity() < 1 ? 1 : line.getQuantity();
result.merge(line.getComboSpuId(), qty, Integer::sum);
}
return result;
}
@Override
public List<SpuPrintNameVo> loadSpuPrintNames(List<Long> productIds) {
if (CollUtil.isEmpty(productIds)) {
@@ -99,7 +99,8 @@
IFNULL(t.offline_stock, 600) AS offline_stock,
IFNULL(t.online_sold, 0) AS online_sold,
IFNULL(t.offline_sold, 0) AS offline_sold,
sp.sales_count,
<!-- 每日菜品「已订/已售」展示当日移动端已订,勿用 canteen_spu 历史累计销量 -->
IFNULL(t.online_sold, 0) AS sales_count,
sp.sort,
sp.is_show,
c.id AS category_id,
@@ -154,7 +155,7 @@
IFNULL(t.offline_stock, 600) AS offline_stock,
IFNULL(t.online_sold, 0) AS online_sold,
IFNULL(t.offline_sold, 0) AS offline_sold,
sp.sales_count,
IFNULL(t.online_sold, 0) AS sales_count,
sp.sort,
sp.is_show,
c.id AS category_id,
@@ -197,7 +198,7 @@
IFNULL(t.offline_stock, 600) AS offline_stock,
IFNULL(t.online_sold, 0) AS online_sold,
IFNULL(t.offline_sold, 0) AS offline_sold,
sp.sales_count,
IFNULL(t.online_sold, 0) AS sales_count,
sp.sort,
sp.is_show,
c.id AS category_id,
@@ -283,7 +284,7 @@
IFNULL(m.offline_stock, 600) AS offline_stock,
IFNULL(m.online_sold, 0) AS online_sold,
IFNULL(m.offline_sold, 0) AS offline_sold,
s.sales_count,
IFNULL(m.online_sold, 0) AS sales_count,
s.pic_url,
s.slider_pic_urls,
s.introduction,
@@ -73,6 +73,12 @@ public class HardOrderDto extends HardOrderEntity implements BaseIsIncludeTreeIn
*/
private Long productId;
/**
* 购物车商品 SPU ID 集合;用于含套餐父商品的「相关订单」查询(本菜 + 含本菜的套餐)。
* 与 {@link #productId} 同时有值时取并集语义(XML 用 IN)。
*/
private List<Long> productIds;
private LocalDateTime[] refundTimeList;
/** 预计使用日期范围。 */
@@ -239,12 +239,22 @@
<if test="dto.apptUseDateTimeList != null and dto.apptUseDateTimeList.length == 2">
and a.appt_use_date between #{dto.apptUseDateTimeList[0]} and #{dto.apptUseDateTimeList[1]}
</if>
<if test="dto.productId != null">
<if test="dto.productId != null or (dto.productIds != null and dto.productIds.size() != 0)">
and exists (
select 1 from hard_car c
where c.parent_car_id = a.parent_car_id
and c.del_flag = '0'
and c.product_id = #{dto.productId}
<choose>
<when test="dto.productIds != null and dto.productIds.size() != 0">
and c.product_id in
<foreach collection="dto.productIds" item="v" open="(" close=")" separator=",">
#{v}
</foreach>
</when>
<otherwise>
and c.product_id = #{dto.productId}
</otherwise>
</choose>
)
</if>
<if test="dto.apptUseDateStart != null">