|
@@ -0,0 +1,529 @@
|
|
|
|
|
+package shop.alien.store.service.impl;
|
|
|
|
|
+
|
|
|
|
|
+import com.alibaba.fastjson.JSONObject;
|
|
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
|
|
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
|
|
|
|
+import com.baomidou.mybatisplus.core.metadata.IPage;
|
|
|
|
|
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
|
|
|
+import lombok.RequiredArgsConstructor;
|
|
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
|
|
+import org.springframework.transaction.annotation.Transactional;
|
|
|
|
|
+import org.springframework.util.StringUtils;
|
|
|
|
|
+import shop.alien.entity.result.R;
|
|
|
|
|
+import shop.alien.entity.store.StoreServiceFeeRule;
|
|
|
|
|
+import shop.alien.entity.store.StoreServiceFeeRuleSlot;
|
|
|
|
|
+import shop.alien.entity.store.StoreServiceFeeRuleTable;
|
|
|
|
|
+import shop.alien.entity.store.dto.StoreServiceFeeRuleSaveDto;
|
|
|
|
|
+import shop.alien.entity.store.dto.StoreServiceFeeRuleSlotDto;
|
|
|
|
|
+import shop.alien.entity.store.vo.StoreBookingTableVo;
|
|
|
|
|
+import shop.alien.entity.store.vo.StoreServiceFeeRuleDetailVo;
|
|
|
|
|
+import shop.alien.entity.store.vo.StoreServiceFeeRuleListVo;
|
|
|
|
|
+import shop.alien.mapper.StoreServiceFeeRuleMapper;
|
|
|
|
|
+import shop.alien.mapper.StoreServiceFeeRuleSlotMapper;
|
|
|
|
|
+import shop.alien.mapper.StoreServiceFeeRuleTableMapper;
|
|
|
|
|
+import shop.alien.store.service.StoreBookingTableService;
|
|
|
|
|
+import shop.alien.store.service.StoreServiceFeeRuleService;
|
|
|
|
|
+import shop.alien.util.common.JwtUtil;
|
|
|
|
|
+
|
|
|
|
|
+import java.text.ParseException;
|
|
|
|
|
+import java.text.SimpleDateFormat;
|
|
|
|
|
+import java.time.LocalDate;
|
|
|
|
|
+import java.time.LocalDateTime;
|
|
|
|
|
+import java.time.LocalTime;
|
|
|
|
|
+import java.time.format.DateTimeFormatter;
|
|
|
|
|
+import java.util.ArrayList;
|
|
|
|
|
+import java.util.Collections;
|
|
|
|
|
+import java.util.Comparator;
|
|
|
|
|
+import java.util.Date;
|
|
|
|
|
+import java.util.HashMap;
|
|
|
|
|
+import java.util.HashSet;
|
|
|
|
|
+import java.util.List;
|
|
|
|
|
+import java.util.Map;
|
|
|
|
|
+import java.util.Set;
|
|
|
|
|
+import java.util.stream.Collectors;
|
|
|
|
|
+
|
|
|
|
|
+@Slf4j
|
|
|
|
|
+@Service
|
|
|
|
|
+@Transactional(rollbackFor = Exception.class)
|
|
|
|
|
+@RequiredArgsConstructor
|
|
|
|
|
+public class StoreServiceFeeRuleServiceImpl implements StoreServiceFeeRuleService {
|
|
|
|
|
+
|
|
|
|
|
+ private static final String MODE_PERMANENT = "PERMANENT";
|
|
|
|
|
+ private static final String MODE_CUSTOM = "CUSTOM";
|
|
|
|
|
+ private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss");
|
|
|
|
|
+ private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
|
|
|
|
|
+
|
|
|
|
|
+ private final StoreServiceFeeRuleMapper ruleMapper;
|
|
|
|
|
+ private final StoreServiceFeeRuleTableMapper ruleTableMapper;
|
|
|
|
|
+ private final StoreServiceFeeRuleSlotMapper ruleSlotMapper;
|
|
|
|
|
+ private final StoreBookingTableService storeBookingTableService;
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public R<IPage<StoreServiceFeeRuleListVo>> listRules(Integer storeId, String name, Integer status, Integer pageNum, Integer pageSize) {
|
|
|
|
|
+ if (storeId == null) {
|
|
|
|
|
+ return R.fail("门店ID不能为空");
|
|
|
|
|
+ }
|
|
|
|
|
+ int pn = pageNum == null || pageNum < 1 ? 1 : pageNum;
|
|
|
|
|
+ int ps = pageSize == null || pageSize < 1 ? 10 : pageSize;
|
|
|
|
|
+ Page<StoreServiceFeeRule> page = new Page<>(pn, ps);
|
|
|
|
|
+ LambdaQueryWrapper<StoreServiceFeeRule> wrapper = new LambdaQueryWrapper<>();
|
|
|
|
|
+ wrapper.eq(StoreServiceFeeRule::getStoreId, storeId);
|
|
|
|
|
+ if (StringUtils.hasText(name)) {
|
|
|
|
|
+ wrapper.like(StoreServiceFeeRule::getFeeName, name.trim());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (status != null) {
|
|
|
|
|
+ wrapper.eq(StoreServiceFeeRule::getStatus, status);
|
|
|
|
|
+ }
|
|
|
|
|
+ wrapper.orderByDesc(StoreServiceFeeRule::getUpdatedTime);
|
|
|
|
|
+ IPage<StoreServiceFeeRule> rulePage = ruleMapper.selectPage(page, wrapper);
|
|
|
|
|
+
|
|
|
|
|
+ List<Integer> ruleIds = rulePage.getRecords().stream().map(StoreServiceFeeRule::getId).collect(Collectors.toList());
|
|
|
|
|
+ Map<Integer, Integer> tableCountMap = buildRuleTableCountMap(ruleIds);
|
|
|
|
|
+ Map<Integer, List<String>> ruleTableNamesMap = buildRuleTableNamesMap(storeId, ruleIds);
|
|
|
|
|
+
|
|
|
|
|
+ List<StoreServiceFeeRuleListVo> records = rulePage.getRecords().stream().map(rule -> {
|
|
|
|
|
+ StoreServiceFeeRuleListVo vo = new StoreServiceFeeRuleListVo();
|
|
|
|
|
+ vo.setId(rule.getId());
|
|
|
|
|
+ vo.setStoreId(rule.getStoreId());
|
|
|
|
|
+ vo.setFeeName(rule.getFeeName());
|
|
|
|
|
+ vo.setFeeType(rule.getFeeType());
|
|
|
|
|
+ vo.setFeeValue(rule.getFeeValue());
|
|
|
|
|
+ vo.setStatus(rule.getStatus());
|
|
|
|
|
+ vo.setEffectiveMode(rule.getEffectiveMode());
|
|
|
|
|
+ vo.setUpdatedTime(rule.getUpdatedTime());
|
|
|
|
|
+ vo.setTableCount(tableCountMap.getOrDefault(rule.getId(), 0));
|
|
|
|
|
+ vo.setTableNameList(ruleTableNamesMap.getOrDefault(rule.getId(), Collections.emptyList()));
|
|
|
|
|
+ return vo;
|
|
|
|
|
+ }).collect(Collectors.toList());
|
|
|
|
|
+
|
|
|
|
|
+ Page<StoreServiceFeeRuleListVo> result = new Page<>(pn, ps, rulePage.getTotal());
|
|
|
|
|
+ result.setRecords(records);
|
|
|
|
|
+ return R.data(result);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public R<StoreServiceFeeRuleDetailVo> getRuleDetail(Integer id) {
|
|
|
|
|
+ if (id == null) {
|
|
|
|
|
+ return R.fail("规则ID不能为空");
|
|
|
|
|
+ }
|
|
|
|
|
+ StoreServiceFeeRule rule = ruleMapper.selectById(id);
|
|
|
|
|
+ if (rule == null) {
|
|
|
|
|
+ return R.fail("服务费规则不存在");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ StoreServiceFeeRuleDetailVo vo = new StoreServiceFeeRuleDetailVo();
|
|
|
|
|
+ vo.setId(rule.getId());
|
|
|
|
|
+ vo.setStoreId(rule.getStoreId());
|
|
|
|
|
+ vo.setFeeName(rule.getFeeName());
|
|
|
|
|
+ vo.setFeeType(rule.getFeeType());
|
|
|
|
|
+ vo.setFeeValue(rule.getFeeValue());
|
|
|
|
|
+ vo.setStatus(rule.getStatus());
|
|
|
|
|
+ vo.setEffectiveMode(rule.getEffectiveMode());
|
|
|
|
|
+ vo.setStartDate(formatDate(rule.getStartDate()));
|
|
|
|
|
+ vo.setEndDate(formatDate(rule.getEndDate()));
|
|
|
|
|
+
|
|
|
|
|
+ List<StoreServiceFeeRuleTable> tableList = ruleTableMapper.selectList(
|
|
|
|
|
+ new LambdaQueryWrapper<StoreServiceFeeRuleTable>().eq(StoreServiceFeeRuleTable::getRuleId, id));
|
|
|
|
|
+ vo.setTableIds(tableList.stream().map(StoreServiceFeeRuleTable::getTableId).collect(Collectors.toList()));
|
|
|
|
|
+
|
|
|
|
|
+ List<StoreServiceFeeRuleSlot> slotList = ruleSlotMapper.selectList(
|
|
|
|
|
+ new LambdaQueryWrapper<StoreServiceFeeRuleSlot>().eq(StoreServiceFeeRuleSlot::getRuleId, id));
|
|
|
|
|
+ vo.setSlots(slotList.stream().map(this::toSlotDto).collect(Collectors.toList()));
|
|
|
|
|
+ log.info("StoreServiceFeeRuleServiceImpl.getRuleDetail?接口出参vo={}", vo);
|
|
|
|
|
+ return R.data(vo);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public R<Integer> createRule(StoreServiceFeeRuleSaveDto dto) {
|
|
|
|
|
+ log.info("StoreServiceFeeRuleServiceImpl.createRule?dto={}", dto);
|
|
|
|
|
+ String checkMsg = validateAndCheckConflict(dto, null);
|
|
|
|
|
+ if (checkMsg != null) {
|
|
|
|
|
+ return R.fail(checkMsg);
|
|
|
|
|
+ }
|
|
|
|
|
+ Integer userId = getCurrentUserId();
|
|
|
|
|
+ StoreServiceFeeRule rule = new StoreServiceFeeRule();
|
|
|
|
|
+ fillRuleFromDto(rule, dto, userId, false);
|
|
|
|
|
+ ruleMapper.insert(rule);
|
|
|
|
|
+ saveChildren(rule.getId(), dto, userId);
|
|
|
|
|
+ return R.data(rule.getId());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public R<Boolean> updateRule(StoreServiceFeeRuleSaveDto dto) {
|
|
|
|
|
+ if (dto == null || dto.getId() == null) {
|
|
|
|
|
+ return R.fail("规则ID不能为空");
|
|
|
|
|
+ }
|
|
|
|
|
+ StoreServiceFeeRule existing = ruleMapper.selectById(dto.getId());
|
|
|
|
|
+ if (existing == null) {
|
|
|
|
|
+ return R.fail("服务费规则不存在");
|
|
|
|
|
+ }
|
|
|
|
|
+ String checkMsg = validateAndCheckConflict(dto, dto.getId());
|
|
|
|
|
+ if (checkMsg != null) {
|
|
|
|
|
+ return R.fail(checkMsg);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Integer userId = getCurrentUserId();
|
|
|
|
|
+ StoreServiceFeeRule updateRule = new StoreServiceFeeRule();
|
|
|
|
|
+ updateRule.setId(dto.getId());
|
|
|
|
|
+ fillRuleFromDto(updateRule, dto, userId, true);
|
|
|
|
|
+ ruleMapper.updateById(updateRule);
|
|
|
|
|
+
|
|
|
|
|
+ ruleTableMapper.delete(new LambdaQueryWrapper<StoreServiceFeeRuleTable>().eq(StoreServiceFeeRuleTable::getRuleId, dto.getId()));
|
|
|
|
|
+ ruleSlotMapper.delete(new LambdaQueryWrapper<StoreServiceFeeRuleSlot>().eq(StoreServiceFeeRuleSlot::getRuleId, dto.getId()));
|
|
|
|
|
+ saveChildren(dto.getId(), dto, userId);
|
|
|
|
|
+ return R.data(true);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public R<Boolean> deleteRule(Integer id) {
|
|
|
|
|
+ if (id == null) {
|
|
|
|
|
+ return R.fail("规则ID不能为空");
|
|
|
|
|
+ }
|
|
|
|
|
+ StoreServiceFeeRule existing = ruleMapper.selectById(id);
|
|
|
|
|
+ if (existing == null) {
|
|
|
|
|
+ return R.fail("服务费规则不存在");
|
|
|
|
|
+ }
|
|
|
|
|
+ ruleMapper.deleteById(id);
|
|
|
|
|
+ ruleTableMapper.delete(new LambdaQueryWrapper<StoreServiceFeeRuleTable>().eq(StoreServiceFeeRuleTable::getRuleId, id));
|
|
|
|
|
+ ruleSlotMapper.delete(new LambdaQueryWrapper<StoreServiceFeeRuleSlot>().eq(StoreServiceFeeRuleSlot::getRuleId, id));
|
|
|
|
|
+ return R.data(true);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public R<List<StoreBookingTableVo>> getTableList(Integer storeId, Integer categoryId) {
|
|
|
|
|
+ if (storeId == null) {
|
|
|
|
|
+ return R.fail("门店ID不能为空");
|
|
|
|
|
+ }
|
|
|
|
|
+ List<StoreBookingTableVo> list = storeBookingTableService.getTableListWithCategoryName(storeId, categoryId);
|
|
|
|
|
+ return R.data(list);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public int autoCloseExpiredCustomRules() {
|
|
|
|
|
+ LocalDate today = LocalDate.now();
|
|
|
|
|
+ LocalDateTime now = LocalDateTime.now();
|
|
|
|
|
+ List<StoreServiceFeeRule> candidates = ruleMapper.selectList(
|
|
|
|
|
+ new LambdaQueryWrapper<StoreServiceFeeRule>()
|
|
|
|
|
+ .eq(StoreServiceFeeRule::getStatus, 1)
|
|
|
|
|
+ .eq(StoreServiceFeeRule::getEffectiveMode, MODE_CUSTOM)
|
|
|
|
|
+ .isNotNull(StoreServiceFeeRule::getEndDate)
|
|
|
|
|
+ .le(StoreServiceFeeRule::getEndDate, java.sql.Date.valueOf(today)));
|
|
|
|
|
+ if (candidates == null || candidates.isEmpty()) {
|
|
|
|
|
+ return 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ List<Integer> ruleIds = candidates.stream().map(StoreServiceFeeRule::getId).collect(Collectors.toList());
|
|
|
|
|
+ List<StoreServiceFeeRuleSlot> allSlots = ruleSlotMapper.selectList(
|
|
|
|
|
+ new LambdaQueryWrapper<StoreServiceFeeRuleSlot>().in(StoreServiceFeeRuleSlot::getRuleId, ruleIds));
|
|
|
|
|
+ Map<Integer, List<StoreServiceFeeRuleSlot>> slotMap = allSlots.stream()
|
|
|
|
|
+ .collect(Collectors.groupingBy(StoreServiceFeeRuleSlot::getRuleId));
|
|
|
|
|
+
|
|
|
|
|
+ int closed = 0;
|
|
|
|
|
+ for (StoreServiceFeeRule rule : candidates) {
|
|
|
|
|
+ LocalDate endDate = new java.sql.Date(rule.getEndDate().getTime()).toLocalDate();
|
|
|
|
|
+ LocalTime maxEndTime = slotMap.getOrDefault(rule.getId(), Collections.emptyList())
|
|
|
|
|
+ .stream()
|
|
|
|
|
+ .map(StoreServiceFeeRuleSlot::getEndTime)
|
|
|
|
|
+ .filter(t -> t != null)
|
|
|
|
|
+ .max(LocalTime::compareTo)
|
|
|
|
|
+ .orElse(LocalTime.of(23, 59, 59));
|
|
|
|
|
+ LocalDateTime expireAt = LocalDateTime.of(endDate, maxEndTime);
|
|
|
|
|
+ if (!now.isBefore(expireAt)) {
|
|
|
|
|
+ StoreServiceFeeRule update = new StoreServiceFeeRule();
|
|
|
|
|
+ update.setId(rule.getId());
|
|
|
|
|
+ update.setStatus(0);
|
|
|
|
|
+ update.setUpdatedUserId(0);
|
|
|
|
|
+ if (ruleMapper.updateById(update) > 0) {
|
|
|
|
|
+ closed++;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if (closed > 0) {
|
|
|
|
|
+ log.info("自动关闭过期服务费规则完成,本次关闭数量={}", closed);
|
|
|
|
|
+ }
|
|
|
|
|
+ return closed;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private Map<Integer, Integer> buildRuleTableCountMap(List<Integer> ruleIds) {
|
|
|
|
|
+ if (ruleIds == null || ruleIds.isEmpty()) {
|
|
|
|
|
+ return Collections.emptyMap();
|
|
|
|
|
+ }
|
|
|
|
|
+ List<StoreServiceFeeRuleTable> tableList = ruleTableMapper.selectList(
|
|
|
|
|
+ new LambdaQueryWrapper<StoreServiceFeeRuleTable>().in(StoreServiceFeeRuleTable::getRuleId, ruleIds));
|
|
|
|
|
+ Map<Integer, Set<Integer>> temp = new HashMap<>();
|
|
|
|
|
+ for (StoreServiceFeeRuleTable row : tableList) {
|
|
|
|
|
+ temp.computeIfAbsent(row.getRuleId(), k -> new HashSet<>()).add(row.getTableId());
|
|
|
|
|
+ }
|
|
|
|
|
+ Map<Integer, Integer> countMap = new HashMap<>();
|
|
|
|
|
+ temp.forEach((k, v) -> countMap.put(k, v.size()));
|
|
|
|
|
+ return countMap;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private Map<Integer, List<String>> buildRuleTableNamesMap(Integer storeId, List<Integer> ruleIds) {
|
|
|
|
|
+ if (ruleIds == null || ruleIds.isEmpty()) {
|
|
|
|
|
+ return Collections.emptyMap();
|
|
|
|
|
+ }
|
|
|
|
|
+ List<StoreServiceFeeRuleTable> relations = ruleTableMapper.selectList(
|
|
|
|
|
+ new LambdaQueryWrapper<StoreServiceFeeRuleTable>().in(StoreServiceFeeRuleTable::getRuleId, ruleIds));
|
|
|
|
|
+ if (relations == null || relations.isEmpty()) {
|
|
|
|
|
+ return Collections.emptyMap();
|
|
|
|
|
+ }
|
|
|
|
|
+ Map<Integer, String> tableIdNameMap = storeBookingTableService.getTableListWithCategoryName(storeId, null).stream()
|
|
|
|
|
+ .collect(Collectors.toMap(StoreBookingTableVo::getId, StoreBookingTableVo::getTableNumber, (a, b) -> a));
|
|
|
|
|
+
|
|
|
|
|
+ Map<Integer, List<String>> groupedNames = new HashMap<>();
|
|
|
|
|
+ for (StoreServiceFeeRuleTable relation : relations) {
|
|
|
|
|
+ String tableNumber = tableIdNameMap.get(relation.getTableId());
|
|
|
|
|
+ if (!StringUtils.hasText(tableNumber)) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ groupedNames.computeIfAbsent(relation.getRuleId(), k -> new ArrayList<>()).add(tableNumber);
|
|
|
|
|
+ }
|
|
|
|
|
+ Map<Integer, List<String>> result = new HashMap<>();
|
|
|
|
|
+ groupedNames.forEach((ruleId, names) -> result.put(ruleId, names.stream().distinct().collect(Collectors.toList())));
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void fillRuleFromDto(StoreServiceFeeRule rule, StoreServiceFeeRuleSaveDto dto, Integer userId, boolean isUpdate) {
|
|
|
|
|
+ rule.setStoreId(dto.getStoreId());
|
|
|
|
|
+ rule.setFeeName(dto.getFeeName().trim());
|
|
|
|
|
+ rule.setFeeType(dto.getFeeType());
|
|
|
|
|
+ rule.setFeeValue(dto.getFeeValue());
|
|
|
|
|
+ rule.setStatus(dto.getStatus());
|
|
|
|
|
+ rule.setEffectiveMode(dto.getEffectiveMode());
|
|
|
|
|
+ if (MODE_CUSTOM.equals(dto.getEffectiveMode())) {
|
|
|
|
|
+ rule.setStartDate(parseDate(dto.getStartDate()));
|
|
|
|
|
+ rule.setEndDate(parseDate(dto.getEndDate()));
|
|
|
|
|
+ } else {
|
|
|
|
|
+ rule.setStartDate(null);
|
|
|
|
|
+ rule.setEndDate(null);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (isUpdate) {
|
|
|
|
|
+ rule.setUpdatedUserId(userId);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ rule.setCreatedUserId(userId);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void saveChildren(Integer ruleId, StoreServiceFeeRuleSaveDto dto, Integer userId) {
|
|
|
|
|
+ for (Integer tableId : dto.getTableIds()) {
|
|
|
|
|
+ StoreServiceFeeRuleTable row = new StoreServiceFeeRuleTable();
|
|
|
|
|
+ row.setRuleId(ruleId);
|
|
|
|
|
+ row.setStoreId(dto.getStoreId());
|
|
|
|
|
+ row.setTableId(tableId);
|
|
|
|
|
+ row.setCreatedUserId(userId);
|
|
|
|
|
+ row.setUpdatedUserId(userId);
|
|
|
|
|
+ ruleTableMapper.insert(row);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ for (StoreServiceFeeRuleSlotDto slotDto : dto.getSlots()) {
|
|
|
|
|
+ StoreServiceFeeRuleSlot slot = new StoreServiceFeeRuleSlot();
|
|
|
|
|
+ slot.setRuleId(ruleId);
|
|
|
|
|
+ slot.setWeekdayMask(slotDto.getWeekdayMask());
|
|
|
|
|
+ slot.setStartTime(LocalTime.parse(slotDto.getStartTime(), TIME_FORMATTER));
|
|
|
|
|
+ slot.setEndTime(LocalTime.parse(slotDto.getEndTime(), TIME_FORMATTER));
|
|
|
|
|
+ slot.setCreatedUserId(userId);
|
|
|
|
|
+ slot.setUpdatedUserId(userId);
|
|
|
|
|
+ ruleSlotMapper.insert(slot);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private String validateAndCheckConflict(StoreServiceFeeRuleSaveDto dto, Integer excludeRuleId) {
|
|
|
|
|
+ String basicMsg = validateBasic(dto);
|
|
|
|
|
+ if (basicMsg != null) {
|
|
|
|
|
+ return basicMsg;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 名称校验(同店不可重复)
|
|
|
|
|
+ LambdaQueryWrapper<StoreServiceFeeRule> nameWrapper = new LambdaQueryWrapper<>();
|
|
|
|
|
+ nameWrapper.eq(StoreServiceFeeRule::getStoreId, dto.getStoreId())
|
|
|
|
|
+ .eq(StoreServiceFeeRule::getFeeName, dto.getFeeName().trim());
|
|
|
|
|
+ if (excludeRuleId != null) {
|
|
|
|
|
+ nameWrapper.ne(StoreServiceFeeRule::getId, excludeRuleId);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (ruleMapper.selectCount(nameWrapper) > 0) {
|
|
|
|
|
+ return "服务费名称已存在";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 状态为关闭时,仅做名称校验即可(不参与冲突检查)
|
|
|
|
|
+ if (dto.getStatus() != null && dto.getStatus() == 0) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return checkTimeConflict(dto, excludeRuleId);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private String validateBasic(StoreServiceFeeRuleSaveDto dto) {
|
|
|
|
|
+ if (dto == null) {
|
|
|
|
|
+ return "请求参数不能为空";
|
|
|
|
|
+ }
|
|
|
|
|
+ if (dto.getStoreId() == null) {
|
|
|
|
|
+ return "门店ID不能为空";
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!StringUtils.hasText(dto.getFeeName())) {
|
|
|
|
|
+ return "服务费名称不能为空";
|
|
|
|
|
+ }
|
|
|
|
|
+ if (dto.getFeeType() == null) {
|
|
|
|
|
+ return "服务费类型不能为空";
|
|
|
|
|
+ }
|
|
|
|
|
+ if (dto.getFeeValue() == null) {
|
|
|
|
|
+ return "服务费金额不能为空";
|
|
|
|
|
+ }
|
|
|
|
|
+ if (dto.getStatus() == null) {
|
|
|
|
|
+ return "服务费状态不能为空";
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!StringUtils.hasText(dto.getEffectiveMode())) {
|
|
|
|
|
+ return "生效模式不能为空";
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!MODE_PERMANENT.equals(dto.getEffectiveMode()) && !MODE_CUSTOM.equals(dto.getEffectiveMode())) {
|
|
|
|
|
+ return "生效模式不合法";
|
|
|
|
|
+ }
|
|
|
|
|
+ if (dto.getTableIds() == null || dto.getTableIds().isEmpty()) {
|
|
|
|
|
+ return "适用桌台不能为空";
|
|
|
|
|
+ }
|
|
|
|
|
+ if (dto.getSlots() == null || dto.getSlots().isEmpty()) {
|
|
|
|
|
+ return "生效时段不能为空";
|
|
|
|
|
+ }
|
|
|
|
|
+ if (MODE_CUSTOM.equals(dto.getEffectiveMode())) {
|
|
|
|
|
+ if (!StringUtils.hasText(dto.getStartDate()) || !StringUtils.hasText(dto.getEndDate())) {
|
|
|
|
|
+ return "自定义日期模式下开始/结束日期不能为空";
|
|
|
|
|
+ }
|
|
|
|
|
+ Date start = parseDate(dto.getStartDate());
|
|
|
|
|
+ Date end = parseDate(dto.getEndDate());
|
|
|
|
|
+ if (start.after(end)) {
|
|
|
|
|
+ return "开始日期不能大于结束日期";
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ for (StoreServiceFeeRuleSlotDto slot : dto.getSlots()) {
|
|
|
|
|
+ if (slot.getWeekdayMask() == null || slot.getWeekdayMask() <= 0) {
|
|
|
|
|
+ return "生效星期不能为空";
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!StringUtils.hasText(slot.getStartTime()) || !StringUtils.hasText(slot.getEndTime())) {
|
|
|
|
|
+ return "生效时间不能为空";
|
|
|
|
|
+ }
|
|
|
|
|
+ LocalTime st = LocalTime.parse(slot.getStartTime(), TIME_FORMATTER);
|
|
|
|
|
+ LocalTime et = LocalTime.parse(slot.getEndTime(), TIME_FORMATTER);
|
|
|
|
|
+ if (!st.isBefore(et)) {
|
|
|
|
|
+ return "生效开始时间必须早于结束时间";
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private String checkTimeConflict(StoreServiceFeeRuleSaveDto dto, Integer excludeRuleId) {
|
|
|
|
|
+ List<Integer> tableIds = dto.getTableIds().stream().distinct().collect(Collectors.toList());
|
|
|
|
|
+ // 找到这些桌台对应的已启用规则
|
|
|
|
|
+ LambdaQueryWrapper<StoreServiceFeeRuleTable> tableWrapper = new LambdaQueryWrapper<>();
|
|
|
|
|
+ tableWrapper.eq(StoreServiceFeeRuleTable::getStoreId, dto.getStoreId())
|
|
|
|
|
+ .in(StoreServiceFeeRuleTable::getTableId, tableIds);
|
|
|
|
|
+ List<StoreServiceFeeRuleTable> relationList = ruleTableMapper.selectList(tableWrapper);
|
|
|
|
|
+ if (relationList.isEmpty()) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Map<Integer, Set<Integer>> ruleTableMap = new HashMap<>();
|
|
|
|
|
+ for (StoreServiceFeeRuleTable rt : relationList) {
|
|
|
|
|
+ ruleTableMap.computeIfAbsent(rt.getRuleId(), k -> new HashSet<>()).add(rt.getTableId());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ List<Integer> candidateRuleIds = new ArrayList<>(ruleTableMap.keySet());
|
|
|
|
|
+ LambdaQueryWrapper<StoreServiceFeeRule> ruleWrapper = new LambdaQueryWrapper<>();
|
|
|
|
|
+ ruleWrapper.in(StoreServiceFeeRule::getId, candidateRuleIds)
|
|
|
|
|
+ .eq(StoreServiceFeeRule::getStoreId, dto.getStoreId())
|
|
|
|
|
+ .eq(StoreServiceFeeRule::getStatus, 1);
|
|
|
|
|
+ if (excludeRuleId != null) {
|
|
|
|
|
+ ruleWrapper.ne(StoreServiceFeeRule::getId, excludeRuleId);
|
|
|
|
|
+ }
|
|
|
|
|
+ List<StoreServiceFeeRule> existRules = ruleMapper.selectList(ruleWrapper);
|
|
|
|
|
+ if (existRules.isEmpty()) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ List<Integer> validRuleIds = existRules.stream().map(StoreServiceFeeRule::getId).collect(Collectors.toList());
|
|
|
|
|
+ List<StoreServiceFeeRuleSlot> allExistSlots = ruleSlotMapper.selectList(
|
|
|
|
|
+ new LambdaQueryWrapper<StoreServiceFeeRuleSlot>().in(StoreServiceFeeRuleSlot::getRuleId, validRuleIds));
|
|
|
|
|
+ Map<Integer, List<StoreServiceFeeRuleSlot>> slotMap = allExistSlots.stream()
|
|
|
|
|
+ .collect(Collectors.groupingBy(StoreServiceFeeRuleSlot::getRuleId));
|
|
|
|
|
+
|
|
|
|
|
+ Date newStartDate = MODE_CUSTOM.equals(dto.getEffectiveMode()) ? parseDate(dto.getStartDate()) : toDate(LocalDate.of(1970, 1, 1));
|
|
|
|
|
+ Date newEndDate = MODE_CUSTOM.equals(dto.getEffectiveMode()) ? parseDate(dto.getEndDate()) : toDate(LocalDate.of(2099, 12, 31));
|
|
|
|
|
+ List<StoreServiceFeeRuleSlotDto> newSlots = dto.getSlots();
|
|
|
|
|
+
|
|
|
|
|
+ for (StoreServiceFeeRule existRule : existRules) {
|
|
|
|
|
+ Date oldStart = MODE_CUSTOM.equals(existRule.getEffectiveMode()) ? existRule.getStartDate() : toDate(LocalDate.of(1970, 1, 1));
|
|
|
|
|
+ Date oldEnd = MODE_CUSTOM.equals(existRule.getEffectiveMode()) ? existRule.getEndDate() : toDate(LocalDate.of(2099, 12, 31));
|
|
|
|
|
+ if (!isDateOverlap(newStartDate, newEndDate, oldStart, oldEnd)) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ List<StoreServiceFeeRuleSlot> oldSlots = slotMap.getOrDefault(existRule.getId(), Collections.emptyList());
|
|
|
|
|
+ if (oldSlots.isEmpty()) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ for (StoreServiceFeeRuleSlotDto ns : newSlots) {
|
|
|
|
|
+ LocalTime nsStart = LocalTime.parse(ns.getStartTime(), TIME_FORMATTER);
|
|
|
|
|
+ LocalTime nsEnd = LocalTime.parse(ns.getEndTime(), TIME_FORMATTER);
|
|
|
|
|
+ for (StoreServiceFeeRuleSlot os : oldSlots) {
|
|
|
|
|
+ if ((ns.getWeekdayMask() & os.getWeekdayMask()) <= 0) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (isTimeOverlap(nsStart, nsEnd, os.getStartTime(), os.getEndTime())) {
|
|
|
|
|
+ Set<Integer> overlapTables = new HashSet<>(ruleTableMap.getOrDefault(existRule.getId(), Collections.emptySet()));
|
|
|
|
|
+ overlapTables.retainAll(new HashSet<>(tableIds));
|
|
|
|
|
+ if (!overlapTables.isEmpty()) {
|
|
|
|
|
+ String tableTip = overlapTables.stream().sorted(Comparator.naturalOrder()).map(String::valueOf).collect(Collectors.joining(","));
|
|
|
|
|
+ if (!dto.getFeeType().equals(existRule.getFeeType())) {
|
|
|
|
|
+ return "桌台[" + tableTip + "]在该时间段已配置其他类型服务费";
|
|
|
|
|
+ }
|
|
|
|
|
+ return "桌台[" + tableTip + "]服务费时间重叠,请调整生效日期/星期/时间";
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private boolean isDateOverlap(Date s1, Date e1, Date s2, Date e2) {
|
|
|
|
|
+ return !s1.after(e2) && !s2.after(e1);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private boolean isTimeOverlap(LocalTime s1, LocalTime e1, LocalTime s2, LocalTime e2) {
|
|
|
|
|
+ return s1.isBefore(e2) && s2.isBefore(e1);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private Date parseDate(String dateStr) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ return DATE_FORMAT.parse(dateStr);
|
|
|
|
|
+ } catch (ParseException e) {
|
|
|
|
|
+ throw new IllegalArgumentException("日期格式错误,必须是yyyy-MM-dd");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private String formatDate(Date date) {
|
|
|
|
|
+ return date == null ? null : DATE_FORMAT.format(date);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private Date toDate(LocalDate localDate) {
|
|
|
|
|
+ return java.sql.Date.valueOf(localDate);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private StoreServiceFeeRuleSlotDto toSlotDto(StoreServiceFeeRuleSlot slot) {
|
|
|
|
|
+ StoreServiceFeeRuleSlotDto dto = new StoreServiceFeeRuleSlotDto();
|
|
|
|
|
+ dto.setWeekdayMask(slot.getWeekdayMask());
|
|
|
|
|
+ dto.setStartTime(slot.getStartTime() != null ? slot.getStartTime().format(TIME_FORMATTER) : null);
|
|
|
|
|
+ dto.setEndTime(slot.getEndTime() != null ? slot.getEndTime().format(TIME_FORMATTER) : null);
|
|
|
|
|
+ return dto;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private Integer getCurrentUserId() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ JSONObject userInfo = JwtUtil.getCurrentUserInfo();
|
|
|
|
|
+ if (userInfo != null) {
|
|
|
|
|
+ return userInfo.getInteger("userId");
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.warn("获取当前登录用户ID失败: {}", e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|