From 098713f0757d6c6d51564667530b767923ec63e3 Mon Sep 17 00:00:00 2001
From: zjf <1799650030@qq.com>
Date: Fri, 4 Sep 2026 16:39:54 +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=E6=B2=A1=E6=9C=89id=E6=8C=89=E6=96=B0=E5=A2=9E?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../dto/CanteenDailyMenuImportExcel.java | 1 +
.../impl/CanteenDailyMenuImportHelper.java | 187 ++++++++++++------
2 files changed, 123 insertions(+), 65 deletions(-)
diff --git a/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/dto/CanteenDailyMenuImportExcel.java b/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/dto/CanteenDailyMenuImportExcel.java
index 44157f298..7c8f753fc 100644
--- a/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/dto/CanteenDailyMenuImportExcel.java
+++ b/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/dto/CanteenDailyMenuImportExcel.java
@@ -10,6 +10,7 @@ import java.math.BigDecimal;
*
列顺序:日期、餐段、套餐名、菜品名、分类、单价、套餐价、库存、配料、菜谱ID、菜品SPU ID、填写说明
*
配料:单品写单品 SPU;套餐仅在首行填写,写入套餐 SPU
*
菜谱ID / 菜品SPU ID 必须用文本导出,否则 Excel 对超长雪花 ID 会丢精度导致再导入报「不存在」。
+ *
无「菜品SPU ID」时一律新建菜品(不按菜名复用);有 ID 才按 ID 更新。
*/
@Data
public class CanteenDailyMenuImportExcel {
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 b940fa100..538098cf9 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
@@ -81,11 +81,11 @@ public class CanteenDailyMenuImportHelper {
public List templateRows() {
List list = new ArrayList<>();
list.add(row("2026-08-17", "午餐", "A套餐", "糖醋排骨", "荤菜", "青莲仔排", "12", "21", "100",
- "分类按名称匹配;套餐价/库存/配料只填首行。菜谱ID/SPU ID为文本勿改成数字,否则Excel会丢精度"));
+ "无菜品SPU ID则新建菜品(不按菜名复用);有ID才更新。套餐价/库存/配料填首行。ID列勿改数字格式"));
list.add(row(null, null, null, "蒜苔炒腊肉", "荤菜", null, "8", null, null, null));
list.add(row(null, null, null, "清炒时蔬", "素菜", null, "3", null, null, null));
list.add(row(null, null, "B套餐", "红烧肉", "荤菜", "五花肉、土豆", "10", "18", "80",
- "换套餐名即另一套;名称已存在不新增"));
+ "换套餐名即另一套;无套餐SPU ID则新建套餐"));
list.add(row(null, null, null, "土豆丝", "素菜", null, "4", null, null, null));
list.add(row(null, null, "无", "千张大骨汤", "汤类", null, "0", null, "50",
"单品:套餐名填「无」「单品」「单点」或「单品-xxx/单点-xxx」;价格填单价列"));
@@ -93,7 +93,7 @@ public class CanteenDailyMenuImportHelper {
"单品-xxx / 单点-xxx 一律按单品,套餐价可留空"));
list.add(row(null, null, "单点-2", "豆浆", "饮品", null, "2", null, "100", null));
list.add(row("2026-08-18", "午餐,晚餐", "无", "番茄蛋汤", "汤类", null, "2", null, "80",
- "同一道菜多餐:餐段可写「午餐,晚餐」或「三餐/全天」,系统拆成多条排菜且菜品只建一份"));
+ "多餐拆行:无菜品SPU ID时各餐段各自新建菜品(库存独立)"));
return list;
}
@@ -522,7 +522,7 @@ public class CanteenDailyMenuImportHelper {
else {
action = "INSERT";
actionLabel = "新增";
- tip = menuId != null ? "菜谱ID未匹配,将按新增排菜" : "新增排菜(菜品不存在时自动创建)";
+ tip = menuId != null ? "菜谱ID未匹配,将按新增排菜" : "新增排菜(无菜品SPU ID则新建菜品,不按菜名复用)";
selected = true;
totalInsert++;
}
@@ -708,7 +708,7 @@ public class CanteenDailyMenuImportHelper {
FindOrCreate r = findOrCreateDish(merchantId, deptId,
item.getCategoryName(), categoryId, categoryMap,
item.getDishName(), item.getIngredients(), resolveDishPriceFen(item), item.getStock(),
- cache);
+ parseExcelLong(item.getSpuId()), cache);
if (r.created) {
createdDish++;
}
@@ -738,7 +738,8 @@ public class CanteenDailyMenuImportHelper {
}
FindOrCreate r = findOrCreateDish(merchantId, deptId,
item.getCategoryName(), categoryId, categoryMap,
- item.getDishName(), null, resolveDishPriceFen(item), null, cache);
+ item.getDishName(), null, resolveDishPriceFen(item), null,
+ parseExcelLong(item.getSpuId()), cache);
if (r.created) {
createdDish++;
}
@@ -758,7 +759,7 @@ public class CanteenDailyMenuImportHelper {
FindOrCreate combo = findOrCreateCombo(merchantId, deptId,
first.getCategoryName(), categoryId, categoryMap,
fullComboName, comboName, comboPrice, comboStock, children,
- resolveComboIngredients(first), cache);
+ resolveComboIngredients(first), parseExcelLong(first.getSpuId()), cache);
if (combo.created) {
createdCombo++;
}
@@ -1001,6 +1002,9 @@ public class CanteenDailyMenuImportHelper {
if (spu.getPicUrl() == null) {
spu.setPicUrl("");
}
+ if (spu.getId() != null) {
+ cache.spuById.put(spu.getId(), spu);
+ }
int type = spu.getSpuType() == null ? 1 : spu.getSpuType();
if (type == 2) {
cache.comboByName.putIfAbsent(spu.getName(), spu);
@@ -1126,7 +1130,7 @@ public class CanteenDailyMenuImportHelper {
}
FindOrCreate created = findOrCreateDish(merchantId, deptId,
row.getCategoryName(), defaultCategoryId, categoryMap, dish, row.getIngredients(),
- resolveDishPriceFen(row), row.getStock(), cache);
+ resolveDishPriceFen(row), row.getStock(), parseExcelLong(row.getSpuId()), cache);
menu.setStartTime(date);
menu.setHourType(hourType);
menu.setSpuId(created.spu.getId());
@@ -1156,7 +1160,7 @@ public class CanteenDailyMenuImportHelper {
}
FindOrCreate r = findOrCreateDish(merchantId, deptId,
item.getCategoryName(), defaultCategoryId, categoryMap, dish,
- null, resolveDishPriceFen(item), null, cache);
+ null, resolveDishPriceFen(item), null, parseExcelLong(item.getSpuId()), cache);
children.add(r.spu);
}
children = dedupeComboChildren(children);
@@ -1174,7 +1178,8 @@ public class CanteenDailyMenuImportHelper {
String fullComboName = comboName + "-" + mainName;
FindOrCreate combo = findOrCreateCombo(merchantId, deptId,
items.get(0).getCategoryName(), defaultCategoryId, categoryMap, fullComboName,
- comboName, comboPrice, comboStock, children, resolveComboIngredients(items.get(0)), cache);
+ comboName, comboPrice, comboStock, children, resolveComboIngredients(items.get(0)),
+ parseExcelLong(items.get(0).getSpuId()), cache);
menu.setStartTime(date);
menu.setHourType(hourType);
menu.setSpuId(combo.spu.getId());
@@ -1212,62 +1217,32 @@ public class CanteenDailyMenuImportHelper {
return true;
}
+ /**
+ * 解析/创建单品。
+ * 有「菜品SPU ID」→ 按 ID 复用并按需更新;无 ID → 一律新建,不再按菜名复用(避免多日导入互相覆盖库存)。
+ */
private FindOrCreate findOrCreateDish(Long merchantId, Long deptId, String categoryName, Long defaultCategoryId,
Map categoryMap, String name, String ingredients, Integer priceFen, Integer stock,
- ImportCache cache) {
+ Long excelSpuId, ImportCache cache) {
String clean = cleanDishName(name);
- CanteenSpuEntity exist = cache.dishByName.get(clean);
- if (exist != null) {
- String importIngredients = normalizeImportIngredients(ingredients);
- boolean changeDesc = false;
- boolean changePrice = false;
- boolean changeStock = false;
- boolean changeCategory = false;
- if (StrUtil.isNotBlank(importIngredients)) {
- String existPlain = toPlainIngredients(exist.getDescription());
- if (!importIngredients.equals(existPlain)) {
- exist.setDescription(importIngredients);
- changeDesc = true;
- }
+ if (excelSpuId != null) {
+ CanteenSpuEntity exist = resolveSpuById(excelSpuId, cache);
+ if (exist == null) {
+ throw new IllegalArgumentException("菜品SPU ID " + excelSpuId + " 不存在");
}
- if (priceFen != null && priceFen > 0 && !Objects.equals(exist.getPrice(), priceFen)) {
- exist.setPrice(priceFen);
- exist.setMarketPrice(priceFen);
- changePrice = true;
+ if (!merchantId.equals(exist.getMerchantId())) {
+ throw new IllegalArgumentException("菜品SPU ID " + excelSpuId + " 不属于当前商户");
}
- if (stock != null && stock >= 0 && !Objects.equals(exist.getStock(), stock)) {
- exist.setStock(stock);
- changeStock = true;
- }
- // Excel 明确填了分类才改;空着不覆盖已有分类
- if (StrUtil.isNotBlank(normalizeCategoryName(categoryName))) {
- Long newCatId = resolveOrCreateCategoryId(categoryName, defaultCategoryId, categoryMap, merchantId,
- deptId);
- if (newCatId != null && !Objects.equals(exist.getCategoryId(), newCatId)) {
- exist.setCategoryId(newCatId);
- changeCategory = true;
- }
- }
- if (changeDesc || changePrice || changeStock || changeCategory) {
- // 禁止 updateById(半截实体):pic_url 等为 ALWAYS,null 会写库报错
- var uw = Wrappers.lambdaUpdate().eq(CanteenSpuEntity::getId, exist.getId());
- if (changeDesc) {
- uw.set(CanteenSpuEntity::getDescription, exist.getDescription());
- }
- if (changePrice) {
- uw.set(CanteenSpuEntity::getPrice, exist.getPrice());
- uw.set(CanteenSpuEntity::getMarketPrice, exist.getMarketPrice());
- }
- if (changeStock) {
- uw.set(CanteenSpuEntity::getStock, exist.getStock());
- }
- if (changeCategory) {
- uw.set(CanteenSpuEntity::getCategoryId, exist.getCategoryId());
- }
- canteenSpuService.update(uw);
+ int type = exist.getSpuType() == null ? 1 : exist.getSpuType();
+ if (type == 2) {
+ throw new IllegalArgumentException("菜品SPU ID " + excelSpuId + " 是套餐,单品行请填写单品 SPU ID");
}
+ applyDishUpdates(exist, categoryName, defaultCategoryId, categoryMap, merchantId, deptId, ingredients,
+ priceFen, stock);
return new FindOrCreate(exist, false);
}
+
+ // 无 SPU ID:始终新建,不按菜名命中已有菜品
Long categoryId = resolveOrCreateCategoryId(categoryName, defaultCategoryId, categoryMap, merchantId, deptId);
if (categoryId == null) {
throw new IllegalArgumentException(missingCategoryMessage("新建菜品「" + clean + "」", categoryName, categoryMap));
@@ -1280,14 +1255,65 @@ public class CanteenDailyMenuImportHelper {
spu.setStock(stock);
}
canteenSpuService.save(spu);
- cache.dishByName.put(clean, spu);
return new FindOrCreate(spu, true);
}
+ private void applyDishUpdates(CanteenSpuEntity exist, String categoryName, Long defaultCategoryId,
+ Map categoryMap, Long merchantId, Long deptId, String ingredients, Integer priceFen,
+ Integer stock) {
+ String importIngredients = normalizeImportIngredients(ingredients);
+ boolean changeDesc = false;
+ boolean changePrice = false;
+ boolean changeStock = false;
+ boolean changeCategory = false;
+ if (StrUtil.isNotBlank(importIngredients)) {
+ String existPlain = toPlainIngredients(exist.getDescription());
+ if (!importIngredients.equals(existPlain)) {
+ exist.setDescription(importIngredients);
+ changeDesc = true;
+ }
+ }
+ if (priceFen != null && priceFen > 0 && !Objects.equals(exist.getPrice(), priceFen)) {
+ exist.setPrice(priceFen);
+ exist.setMarketPrice(priceFen);
+ changePrice = true;
+ }
+ if (stock != null && stock >= 0 && !Objects.equals(exist.getStock(), stock)) {
+ exist.setStock(stock);
+ changeStock = true;
+ }
+ if (StrUtil.isNotBlank(normalizeCategoryName(categoryName))) {
+ Long newCatId = resolveOrCreateCategoryId(categoryName, defaultCategoryId, categoryMap, merchantId, deptId);
+ if (newCatId != null && !Objects.equals(exist.getCategoryId(), newCatId)) {
+ exist.setCategoryId(newCatId);
+ changeCategory = true;
+ }
+ }
+ if (changeDesc || changePrice || changeStock || changeCategory) {
+ var uw = Wrappers.lambdaUpdate().eq(CanteenSpuEntity::getId, exist.getId());
+ if (changeDesc) {
+ uw.set(CanteenSpuEntity::getDescription, exist.getDescription());
+ }
+ if (changePrice) {
+ uw.set(CanteenSpuEntity::getPrice, exist.getPrice());
+ uw.set(CanteenSpuEntity::getMarketPrice, exist.getMarketPrice());
+ }
+ if (changeStock) {
+ uw.set(CanteenSpuEntity::getStock, exist.getStock());
+ }
+ if (changeCategory) {
+ uw.set(CanteenSpuEntity::getCategoryId, exist.getCategoryId());
+ }
+ canteenSpuService.update(uw);
+ }
+ }
+
+ /**
+ * 解析/创建套餐。有套餐 SPU ID 则复用;无 ID 一律新建(不按套餐全名复用)。
+ */
private FindOrCreate findOrCreateCombo(Long merchantId, Long deptId, String categoryName, Long defaultCategoryId,
Map categoryMap, String fullName, String pkgLabel, Integer priceFen, Integer stock,
- List children, String ingredients, ImportCache cache) {
- CanteenSpuEntity exist = cache.comboByName.get(fullName);
+ List children, String ingredients, Long excelSpuId, ImportCache cache) {
List items = new ArrayList<>();
int sort = 0;
List newChildIds = new ArrayList<>(children.size());
@@ -1299,7 +1325,19 @@ public class CanteenDailyMenuImportHelper {
items.add(line);
newChildIds.add(child.getId());
}
- if (exist != null) {
+
+ if (excelSpuId != null) {
+ CanteenSpuEntity exist = resolveSpuById(excelSpuId, cache);
+ if (exist == null) {
+ throw new IllegalArgumentException("菜品SPU ID " + excelSpuId + " 不存在");
+ }
+ if (!merchantId.equals(exist.getMerchantId())) {
+ throw new IllegalArgumentException("菜品SPU ID " + excelSpuId + " 不属于当前商户");
+ }
+ int type = exist.getSpuType() == null ? 1 : exist.getSpuType();
+ if (type != 2) {
+ throw new IllegalArgumentException("菜品SPU ID " + excelSpuId + " 不是套餐,套餐行请填写套餐 SPU ID");
+ }
boolean changePrice = priceFen != null && !Objects.equals(exist.getPrice(), priceFen);
if (changePrice) {
exist.setPrice(priceFen);
@@ -1358,9 +1396,9 @@ public class CanteenDailyMenuImportHelper {
}
canteenSpuService.update(uw);
}
- cache.comboByName.put(fullName, exist);
return new FindOrCreate(exist, false);
}
+
Long categoryId = resolveOrCreateCategoryId(categoryName, defaultCategoryId, categoryMap, merchantId, deptId);
if (categoryId == null) {
throw new IllegalArgumentException(missingCategoryMessage("新建套餐「" + fullName + "」", categoryName, categoryMap));
@@ -1375,13 +1413,30 @@ 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);
}
+ private CanteenSpuEntity resolveSpuById(Long spuId, ImportCache cache) {
+ if (spuId == null) {
+ return null;
+ }
+ CanteenSpuEntity cached = cache.spuById.get(spuId);
+ if (cached != null) {
+ return cached;
+ }
+ CanteenSpuEntity db = canteenSpuService.getById(spuId);
+ if (db != null) {
+ cache.spuById.put(spuId, db);
+ if (db.getPicUrl() == null) {
+ db.setPicUrl("");
+ }
+ }
+ return db;
+ }
+
private static String menuKey(LocalDate date, Integer hourType, Long spuId) {
return date + "|" + hourType + "|" + spuId;
}
@@ -1675,7 +1730,7 @@ public class CanteenDailyMenuImportHelper {
* 多餐:午餐,晚餐 / 午餐、晚餐 / 午餐/晚餐
* 快捷:三餐、全天、早中晚 → 早+午+晚;中晚餐 → 午+晚
*
- * 同一道菜多餐时菜品库仍按菜名复用一份 SPU,排菜按餐段各挂一条。
+ * 无菜品SPU ID 时各餐段各自新建 SPU(库存互不影响);有 SPU ID 则按 ID 复用。
*/
private static List parseMeals(String meal) {
if (StrUtil.isBlank(meal)) {
@@ -1887,6 +1942,8 @@ public class CanteenDailyMenuImportHelper {
private static class ImportCache {
final Map dishByName = new HashMap<>();
final Map comboByName = new HashMap<>();
+ /** 按 ID 解析 Excel「菜品SPU ID」 */
+ final Map spuById = new HashMap<>();
final Set existingMenuKeys = new HashSet<>();
/** 套餐组成 childSpuId 有序列表,用于判断是否需要 updateDeep */
final Map> comboChildIds = new HashMap<>();