feat: 各模块支持导入导出及用户列表优化
* canteen - 每日菜谱:支持模板下载与数据导出 - 菜品列表:支持模板下载与数据导出 * umps - 用户管理:筛选条件增加「启用状态」 - 用户列表导出:新增「启用状态」字段 fix(canteen): 修复管理员维护其他商户时的部门归属问题 管理员账号维护非本商户数据时,部门统一归属到当前商户部门, 避免越权及数据归属混乱。
This commit is contained in:
1 parent
3989a4f95b
commit
deb28ffb22
28 files changed
+2402
-67
No files matched your search
+40
@@ -0,0 +1,40 @@
|
||||
package com.bsd.pjcloud.canteen.dto;
|
||||
|
||||
import com.alibaba.excel.annotation.ExcelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 每日菜谱规范导入模板行
|
||||
*/
|
||||
@Data
|
||||
public class CanteenDailyMenuImportExcel {
|
||||
|
||||
@ExcelProperty(value = "日期", index = 0)
|
||||
private String bizDate;
|
||||
|
||||
@ExcelProperty(value = "餐段", index = 1)
|
||||
private String mealName;
|
||||
|
||||
@ExcelProperty(value = "套餐名", index = 2)
|
||||
private String comboName;
|
||||
|
||||
@ExcelProperty(value = "菜品名", index = 3)
|
||||
private String dishName;
|
||||
|
||||
@ExcelProperty(value = "配料", index = 4)
|
||||
private String ingredients;
|
||||
|
||||
/** 菜品自身单价;不填默认 0 */
|
||||
@ExcelProperty(value = "菜品单价(元)", index = 5)
|
||||
private BigDecimal dishPriceYuan;
|
||||
|
||||
/** 套餐售价(套餐首行填写,可与套餐名合并);单品可留空,用「菜品单价」 */
|
||||
@ExcelProperty(value = "套餐价(元)", index = 6)
|
||||
private BigDecimal priceYuan;
|
||||
|
||||
/** 仅示例说明用,导入逻辑忽略 */
|
||||
@ExcelProperty(value = "填写说明", index = 7)
|
||||
private String tip;
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package com.bsd.pjcloud.canteen.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 菜品导出筛选(POST body,商户/分类 ID 用字符串避免前端雪花精度丢失)
|
||||
*/
|
||||
@Data
|
||||
public class CanteenSpuExportQuery {
|
||||
|
||||
private String name;
|
||||
|
||||
private String categoryId;
|
||||
|
||||
private String merchantId;
|
||||
|
||||
/** 勾选导出时的 SPU id */
|
||||
private List<String> ids;
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package com.bsd.pjcloud.canteen.dto;
|
||||
|
||||
import com.alibaba.excel.annotation.ExcelProperty;
|
||||
import com.bsd.pjcloud.canteen.excel.ExcelYuanConverter;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 菜品列表 Excel 导入/导出/模板行(单品 + 套餐)
|
||||
* <p>套餐名留空或「无」=单品;套餐名相同多行=同一套餐的组成菜。
|
||||
* <p>导出含「菜品ID/套餐ID」,覆盖导入优先按 ID 更新,避免同名重复新增。
|
||||
*/
|
||||
@Data
|
||||
public class CanteenSpuImportExcel {
|
||||
|
||||
@ExcelProperty(value = "套餐名", index = 0)
|
||||
private String comboName;
|
||||
|
||||
@ExcelProperty(value = "菜品名称", index = 1)
|
||||
private String name;
|
||||
|
||||
@ExcelProperty(value = "分类名称", index = 2)
|
||||
private String categoryName;
|
||||
|
||||
@ExcelProperty(value = "菜品单价(元)", index = 3, converter = ExcelYuanConverter.class)
|
||||
private BigDecimal priceYuan;
|
||||
|
||||
@ExcelProperty(value = "套餐价(元)", index = 4, converter = ExcelYuanConverter.class)
|
||||
private BigDecimal comboPriceYuan;
|
||||
|
||||
@ExcelProperty(value = "市场价(元)", index = 5, converter = ExcelYuanConverter.class)
|
||||
private BigDecimal marketPriceYuan;
|
||||
|
||||
@ExcelProperty(value = "库存", index = 6)
|
||||
private Integer stock;
|
||||
|
||||
@ExcelProperty(value = "排序", index = 7)
|
||||
private Integer sort;
|
||||
|
||||
@ExcelProperty(value = "上架状态", index = 8)
|
||||
private String statusName;
|
||||
|
||||
@ExcelProperty(value = "移动端显示", index = 9)
|
||||
private String isShowName;
|
||||
|
||||
@ExcelProperty(value = "配料/详情", index = 10)
|
||||
private String description;
|
||||
|
||||
@ExcelProperty(value = "填写说明", index = 11)
|
||||
private String tip;
|
||||
|
||||
/** 单品ID,或套餐组成菜的子菜品ID(导出为文本,勿改) */
|
||||
@ExcelProperty(value = "菜品ID", index = 12)
|
||||
private String dishId;
|
||||
|
||||
/** 套餐SPU ID(仅套餐行;导出为文本,勿改) */
|
||||
@ExcelProperty(value = "套餐ID", index = 13)
|
||||
private String comboId;
|
||||
}
|
||||
+5
-6
@@ -60,6 +60,7 @@ public class CanteenSpuEntity extends Model<CanteenSpuEntity> {
|
||||
/**
|
||||
* 商品封面图
|
||||
*/
|
||||
@TableField(insertStrategy = FieldStrategy.ALWAYS, updateStrategy = FieldStrategy.ALWAYS)
|
||||
@Schema(description="商品封面图")
|
||||
private String picUrl;
|
||||
|
||||
@@ -78,9 +79,9 @@ public class CanteenSpuEntity extends Model<CanteenSpuEntity> {
|
||||
private Integer sort;
|
||||
|
||||
/**
|
||||
* 商品状态: 1 上架(开启) 0 下架(禁用) -1 回收
|
||||
* 商品状态: 0 上架(开启) 1 下架(禁用) -1 回收
|
||||
*/
|
||||
@Schema(description="商品状态: 1 上架(开启) 0 下架(禁用) -1 回收")
|
||||
@Schema(description="商品状态: 0 上架(开启) 1 下架(禁用) -1 回收")
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
@@ -98,17 +99,15 @@ public class CanteenSpuEntity extends Model<CanteenSpuEntity> {
|
||||
/**
|
||||
* 商品价格,单位使用:分
|
||||
*/
|
||||
@TableField(updateStrategy = FieldStrategy.ALWAYS)
|
||||
@Schema(description="商品价格,单位使用:分")
|
||||
// @JsonSerialize(using = MoneySerializer.class) // 分 -> 元
|
||||
// @JsonDeserialize(using = MoneyDeserializer.class) // 元 -> 分
|
||||
private Integer price;
|
||||
|
||||
/**
|
||||
* 市场价,单位使用:分
|
||||
*/
|
||||
@TableField(updateStrategy = FieldStrategy.ALWAYS)
|
||||
@Schema(description="市场价,单位使用:分")
|
||||
// @JsonSerialize(using = MoneySerializer.class) // 分 -> 元
|
||||
// @JsonDeserialize(using = MoneyDeserializer.class) // 元 -> 分
|
||||
private Integer marketPrice;
|
||||
|
||||
/**
|
||||
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
package com.bsd.pjcloud.canteen.excel;
|
||||
|
||||
import com.alibaba.excel.converters.Converter;
|
||||
import com.alibaba.excel.enums.CellDataTypeEnum;
|
||||
import com.alibaba.excel.metadata.GlobalConfiguration;
|
||||
import com.alibaba.excel.metadata.data.ReadCellData;
|
||||
import com.alibaba.excel.metadata.data.WriteCellData;
|
||||
import com.alibaba.excel.metadata.property.ExcelContentProperty;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* Excel 金额(元):兼容数字单元格与文本单元格。
|
||||
* <p>字段上指定本 converter 后,按单元格实际类型解析,不依赖 supportExcelTypeKey 匹配。
|
||||
*/
|
||||
public class ExcelYuanConverter implements Converter<BigDecimal> {
|
||||
|
||||
@Override
|
||||
public Class<BigDecimal> supportJavaTypeKey() {
|
||||
return BigDecimal.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CellDataTypeEnum supportExcelTypeKey() {
|
||||
// 注册表用;字段注解指定本类时仍会直接调用 convertToJavaData
|
||||
return CellDataTypeEnum.NUMBER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty,
|
||||
GlobalConfiguration globalConfiguration) {
|
||||
if (cellData == null || cellData.getType() == null || cellData.getType() == CellDataTypeEnum.EMPTY) {
|
||||
return null;
|
||||
}
|
||||
if (cellData.getType() == CellDataTypeEnum.NUMBER && cellData.getNumberValue() != null) {
|
||||
return cellData.getNumberValue();
|
||||
}
|
||||
if (cellData.getType() == CellDataTypeEnum.BOOLEAN && cellData.getBooleanValue() != null) {
|
||||
return cellData.getBooleanValue() ? BigDecimal.ONE : BigDecimal.ZERO;
|
||||
}
|
||||
String raw = cellData.getStringValue();
|
||||
if ((raw == null || raw.trim().isEmpty()) && cellData.getNumberValue() != null) {
|
||||
return cellData.getNumberValue();
|
||||
}
|
||||
if (raw == null || raw.trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
String n = raw.trim().replace(",", "").replace("¥", "").replace("¥", "").replace("元", "");
|
||||
if (n.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return new BigDecimal(n);
|
||||
}
|
||||
catch (NumberFormatException ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public WriteCellData<?> convertToExcelData(BigDecimal value, ExcelContentProperty contentProperty,
|
||||
GlobalConfiguration globalConfiguration) {
|
||||
if (value == null) {
|
||||
return new WriteCellData<>("");
|
||||
}
|
||||
return new WriteCellData<>(value.stripTrailingZeros().toPlainString());
|
||||
}
|
||||
}
|
||||
+6
-5
@@ -15,7 +15,6 @@ import com.bsd.pjcloud.common.log.annotation.SysLog;
|
||||
import com.bsd.pjcloud.canteen.entity.BoxesInfoEntity;
|
||||
import com.bsd.pjcloud.canteen.service.BoxesInfoService;
|
||||
import com.bsd.pjcloud.canteen.util.CanteenMerchantScopeHelper;
|
||||
import com.bsd.pjcloud.common.security.util.SecurityUtils;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
@@ -96,11 +95,13 @@ public class BoxesInfoController {
|
||||
@PostMapping
|
||||
@PreAuthorize("@pms.hasPermission('canteen_boxesInfo_add')")
|
||||
public R save(@RequestBody BoxesInfoEntity boxesInfo) {
|
||||
merchantScopeHelper.fillDeptIdByMerchantId(boxesInfo.getMerchantId(), boxesInfo::setDeptId);
|
||||
if (boxesInfo.getDeptId() == null) {
|
||||
boxesInfo.setDeptId(SecurityUtils.getUser().getDeptId());
|
||||
try {
|
||||
merchantScopeHelper.fillDeptIdByMerchantId(boxesInfo.getMerchantId(), boxesInfo::setDeptId);
|
||||
return R.ok(boxesInfoService.saveInfo(boxesInfo));
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
return R.failed(ex.getMessage());
|
||||
}
|
||||
return R.ok(boxesInfoService.saveInfo(boxesInfo));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+12
-2
@@ -83,7 +83,12 @@ public class CanteenCategoryController {
|
||||
@PostMapping
|
||||
@PreAuthorize("@pms.hasPermission('canteen_canteenCategory_add')")
|
||||
public R save(@RequestBody CanteenCategoryEntity canteenCategory) {
|
||||
return R.ok(canteenCategoryService.create(canteenCategory));
|
||||
try {
|
||||
return R.ok(canteenCategoryService.create(canteenCategory));
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
return R.failed(ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -97,7 +102,12 @@ public class CanteenCategoryController {
|
||||
@PutMapping
|
||||
@PreAuthorize("@pms.hasPermission('canteen_canteenCategory_edit')")
|
||||
public R updateById(@RequestBody CanteenCategoryEntity canteenCategory) {
|
||||
return R.ok(canteenCategoryService.edit(canteenCategory));
|
||||
try {
|
||||
return R.ok(canteenCategoryService.edit(canteenCategory));
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
return R.failed(ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+46
-5
@@ -19,8 +19,7 @@ import com.bsd.pjcloud.common.core.util.R;
|
||||
import com.bsd.pjcloud.common.log.annotation.SysLog;
|
||||
import com.bsd.pjcloud.canteen.entity.CanteenDailyMenuEntity;
|
||||
import com.bsd.pjcloud.canteen.service.CanteenDailyMenuService;
|
||||
import com.bsd.pjcloud.common.security.service.PjcloudUser;
|
||||
import com.bsd.pjcloud.common.security.util.SecurityUtils;
|
||||
import com.bsd.pjcloud.canteen.util.CanteenMerchantScopeHelper;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import com.bsd.pjcloud.common.excel.annotation.ResponseExcel;
|
||||
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
||||
@@ -32,6 +31,11 @@ import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import com.bsd.pjcloud.canteen.dto.CanteenDailyMenuImportExcel;
|
||||
import com.bsd.pjcloud.canteen.excel.CanteenDailyMenuTemplateMergeHandler;
|
||||
import com.bsd.pjcloud.canteen.service.impl.CanteenDailyMenuImportHelper;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -51,6 +55,10 @@ public class CanteenDailyMenuController {
|
||||
|
||||
private final CanteenSpuService canteenSpuService;
|
||||
|
||||
private final CanteenDailyMenuImportHelper dailyMenuImportHelper;
|
||||
|
||||
private final CanteenMerchantScopeHelper merchantScopeHelper;
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
*
|
||||
@@ -67,6 +75,17 @@ public class CanteenDailyMenuController {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 下载导入模板(须放在 /{id} 之前,避免被路径变量误匹配)
|
||||
*/
|
||||
@Operation(summary = "下载每日菜谱导入模板")
|
||||
@ResponseExcel(name = "每日菜谱导入模板", writeHandler = CanteenDailyMenuTemplateMergeHandler.class)
|
||||
@GetMapping("/import/template")
|
||||
@PreAuthorize("@pms.hasPermission('canteen_canteenDailyMenu_add')")
|
||||
public List<CanteenDailyMenuImportExcel> downloadImportTemplate() {
|
||||
return dailyMenuImportHelper.templateRows();
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过id查询每日菜谱
|
||||
*
|
||||
@@ -91,9 +110,14 @@ public class CanteenDailyMenuController {
|
||||
@PostMapping
|
||||
@PreAuthorize("@pms.hasPermission('canteen_canteenDailyMenu_add')")
|
||||
public R save(@RequestBody CanteenDailyMenuEntity canteenDailyMenu) {
|
||||
canteenDailyMenu.setDeptId(SecurityUtils.getUser().getDeptId());
|
||||
canteenDailyMenuService.saveToday(canteenDailyMenu);
|
||||
return R.ok();
|
||||
try {
|
||||
merchantScopeHelper.fillDeptIdByMerchantId(canteenDailyMenu.getMerchantId(), canteenDailyMenu::setDeptId);
|
||||
canteenDailyMenuService.saveToday(canteenDailyMenu);
|
||||
return R.ok();
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
return R.failed(ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -118,6 +142,23 @@ public class CanteenDailyMenuController {
|
||||
public R cv(@RequestBody CanteenDailyMenuCVDTO creenDailyMenuCVDTO) {
|
||||
return canteenDailyMenuService.cv(creenDailyMenuCVDTO);
|
||||
}
|
||||
|
||||
@Operation(summary = "Excel导入每日菜谱(规范模板)")
|
||||
@SysLog("导入每日菜谱")
|
||||
@PostMapping("/import")
|
||||
@PreAuthorize("@pms.hasPermission('canteen_canteenDailyMenu_add')")
|
||||
public R importExcel(@RequestParam("file") MultipartFile file,
|
||||
@RequestParam("merchantId") Long merchantId,
|
||||
@RequestParam("categoryId") Long categoryId,
|
||||
@RequestParam(value = "replaceDay", defaultValue = "true") boolean replaceDay) throws Exception {
|
||||
try {
|
||||
return dailyMenuImportHelper.importExcel(file, merchantId, categoryId, replaceDay);
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
return R.failed(ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过id删除每日菜谱
|
||||
*1
|
||||
|
||||
+6
-5
@@ -14,7 +14,6 @@ import com.bsd.pjcloud.common.log.annotation.SysLog;
|
||||
import com.bsd.pjcloud.canteen.entity.CanteenErpCategoryEntity;
|
||||
import com.bsd.pjcloud.canteen.service.CanteenErpCategoryService;
|
||||
import com.bsd.pjcloud.canteen.util.CanteenMerchantScopeHelper;
|
||||
import com.bsd.pjcloud.common.security.util.SecurityUtils;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import com.bsd.pjcloud.common.excel.annotation.ResponseExcel;
|
||||
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
||||
@@ -84,11 +83,13 @@ public class CanteenErpCategoryController {
|
||||
@PostMapping
|
||||
@PreAuthorize("@pms.hasPermission('canteen_canteenErpCategory_add')" )
|
||||
public R save(@RequestBody CanteenErpCategoryEntity canteenErpCategory) {
|
||||
merchantScopeHelper.fillDeptIdByMerchantId(canteenErpCategory.getMerchantId(), canteenErpCategory::setDeptId);
|
||||
if (canteenErpCategory.getDeptId() == null) {
|
||||
canteenErpCategory.setDeptId(SecurityUtils.getUser().getDeptId());
|
||||
try {
|
||||
merchantScopeHelper.fillDeptIdByMerchantId(canteenErpCategory.getMerchantId(), canteenErpCategory::setDeptId);
|
||||
return R.ok(canteenErpCategoryService.save(canteenErpCategory));
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
return R.failed(ex.getMessage());
|
||||
}
|
||||
return R.ok(canteenErpCategoryService.save(canteenErpCategory));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+10
-3
@@ -6,10 +6,10 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.bsd.pjcloud.canteen.dto.menurecipe.CanteenMenuRecipeMatchDTO;
|
||||
import com.bsd.pjcloud.canteen.entity.CanteenMenuRecipeEntity;
|
||||
import com.bsd.pjcloud.canteen.service.CanteenMenuRecipeService;
|
||||
import com.bsd.pjcloud.canteen.util.CanteenMerchantScopeHelper;
|
||||
import com.bsd.pjcloud.canteen.vo.menurecipe.CanteenMenuRecipeImportResultVO;
|
||||
import com.bsd.pjcloud.common.core.util.R;
|
||||
import com.bsd.pjcloud.common.log.annotation.SysLog;
|
||||
import com.bsd.pjcloud.common.security.util.SecurityUtils;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
@@ -31,6 +31,8 @@ public class CanteenMenuRecipeController {
|
||||
|
||||
private final CanteenMenuRecipeService canteenMenuRecipeService;
|
||||
|
||||
private final CanteenMerchantScopeHelper merchantScopeHelper;
|
||||
|
||||
@Operation(summary = "分页查询")
|
||||
@GetMapping("/page")
|
||||
@PreAuthorize("@pms.hasPermission('canteen_canteenMenuRecipe_view')")
|
||||
@@ -58,8 +60,13 @@ public class CanteenMenuRecipeController {
|
||||
@PostMapping
|
||||
@PreAuthorize("@pms.hasPermission('canteen_canteenMenuRecipe_add')")
|
||||
public R save(@RequestBody CanteenMenuRecipeEntity entity) {
|
||||
entity.setDeptId(SecurityUtils.getUser().getDeptId());
|
||||
return R.ok(canteenMenuRecipeService.saveDeep(entity));
|
||||
try {
|
||||
merchantScopeHelper.fillDeptIdByMerchantIdOrCurrentUser(entity.getMerchantId(), entity::setDeptId);
|
||||
return R.ok(canteenMenuRecipeService.saveDeep(entity));
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
return R.failed(ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@SysLog("修改排菜菜谱")
|
||||
|
||||
+1
-1
@@ -151,8 +151,8 @@ public class CanteenOnlineSpuController {
|
||||
return R.failed("择一分组不存在(可能是前端 long 精度丢失导致 id 被圆整),请重新选择分组");
|
||||
}
|
||||
}
|
||||
canteenOnlineSpu.setDeptId(SecurityUtils.getUser().getDeptId());
|
||||
try {
|
||||
merchantScopeHelper.fillDeptIdByMerchantId(canteenOnlineSpu.getMerchantId(), canteenOnlineSpu::setDeptId);
|
||||
return R.ok(canteenOnlineSpuService.saveDeep(canteenOnlineSpu));
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
|
||||
+69
-9
@@ -9,12 +9,17 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.bsd.pjcloud.canteen.constant.StockTypeEnum;
|
||||
import com.bsd.pjcloud.canteen.dto.CanteenDailySpuDTO;
|
||||
import com.bsd.pjcloud.canteen.dto.CanteenSpuExportQuery;
|
||||
import com.bsd.pjcloud.canteen.dto.CanteenSpuImportExcel;
|
||||
import com.bsd.pjcloud.canteen.dto.CanteenSpuUpdateStockReqDTO;
|
||||
import com.bsd.pjcloud.canteen.entity.CanteenErpMaterialEntity;
|
||||
import com.bsd.pjcloud.canteen.entity.CanteenOnlineSpuEntity;
|
||||
import com.bsd.pjcloud.canteen.entity.CanteenStockEntity;
|
||||
import com.bsd.pjcloud.canteen.mapper.CanteenStockMapper;
|
||||
import com.bsd.pjcloud.canteen.service.CanteenStockService;
|
||||
import com.bsd.pjcloud.canteen.excel.CanteenSpuTemplateMergeHandler;
|
||||
import com.bsd.pjcloud.canteen.service.impl.CanteenSpuImportHelper;
|
||||
import com.bsd.pjcloud.canteen.util.CanteenMerchantScopeHelper;
|
||||
import com.bsd.pjcloud.canteen.vo.ADCanteenSpuListVO;
|
||||
import com.bsd.pjcloud.canteen.vo.app.OnlineSpuComboItemVO;
|
||||
import com.bsd.pjcloud.canteen.vo.erp.CanteenSpuSimpleVO;
|
||||
@@ -35,6 +40,7 @@ import io.swagger.v3.oas.annotations.Operation;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -58,6 +64,10 @@ public class CanteenSpuController {
|
||||
|
||||
private final CanteenStockService canteenStockService;
|
||||
|
||||
private final CanteenSpuImportHelper canteenSpuImportHelper;
|
||||
|
||||
private final CanteenMerchantScopeHelper merchantScopeHelper;
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
*
|
||||
@@ -218,17 +228,67 @@ public class CanteenSpuController {
|
||||
|
||||
|
||||
/**
|
||||
* 导出excel 表格
|
||||
*
|
||||
* @param canteenSpu 查询条件
|
||||
* @param ids 导出指定ID
|
||||
* @return excel 文件流
|
||||
* 下载菜品导入模板(单品 + 套餐示例,套餐列已合并)
|
||||
*/
|
||||
@ResponseExcel
|
||||
@GetMapping("/export")
|
||||
@Operation(summary = "下载菜品导入模板")
|
||||
@ResponseExcel(name = "菜品导入模板", inMemory = true, writeHandler = CanteenSpuTemplateMergeHandler.class)
|
||||
@GetMapping("/import/template")
|
||||
@PreAuthorize("@pms.hasPermission('canteen_canteenSpu_add')")
|
||||
public List<CanteenSpuImportExcel> downloadImportTemplate() {
|
||||
return canteenSpuImportHelper.templateRows();
|
||||
}
|
||||
|
||||
/**
|
||||
* Excel 导入菜品(单品 + 套餐)
|
||||
*
|
||||
* @param cover true=覆盖(同名更新),false=仅新增(同名跳过)
|
||||
*/
|
||||
@Operation(summary = "Excel导入菜品", description = "cover=true 覆盖导入;cover=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 {
|
||||
try {
|
||||
return canteenSpuImportHelper.importExcel(file, merchantId, categoryId, cover);
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
return R.failed(ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出excel(POST:筛选条件放 body,避免 GET 丢参/雪花 Long 精度问题;条件与列表 selectPage 一致)
|
||||
*/
|
||||
@ResponseExcel(name = "菜品列表", inMemory = true, writeHandler = CanteenSpuTemplateMergeHandler.class)
|
||||
@PostMapping("/export")
|
||||
@PreAuthorize("@pms.hasPermission('canteen_canteenSpu_export')")
|
||||
public List<CanteenSpuEntity> export(CanteenSpuEntity canteenSpu, Long[] ids) {
|
||||
return canteenSpuService.list(Wrappers.lambdaQuery(canteenSpu).in(ArrayUtil.isNotEmpty(ids), CanteenSpuEntity::getId, ids));
|
||||
public List<CanteenSpuImportExcel> export(@RequestBody(required = false) CanteenSpuExportQuery query) {
|
||||
if (query == null) {
|
||||
query = new CanteenSpuExportQuery();
|
||||
}
|
||||
Long merchantId = parseLongId(query.getMerchantId());
|
||||
Long categoryId = parseLongId(query.getCategoryId());
|
||||
Long[] ids = null;
|
||||
if (CollUtil.isNotEmpty(query.getIds())) {
|
||||
ids = query.getIds().stream().map(this::parseLongId).filter(Objects::nonNull).toArray(Long[]::new);
|
||||
}
|
||||
return canteenSpuImportHelper.toExportRows(
|
||||
canteenSpuService.listForExport(query.getName(), categoryId, merchantId, ids));
|
||||
}
|
||||
|
||||
private Long parseLongId(String raw) {
|
||||
if (StrUtil.isBlank(raw)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return Long.valueOf(raw.trim());
|
||||
}
|
||||
catch (NumberFormatException ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
package com.bsd.pjcloud.canteen.excel;
|
||||
|
||||
import com.alibaba.excel.write.handler.WorkbookWriteHandler;
|
||||
import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
|
||||
import org.apache.poi.ss.usermodel.Cell;
|
||||
import org.apache.poi.ss.usermodel.CellStyle;
|
||||
import org.apache.poi.ss.usermodel.HorizontalAlignment;
|
||||
import org.apache.poi.ss.usermodel.Row;
|
||||
import org.apache.poi.ss.usermodel.Sheet;
|
||||
import org.apache.poi.ss.usermodel.VerticalAlignment;
|
||||
import org.apache.poi.ss.util.CellRangeAddress;
|
||||
|
||||
/**
|
||||
* 每日菜谱导入模板:套餐多行合并日期/餐段/套餐名/套餐价/说明;菜品单价不合并。
|
||||
* <p>列:0日期 1餐段 2套餐名 3菜品名 4配料 5菜品单价 6套餐价 7填写说明
|
||||
* <p>数据行(表头第 0 行):1-3 A套餐,4-5 B套餐,6 午餐单品,7-8 早餐单品
|
||||
*/
|
||||
public class CanteenDailyMenuTemplateMergeHandler implements WorkbookWriteHandler {
|
||||
|
||||
@Override
|
||||
public void afterWorkbookDispose(WriteWorkbookHolder writeWorkbookHolder) {
|
||||
Sheet sheet = writeWorkbookHolder.getWorkbook().getSheetAt(0);
|
||||
if (sheet == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
merge(sheet, 1, 8, 0, 0);
|
||||
merge(sheet, 1, 6, 1, 1);
|
||||
merge(sheet, 7, 8, 1, 1);
|
||||
// A 套餐:套餐名、套餐价、填写说明(菜品单价列不合并)
|
||||
merge(sheet, 1, 3, 2, 2);
|
||||
merge(sheet, 1, 3, 6, 6);
|
||||
merge(sheet, 1, 3, 7, 7);
|
||||
// B 套餐
|
||||
merge(sheet, 4, 5, 2, 2);
|
||||
merge(sheet, 4, 5, 6, 6);
|
||||
merge(sheet, 4, 5, 7, 7);
|
||||
|
||||
CellStyle center = writeWorkbookHolder.getWorkbook().createCellStyle();
|
||||
center.setAlignment(HorizontalAlignment.CENTER);
|
||||
center.setVerticalAlignment(VerticalAlignment.CENTER);
|
||||
applyStyle(sheet, center, new int[] { 0, 1, 2, 6, 7 }, 1, 8);
|
||||
|
||||
sheet.setColumnWidth(0, 12 * 256);
|
||||
sheet.setColumnWidth(1, 8 * 256);
|
||||
sheet.setColumnWidth(2, 10 * 256);
|
||||
sheet.setColumnWidth(3, 16 * 256);
|
||||
sheet.setColumnWidth(4, 22 * 256);
|
||||
sheet.setColumnWidth(5, 14 * 256);
|
||||
sheet.setColumnWidth(6, 12 * 256);
|
||||
sheet.setColumnWidth(7, 48 * 256);
|
||||
}
|
||||
|
||||
private static void merge(Sheet sheet, int firstRow, int lastRow, int firstCol, int lastCol) {
|
||||
if (firstRow == lastRow && firstCol == lastCol) {
|
||||
return;
|
||||
}
|
||||
sheet.addMergedRegion(new CellRangeAddress(firstRow, lastRow, firstCol, lastCol));
|
||||
}
|
||||
|
||||
private static void applyStyle(Sheet sheet, CellStyle style, int[] cols, int firstRow, int lastRow) {
|
||||
for (int r = firstRow; r <= lastRow; r++) {
|
||||
Row row = sheet.getRow(r);
|
||||
if (row == null) {
|
||||
continue;
|
||||
}
|
||||
for (int c : cols) {
|
||||
Cell cell = row.getCell(c);
|
||||
if (cell != null) {
|
||||
cell.setCellStyle(style);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+174
@@ -0,0 +1,174 @@
|
||||
package com.bsd.pjcloud.canteen.excel;
|
||||
|
||||
import com.alibaba.excel.write.handler.WorkbookWriteHandler;
|
||||
import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
|
||||
import org.apache.poi.ss.usermodel.Cell;
|
||||
import org.apache.poi.ss.usermodel.CellStyle;
|
||||
import org.apache.poi.ss.usermodel.CellType;
|
||||
import org.apache.poi.ss.usermodel.HorizontalAlignment;
|
||||
import org.apache.poi.ss.usermodel.Row;
|
||||
import org.apache.poi.ss.usermodel.Sheet;
|
||||
import org.apache.poi.ss.usermodel.VerticalAlignment;
|
||||
import org.apache.poi.ss.util.CellRangeAddress;
|
||||
|
||||
/**
|
||||
* 菜品 Excel:套餐多行时合并套餐级字段(模板/导出通用)。
|
||||
* <p>列:0套餐名 1菜品名称 2分类 3菜品单价 4套餐价 5市场价 6库存 7排序 8状态 9移动端 10详情 11说明 12菜品ID 13套餐ID
|
||||
* <p>须配合 {@code @ResponseExcel(inMemory = true)},否则 SXSSF 刷盘后靠前行无法合并。
|
||||
*/
|
||||
public class CanteenSpuTemplateMergeHandler implements WorkbookWriteHandler {
|
||||
|
||||
/** 套餐级合并列:套餐名、套餐价、库存、排序、上架、移动端、说明、套餐ID */
|
||||
private static final int[] MERGE_COLS = { 0, 4, 6, 7, 8, 9, 11, 13 };
|
||||
|
||||
private static final int COL_DISH_ID = 12;
|
||||
private static final int COL_COMBO_ID = 13;
|
||||
|
||||
@Override
|
||||
public void afterWorkbookDispose(WriteWorkbookHolder writeWorkbookHolder) {
|
||||
Sheet sheet = writeWorkbookHolder.getWorkbook().getNumberOfSheets() > 0
|
||||
? writeWorkbookHolder.getWorkbook().getSheetAt(0) : null;
|
||||
if (sheet == null) {
|
||||
return;
|
||||
}
|
||||
int lastRow = sheet.getLastRowNum();
|
||||
if (lastRow < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
int groupStart = -1;
|
||||
String groupName = null;
|
||||
for (int r = 1; r <= lastRow + 1; r++) {
|
||||
boolean end = r > lastRow;
|
||||
String raw = end ? null : cellText(sheet, r, 0);
|
||||
boolean blankCell = !end && (raw == null || raw.trim().isEmpty());
|
||||
String combo = end ? null : normalizeCombo(raw);
|
||||
|
||||
if (groupStart >= 0) {
|
||||
boolean continueGroup = !end && (blankCell || (combo != null && combo.equals(groupName)));
|
||||
if (!continueGroup) {
|
||||
mergeGroup(sheet, groupStart, r - 1);
|
||||
groupStart = -1;
|
||||
groupName = null;
|
||||
}
|
||||
}
|
||||
if (!end && combo != null && groupStart < 0) {
|
||||
groupStart = r;
|
||||
groupName = combo;
|
||||
}
|
||||
}
|
||||
|
||||
CellStyle center = writeWorkbookHolder.getWorkbook().createCellStyle();
|
||||
center.setAlignment(HorizontalAlignment.CENTER);
|
||||
center.setVerticalAlignment(VerticalAlignment.CENTER);
|
||||
for (int r = 1; r <= lastRow; r++) {
|
||||
for (int c : MERGE_COLS) {
|
||||
Cell cell = getCell(sheet, r, c);
|
||||
if (cell != null) {
|
||||
cell.setCellStyle(center);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 雪花ID强制文本,避免 Excel 科学计数法丢精度
|
||||
CellStyle textStyle = writeWorkbookHolder.getWorkbook().createCellStyle();
|
||||
textStyle.setDataFormat(writeWorkbookHolder.getWorkbook().createDataFormat().getFormat("@"));
|
||||
for (int r = 1; r <= lastRow; r++) {
|
||||
forceTextId(sheet, r, COL_DISH_ID, textStyle);
|
||||
forceTextId(sheet, r, COL_COMBO_ID, textStyle);
|
||||
}
|
||||
|
||||
int[] widths = { 10, 14, 10, 12, 10, 10, 8, 6, 8, 10, 18, 28, 22, 22 };
|
||||
for (int i = 0; i < widths.length; i++) {
|
||||
sheet.setColumnWidth(i, widths[i] * 256);
|
||||
}
|
||||
}
|
||||
|
||||
private static void forceTextId(Sheet sheet, int rowIdx, int colIdx, CellStyle textStyle) {
|
||||
Row row = sheet.getRow(rowIdx);
|
||||
if (row == null) {
|
||||
return;
|
||||
}
|
||||
Cell cell = row.getCell(colIdx);
|
||||
if (cell == null || cell.getCellType() == CellType.BLANK) {
|
||||
return;
|
||||
}
|
||||
String text;
|
||||
if (cell.getCellType() == CellType.NUMERIC) {
|
||||
text = java.math.BigDecimal.valueOf(cell.getNumericCellValue()).toPlainString().split("\\.")[0];
|
||||
}
|
||||
else if (cell.getCellType() == CellType.STRING) {
|
||||
text = cell.getStringCellValue();
|
||||
}
|
||||
else {
|
||||
text = cell.toString();
|
||||
}
|
||||
if (text == null || text.trim().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
cell.setCellStyle(textStyle);
|
||||
cell.setCellValue(text.trim());
|
||||
}
|
||||
|
||||
/** @return 可合并的套餐名;单品标记或空返回 null */
|
||||
private static String normalizeCombo(String raw) {
|
||||
if (raw == null || raw.trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
String c = raw.trim();
|
||||
if ("无".equals(c) || "-".equals(c) || "单品".equals(c) || "(无)".equals(c) || "(无)".equals(c)) {
|
||||
return null;
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
private static void mergeGroup(Sheet sheet, int firstRow, int lastRow) {
|
||||
if (firstRow >= lastRow) {
|
||||
return;
|
||||
}
|
||||
for (int c : MERGE_COLS) {
|
||||
for (int r = firstRow + 1; r <= lastRow; r++) {
|
||||
Cell cell = getCell(sheet, r, c);
|
||||
if (cell != null) {
|
||||
cell.setBlank();
|
||||
}
|
||||
}
|
||||
sheet.addMergedRegion(new CellRangeAddress(firstRow, lastRow, c, c));
|
||||
}
|
||||
}
|
||||
|
||||
private static String cellText(Sheet sheet, int rowIdx, int colIdx) {
|
||||
Cell cell = getCell(sheet, rowIdx, colIdx);
|
||||
if (cell == null) {
|
||||
return null;
|
||||
}
|
||||
CellType type = cell.getCellType();
|
||||
if (type == CellType.STRING) {
|
||||
return cell.getStringCellValue();
|
||||
}
|
||||
if (type == CellType.NUMERIC) {
|
||||
double v = cell.getNumericCellValue();
|
||||
if (v == Math.floor(v) && !Double.isInfinite(v)) {
|
||||
return String.valueOf((long) v);
|
||||
}
|
||||
return String.valueOf(v);
|
||||
}
|
||||
if (type == CellType.BLANK) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return cell.toString();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static Cell getCell(Sheet sheet, int rowIdx, int colIdx) {
|
||||
Row row = sheet.getRow(rowIdx);
|
||||
if (row == null) {
|
||||
return null;
|
||||
}
|
||||
return row.getCell(colIdx);
|
||||
}
|
||||
}
|
||||
+4
@@ -49,6 +49,10 @@ public interface CanteenSpuService extends IService<CanteenSpuEntity> {
|
||||
*/
|
||||
Object pageScope(Page page, CanteenSpuEntity canteenSpu);
|
||||
|
||||
/**
|
||||
* 导出列表查询(与列表页 selectPage 同筛选:商户/名称/分类,并排除已关联线上菜品)
|
||||
*/
|
||||
List<CanteenSpuEntity> listForExport(String name, Long categoryId, Long merchantId, Long[] ids);
|
||||
|
||||
/**
|
||||
* 分页查询,并且过滤已选择的菜品
|
||||
|
||||
+9
-1
@@ -42,6 +42,9 @@ public class CanteenCategoryServiceImpl extends ServiceImpl<CanteenCategoryMappe
|
||||
|
||||
@Override
|
||||
public Long create(CanteenCategoryEntity canteenCategory) {
|
||||
if (canteenCategory.getMerchantId() == null) {
|
||||
throw new IllegalArgumentException("商户不能为空");
|
||||
}
|
||||
merchantScopeHelper.fillDeptIdByMerchantId(canteenCategory.getMerchantId(), canteenCategory::setDeptId);
|
||||
baseMapper.insert(canteenCategory);
|
||||
// 发送mq消息
|
||||
@@ -53,7 +56,12 @@ public class CanteenCategoryServiceImpl extends ServiceImpl<CanteenCategoryMappe
|
||||
public Boolean edit(CanteenCategoryEntity canteenCategory) {
|
||||
CanteenCategoryEntity detail = baseMapper.selectById(canteenCategory.getId());
|
||||
if (ObjectUtil.isEmpty(detail)) throw new RuntimeException("菜品分类不存在");
|
||||
merchantScopeHelper.fillDeptIdByMerchantId(canteenCategory.getMerchantId(), canteenCategory::setDeptId);
|
||||
Long merchantId = canteenCategory.getMerchantId() != null ? canteenCategory.getMerchantId() : detail.getMerchantId();
|
||||
if (merchantId == null) {
|
||||
throw new IllegalArgumentException("商户不能为空");
|
||||
}
|
||||
canteenCategory.setMerchantId(merchantId);
|
||||
merchantScopeHelper.fillDeptIdByMerchantId(merchantId, canteenCategory::setDeptId);
|
||||
baseMapper.updateById(canteenCategory);
|
||||
// 发送mq消息
|
||||
remoteMqttEventService.sendCanteenClassifyChangeMsg(
|
||||
|
||||
+437
@@ -0,0 +1,437 @@
|
||||
package com.bsd.pjcloud.canteen.service.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.alibaba.excel.EasyExcel;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.bsd.pjcloud.canteen.dto.CanteenDailyMenuImportExcel;
|
||||
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.service.CanteenDailyMenuService;
|
||||
import com.bsd.pjcloud.canteen.service.CanteenSpuService;
|
||||
import com.bsd.pjcloud.canteen.util.CanteenMerchantScopeHelper;
|
||||
import com.bsd.pjcloud.common.core.util.R;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 每日菜谱 Excel 导入(规范模板)
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class CanteenDailyMenuImportHelper {
|
||||
|
||||
private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
||||
|
||||
private final CanteenSpuService canteenSpuService;
|
||||
private final CanteenDailyMenuService canteenDailyMenuService;
|
||||
private final CanteenMerchantScopeHelper merchantScopeHelper;
|
||||
|
||||
public List<CanteenDailyMenuImportExcel> templateRows() {
|
||||
List<CanteenDailyMenuImportExcel> list = new ArrayList<>();
|
||||
// 午餐 A 套餐:套餐价 21;各菜可有自己的菜品单价
|
||||
list.add(row("2026-08-17", "午餐", "A套餐", "糖醋排骨", "青莲仔排", "12", "21",
|
||||
"套餐价只填首行;菜品单价每行可填,不填默认0。套餐名/菜品名在菜品列表已存在则复用,不存在则自动新增"));
|
||||
list.add(row(null, null, null, "蒜苔炒腊肉", "蒜苔,腊肉,胡萝卜", "8", null, null));
|
||||
list.add(row(null, null, null, "清炒时蔬", "空心菜", "3", null, null));
|
||||
// 午餐 B 套餐
|
||||
list.add(row(null, null, "B套餐", "红烧肉", "五花肉", "10", "18",
|
||||
"换套餐名即另一套;名称已存在不新增"));
|
||||
list.add(row(null, null, null, "土豆丝", "土豆", "4", null, null));
|
||||
// 单品:套餐名「无」,单价写在菜品单价
|
||||
list.add(row(null, null, "无", "千张大骨汤", null, "0", null,
|
||||
"单品:套餐名填「无」;菜品已存在则复用"));
|
||||
list.add(row(null, "早餐", "无", "榨菜肉丝面", "榨菜,肉丝", "6.4", null,
|
||||
"早餐单品"));
|
||||
list.add(row(null, null, "无", "豆浆", null, "2", null, null));
|
||||
return list;
|
||||
}
|
||||
|
||||
private static CanteenDailyMenuImportExcel row(String date, String meal, String combo, String dish,
|
||||
String ingredients, String dishPrice, String comboPrice, String tip) {
|
||||
CanteenDailyMenuImportExcel r = new CanteenDailyMenuImportExcel();
|
||||
r.setBizDate(date);
|
||||
r.setMealName(meal);
|
||||
r.setComboName(combo);
|
||||
r.setDishName(dish);
|
||||
r.setIngredients(ingredients);
|
||||
if (StrUtil.isNotBlank(dishPrice)) {
|
||||
r.setDishPriceYuan(new BigDecimal(dishPrice));
|
||||
}
|
||||
if (StrUtil.isNotBlank(comboPrice)) {
|
||||
r.setPriceYuan(new BigDecimal(comboPrice));
|
||||
}
|
||||
r.setTip(tip);
|
||||
return r;
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R importExcel(MultipartFile file, Long merchantId, Long categoryId, boolean replaceDay)
|
||||
throws IOException {
|
||||
if (merchantId == null) {
|
||||
return R.failed("请选择商户");
|
||||
}
|
||||
if (categoryId == null) {
|
||||
return R.failed("请选择新建菜品默认分类");
|
||||
}
|
||||
if (file == null || file.isEmpty()) {
|
||||
return R.failed("请上传 Excel 文件");
|
||||
}
|
||||
List<CanteenDailyMenuImportExcel> rows = EasyExcel.read(file.getInputStream())
|
||||
.head(CanteenDailyMenuImportExcel.class)
|
||||
.sheet()
|
||||
.headRowNumber(1)
|
||||
.doReadSync();
|
||||
if (CollUtil.isEmpty(rows)) {
|
||||
return R.failed("导入数据为空");
|
||||
}
|
||||
normalizeMergedCells(rows);
|
||||
|
||||
Long deptId = merchantScopeHelper.requireDeptIdByMerchantId(merchantId);
|
||||
|
||||
// groupKey = date|hourType|comboOrEmpty
|
||||
Map<String, List<CanteenDailyMenuImportExcel>> groups = new LinkedHashMap<>();
|
||||
List<String> errors = new ArrayList<>();
|
||||
int line = 1;
|
||||
for (CanteenDailyMenuImportExcel row : rows) {
|
||||
line++;
|
||||
if (isBlankRow(row)) {
|
||||
continue;
|
||||
}
|
||||
Integer hourType = parseMeal(row.getMealName());
|
||||
LocalDate date = parseDate(row.getBizDate());
|
||||
String dish = StrUtil.trim(row.getDishName());
|
||||
if (date == null) {
|
||||
errors.add("第" + line + "行:日期格式应为 yyyy-MM-dd");
|
||||
continue;
|
||||
}
|
||||
if (hourType == null) {
|
||||
errors.add("第" + line + "行:餐段应为 早餐/午餐/晚餐");
|
||||
continue;
|
||||
}
|
||||
if (StrUtil.isBlank(dish)) {
|
||||
errors.add("第" + line + "行:菜品名不能为空");
|
||||
continue;
|
||||
}
|
||||
String combo = StrUtil.trim(row.getComboName());
|
||||
String key = date + "|" + hourType + "|" + StrUtil.blankToDefault(combo, "");
|
||||
groups.computeIfAbsent(key, k -> new ArrayList<>()).add(row);
|
||||
}
|
||||
if (CollUtil.isNotEmpty(errors)) {
|
||||
return R.failed(String.join(";", errors.subList(0, Math.min(errors.size(), 20))));
|
||||
}
|
||||
|
||||
Set<String> clearedDayHour = new LinkedHashSet<>();
|
||||
int createdDish = 0;
|
||||
int createdCombo = 0;
|
||||
int scheduled = 0;
|
||||
|
||||
for (Map.Entry<String, List<CanteenDailyMenuImportExcel>> e : groups.entrySet()) {
|
||||
String[] parts = e.getKey().split("\\|", -1);
|
||||
LocalDate date = LocalDate.parse(parts[0]);
|
||||
Integer hourType = Integer.valueOf(parts[1]);
|
||||
String comboName = parts[2];
|
||||
List<CanteenDailyMenuImportExcel> items = e.getValue();
|
||||
|
||||
if (StrUtil.isBlank(comboName)) {
|
||||
for (CanteenDailyMenuImportExcel item : items) {
|
||||
FindOrCreate r = findOrCreateDish(merchantId, deptId, categoryId, item.getDishName(),
|
||||
item.getIngredients(), resolveDishPriceFen(item));
|
||||
if (r.created) {
|
||||
createdDish++;
|
||||
}
|
||||
schedule(merchantId, deptId, date, hourType, r.spu.getId(), replaceDay, clearedDayHour);
|
||||
scheduled++;
|
||||
}
|
||||
}
|
||||
else {
|
||||
List<CanteenSpuEntity> children = new ArrayList<>();
|
||||
Integer comboPrice = null;
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
CanteenDailyMenuImportExcel item = items.get(i);
|
||||
if (i == 0) {
|
||||
Integer fen = toFen(item.getPriceYuan());
|
||||
if (fen != null) {
|
||||
comboPrice = fen;
|
||||
}
|
||||
}
|
||||
FindOrCreate r = findOrCreateDish(merchantId, deptId, categoryId, item.getDishName(),
|
||||
item.getIngredients(), resolveDishPriceFen(item));
|
||||
if (r.created) {
|
||||
createdDish++;
|
||||
}
|
||||
children.add(r.spu);
|
||||
}
|
||||
if (comboPrice == null) {
|
||||
comboPrice = 0;
|
||||
}
|
||||
String mainName = children.get(0).getName();
|
||||
String fullComboName = comboName + "-" + mainName;
|
||||
FindOrCreate combo = findOrCreateCombo(merchantId, deptId, categoryId, fullComboName, comboName,
|
||||
comboPrice, children);
|
||||
if (combo.created) {
|
||||
createdCombo++;
|
||||
}
|
||||
schedule(merchantId, deptId, date, hourType, combo.spu.getId(), replaceDay, clearedDayHour);
|
||||
scheduled++;
|
||||
}
|
||||
}
|
||||
|
||||
return R.ok(null, String.format("导入成功:新建单品 %d,新建套餐 %d,排菜 %d", createdDish, createdCombo, scheduled));
|
||||
}
|
||||
|
||||
private void schedule(Long merchantId, Long deptId, LocalDate date, Integer hourType, Long spuId,
|
||||
boolean replaceDay, Set<String> clearedDayHour) {
|
||||
String dh = date + "|" + hourType;
|
||||
if (replaceDay && !clearedDayHour.contains(dh)) {
|
||||
canteenDailyMenuService.remove(Wrappers.<CanteenDailyMenuEntity>lambdaQuery()
|
||||
.eq(CanteenDailyMenuEntity::getMerchantId, merchantId)
|
||||
.eq(CanteenDailyMenuEntity::getStartTime, date)
|
||||
.eq(CanteenDailyMenuEntity::getHourType, hourType));
|
||||
clearedDayHour.add(dh);
|
||||
}
|
||||
boolean exists = canteenDailyMenuService.count(Wrappers.<CanteenDailyMenuEntity>lambdaQuery()
|
||||
.eq(CanteenDailyMenuEntity::getMerchantId, merchantId)
|
||||
.eq(CanteenDailyMenuEntity::getStartTime, date)
|
||||
.eq(CanteenDailyMenuEntity::getHourType, hourType)
|
||||
.eq(CanteenDailyMenuEntity::getSpuId, spuId)) > 0;
|
||||
if (exists) {
|
||||
return;
|
||||
}
|
||||
CanteenDailyMenuEntity menu = new CanteenDailyMenuEntity();
|
||||
menu.setMerchantId(merchantId);
|
||||
menu.setDeptId(deptId);
|
||||
menu.setStartTime(date);
|
||||
menu.setHourType(hourType);
|
||||
menu.setSpuId(spuId);
|
||||
canteenDailyMenuService.save(menu);
|
||||
}
|
||||
|
||||
private FindOrCreate findOrCreateDish(Long merchantId, Long deptId, Long categoryId, String name,
|
||||
String ingredients, Integer priceFen) {
|
||||
String clean = cleanDishName(name);
|
||||
CanteenSpuEntity exist = canteenSpuService.getOne(Wrappers.<CanteenSpuEntity>lambdaQuery()
|
||||
.eq(CanteenSpuEntity::getMerchantId, merchantId)
|
||||
.eq(CanteenSpuEntity::getName, clean)
|
||||
.eq(CanteenSpuEntity::getSpuType, 1)
|
||||
.last("limit 1"), false);
|
||||
if (exist != null) {
|
||||
boolean needUpdate = false;
|
||||
if (StrUtil.isNotBlank(ingredients) && StrUtil.isBlank(exist.getDescription())) {
|
||||
exist.setDescription(ingredients.trim());
|
||||
needUpdate = true;
|
||||
}
|
||||
if (priceFen != null && priceFen > 0 && (exist.getPrice() == null || exist.getPrice() == 0)) {
|
||||
exist.setPrice(priceFen);
|
||||
exist.setMarketPrice(priceFen);
|
||||
needUpdate = true;
|
||||
}
|
||||
if (needUpdate) {
|
||||
canteenSpuService.updateById(exist);
|
||||
}
|
||||
return new FindOrCreate(exist, false);
|
||||
}
|
||||
CanteenSpuEntity spu = newBaseSpu(merchantId, deptId, categoryId, clean, 1);
|
||||
spu.setDescription(StrUtil.trim(ingredients));
|
||||
spu.setPrice(priceFen == null ? 0 : priceFen);
|
||||
spu.setMarketPrice(spu.getPrice());
|
||||
canteenSpuService.save(spu);
|
||||
return new FindOrCreate(spu, true);
|
||||
}
|
||||
|
||||
private FindOrCreate findOrCreateCombo(Long merchantId, Long deptId, Long categoryId, String fullName,
|
||||
String pkgLabel, Integer priceFen, List<CanteenSpuEntity> children) {
|
||||
CanteenSpuEntity exist = canteenSpuService.getOne(Wrappers.<CanteenSpuEntity>lambdaQuery()
|
||||
.eq(CanteenSpuEntity::getMerchantId, merchantId)
|
||||
.eq(CanteenSpuEntity::getName, fullName)
|
||||
.eq(CanteenSpuEntity::getSpuType, 2)
|
||||
.last("limit 1"), false);
|
||||
List<CanteenSpuComboItemEntity> items = new ArrayList<>();
|
||||
int sort = 0;
|
||||
for (CanteenSpuEntity child : children) {
|
||||
CanteenSpuComboItemEntity line = new CanteenSpuComboItemEntity();
|
||||
line.setChildSpuId(child.getId());
|
||||
line.setQuantity(1);
|
||||
line.setSortOrder(sort++);
|
||||
items.add(line);
|
||||
}
|
||||
if (exist != null) {
|
||||
exist.setPrice(priceFen);
|
||||
exist.setMarketPrice(priceFen);
|
||||
exist.setComboItems(items);
|
||||
canteenSpuService.updateDeep(exist);
|
||||
return new FindOrCreate(exist, false);
|
||||
}
|
||||
CanteenSpuEntity combo = newBaseSpu(merchantId, deptId, categoryId, fullName, 2);
|
||||
combo.setIntroduction(pkgLabel);
|
||||
combo.setPrice(priceFen);
|
||||
combo.setMarketPrice(priceFen);
|
||||
combo.setComboItems(items);
|
||||
canteenSpuService.saveDeep(combo);
|
||||
return new FindOrCreate(combo, true);
|
||||
}
|
||||
|
||||
private CanteenSpuEntity newBaseSpu(Long merchantId, Long deptId, Long categoryId, String name, int spuType) {
|
||||
CanteenSpuEntity spu = new CanteenSpuEntity();
|
||||
spu.setName(name);
|
||||
spu.setCategoryId(categoryId);
|
||||
spu.setMerchantId(merchantId);
|
||||
spu.setDeptId(deptId);
|
||||
spu.setSpuType(spuType);
|
||||
spu.setPicUrl("");
|
||||
spu.setStatus(0);
|
||||
spu.setSpecType(Boolean.FALSE);
|
||||
spu.setSort(0);
|
||||
spu.setStock(9999);
|
||||
spu.setSalesCount(0);
|
||||
spu.setVirtualSalesCount(0);
|
||||
spu.setBrowseCount(0);
|
||||
spu.setCostPrice(0);
|
||||
spu.setSystemType(2L);
|
||||
spu.setIsShow("0");
|
||||
return spu;
|
||||
}
|
||||
|
||||
private static boolean isBlankRow(CanteenDailyMenuImportExcel row) {
|
||||
return StrUtil.isBlank(row.getBizDate()) && StrUtil.isBlank(row.getDishName())
|
||||
&& StrUtil.isBlank(row.getMealName()) && StrUtil.isBlank(row.getComboName());
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并单元格模板读入后,非首行日期/餐段/套餐名为空,需向下填充。
|
||||
* 套餐名填「无」「-」「单品」表示单品,不参与套餐继承。
|
||||
*/
|
||||
private static void normalizeMergedCells(List<CanteenDailyMenuImportExcel> rows) {
|
||||
String lastDate = null;
|
||||
String lastMeal = null;
|
||||
String lastCombo = null;
|
||||
for (CanteenDailyMenuImportExcel row : rows) {
|
||||
if (isBlankRow(row)) {
|
||||
continue;
|
||||
}
|
||||
if (StrUtil.isBlank(row.getBizDate())) {
|
||||
row.setBizDate(lastDate);
|
||||
}
|
||||
else {
|
||||
lastDate = StrUtil.trim(row.getBizDate());
|
||||
row.setBizDate(lastDate);
|
||||
}
|
||||
if (StrUtil.isBlank(row.getMealName())) {
|
||||
row.setMealName(lastMeal);
|
||||
}
|
||||
else {
|
||||
lastMeal = StrUtil.trim(row.getMealName());
|
||||
row.setMealName(lastMeal);
|
||||
}
|
||||
String combo = StrUtil.trim(row.getComboName());
|
||||
if (StrUtil.isBlank(combo)) {
|
||||
row.setComboName(lastCombo);
|
||||
}
|
||||
else if (isSingleComboMarker(combo)) {
|
||||
row.setComboName(null);
|
||||
lastCombo = null;
|
||||
}
|
||||
else {
|
||||
lastCombo = combo;
|
||||
row.setComboName(combo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isSingleComboMarker(String combo) {
|
||||
return "无".equals(combo) || "-".equals(combo) || "单品".equals(combo)
|
||||
|| "(无)".equals(combo) || "(无)".equals(combo);
|
||||
}
|
||||
|
||||
private static Integer parseMeal(String meal) {
|
||||
if (StrUtil.isBlank(meal)) {
|
||||
return null;
|
||||
}
|
||||
String m = meal.trim();
|
||||
if (m.contains("早")) {
|
||||
return 1;
|
||||
}
|
||||
if (m.contains("午") || m.contains("中")) {
|
||||
return 2;
|
||||
}
|
||||
if (m.contains("晚")) {
|
||||
return 3;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static LocalDate parseDate(String s) {
|
||||
if (StrUtil.isBlank(s)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return LocalDate.parse(s.trim(), DATE_FMT);
|
||||
}
|
||||
catch (DateTimeParseException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static Integer toFen(BigDecimal yuan) {
|
||||
if (yuan == null) {
|
||||
return null;
|
||||
}
|
||||
return yuan.multiply(new BigDecimal("100")).setScale(0, RoundingMode.HALF_UP).intValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* 菜品单价:优先「菜品单价(元)」;未填则单品可回退「套餐价(元)」兼容旧模板;都无则 0。
|
||||
*/
|
||||
private static int resolveDishPriceFen(CanteenDailyMenuImportExcel item) {
|
||||
Integer dishFen = toFen(item.getDishPriceYuan());
|
||||
if (dishFen != null) {
|
||||
return Math.max(dishFen, 0);
|
||||
}
|
||||
Integer legacy = toFen(item.getPriceYuan());
|
||||
if (legacy != null && StrUtil.isBlank(item.getComboName())) {
|
||||
return Math.max(legacy, 0);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static String cleanDishName(String raw) {
|
||||
if (raw == null) {
|
||||
return "";
|
||||
}
|
||||
String s = raw.replace("\r", "\n").trim();
|
||||
String first = s.split("\n")[0].trim();
|
||||
first = first.replaceAll("[\\p{So}\\p{Cn}]", "").trim();
|
||||
String cn = first.replaceAll("[A-Za-z].*$", "").trim();
|
||||
cn = cn.replaceAll("[@#%&&]+$", "").replaceAll("\\s+", "");
|
||||
if (cn.length() >= 2) {
|
||||
return cn;
|
||||
}
|
||||
return first.replaceAll("\\s+", " ").trim();
|
||||
}
|
||||
|
||||
private static class FindOrCreate {
|
||||
final CanteenSpuEntity spu;
|
||||
final boolean created;
|
||||
|
||||
FindOrCreate(CanteenSpuEntity spu, boolean created) {
|
||||
this.spu = spu;
|
||||
this.created = created;
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-4
@@ -57,10 +57,8 @@ public class CanteenInventoryRecordsServiceImpl extends ServiceImpl<CanteenInven
|
||||
public Object saveCanteenInventoryRecordsdDTO(CanteenInventoryRecordsdDTO canteenInventoryRecordsdDTO) {
|
||||
|
||||
CanteenInventoryRecordsEntity canteenInventoryRecordsEntity= BeanUtils.toBean(canteenInventoryRecordsdDTO, CanteenInventoryRecordsEntity.class);
|
||||
merchantScopeHelper.fillDeptIdByMerchantId(canteenInventoryRecordsEntity.getMerchantId(), canteenInventoryRecordsEntity::setDeptId);
|
||||
if (canteenInventoryRecordsEntity.getDeptId() == null) {
|
||||
canteenInventoryRecordsEntity.setDeptId(SecurityUtils.getUser().getDeptId());
|
||||
}
|
||||
merchantScopeHelper.fillDeptIdByMerchantId(canteenInventoryRecordsEntity.getMerchantId(),
|
||||
canteenInventoryRecordsEntity::setDeptId);
|
||||
canteenInventoryRecordsEntity.setCheckStatus(TaskCheckStatusEnum.PADDING.getCode());
|
||||
// canteenInventoryRecordsEntity.setCheckStatus(TaskCheckStatusEnum.RESULT.getCode());
|
||||
baseMapper.insert(canteenInventoryRecordsEntity);
|
||||
|
||||
+3
-2
@@ -22,6 +22,7 @@ import com.bsd.pjcloud.canteen.mapper.CanteenMenuRecipeMapper;
|
||||
import com.bsd.pjcloud.canteen.service.CanteenMealPlanService;
|
||||
import com.bsd.pjcloud.canteen.service.CanteenMenuRecipeCategoryService;
|
||||
import com.bsd.pjcloud.canteen.service.CanteenMenuRecipeService;
|
||||
import com.bsd.pjcloud.canteen.util.CanteenMerchantScopeHelper;
|
||||
import com.bsd.pjcloud.canteen.vo.mealplan.CanteenMealPlanExportRow;
|
||||
import com.bsd.pjcloud.canteen.vo.mealplan.CanteenMealPlanMaterialBriefVO;
|
||||
import com.bsd.pjcloud.canteen.vo.mealplan.CanteenMealPlanVO;
|
||||
@@ -29,7 +30,6 @@ import com.bsd.pjcloud.canteen.vo.menurecipe.CanteenMenuRecipeVO;
|
||||
import com.bsd.pjcloud.common.core.util.BeanUtils;
|
||||
import com.bsd.pjcloud.common.core.util.R;
|
||||
import com.bsd.pjcloud.common.data.datascope.DataScope;
|
||||
import com.bsd.pjcloud.common.security.util.SecurityUtils;
|
||||
import com.bsd.pjcloud.wallet.api.feign.RemoteMchService;
|
||||
import com.bsd.pjcloud.wallet.api.vo.MchManageVo;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
@@ -65,6 +65,7 @@ public class CanteenMealPlanServiceImpl extends ServiceImpl<CanteenMealPlanMappe
|
||||
private final CanteenMenuRecipeMapper menuRecipeMapper;
|
||||
private final CanteenMenuRecipeCategoryService recipeCategoryService;
|
||||
private final RemoteMchService remoteMchService;
|
||||
private final CanteenMerchantScopeHelper merchantScopeHelper;
|
||||
|
||||
@Override
|
||||
public Page<CanteenMealPlanEntity> pageScope(Page<CanteenMealPlanEntity> page,
|
||||
@@ -78,7 +79,7 @@ public class CanteenMealPlanServiceImpl extends ServiceImpl<CanteenMealPlanMappe
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long savePlan(CanteenMealPlanSaveDTO dto) {
|
||||
CanteenMealPlanEntity entity = toEntity(dto);
|
||||
entity.setDeptId(SecurityUtils.getUser().getDeptId());
|
||||
merchantScopeHelper.fillDeptIdByMerchantIdOrCurrentUser(entity.getMerchantId(), entity::setDeptId);
|
||||
if (entity.getMatchMode() == null) {
|
||||
entity.setMatchMode(1);
|
||||
}
|
||||
|
||||
+5
-2
@@ -18,13 +18,13 @@ import com.bsd.pjcloud.canteen.mapper.CanteenMenuRecipeMaterialMapper;
|
||||
import com.bsd.pjcloud.canteen.mapper.CanteenOnlineSpuMapper;
|
||||
import com.bsd.pjcloud.canteen.service.CanteenMenuRecipeCategoryService;
|
||||
import com.bsd.pjcloud.canteen.service.CanteenMenuRecipeService;
|
||||
import com.bsd.pjcloud.canteen.util.CanteenMerchantScopeHelper;
|
||||
import com.bsd.pjcloud.canteen.util.CustomerRecipeExcelParser;
|
||||
import com.bsd.pjcloud.canteen.vo.menurecipe.CanteenMenuRecipeImportResultVO;
|
||||
import com.bsd.pjcloud.canteen.vo.menurecipe.CanteenMenuRecipeMaterialVO;
|
||||
import com.bsd.pjcloud.canteen.vo.menurecipe.CanteenMenuRecipeVO;
|
||||
import com.bsd.pjcloud.common.core.util.BeanUtils;
|
||||
import com.bsd.pjcloud.common.data.datascope.DataScope;
|
||||
import com.bsd.pjcloud.common.security.util.SecurityUtils;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -43,6 +43,7 @@ public class CanteenMenuRecipeServiceImpl extends ServiceImpl<CanteenMenuRecipeM
|
||||
private final CanteenErpMaterialMapper erpMaterialMapper;
|
||||
private final CanteenOnlineSpuMapper onlineSpuMapper;
|
||||
private final CanteenMenuRecipeCategoryService recipeCategoryService;
|
||||
private final CanteenMerchantScopeHelper merchantScopeHelper;
|
||||
|
||||
@Override
|
||||
public Page<CanteenMenuRecipeEntity> pageScope(Page<CanteenMenuRecipeEntity> page,
|
||||
@@ -252,7 +253,9 @@ public class CanteenMenuRecipeServiceImpl extends ServiceImpl<CanteenMenuRecipeM
|
||||
|
||||
CanteenMenuRecipeImportResultVO result = new CanteenMenuRecipeImportResultVO();
|
||||
result.setCategory(cat);
|
||||
Long deptId = SecurityUtils.getUser().getDeptId();
|
||||
final Long[] deptHolder = new Long[1];
|
||||
merchantScopeHelper.fillDeptIdByMerchantIdOrCurrentUser(merchantId, d -> deptHolder[0] = d);
|
||||
Long deptId = deptHolder[0];
|
||||
String mch = StrUtil.blankToDefault(merchantId, null);
|
||||
|
||||
Map<String, CanteenErpMaterialEntity> materialCache = new HashMap<>();
|
||||
|
||||
+4
-14
@@ -1,15 +1,13 @@
|
||||
package com.bsd.pjcloud.canteen.service.impl;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.bsd.pjcloud.canteen.entity.CanteenOnlineCategoryEntity;
|
||||
import com.bsd.pjcloud.canteen.mapper.CanteenOnlineCategoryMapper;
|
||||
import com.bsd.pjcloud.canteen.service.CanteenOnlineCategoryService;
|
||||
import com.bsd.pjcloud.canteen.util.CanteenMerchantScopeHelper;
|
||||
import com.bsd.pjcloud.common.data.datascope.DataScope;
|
||||
import com.bsd.pjcloud.wallet.api.feign.RemoteMchService;
|
||||
import com.bsd.pjcloud.wallet.api.vo.MchManageVo;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@@ -25,7 +23,7 @@ import java.util.List;
|
||||
@AllArgsConstructor
|
||||
public class CanteenOnlineCategoryServiceImpl extends ServiceImpl<CanteenOnlineCategoryMapper, CanteenOnlineCategoryEntity> implements CanteenOnlineCategoryService {
|
||||
|
||||
private final RemoteMchService remoteMchService;
|
||||
private final CanteenMerchantScopeHelper merchantScopeHelper;
|
||||
|
||||
@Override
|
||||
public Object pageScope(Page page, LambdaQueryWrapper<CanteenOnlineCategoryEntity> wrapper) {
|
||||
@@ -42,11 +40,7 @@ public class CanteenOnlineCategoryServiceImpl extends ServiceImpl<CanteenOnlineC
|
||||
*/
|
||||
@Override
|
||||
public Long create(CanteenOnlineCategoryEntity dto) {
|
||||
// 查询商户对应的部门id
|
||||
MchManageVo mchVo = remoteMchService.getById(dto.getMerchantId()).getData();
|
||||
if (ObjectUtil.isNotEmpty(mchVo)) {
|
||||
dto.setDeptId(mchVo.getDeptId());
|
||||
}
|
||||
merchantScopeHelper.fillDeptIdByMerchantId(dto.getMerchantId(), dto::setDeptId);
|
||||
baseMapper.insert(dto);
|
||||
return dto.getId();
|
||||
}
|
||||
@@ -56,11 +50,7 @@ public class CanteenOnlineCategoryServiceImpl extends ServiceImpl<CanteenOnlineC
|
||||
*/
|
||||
@Override
|
||||
public Long edit(CanteenOnlineCategoryEntity dto) {
|
||||
// 查询商户对应的部门id
|
||||
MchManageVo mchVo = remoteMchService.getById(dto.getMerchantId()).getData();
|
||||
if (ObjectUtil.isNotEmpty(mchVo)) {
|
||||
dto.setDeptId(mchVo.getDeptId());
|
||||
}
|
||||
merchantScopeHelper.fillDeptIdByMerchantId(dto.getMerchantId(), dto::setDeptId);
|
||||
baseMapper.updateById(dto);
|
||||
return dto.getId();
|
||||
}
|
||||
|
||||
+3
-2
@@ -34,7 +34,6 @@ import com.bsd.pjcloud.canteen.vo.erp.CanteenOnlineSpuSimpleVO;
|
||||
import com.bsd.pjcloud.common.core.util.BeanUtils;
|
||||
import com.bsd.pjcloud.common.core.util.R;
|
||||
import com.bsd.pjcloud.common.data.datascope.DataScope;
|
||||
import com.bsd.pjcloud.common.security.util.SecurityUtils;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
@@ -107,6 +106,7 @@ public class CanteenOnlineSpuServiceImpl extends ServiceImpl<CanteenOnlineSpuMap
|
||||
public Boolean saveDeep(CanteenOnlineSpuEntity entity) {
|
||||
normalizeSpuType(entity);
|
||||
validateCombo(entity, null);
|
||||
merchantScopeHelper.fillDeptIdByMerchantId(entity.getMerchantId(), entity::setDeptId);
|
||||
baseMapper.insert(entity);
|
||||
saveComboItems(entity.getId(), entity.getSpuType(), entity.getComboItems());
|
||||
return Boolean.TRUE;
|
||||
@@ -117,6 +117,7 @@ public class CanteenOnlineSpuServiceImpl extends ServiceImpl<CanteenOnlineSpuMap
|
||||
public Boolean updateDeep(CanteenOnlineSpuEntity entity) {
|
||||
normalizeSpuType(entity);
|
||||
validateCombo(entity, entity.getId());
|
||||
merchantScopeHelper.fillDeptIdByMerchantId(entity.getMerchantId(), entity::setDeptId);
|
||||
baseMapper.updateById(entity);
|
||||
comboItemMapper.delete(Wrappers.<CanteenOnlineComboItemEntity>lambdaQuery()
|
||||
.eq(CanteenOnlineComboItemEntity::getComboSpuId, entity.getId()));
|
||||
@@ -586,7 +587,7 @@ public class CanteenOnlineSpuServiceImpl extends ServiceImpl<CanteenOnlineSpuMap
|
||||
Map<Long, CanteenCategoryEntity> canteenCategoryEntityMap = canteenCategoryEntities.stream()
|
||||
.collect(Collectors.toMap(CanteenCategoryEntity::getId, c -> c));
|
||||
|
||||
Long deptId = SecurityUtils.getUser().getDeptId();
|
||||
Long deptId = merchantScopeHelper.requireDeptIdByMerchantId(merchantId);
|
||||
for (CanteenOnlineSpuEntity c : canteenSpuEntities) {
|
||||
if (c.getChoiceGroupId() != null) {
|
||||
CanteenOnlineSpuChoiceGroupEntity g = canteenOnlineSpuChoiceGroupService.getById(c.getChoiceGroupId());
|
||||
|
||||
+1188
File diff suppressed because it is too large.
Load diff
+31
@@ -134,6 +134,31 @@ public class CanteenSpuServiceImpl extends ServiceImpl<CanteenSpuMapper, Canteen
|
||||
return pageData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CanteenSpuEntity> listForExport(String name, Long categoryId, Long merchantId, Long[] ids) {
|
||||
// 与 selectPage(菜品列表页)保持同一套筛选,避免导出比列表多/变成“全部”
|
||||
LambdaQueryWrapper<CanteenSpuEntity> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.like(StrUtil.isNotBlank(name), CanteenSpuEntity::getName, name);
|
||||
wrapper.eq(Objects.nonNull(categoryId), CanteenSpuEntity::getCategoryId, categoryId);
|
||||
merchantScopeHelper.applyMerchantIdScope(wrapper, merchantId, CanteenSpuEntity::getMerchantId);
|
||||
|
||||
LambdaQueryWrapper<CanteenOnlineSpuEntity> onlineQw = new LambdaQueryWrapper<>();
|
||||
onlineQw.isNotNull(CanteenOnlineSpuEntity::getLinkSpuId);
|
||||
merchantScopeHelper.applyMerchantIdScope(onlineQw, merchantId, CanteenOnlineSpuEntity::getMerchantId);
|
||||
List<CanteenOnlineSpuEntity> onlineList = canteenOnlineSpuMapper.selectListByScope(onlineQw, DataScope.of());
|
||||
Set<Long> linkedIds = onlineList.stream()
|
||||
.map(CanteenOnlineSpuEntity::getLinkSpuId)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toSet());
|
||||
wrapper.notIn(!linkedIds.isEmpty(), CanteenSpuEntity::getId, linkedIds);
|
||||
|
||||
if (ids != null && ids.length > 0) {
|
||||
wrapper.in(CanteenSpuEntity::getId, Arrays.asList(ids));
|
||||
}
|
||||
wrapper.orderByAsc(CanteenSpuEntity::getSort).orderByDesc(CanteenSpuEntity::getId);
|
||||
return baseMapper.selectListByScope(wrapper, DataScope.of());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object pageDailyScope(Page page, CanteenDailySpuDTO canteenDailySpuDTO) {
|
||||
LambdaQueryWrapper<CanteenSpuEntity> wrapper = Wrappers.lambdaQuery();
|
||||
@@ -297,6 +322,12 @@ public class CanteenSpuServiceImpl extends ServiceImpl<CanteenSpuMapper, Canteen
|
||||
public Boolean saveDeep(CanteenSpuEntity entity) {
|
||||
normalizeSpuType(entity);
|
||||
validateCombo(entity, null);
|
||||
if (entity.getMerchantId() == null) {
|
||||
throw new IllegalArgumentException("商户不能为空");
|
||||
}
|
||||
if (entity.getPicUrl() == null) {
|
||||
entity.setPicUrl("");
|
||||
}
|
||||
merchantScopeHelper.fillDeptIdByMerchantId(entity.getMerchantId(), entity::setDeptId);
|
||||
baseMapper.insert(entity);
|
||||
saveComboItems(entity.getId(), entity.getSpuType(), entity.getComboItems());
|
||||
|
||||
+69
-3
@@ -161,20 +161,86 @@ public class CanteenMerchantScopeHelper {
|
||||
}
|
||||
|
||||
/**
|
||||
* 按商户回填 deptId(写入时与商户部门对齐,保证 DataScope 与商户隔离一致)
|
||||
* 按商户回填 deptId(写入时与商户部门对齐,保证 DataScope 与商户隔离一致)。
|
||||
* <p>有 merchantId 时必须能解析到商户部门,失败直接抛错,禁止静默落成管理员部门。
|
||||
*/
|
||||
public void fillDeptIdByMerchantId(Long merchantId, Consumer<Long> deptIdSetter) {
|
||||
if (merchantId == null || deptIdSetter == null) {
|
||||
return;
|
||||
}
|
||||
deptIdSetter.accept(requireDeptIdByMerchantId(merchantId));
|
||||
}
|
||||
|
||||
/**
|
||||
* merchantId 为字符串时的回填(排菜菜谱 / 排餐计划等)
|
||||
*/
|
||||
public void fillDeptIdByMerchantId(String merchantId, Consumer<Long> deptIdSetter) {
|
||||
if (StrUtil.isBlank(merchantId) || deptIdSetter == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
fillDeptIdByMerchantId(Long.parseLong(merchantId.trim()), deptIdSetter);
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException("商户ID格式错误:" + merchantId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 有商户则写商户部门;无商户则写当前登录人部门(仅商户可选场景)
|
||||
*/
|
||||
public void fillDeptIdByMerchantIdOrCurrentUser(Long merchantId, Consumer<Long> deptIdSetter) {
|
||||
if (deptIdSetter == null) {
|
||||
return;
|
||||
}
|
||||
if (merchantId != null) {
|
||||
deptIdSetter.accept(requireDeptIdByMerchantId(merchantId));
|
||||
return;
|
||||
}
|
||||
PjcloudUser user = SecurityUtils.getUser();
|
||||
if (user == null || user.getDeptId() == null) {
|
||||
throw new IllegalArgumentException("无法确定所属部门,请选择商户或检查账号部门");
|
||||
}
|
||||
deptIdSetter.accept(user.getDeptId());
|
||||
}
|
||||
|
||||
public void fillDeptIdByMerchantIdOrCurrentUser(String merchantId, Consumer<Long> deptIdSetter) {
|
||||
if (StrUtil.isBlank(merchantId)) {
|
||||
fillDeptIdByMerchantIdOrCurrentUser((Long) null, deptIdSetter);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
fillDeptIdByMerchantIdOrCurrentUser(Long.parseLong(merchantId.trim()), deptIdSetter);
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException("商户ID格式错误:" + merchantId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析商户绑定部门;查不到或未配置部门时抛错,避免写入当前管理员 dept_id
|
||||
*/
|
||||
public Long requireDeptIdByMerchantId(Long merchantId) {
|
||||
if (merchantId == null) {
|
||||
throw new IllegalArgumentException("商户不能为空");
|
||||
}
|
||||
try {
|
||||
R<MchManageVo> r = remoteMchService.getById(merchantId);
|
||||
if (r != null && r.isOk() && ObjectUtil.isNotEmpty(r.getData()) && r.getData().getDeptId() != null) {
|
||||
deptIdSetter.accept(r.getData().getDeptId());
|
||||
if (r == null || !r.isOk() || ObjectUtil.isEmpty(r.getData())) {
|
||||
throw new IllegalArgumentException("商户不存在:" + merchantId);
|
||||
}
|
||||
Long deptId = r.getData().getDeptId();
|
||||
if (deptId == null) {
|
||||
throw new IllegalArgumentException("商户未绑定部门,请先在商户管理中配置部门:" + merchantId);
|
||||
}
|
||||
return deptId;
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
throw e;
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.warn("按商户回填部门失败, merchantId={}", merchantId, e);
|
||||
throw new IllegalArgumentException("按商户获取部门失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -101,9 +101,9 @@ public class UserExcelVO implements Serializable {
|
||||
private String staffingTypeParse;
|
||||
|
||||
/**
|
||||
* 锁定标记
|
||||
* 启用状态(导出:启用/禁用;导入仍兼容 0/9)
|
||||
*/
|
||||
@ExcelProperty("锁定标记,0:正常,9:已锁定")
|
||||
@ExcelProperty("启用状态")
|
||||
private String lockFlag;
|
||||
|
||||
/**
|
||||
|
||||
+23
-1
@@ -166,6 +166,13 @@ public class SysUserImportExportServiceImpl implements SysUserImportExportServic
|
||||
excelVO.setStaffingType(staffingType.getDescription());
|
||||
}
|
||||
}
|
||||
// 启用状态:0=启用,9=禁用(与列表开关一致)
|
||||
if ("0".equals(userVO.getLockFlag())) {
|
||||
excelVO.setLockFlag("启用");
|
||||
}
|
||||
else if ("9".equals(userVO.getLockFlag())) {
|
||||
excelVO.setLockFlag("禁用");
|
||||
}
|
||||
return excelVO;
|
||||
}).toList();
|
||||
}
|
||||
@@ -729,7 +736,7 @@ public class SysUserImportExportServiceImpl implements SysUserImportExportServic
|
||||
user.setNickname(excel.getNickname());
|
||||
user.setName(excel.getName());
|
||||
user.setEmail(excel.getEmail());
|
||||
user.setLockFlag(excel.getLockFlag());
|
||||
user.setLockFlag(parseLockFlag(excel.getLockFlag()));
|
||||
user.setStaffingType(excel.getStaffingTypeParse());
|
||||
user.setPasswordModifyTime(LocalDateTime.now());
|
||||
user.setPassword(excel.getPhone());
|
||||
@@ -739,6 +746,21 @@ public class SysUserImportExportServiceImpl implements SysUserImportExportServic
|
||||
sysUserService.saveUser(user);
|
||||
}
|
||||
|
||||
/** 启用状态:启用/0 → 0;禁用/9 → 9;空默认启用 */
|
||||
private static String parseLockFlag(String raw) {
|
||||
if (StrUtil.isBlank(raw)) {
|
||||
return "0";
|
||||
}
|
||||
String v = raw.trim();
|
||||
if ("启用".equals(v) || "正常".equals(v) || "0".equals(v)) {
|
||||
return "0";
|
||||
}
|
||||
if ("禁用".equals(v) || "已锁定".equals(v) || "锁定".equals(v) || "9".equals(v)) {
|
||||
return "9";
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建学生用户及监护关系,学生扩展信息只属于 STUDENT 身份。
|
||||
*
|
||||
|
||||
@@ -219,6 +219,9 @@
|
||||
<if test="query.staffingType != null">
|
||||
AND u.staffing_type = #{query.staffingType}
|
||||
</if>
|
||||
<if test="query.lockFlag != null and query.lockFlag != ''">
|
||||
AND u.lock_flag = #{query.lockFlag}
|
||||
</if>
|
||||
<if test="query.role != null and query.role.size() != 0">
|
||||
and urole.role_id IN
|
||||
<foreach collection="query.role" item="v" open="(" close=")" separator=",">
|
||||
@@ -300,13 +303,33 @@
|
||||
LEFT JOIN sys_user_role urole ON urole.user_id = u.user_id
|
||||
<where>
|
||||
u.del_flag = '0'
|
||||
<if test="query.userId != null">
|
||||
AND u.user_id = #{query.userId}
|
||||
</if>
|
||||
<if test="query.processStatus != null and query.processStatus != '' ">
|
||||
AND u.process_status = #{query.processStatus}
|
||||
</if>
|
||||
<if test="query.username != null and query.username != ''">
|
||||
<bind name="usernameLike" value="'%'+query.username+'%'"/>
|
||||
AND u.username LIKE #{usernameLike}
|
||||
</if>
|
||||
<if test="query.name != null and query.name != ''">
|
||||
<bind name="nameLike" value="'%'+query.name+'%'"/>
|
||||
AND u.name LIKE #{nameLike}
|
||||
</if>
|
||||
<if test="query.deptId != null and query.deptId != ''">
|
||||
AND u.dept_id = #{query.deptId}
|
||||
</if>
|
||||
<if test="query.staffingType != null">
|
||||
AND u.staffing_type = #{query.staffingType}
|
||||
</if>
|
||||
<if test="query.lockFlag != null and query.lockFlag != ''">
|
||||
AND u.lock_flag = #{query.lockFlag}
|
||||
</if>
|
||||
<if test="query.phone != null and query.phone != ''">
|
||||
<bind name="phoneLike" value="'%'+query.phone+'%'"/>
|
||||
AND u.phone LIKE #{phoneLike}
|
||||
</if>
|
||||
<if test="query.role != null and query.role.size() != 0">
|
||||
and urole.role_id IN
|
||||
<foreach collection="query.role" item="v" open="(" close=")" separator=",">
|
||||
@@ -335,6 +358,12 @@
|
||||
#{id}
|
||||
</foreach>
|
||||
</if>
|
||||
<if test="query.userIds != null and query.userIds.size() != 0">
|
||||
and u.user_id IN
|
||||
<foreach collection="query.userIds" item="v" open="(" close=")" separator=",">
|
||||
#{v}
|
||||
</foreach>
|
||||
</if>
|
||||
</where>
|
||||
group by u.user_id
|
||||
<if test="_databaseId != 'mssql'">
|
||||
|
||||
Reference in new issue
Block a user