每日菜谱导入优化
This commit is contained in:
1 parent
43966c04ec
commit
bf3faae01a
2 files changed
+139
-72
No files matched your search
+132
-70
@@ -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<CanteenDailyMenuImportExcel> templateRows() {
|
||||
List<CanteenDailyMenuImportExcel> list = new ArrayList<>();
|
||||
@@ -317,11 +319,8 @@ public class CanteenDailyMenuImportHelper {
|
||||
|
||||
// groupKey = date|hourType|comboOrEmpty|menuIdOrNew
|
||||
Map<String, List<CanteenDailyMenuImportExcel>> groups = new LinkedHashMap<>();
|
||||
/** 菜谱ID → 出现过的 日期|餐段(用于汇总跨天串 ID) */
|
||||
Map<Long, LinkedHashSet<String>> menuIdDayHours = new LinkedHashMap<>();
|
||||
/** 菜谱ID → 示例套餐/菜名(取首次) */
|
||||
Map<Long, String> menuIdLabel = new LinkedHashMap<>();
|
||||
Set<LocalDate> involvedDates = new HashSet<>();
|
||||
Set<Long> 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<String> crossDayIdErrors = new ArrayList<>();
|
||||
for (Map.Entry<Long, LinkedHashSet<String>> en : menuIdDayHours.entrySet()) {
|
||||
if (en.getValue().size() <= 1) {
|
||||
continue;
|
||||
}
|
||||
List<String> 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<String> clearedDayHour = new LinkedHashSet<>();
|
||||
List<CanteenDailyMenuEntity> pendingMenus = new ArrayList<>();
|
||||
int updated = 0;
|
||||
int createdDish = 0;
|
||||
int createdCombo = 0;
|
||||
int scheduled = 0;
|
||||
int idAsNew = 0;
|
||||
|
||||
for (Map.Entry<String, List<CanteenDailyMenuImportExcel>> e : groups.entrySet()) {
|
||||
List<CanteenDailyMenuImportExcel> 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<LocalDate> dates) {
|
||||
private ImportCache buildImportCache(Long merchantId, Set<LocalDate> dates, Set<Long> menuIds) {
|
||||
ImportCache cache = new ImportCache();
|
||||
// 只取导入比对所需字段;更新时用 UpdateWrapper,避免 pic_url 等 ALWAYS 字段被写成 null
|
||||
List<CanteenSpuEntity> spus = canteenSpuService.list(Wrappers.<CanteenSpuEntity>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<Long> 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<CanteenSpuComboItemEntity> lines = canteenSpuComboItemMapper.selectList(
|
||||
Wrappers.<CanteenSpuComboItemEntity>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<CanteenDailyMenuEntity> menus = canteenDailyMenuService.list(
|
||||
Wrappers.<CanteenDailyMenuEntity>lambdaQuery()
|
||||
@@ -609,9 +600,34 @@ public class CanteenDailyMenuImportHelper {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (CollUtil.isNotEmpty(menuIds)) {
|
||||
List<CanteenDailyMenuEntity> 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<CanteenDailyMenuEntity> pendingMenus) {
|
||||
if (CollUtil.isEmpty(pendingMenus)) {
|
||||
return;
|
||||
@@ -625,7 +641,7 @@ public class CanteenDailyMenuImportHelper {
|
||||
*/
|
||||
private String upsertGroupById(Long menuId, List<CanteenDailyMenuImportExcel> items, Long merchantId, Long deptId,
|
||||
Long defaultCategoryId, Map<String, Long> 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<CanteenSpuComboItemEntity> items = new ArrayList<>();
|
||||
int sort = 0;
|
||||
List<Long> 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<Long> 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.<CanteenSpuEntity>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<String, CanteenSpuEntity> dishByName = new HashMap<>();
|
||||
final Map<String, CanteenSpuEntity> comboByName = new HashMap<>();
|
||||
final Set<String> existingMenuKeys = new HashSet<>();
|
||||
/** 套餐组成 childSpuId 有序列表,用于判断是否需要 updateDeep */
|
||||
final Map<Long, List<Long>> comboChildIds = new HashMap<>();
|
||||
/** Excel 菜谱 ID → 实体,避免每组 getById */
|
||||
final Map<Long, CanteenDailyMenuEntity> menuById = new HashMap<>();
|
||||
}
|
||||
}
|
||||
+7
-2
@@ -485,7 +485,10 @@ public class CanteenSpuServiceImpl extends ServiceImpl<CanteenSpuMapper, Canteen
|
||||
if (entity.getPicUrl() == null) {
|
||||
entity.setPicUrl("");
|
||||
}
|
||||
merchantScopeHelper.fillDeptIdByMerchantId(entity.getMerchantId(), entity::setDeptId);
|
||||
// 调用方已带 deptId 时不再 Feign 查商户(导入场景会反复调用,极慢)
|
||||
if (entity.getDeptId() == null) {
|
||||
merchantScopeHelper.fillDeptIdByMerchantId(entity.getMerchantId(), entity::setDeptId);
|
||||
}
|
||||
baseMapper.insert(entity);
|
||||
saveComboItems(entity.getId(), entity.getSpuType(), entity.getComboItems());
|
||||
return Boolean.TRUE;
|
||||
@@ -496,7 +499,9 @@ public class CanteenSpuServiceImpl extends ServiceImpl<CanteenSpuMapper, Canteen
|
||||
public Boolean updateDeep(CanteenSpuEntity entity) {
|
||||
normalizeSpuType(entity);
|
||||
validateCombo(entity, entity.getId());
|
||||
merchantScopeHelper.fillDeptIdByMerchantId(entity.getMerchantId(), entity::setDeptId);
|
||||
if (entity.getDeptId() == null) {
|
||||
merchantScopeHelper.fillDeptIdByMerchantId(entity.getMerchantId(), entity::setDeptId);
|
||||
}
|
||||
baseMapper.updateById(entity);
|
||||
comboItemMapper.delete(Wrappers.<CanteenSpuComboItemEntity>lambdaQuery()
|
||||
.eq(CanteenSpuComboItemEntity::getComboSpuId, entity.getId()));
|
||||
|
||||
Reference in new issue
Block a user