修复权限问题

This commit is contained in:
zjf committed 2026-09-11 18:42:16 +08:00
1 parent 26fe9834cb
commit 7fb7feea3f
12 files changed
+231 -73

No files matched your search

@@ -61,6 +61,14 @@ public interface RemoteDailyMenuService {
@NoToken
R<Boolean> checkAvailability(@RequestBody @Validated DailyMenuAvailabilityCheckDTO request);
/**
* 移动端下单复核:按商户+日期+餐段拉取菜谱(线上剩余),无数据权限过滤。
*/
@GetMapping("/canteenDailyMenu/inner/list-for-order-validate")
@NoToken
R<List<DailyMenuAppVO>> listForOrderValidate(@RequestParam("day") String day,
@RequestParam("hourType") Integer hourType,
@RequestParam("merchantId") Long merchantId);
/**
* app每日菜谱分页查询
@@ -79,6 +79,17 @@ public class CanteenDailyMenuController {
return R.ok(canteenDailyMenuService.checkAvailability(request));
}
/**
* 供 Hardware 移动端下单复核菜谱与线上库存(无 DataScope,与 App 列表一致)。
*/
@Inner
@GetMapping("/inner/list-for-order-validate")
public R<List<DailyMenuAppVO>> listForOrderValidate(@RequestParam("day") String day,
@RequestParam("hourType") Integer hourType,
@RequestParam("merchantId") Long merchantId) {
return R.ok(canteenDailyMenuService.listForOrderValidate(day, hourType, merchantId));
}
/**
* 分页查询
*
@@ -35,6 +35,12 @@ public interface CanteenDailyMenuMapper extends PjcloudBaseMapper<CanteenDailyMe
List<DailyMenuAppVO> getCanteenMenuList(@Param("day") String day, @Param("hourType") Integer hourType,
@Param("categoryId") Long categoryId, @Param("merchantId") Long merchantId, DataScope dataScope);
/**
* Hardware 下单复核:按商户+日期+餐段查菜谱,不加 DataScope(与 App /app/page、check-availability 一致)。
*/
List<DailyMenuAppVO> getCanteenMenuListForOrder(@Param("day") String day, @Param("hourType") Integer hourType,
@Param("merchantId") Long merchantId);
/**
* 按闭区间查询收银终端多日菜谱展示数据,字段与单日查询保持一致。
*
@@ -35,10 +35,14 @@ public interface CanteenReportSwipeMapper extends BaseMapper<CanteenReportSwipeE
@Param("park") String park, @Param("limit") int limit);
/**
* 删除某月 FACE,但保留 excludeParks 中的厂区数据(排除厂区汇总时用)
* 删除某月 FACE,但保留 excludeParks 中的厂区数据
*/
int deletePhysicallyFaceByBizMonthExceptParks(@Param("bizMonth") String bizMonth,
@Param("excludeParks") List<String> excludeParks, @Param("limit") int limit);
/** 仅删除指定厂区的 FACE(排除厂区后从报表消失) */
int deletePhysicallyFaceByBizMonthOnlyParks(@Param("bizMonth") String bizMonth,
@Param("parks") List<String> parks, @Param("limit") int limit);
int insertBatch(@Param("list") List<CanteenReportSwipeEntity> list);
}
@@ -63,6 +63,11 @@ public interface CanteenDailyMenuService extends IService<CanteenDailyMenuEntity
*/
List<DailyMenuAppVO> getCanteenMenuListScope(String day, Integer hourType, Long categoryId, Long merchantId);
/**
* Hardware 移动端下单复核:无数据权限过滤,stock/remainOnlineStock 为线上剩余。
*/
List<DailyMenuAppVO> listForOrderValidate(String day, Integer hourType, Long merchantId);
/**
* 按闭区间查询收银终端多日菜谱,并补充与单日接口一致的套餐组成数据。
*
@@ -2,6 +2,7 @@ package com.bsd.pjcloud.canteen.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.nacos.common.utils.CollectionUtils;
import com.baomidou.lock.LockInfo;
import com.baomidou.lock.LockTemplate;
@@ -116,7 +117,18 @@ public class CanteenDailyMenuServiceImpl extends ServiceImpl<CanteenDailyMenuMap
.stream()
.map(menu -> new DailyMenuAvailabilityKey(menu.getStartTime(), menu.getHourType(), menu.getSpuId()))
.collect(Collectors.toSet());
return availableKeys.containsAll(requestedKeys);
if (!availableKeys.containsAll(requestedKeys)) {
return false;
}
// 与 App / 下单复核一致:菜品须未删除且展示中(is_show=0)
Set<Long> visibleSpuIds = canteenSpuMapper.selectList(new LambdaQueryWrapper<CanteenSpuEntity>()
.in(CanteenSpuEntity::getId, spuIds)
.eq(CanteenSpuEntity::getIsShow, "0")
.select(CanteenSpuEntity::getId))
.stream()
.map(CanteenSpuEntity::getId)
.collect(Collectors.toSet());
return visibleSpuIds.containsAll(spuIds);
}
/**
@@ -378,6 +390,27 @@ public class CanteenDailyMenuServiceImpl extends ServiceImpl<CanteenDailyMenuMap
return list;
}
/**
* 下单复核:与 App 可见菜谱同一数据范围,避免 AD 列表 DataScope 把结果滤成空导致误报「商品不存在」。
*/
@Override
public List<DailyMenuAppVO> listForOrderValidate(String day, Integer hourType, Long merchantId) {
if (StrUtil.isBlank(day) || hourType == null || merchantId == null) {
return Collections.emptyList();
}
List<DailyMenuAppVO> list = baseMapper.getCanteenMenuListForOrder(day, hourType, merchantId);
fillDailyMenuAppCombo(list);
LocalDate mealDate;
try {
mealDate = LocalDate.parse(day);
}
catch (Exception ignored) {
mealDate = null;
}
dailyMenuStockSupport.fillAppOnlineRemain(list, merchantId, mealDate);
return list;
}
/**
* 按闭区间查询收银终端多日菜谱,并批量补充套餐组成以避免逐条查询。
*
@@ -127,11 +127,15 @@ public class CanteenReportFaceImportServiceImpl extends ServiceImpl<CanteenRepor
return total;
}
private int deleteSwipeFaceByBizMonthExceptParks(String bizMonth, List<String> excludeParks) {
/** 仅删除指定厂区的 FACE(排除厂区后从报表消失) */
private int deleteSwipeFaceByBizMonthOnlyParks(String bizMonth, List<String> parks) {
if (CollUtil.isEmpty(parks)) {
return 0;
}
int total = 0;
int n;
do {
n = swipeMapper.deletePhysicallyFaceByBizMonthExceptParks(bizMonth, excludeParks, DELETE_BATCH);
n = swipeMapper.deletePhysicallyFaceByBizMonthOnlyParks(bizMonth, parks, DELETE_BATCH);
total += n;
}
while (n >= DELETE_BATCH);
@@ -566,6 +570,10 @@ public class CanteenReportFaceImportServiceImpl extends ServiceImpl<CanteenRepor
}
if (CollUtil.isEmpty(saveList)) {
// 无可写入行时也清掉排除厂区,避免看板仍显示
if (CollUtil.isNotEmpty(excludeParkSet)) {
deleteSwipeFaceByBizMonthOnlyParks(month, new ArrayList<>(excludeParkSet));
}
return R.failed(excludedParkRows > 0
? String.format("汇总后可写入金额均为 0 或均被排除厂区过滤(排除 %d 条),未写入刷卡明细", excludedParkRows)
: "汇总后可写入金额均为 0,未写入刷卡明细");
@@ -575,14 +583,15 @@ public class CanteenReportFaceImportServiceImpl extends ServiceImpl<CanteenRepor
if (importLogId != null) {
deleteSwipeByImportLogId(importLogId);
}
else if (CollUtil.isNotEmpty(excludeParkSet)) {
// 排除厂区:保留这些厂区已有 FACE,只覆盖其余
deleteSwipeFaceByBizMonthExceptParks(month, new ArrayList<>(excludeParkSet));
}
else {
// 整月 FACE 清空再写入;被排除厂区不写入,从而报表列也不再出现
deleteSwipeFaceByBizMonth(month);
}
}
else if (CollUtil.isNotEmpty(excludeParkSet)) {
// 非覆盖模式也清掉排除厂区旧 FACE,避免看板仍显示这些厂区
deleteSwipeFaceByBizMonthOnlyParks(month, new ArrayList<>(excludeParkSet));
}
for (List<CanteenReportSwipeEntity> chunk : CollUtil.split(saveList, BATCH_SIZE)) {
swipeMapper.insertBatch(chunk);
@@ -622,7 +631,7 @@ public class CanteenReportFaceImportServiceImpl extends ServiceImpl<CanteenRepor
tip.append(String.format(";有 %d 条结算台未配置厂区,请到「结算台厂区」维护后再重新汇总", unmatchedPark));
}
if (excludedParkRows > 0) {
tip.append(String.format(";已排除厂区数据 %d 条(保留原 FACE 不覆盖)", excludedParkRows));
tip.append(String.format(";已排除厂区数据 %d 条(已清除这些厂区原 FACE,报表不再展示)", excludedParkRows));
}
CanteenReportAggResultVO result = new CanteenReportAggResultVO();
result.setSwipeRows(saveList.size());
@@ -123,6 +123,45 @@
</where>
order by sp.sort asc
</select>
<!-- 下单复核专用:无 DataScope;与 App 一致要求 is_show=0 -->
<select id="getCanteenMenuListForOrder" resultMap="adDailyMenuAppVO">
SELECT
t.id,
t.start_time,
t.merchant_id,
sp.id AS spu_id,
sp.name,
sp.price,
sp.market_price,
sp.pic_url,
sp.slider_pic_urls,
sp.introduction,
sp.description,
IFNULL(t.online_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,
c.id AS category_id,
c.name AS category_name,
IFNULL(sp.spu_type, 1) AS spu_type,
t.hour_type
FROM canteen_daily_menu t
INNER JOIN canteen_spu sp ON t.spu_id = sp.id
LEFT JOIN canteen_category c ON sp.category_id = c.id AND c.del_flag = 0
WHERE t.del_flag = 0
AND sp.del_flag = 0
AND sp.is_show = 0
AND t.merchant_id = #{merchantId}
AND t.start_time = #{day}
AND t.hour_type = #{hourType}
ORDER BY sp.sort ASC
</select>
<select id="getCanteenMenuListByDateRange" resultMap="adDailyMenuAppVO">
SELECT
t.id,
@@ -70,6 +70,17 @@
LIMIT #{limit}
</delete>
<delete id="deletePhysicallyFaceByBizMonthOnlyParks">
DELETE /*+ QUERY_TIMEOUT(120000000) */ FROM canteen_report_swipe
WHERE source = 'FACE'
AND biz_month = #{bizMonth}
AND park IN
<foreach collection="parks" item="p" open="(" separator="," close=")">
#{p}
</foreach>
LIMIT #{limit}
</delete>
<insert id="insertBatch">
INSERT INTO canteen_report_swipe (
id, biz_month, biz_date, emp_no, emp_name, legal_entity, dept_name,
@@ -3,7 +3,9 @@ package com.bsd.pjcloud.canteen.service.impl;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.bsd.pjcloud.canteen.dto.DailyMenuAvailabilityCheckDTO;
import com.bsd.pjcloud.canteen.entity.CanteenDailyMenuEntity;
import com.bsd.pjcloud.canteen.entity.CanteenSpuEntity;
import com.bsd.pjcloud.canteen.mapper.CanteenDailyMenuMapper;
import com.bsd.pjcloud.canteen.mapper.CanteenSpuMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.test.util.ReflectionTestUtils;
@@ -23,73 +25,100 @@ import static org.mockito.Mockito.when;
*/
class CanteenDailyMenuServiceImplAvailabilityTest {
/** 每日菜谱 Mapper,模拟 MyBatis-Plus 已过滤逻辑删除记录后的查询结果。 */
private final CanteenDailyMenuMapper dailyMenuMapper = mock(CanteenDailyMenuMapper.class);
/** 每日菜谱 Mapper,模拟 MyBatis-Plus 已过滤逻辑删除记录后的查询结果。 */
private final CanteenDailyMenuMapper dailyMenuMapper = mock(CanteenDailyMenuMapper.class);
/** 被测每日菜谱服务,仅执行真实可售组合校验方法。 */
private final CanteenDailyMenuServiceImpl service = mock(CanteenDailyMenuServiceImpl.class, CALLS_REAL_METHODS);
/** 菜品 Mapper,用于复核 is_show / 未删除。 */
private final CanteenSpuMapper canteenSpuMapper = mock(CanteenSpuMapper.class);
/**
* 注入每日菜谱 Mapper,使测试不依赖真实数据库。
*/
@BeforeEach
void setUp() {
ReflectionTestUtils.setField(service, "baseMapper", dailyMenuMapper);
}
/** 被测每日菜谱服务,仅执行真实可售组合校验方法。 */
private final CanteenDailyMenuServiceImpl service = mock(CanteenDailyMenuServiceImpl.class, CALLS_REAL_METHODS);
/**
* 指定日期、晚餐餐段和全部 SPU 均存在有效记录时应允许继续下单。
*/
@Test
void allRequestedProductsRemainAvailable() {
DailyMenuAvailabilityCheckDTO request = request(List.of(11L, 12L));
when(dailyMenuMapper.selectList(any(Wrapper.class)))
.thenReturn(List.of(menu(11L), menu(12L)));
/**
* 注入 Mapper,使测试不依赖真实数据库。
*/
@BeforeEach
void setUp() {
ReflectionTestUtils.setField(service, "baseMapper", dailyMenuMapper);
ReflectionTestUtils.setField(service, "canteenSpuMapper", canteenSpuMapper);
}
assertTrue(service.checkAvailability(request));
}
/**
* 指定日期、晚餐餐段和全部 SPU 均存在有效记录时应允许继续下单。
*/
@Test
void allRequestedProductsRemainAvailable() {
DailyMenuAvailabilityCheckDTO request = request(List.of(11L, 12L));
when(dailyMenuMapper.selectList(any(Wrapper.class)))
.thenReturn(List.of(menu(11L), menu(12L)));
when(canteenSpuMapper.selectList(any(Wrapper.class)))
.thenReturn(List.of(spu(11L), spu(12L)));
/**
* 任一 SPU 的晚餐每日菜谱记录已被逻辑删除时,批量校验必须整批返回不可售。
*/
@Test
void removedDinnerProductMakesBatchUnavailable() {
DailyMenuAvailabilityCheckDTO request = request(List.of(11L, 12L));
when(dailyMenuMapper.selectList(any(Wrapper.class)))
.thenReturn(List.of(menu(11L)));
assertTrue(service.checkAvailability(request));
}
assertFalse(service.checkAvailability(request));
}
/**
* 任一 SPU 的晚餐每日菜谱记录已被逻辑删除时,批量校验必须整批返回不可售。
*/
@Test
void removedDinnerProductMakesBatchUnavailable() {
DailyMenuAvailabilityCheckDTO request = request(List.of(11L, 12L));
when(dailyMenuMapper.selectList(any(Wrapper.class)))
.thenReturn(List.of(menu(11L)));
/**
* 构造 2026 年 9 月 1 日晚餐的商品可售校验请求。
*
* @param spuIds 待校验的基础菜品 SPU ID
* @return 固定商户、日期和晚餐餐段的请求
*/
private DailyMenuAvailabilityCheckDTO request(List<Long> spuIds) {
DailyMenuAvailabilityCheckDTO.Group group = new DailyMenuAvailabilityCheckDTO.Group();
group.setDate(LocalDate.of(2026, 9, 1));
group.setHourType(3);
group.setSpuIds(spuIds);
DailyMenuAvailabilityCheckDTO request = new DailyMenuAvailabilityCheckDTO();
request.setMerchantId(100L);
request.setGroups(List.of(group));
return request;
}
assertFalse(service.checkAvailability(request));
}
/**
* 构造仍然有效的 2026 年 9 月 1 日晚餐每日菜谱记录。
*
* @param spuId 基础菜品 SPU ID
* @return 可被组合校验识别的每日菜谱实体
*/
private CanteenDailyMenuEntity menu(Long spuId) {
CanteenDailyMenuEntity menu = new CanteenDailyMenuEntity();
menu.setMerchantId(100L);
menu.setStartTime(LocalDate.of(2026, 9, 1));
menu.setHourType(3);
menu.setSpuId(spuId);
return menu;
}
/**
* 菜谱行仍在但菜品已隐藏时不可售。
*/
@Test
void hiddenSpuMakesBatchUnavailable() {
DailyMenuAvailabilityCheckDTO request = request(List.of(11L, 12L));
when(dailyMenuMapper.selectList(any(Wrapper.class)))
.thenReturn(List.of(menu(11L), menu(12L)));
when(canteenSpuMapper.selectList(any(Wrapper.class)))
.thenReturn(List.of(spu(11L)));
assertFalse(service.checkAvailability(request));
}
/**
* 构造 2026 年 9 月 1 日晚餐的商品可售校验请求。
*
* @param spuIds 待校验的基础菜品 SPU ID
* @return 固定商户、日期和晚餐餐段的请求
*/
private DailyMenuAvailabilityCheckDTO request(List<Long> spuIds) {
DailyMenuAvailabilityCheckDTO.Group group = new DailyMenuAvailabilityCheckDTO.Group();
group.setDate(LocalDate.of(2026, 9, 1));
group.setHourType(3);
group.setSpuIds(spuIds);
DailyMenuAvailabilityCheckDTO request = new DailyMenuAvailabilityCheckDTO();
request.setMerchantId(100L);
request.setGroups(List.of(group));
return request;
}
/**
* 构造仍然有效的 2026 年 9 月 1 日晚餐每日菜谱记录。
*
* @param spuId 基础菜品 SPU ID
* @return 可被组合校验识别的每日菜谱实体
*/
private CanteenDailyMenuEntity menu(Long spuId) {
CanteenDailyMenuEntity menu = new CanteenDailyMenuEntity();
menu.setMerchantId(100L);
menu.setStartTime(LocalDate.of(2026, 9, 1));
menu.setHourType(3);
menu.setSpuId(spuId);
return menu;
}
private CanteenSpuEntity spu(Long id) {
CanteenSpuEntity spu = new CanteenSpuEntity();
spu.setId(id);
spu.setIsShow("0");
return spu;
}
}
@@ -58,7 +58,7 @@ public class PjcloudDefaultDataScopeHandle implements DataScopeHandle {
public Boolean calcScope(DataScope dataScope) {
// 对于TOC客户端,不进行数据权限校验
PjcloudUser user = SecurityUtils.getUser();
if (UserTypeEnum.TOC.getStatus().equals(user.getUserType())) {
if (user != null && UserTypeEnum.TOC.getStatus().equals(user.getUserType())) {
return true;
}
@@ -2191,8 +2191,8 @@ public class HardOrderServiceImpl extends ServiceImpl<HardOrderMapper, HardOrder
if (mealDate == null || hourType == null) {
throw new I18nBizException(ErrorCodes.HARD_DAILY_MENU_PRODUCT_BAD);
}
R<List<DailyMenuAppVO>> menuResult = remoteDailyMenuService.getCanteenList(
mealDate.toString(), hourType, null, merchantId);
R<List<DailyMenuAppVO>> menuResult = remoteDailyMenuService.listForOrderValidate(
mealDate.toString(), hourType, merchantId);
if (menuResult == null || !menuResult.isOk() || menuResult.getData() == null) {
throw new I18nBizException(menuResult == null ? ErrorCodes.HARD_DAILY_PRODUCT_NO_RESP
: ErrorCodes.HARD_DAILY_PRODUCT_QUERY_FAIL);
@@ -2773,6 +2773,9 @@ public class HardOrderServiceImpl extends ServiceImpl<HardOrderMapper, HardOrder
stockDto.setItems(items);
R<?> result = remoteCanteenSpuService.updateSkuStockIdempotent(stockDto);
if (result == null || !result.isOk()) {
if (result != null && StrUtil.isNotBlank(result.getMsg())) {
throw new RuntimeException(result.getMsg());
}
throw new I18nBizException(reduce ? ErrorCodes.HARD_DAILY_STOCK_DEDUCT_FAIL : ErrorCodes.HARD_DAILY_STOCK_RESTORE_FAIL);
}
}