MrShi
10 小时以前 e50954f0708ecbbc672352102ae3b24279d40cc1
server/services/src/main/java/com/doumee/service/business/impl/ShopInfoServiceImpl.java
@@ -1,24 +1,50 @@
package com.doumee.service.business.impl;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.doumee.config.jwt.JwtTokenUtil;
import com.doumee.core.constants.Constants;
import com.doumee.core.constants.ResponseStatus;
import com.doumee.core.exception.BusinessException;
import com.doumee.core.model.PageData;
import com.doumee.core.model.PageWrap;
import com.doumee.core.utils.Utils;
import com.doumee.biz.system.AreasBiz;
import com.doumee.biz.system.SystemDictDataBiz;
import com.doumee.dao.business.MemberMapper;
import com.doumee.dao.business.MultifileMapper;
import com.doumee.dao.business.OrdersMapper;
import com.doumee.dao.business.PricingRuleMapper;
import com.doumee.dao.business.ShopInfoMapper;
import com.doumee.dao.business.model.Areas;
import com.doumee.dao.business.model.Member;
import com.doumee.dao.business.model.Multifile;
import com.doumee.dao.business.model.Orders;
import com.doumee.dao.business.model.PricingRule;
import com.doumee.dao.business.model.ShopInfo;
import com.doumee.dao.dto.*;
import com.doumee.dao.system.SystemUserMapper;
import com.doumee.dao.system.model.SystemUser;
import com.doumee.dao.vo.ShopDetailVO;
import com.doumee.dao.vo.ShopCenterVO;
import com.doumee.dao.vo.ShopLoginVO;
import com.doumee.dao.vo.ShopNearbyVO;
import com.doumee.dao.vo.ShopWebDetailVO;
import com.doumee.service.business.AreasService;
import com.doumee.service.business.ShopInfoService;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Objects;
import java.util.*;
import java.util.stream.Collectors;
/**
 * 门店信息Service实现
@@ -31,6 +57,32 @@
    @Autowired
    private ShopInfoMapper shopInfoMapper;
    @Autowired
    private MemberMapper memberMapper;
    @Autowired
    private MultifileMapper multifileMapper;
    @Autowired
    private SystemDictDataBiz systemDictDataBiz;
    @Autowired
    private SystemUserMapper systemUserMapper;
    @Autowired
    private AreasBiz areasBiz;
    @Autowired
    private RedisTemplate<String,Object> redisTemplate;
    @Autowired
    private PricingRuleMapper pricingRuleMapper;
    @Autowired
    private OrdersMapper ordersMapper;
    @Autowired
    private AreasService areasService;
    @Override
    public Integer create(ShopInfo shopInfo) {
        shopInfoMapper.insert(shopInfo);
@@ -50,7 +102,7 @@
    @Override
    public void deleteByIdInBatch(List<Integer> ids) {
        if (CollectionUtils.isEmpty(ids)) {
        if (ids == null || ids.isEmpty()) {
            return;
        }
        shopInfoMapper.deleteBatchIds(ids);
@@ -63,7 +115,7 @@
    @Override
    public void updateByIdInBatch(List<ShopInfo> shopInfos) {
        if (CollectionUtils.isEmpty(shopInfos)) {
        if (shopInfos == null || shopInfos.isEmpty()) {
            return;
        }
        for (ShopInfo shopInfo : shopInfos) {
@@ -111,6 +163,12 @@
            queryWrapper.lambda().ge(ShopInfo::getCreateTime, Utils.Date.getStart(pageWrap.getModel().getCreateTime()));
            queryWrapper.lambda().le(ShopInfo::getCreateTime, Utils.Date.getEnd(pageWrap.getModel().getCreateTime()));
        }
        if (pageWrap.getModel().getCreateStartTime() != null) {
            queryWrapper.lambda().ge(ShopInfo::getCreateTime, Utils.Date.getStart(pageWrap.getModel().getCreateStartTime()));
        }
        if (pageWrap.getModel().getCreateEndTime() != null) {
            queryWrapper.lambda().le(ShopInfo::getCreateTime, Utils.Date.getEnd(pageWrap.getModel().getCreateEndTime()));
        }
        if (pageWrap.getModel().getUpdateUser() != null) {
            queryWrapper.lambda().eq(ShopInfo::getUpdateUser, pageWrap.getModel().getUpdateUser());
        }
@@ -139,6 +197,12 @@
        if (pageWrap.getModel().getAreaId() != null) {
            queryWrapper.lambda().eq(ShopInfo::getAreaId, pageWrap.getModel().getAreaId());
        }
        if (pageWrap.getModel().getAuditStatus() != null) {
            queryWrapper.lambda().eq(ShopInfo::getAuditStatus, pageWrap.getModel().getAuditStatus());
        }
        if (pageWrap.getModel().getAuditStatusList() != null && !pageWrap.getModel().getAuditStatusList().isEmpty()) {
            queryWrapper.lambda().in(ShopInfo::getAuditStatus, pageWrap.getModel().getAuditStatusList());
        }
        if (pageWrap.getModel().getStatus() != null) {
            queryWrapper.lambda().eq(ShopInfo::getStatus, pageWrap.getModel().getStatus());
        }
@@ -168,4 +232,931 @@
        return shopInfoMapper.selectCount(wrapper);
    }
    // ========== 门店入驻业务 ==========
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void applyShop(ShopApplyDTO request, Member member) {
        Integer memberId = member.getId();
        // 1. 校验门店手机号唯一性(shop_info.telephone)
        checkTelephoneUnique(request.getTelephone(), null);
        // 2. 根据类型校验附件
        if (Constants.equalsInteger(request.getCompanyType(), Constants.ZERO)) {
            // 个人类型:必须上传劳动合同和社保证明
            if (CollectionUtils.isEmpty(request.getLaborContractImgs())) {
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "个人类型必须上传有效劳动合同");
            }
            if (CollectionUtils.isEmpty(request.getSocialSecurityImgs())) {
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "个人类型必须上传社保缴纳证明");
            }
        } else if (Constants.equalsInteger(request.getCompanyType(), Constants.ONE)) {
            // 企业类型:必须填写法人信息和营业执照
            if (StringUtils.isBlank(request.getBusinessImg())) {
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "企业类型必须上传营业执照");
            }
            if (StringUtils.isBlank(request.getLegalPersonName())) {
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "企业类型必须填写法人姓名");
            }
            if (StringUtils.isBlank(request.getLegalPersonPhone())) {
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "企业类型必须填写法人电话");
            }
            if (StringUtils.isBlank(request.getLegalPersonCard())) {
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "企业类型必须填写法人身份证号码");
            }
        }
        Date now = new Date();
        String rawPassword = generateDefaultPassword(request.getTelephone());
        String salt = RandomStringUtils.randomAlphabetic(6);
        String encryptedPassword = Utils.Secure.encryptPassword(rawPassword, salt);
        // 3. 查询该会员是否已有门店记录
        QueryWrapper<ShopInfo> existQw = new QueryWrapper<>();
        existQw.lambda()
                .eq(ShopInfo::getRegionMemberId, memberId)
                .eq(ShopInfo::getDeleted, Constants.ZERO)
                .last("limit 1");
        ShopInfo existing = shopInfoMapper.selectOne(existQw);
        Integer shopId;
        if (existing != null) {
            // 校验状态:只有待审批(0)和被驳回(2)可修改
            if (!Constants.equalsInteger(existing.getAuditStatus(), Constants.ZERO)
                    && !Constants.equalsInteger(existing.getAuditStatus(), Constants.TWO)) {
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "当前门店状态不允许修改");
            }
            // 校验openid匹配:当前登录会员的openid必须与门店的openid一致
            if (existing.getOpenid() != null && !existing.getOpenid().equals(member.getOpenid())) {
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "无权限操作当前门店信息");
            }
            // 校验手机号唯一性(排除自身)
            if (!request.getTelephone().equals(existing.getTelephone())) {
                checkTelephoneUnique(request.getTelephone(), existing.getId());
            }
            // 更新
            existing.setCompanyType(request.getCompanyType());
            existing.setName(request.getName());
            existing.setTelephone(request.getTelephone());
            existing.setLinkName(request.getLinkName());
            existing.setLinkPhone(request.getLinkPhone());
            existing.setIdcard(request.getIdcard());
            existing.setAreaId(request.getAreaId());
            existing.setLongitude(request.getLongitude());
            existing.setLatitude(request.getLatitude());
            existing.setAddress(request.getAddress());
            existing.setIdcardImg(request.getIdcardImg());
            existing.setIdcardImgBack(request.getIdcardImgBack());
            existing.setBusinessImg(request.getBusinessImg());
            existing.setLegalPersonName(request.getLegalPersonName());
            existing.setLegalPersonPhone(request.getLegalPersonPhone());
            existing.setLegalPersonCard(request.getLegalPersonCard());
            existing.setPassword(encryptedPassword);
            existing.setSalt(salt);
            existing.setAliAccount(request.getAliAccount());
            existing.setAuditStatus(Constants.ZERO);
            existing.setUpdateTime(now);
            existing.setUpdateUser(memberId);
            existing.setAuditRemark(null);
            existing.setAuditTime(null);
            existing.setAuditUserId(null);
            shopInfoMapper.updateById(existing);
            shopId = existing.getId();
        } else {
            // 新建
            ShopInfo shopInfo = new ShopInfo();
            shopInfo.setCompanyType(request.getCompanyType());
            shopInfo.setName(request.getName());
            shopInfo.setTelephone(request.getTelephone());
            shopInfo.setLinkName(request.getLinkName());
            shopInfo.setLinkPhone(request.getLinkPhone());
            shopInfo.setIdcard(request.getIdcard());
            shopInfo.setAreaId(request.getAreaId());
            shopInfo.setLongitude(request.getLongitude());
            shopInfo.setLatitude(request.getLatitude());
            shopInfo.setAddress(request.getAddress());
            shopInfo.setIdcardImg(request.getIdcardImg());
            shopInfo.setIdcardImgBack(request.getIdcardImgBack());
            shopInfo.setBusinessImg(request.getBusinessImg());
            shopInfo.setLegalPersonName(request.getLegalPersonName());
            shopInfo.setLegalPersonPhone(request.getLegalPersonPhone());
            shopInfo.setLegalPersonCard(request.getLegalPersonCard());
            shopInfo.setPassword(encryptedPassword);
            shopInfo.setSalt(salt);
            shopInfo.setAliAccount(request.getAliAccount());
            shopInfo.setOpenid(member.getOpenid());
            shopInfo.setAuditStatus(Constants.ZERO);
            shopInfo.setStatus(Constants.ZERO);
            shopInfo.setDeleted(Constants.ZERO);
            shopInfo.setCreateTime(now);
            shopInfo.setUpdateTime(now);
            shopInfo.setCreateUser(memberId);
            shopInfo.setRegionMemberId(memberId);
            shopInfoMapper.insert(shopInfo);
            shopId = shopInfo.getId();
        }
        // 4. 删除旧附件记录
        multifileMapper.delete(new QueryWrapper<Multifile>().lambda()
                .eq(Multifile::getObjId, shopId)
                .in(Multifile::getObjType,
                        Constants.FileType.STORE_FRONT.getKey(),
                        Constants.FileType.STORE_INTERIOR.getKey(),
                        Constants.FileType.OTHER_MATERIAL.getKey(),
                        Constants.FileType.LABOR_CONTRACT.getKey(),
                        Constants.FileType.SOCIAL_SECURITY.getKey()));
        // 5. 保存新附件记录
        saveMultifileList(shopId, Constants.FileType.STORE_FRONT.getKey(), request.getStoreFrontImgs(), now);
        saveMultifileList(shopId, Constants.FileType.STORE_INTERIOR.getKey(), request.getStoreInteriorImgs(), now);
        saveMultifileList(shopId, Constants.FileType.OTHER_MATERIAL.getKey(), request.getOtherMaterialImgs(), now);
        saveMultifileList(shopId, Constants.FileType.LABOR_CONTRACT.getKey(), request.getLaborContractImgs(), now);
        saveMultifileList(shopId, Constants.FileType.SOCIAL_SECURITY.getKey(), request.getSocialSecurityImgs(), now);
    }
    @Override
    public ShopDetailVO getShopDetail(Integer shopId) {
        ShopInfo shopInfo = shopInfoMapper.selectById(shopId);
        if (shopInfo == null || Constants.equalsInteger(shopInfo.getDeleted(), Constants.ONE)) {
            throw new BusinessException(ResponseStatus.DATA_EMPTY);
        }
        return buildShopDetailVO(shopInfo);
    }
    @Override
    public ShopDetailVO getMyShop(Integer memberId) {
        QueryWrapper<ShopInfo> qw = new QueryWrapper<>();
        qw.lambda()
                .eq(ShopInfo::getRegionMemberId, memberId)
                .eq(ShopInfo::getDeleted, Constants.ZERO)
                .last("limit 1");
        ShopInfo shopInfo = shopInfoMapper.selectOne(qw);
        if (shopInfo == null) {
            return null;
        }
        return buildShopDetailVO(shopInfo);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void auditShop(AuditDTO auditDTO) {
        if (auditDTO.getId() == null) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "主键不能为空");
        }
        if (auditDTO.getAuditStatus() == null
                || (auditDTO.getAuditStatus() != 0 && auditDTO.getAuditStatus() != 1)) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "审批状态参数错误");
        }
        ShopInfo shopInfo = shopInfoMapper.selectById(auditDTO.getId());
        if (shopInfo == null || Constants.equalsInteger(shopInfo.getDeleted(), Constants.ONE)) {
            throw new BusinessException(ResponseStatus.DATA_EMPTY);
        }
        if (!Constants.equalsInteger(shopInfo.getAuditStatus(), Constants.ZERO)) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "当前状态不允许审批");
        }
        Date now = new Date();
        // auditDTO.auditStatus: 0=通过 → auditStatus=1, 1=驳回 → auditStatus=2
        Integer newAuditStatus = Constants.equalsInteger(auditDTO.getAuditStatus(), Constants.ZERO) ? Constants.ONE : Constants.TWO;
        shopInfo.setAuditStatus(newAuditStatus);
        shopInfo.setAuditTime(now);
        shopInfo.setAuditRemark(auditDTO.getAuditRemark());
        shopInfo.setAuditUserId(auditDTO.getAuditUser());
        shopInfo.setUpdateTime(now);
        // 审批通过时,校验城市pricing_rule配置,读取押金金额
        if (Constants.equalsInteger(newAuditStatus, Constants.ONE)) {
            // 1. 解析门店所在城市
            Areas area = areasBiz.resolveArea(shopInfo.getAreaId());
            if (area == null || area.getParentId() == null) {
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "门店区划信息异常,无法确定所在城市");
            }
            Integer cityId = area.getParentId();
            // 2. 校验 pricing_rule 配置(城市开通在押金支付完成后处理)
            Areas cityArea = areasService.findById(cityId, Constants.ONE);
            if (cityArea == null) {
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "城市信息不存在");
            }
            if (!Constants.equalsInteger(cityArea.getStatus(), Constants.ONE)) {
                List<String> errors = validateCityPricingRules(cityId);
                if (!errors.isEmpty()) {
                    throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(),
                            "城市[" + cityArea.getName() + "]尚未开通,定价规则未配置完整:" + String.join(";", errors));
                }
            }
            // 3. 从PricingRule读取押金金额
            PricingRule pricingRule = pricingRuleMapper.selectOne(new QueryWrapper<PricingRule>().lambda()
                    .eq(PricingRule::getDeleted, Constants.ZERO)
                    .eq(PricingRule::getType, Constants.THREE)
                    .eq(PricingRule::getFieldA, String.valueOf(shopInfo.getCompanyType()))
                    .last("limit 1"));
            if (pricingRule != null && StringUtils.isNotBlank(pricingRule.getFieldB())) {
                shopInfo.setDepositAmount(Long.parseLong(pricingRule.getFieldB()));
            }
        }
        shopInfoMapper.updateById(shopInfo);
    }
    @Override
    public void changeStatus(ChangeStatusDTO dto) {
        if (dto.getId() == null) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "主键不能为空");
        }
        if (dto.getStatus() == null || (dto.getStatus() != 0 && dto.getStatus() != 1)) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "状态参数错误,0=启用;1=禁用");
        }
        ShopInfo shopInfo = shopInfoMapper.selectById(dto.getId());
        if (shopInfo == null || Constants.equalsInteger(shopInfo.getDeleted(), Constants.ONE)) {
            throw new BusinessException(ResponseStatus.DATA_EMPTY);
        }
        shopInfo.setStatus(dto.getStatus());
        shopInfo.setUpdateTime(new Date());
        shopInfoMapper.updateById(shopInfo);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void resetPassword(ResetPasswordDTO dto) {
        if (dto.getId() == null) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "门店主键不能为空");
        }
        ShopInfo shopInfo = shopInfoMapper.selectById(dto.getId());
        if (shopInfo == null || Constants.equalsInteger(shopInfo.getDeleted(), Constants.ONE)) {
            throw new BusinessException(ResponseStatus.DATA_EMPTY);
        }
        String rawPassword = generateDefaultPassword(shopInfo.getTelephone());
        String salt = RandomStringUtils.randomAlphabetic(6);
        shopInfo.setPassword(Utils.Secure.encryptPassword(rawPassword, salt));
        shopInfo.setSalt(salt);
        shopInfo.setUpdateTime(new Date());
        shopInfoMapper.updateById(shopInfo);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateShop(ShopUpdateDTO request) {
        if (request.getId() == null) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "门店主键不能为空");
        }
        ShopInfo shopInfo = shopInfoMapper.selectById(request.getId());
        if (shopInfo == null || Constants.equalsInteger(shopInfo.getDeleted(), Constants.ONE)) {
            throw new BusinessException(ResponseStatus.DATA_EMPTY);
        }
        // 校验手机号唯一性(排除自身)
        if (!request.getTelephone().equals(shopInfo.getTelephone())) {
            checkTelephoneUnique(request.getTelephone(), shopInfo.getId());
        }
        // 根据类型校验必填
        if (Constants.equalsInteger(request.getCompanyType(), Constants.ZERO)) {
            // 个人类型:必须上传劳动合同和社保证明
            if (CollectionUtils.isEmpty(request.getLaborContractImgs())) {
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "个人类型必须上传有效劳动合同");
            }
            if (CollectionUtils.isEmpty(request.getSocialSecurityImgs())) {
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "个人类型必须上传社保缴纳证明");
            }
        } else if (Constants.equalsInteger(request.getCompanyType(), Constants.ONE)) {
            // 企业类型:必须填写法人信息和营业执照
            if (StringUtils.isBlank(request.getBusinessImg())) {
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "企业类型必须上传营业执照");
            }
            if (StringUtils.isBlank(request.getLegalPersonName())) {
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "企业类型必须填写法人姓名");
            }
            if (StringUtils.isBlank(request.getLegalPersonPhone())) {
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "企业类型必须填写法人电话");
            }
            if (StringUtils.isBlank(request.getLegalPersonCard())) {
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "企业类型必须填写法人身份证号码");
            }
        }
        Date now = new Date();
        // 更新基础信息
        shopInfo.setCompanyType(request.getCompanyType());
        shopInfo.setName(request.getName());
        shopInfo.setTelephone(request.getTelephone());
        shopInfo.setLinkName(request.getLinkName());
        shopInfo.setLinkPhone(request.getLinkPhone());
        shopInfo.setIdcard(request.getIdcard());
        shopInfo.setAreaId(request.getAreaId());
        shopInfo.setLongitude(request.getLongitude());
        shopInfo.setLatitude(request.getLatitude());
        shopInfo.setAddress(request.getAddress());
        // 企业类信息
        shopInfo.setIdcardImg(request.getIdcardImg());
        shopInfo.setIdcardImgBack(request.getIdcardImgBack());
        shopInfo.setBusinessImg(request.getBusinessImg());
        shopInfo.setLegalPersonName(request.getLegalPersonName());
        shopInfo.setLegalPersonPhone(request.getLegalPersonPhone());
        shopInfo.setLegalPersonCard(request.getLegalPersonCard());
        shopInfo.setUpdateTime(now);
        shopInfoMapper.updateById(shopInfo);
        // 删除旧附件记录
        multifileMapper.delete(new QueryWrapper<Multifile>().lambda()
                .eq(Multifile::getObjId, shopInfo.getId())
                .in(Multifile::getObjType,
                        Constants.FileType.STORE_FRONT.getKey(),
                        Constants.FileType.STORE_INTERIOR.getKey(),
                        Constants.FileType.OTHER_MATERIAL.getKey(),
                        Constants.FileType.LABOR_CONTRACT.getKey(),
                        Constants.FileType.SOCIAL_SECURITY.getKey()));
        // 保存新附件记录
        saveMultifileList(shopInfo.getId(), Constants.FileType.STORE_FRONT.getKey(), request.getStoreFrontImgs(), now);
        saveMultifileList(shopInfo.getId(), Constants.FileType.STORE_INTERIOR.getKey(), request.getStoreInteriorImgs(), now);
        saveMultifileList(shopInfo.getId(), Constants.FileType.OTHER_MATERIAL.getKey(), request.getOtherMaterialImgs(), now);
        saveMultifileList(shopInfo.getId(), Constants.FileType.LABOR_CONTRACT.getKey(), request.getLaborContractImgs(), now);
        saveMultifileList(shopInfo.getId(), Constants.FileType.SOCIAL_SECURITY.getKey(), request.getSocialSecurityImgs(), now);
    }
    // ========== 私有方法 ==========
    /**
     * 生成默认密码:手机号后6位 + @123456
     */
    private String generateDefaultPassword(String telephone) {
        if (StringUtils.isBlank(telephone) || telephone.length() < 6) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "手机号格式异常,无法生成默认密码");
        }
        return telephone.substring(telephone.length() - 6) + "@123456";
    }
    private void checkTelephoneUnique(String telephone, Integer excludeId) {
        QueryWrapper<ShopInfo> qw = new QueryWrapper<>();
        qw.lambda()
                .eq(ShopInfo::getTelephone, telephone)
                .eq(ShopInfo::getDeleted, Constants.ZERO);
        if (excludeId != null) {
            qw.lambda().ne(ShopInfo::getId, excludeId);
        }
        long count = shopInfoMapper.selectCount(qw);
        if (count > 0) {
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "该手机号已注册门店");
        }
    }
    private void saveMultifileList(Integer objId, Integer objType, List<String> urls, Date now) {
        if (CollectionUtils.isEmpty(urls)) {
            return;
        }
        int sortNum = 0;
        for (String url : urls) {
            Multifile multifile = new Multifile();
            multifile.setObjId(objId);
            multifile.setObjType(objType);
            multifile.setType(Constants.ZERO);
            multifile.setFileurl(url);
            multifile.setIsdeleted(Constants.ZERO);
            multifile.setCreateDate(now);
            multifile.setSortnum(sortNum++);
            multifileMapper.insert(multifile);
        }
    }
    private ShopDetailVO buildShopDetailVO(ShopInfo shopInfo) {
        ShopDetailVO vo = new ShopDetailVO();
        vo.setId(shopInfo.getId());
        vo.setCompanyType(shopInfo.getCompanyType());
        vo.setName(shopInfo.getName());
        vo.setTelephone(shopInfo.getTelephone());
        vo.setLinkName(shopInfo.getLinkName());
        vo.setLinkPhone(shopInfo.getLinkPhone());
        vo.setIdcard(shopInfo.getIdcard());
        vo.setAreaId(shopInfo.getAreaId());
        // 填充省市区名称及主键
        Areas area = areasBiz.resolveArea(shopInfo.getAreaId());
        if (area != null) {
            vo.setProvinceId(area.getProvinceId());
            vo.setCityId(area.getCityId());
            vo.setProvinceName(area.getProvinceName());
            vo.setCityName(area.getCityName());
            vo.setAreaName(area.getName());
        }
        vo.setLongitude(shopInfo.getLongitude());
        vo.setLatitude(shopInfo.getLatitude());
        vo.setAddress(shopInfo.getAddress());
        vo.setLegalPersonName(shopInfo.getLegalPersonName());
        vo.setLegalPersonPhone(shopInfo.getLegalPersonPhone());
        vo.setLegalPersonCard(shopInfo.getLegalPersonCard());
        vo.setAuditStatus(shopInfo.getAuditStatus());
        vo.setStatus(shopInfo.getStatus());
        vo.setAuditTime(shopInfo.getAuditTime());
        if(Objects.nonNull(shopInfo.getAuditUserId())){
            SystemUser systemUser = systemUserMapper.selectById(shopInfo.getAuditUserId());
            if (systemUser != null) vo.setAuditName(systemUser.getRealname());
        }
        vo.setAuditRemark(shopInfo.getAuditRemark());
        vo.setOpenid(shopInfo.getOpenid());
        vo.setPayStatus(shopInfo.getPayStatus());
        vo.setScore(shopInfo.getScore());
        vo.setCreateTime(shopInfo.getCreateTime());
        // 拼接图片前缀
        String imgPrefix = "";
        try {
            imgPrefix = systemDictDataBiz.queryByCode(Constants.OSS, Constants.RESOURCE_PATH).getCode()
                    + systemDictDataBiz.queryByCode(Constants.SYSTEM, Constants.SHOP_FILES).getCode();
        } catch (Exception e) {
            // 未配置时忽略
        }
        vo.setImgPrefix(imgPrefix);
        // 单图字段:半路径 + 全路径
        vo.setIdcardImg(shopInfo.getIdcardImg());
        vo.setIdcardImgUrl(StringUtils.isNotBlank(shopInfo.getIdcardImg()) ? imgPrefix + shopInfo.getIdcardImg() : null);
        vo.setIdcardImgBack(shopInfo.getIdcardImgBack());
        vo.setIdcardImgBackUrl(StringUtils.isNotBlank(shopInfo.getIdcardImgBack()) ? imgPrefix + shopInfo.getIdcardImgBack() : null);
        vo.setBusinessImg(shopInfo.getBusinessImg());
        vo.setBusinessImgUrl(StringUtils.isNotBlank(shopInfo.getBusinessImg()) ? imgPrefix + shopInfo.getBusinessImg() : null);
        // 查询附件
        QueryWrapper<Multifile> fileQw = new QueryWrapper<>();
        fileQw.lambda()
                .eq(Multifile::getObjId, shopInfo.getId())
                .eq(Multifile::getIsdeleted, Constants.ZERO)
                .in(Multifile::getObjType,
                        Constants.FileType.STORE_FRONT.getKey(),
                        Constants.FileType.STORE_INTERIOR.getKey(),
                        Constants.FileType.OTHER_MATERIAL.getKey(),
                        Constants.FileType.LABOR_CONTRACT.getKey(),
                        Constants.FileType.SOCIAL_SECURITY.getKey())
                .orderByAsc(Multifile::getObjType, Multifile::getSortnum);
        List<Multifile> files = multifileMapper.selectList(fileQw);
        // 按 objType 分组,半路径 + 全路径
        Map<Integer, List<String>> fileMap = new HashMap<>();
        Map<Integer, List<String>> fileUrlMap = new HashMap<>();
        for (Multifile f : files) {
            fileMap.computeIfAbsent(f.getObjType(), k -> new ArrayList<>()).add(f.getFileurl());
            String fullUrl = StringUtils.isNotBlank(f.getFileurl()) ? imgPrefix + f.getFileurl() : f.getFileurl();
            fileUrlMap.computeIfAbsent(f.getObjType(), k -> new ArrayList<>()).add(fullUrl);
        }
        vo.setStoreFrontImgs(fileMap.getOrDefault(Constants.FileType.STORE_FRONT.getKey(), new ArrayList<>()));
        vo.setStoreInteriorImgs(fileMap.getOrDefault(Constants.FileType.STORE_INTERIOR.getKey(), new ArrayList<>()));
        vo.setOtherMaterialImgs(fileMap.getOrDefault(Constants.FileType.OTHER_MATERIAL.getKey(), new ArrayList<>()));
        vo.setLaborContractImgs(fileMap.getOrDefault(Constants.FileType.LABOR_CONTRACT.getKey(), new ArrayList<>()));
        vo.setSocialSecurityImgs(fileMap.getOrDefault(Constants.FileType.SOCIAL_SECURITY.getKey(), new ArrayList<>()));
        vo.setStoreFrontImgUrls(fileUrlMap.getOrDefault(Constants.FileType.STORE_FRONT.getKey(), new ArrayList<>()));
        vo.setStoreInteriorImgUrls(fileUrlMap.getOrDefault(Constants.FileType.STORE_INTERIOR.getKey(), new ArrayList<>()));
        vo.setOtherMaterialImgUrls(fileUrlMap.getOrDefault(Constants.FileType.OTHER_MATERIAL.getKey(), new ArrayList<>()));
        vo.setLaborContractImgUrls(fileUrlMap.getOrDefault(Constants.FileType.LABOR_CONTRACT.getKey(), new ArrayList<>()));
        vo.setSocialSecurityImgUrls(fileUrlMap.getOrDefault(Constants.FileType.SOCIAL_SECURITY.getKey(), new ArrayList<>()));
        // 查询绑定开户会员头像(payMemberOpenId 关联 member.openid)
        if (StringUtils.isNotBlank(shopInfo.getPayMemberOpenId())) {
            QueryWrapper<Member> memberQw = new QueryWrapper<>();
            memberQw.lambda().eq(Member::getOpenid, shopInfo.getPayMemberOpenId()).last("limit 1");
            Member payMember = memberMapper.selectOne(memberQw);
            if (payMember != null && StringUtils.isNotBlank(payMember.getCoverImage())) {
                String memberPrefix = "";
                try {
                    memberPrefix = systemDictDataBiz.queryByCode(Constants.OSS, Constants.RESOURCE_PATH).getCode()
                            + systemDictDataBiz.queryByCode(Constants.SYSTEM, Constants.MEMBER_FILES).getCode();
                } catch (Exception e) {
                    // 未配置时忽略
                }
                vo.setPayMemberCoverImage(memberPrefix + payMember.getCoverImage());
            }
        }
        return vo;
    }
    @Override
    public PageData<ShopNearbyVO> findNearbyShops(PageWrap<ShopNearbyDTO> pageWrap) {
        IPage<ShopInfo> page = new Page<>(pageWrap.getPage(), pageWrap.getCapacity());
        ShopNearbyDTO dto = pageWrap.getModel();
        Double longitude = dto.getLongitude();
        Double latitude = dto.getLatitude();
        Integer sortType = dto.getSortType();
        Integer distanceMeter = dto.getDistance();
        // Haversine距离公式(单位km)
        String distanceFormula = "(6371 * acos(cos(radians(" + latitude + ")) * cos(radians(latitude)) " +
                "* cos(radians(longitude) - radians(" + longitude + ")) " +
                "+ sin(radians(" + latitude + ")) * sin(radians(latitude))))";
        QueryWrapper<ShopInfo> qw = new QueryWrapper<>();
        qw.lambda()
                .eq(ShopInfo::getDeleted, Constants.ZERO)
                .eq(ShopInfo::getStatus, Constants.ZERO)
                .eq(ShopInfo::getAuditStatus, Constants.THREE);
        // 门店营业类型筛选
        if (dto.getBusinessType() != null) {
            qw.lambda().eq(ShopInfo::getBusinessType, dto.getBusinessType());
        }
        // 门店名称模糊查询
        if (StringUtils.isNotBlank(dto.getName())) {
            qw.lambda().like(ShopInfo::getName, dto.getName());
        }
        // 距离筛选(单位:米 → 转换为km比较)
        if (distanceMeter != null && distanceMeter > 0) {
            double maxKm = distanceMeter / 1000.0;
            qw.apply(distanceFormula + " <= {0}", maxKm);
        }
        // 排序
        if (longitude != null && latitude != null) {
            if (sortType != null && sortType == 2) {
                // 按评分降序
                qw.last("ORDER BY score DESC");
            } else {
                // 默认:按距离升序
                qw.last("ORDER BY " + distanceFormula + " ASC");
            }
        } else {
            qw.lambda().orderByDesc(ShopInfo::getCreateTime);
        }
        IPage<ShopInfo> result = shopInfoMapper.selectPage(page, qw);
        // 图片前缀
        String imgPrefix = getShopPrefix();
        List<ShopNearbyVO> voList = new ArrayList<>();
        for (ShopInfo shop : result.getRecords()) {
            ShopNearbyVO vo = new ShopNearbyVO();
            vo.setId(shop.getId());
            vo.setName(shop.getName());
            vo.setShopHours(shop.getShopHours());
            vo.setAddress(shop.getAddress());
            vo.setScore(shop.getScore());
            // 门头照第一张
            vo.setCoverImg(getFirstImage(shop.getId(), Constants.FileType.STORE_FRONT.getKey(), imgPrefix));
            // 距离
            if (longitude != null && latitude != null && shop.getLongitude() != null && shop.getLatitude() != null) {
                double distKm = haversine(latitude, longitude, shop.getLatitude(), shop.getLongitude());
                vo.setDistanceText(formatDistance(distKm));
            }
            voList.add(vo);
        }
        IPage<ShopNearbyVO> vPage = new Page<>(pageWrap.getPage(), pageWrap.getCapacity());
        PageData<ShopNearbyVO> pageData = PageData.from(vPage);
        pageData.setRecords(voList);
        pageData.setTotal(result.getTotal());
        pageData.setPage(result.getCurrent());
        pageData.setCapacity(result.getSize());
        return pageData;
    }
    @Override
    public ShopWebDetailVO getShopWebDetail(ShopDetailQueryDTO dto) {
        ShopInfo shop = shopInfoMapper.selectById(dto.getId());
        if (Objects.isNull(shop) || Constants.equalsInteger(shop.getDeleted(), Constants.ONE)) {
            throw new BusinessException(ResponseStatus.DATA_EMPTY);
        }
        ShopWebDetailVO vo = new ShopWebDetailVO();
        vo.setId(shop.getId());
        vo.setName(shop.getName());
        vo.setAddress(shop.getAddress());
        vo.setContent(shop.getContent());
        // 门头照 + 内部照 全路径集合
        String imgPrefix = getShopPrefix();
        List<String> images = new ArrayList<>();
        images.addAll(getImageList(dto.getId(), Constants.FileType.STORE_FRONT.getKey(), imgPrefix));
        images.addAll(getImageList(dto.getId(), Constants.FileType.STORE_INTERIOR.getKey(), imgPrefix));
        vo.setImages(images);
        // 距离
        if (dto.getLongitude() != null && dto.getLatitude() != null && shop.getLongitude() != null && shop.getLatitude() != null) {
            double distKm = haversine(dto.getLatitude(), dto.getLongitude(), shop.getLatitude(), shop.getLongitude());
            vo.setDistanceText(formatDistance(distKm));
        }
        return vo;
    }
    @Override
    public void maintainShopInfo(Integer memberId, ShopInfoMaintainDTO dto) {
        // 门店主键与会员主键一致
        ShopInfo shop = shopInfoMapper.selectById(memberId);
        if (Objects.isNull(shop) || Constants.equalsInteger(shop.getDeleted(), Constants.ONE)) {
            throw new BusinessException(ResponseStatus.DATA_EMPTY);
        }
        // 需要支付完押金后才可维护(auditStatus >= 2)
        if (shop.getAuditStatus() == null || shop.getAuditStatus() < Constants.TWO) {
            throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "请先完成押金支付后再维护门店信息");
        }
        UpdateWrapper<ShopInfo> updateWrapper = new UpdateWrapper<>();
        updateWrapper.lambda()
                .eq(ShopInfo::getId, shop.getId())
                .set(ShopInfo::getUpdateTime, new Date())
                .set(dto.getCoverImg() != null, ShopInfo::getCoverImg, dto.getCoverImg())
                .set(dto.getContent() != null, ShopInfo::getContent, dto.getContent())
                .set(dto.getDepositTypes() != null, ShopInfo::getDepositTypes, dto.getDepositTypes())
                .set(dto.getFeeStandard() != null, ShopInfo::getFeeStandard, dto.getFeeStandard())
                .set(dto.getDeliveryArea() != null, ShopInfo::getDeliveryArea, dto.getDeliveryArea())
                .set(dto.getShopHours() != null, ShopInfo::getShopHours, dto.getShopHours())
                .set(dto.getBusinessType() != null, ShopInfo::getBusinessType, dto.getBusinessType());
        shopInfoMapper.update(updateWrapper);
    }
    @Override
    public ShopInfoMaintainDTO getShopMaintainInfo(Integer memberId) {
        ShopInfo shop = shopInfoMapper.selectById(memberId);
        if (Objects.isNull(shop) || Constants.equalsInteger(shop.getDeleted(), Constants.ONE)) {
            return null;
        }
        ShopInfoMaintainDTO dto = new ShopInfoMaintainDTO();
        dto.setCoverImg(shop.getCoverImg());
        dto.setContent(shop.getContent());
        dto.setDepositTypes(shop.getDepositTypes());
        dto.setFeeStandard(shop.getFeeStandard());
        dto.setDeliveryArea(shop.getDeliveryArea());
        dto.setShopHours(shop.getShopHours());
        dto.setBusinessType(shop.getBusinessType());
        return dto;
    }
    /**
     * Haversine公式计算两点间距离(km)
     */
    private double haversine(double lat1, double lng1, double lat2, double lng2) {
        double R = 6371;
        double dLat = Math.toRadians(lat2 - lat1);
        double dLng = Math.toRadians(lng2 - lng1);
        double a = Math.sin(dLat / 2) * Math.sin(dLat / 2)
                + Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2))
                * Math.sin(dLng / 2) * Math.sin(dLng / 2);
        double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
        return R * c;
    }
    /**
     * 格式化距离:小于1km显示米,大于等于1km显示千米(保留1位小数)
     */
    private String formatDistance(double km) {
        if (km < 1) {
            return Math.round(km * 1000) + "m";
        }
        return String.format("%.1fkm", km);
    }
    /**
     * 获取门店图片前缀
     */
    private String getShopPrefix() {
        try {
            return systemDictDataBiz.queryByCode(Constants.OSS, Constants.RESOURCE_PATH).getCode()
                    + systemDictDataBiz.queryByCode(Constants.SYSTEM, Constants.SHOP_FILES).getCode();
        } catch (Exception e) {
            return "";
        }
    }
    /**
     * 获取门店指定类型的第一张图片全路径
     */
    private String getFirstImage(Integer shopId, int objType, String imgPrefix) {
        QueryWrapper<Multifile> qw = new QueryWrapper<>();
        qw.lambda()
                .eq(Multifile::getObjId, shopId)
                .eq(Multifile::getObjType, objType)
                .eq(Multifile::getIsdeleted, Constants.ZERO)
                .orderByAsc(Multifile::getSortnum)
                .last("limit 1");
        Multifile f = multifileMapper.selectOne(qw);
        return f != null && StringUtils.isNotBlank(f.getFileurl()) ? imgPrefix + f.getFileurl() : null;
    }
    /**
     * 获取门店指定类型的所有图片全路径
     */
    private List<String> getImageList(Integer shopId, int objType, String imgPrefix) {
        QueryWrapper<Multifile> qw = new QueryWrapper<>();
        qw.lambda()
                .eq(Multifile::getObjId, shopId)
                .eq(Multifile::getObjType, objType)
                .eq(Multifile::getIsdeleted, Constants.ZERO)
                .orderByAsc(Multifile::getSortnum);
        List<Multifile> files = multifileMapper.selectList(qw);
        List<String> urls = new ArrayList<>();
        for (Multifile f : files) {
            if (StringUtils.isNotBlank(f.getFileurl())) {
                urls.add(imgPrefix + f.getFileurl());
            }
        }
        return urls;
    }
    @Override
    public ShopCenterVO getShopCenterInfo(Integer shopId) {
        ShopInfo shop = shopInfoMapper.selectById(shopId);
        if (shop == null || Constants.equalsInteger(shop.getDeleted(), Constants.ONE)) {
            throw new BusinessException(ResponseStatus.DATA_EMPTY);
        }
        ShopCenterVO vo = new ShopCenterVO();
        vo.setShopName(shop.getName());
        vo.setLinkName(shop.getLinkName());
        vo.setCompanyType(shop.getCompanyType());
        vo.setCoverImg(shop.getCoverImg());
        if (StringUtils.isNotBlank(shop.getCoverImg())) {
            String path = systemDictDataBiz.queryByCode(Constants.OSS, Constants.RESOURCE_PATH).getCode()
                    + systemDictDataBiz.queryByCode(Constants.SYSTEM, Constants.SHOP_FILES).getCode();
            vo.setFullCoverImg(path + shop.getCoverImg());
        }
        vo.setHasMessage(false);
        // 待核验订单数量(存件门店,status=1)
        Long waitDepositCount = ordersMapper.selectCount(new QueryWrapper<Orders>().lambda()
                .eq(Orders::getDepositShopId, shopId)
                .eq(Orders::getDeleted, Constants.ZERO)
                .eq(Orders::getStatus, Constants.OrderStatus.waitDeposit.getStatus()));
        vo.setWaitDepositCount(waitDepositCount.intValue());
        // 待收货订单数量(取件门店,status IN 4,5)
        Long waitReceiveCount = ordersMapper.selectCount(new QueryWrapper<Orders>().lambda()
                .eq(Orders::getTakeShopId, shopId)
                .eq(Orders::getDeleted, Constants.ZERO)
                .in(Orders::getStatus, Constants.OrderStatus.delivering.getStatus(), Constants.OrderStatus.arrived.getStatus()));
        vo.setWaitReceiveCount(waitReceiveCount.intValue());
        return vo;
    }
    /**
     * 商户账号密码登录
     * @param dto
     * @return
             */
    @Override
    public ShopLoginVO shopPasswordLogin(ShopLoginDTO dto){
        if(StringUtils.isBlank(dto.getTelephone())
                || StringUtils.isBlank(dto.getPassword())
                || StringUtils.isBlank(dto.getOpenid())
        ){
            throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(),"用户名或密码不能为空");
        }
        ShopInfo shop = shopInfoMapper.selectOne(new QueryWrapper<ShopInfo>().lambda().eq(ShopInfo::getTelephone, dto.getTelephone())
                .eq(ShopInfo::getDeleted,Constants.ZERO)
                .last("limit 1")
        );
        if(shop==null){
            throw new BusinessException(ResponseStatus.ACCOUNT_INCORRECT);
        }
        //加密密码
        String pwd = Utils.Secure.encryptPassword( dto.getPassword(), shop.getSalt());
        if(!pwd.equals(shop.getPassword())){
            throw new BusinessException(ResponseStatus.ACCOUNT_INCORRECT);
        }
        // 更新当前登录会员的openid到门店
        if(StringUtils.isNotBlank(dto.getOpenid())){
            shopInfoMapper.update(null,new UpdateWrapper<ShopInfo>().lambda()
                    .set(ShopInfo::getOpenid,dto.getOpenid())
                    .eq(ShopInfo::getId,shop.getId())
            );
            // 清空其他门店的同一openid,保证唯一
            shopInfoMapper.update(null,new UpdateWrapper<ShopInfo>().lambda()
                    .set(ShopInfo::getOpenid,null)
                    .eq(ShopInfo::getOpenid,dto.getOpenid())
                    .ne(ShopInfo::getId,shop.getId())
            );
            shop.setOpenid(dto.getOpenid());
        }
        // 创建token(generateTokenForRedis 已自动清除该用户旧token,保证唯一有效)
        String token = JwtTokenUtil.generateTokenForRedis(shop.getId(), Constants.TWO, JSONObject.toJSONString(shop), redisTemplate);
        // 构建响应
        ShopLoginVO vo = new ShopLoginVO();
        vo.setToken(token);
        vo.setShopId(shop.getId());
        vo.setShopName(shop.getName());
        vo.setCompanyType(shop.getCompanyType());
        // 所属城市名称
        Areas area = areasBiz.resolveArea(shop.getAreaId());
        if (area != null) {
            vo.setCityName(area.getCityName());
        }
        return vo;
    }
    @Override
    public ShopLoginVO shopSilentLogin(String openid) {
        if (StringUtils.isBlank(openid)) {
            throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "openid不能为空");
        }
        ShopInfo shop = shopInfoMapper.selectOne(new QueryWrapper<ShopInfo>().lambda()
                .eq(ShopInfo::getOpenid, openid)
                .eq(ShopInfo::getDeleted, Constants.ZERO)
                .last("limit 1"));
        if (shop == null) {
            return null;
        }
        // 创建token(generateTokenForRedis 已自动清除该用户旧token,保证唯一有效)
        String token = JwtTokenUtil.generateTokenForRedis(shop.getId(), Constants.TWO, JSONObject.toJSONString(shop), redisTemplate);
        ShopLoginVO vo = new ShopLoginVO();
        vo.setToken(token);
        vo.setShopId(shop.getId());
        vo.setShopName(shop.getName());
        vo.setCompanyType(shop.getCompanyType());
        Areas area = areasBiz.resolveArea(shop.getAreaId());
        if (area != null) {
            vo.setCityName(area.getCityName());
        }
        return vo;
    }
    /**
     * 校验城市定价规则配置是否完整
     * @param cityId 城市主键
     * @return 错误信息列表,空表示校验通过
     */
    private List<String> validateCityPricingRules(Integer cityId) {
        List<String> errors = new ArrayList<>();
        // type=0 就地存取规则:至少1条,fieldB不为空
        List<PricingRule> type0 = pricingRuleMapper.selectList(new QueryWrapper<PricingRule>().lambda()
                .eq(PricingRule::getDeleted, Constants.ZERO)
                .eq(PricingRule::getType, Constants.ZERO)
                .eq(PricingRule::getCityId, cityId));
        if (type0.isEmpty()) {
            errors.add("缺少就地存取规则");
        } else if (type0.stream().allMatch(r -> StringUtils.isBlank(r.getFieldB()))) {
            errors.add("就地存取规则未配置收费单价");
        }
        // type=1 异地寄送规则:至少1条,fieldB/C/D/E不为空
        List<PricingRule> type1 = pricingRuleMapper.selectList(new QueryWrapper<PricingRule>().lambda()
                .eq(PricingRule::getDeleted, Constants.ZERO)
                .eq(PricingRule::getType, Constants.ONE)
                .eq(PricingRule::getCityId, cityId));
        if (type1.isEmpty()) {
            errors.add("缺少异地寄送规则");
        } else if (type1.stream().allMatch(r -> StringUtils.isAnyBlank(r.getFieldB(), r.getFieldC(), r.getFieldD(), r.getFieldE()))) {
            errors.add("异地寄送规则配置不完整");
        }
        // type=2 预计时效:fieldA=1(标速达) 和 fieldA=2(极速达) 各1条
        List<PricingRule> type2 = pricingRuleMapper.selectList(new QueryWrapper<PricingRule>().lambda()
                .eq(PricingRule::getDeleted, Constants.ZERO)
                .eq(PricingRule::getType, Constants.TWO)
                .eq(PricingRule::getCityId, cityId));
        Map<String, PricingRule> type2Map = type2.stream()
                .collect(Collectors.toMap(PricingRule::getFieldA, r -> r, (a, b) -> a));
        if (!type2Map.containsKey("1")) {
            errors.add("缺少预计时效-标速达配置");
        } else if (StringUtils.isAnyBlank(type2Map.get("1").getFieldB(), type2Map.get("1").getFieldC(),
                type2Map.get("1").getFieldD(), type2Map.get("1").getFieldE())) {
            errors.add("预计时效-标速达配置不完整");
        }
        if (!type2Map.containsKey("2")) {
            errors.add("缺少预计时效-极速达配置");
        } else if (StringUtils.isAnyBlank(type2Map.get("2").getFieldB(), type2Map.get("2").getFieldC(),
                type2Map.get("2").getFieldD(), type2Map.get("2").getFieldE())) {
            errors.add("预计时效-极速达配置不完整");
        }
        // type=3 门店注册押金:fieldA=0(企业) 和 fieldA=1(个人) 各1条,fieldB不为空
        List<PricingRule> type3 = pricingRuleMapper.selectList(new QueryWrapper<PricingRule>().lambda()
                .eq(PricingRule::getDeleted, Constants.ZERO)
                .eq(PricingRule::getType, Constants.THREE)
                .eq(PricingRule::getCityId, cityId));
        Map<String, PricingRule> type3Map = type3.stream()
                .collect(Collectors.toMap(PricingRule::getFieldA, r -> r, (a, b) -> a));
        String[] depositNames = {"企业", "个人"};
        for (int i = 0; i <= 1; i++) {
            String key = String.valueOf(i);
            if (!type3Map.containsKey(key)) {
                errors.add("缺少门店注册押金-" + depositNames[i] + "配置");
            } else if (StringUtils.isBlank(type3Map.get(key).getFieldB())) {
                errors.add("门店注册押金-" + depositNames[i] + "未配置押金金额");
            }
        }
        // type=4 分成比例:fieldA=0~4 共5条,fieldB不为空
        List<PricingRule> type4 = pricingRuleMapper.selectList(new QueryWrapper<PricingRule>().lambda()
                .eq(PricingRule::getDeleted, Constants.ZERO)
                .eq(PricingRule::getType, Constants.FOUR)
                .eq(PricingRule::getCityId, cityId));
        String[] shareNames = {"企业寄", "个人寄", "企业取", "个人取", "配送员"};
        Map<String, PricingRule> type4Map = type4.stream()
                .collect(Collectors.toMap(PricingRule::getFieldA, r -> r, (a, b) -> a));
        for (int i = 0; i <= 4; i++) {
            String key = String.valueOf(i);
            if (!type4Map.containsKey(key)) {
                errors.add("缺少分成比例-" + shareNames[i] + "配置");
            } else if (StringUtils.isBlank(type4Map.get(key).getFieldB())) {
                errors.add("分成比例-" + shareNames[i] + "未配置分成比例");
            }
        }
        return errors;
    }
}