Merge branch 'master' of https://gitea.zianxin.com/zax/biz-api-cloud-zax
This commit is contained in:
commit
a9a5a2f8d0
2 files changed
+123
-65
No files matched your search
+1
@@ -10,6 +10,7 @@ import java.math.BigDecimal;
|
||||
* <p>列顺序:日期、餐段、套餐名、菜品名、分类、单价、套餐价、库存、配料、菜谱ID、菜品SPU ID、填写说明
|
||||
* <p>配料:单品写单品 SPU;套餐仅在首行填写,写入套餐 SPU
|
||||
* <p>菜谱ID / 菜品SPU ID 必须用文本导出,否则 Excel 对超长雪花 ID 会丢精度导致再导入报「不存在」。
|
||||
* <p>无「菜品SPU ID」时一律新建菜品(不按菜名复用);有 ID 才按 ID 更新。
|
||||
*/
|
||||
@Data
|
||||
public class CanteenDailyMenuImportExcel {
|
||||
|
||||
+122
-65
@@ -81,11 +81,11 @@ public class CanteenDailyMenuImportHelper {
|
||||
public List<CanteenDailyMenuImportExcel> templateRows() {
|
||||
List<CanteenDailyMenuImportExcel> 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析/创建单品。
|
||||
* <p>有「菜品SPU ID」→ 按 ID 复用并按需更新;无 ID → 一律新建,不再按菜名复用(避免多日导入互相覆盖库存)。
|
||||
*/
|
||||
private FindOrCreate findOrCreateDish(Long merchantId, Long deptId, String categoryName, Long defaultCategoryId,
|
||||
Map<String, Long> 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.<CanteenSpuEntity>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<String, Long> 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.<CanteenSpuEntity>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<String, Long> categoryMap, String fullName, String pkgLabel, Integer priceFen, Integer stock,
|
||||
List<CanteenSpuEntity> children, String ingredients, ImportCache cache) {
|
||||
CanteenSpuEntity exist = cache.comboByName.get(fullName);
|
||||
List<CanteenSpuEntity> children, String ingredients, Long excelSpuId, ImportCache cache) {
|
||||
List<CanteenSpuComboItemEntity> items = new ArrayList<>();
|
||||
int sort = 0;
|
||||
List<Long> 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 {
|
||||
* <li>多餐:午餐,晚餐 / 午餐、晚餐 / 午餐/晚餐</li>
|
||||
* <li>快捷:三餐、全天、早中晚 → 早+午+晚;中晚餐 → 午+晚</li>
|
||||
* </ul>
|
||||
* 同一道菜多餐时菜品库仍按菜名复用一份 SPU,排菜按餐段各挂一条。
|
||||
* 无菜品SPU ID 时各餐段各自新建 SPU(库存互不影响);有 SPU ID 则按 ID 复用。
|
||||
*/
|
||||
private static List<Integer> parseMeals(String meal) {
|
||||
if (StrUtil.isBlank(meal)) {
|
||||
@@ -1887,6 +1942,8 @@ public class CanteenDailyMenuImportHelper {
|
||||
private static class ImportCache {
|
||||
final Map<String, CanteenSpuEntity> dishByName = new HashMap<>();
|
||||
final Map<String, CanteenSpuEntity> comboByName = new HashMap<>();
|
||||
/** 按 ID 解析 Excel「菜品SPU ID」 */
|
||||
final Map<Long, CanteenSpuEntity> spuById = new HashMap<>();
|
||||
final Set<String> existingMenuKeys = new HashSet<>();
|
||||
/** 套餐组成 childSpuId 有序列表,用于判断是否需要 updateDeep */
|
||||
final Map<Long, List<Long>> comboChildIds = new HashMap<>();
|
||||
|
||||
Reference in new issue
Block a user