每日导入导出修改优化,增加根据订单自动计算剩余库存
This commit is contained in:
1 parent
364d378708
commit
2edbdcce79
7 files changed
+331
-88
No files matched your search
+16
-8
@@ -7,11 +7,12 @@ import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 每日菜谱导入/导出 Excel 行(导出含菜谱 ID,导入时可按 ID 更新)
|
||||
* <p>列顺序:日期、餐段、套餐名、菜品名、分类、单价、套餐价、线上库存、线下库存、配料、菜谱ID、菜品SPU ID、填写说明
|
||||
* <p>列顺序:日期、餐段、套餐名、菜品名、分类、单价、套餐价、线上库存、线下库存、配料、菜谱ID、菜品SPU ID、子SPU ID、填写说明
|
||||
* <p>线上/线下库存挂在每日菜谱行上,不再写入菜品列表 {@code canteen_spu.stock}。
|
||||
* <p>菜谱ID / 菜品SPU ID 必须用文本导出,否则 Excel 对超长雪花 ID 会丢精度导致再导入报「不存在」。
|
||||
* <p>套餐块「菜品SPU ID」=套餐本体一个 ID(首行合并);组成菜靠菜名。单品行=该单品 ID。
|
||||
* <p>无「菜品SPU ID」时一律新建菜品(不按菜名复用);有 ID 才按 ID 更新。
|
||||
* <p>菜谱ID / 菜品SPU ID / 子SPU ID 必须用文本导出,否则 Excel 对超长雪花 ID 会丢精度。
|
||||
* <p>业务列(日期/餐段/套餐名/套餐价/库存等)导出时每行写全,不合并;套餐块仅「菜谱ID」「菜品SPU ID」可合并。
|
||||
* 「子SPU ID」=每行组成菜 ID,不合并。单品行「菜品SPU ID」=该单品,「子SPU ID」可空。
|
||||
* <p>无「菜品SPU ID」时一律新建菜品/套餐;无「子SPU ID」时组成菜按菜名+配料回退或新建。
|
||||
*/
|
||||
@Data
|
||||
public class CanteenDailyMenuImportExcel {
|
||||
@@ -41,14 +42,14 @@ public class CanteenDailyMenuImportExcel {
|
||||
private BigDecimal priceYuan;
|
||||
|
||||
/**
|
||||
* 当日线上可预约库存(挂每日菜谱)。单品填本行;套餐填首行。
|
||||
* 当日线上可预约库存(挂每日菜谱)。单品填本行;套餐组成行建议每行都填相同值。
|
||||
* 不填:默认 600。
|
||||
*/
|
||||
@ExcelProperty(value = "线上库存", index = 7)
|
||||
private Integer onlineStock;
|
||||
|
||||
/**
|
||||
* 当日线下销售库存(挂每日菜谱,安卓消费机读此池)。单品填本行;套餐填首行。
|
||||
* 当日线下销售库存(挂每日菜谱,安卓消费机读此池)。单品填本行;套餐组成行建议每行都填相同值。
|
||||
* 不填:默认 600。
|
||||
*/
|
||||
@ExcelProperty(value = "线下库存", index = 8)
|
||||
@@ -65,13 +66,20 @@ public class CanteenDailyMenuImportExcel {
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 关联菜品 SPU ID(文本)。套餐块仅填首行=套餐本体 ID(与菜谱ID一样合并展示);组成菜靠菜名+配料,不写子菜 ID。
|
||||
* 关联菜品 SPU ID(文本)。套餐块各行填同一套餐本体 ID(导出可合并单元格)。
|
||||
* 单品行=该单品 ID。导入:有则按 ID 更新;无则新建。
|
||||
*/
|
||||
@ExcelProperty(value = "菜品SPU ID", index = 11, converter = com.bsd.pjcloud.canteen.excel.ExcelSnowflakeIdConverter.class)
|
||||
private String spuId;
|
||||
|
||||
/**
|
||||
* 套餐组成菜 SPU ID(文本,每行一个,不合并)。
|
||||
* 有则按 ID 更新/复用组成菜;无则按「菜名+配料」回退或新建。单品行可留空。
|
||||
*/
|
||||
@ExcelProperty(value = "子SPU ID", index = 12, converter = com.bsd.pjcloud.canteen.excel.ExcelSnowflakeIdConverter.class)
|
||||
private String childSpuId;
|
||||
|
||||
/** 仅示例说明用,导入逻辑忽略 */
|
||||
@ExcelProperty(value = "填写说明", index = 12)
|
||||
@ExcelProperty(value = "填写说明", index = 13)
|
||||
private String tip;
|
||||
}
|
||||
+12
@@ -74,6 +74,15 @@ public class CanteenDailyMenuImportPreviewVO {
|
||||
@Schema(description = "菜品SPU ID")
|
||||
private String spuId;
|
||||
|
||||
@Schema(description = "子SPU ID(套餐组成菜)")
|
||||
private String childSpuId;
|
||||
|
||||
/**
|
||||
* 排菜分组键:同一套餐多行组成菜共用,前端按此统计「一道菜」而不是 Excel 行数。
|
||||
*/
|
||||
@Schema(description = "排菜分组键")
|
||||
private String groupKey;
|
||||
|
||||
/** INSERT / UPDATE / SKIP_LOCKED */
|
||||
@Schema(description = "动作")
|
||||
private String action;
|
||||
@@ -99,6 +108,9 @@ public class CanteenDailyMenuImportPreviewVO {
|
||||
private int insertCount;
|
||||
private int updateCount;
|
||||
private int skipLockedCount;
|
||||
/** 排菜道数(套餐整组算 1) */
|
||||
private int rowCount;
|
||||
/** Excel 明细行数(套餐组成菜多行会大于 rowCount) */
|
||||
private int excelRowCount;
|
||||
}
|
||||
}
|
||||
+22
@@ -42,6 +42,7 @@ import com.bsd.pjcloud.canteen.service.CanteenDailyMenuLockLogService;
|
||||
import com.bsd.pjcloud.canteen.service.impl.CanteenDailyMenuImportHelper;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -293,6 +294,27 @@ public class CanteenDailyMenuController {
|
||||
return dailyMenuLockLogService.setDayLock(dto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按硬件订单净销量回填当日线上/线下 sold(配额不动)。
|
||||
* 用于历史 sold 未累计、或怀疑销量漂移时的运维纠偏。
|
||||
*/
|
||||
@Operation(summary = "按订单重算当日分池销量", description = "覆盖 online_sold/offline_sold,不改配额")
|
||||
@SysLog("按订单重算每日菜谱销量")
|
||||
@PutMapping("/syncChannelSold")
|
||||
@PreAuthorize("@pms.hasPermission('canteen_canteenDailyMenu_edit')")
|
||||
public R<Integer> syncChannelSold(@RequestParam("merchantId") Long merchantId,
|
||||
@RequestParam("mealDate") String mealDate) {
|
||||
if (merchantId == null || StrUtil.isBlank(mealDate)) {
|
||||
return R.failed("商户和用餐日不能为空");
|
||||
}
|
||||
try {
|
||||
return canteenDailyMenuService.syncChannelSoldFromOrders(merchantId, LocalDate.parse(mealDate.trim()));
|
||||
}
|
||||
catch (Exception ex) {
|
||||
return R.failed("用餐日格式应为 yyyy-MM-dd");
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "分页查询每日菜谱导入日志")
|
||||
@GetMapping("/import/log/page")
|
||||
@PreAuthorize("@pms.hasPermission('canteen_canteenDailyMenu_view')")
|
||||
|
||||
+120
-44
@@ -12,8 +12,11 @@ import org.apache.poi.ss.usermodel.VerticalAlignment;
|
||||
import org.apache.poi.ss.util.CellRangeAddress;
|
||||
|
||||
/**
|
||||
* 每日菜谱导入模板 / 导出:按数据动态合并日期、餐段、套餐名、套餐价、菜谱ID、套餐SPU、说明。
|
||||
* <p>列:0日期 1餐段 2套餐名 3菜品名 4分类 5单价 6套餐价 7线上库存 8线下库存 9配料 10菜谱ID 11SPU ID 12填写说明
|
||||
* 每日菜谱导入模板 / 导出写后处理。
|
||||
* <p>列:0日期 1餐段 2套餐名 3菜品名 4分类 5单价 6套餐价 7线上库存 8线下库存 9配料
|
||||
* 10菜谱ID 11菜品SPU ID 12子SPU ID 13填写说明
|
||||
* <p>为减少填写混乱:日期/餐段/套餐名/套餐价/库存/说明等<strong>每行写全、不合并</strong>;
|
||||
* 仅套餐块内合并「菜谱ID」「菜品SPU ID」(同一排菜共用)。子SPU ID 每行独立、不合并。
|
||||
*/
|
||||
public class CanteenDailyMenuTemplateMergeHandler implements WorkbookWriteHandler {
|
||||
|
||||
@@ -25,7 +28,7 @@ public class CanteenDailyMenuTemplateMergeHandler implements WorkbookWriteHandle
|
||||
private static final int COL_OFFLINE_STOCK = 8;
|
||||
private static final int COL_ID = 10;
|
||||
private static final int COL_SPU = 11;
|
||||
private static final int COL_TIP = 12;
|
||||
private static final int COL_CHILD_SPU = 12;
|
||||
|
||||
@Override
|
||||
public void afterWorkbookDispose(WriteWorkbookHolder writeWorkbookHolder) {
|
||||
@@ -44,12 +47,12 @@ public class CanteenDailyMenuTemplateMergeHandler implements WorkbookWriteHandle
|
||||
center.setAlignment(HorizontalAlignment.CENTER);
|
||||
center.setVerticalAlignment(VerticalAlignment.CENTER);
|
||||
|
||||
mergeByRuns(sheet, center);
|
||||
fillEveryRowAndMergeIdsOnly(sheet, center);
|
||||
forceIdColumnsAsText(sheet, writeWorkbookHolder);
|
||||
setColumnWidths(sheet);
|
||||
}
|
||||
|
||||
/** 菜谱ID / SPU ID 强制文本格式,降低 Excel 自动改成数字丢精度的概率 */
|
||||
/** 菜谱ID / 菜品SPU / 子SPU 强制文本格式,降低 Excel 自动改成数字丢精度的概率 */
|
||||
private static void forceIdColumnsAsText(Sheet sheet, WriteWorkbookHolder holder) {
|
||||
CellStyle textStyle = holder.getWorkbook().createCellStyle();
|
||||
textStyle.setDataFormat(holder.getWorkbook().createDataFormat().getFormat("@"));
|
||||
@@ -57,7 +60,7 @@ public class CanteenDailyMenuTemplateMergeHandler implements WorkbookWriteHandle
|
||||
textStyle.setVerticalAlignment(VerticalAlignment.CENTER);
|
||||
int lastRow = sheet.getLastRowNum();
|
||||
for (int r = 1; r <= lastRow; r++) {
|
||||
for (int col : new int[] { COL_ID, COL_SPU }) {
|
||||
for (int col : new int[] { COL_ID, COL_SPU, COL_CHILD_SPU }) {
|
||||
Cell cell = getCell(sheet, r, col);
|
||||
if (cell == null) {
|
||||
continue;
|
||||
@@ -73,20 +76,28 @@ public class CanteenDailyMenuTemplateMergeHandler implements WorkbookWriteHandle
|
||||
}
|
||||
|
||||
/**
|
||||
* 向下填充后,按连续相同值合并:餐段 / 套餐块(日期列不合并,每行写全)。
|
||||
* 将套餐续行空白补全为与首行一致的业务值(便于直接改表),并仅合并菜谱ID / 菜品SPU ID。
|
||||
*/
|
||||
private static void mergeByRuns(Sheet sheet, CellStyle center) {
|
||||
private static void fillEveryRowAndMergeIdsOnly(Sheet sheet, CellStyle center) {
|
||||
int lastRow = sheet.getLastRowNum();
|
||||
String[] dates = new String[lastRow + 1];
|
||||
String[] meals = new String[lastRow + 1];
|
||||
String[] combos = new String[lastRow + 1];
|
||||
String[] menuIds = new String[lastRow + 1];
|
||||
String[] spuIds = new String[lastRow + 1];
|
||||
String[] comboPrices = new String[lastRow + 1];
|
||||
String[] onlineStocks = new String[lastRow + 1];
|
||||
String[] offlineStocks = new String[lastRow + 1];
|
||||
boolean[] comboGroup = new boolean[lastRow + 1];
|
||||
|
||||
String lastDate = null;
|
||||
String lastMeal = null;
|
||||
String lastCombo = null;
|
||||
String lastMenuId = null;
|
||||
String lastSpuId = null;
|
||||
String lastComboPrice = null;
|
||||
String lastOnline = null;
|
||||
String lastOffline = null;
|
||||
boolean inCombo = false;
|
||||
|
||||
for (int r = 1; r <= lastRow; r++) {
|
||||
@@ -94,6 +105,10 @@ public class CanteenDailyMenuTemplateMergeHandler implements WorkbookWriteHandle
|
||||
String meal = cellText(sheet, r, COL_MEAL);
|
||||
String combo = cellText(sheet, r, COL_COMBO);
|
||||
String menuId = cellText(sheet, r, COL_ID);
|
||||
String spuId = cellText(sheet, r, COL_SPU);
|
||||
String comboPrice = cellText(sheet, r, COL_COMBO_PRICE);
|
||||
String online = cellText(sheet, r, COL_ONLINE_STOCK);
|
||||
String offline = cellText(sheet, r, COL_OFFLINE_STOCK);
|
||||
|
||||
if (isBlank(date)) {
|
||||
date = lastDate;
|
||||
@@ -113,6 +128,10 @@ public class CanteenDailyMenuTemplateMergeHandler implements WorkbookWriteHandle
|
||||
inCombo = false;
|
||||
lastCombo = null;
|
||||
lastMenuId = null;
|
||||
lastSpuId = null;
|
||||
lastComboPrice = null;
|
||||
lastOnline = null;
|
||||
lastOffline = null;
|
||||
combo = "无";
|
||||
}
|
||||
else {
|
||||
@@ -121,6 +140,18 @@ public class CanteenDailyMenuTemplateMergeHandler implements WorkbookWriteHandle
|
||||
if (!isBlank(menuId)) {
|
||||
lastMenuId = menuId;
|
||||
}
|
||||
if (!isBlank(spuId)) {
|
||||
lastSpuId = spuId;
|
||||
}
|
||||
if (!isBlank(comboPrice)) {
|
||||
lastComboPrice = comboPrice;
|
||||
}
|
||||
if (!isBlank(online)) {
|
||||
lastOnline = online;
|
||||
}
|
||||
if (!isBlank(offline)) {
|
||||
lastOffline = offline;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (inCombo) {
|
||||
@@ -129,40 +160,86 @@ public class CanteenDailyMenuTemplateMergeHandler implements WorkbookWriteHandle
|
||||
menuId = lastMenuId;
|
||||
}
|
||||
else {
|
||||
// 新菜谱 ID 出现,视为新套餐组起点
|
||||
lastMenuId = menuId;
|
||||
}
|
||||
if (isBlank(spuId)) {
|
||||
spuId = lastSpuId;
|
||||
}
|
||||
else {
|
||||
lastSpuId = spuId;
|
||||
}
|
||||
if (isBlank(comboPrice)) {
|
||||
comboPrice = lastComboPrice;
|
||||
}
|
||||
else {
|
||||
lastComboPrice = comboPrice;
|
||||
}
|
||||
if (isBlank(online)) {
|
||||
online = lastOnline;
|
||||
}
|
||||
else {
|
||||
lastOnline = online;
|
||||
}
|
||||
if (isBlank(offline)) {
|
||||
offline = lastOffline;
|
||||
}
|
||||
else {
|
||||
lastOffline = offline;
|
||||
}
|
||||
}
|
||||
else {
|
||||
combo = "无";
|
||||
lastMenuId = null;
|
||||
lastSpuId = null;
|
||||
lastComboPrice = null;
|
||||
lastOnline = null;
|
||||
lastOffline = null;
|
||||
}
|
||||
|
||||
if (inCombo && !isSingleCombo(combo)) {
|
||||
if (isBlank(menuId)) {
|
||||
menuId = lastMenuId;
|
||||
}
|
||||
if (isBlank(spuId)) {
|
||||
spuId = lastSpuId;
|
||||
}
|
||||
if (isBlank(comboPrice)) {
|
||||
comboPrice = lastComboPrice;
|
||||
}
|
||||
if (isBlank(online)) {
|
||||
online = lastOnline;
|
||||
}
|
||||
if (isBlank(offline)) {
|
||||
offline = lastOffline;
|
||||
}
|
||||
}
|
||||
|
||||
dates[r] = date;
|
||||
meals[r] = meal;
|
||||
combos[r] = combo;
|
||||
menuIds[r] = menuId;
|
||||
spuIds[r] = spuId;
|
||||
comboPrices[r] = comboPrice;
|
||||
onlineStocks[r] = online;
|
||||
offlineStocks[r] = offline;
|
||||
comboGroup[r] = inCombo && !isSingleCombo(combo);
|
||||
}
|
||||
|
||||
// 日期列不合并:每行都写入完整日期,避免导入时合并续行读到空
|
||||
// 业务列每行写全,不再合并(避免只改首行/续行读空)
|
||||
for (int r = 1; r <= lastRow; r++) {
|
||||
if (isBlank(dates[r])) {
|
||||
continue;
|
||||
writeText(sheet, r, COL_DATE, dates[r], center);
|
||||
writeText(sheet, r, COL_MEAL, meals[r], center);
|
||||
writeText(sheet, r, COL_COMBO, combos[r], center);
|
||||
if (comboGroup[r]) {
|
||||
writeText(sheet, r, COL_COMBO_PRICE, comboPrices[r], center);
|
||||
writeText(sheet, r, COL_ONLINE_STOCK, onlineStocks[r], center);
|
||||
writeText(sheet, r, COL_OFFLINE_STOCK, offlineStocks[r], center);
|
||||
writeText(sheet, r, COL_ID, menuIds[r], center);
|
||||
writeText(sheet, r, COL_SPU, spuIds[r], center);
|
||||
}
|
||||
Cell cell = getCell(sheet, r, COL_DATE);
|
||||
if (cell == null) {
|
||||
Row row = sheet.getRow(r);
|
||||
if (row == null) {
|
||||
row = sheet.createRow(r);
|
||||
}
|
||||
cell = row.createCell(COL_DATE);
|
||||
}
|
||||
cell.setCellValue(dates[r]);
|
||||
cell.setCellStyle(center);
|
||||
}
|
||||
mergeEqualRuns(sheet, center, lastRow, meals, COL_MEAL, dates, null);
|
||||
|
||||
// 仅套餐块合并菜谱ID、菜品SPU ID
|
||||
int runStart = 1;
|
||||
for (int r = 2; r <= lastRow + 1; r++) {
|
||||
boolean same = r <= lastRow
|
||||
@@ -175,35 +252,21 @@ public class CanteenDailyMenuTemplateMergeHandler implements WorkbookWriteHandle
|
||||
if (!same) {
|
||||
if (comboGroup[runStart] && runStart <= lastRow) {
|
||||
int endRow = Math.min(r - 1, lastRow);
|
||||
mergeRange(sheet, runStart, endRow, COL_COMBO, center);
|
||||
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_ID, center);
|
||||
// 套餐 SPU ID 与菜谱 ID 一样合并:整块一个套餐本体 ID
|
||||
mergeRange(sheet, runStart, endRow, COL_SPU, center);
|
||||
mergeRange(sheet, runStart, endRow, COL_TIP, center);
|
||||
}
|
||||
runStart = r;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void mergeEqualRuns(Sheet sheet, CellStyle center, int lastRow, String[] values, int col,
|
||||
String[] key1, String[] key2) {
|
||||
int runStart = 1;
|
||||
for (int r = 2; r <= lastRow + 1; r++) {
|
||||
boolean same = r <= lastRow
|
||||
&& !isBlank(values[runStart])
|
||||
&& eq(values[r], values[runStart])
|
||||
&& (key1 == null || eq(key1[r], key1[runStart]))
|
||||
&& (key2 == null || eq(key2[r], key2[runStart]));
|
||||
if (!same) {
|
||||
mergeRange(sheet, runStart, Math.min(r - 1, lastRow), col, center);
|
||||
runStart = r;
|
||||
}
|
||||
private static void writeText(Sheet sheet, int rowIdx, int col, String value, CellStyle center) {
|
||||
if (isBlank(value)) {
|
||||
return;
|
||||
}
|
||||
Cell cell = getOrCreateCell(sheet, rowIdx, col);
|
||||
cell.setCellValue(value);
|
||||
cell.setCellStyle(center);
|
||||
}
|
||||
|
||||
private static void mergeRange(Sheet sheet, int firstRow, int lastRow, int col, CellStyle center) {
|
||||
@@ -226,6 +289,18 @@ public class CanteenDailyMenuTemplateMergeHandler implements WorkbookWriteHandle
|
||||
}
|
||||
}
|
||||
|
||||
private static Cell getOrCreateCell(Sheet sheet, int rowIdx, int col) {
|
||||
Row row = sheet.getRow(rowIdx);
|
||||
if (row == null) {
|
||||
row = sheet.createRow(rowIdx);
|
||||
}
|
||||
Cell cell = row.getCell(col);
|
||||
if (cell == null) {
|
||||
cell = row.createCell(col);
|
||||
}
|
||||
return cell;
|
||||
}
|
||||
|
||||
private static Cell getCell(Sheet sheet, int rowIdx, int col) {
|
||||
Row row = sheet.getRow(rowIdx);
|
||||
return row == null ? null : row.getCell(col);
|
||||
@@ -327,6 +402,7 @@ public class CanteenDailyMenuTemplateMergeHandler implements WorkbookWriteHandle
|
||||
sheet.setColumnWidth(9, 22 * 256);
|
||||
sheet.setColumnWidth(10, 20 * 256);
|
||||
sheet.setColumnWidth(11, 20 * 256);
|
||||
sheet.setColumnWidth(12, 48 * 256);
|
||||
sheet.setColumnWidth(12, 20 * 256);
|
||||
sheet.setColumnWidth(13, 48 * 256);
|
||||
}
|
||||
}
|
||||
+13
@@ -13,6 +13,7 @@ import com.bsd.pjcloud.canteen.vo.app.DailyMenuAppVO;
|
||||
import com.bsd.pjcloud.common.core.util.R;
|
||||
import com.bsd.pjcloud.common.security.service.PjcloudUser;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -98,4 +99,16 @@ public interface CanteenDailyMenuService extends IService<CanteenDailyMenuEntity
|
||||
* PC 管理端每日菜谱列表(含库存与订单统计)
|
||||
*/
|
||||
List<DailyMenuAdminVO> selectAdminList(String time, Long merchantId, Long categoryId);
|
||||
|
||||
/**
|
||||
* 按硬件订单净销量回填当日线上/线下 sold(不改配额)。
|
||||
* <p>口径与 hardware {@code listDailyMenuNetSold} 一致:CANTEEN_DAILY_MENU、
|
||||
* 已支付/已完成,扣已完成售后;orderType=2→onlineSold,1→offlineSold。
|
||||
* 当日无对应订单的菜谱行 sold 置 0。
|
||||
*
|
||||
* @param merchantId 食堂商户
|
||||
* @param mealDate 用餐日
|
||||
* @return 实际改写行数
|
||||
*/
|
||||
R<Integer> syncChannelSoldFromOrders(Long merchantId, LocalDate mealDate);
|
||||
}
|
||||
+71
-36
@@ -96,18 +96,19 @@ public class CanteenDailyMenuImportHelper {
|
||||
|
||||
public List<CanteenDailyMenuImportExcel> templateRows() {
|
||||
List<CanteenDailyMenuImportExcel> list = new ArrayList<>();
|
||||
// 业务列每行写全(不靠合并续行);仅菜谱ID/菜品SPU ID 在套餐块内可能合并展示
|
||||
list.add(row("2026-08-17", "午餐", "A套餐", "糖醋排骨", "荤菜", "青莲仔排", "12", "21", "80", "100",
|
||||
"无菜品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/套餐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」;价格填单价列;库存填本行线上/线下"));
|
||||
list.add(row(null, "早餐", "单品-1", "榨菜肉丝面", "面食", "榨菜,肉丝", "6.4", null, "30", "40",
|
||||
"套餐多行:日期/餐段/套餐名/套餐价/库存每行都填;菜品SPU ID=套餐本体;子SPU ID=本行组成菜"));
|
||||
list.add(row("2026-08-17", "午餐", "A套餐", "蒜苔炒腊肉", "荤菜", null, "8", "21", "80", "100", null));
|
||||
list.add(row("2026-08-17", "午餐", "A套餐", "清炒时蔬", "素菜", null, "3", "21", "80", "100", null));
|
||||
list.add(row("2026-08-17", "午餐", "B套餐", "红烧肉", "荤菜", "五花肉、土豆", "10", "18", "60", "80",
|
||||
"换套餐名即另一套;无菜谱ID时新建;有菜谱ID/套餐SPU则更新"));
|
||||
list.add(row("2026-08-17", "午餐", "B套餐", "土豆丝", "素菜", null, "4", "18", "60", "80", null));
|
||||
list.add(row("2026-08-17", "午餐", "无", "千张大骨汤", "汤类", null, "0", null, "50", "50",
|
||||
"单品:套餐名填「无」;菜品SPU ID=本行单品;子SPU ID可空"));
|
||||
list.add(row("2026-08-17", "早餐", "单品-1", "榨菜肉丝面", "面食", "榨菜,肉丝", "6.4", null, "30", "40",
|
||||
"单品-xxx / 单点-xxx 一律按单品,套餐价可留空;不填库存则默认600"));
|
||||
list.add(row(null, null, "单点-2", "豆浆", "饮品", null, "2", null, "100", "100", null));
|
||||
list.add(row("2026-08-17", "早餐", "单点-2", "豆浆", "饮品", null, "2", null, "100", "100", null));
|
||||
list.add(row("2026-08-18", "午餐,晚餐", "无", "番茄蛋汤", "汤类", null, "2", null, "80", "80",
|
||||
"多餐拆行:无菜品SPU ID时各餐段各自新建菜品;每日线上/线下库存各自独立"));
|
||||
return list;
|
||||
@@ -187,6 +188,7 @@ public class CanteenDailyMenuImportHelper {
|
||||
Set<Long> exportedChildIds = new HashSet<>();
|
||||
int written = 0;
|
||||
String comboSpuId = menu.getSpuId() == null ? null : String.valueOf(menu.getSpuId());
|
||||
String menuIdStr = menu.getId() == null ? null : String.valueOf(menu.getId());
|
||||
for (OnlineSpuComboItemVO item : items) {
|
||||
if (item == null) {
|
||||
continue;
|
||||
@@ -195,22 +197,24 @@ public class CanteenDailyMenuImportHelper {
|
||||
if (childId != null && !exportedChildIds.add(childId)) {
|
||||
continue;
|
||||
}
|
||||
// 套餐组成行:业务列每行写全,避免依赖合并单元格;子SPU 仍各自一行
|
||||
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());
|
||||
}
|
||||
// 套餐只导出一个套餐 SPU;配料列按组成菜各自导出(同名不同配料靠此区分)
|
||||
row.setId(menuIdStr);
|
||||
row.setSpuId(comboSpuId);
|
||||
row.setBizDate(dateStr);
|
||||
row.setMealName(meal);
|
||||
row.setComboName(comboLabel);
|
||||
row.setPriceYuan(comboPrice);
|
||||
row.setOnlineStock(menu.getOnlineStock());
|
||||
row.setOfflineStock(menu.getOfflineStock());
|
||||
row.setChildSpuId(childId == null ? null : String.valueOf(childId));
|
||||
row.setIngredients(toExcelIngredients(item.getIngredients()));
|
||||
row.setCategoryName(menu.getCategoryName());
|
||||
row.setDishName(StrUtil.blankToDefault(item.getName(), ""));
|
||||
row.setDishPriceYuan(fenToYuan(item.getPrice()));
|
||||
if (written == 0) {
|
||||
row.setTip("套餐块:同套餐名多行=一道排菜;仅菜谱ID/菜品SPU可合并展示");
|
||||
}
|
||||
rows.add(row);
|
||||
written++;
|
||||
}
|
||||
@@ -634,6 +638,7 @@ public class CanteenDailyMenuImportHelper {
|
||||
|
||||
for (Map.Entry<String, List<CanteenDailyMenuImportExcel>> e : groups.entrySet()) {
|
||||
List<CanteenDailyMenuImportExcel> items = e.getValue();
|
||||
String groupKey = e.getKey();
|
||||
CanteenDailyMenuImportExcel firstRow = items.get(0);
|
||||
LocalDate groupDate = parseDate(firstRow.getBizDate());
|
||||
Integer groupHour = parseMeal(firstRow.getMealName());
|
||||
@@ -663,7 +668,7 @@ public class CanteenDailyMenuImportHelper {
|
||||
actionLabel = "已锁定跳过";
|
||||
tip = dateStr + " 已锁定,确认导入时不会写入";
|
||||
selected = false;
|
||||
totalSkip += items.size();
|
||||
totalSkip++;
|
||||
}
|
||||
else if (isUpdate) {
|
||||
action = "UPDATE";
|
||||
@@ -686,9 +691,11 @@ public class CanteenDailyMenuImportHelper {
|
||||
s.setLocked(lockedDates.contains(d));
|
||||
return s;
|
||||
});
|
||||
day.setRowCount(day.getRowCount() + items.size());
|
||||
// 按排菜组计数(套餐多行组成菜只算 1 道),便于核对当日新增/更新数量
|
||||
day.setRowCount(day.getRowCount() + 1);
|
||||
day.setExcelRowCount(day.getExcelRowCount() + items.size());
|
||||
if (dayLocked) {
|
||||
day.setSkipLockedCount(day.getSkipLockedCount() + items.size());
|
||||
day.setSkipLockedCount(day.getSkipLockedCount() + 1);
|
||||
}
|
||||
else if (isUpdate) {
|
||||
day.setUpdateCount(day.getUpdateCount() + 1);
|
||||
@@ -714,6 +721,8 @@ public class CanteenDailyMenuImportHelper {
|
||||
previewRow.setIngredients(StrUtil.trim(item.getIngredients()));
|
||||
previewRow.setId(StrUtil.trim(item.getId()));
|
||||
previewRow.setSpuId(StrUtil.trim(item.getSpuId()));
|
||||
previewRow.setChildSpuId(StrUtil.trim(item.getChildSpuId()));
|
||||
previewRow.setGroupKey(groupKey);
|
||||
previewRow.setAction(action);
|
||||
previewRow.setActionLabel(actionLabel);
|
||||
previewRow.setLocked(dayLocked);
|
||||
@@ -897,8 +906,8 @@ public class CanteenDailyMenuImportHelper {
|
||||
if (StrUtil.isBlank(cleanDishName(item.getDishName())) || !seenDishKeys.add(dishKey)) {
|
||||
continue;
|
||||
}
|
||||
Long childSpuId = resolveComboChildExcelSpuId(item.getSpuId(), comboExcelSpuId, uniformSpu,
|
||||
existChildByKey.get(dishKey), cache);
|
||||
Long childSpuId = resolveComboChildExcelSpuId(item.getChildSpuId(), item.getSpuId(), comboExcelSpuId,
|
||||
uniformSpu, existChildByKey.get(dishKey), cache);
|
||||
FindOrCreate r = findOrCreateDish(merchantId, deptId,
|
||||
item.getCategoryName(), categoryId, categoryMap,
|
||||
item.getDishName(), item.getIngredients(), resolveDishPriceFen(item), null, childSpuId,
|
||||
@@ -1144,6 +1153,9 @@ public class CanteenDailyMenuImportHelper {
|
||||
if (isNumericCorruptId(row.getSpuId())) {
|
||||
bad.add("第" + line + "行「菜品SPU ID」");
|
||||
}
|
||||
if (isNumericCorruptId(row.getChildSpuId())) {
|
||||
bad.add("第" + line + "行「子SPU ID」");
|
||||
}
|
||||
if (bad.size() >= 8) {
|
||||
break;
|
||||
}
|
||||
@@ -1154,7 +1166,7 @@ public class CanteenDailyMenuImportHelper {
|
||||
throw new IllegalArgumentException(
|
||||
"检测到雪花 ID 被 Excel 当成数字保存(会丢末位精度,常见感叹号警告、位数变短)。\n"
|
||||
+ "这会导致套餐/单品串组,并报「不是套餐,套餐行请填写套餐 SPU ID」。\n"
|
||||
+ "请重新从系统导出后再导入,不要把「菜谱ID」「菜品SPU ID」列改成数字格式。\n"
|
||||
+ "请重新从系统导出后再导入,不要把「菜谱ID」「菜品SPU ID」「子SPU ID」列改成数字格式。\n"
|
||||
+ "问题位置:" + String.join("、", bad));
|
||||
}
|
||||
|
||||
@@ -1556,8 +1568,8 @@ public class CanteenDailyMenuImportHelper {
|
||||
if (!seenDishKeys.add(dishKey)) {
|
||||
continue;
|
||||
}
|
||||
Long childSpuId = resolveComboChildExcelSpuId(item.getSpuId(), comboExcelSpuId, uniformSpu,
|
||||
existChildByKey.get(dishKey), cache);
|
||||
Long childSpuId = resolveComboChildExcelSpuId(item.getChildSpuId(), item.getSpuId(), comboExcelSpuId,
|
||||
uniformSpu, existChildByKey.get(dishKey), cache);
|
||||
FindOrCreate r = findOrCreateDish(merchantId, deptId,
|
||||
item.getCategoryName(), defaultCategoryId, categoryMap, dish,
|
||||
item.getIngredients(), resolveDishPriceFen(item), null, childSpuId, cache);
|
||||
@@ -1636,11 +1648,25 @@ public class CanteenDailyMenuImportHelper {
|
||||
}
|
||||
|
||||
/**
|
||||
* 组成菜 SPU:套餐块统一填套餐本体 ID 时不按该 ID 建子菜;
|
||||
* 组成菜 SPU:优先「子SPU ID」列;兼容旧表把组成菜 ID 写在「菜品SPU ID」且非整块统一套餐 ID 的情况;
|
||||
* 可回退到已有套餐组成中「菜名+配料」完全一致的子菜 ID。
|
||||
*/
|
||||
private Long resolveComboChildExcelSpuId(String rowSpuId, Long comboSpuId, boolean uniformSpu,
|
||||
private Long resolveComboChildExcelSpuId(String childSpuIdRaw, String rowSpuId, Long comboSpuId, boolean uniformSpu,
|
||||
Long existChildByKey, ImportCache cache) {
|
||||
Long fromChildCol = parseExcelLong(childSpuIdRaw);
|
||||
if (fromChildCol != null) {
|
||||
if (Objects.equals(fromChildCol, comboSpuId)) {
|
||||
throw new IllegalArgumentException("「子SPU ID」不能填套餐本体 ID,请填组成菜的单品 SPU ID");
|
||||
}
|
||||
CanteenSpuEntity exist = resolveSpuById(fromChildCol, cache);
|
||||
if (exist != null) {
|
||||
int type = exist.getSpuType() == null ? 1 : exist.getSpuType();
|
||||
if (type == 2) {
|
||||
throw new IllegalArgumentException("「子SPU ID」" + fromChildCol + " 是套餐,组成菜必须是单品");
|
||||
}
|
||||
}
|
||||
return fromChildCol;
|
||||
}
|
||||
if (uniformSpu) {
|
||||
return existChildByKey;
|
||||
}
|
||||
@@ -1717,7 +1743,7 @@ public class CanteenDailyMenuImportHelper {
|
||||
|
||||
/**
|
||||
* 解析/创建单品。
|
||||
* <p>有「菜品SPU ID」→ 按 ID 复用并按需更新;无 ID → 一律新建,不再按菜名复用。
|
||||
* <p>有「菜品SPU ID」/「子SPU ID」→ 按 ID 复用并按需更新名称、配料、价格、分类;无 ID → 一律新建,不再按菜名复用。
|
||||
* <p>{@code stock} 仅在显式传入时更新 {@code canteen_spu.stock};每日菜谱导入传 {@code null},
|
||||
* 当日可售 = 配额(online_stock/offline_stock) − 销量;导入只写配额,不改销量。
|
||||
*/
|
||||
@@ -1737,7 +1763,7 @@ public class CanteenDailyMenuImportHelper {
|
||||
if (type == 2) {
|
||||
throw new IllegalArgumentException("菜品SPU ID " + excelSpuId + " 是套餐,单品行请填写单品 SPU ID");
|
||||
}
|
||||
applyDishUpdates(exist, categoryName, defaultCategoryId, categoryMap, merchantId, deptId, ingredients,
|
||||
applyDishUpdates(exist, clean, categoryName, defaultCategoryId, categoryMap, merchantId, deptId, ingredients,
|
||||
priceFen, stock);
|
||||
return new FindOrCreate(exist, false);
|
||||
}
|
||||
@@ -1758,14 +1784,19 @@ public class CanteenDailyMenuImportHelper {
|
||||
return new FindOrCreate(spu, true);
|
||||
}
|
||||
|
||||
private void applyDishUpdates(CanteenSpuEntity exist, String categoryName, Long defaultCategoryId,
|
||||
private void applyDishUpdates(CanteenSpuEntity exist, String cleanName, String categoryName, Long defaultCategoryId,
|
||||
Map<String, Long> categoryMap, Long merchantId, Long deptId, String ingredients, Integer priceFen,
|
||||
Integer stock) {
|
||||
String importIngredients = normalizeImportIngredients(ingredients);
|
||||
boolean changeName = false;
|
||||
boolean changeDesc = false;
|
||||
boolean changePrice = false;
|
||||
boolean changeStock = false;
|
||||
boolean changeCategory = false;
|
||||
if (StrUtil.isNotBlank(cleanName) && !cleanName.equals(StrUtil.trim(exist.getName()))) {
|
||||
exist.setName(cleanName);
|
||||
changeName = true;
|
||||
}
|
||||
if (StrUtil.isNotBlank(importIngredients)) {
|
||||
String existPlain = toPlainIngredients(exist.getDescription());
|
||||
if (!importIngredients.equals(existPlain)) {
|
||||
@@ -1789,8 +1820,11 @@ public class CanteenDailyMenuImportHelper {
|
||||
changeCategory = true;
|
||||
}
|
||||
}
|
||||
if (changeDesc || changePrice || changeStock || changeCategory) {
|
||||
if (changeName || changeDesc || changePrice || changeStock || changeCategory) {
|
||||
var uw = Wrappers.<CanteenSpuEntity>lambdaUpdate().eq(CanteenSpuEntity::getId, exist.getId());
|
||||
if (changeName) {
|
||||
uw.set(CanteenSpuEntity::getName, exist.getName());
|
||||
}
|
||||
if (changeDesc) {
|
||||
uw.set(CanteenSpuEntity::getDescription, exist.getDescription());
|
||||
}
|
||||
@@ -2093,7 +2127,7 @@ public class CanteenDailyMenuImportHelper {
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并单元格模板读入后,非首行日期/餐段/套餐名/分类为空,需向下填充。
|
||||
* 合并单元格旧模板读入后,非首行日期/餐段/套餐名/分类为空,需向下填充(新导出已每行写全,仍兼容旧表)。
|
||||
* 套餐名填「无」「单品」「单点」或「单品-xxx / 单点-xxx」表示单品,不参与套餐继承。
|
||||
* <p>换日/换餐段时必须清空菜谱ID继承,否则「同名套餐」跨天会被合成一组,组成菜看起来像重复。
|
||||
*/
|
||||
@@ -2372,6 +2406,7 @@ public class CanteenDailyMenuImportHelper {
|
||||
copy.setIngredients(src.getIngredients());
|
||||
copy.setId(src.getId());
|
||||
copy.setSpuId(src.getSpuId());
|
||||
copy.setChildSpuId(src.getChildSpuId());
|
||||
copy.setTip(src.getTip());
|
||||
return copy;
|
||||
}
|
||||
|
||||
+77
@@ -7,6 +7,7 @@ import com.alibaba.nacos.common.utils.CollectionUtils;
|
||||
import com.baomidou.lock.LockInfo;
|
||||
import com.baomidou.lock.LockTemplate;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.bsd.pjcloud.canteen.dto.CanteenDailyMenuCVDTO;
|
||||
@@ -28,6 +29,10 @@ import com.bsd.pjcloud.canteen.vo.app.DailyMenuAppVO;
|
||||
import com.bsd.pjcloud.common.core.util.R;
|
||||
import com.bsd.pjcloud.common.data.datascope.DataScope;
|
||||
import com.bsd.pjcloud.common.security.service.PjcloudUser;
|
||||
import com.bsd.pjcloud.hard.api.constant.order.HardOrderTypeEnum;
|
||||
import com.bsd.pjcloud.hard.api.dto.order.remote.DailyMenuNetSoldQueryDTO;
|
||||
import com.bsd.pjcloud.hard.api.feign.RemoteHardOrderService;
|
||||
import com.bsd.pjcloud.hard.api.vo.order.DailyMenuNetSoldVO;
|
||||
import com.bsd.pjcloud.mqtt.api.feign.RemoteMqttEventService;
|
||||
import com.bsd.pjcloud.wallet.api.feign.RemoteMchService;
|
||||
import com.bsd.pjcloud.wallet.api.vo.MchManageVo;
|
||||
@@ -71,6 +76,8 @@ public class CanteenDailyMenuServiceImpl extends ServiceImpl<CanteenDailyMenuMap
|
||||
|
||||
private final CanteenDailyMenuStockSupport dailyMenuStockSupport;
|
||||
|
||||
private final RemoteHardOrderService remoteHardOrderService;
|
||||
|
||||
/**
|
||||
* 按逻辑删除后的当前数据批量校验日期、餐段和 SPU 组合,避免 App 页面缓存仍包含已下架菜品。
|
||||
* 基础 SPU 的全局上下架、归属和库存由 Hardware 下单服务继续按现有逻辑独立复核。
|
||||
@@ -483,6 +490,76 @@ public class CanteenDailyMenuServiceImpl extends ServiceImpl<CanteenDailyMenuMap
|
||||
return rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按订单净销量覆盖当日 {@code online_sold}/{@code offline_sold},配额字段不动。
|
||||
* 无订单命中的菜谱行销量置 0,用于历史 sold 未累计时的运维回填。
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<Integer> syncChannelSoldFromOrders(Long merchantId, LocalDate mealDate) {
|
||||
if (merchantId == null || mealDate == null) {
|
||||
return R.failed("商户和用餐日不能为空");
|
||||
}
|
||||
List<CanteenDailyMenuEntity> menus = list(Wrappers.<CanteenDailyMenuEntity>lambdaQuery()
|
||||
.eq(CanteenDailyMenuEntity::getMerchantId, merchantId)
|
||||
.eq(CanteenDailyMenuEntity::getStartTime, mealDate));
|
||||
if (CollectionUtils.isEmpty(menus)) {
|
||||
return R.ok(0, "当日无菜谱记录");
|
||||
}
|
||||
|
||||
DailyMenuNetSoldQueryDTO query = new DailyMenuNetSoldQueryDTO();
|
||||
query.setMerchantId(merchantId);
|
||||
query.setMealDate(mealDate);
|
||||
R<List<DailyMenuNetSoldVO>> soldR = remoteHardOrderService.listDailyMenuNetSold(query);
|
||||
if (soldR == null || !soldR.isOk()) {
|
||||
String tip = soldR == null ? "订单服务无响应" : soldR.getMsg();
|
||||
return R.failed(StrUtil.blankToDefault(tip, "查询订单净销量失败"));
|
||||
}
|
||||
List<DailyMenuNetSoldVO> netRows = soldR.getData() == null ? List.of() : soldR.getData();
|
||||
|
||||
// key = hourType|spuId → [onlineSold, offlineSold]
|
||||
Map<String, int[]> soldByHourProduct = new HashMap<>();
|
||||
for (DailyMenuNetSoldVO row : netRows) {
|
||||
if (row == null || row.getProductId() == null || StrUtil.isBlank(row.getHourType())) {
|
||||
continue;
|
||||
}
|
||||
String key = row.getHourType().trim() + "|" + row.getProductId();
|
||||
int[] pair = soldByHourProduct.computeIfAbsent(key, ignored -> new int[] { 0, 0 });
|
||||
int qty = row.getNetSold() == null ? 0 : Math.max(0, row.getNetSold());
|
||||
if (HardOrderTypeEnum.RESERVE.getType().equals(row.getOrderType())) {
|
||||
pair[0] += qty;
|
||||
}
|
||||
else if (HardOrderTypeEnum.BELOW.getType().equals(row.getOrderType())) {
|
||||
pair[1] += qty;
|
||||
}
|
||||
}
|
||||
|
||||
List<CanteenDailyMenuEntity> toUpdate = new ArrayList<>();
|
||||
for (CanteenDailyMenuEntity menu : menus) {
|
||||
if (menu.getHourType() == null || menu.getSpuId() == null) {
|
||||
continue;
|
||||
}
|
||||
String key = menu.getHourType() + "|" + menu.getSpuId();
|
||||
int[] pair = soldByHourProduct.getOrDefault(key, new int[] { 0, 0 });
|
||||
int onlineSold = pair[0];
|
||||
int offlineSold = pair[1];
|
||||
int curOnline = CanteenDailyMenuStockSupport.soldOrDefault(menu.getOnlineSold());
|
||||
int curOffline = CanteenDailyMenuStockSupport.soldOrDefault(menu.getOfflineSold());
|
||||
if (curOnline == onlineSold && curOffline == offlineSold) {
|
||||
continue;
|
||||
}
|
||||
menu.setOnlineSold(onlineSold);
|
||||
menu.setOfflineSold(offlineSold);
|
||||
toUpdate.add(menu);
|
||||
}
|
||||
if (CollUtil.isNotEmpty(toUpdate)) {
|
||||
updateBatchById(toUpdate);
|
||||
}
|
||||
log.info("每日菜谱按订单重算销量 merchantId={} mealDate={} menuRows={} updated={}",
|
||||
merchantId, mealDate, menus.size(), toUpdate.size());
|
||||
return R.ok(toUpdate.size(), "已按订单重算 " + toUpdate.size() + " 条销量(共 " + menus.size() + " 条菜谱)");
|
||||
}
|
||||
|
||||
private static String weekdayLabel(DayOfWeek dayOfWeek) {
|
||||
return switch (dayOfWeek) {
|
||||
case MONDAY -> "周一";
|
||||
|
||||
Reference in new issue
Block a user