每日库存
This commit is contained in:
1 parent
ba4a564c85
commit
430fd5e9f9
6 files changed
+143
-22
No files matched your search
+3
-2
@@ -10,6 +10,7 @@ import java.math.BigDecimal;
|
||||
* <p>列顺序:日期、餐段、套餐名、菜品名、分类、单价、套餐价、线上库存、线下库存、配料、菜谱ID、菜品SPU ID、填写说明
|
||||
* <p>线上/线下库存挂在每日菜谱行上,不再写入菜品列表 {@code canteen_spu.stock}。
|
||||
* <p>菜谱ID / 菜品SPU ID 必须用文本导出,否则 Excel 对超长雪花 ID 会丢精度导致再导入报「不存在」。
|
||||
* <p>套餐块「菜品SPU ID」=套餐本体一个 ID(首行合并);组成菜靠菜名。单品行=该单品 ID。
|
||||
* <p>无「菜品SPU ID」时一律新建菜品(不按菜名复用);有 ID 才按 ID 更新。
|
||||
*/
|
||||
@Data
|
||||
@@ -64,8 +65,8 @@ public class CanteenDailyMenuImportExcel {
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 关联菜品 SPU ID(文本)。套餐导出时为本行组成菜 ID,不是套餐本身 ID。
|
||||
* 导入:有则按 ID 更新该菜;套餐主体仍靠「菜谱ID」关联已有套餐。
|
||||
* 关联菜品 SPU ID(文本)。套餐块仅填首行=套餐本体 ID(与菜谱ID一样合并展示);组成菜靠菜名,不写子菜 ID。
|
||||
* 单品行=该单品 ID。导入:有则按 ID 更新;无则新建。
|
||||
*/
|
||||
@ExcelProperty(value = "菜品SPU ID", index = 11)
|
||||
private String spuId;
|
||||
|
||||
+4
@@ -45,4 +45,8 @@ public class CanteenSpuComboItemEntity extends Model<CanteenSpuComboItemEntity>
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "组成菜品价格(分)")
|
||||
private Integer childSpuPrice;
|
||||
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "组成菜品配料/详情")
|
||||
private String childSpuIngredients;
|
||||
}
|
||||
+3
@@ -24,4 +24,7 @@ public class OnlineSpuComboItemVO {
|
||||
|
||||
@Schema(description = "组成菜品单价(分,仅展示)")
|
||||
private Integer price;
|
||||
|
||||
@Schema(description = "组成菜品配料(纯文本/富文本,导入导出用)")
|
||||
private String ingredients;
|
||||
}
|
||||
+6
-5
@@ -12,7 +12,7 @@ import org.apache.poi.ss.usermodel.VerticalAlignment;
|
||||
import org.apache.poi.ss.util.CellRangeAddress;
|
||||
|
||||
/**
|
||||
* 每日菜谱导入模板 / 导出:按数据动态合并日期、餐段、套餐名、套餐价、菜谱ID、说明。
|
||||
* 每日菜谱导入模板 / 导出:按数据动态合并日期、餐段、套餐名、套餐价、菜谱ID、套餐SPU、说明。
|
||||
* <p>列:0日期 1餐段 2套餐名 3菜品名 4分类 5单价 6套餐价 7线上库存 8线下库存 9配料 10菜谱ID 11SPU ID 12填写说明
|
||||
*/
|
||||
public class CanteenDailyMenuTemplateMergeHandler implements WorkbookWriteHandler {
|
||||
@@ -23,8 +23,8 @@ public class CanteenDailyMenuTemplateMergeHandler implements WorkbookWriteHandle
|
||||
private static final int COL_COMBO_PRICE = 6;
|
||||
private static final int COL_ONLINE_STOCK = 7;
|
||||
private static final int COL_OFFLINE_STOCK = 8;
|
||||
private static final int COL_INGREDIENTS = 9;
|
||||
private static final int COL_ID = 10;
|
||||
private static final int COL_SPU = 11;
|
||||
private static final int COL_TIP = 12;
|
||||
|
||||
@Override
|
||||
@@ -49,7 +49,7 @@ public class CanteenDailyMenuTemplateMergeHandler implements WorkbookWriteHandle
|
||||
}
|
||||
|
||||
/**
|
||||
* 向下填充后,按连续相同值合并:日期 / 餐段 / 套餐块(套餐名+套餐价+ID+SPU+说明)
|
||||
* 向下填充后,按连续相同值合并:日期 / 餐段 / 套餐块(套餐名+套餐价+库存+ID+SPU+说明;配料不合并)
|
||||
*/
|
||||
private static void mergeByRuns(Sheet sheet, CellStyle center) {
|
||||
int lastRow = sheet.getLastRowNum();
|
||||
@@ -140,9 +140,10 @@ public class CanteenDailyMenuTemplateMergeHandler implements WorkbookWriteHandle
|
||||
mergeRange(sheet, runStart, endRow, COL_COMBO_PRICE, center);
|
||||
mergeRange(sheet, runStart, endRow, COL_ONLINE_STOCK, center);
|
||||
mergeRange(sheet, runStart, endRow, COL_OFFLINE_STOCK, center);
|
||||
mergeRange(sheet, runStart, endRow, COL_INGREDIENTS, center);
|
||||
// 配料不合并:组成菜各自配料(同名不同配料靠此区分)
|
||||
mergeRange(sheet, runStart, endRow, COL_ID, center);
|
||||
// 菜品SPU ID 不合并:套餐每行对应组成菜自己的 ID
|
||||
// 套餐 SPU ID 与菜谱 ID 一样合并:整块一个套餐本体 ID
|
||||
mergeRange(sheet, runStart, endRow, COL_SPU, center);
|
||||
mergeRange(sheet, runStart, endRow, COL_TIP, center);
|
||||
}
|
||||
runStart = r;
|
||||
|
||||
+125
-15
@@ -95,11 +95,11 @@ public class CanteenDailyMenuImportHelper {
|
||||
public List<CanteenDailyMenuImportExcel> templateRows() {
|
||||
List<CanteenDailyMenuImportExcel> list = new ArrayList<>();
|
||||
list.add(row("2026-08-17", "午餐", "A套餐", "糖醋排骨", "荤菜", "青莲仔排", "12", "21", "80", "100",
|
||||
"无菜品SPU ID则新建菜品;有则按本行菜品ID更新。套餐价/线上线下库存/配料填首行;套餐每行SPU=组成菜ID"));
|
||||
"无菜品SPU ID则新建;有则按ID更新。套餐价/库存/套餐SPU填首行;配料按组成菜分行填(同名不同配料会建两道菜)"));
|
||||
list.add(row(null, null, null, "蒜苔炒腊肉", "荤菜", null, "8", null, null, null, null));
|
||||
list.add(row(null, null, null, "清炒时蔬", "素菜", null, "3", null, null, null, null));
|
||||
list.add(row(null, null, "B套餐", "红烧肉", "荤菜", "五花肉、土豆", "10", "18", "60", "80",
|
||||
"换套餐名即另一套;无菜谱ID时新建套餐;有菜谱ID则更新该排菜套餐组成"));
|
||||
"换套餐名即另一套;无菜谱ID时新建套餐;有菜谱ID/套餐SPU则更新该套餐"));
|
||||
list.add(row(null, null, null, "土豆丝", "素菜", null, "4", null, null, null, null));
|
||||
list.add(row(null, null, "无", "千张大骨汤", "汤类", null, "0", null, "50", "50",
|
||||
"单品:套餐名填「无」「单品」「单点」或「单品-xxx/单点-xxx」;价格填单价列;库存填本行线上/线下"));
|
||||
@@ -184,6 +184,7 @@ public class CanteenDailyMenuImportHelper {
|
||||
List<OnlineSpuComboItemVO> items = menu.getComboItems();
|
||||
Set<Long> exportedChildIds = new HashSet<>();
|
||||
int written = 0;
|
||||
String comboSpuId = menu.getSpuId() == null ? null : String.valueOf(menu.getSpuId());
|
||||
for (OnlineSpuComboItemVO item : items) {
|
||||
if (item == null) {
|
||||
continue;
|
||||
@@ -195,17 +196,16 @@ public class CanteenDailyMenuImportHelper {
|
||||
CanteenDailyMenuImportExcel row = new CanteenDailyMenuImportExcel();
|
||||
if (written == 0) {
|
||||
row.setId(menu.getId() == null ? null : String.valueOf(menu.getId()));
|
||||
row.setSpuId(comboSpuId);
|
||||
row.setBizDate(dateStr);
|
||||
row.setMealName(meal);
|
||||
row.setComboName(comboLabel);
|
||||
row.setPriceYuan(comboPrice);
|
||||
row.setOnlineStock(menu.getOnlineStock());
|
||||
row.setOfflineStock(menu.getOfflineStock());
|
||||
row.setIngredients(toExcelIngredients(menu.getIngredients()));
|
||||
}
|
||||
// 每行导出组成菜自己的 SPU ID(不是套餐 SPU);便于再导入按菜更新
|
||||
row.setSpuId(childId == null ? null : String.valueOf(childId));
|
||||
// 每行都带分类,改菜名新建子菜时不丢分类
|
||||
// 套餐只导出一个套餐 SPU;配料列按组成菜各自导出(同名不同配料靠此区分)
|
||||
row.setIngredients(toExcelIngredients(item.getIngredients()));
|
||||
row.setCategoryName(menu.getCategoryName());
|
||||
row.setDishName(StrUtil.blankToDefault(item.getName(), ""));
|
||||
row.setDishPriceYuan(fenToYuan(item.getPrice()));
|
||||
@@ -859,6 +859,9 @@ public class CanteenDailyMenuImportHelper {
|
||||
Integer comboPrice = null;
|
||||
Integer onlineStock = first.getOnlineStock();
|
||||
Integer offlineStock = first.getOfflineStock();
|
||||
Long comboExcelSpuId = resolveComboExcelSpuId(items, cache);
|
||||
Map<String, Long> existChildByKey = existingComboChildKeyMap(comboExcelSpuId, cache);
|
||||
boolean uniformSpu = isUniformSpuId(items);
|
||||
Set<String> seenDishKeys = new LinkedHashSet<>();
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
CanteenDailyMenuImportExcel item = items.get(i);
|
||||
@@ -868,14 +871,16 @@ public class CanteenDailyMenuImportHelper {
|
||||
comboPrice = fen;
|
||||
}
|
||||
}
|
||||
String dishKey = cleanDishName(item.getDishName());
|
||||
if (StrUtil.isBlank(dishKey) || !seenDishKeys.add(dishKey)) {
|
||||
String dishKey = dishIdentityKey(item.getDishName(), item.getIngredients());
|
||||
if (StrUtil.isBlank(cleanDishName(item.getDishName())) || !seenDishKeys.add(dishKey)) {
|
||||
continue;
|
||||
}
|
||||
Long childSpuId = resolveComboChildExcelSpuId(item.getSpuId(), comboExcelSpuId, uniformSpu,
|
||||
existChildByKey.get(dishKey), cache);
|
||||
FindOrCreate r = findOrCreateDish(merchantId, deptId,
|
||||
item.getCategoryName(), categoryId, categoryMap,
|
||||
item.getDishName(), null, resolveDishPriceFen(item), null,
|
||||
parseExcelLong(item.getSpuId()), cache);
|
||||
item.getDishName(), item.getIngredients(), resolveDishPriceFen(item), null, childSpuId,
|
||||
cache);
|
||||
if (r.created) {
|
||||
createdDish++;
|
||||
}
|
||||
@@ -895,7 +900,7 @@ public class CanteenDailyMenuImportHelper {
|
||||
FindOrCreate combo = findOrCreateCombo(merchantId, deptId,
|
||||
first.getCategoryName(), categoryId, categoryMap,
|
||||
fullComboName, comboName, comboPrice, null, children,
|
||||
resolveComboIngredients(first), null, cache);
|
||||
resolveComboIngredients(first), comboExcelSpuId, cache);
|
||||
if (combo.created) {
|
||||
createdCombo++;
|
||||
}
|
||||
@@ -1378,6 +1383,12 @@ public class CanteenDailyMenuImportHelper {
|
||||
Integer comboPrice = null;
|
||||
Integer onlineStock = items.get(0).getOnlineStock();
|
||||
Integer offlineStock = items.get(0).getOfflineStock();
|
||||
Long comboExcelSpuId = resolveComboExcelSpuId(items, cache);
|
||||
if (comboExcelSpuId == null) {
|
||||
comboExcelSpuId = menu.getSpuId();
|
||||
}
|
||||
Map<String, Long> existChildByKey = existingComboChildKeyMap(comboExcelSpuId, cache);
|
||||
boolean uniformSpu = isUniformSpuId(items);
|
||||
Set<String> seenDishKeys = new LinkedHashSet<>();
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
CanteenDailyMenuImportExcel item = items.get(i);
|
||||
@@ -1388,13 +1399,15 @@ public class CanteenDailyMenuImportHelper {
|
||||
if (StrUtil.isBlank(dish)) {
|
||||
return "菜谱ID " + menu.getId() + ":套餐组成菜品名不能为空";
|
||||
}
|
||||
String dishKey = cleanDishName(dish);
|
||||
String dishKey = dishIdentityKey(dish, item.getIngredients());
|
||||
if (!seenDishKeys.add(dishKey)) {
|
||||
continue;
|
||||
}
|
||||
Long childSpuId = resolveComboChildExcelSpuId(item.getSpuId(), comboExcelSpuId, uniformSpu,
|
||||
existChildByKey.get(dishKey), cache);
|
||||
FindOrCreate r = findOrCreateDish(merchantId, deptId,
|
||||
item.getCategoryName(), defaultCategoryId, categoryMap, dish,
|
||||
null, resolveDishPriceFen(item), null, parseExcelLong(item.getSpuId()), cache);
|
||||
item.getIngredients(), resolveDishPriceFen(item), null, childSpuId, cache);
|
||||
children.add(r.spu);
|
||||
}
|
||||
children = dedupeComboChildren(children);
|
||||
@@ -1413,7 +1426,7 @@ public class CanteenDailyMenuImportHelper {
|
||||
FindOrCreate combo = findOrCreateCombo(merchantId, deptId,
|
||||
items.get(0).getCategoryName(), defaultCategoryId, categoryMap, fullComboName,
|
||||
comboName, comboPrice, null, children, resolveComboIngredients(items.get(0)),
|
||||
menu.getSpuId(), cache);
|
||||
comboExcelSpuId, cache);
|
||||
menu.setStartTime(date);
|
||||
menu.setHourType(hourType);
|
||||
menu.setSpuId(combo.spu.getId());
|
||||
@@ -1423,6 +1436,100 @@ public class CanteenDailyMenuImportHelper {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 套餐块「菜品SPU ID」:仅当确实是套餐类型时才当作套餐本体;
|
||||
* 旧导出把组成菜 ID 写在各行时(类型为单品)返回 null,避免误当套餐。
|
||||
*/
|
||||
private Long resolveComboExcelSpuId(List<CanteenDailyMenuImportExcel> items, ImportCache cache) {
|
||||
if (CollUtil.isEmpty(items)) {
|
||||
return null;
|
||||
}
|
||||
for (CanteenDailyMenuImportExcel item : items) {
|
||||
Long id = parseExcelLong(item == null ? null : item.getSpuId());
|
||||
if (id == null) {
|
||||
continue;
|
||||
}
|
||||
CanteenSpuEntity exist = resolveSpuById(id, cache);
|
||||
if (exist == null) {
|
||||
continue;
|
||||
}
|
||||
int type = exist.getSpuType() == null ? 1 : exist.getSpuType();
|
||||
if (type == 2) {
|
||||
return id;
|
||||
}
|
||||
// 整块同一单品 ID(旧导出合并继承)不算套餐本体
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean isUniformSpuId(List<CanteenDailyMenuImportExcel> items) {
|
||||
String first = null;
|
||||
boolean any = false;
|
||||
for (CanteenDailyMenuImportExcel item : items) {
|
||||
String s = StrUtil.trim(item == null ? null : item.getSpuId());
|
||||
if (StrUtil.isBlank(s)) {
|
||||
continue;
|
||||
}
|
||||
any = true;
|
||||
if (first == null) {
|
||||
first = s;
|
||||
}
|
||||
else if (!first.equals(s)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return any;
|
||||
}
|
||||
|
||||
/**
|
||||
* 组成菜 SPU:套餐块统一填套餐本体 ID 时不按该 ID 建子菜;
|
||||
* 可回退到已有套餐组成中「菜名+配料」完全一致的子菜 ID。
|
||||
*/
|
||||
private Long resolveComboChildExcelSpuId(String rowSpuId, Long comboSpuId, boolean uniformSpu,
|
||||
Long existChildByKey, ImportCache cache) {
|
||||
if (uniformSpu) {
|
||||
return existChildByKey;
|
||||
}
|
||||
Long id = parseExcelLong(rowSpuId);
|
||||
if (id == null || Objects.equals(id, comboSpuId)) {
|
||||
return existChildByKey;
|
||||
}
|
||||
CanteenSpuEntity exist = resolveSpuById(id, cache);
|
||||
if (exist != null) {
|
||||
int type = exist.getSpuType() == null ? 1 : exist.getSpuType();
|
||||
if (type == 2) {
|
||||
return existChildByKey;
|
||||
}
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
/** 已有套餐组成:key=菜名+配料,避免同名不同配料被复用错。 */
|
||||
private Map<String, Long> existingComboChildKeyMap(Long comboSpuId, ImportCache cache) {
|
||||
Map<String, Long> map = new HashMap<>();
|
||||
if (comboSpuId == null || cache == null) {
|
||||
return map;
|
||||
}
|
||||
List<Long> childIds = cache.comboChildIds.get(comboSpuId);
|
||||
if (CollUtil.isEmpty(childIds)) {
|
||||
return map;
|
||||
}
|
||||
for (Long childId : childIds) {
|
||||
CanteenSpuEntity child = resolveSpuById(childId, cache);
|
||||
if (child == null || StrUtil.isBlank(child.getName())) {
|
||||
continue;
|
||||
}
|
||||
map.putIfAbsent(dishIdentityKey(child.getName(), child.getDescription()), childId);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/** 组成菜去重/复用键:规范化菜名 + 纯文本配料 */
|
||||
private static String dishIdentityKey(String name, String ingredients) {
|
||||
return cleanDishName(name) + "|" + normalizeImportIngredients(ingredients);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true 表示新增了一条排菜
|
||||
*/
|
||||
@@ -1710,8 +1817,11 @@ public class CanteenDailyMenuImportHelper {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 套餐块「配料」列表示组成菜配料,不再写入套餐本体 description,避免把首道菜配料盖到套餐上。
|
||||
*/
|
||||
private static String resolveComboIngredients(CanteenDailyMenuImportExcel first) {
|
||||
return first == null ? "" : normalizeImportIngredients(first.getIngredients());
|
||||
return "";
|
||||
}
|
||||
|
||||
private Map<String, Long> loadCategoryMap(Long merchantId) {
|
||||
|
||||
+2
@@ -830,6 +830,7 @@ public class CanteenSpuServiceImpl extends ServiceImpl<CanteenSpuMapper, Canteen
|
||||
line.setChildSpuName(child.getName());
|
||||
line.setChildSpuPicUrl(child.getPicUrl());
|
||||
line.setChildSpuPrice(child.getPrice());
|
||||
line.setChildSpuIngredients(child.getDescription());
|
||||
}
|
||||
}
|
||||
return lines.stream().collect(Collectors.groupingBy(CanteenSpuComboItemEntity::getComboSpuId,
|
||||
@@ -849,6 +850,7 @@ public class CanteenSpuServiceImpl extends ServiceImpl<CanteenSpuMapper, Canteen
|
||||
vo.setQuantity(line.getQuantity());
|
||||
vo.setSortOrder(line.getSortOrder());
|
||||
vo.setPrice(line.getChildSpuPrice());
|
||||
vo.setIngredients(line.getChildSpuIngredients());
|
||||
list.add(vo);
|
||||
}
|
||||
return list;
|
||||
|
||||
Reference in new issue
Block a user