From bf3faae01aa6cfbd0a3edf34cf71d62897c0e826 Mon Sep 17 00:00:00 2001 From: zjf <1799650030@qq.com> Date: Fri, 28 Aug 2026 19:53:57 +0800 Subject: [PATCH] =?UTF-8?q?=E6=AF=8F=E6=97=A5=E8=8F=9C=E8=B0=B1=E5=AF=BC?= =?UTF-8?q?=E5=85=A5=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../impl/CanteenDailyMenuImportHelper.java | 202 ++++++++++++------ .../service/impl/CanteenSpuServiceImpl.java | 9 +- 2 files changed, 139 insertions(+), 72 deletions(-) diff --git a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/impl/CanteenDailyMenuImportHelper.java b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/impl/CanteenDailyMenuImportHelper.java index 050576ff..4bc7963f 100644 --- a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/impl/CanteenDailyMenuImportHelper.java +++ b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/impl/CanteenDailyMenuImportHelper.java @@ -12,6 +12,7 @@ import com.bsd.pjcloud.canteen.entity.CanteenDailyMenuEntity; import com.bsd.pjcloud.canteen.entity.CanteenSpuComboItemEntity; import com.bsd.pjcloud.canteen.entity.CanteenSpuEntity; import com.bsd.pjcloud.canteen.mapper.CanteenCategoryMapper; +import com.bsd.pjcloud.canteen.mapper.CanteenSpuComboItemMapper; import com.bsd.pjcloud.canteen.service.CanteenDailyMenuService; import com.bsd.pjcloud.canteen.service.CanteenSpuService; import com.bsd.pjcloud.canteen.util.CanteenMerchantScopeHelper; @@ -61,6 +62,7 @@ public class CanteenDailyMenuImportHelper { private final CanteenDailyMenuService canteenDailyMenuService; private final CanteenMerchantScopeHelper merchantScopeHelper; private final CanteenCategoryMapper canteenCategoryMapper; + private final CanteenSpuComboItemMapper canteenSpuComboItemMapper; public List templateRows() { List list = new ArrayList<>(); @@ -317,11 +319,8 @@ public class CanteenDailyMenuImportHelper { // groupKey = date|hourType|comboOrEmpty|menuIdOrNew Map> groups = new LinkedHashMap<>(); - /** 菜谱ID → 出现过的 日期|餐段(用于汇总跨天串 ID) */ - Map> menuIdDayHours = new LinkedHashMap<>(); - /** 菜谱ID → 示例套餐/菜名(取首次) */ - Map menuIdLabel = new LinkedHashMap<>(); Set involvedDates = new HashSet<>(); + Set excelMenuIds = new HashSet<>(); int line = 1; for (CanteenDailyMenuImportExcel row : dataRows) { line++; @@ -344,12 +343,12 @@ public class CanteenDailyMenuImportHelper { String combo = StrUtil.trim(row.getComboName()); String comboKey = StrUtil.blankToDefault(combo, ""); Long menuId = parseExcelLong(row.getId()); - // 必须带上日期+餐段:仅按菜谱ID 分组时,同名套餐跨天若串了同一 ID,会把两天组成菜并成一组报「重复」 + if (menuId != null) { + excelMenuIds.add(menuId); + } + // 带上日期+餐段,避免同 ID 跨天被并成一组 String groupKey; if (menuId != null) { - String dayHour = date + "|" + hourType; - menuIdDayHours.computeIfAbsent(menuId, k -> new LinkedHashSet<>()).add(dayHour); - menuIdLabel.putIfAbsent(menuId, StrUtil.blankToDefault(comboKey, dish)); groupKey = "id:" + menuId + "|" + date + "|" + hourType; } else if (StrUtil.isNotBlank(comboKey) && !isSingleComboMarker(comboKey)) { @@ -360,75 +359,42 @@ public class CanteenDailyMenuImportHelper { } groups.computeIfAbsent(groupKey, k -> new ArrayList<>()).add(row); } - List crossDayIdErrors = new ArrayList<>(); - for (Map.Entry> en : menuIdDayHours.entrySet()) { - if (en.getValue().size() <= 1) { - continue; - } - List days = en.getValue().stream().map(CanteenDailyMenuImportHelper::describeDayHour).toList(); - crossDayIdErrors.add(String.format("ID %s — %s(%s)", en.getKey(), String.join(" / ", days), - StrUtil.blankToDefault(menuIdLabel.get(en.getKey()), "-"))); - } - if (CollUtil.isNotEmpty(crossDayIdErrors)) { - StringBuilder sb = new StringBuilder(); - sb.append("发现 ").append(crossDayIdErrors.size()).append(" 处菜谱ID跨天/跨餐段重复(同一ID只能对应一天一餐):\n"); - int show = Math.min(crossDayIdErrors.size(), 15); - for (int i = 0; i < show; i++) { - sb.append(i + 1).append(". ").append(crossDayIdErrors.get(i)).append('\n'); - } - if (crossDayIdErrors.size() > show) { - sb.append("…另有 ").append(crossDayIdErrors.size() - show).append(" 处未列出\n"); - } - sb.append("请删掉错误那天的「菜谱ID」列,或重新导出后再改。"); - return R.failed(sb.toString().trim()); - } if (CollUtil.isNotEmpty(errors)) { return R.failed(formatImportErrors(errors)); } - ImportCache cache = buildImportCache(merchantId, involvedDates); + ImportCache cache = buildImportCache(merchantId, involvedDates, excelMenuIds); Set clearedDayHour = new LinkedHashSet<>(); List pendingMenus = new ArrayList<>(); int updated = 0; int createdDish = 0; int createdCombo = 0; int scheduled = 0; + int idAsNew = 0; for (Map.Entry> e : groups.entrySet()) { List items = e.getValue(); CanteenDailyMenuImportExcel firstRow = items.get(0); LocalDate groupDate = parseDate(firstRow.getBizDate()); Integer groupHour = parseMeal(firstRow.getMealName()); - String comboHint = StrUtil.blankToDefault(StrUtil.trim(firstRow.getComboName()), - StrUtil.trim(firstRow.getDishName())); Long menuId = items.stream().map(CanteenDailyMenuImportExcel::getId).map(CanteenDailyMenuImportHelper::parseExcelLong) .filter(Objects::nonNull).findFirst().orElse(null); if (menuId != null) { - CanteenDailyMenuEntity existMenu = canteenDailyMenuService.getById(menuId); - if (existMenu == null) { - throw new IllegalArgumentException(String.format( - "%s %s「%s」菜谱ID %s 不存在,请检查是否复制错了或被 Excel 改坏;若是新排菜请清空该行菜谱ID", - groupDate, formatMeal(groupHour), comboHint, menuId)); - } - if (!merchantId.equals(existMenu.getMerchantId())) { - throw new IllegalArgumentException(String.format( - "%s %s「%s」菜谱ID %s 不属于当前商户,请勿串用其他食堂的导出表", - groupDate, formatMeal(groupHour), comboHint, menuId)); - } - if (groupDate != null && existMenu.getStartTime() != null - && !groupDate.equals(existMenu.getStartTime())) { - throw new IllegalArgumentException(String.format( - "%s %s「%s」菜谱ID %s 在系统中属于 %s %s,不能用在当前日期;请改正 ID 或清空后按新增导入", - groupDate, formatMeal(groupHour), comboHint, menuId, - existMenu.getStartTime(), formatMeal(existMenu.getHourType()))); - } - if (groupHour != null && existMenu.getHourType() != null - && !groupHour.equals(existMenu.getHourType())) { - throw new IllegalArgumentException(String.format( - "%s %s「%s」菜谱ID %s 在系统中属于 %s,不能用在当前餐段;请改正 ID 或清空后按新增导入", - groupDate, formatMeal(groupHour), comboHint, menuId, - formatMeal(existMenu.getHourType()))); + CanteenDailyMenuEntity existMenu = resolveMenu(menuId, cache); + boolean matched = existMenu != null + && merchantId.equals(existMenu.getMerchantId()) + && (groupDate == null || existMenu.getStartTime() == null + || groupDate.equals(existMenu.getStartTime())) + && (groupHour == null || existMenu.getHourType() == null + || groupHour.equals(existMenu.getHourType())); + if (!matched) { + // 空已走新增;ID 不存在 / 商户或日期餐段对不上 → 当新增排菜 + idAsNew++; + items.forEach(r -> r.setId(null)); + menuId = null; } + } + if (menuId != null) { String err = upsertGroupById(menuId, items, merchantId, deptId, categoryId, categoryMap, cache); if (err != null) { // 事务内勿 return R.failed:内层已写库/抛错会标 rollback-only,提交会报 UnexpectedRollback @@ -514,6 +480,9 @@ public class CanteenDailyMenuImportHelper { String msg = String.format("导入成功:更新 %d,新建单品 %d,新建套餐 %d,新增排菜 %d", updated, createdDish, createdCombo, scheduled); + if (idAsNew > 0) { + msg += String.format("(其中 %d 组菜谱ID未匹配,已按新增排菜)", idAsNew); + } return R.ok(null, msg); } @@ -567,16 +536,17 @@ public class CanteenDailyMenuImportHelper { return rows; } - private ImportCache buildImportCache(Long merchantId, Set dates) { + private ImportCache buildImportCache(Long merchantId, Set dates, Set menuIds) { ImportCache cache = new ImportCache(); // 只取导入比对所需字段;更新时用 UpdateWrapper,避免 pic_url 等 ALWAYS 字段被写成 null List spus = canteenSpuService.list(Wrappers.lambdaQuery() .eq(CanteenSpuEntity::getMerchantId, merchantId) .select(CanteenSpuEntity::getId, CanteenSpuEntity::getName, CanteenSpuEntity::getSpuType, CanteenSpuEntity::getPrice, CanteenSpuEntity::getMarketPrice, CanteenSpuEntity::getStock, - CanteenSpuEntity::getDescription, CanteenSpuEntity::getCategoryId, - CanteenSpuEntity::getMerchantId, CanteenSpuEntity::getDeptId, + CanteenSpuEntity::getDescription, CanteenSpuEntity::getIntroduction, + CanteenSpuEntity::getCategoryId, CanteenSpuEntity::getMerchantId, CanteenSpuEntity::getDeptId, CanteenSpuEntity::getPicUrl)); + List comboIds = new ArrayList<>(); if (CollUtil.isNotEmpty(spus)) { for (CanteenSpuEntity spu : spus) { if (spu == null || StrUtil.isBlank(spu.getName())) { @@ -588,12 +558,33 @@ public class CanteenDailyMenuImportHelper { int type = spu.getSpuType() == null ? 1 : spu.getSpuType(); if (type == 2) { cache.comboByName.putIfAbsent(spu.getName(), spu); + if (spu.getId() != null) { + comboIds.add(spu.getId()); + } } else { cache.dishByName.putIfAbsent(spu.getName(), spu); } } } + if (CollUtil.isNotEmpty(comboIds)) { + List lines = canteenSpuComboItemMapper.selectList( + Wrappers.lambdaQuery() + .in(CanteenSpuComboItemEntity::getComboSpuId, comboIds) + .orderByAsc(CanteenSpuComboItemEntity::getSortOrder) + .orderByAsc(CanteenSpuComboItemEntity::getId) + .select(CanteenSpuComboItemEntity::getComboSpuId, CanteenSpuComboItemEntity::getChildSpuId, + CanteenSpuComboItemEntity::getSortOrder, CanteenSpuComboItemEntity::getId)); + if (CollUtil.isNotEmpty(lines)) { + for (CanteenSpuComboItemEntity line : lines) { + if (line.getComboSpuId() == null || line.getChildSpuId() == null) { + continue; + } + cache.comboChildIds.computeIfAbsent(line.getComboSpuId(), k -> new ArrayList<>()) + .add(line.getChildSpuId()); + } + } + } if (CollUtil.isNotEmpty(dates)) { List menus = canteenDailyMenuService.list( Wrappers.lambdaQuery() @@ -609,9 +600,34 @@ public class CanteenDailyMenuImportHelper { } } } + if (CollUtil.isNotEmpty(menuIds)) { + List byIds = canteenDailyMenuService.listByIds(menuIds); + if (CollUtil.isNotEmpty(byIds)) { + for (CanteenDailyMenuEntity m : byIds) { + if (m != null && m.getId() != null) { + cache.menuById.put(m.getId(), m); + } + } + } + } return cache; } + private CanteenDailyMenuEntity resolveMenu(Long menuId, ImportCache cache) { + if (menuId == null) { + return null; + } + CanteenDailyMenuEntity cached = cache.menuById.get(menuId); + if (cached != null) { + return cached; + } + CanteenDailyMenuEntity db = canteenDailyMenuService.getById(menuId); + if (db != null) { + cache.menuById.put(menuId, db); + } + return db; + } + private void flushPendingMenus(List pendingMenus) { if (CollUtil.isEmpty(pendingMenus)) { return; @@ -625,7 +641,7 @@ public class CanteenDailyMenuImportHelper { */ private String upsertGroupById(Long menuId, List items, Long merchantId, Long deptId, Long defaultCategoryId, Map categoryMap, ImportCache cache) { - CanteenDailyMenuEntity menu = canteenDailyMenuService.getById(menuId); + CanteenDailyMenuEntity menu = resolveMenu(menuId, cache); if (menu == null) { return "菜谱ID " + menuId + " 不存在(若由Excel导出再导入,多为ID被当成数字丢了末位精度,请重新导出且保持菜谱ID列为文本)"; } @@ -828,35 +844,74 @@ public class CanteenDailyMenuImportHelper { CanteenSpuEntity exist = cache.comboByName.get(fullName); List items = new ArrayList<>(); int sort = 0; + List newChildIds = new ArrayList<>(children.size()); for (CanteenSpuEntity child : children) { CanteenSpuComboItemEntity line = new CanteenSpuComboItemEntity(); line.setChildSpuId(child.getId()); line.setQuantity(1); line.setSortOrder(sort++); items.add(line); + newChildIds.add(child.getId()); } if (exist != null) { - exist.setPrice(priceFen); - exist.setMarketPrice(priceFen); - if (stock != null && stock >= 0) { + boolean changePrice = priceFen != null && !Objects.equals(exist.getPrice(), priceFen); + if (changePrice) { + exist.setPrice(priceFen); + exist.setMarketPrice(priceFen); + } + boolean changeStock = stock != null && stock >= 0 && !Objects.equals(exist.getStock(), stock); + if (changeStock) { exist.setStock(stock); } - if (StrUtil.isNotBlank(pkgLabel)) { + boolean changeIntro = StrUtil.isNotBlank(pkgLabel) && !Objects.equals(exist.getIntroduction(), pkgLabel); + if (changeIntro) { exist.setIntroduction(pkgLabel); } + boolean changeCategory = false; if (StrUtil.isNotBlank(normalizeCategoryName(categoryName))) { Long newCatId = resolveOrCreateCategoryId(categoryName, defaultCategoryId, categoryMap, merchantId, deptId); - if (newCatId != null) { + if (newCatId != null && !Objects.equals(exist.getCategoryId(), newCatId)) { exist.setCategoryId(newCatId); + changeCategory = true; } } + String beforeDesc = exist.getDescription(); applyComboIngredients(exist, ingredients); - exist.setComboItems(items); - if (exist.getPicUrl() == null) { - exist.setPicUrl(""); + boolean changeDesc = !Objects.equals(beforeDesc, exist.getDescription()); + List oldChildIds = cache.comboChildIds.getOrDefault(exist.getId(), List.of()); + boolean itemsChanged = !newChildIds.equals(oldChildIds); + if (itemsChanged) { + exist.setComboItems(items); + if (exist.getDeptId() == null) { + exist.setDeptId(deptId); + } + if (exist.getPicUrl() == null) { + exist.setPicUrl(""); + } + canteenSpuService.updateDeep(exist); + cache.comboChildIds.put(exist.getId(), new ArrayList<>(newChildIds)); + } + else if (changePrice || changeStock || changeIntro || changeDesc || changeCategory) { + var uw = Wrappers.lambdaUpdate().eq(CanteenSpuEntity::getId, exist.getId()); + if (changePrice) { + uw.set(CanteenSpuEntity::getPrice, exist.getPrice()); + uw.set(CanteenSpuEntity::getMarketPrice, exist.getMarketPrice()); + } + if (changeStock) { + uw.set(CanteenSpuEntity::getStock, exist.getStock()); + } + if (changeIntro) { + uw.set(CanteenSpuEntity::getIntroduction, exist.getIntroduction()); + } + if (changeDesc) { + uw.set(CanteenSpuEntity::getDescription, exist.getDescription()); + } + if (changeCategory) { + uw.set(CanteenSpuEntity::getCategoryId, exist.getCategoryId()); + } + canteenSpuService.update(uw); } - canteenSpuService.updateDeep(exist); cache.comboByName.put(fullName, exist); return new FindOrCreate(exist, false); } @@ -875,6 +930,9 @@ public class CanteenDailyMenuImportHelper { combo.setComboItems(items); canteenSpuService.saveDeep(combo); cache.comboByName.put(fullName, combo); + if (combo.getId() != null) { + cache.comboChildIds.put(combo.getId(), new ArrayList<>(newChildIds)); + } return new FindOrCreate(combo, true); } @@ -1283,5 +1341,9 @@ public class CanteenDailyMenuImportHelper { final Map dishByName = new HashMap<>(); final Map comboByName = new HashMap<>(); final Set existingMenuKeys = new HashSet<>(); + /** 套餐组成 childSpuId 有序列表,用于判断是否需要 updateDeep */ + final Map> comboChildIds = new HashMap<>(); + /** Excel 菜谱 ID → 实体,避免每组 getById */ + final Map menuById = new HashMap<>(); } } diff --git a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/impl/CanteenSpuServiceImpl.java b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/impl/CanteenSpuServiceImpl.java index edae5576..351090ef 100644 --- a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/impl/CanteenSpuServiceImpl.java +++ b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/impl/CanteenSpuServiceImpl.java @@ -485,7 +485,10 @@ public class CanteenSpuServiceImpl extends ServiceImpllambdaQuery() .eq(CanteenSpuComboItemEntity::getComboSpuId, entity.getId()));