每日库存
This commit is contained in:
1 parent
2087dd7933
commit
c6d7e4cad0
15 files changed
+351
-91
No files matched your search
+10
-4
@@ -46,18 +46,24 @@ public interface RemoteCanteenSpuService {
|
||||
|
||||
|
||||
/**
|
||||
* 获取菜品列表
|
||||
* 获取菜品列表(有每日菜谱时 stock=当日线下剩余)
|
||||
* @param categoryId 不填默认查全部
|
||||
* @param day 菜谱日期 yyyy-MM-dd,不填默认当天
|
||||
* @param hourType 餐段,不填按分类解析
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("/canteenSpu/ad/getCanteenSpuList")
|
||||
R getCanteenSpuList(@RequestParam(value = "categoryId",required = false)String categoryId);
|
||||
R getCanteenSpuList(@RequestParam(value = "categoryId",required = false)String categoryId,
|
||||
@RequestParam(value = "day", required = false) String day,
|
||||
@RequestParam(value = "hourType", required = false) Integer hourType);
|
||||
|
||||
/**
|
||||
* 获取菜品详情(含详情富文本、套餐组成)
|
||||
* 获取菜品详情(含详情富文本、套餐组成);有每日菜谱时 stock=线下剩余
|
||||
*/
|
||||
@GetMapping("/canteenSpu/ad/getCanteenSpuDetails")
|
||||
R getCanteenSpuDetails(@RequestParam("id") Long id);
|
||||
R getCanteenSpuDetails(@RequestParam("id") Long id,
|
||||
@RequestParam(value = "day", required = false) String day,
|
||||
@RequestParam(value = "hourType", required = false) Integer hourType);
|
||||
|
||||
|
||||
/**
|
||||
|
||||
+14
-2
@@ -50,17 +50,29 @@ public class CanteenDailyMenuEntity extends Model<CanteenDailyMenuEntity> {
|
||||
private Integer hourType;
|
||||
|
||||
/**
|
||||
* 线上可预约库存配额(人工设置;剩余=配额−线上净销量)
|
||||
* 线上可预约库存配额(人工设置;下单不改;剩余=配额−onlineSold)
|
||||
*/
|
||||
@Schema(description = "线上可预约库存配额")
|
||||
private Integer onlineStock;
|
||||
|
||||
/**
|
||||
* 线下销售库存配额(人工设置;剩余对外字段名仍用 stock)
|
||||
* 线下销售库存配额(人工设置;下单不改;剩余=配额−offlineSold)
|
||||
*/
|
||||
@Schema(description = "线下销售库存配额")
|
||||
private Integer offlineStock;
|
||||
|
||||
/**
|
||||
* 线上净销量(下单增加、售后完成减少)
|
||||
*/
|
||||
@Schema(description = "线上净销量")
|
||||
private Integer onlineSold;
|
||||
|
||||
/**
|
||||
* 线下净销量(下单增加、售后完成减少)
|
||||
*/
|
||||
@Schema(description = "线下净销量")
|
||||
private Integer offlineSold;
|
||||
|
||||
/**
|
||||
* 商户id
|
||||
*/
|
||||
|
||||
+2
-2
@@ -65,9 +65,9 @@ public class ADCanteenSpuListVO {
|
||||
|
||||
|
||||
/**
|
||||
* 库存
|
||||
* 库存:无每日菜谱时为 canteen_spu.stock;有菜谱时 AD=线下剩余、App 详情=线上剩余
|
||||
*/
|
||||
@Schema(description="库存")
|
||||
@Schema(description="库存(有每日菜谱时为通道剩余)")
|
||||
private Integer stock;
|
||||
|
||||
|
||||
|
||||
+6
@@ -53,6 +53,12 @@ public class DailyMenuAdminVO {
|
||||
*/
|
||||
private Integer stock;
|
||||
|
||||
/** 线上净销量(仅服务端填剩余用,可不展示) */
|
||||
private Integer onlineSold;
|
||||
|
||||
/** 线下净销量(仅服务端填剩余用,可不展示) */
|
||||
private Integer offlineSold;
|
||||
|
||||
/** 线上剩余 = onlineStock − 预约净销量 */
|
||||
private Integer remainOnlineStock;
|
||||
|
||||
|
||||
+14
-2
@@ -82,12 +82,24 @@ public class DailyMenuAppVO {
|
||||
*/
|
||||
private Integer stock;
|
||||
|
||||
/** 线上配额(可选透出) */
|
||||
/** 线上配额(人工;下单不改) */
|
||||
private Integer onlineStock;
|
||||
|
||||
/** 线下配额(可选透出) */
|
||||
/** 线下配额(人工;下单不改) */
|
||||
private Integer offlineStock;
|
||||
|
||||
/** 线上净销量(服务端计算剩余用) */
|
||||
private Integer onlineSold;
|
||||
|
||||
/** 线下净销量(服务端计算剩余用) */
|
||||
private Integer offlineSold;
|
||||
|
||||
/** 线上剩余 = onlineStock − onlineSold */
|
||||
private Integer remainOnlineStock;
|
||||
|
||||
/** 线下剩余 = offlineStock − offlineSold */
|
||||
private Integer remainOfflineStock;
|
||||
|
||||
/**
|
||||
* 商品销量,仅用于详情页展示。
|
||||
*/
|
||||
|
||||
+32
-23
@@ -26,7 +26,6 @@ import com.bsd.pjcloud.canteen.vo.erp.CanteenSpuSimpleVO;
|
||||
import com.bsd.pjcloud.canteen.vo.erp.CanteenSpuStockVO;
|
||||
import com.bsd.pjcloud.common.core.exception.ErrorCodes;
|
||||
import com.bsd.pjcloud.common.core.util.MsgUtils;
|
||||
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.CanteenSpuEntity;
|
||||
@@ -45,7 +44,6 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
@@ -339,57 +337,68 @@ public class CanteenSpuController {
|
||||
|
||||
|
||||
/**
|
||||
* 获取菜品列表
|
||||
* 获取菜品列表(有每日菜谱时 stock=当日线下剩余)
|
||||
*
|
||||
* @param categoryId 分类编号,不传默认查询全部
|
||||
* @return
|
||||
* @param day 菜谱日期 yyyy-MM-dd,不传默认当天
|
||||
* @param hourType 餐段,不传则按分类 categoryType 解析
|
||||
*/
|
||||
@GetMapping("/ad/getCanteenSpuList")
|
||||
public R getCanteenSpuList(@RequestParam(value = "categoryId", required = false) String categoryId) {
|
||||
return canteenSpuService.getCanteenSpuList(categoryId);
|
||||
public R getCanteenSpuList(@RequestParam(value = "categoryId", required = false) String categoryId,
|
||||
@RequestParam(value = "day", required = false) String day,
|
||||
@RequestParam(value = "hourType", required = false) Integer hourType) {
|
||||
return canteenSpuService.getCanteenSpuList(categoryId, day, hourType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询收银终端使用的每日菜品详情,并补充富文本与套餐组成。
|
||||
* stock:有每日菜谱行时为线下剩余。
|
||||
*
|
||||
* @param id 每日菜品 SPU ID
|
||||
* @param day 菜谱日期,空则当天
|
||||
* @param hourType 餐段,空则按分类解析
|
||||
* @return 每日菜品详情;商品不存在时返回失败响应
|
||||
*/
|
||||
@GetMapping("/ad/getCanteenSpuDetails")
|
||||
public R<ADCanteenSpuListVO> getCanteenSpuDetails(@RequestParam("id") Long id) {
|
||||
CanteenSpuEntity detail = canteenSpuService.getDetail(id);
|
||||
if (detail == null) {
|
||||
public R<ADCanteenSpuListVO> getCanteenSpuDetails(@RequestParam("id") Long id,
|
||||
@RequestParam(value = "day", required = false) String day,
|
||||
@RequestParam(value = "hourType", required = false) Integer hourType) {
|
||||
ADCanteenSpuListVO vo = canteenSpuService.buildAdSpuDetails(id, day, hourType, true);
|
||||
if (vo == null) {
|
||||
return R.failed(MsgUtils.getMessage(ErrorCodes.COMMON_PRODUCT_NOT_FOUND));
|
||||
}
|
||||
ADCanteenSpuListVO vo = BeanUtils.toBean(detail, ADCanteenSpuListVO.class);
|
||||
vo.setIntroduction(detail.getIntroduction());
|
||||
vo.setDescription(detail.getDescription());
|
||||
int type = detail.getSpuType() == null ? 1 : detail.getSpuType();
|
||||
vo.setSpuType(type);
|
||||
if (type == 2) {
|
||||
vo.setComboItems(canteenSpuService.loadComboItemVos(Collections.singletonList(id))
|
||||
.getOrDefault(id, Collections.emptyList()));
|
||||
}
|
||||
else {
|
||||
vo.setComboItems(Collections.emptyList());
|
||||
}
|
||||
return R.ok(vo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按每日菜品 SPU ID 查询 App 商品详情。
|
||||
* stock:有每日菜谱行时为线上剩余。
|
||||
*
|
||||
* @param id 套餐组成返回的 childSpuId,关联 canteen_spu 主键
|
||||
* @param day 菜谱日期,空则当天
|
||||
* @param hourType 餐段,空则按分类解析
|
||||
* @return App 可展示的每日菜品详情;商品不存在时返回失败响应
|
||||
*/
|
||||
@ApiCryptoAes
|
||||
@Operation(summary = "查询每日菜品详情", description = "通过每日菜品 SPU ID 查询富文本和套餐组成")
|
||||
@GetMapping("/app/getCanteenSpuDetails")
|
||||
public R<ADCanteenSpuListVO> getAppCanteenSpuDetails(@RequestParam("id") Long id) {
|
||||
return getCanteenSpuDetails(id);
|
||||
public R<ADCanteenSpuListVO> getAppCanteenSpuDetails(@RequestParam("id") Long id,
|
||||
@RequestParam(value = "day", required = false) String day,
|
||||
@RequestParam(value = "hourType", required = false) Integer hourType) {
|
||||
ADCanteenSpuListVO vo = canteenSpuService.buildAdSpuDetails(id, day, hourType, false);
|
||||
if (vo == null) {
|
||||
return R.failed(MsgUtils.getMessage(ErrorCodes.COMMON_PRODUCT_NOT_FOUND));
|
||||
}
|
||||
return R.ok(vo);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 这里有个问题,就是因为每日加了库存,为了不影响原有的安卓消费机调用扣减库存的逻辑
|
||||
* 因此这里传参还是效验了spuid,因为之前的库存在菜品列表里,所以为什么不直接拿菜谱id去扣减,用「商户+日期+餐段+SPU」去定位
|
||||
* @param updateStockReqDTO
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("/ad/updateSkuStock")
|
||||
@SysLog("更新菜品库存")
|
||||
@PreAuthorize("@pms.hasPermission('canteen_canteenSpu_edit')")
|
||||
|
||||
+2
-2
@@ -64,14 +64,14 @@ public interface CanteenDailyMenuMapper extends PjcloudBaseMapper<CanteenDailyMe
|
||||
@Param("categoryId") Long categoryId, DataScope dataScope);
|
||||
|
||||
/**
|
||||
* 条件更新线上库存;expected 非空时要求更新前等于 expected。
|
||||
* 条件更新线上净销量(delta 为剩余侧增量:下单负数);expected 为操作前剩余。
|
||||
*/
|
||||
int updateOnlineStockDelta(@Param("merchantId") Long merchantId, @Param("mealDate") java.time.LocalDate mealDate,
|
||||
@Param("hourType") Integer hourType, @Param("spuId") Long spuId, @Param("delta") int delta,
|
||||
@Param("expected") Integer expected);
|
||||
|
||||
/**
|
||||
* 条件更新线下库存;expected 非空时要求更新前等于 expected。
|
||||
* 条件更新线下净销量(delta 为剩余侧增量:下单负数);expected 为操作前剩余。
|
||||
*/
|
||||
int updateOfflineStockDelta(@Param("merchantId") Long merchantId, @Param("mealDate") java.time.LocalDate mealDate,
|
||||
@Param("hourType") Integer hourType, @Param("spuId") Long spuId, @Param("delta") int delta,
|
||||
|
||||
+17
-4
@@ -8,6 +8,7 @@ import com.bsd.pjcloud.canteen.dto.CanteenSpuUpdateStockReqDTO;
|
||||
import com.bsd.pjcloud.canteen.entity.CanteenSpuEntity;
|
||||
import com.bsd.pjcloud.canteen.vo.erp.CanteenSpuSimpleVO;
|
||||
import com.bsd.pjcloud.canteen.vo.erp.CanteenSpuStockVO;
|
||||
import com.bsd.pjcloud.canteen.vo.ADCanteenSpuListVO;
|
||||
import com.bsd.pjcloud.canteen.vo.app.OnlineSpuComboItemVO;
|
||||
import com.bsd.pjcloud.common.core.util.R;
|
||||
|
||||
@@ -42,11 +43,23 @@ public interface CanteenSpuService extends IService<CanteenSpuEntity> {
|
||||
|
||||
|
||||
/**
|
||||
* 根据菜品分类id获取菜品信息
|
||||
* @param categoryId
|
||||
* @return
|
||||
* 安卓消费机菜品列表;有每日菜谱行时 stock 为当日线下剩余。
|
||||
*
|
||||
* @param categoryId 分类,空则全部
|
||||
* @param day 菜谱日期 yyyy-MM-dd,空则当天
|
||||
* @param hourType 餐段,空则按分类 categoryType 解析
|
||||
*/
|
||||
R getCanteenSpuList(String categoryId);
|
||||
R getCanteenSpuList(String categoryId, String day, Integer hourType);
|
||||
|
||||
/**
|
||||
* 组装安卓/App 菜品详情 VO,并按通道覆盖每日菜谱剩余库存。
|
||||
*
|
||||
* @param id SPU ID
|
||||
* @param day 菜谱日期,空则当天
|
||||
* @param hourType 餐段,空则按分类解析
|
||||
* @param offline true=线下剩余(AD),false=线上剩余(App)
|
||||
*/
|
||||
ADCanteenSpuListVO buildAdSpuDetails(Long id, String day, Integer hourType, boolean offline);
|
||||
|
||||
|
||||
/**
|
||||
|
||||
+1
-1
@@ -1459,7 +1459,7 @@ public class CanteenDailyMenuImportHelper {
|
||||
* 解析/创建单品。
|
||||
* <p>有「菜品SPU ID」→ 按 ID 复用并按需更新;无 ID → 一律新建,不再按菜名复用。
|
||||
* <p>{@code stock} 仅在显式传入时更新 {@code canteen_spu.stock};每日菜谱导入传 {@code null},
|
||||
* 当日可售走 {@code canteen_daily_menu.online_stock/offline_stock}。
|
||||
* 当日可售 = 配额(online_stock/offline_stock) − 销量;导入只写配额,不改销量。
|
||||
*/
|
||||
private FindOrCreate findOrCreateDish(Long merchantId, Long deptId, String categoryName, Long defaultCategoryId,
|
||||
Map<String, Long> categoryMap, String name, String ingredients, Integer priceFen, Integer stock,
|
||||
|
||||
+132
-28
@@ -1,21 +1,27 @@
|
||||
package com.bsd.pjcloud.canteen.service.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.bsd.pjcloud.canteen.entity.CanteenDailyMenuEntity;
|
||||
import com.bsd.pjcloud.canteen.vo.ADCanteenSpuListVO;
|
||||
import com.bsd.pjcloud.canteen.vo.DailyMenuAdminVO;
|
||||
import com.bsd.pjcloud.canteen.vo.app.DailyMenuAppVO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 每日菜谱分池库存展示。
|
||||
* <p>
|
||||
* {@code online_stock}/{@code offline_stock} 为可售剩余(下单扣减、退货加回);
|
||||
* App 的 {@code stock}=线上剩余,安卓 AD 的 {@code stock}=线下剩余(字段名兼容旧客户端)。
|
||||
* {@code online_stock}/{@code offline_stock} 为人工配额(下单不改);
|
||||
* {@code online_sold}/{@code offline_sold} 为净销量;剩余 = 配额 − 销量。
|
||||
* App 的 {@code stock}=线上剩余,安卓 AD 的 {@code stock}=线下剩余。
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@@ -27,17 +33,30 @@ public class CanteenDailyMenuStockSupport {
|
||||
return quota == null || quota < 0 ? DEFAULT_QUOTA : quota;
|
||||
}
|
||||
|
||||
/** App:stock = 线上剩余 */
|
||||
public static int soldOrDefault(Integer sold) {
|
||||
return sold == null || sold < 0 ? 0 : sold;
|
||||
}
|
||||
|
||||
/** 剩余 = max(0, 配额 − 销量) */
|
||||
public static int remainOf(Integer quota, Integer sold) {
|
||||
return Math.max(0, quotaOrDefault(quota) - soldOrDefault(sold));
|
||||
}
|
||||
|
||||
public static int channelRemain(CanteenDailyMenuEntity menu, boolean offline) {
|
||||
if (menu == null) {
|
||||
return 0;
|
||||
}
|
||||
return offline ? remainOf(menu.getOfflineStock(), menu.getOfflineSold())
|
||||
: remainOf(menu.getOnlineStock(), menu.getOnlineSold());
|
||||
}
|
||||
|
||||
/** App:stock = 线上剩余;onlineStock/offlineStock 仍透出配额 */
|
||||
public void fillAppOnlineRemain(List<DailyMenuAppVO> rows, Long merchantId, LocalDate mealDate) {
|
||||
if (CollUtil.isEmpty(rows)) {
|
||||
return;
|
||||
}
|
||||
for (DailyMenuAppVO row : rows) {
|
||||
int online = quotaOrDefault(row.getOnlineStock());
|
||||
int offline = quotaOrDefault(row.getOfflineStock());
|
||||
row.setOnlineStock(online);
|
||||
row.setOfflineStock(offline);
|
||||
row.setStock(online);
|
||||
applyAppQuotasAndRemains(row, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,11 +66,7 @@ public class CanteenDailyMenuStockSupport {
|
||||
return;
|
||||
}
|
||||
for (DailyMenuAppVO row : rows) {
|
||||
int online = quotaOrDefault(row.getOnlineStock());
|
||||
int offline = quotaOrDefault(row.getOfflineStock());
|
||||
row.setOnlineStock(online);
|
||||
row.setOfflineStock(offline);
|
||||
row.setStock(offline);
|
||||
applyAppQuotasAndRemains(row, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,32 +80,121 @@ public class CanteenDailyMenuStockSupport {
|
||||
for (Map.Entry<LocalDate, List<DailyMenuAppVO>> e : byDate.entrySet()) {
|
||||
fillAdOfflineRemain(e.getValue(), merchantId, e.getKey());
|
||||
}
|
||||
// 无日期行仍规范默认值
|
||||
for (DailyMenuAppVO row : rows) {
|
||||
if (row.getStartTime() == null) {
|
||||
int online = quotaOrDefault(row.getOnlineStock());
|
||||
int offline = quotaOrDefault(row.getOfflineStock());
|
||||
row.setOnlineStock(online);
|
||||
row.setOfflineStock(offline);
|
||||
row.setStock(offline);
|
||||
applyAppQuotasAndRemains(row, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** PC:配额即当前剩余;合计供旧列 remainStock */
|
||||
/** PC:配额与剩余分列展示 */
|
||||
public void fillAdminRemain(List<DailyMenuAdminVO> rows, Long merchantId, LocalDate mealDate) {
|
||||
if (CollUtil.isEmpty(rows)) {
|
||||
return;
|
||||
}
|
||||
for (DailyMenuAdminVO row : rows) {
|
||||
int online = quotaOrDefault(row.getOnlineStock());
|
||||
int offline = quotaOrDefault(row.getOfflineStock());
|
||||
row.setOnlineStock(online);
|
||||
row.setOfflineStock(offline);
|
||||
row.setRemainOnlineStock(online);
|
||||
row.setRemainOfflineStock(offline);
|
||||
row.setStock(offline);
|
||||
row.setRemainStock(online + offline);
|
||||
int onlineQuota = quotaOrDefault(row.getOnlineStock());
|
||||
int offlineQuota = quotaOrDefault(row.getOfflineStock());
|
||||
int onlineRemain = remainOf(onlineQuota, row.getOnlineSold());
|
||||
int offlineRemain = remainOf(offlineQuota, row.getOfflineSold());
|
||||
row.setOnlineStock(onlineQuota);
|
||||
row.setOfflineStock(offlineQuota);
|
||||
row.setRemainOnlineStock(onlineRemain);
|
||||
row.setRemainOfflineStock(offlineRemain);
|
||||
row.setStock(offlineRemain);
|
||||
row.setRemainStock(onlineRemain + offlineRemain);
|
||||
}
|
||||
}
|
||||
|
||||
private static void applyAppQuotasAndRemains(DailyMenuAppVO row, boolean stockAsOnline) {
|
||||
int onlineQuota = quotaOrDefault(row.getOnlineStock());
|
||||
int offlineQuota = quotaOrDefault(row.getOfflineStock());
|
||||
int onlineRemain = remainOf(onlineQuota, row.getOnlineSold());
|
||||
int offlineRemain = remainOf(offlineQuota, row.getOfflineSold());
|
||||
row.setOnlineStock(onlineQuota);
|
||||
row.setOfflineStock(offlineQuota);
|
||||
row.setRemainOnlineStock(onlineRemain);
|
||||
row.setRemainOfflineStock(offlineRemain);
|
||||
row.setStock(stockAsOnline ? onlineRemain : offlineRemain);
|
||||
}
|
||||
|
||||
/**
|
||||
* 安卓 SPU 查看接口:若当日菜谱存在对应行,则 {@code stock} 覆盖为线下剩余;无菜谱行则保留 canteen_spu.stock。
|
||||
*
|
||||
* @param rows 已组装的 AD VO(含 merchantId、categoryType)
|
||||
* @param menus 当日(或指定日)相关每日菜谱行
|
||||
* @param hourTypeOverride 请求显式餐段;为空时从 VO.categoryType 解析
|
||||
* @param offline true=线下剩余(AD),false=线上剩余(App 详情复用)
|
||||
*/
|
||||
public void overlaySpuViewStock(List<ADCanteenSpuListVO> rows, List<CanteenDailyMenuEntity> menus,
|
||||
Integer hourTypeOverride, boolean offline) {
|
||||
if (CollUtil.isEmpty(rows) || CollUtil.isEmpty(menus)) {
|
||||
return;
|
||||
}
|
||||
Map<String, CanteenDailyMenuEntity> byKey = new HashMap<>();
|
||||
for (CanteenDailyMenuEntity menu : menus) {
|
||||
if (menu == null || menu.getMerchantId() == null || menu.getSpuId() == null || menu.getHourType() == null) {
|
||||
continue;
|
||||
}
|
||||
byKey.put(menuKey(menu.getMerchantId(), menu.getHourType(), menu.getSpuId()), menu);
|
||||
}
|
||||
for (ADCanteenSpuListVO row : rows) {
|
||||
if (row == null || row.getMerchantId() == null || row.getId() == null) {
|
||||
continue;
|
||||
}
|
||||
Integer hourType = hourTypeOverride != null ? hourTypeOverride : parseHourType(row.getCategoryType());
|
||||
if (hourType == null) {
|
||||
// 分类未标餐段时:同一商户+SPU 当日若只有一行菜谱,仍可覆盖
|
||||
CanteenDailyMenuEntity only = findUniqueMenu(menus, row.getMerchantId(), row.getId());
|
||||
if (only != null) {
|
||||
row.setStock(channelRemain(only, offline));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
CanteenDailyMenuEntity menu = byKey.get(menuKey(row.getMerchantId(), hourType, row.getId()));
|
||||
if (menu != null) {
|
||||
row.setStock(channelRemain(menu, offline));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static CanteenDailyMenuEntity findUniqueMenu(List<CanteenDailyMenuEntity> menus, Long merchantId,
|
||||
Long spuId) {
|
||||
CanteenDailyMenuEntity found = null;
|
||||
for (CanteenDailyMenuEntity menu : menus) {
|
||||
if (menu == null || !Objects.equals(menu.getMerchantId(), merchantId)
|
||||
|| !Objects.equals(menu.getSpuId(), spuId)) {
|
||||
continue;
|
||||
}
|
||||
if (found != null) {
|
||||
return null;
|
||||
}
|
||||
found = menu;
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
private static String menuKey(Long merchantId, Integer hourType, Long spuId) {
|
||||
return merchantId + "|" + hourType + "|" + spuId;
|
||||
}
|
||||
|
||||
/** categoryType 支持 "2" 或 "2,3";取第一个有效餐段。 */
|
||||
public static Integer parseHourType(String categoryType) {
|
||||
if (StrUtil.isBlank(categoryType)) {
|
||||
return null;
|
||||
}
|
||||
for (String part : categoryType.split(",")) {
|
||||
String token = StrUtil.trim(part);
|
||||
if (StrUtil.isBlank(token)) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
return Integer.valueOf(token);
|
||||
}
|
||||
catch (Exception ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+73
-11
@@ -70,6 +70,7 @@ public class CanteenSpuServiceImpl extends ServiceImpl<CanteenSpuMapper, Canteen
|
||||
private final CanteenOnlineSpuMapper canteenOnlineSpuMapper;
|
||||
private final CanteenSpuComboItemMapper comboItemMapper;
|
||||
private final CanteenMerchantScopeHelper merchantScopeHelper;
|
||||
private final CanteenDailyMenuStockSupport dailyMenuStockSupport;
|
||||
|
||||
@Override
|
||||
public Object selectFoodList(LambdaQueryWrapper<CanteenSpuEntity> wrapper) {
|
||||
@@ -206,7 +207,7 @@ public class CanteenSpuServiceImpl extends ServiceImpl<CanteenSpuMapper, Canteen
|
||||
return "ONLINE".equals(ch) || "OFFLINE".equals(ch);
|
||||
}
|
||||
|
||||
/** 非幂等分池扣减(安卓仍调 /ad/updateSkuStock,由硬件补 stockChannel)。 */
|
||||
/** 非幂等分池扣减:改写 daily_menu 销量字段(安卓仍调 /ad/updateSkuStock,由硬件补 stockChannel)。 */
|
||||
private R updateDailyMenuChannelStock(CanteenSpuUpdateStockReqDTO dto) {
|
||||
if (dto.getMerchantId() == null) {
|
||||
return R.failed(MsgUtils.getMessage(ErrorCodes.CANTEEN_STOCK_BIZ_PARAM_BAD));
|
||||
@@ -221,11 +222,10 @@ public class CanteenSpuServiceImpl extends ServiceImpl<CanteenSpuMapper, Canteen
|
||||
CanteenDailyMenuEntity menu = findDailyMenuRow(dto.getMerchantId(), item.getMealDate(), item.getHourType(),
|
||||
item.getId());
|
||||
if (menu == null) {
|
||||
return R.failed(MsgUtils.getMessage(ErrorCodes.COMMON_PRODUCT_NOT_FOUND));
|
||||
// 分池扣减按「商户+日期+餐段+SPU」查每日菜谱;查不到不是 canteen_spu 不存在
|
||||
return R.failed(MsgUtils.getMessage(ErrorCodes.HARD_DAILY_MENU_PRODUCT_BAD));
|
||||
}
|
||||
int current = offline
|
||||
? CanteenDailyMenuStockSupport.quotaOrDefault(menu.getOfflineStock())
|
||||
: CanteenDailyMenuStockSupport.quotaOrDefault(menu.getOnlineStock());
|
||||
int current = CanteenDailyMenuStockSupport.channelRemain(menu, offline);
|
||||
if (item.getIncrCount() < 0 && Math.abs(item.getIncrCount()) > current) {
|
||||
CanteenSpuEntity spu = baseMapper.selectById(item.getId());
|
||||
return R.failed(MsgUtils.getMessage(ErrorCodes.HARD_PRODUCT_STOCK_LOW,
|
||||
@@ -245,7 +245,7 @@ public class CanteenSpuServiceImpl extends ServiceImpl<CanteenSpuMapper, Canteen
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
/** 幂等分池扣减:改写 daily_menu.online_stock / offline_stock。 */
|
||||
/** 幂等分池扣减:改写 daily_menu.online_sold / offline_sold(配额字段不动)。 */
|
||||
private R updateDailyMenuChannelStockIdempotent(CanteenSpuUpdateStockReqDTO dto, boolean applyOperation,
|
||||
boolean rollbackOperation) {
|
||||
if (dto.getMerchantId() == null
|
||||
@@ -265,11 +265,9 @@ public class CanteenSpuServiceImpl extends ServiceImpl<CanteenSpuMapper, Canteen
|
||||
CanteenDailyMenuEntity menu = findDailyMenuRow(dto.getMerchantId(), item.getMealDate(), item.getHourType(),
|
||||
item.getId());
|
||||
if (menu == null) {
|
||||
return R.failed(MsgUtils.getMessage(ErrorCodes.CANTEEN_STOCK_SPU_BAD));
|
||||
return R.failed(MsgUtils.getMessage(ErrorCodes.HARD_DAILY_MENU_PRODUCT_BAD));
|
||||
}
|
||||
int current = offline
|
||||
? CanteenDailyMenuStockSupport.quotaOrDefault(menu.getOfflineStock())
|
||||
: CanteenDailyMenuStockSupport.quotaOrDefault(menu.getOnlineStock());
|
||||
int current = CanteenDailyMenuStockSupport.channelRemain(menu, offline);
|
||||
int expectedStock = item.getBeforeStock();
|
||||
if (expectedStock < 0) {
|
||||
return R.failed(MsgUtils.getMessage(ErrorCodes.CANTEEN_STOCK_SNAPSHOT_BAD));
|
||||
@@ -367,7 +365,7 @@ public class CanteenSpuServiceImpl extends ServiceImpl<CanteenSpuMapper, Canteen
|
||||
}
|
||||
|
||||
@Override
|
||||
public R getCanteenSpuList(String categoryId) {
|
||||
public R getCanteenSpuList(String categoryId, String day, Integer hourType) {
|
||||
List<CanteenSpuEntity> canteenSpuEntities = baseMapper.selectListByScope(new LambdaQueryWrapper<CanteenSpuEntity>()
|
||||
.eq(StringUtils.isNotEmpty(categoryId), CanteenSpuEntity::getCategoryId, categoryId), DataScope.of());
|
||||
if (CollectionUtils.isEmpty(canteenSpuEntities)) {
|
||||
@@ -404,9 +402,73 @@ public class CanteenSpuServiceImpl extends ServiceImpl<CanteenSpuMapper, Canteen
|
||||
item.setCategoryType(listMap.get(item.getCategoryId()).get(0).getCategoryType());
|
||||
}
|
||||
}
|
||||
// 有每日菜谱则 stock=线下剩余,避免安卓仍展示 canteen_spu.stock
|
||||
overlayDailyRemainOnAdVos(bean, day, hourType, true);
|
||||
return R.ok(bean);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ADCanteenSpuListVO buildAdSpuDetails(Long id, String day, Integer hourType, boolean offline) {
|
||||
CanteenSpuEntity detail = getDetail(id);
|
||||
if (detail == null) {
|
||||
return null;
|
||||
}
|
||||
ADCanteenSpuListVO vo = BeanUtils.toBean(detail, ADCanteenSpuListVO.class);
|
||||
vo.setIntroduction(detail.getIntroduction());
|
||||
vo.setDescription(detail.getDescription());
|
||||
int type = detail.getSpuType() == null ? 1 : detail.getSpuType();
|
||||
vo.setSpuType(type);
|
||||
if (type == 2) {
|
||||
vo.setComboItems(loadComboItemVos(Collections.singletonList(id)).getOrDefault(id, Collections.emptyList()));
|
||||
}
|
||||
else {
|
||||
vo.setComboItems(Collections.emptyList());
|
||||
}
|
||||
if (detail.getCategoryId() != null) {
|
||||
CanteenCategoryEntity category = canteenCategoryMapper.selectById(detail.getCategoryId());
|
||||
if (category != null && category.getCategoryType() != null) {
|
||||
vo.setCategoryType(category.getCategoryType());
|
||||
}
|
||||
}
|
||||
overlayDailyRemainOnAdVos(Collections.singletonList(vo), day, hourType, offline);
|
||||
return vo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量按「商户+日期+餐段+SPU」覆盖 AD/App 查看接口的 stock。
|
||||
*/
|
||||
private void overlayDailyRemainOnAdVos(List<ADCanteenSpuListVO> rows, String day, Integer hourType,
|
||||
boolean offline) {
|
||||
if (CollUtil.isEmpty(rows)) {
|
||||
return;
|
||||
}
|
||||
java.time.LocalDate mealDate = parseMealDateOrToday(day);
|
||||
List<Long> merchantIds = rows.stream().map(ADCanteenSpuListVO::getMerchantId).filter(Objects::nonNull)
|
||||
.distinct().collect(Collectors.toList());
|
||||
List<Long> spuIds = rows.stream().map(ADCanteenSpuListVO::getId).filter(Objects::nonNull).distinct()
|
||||
.collect(Collectors.toList());
|
||||
if (CollUtil.isEmpty(merchantIds) || CollUtil.isEmpty(spuIds)) {
|
||||
return;
|
||||
}
|
||||
List<CanteenDailyMenuEntity> menus = canteenDailyMenuMapper.selectList(Wrappers.<CanteenDailyMenuEntity>lambdaQuery()
|
||||
.in(CanteenDailyMenuEntity::getMerchantId, merchantIds)
|
||||
.eq(CanteenDailyMenuEntity::getStartTime, mealDate)
|
||||
.in(CanteenDailyMenuEntity::getSpuId, spuIds));
|
||||
dailyMenuStockSupport.overlaySpuViewStock(rows, menus, hourType, offline);
|
||||
}
|
||||
|
||||
private static java.time.LocalDate parseMealDateOrToday(String day) {
|
||||
if (StrUtil.isBlank(day)) {
|
||||
return java.time.LocalDate.now();
|
||||
}
|
||||
try {
|
||||
return java.time.LocalDate.parse(day.trim());
|
||||
}
|
||||
catch (Exception e) {
|
||||
return java.time.LocalDate.now();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object pageScope(Page page, CanteenSpuEntity canteenSpu) {
|
||||
LambdaQueryWrapper<CanteenSpuEntity> wrapper = new LambdaQueryWrapper<>();
|
||||
|
||||
+27
-6
@@ -9,6 +9,8 @@
|
||||
<result property="hourType" column="hour_type"/>
|
||||
<result property="onlineStock" column="online_stock"/>
|
||||
<result property="offlineStock" column="offline_stock"/>
|
||||
<result property="onlineSold" column="online_sold"/>
|
||||
<result property="offlineSold" column="offline_sold"/>
|
||||
<result property="merchantId" column="merchant_id"/>
|
||||
<result property="deptId" column="dept_id"/>
|
||||
<result property="createBy" column="create_by"/>
|
||||
@@ -36,6 +38,8 @@
|
||||
<result property="stock" column="stock"/>
|
||||
<result property="onlineStock" column="online_stock"/>
|
||||
<result property="offlineStock" column="offline_stock"/>
|
||||
<result property="onlineSold" column="online_sold"/>
|
||||
<result property="offlineSold" column="offline_sold"/>
|
||||
<result property="salesCount" column="sales_count"/>
|
||||
<result property="hourType" column="hour_type"/>
|
||||
<result property="sort" column="sort"/>
|
||||
@@ -85,6 +89,8 @@
|
||||
IFNULL(t.offline_stock, 600) AS stock,
|
||||
IFNULL(t.online_stock, 600) AS online_stock,
|
||||
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,
|
||||
sp.sort,
|
||||
sp.is_show,
|
||||
@@ -133,6 +139,8 @@
|
||||
IFNULL(t.offline_stock, 600) AS stock,
|
||||
IFNULL(t.online_stock, 600) AS online_stock,
|
||||
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,
|
||||
sp.sort,
|
||||
sp.is_show,
|
||||
@@ -171,6 +179,8 @@
|
||||
IFNULL(m.online_stock, 600) as stock,
|
||||
IFNULL(m.online_stock, 600) as online_stock,
|
||||
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.pic_url,
|
||||
s.sort,
|
||||
s.is_show,
|
||||
@@ -208,6 +218,8 @@
|
||||
IFNULL(m.online_stock, 600) AS stock,
|
||||
IFNULL(m.online_stock, 600) AS online_stock,
|
||||
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,
|
||||
s.pic_url,
|
||||
s.slider_pic_urls,
|
||||
@@ -241,6 +253,8 @@
|
||||
IFNULL(t.offline_stock, 600) AS stock,
|
||||
IFNULL(t.online_stock, 600) AS online_stock,
|
||||
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.sort,
|
||||
sp.description AS ingredients,
|
||||
c.id AS category_id,
|
||||
@@ -264,31 +278,38 @@
|
||||
ORDER BY t.hour_type ASC, sp.sort ASC, t.id ASC
|
||||
</select>
|
||||
|
||||
<!--
|
||||
delta 与旧接口一致:下单扣减传负数、售后回传正数。
|
||||
实际改写销量:sold = sold - delta(扣减时 sold 增加)。
|
||||
expected 为操作前「剩余」快照(配额 - 销量)。
|
||||
-->
|
||||
<update id="updateOnlineStockDelta">
|
||||
UPDATE canteen_daily_menu
|
||||
SET online_stock = online_stock + #{delta}
|
||||
SET online_sold = IFNULL(online_sold, 0) - #{delta}
|
||||
WHERE del_flag = '0'
|
||||
AND merchant_id = #{merchantId}
|
||||
AND start_time = #{mealDate}
|
||||
AND hour_type = #{hourType}
|
||||
AND spu_id = #{spuId}
|
||||
AND online_stock + #{delta} >= 0
|
||||
AND IFNULL(online_sold, 0) - #{delta} >= 0
|
||||
AND IFNULL(online_sold, 0) - #{delta} <= IFNULL(online_stock, 600)
|
||||
<if test="expected != null">
|
||||
AND online_stock = #{expected}
|
||||
AND (IFNULL(online_stock, 600) - IFNULL(online_sold, 0)) = #{expected}
|
||||
</if>
|
||||
</update>
|
||||
|
||||
<update id="updateOfflineStockDelta">
|
||||
UPDATE canteen_daily_menu
|
||||
SET offline_stock = offline_stock + #{delta}
|
||||
SET offline_sold = IFNULL(offline_sold, 0) - #{delta}
|
||||
WHERE del_flag = '0'
|
||||
AND merchant_id = #{merchantId}
|
||||
AND start_time = #{mealDate}
|
||||
AND hour_type = #{hourType}
|
||||
AND spu_id = #{spuId}
|
||||
AND offline_stock + #{delta} >= 0
|
||||
AND IFNULL(offline_sold, 0) - #{delta} >= 0
|
||||
AND IFNULL(offline_sold, 0) - #{delta} <= IFNULL(offline_stock, 600)
|
||||
<if test="expected != null">
|
||||
AND offline_stock = #{expected}
|
||||
AND (IFNULL(offline_stock, 600) - IFNULL(offline_sold, 0)) = #{expected}
|
||||
</if>
|
||||
</update>
|
||||
</mapper>
|
||||
+5
@@ -68,6 +68,11 @@ public class HardOrderDto extends HardOrderEntity implements BaseIsIncludeTreeIn
|
||||
*/
|
||||
private String productName;
|
||||
|
||||
/**
|
||||
* 购物车商品 SPU ID;非空时只返回购物车含该商品的订单(每日菜谱「谁订了这道菜」)。
|
||||
*/
|
||||
private Long productId;
|
||||
|
||||
private LocalDateTime[] refundTimeList;
|
||||
|
||||
/** 预计使用日期范围。 */
|
||||
|
||||
+8
-6
@@ -2222,9 +2222,10 @@ public class HardOrderServiceImpl extends ServiceImpl<HardOrderMapper, HardOrder
|
||||
}
|
||||
for (Map.Entry<Long, Integer> entry : required.entrySet()) {
|
||||
DailyMenuAppVO row = menuBySpu.get(entry.getKey());
|
||||
int onlineRemain = row.getOnlineStock() == null
|
||||
? (row.getStock() == null ? 0 : row.getStock())
|
||||
: row.getOnlineStock();
|
||||
// 线上剩余:优先 remainOnlineStock;兼容旧字段 stock/onlineStock(历史把剩余写在配额字段)
|
||||
int onlineRemain = row.getRemainOnlineStock() != null ? row.getRemainOnlineStock()
|
||||
: (row.getStock() != null ? row.getStock()
|
||||
: (row.getOnlineStock() == null ? 0 : row.getOnlineStock()));
|
||||
if (onlineRemain < entry.getValue()) {
|
||||
throw new I18nBizException(ErrorCodes.HARD_PRODUCT_STOCK_LOW, row.getName());
|
||||
}
|
||||
@@ -2742,7 +2743,7 @@ public class HardOrderServiceImpl extends ServiceImpl<HardOrderMapper, HardOrder
|
||||
}
|
||||
|
||||
/**
|
||||
* 按每日菜谱批次扣减/回滚「线上」库存(canteen_daily_menu.online_stock)。
|
||||
* 按每日菜谱批次扣减/回滚「线上」净销量(canteen_daily_menu.online_sold;配额 online_stock 不动)。
|
||||
* 安卓机线下通道走 fastenerInventory → OFFLINE,互不影响。
|
||||
*/
|
||||
private void updateDailyMenuReserveInventory(List<DailyMenuReserveGroupContext> contexts, int dinerCount,
|
||||
@@ -4666,8 +4667,9 @@ public class HardOrderServiceImpl extends ServiceImpl<HardOrderMapper, HardOrder
|
||||
if (row == null) {
|
||||
throw new I18nBizException(ErrorCodes.HARD_CANTEEN_SPU_BAD);
|
||||
}
|
||||
int offlineRemain = row.getOfflineStock() != null ? row.getOfflineStock()
|
||||
: (row.getStock() == null ? 0 : row.getStock());
|
||||
int offlineRemain = row.getRemainOfflineStock() != null ? row.getRemainOfflineStock()
|
||||
: (row.getStock() != null ? row.getStock()
|
||||
: (row.getOfflineStock() != null ? row.getOfflineStock() : 0));
|
||||
if (entry.getValue() < 0 && offlineRemain < Math.abs(entry.getValue())) {
|
||||
throw new I18nBizException(ErrorCodes.HARD_PRODUCT_STOCK_LOW, row.getName());
|
||||
}
|
||||
|
||||
+8
@@ -235,6 +235,14 @@
|
||||
<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">
|
||||
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}
|
||||
)
|
||||
</if>
|
||||
<if test="dto.apptUseDateStart != null">
|
||||
and a.appt_use_date >= #{dto.apptUseDateStart}
|
||||
</if>
|
||||
|
||||
Reference in new issue
Block a user