This commit is contained in:
dzc committed 2026-08-18 16:36:37 +08:00
commit cb5fdc54d8
2 files changed
+66 -17

No files matched your search

@@ -241,18 +241,24 @@ public class CanteenSpuController {
/**
* Excel 导入菜品(单品 + 套餐)
*
* @param cover true=覆盖(同名更新),false=仅新增(同名跳过)
* @param importMode cover=覆盖更新;skip=同名跳过;force=同名也新增(分类仍复用)。兼容旧参数 cover
*/
@Operation(summary = "Excel导入菜品", description = "cover=true 覆盖导入;cover=false 新增导入;支持套餐")
@Operation(summary = "Excel导入菜品", description = "importMode=cover/skip/force;兼容 cover=true/false")
@SysLog("导入菜品spu")
@PostMapping("/import")
@PreAuthorize("@pms.hasPermission('canteen_canteenSpu_add')")
public R importExcel(@RequestParam("file") MultipartFile file,
@RequestParam("merchantId") Long merchantId,
@RequestParam(value = "categoryId", required = false) Long categoryId,
@RequestParam(value = "cover", defaultValue = "false") boolean cover) throws Exception {
@RequestParam(value = "importMode", required = false) String importMode,
@RequestParam(value = "cover", required = false) Boolean cover) throws Exception {
try {
return canteenSpuImportHelper.importExcel(file, merchantId, categoryId, cover);
String mode = importMode;
if (StrUtil.isBlank(mode) && cover != null) {
mode = Boolean.TRUE.equals(cover) ? CanteenSpuImportHelper.MODE_COVER
: CanteenSpuImportHelper.MODE_SKIP;
}
return canteenSpuImportHelper.importExcel(file, merchantId, categoryId, mode);
}
catch (IllegalArgumentException ex) {
return R.failed(ex.getMessage());
@@ -46,7 +46,7 @@ import java.util.Set;
* 菜品列表 Excel 导入/导出(单品 + 套餐)
* <ul>
* <li>套餐名留空=单品;套餐名相同多行=套餐组成</li>
* <li>cover=true:同名更新;cover=false:同名跳过</li>
* <li>cover:同名更新;skip:同名跳过;force:同名也新增(分类仍按名称复用)</li>
* </ul>
*/
@Service
@@ -190,9 +190,25 @@ public class CanteenSpuImportHelper {
return row;
}
/** 覆盖:同名/同ID更新 */
public static final String MODE_COVER = "cover";
/** 新增:同名跳过 */
public static final String MODE_SKIP = "skip";
/** 不覆盖:同名套餐/单品也强制新增;分类仍复用 */
public static final String MODE_FORCE = "force";
@Transactional(rollbackFor = Exception.class)
public R importExcel(MultipartFile file, Long merchantId, Long defaultCategoryId, boolean cover)
throws IOException {
return importExcel(file, merchantId, defaultCategoryId, cover ? MODE_COVER : MODE_SKIP);
}
@Transactional(rollbackFor = Exception.class)
public R importExcel(MultipartFile file, Long merchantId, Long defaultCategoryId, String importMode)
throws IOException {
String mode = normalizeImportMode(importMode);
boolean cover = MODE_COVER.equals(mode);
boolean force = MODE_FORCE.equals(mode);
if (merchantId == null) {
return R.failed("请选择商户");
}
@@ -269,7 +285,7 @@ public class CanteenSpuImportHelper {
}
try {
int[] c = upsertCombo(e.getKey(), e.getValue(), merchantId, deptId, defaultCategoryId, categoryByName,
cover, errors, cache, pendingUpdates, standaloneDishIds, standaloneNames);
mode, errors, cache, pendingUpdates, standaloneDishIds, standaloneNames);
created += c[0];
updated += c[1];
skipped += c[2];
@@ -281,7 +297,7 @@ public class CanteenSpuImportHelper {
}
if (CollUtil.isNotEmpty(singleRows)) {
try {
int[] c = upsertSingles(singleRows, merchantId, deptId, defaultCategoryId, categoryByName, cover,
int[] c = upsertSingles(singleRows, merchantId, deptId, defaultCategoryId, categoryByName, mode,
errors, cache, pendingUpdates);
created += c[0];
updated += c[1];
@@ -299,8 +315,11 @@ public class CanteenSpuImportHelper {
}
String msg = String.format("导入完成:新增 %d,更新 %d,跳过 %d,价格已改 %d", created, updated, skipped,
priceChanged);
if (!cover && skipped > 0) {
msg += ";当前为「新增导入」,同名已跳过。改价格请选「覆盖导入」";
if (force) {
msg += ";当前为「不覆盖导入」,同名套餐/单品均已新增(分类仍复用)";
}
else if (!cover && skipped > 0) {
msg += ";当前为「新增导入」,同名已跳过。改价格请选「覆盖导入」;同名也要新增请选「不覆盖导入」";
}
if (cover && singlePriceMissing > 0 && singlePriceParsed == 0) {
msg += ";警告:单品行均未读到「菜品单价(元)」,请确认第4列有数值";
@@ -477,8 +496,10 @@ public class CanteenSpuImportHelper {
/** @return [created, updated, skipped, priceChanged] */
private int[] upsertSingles(List<CanteenSpuImportExcel> items, Long merchantId, Long deptId,
Long defaultCategoryId, Map<String, Long> categoryByName, boolean cover, List<String> errors,
Long defaultCategoryId, Map<String, Long> categoryByName, String mode, List<String> errors,
ImportCache cache, List<CanteenSpuEntity> pendingUpdates) {
boolean cover = MODE_COVER.equals(mode);
boolean force = MODE_FORCE.equals(mode);
int created = 0;
int updated = 0;
int skipped = 0;
@@ -487,7 +508,7 @@ public class CanteenSpuImportHelper {
try {
Long categoryId = requireOrCreateCategory(item, defaultCategoryId, categoryByName, merchantId,
deptId);
List<CanteenSpuEntity> exists = resolveDishes(merchantId, item, cache);
List<CanteenSpuEntity> exists = force ? List.of() : resolveDishes(merchantId, item, cache);
if (CollUtil.isNotEmpty(exists)) {
if (!cover) {
skipped++;
@@ -513,7 +534,9 @@ public class CanteenSpuImportHelper {
CanteenSpuEntity spu = newDish(merchantId, deptId, categoryId, item);
canteenSpuService.save(spu);
createStock(spu);
cache.put(spu);
if (!force) {
cache.put(spu);
}
created++;
if (resolveDishPriceFen(item) != null) {
priceChanged++;
@@ -529,9 +552,11 @@ public class CanteenSpuImportHelper {
/** @return [created, updated, skipped, priceChanged] */
private int[] upsertCombo(String comboName, List<CanteenSpuImportExcel> items, Long merchantId, Long deptId,
Long defaultCategoryId, Map<String, Long> categoryByName, boolean cover, List<String> errors,
Long defaultCategoryId, Map<String, Long> categoryByName, String mode, List<String> errors,
ImportCache cache, List<CanteenSpuEntity> pendingUpdates, Set<Long> standaloneDishIds,
Set<String> standaloneNames) {
boolean cover = MODE_COVER.equals(mode);
boolean force = MODE_FORCE.equals(mode);
List<CanteenSpuEntity> children = new ArrayList<>();
int created = 0;
int updated = 0;
@@ -541,7 +566,7 @@ public class CanteenSpuImportHelper {
try {
Long categoryId = requireOrCreateCategory(item, defaultCategoryId, categoryByName, merchantId,
deptId);
List<CanteenSpuEntity> exists = resolveDishes(merchantId, item, cache);
List<CanteenSpuEntity> exists = force ? List.of() : resolveDishes(merchantId, item, cache);
if (CollUtil.isNotEmpty(exists)) {
CanteenSpuEntity exist = exists.get(0);
boolean dishOwnedBySingleRow = standaloneDishIds.contains(exist.getId())
@@ -570,7 +595,9 @@ public class CanteenSpuImportHelper {
CanteenSpuEntity spu = newDish(merchantId, deptId, categoryId, item);
canteenSpuService.save(spu);
createStock(spu);
cache.put(spu);
if (!force) {
cache.put(spu);
}
children.add(spu);
created++;
if (resolveDishPriceFen(item) != null) {
@@ -604,7 +631,7 @@ public class CanteenSpuImportHelper {
comboItems.add(line);
}
CanteenSpuEntity existCombo = resolveCombo(merchantId, comboName, first.getComboId(), cache);
CanteenSpuEntity existCombo = force ? null : resolveCombo(merchantId, comboName, first.getComboId(), cache);
if (existCombo != null) {
if (!cover) {
skipped++;
@@ -654,7 +681,9 @@ public class CanteenSpuImportHelper {
combo.setComboItems(comboItems);
canteenSpuService.saveDeep(combo);
createStock(combo);
cache.put(combo);
if (!force) {
cache.put(combo);
}
created++;
if (comboPriceFen != null) {
priceChanged++;
@@ -663,6 +692,20 @@ public class CanteenSpuImportHelper {
return new int[] { created, updated, skipped, priceChanged };
}
private static String normalizeImportMode(String importMode) {
if (StrUtil.isBlank(importMode)) {
return MODE_SKIP;
}
String m = importMode.trim().toLowerCase();
if (MODE_COVER.equals(m) || "true".equals(m) || "1".equals(m)) {
return MODE_COVER;
}
if (MODE_FORCE.equals(m) || "always".equals(m) || "duplicate".equals(m)) {
return MODE_FORCE;
}
return MODE_SKIP;
}
/** 单品单价:菜品单价优先;为空时用市场价 */
private static Integer resolveDishPriceFen(CanteenSpuImportExcel item) {
Integer dish = toFen(item.getPriceYuan());