rk
13 小时以前 c74a6f59490cfb9a0ee37f70427739b74e7fbd58
server/services/src/main/java/com/doumee/service/business/impl/ShopInfoServiceImpl.java
@@ -15,16 +15,20 @@
import com.doumee.biz.system.AreasBiz;
import com.doumee.biz.system.SystemDictDataBiz;
import com.doumee.core.utils.aliyun.AliSmsService;
import com.doumee.dao.business.CategoryMapper;
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.RevenueMapper;
import com.doumee.dao.business.ShopInfoMapper;
import com.doumee.dao.business.model.Areas;
import com.doumee.dao.business.model.Category;
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.Revenue;
import com.doumee.dao.business.model.ShopInfo;
import com.doumee.config.xyy.XyyConfig;
import com.doumee.config.xyy.dto.AddPrinterRequest;
@@ -36,13 +40,17 @@
import com.doumee.dao.system.model.SystemDictData;
import com.doumee.dao.system.SystemUserMapper;
import com.doumee.dao.system.model.SystemUser;
import com.doumee.dao.vo.LuggageTypeItem;
import com.doumee.dao.vo.ShopDetailVO;
import com.doumee.dao.vo.ShopCenterVO;
import com.doumee.dao.vo.ShopKpiVO;
import com.doumee.dao.vo.ShopLoginVO;
import com.doumee.dao.vo.LocationTagShopCountVO;
import com.doumee.dao.vo.ShopNearbyVO;
import com.doumee.dao.vo.ShopSalesStatsVO;
import com.doumee.dao.vo.ShopWebDetailVO;
import com.doumee.service.business.AreasService;
import com.doumee.service.business.DataBoardService;
import com.doumee.service.business.ShopInfoService;
import com.doumee.dao.business.SmsrecordMapper;
import com.doumee.dao.business.model.Smsrecord;
@@ -105,6 +113,16 @@
    @Autowired
    private XyyConfig xyyConfig;
    @Autowired
    private DataBoardService dataBoardService;
    @Autowired
    private RevenueMapper revenueMapper;
    @Autowired
    private CategoryMapper categoryMapper;
    @Override
    public Integer create(ShopInfo shopInfo) {
        shopInfoMapper.insert(shopInfo);
@@ -329,6 +347,15 @@
            // 保存新变更版本附件
            saveShopAttachments(newChange.getId(), request, now);
            // 标记历史的变更版本为删除
            shopInfoMapper.update(null, new UpdateWrapper<ShopInfo>().lambda()
                    .eq(ShopInfo::getRegionMemberId, member.getId())
                    .eq(ShopInfo::getVersionType, Constants.ONE)
                    .eq(ShopInfo::getDeleted, Constants.ZERO)
                    .ne(ShopInfo::getId, newChange.getId())
                    .set(ShopInfo::getDeleted, Constants.ONE)
                    .set(ShopInfo::getUpdateTime, now));
        } else {
            // 最新变更版本 status=0(待审批) 或 2(被驳回):直接更新
            String rawPassword = generateDefaultPassword(request.getTelephone());
@@ -474,6 +501,19 @@
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "当前状态不允许审批");
        }
        // 审批通过时,收益比例配置必填
        String revenueShareConfig = null;
        if (Constants.equalsInteger(auditDTO.getAuditStatus(), Constants.ZERO)) {
            if (auditDTO.getLocalDeposit() == null || auditDTO.getRemoteDeposit() == null || auditDTO.getRemoteTake() == null) {
                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "审批通过时收益比例配置不能为空");
            }
            JSONObject json = new JSONObject();
            json.put("localDeposit", auditDTO.getLocalDeposit());
            json.put("remoteDeposit", auditDTO.getRemoteDeposit());
            json.put("remoteTake", auditDTO.getRemoteTake());
            revenueShareConfig = json.toJSONString();
        }
        Date now = new Date();
        // auditDTO.auditStatus: 0=通过 → 1, 1=驳回 → 2
        Integer newAuditStatus = Constants.equalsInteger(auditDTO.getAuditStatus(), Constants.ZERO) ? Constants.ONE : Constants.TWO;
@@ -493,6 +533,7 @@
            changeVersion.setUpdateTime(now);
            if (Constants.equalsInteger(newAuditStatus, Constants.ONE)) {
                changeVersion.setRevenueShareConfig(revenueShareConfig);
                validateCityAndSetDeposit(changeVersion);
            }
            shopInfoMapper.updateById(changeVersion);
@@ -506,6 +547,7 @@
                official.setUpdateTime(now);
                if (Constants.equalsInteger(newAuditStatus, Constants.ONE)) {
                    official.setDepositAmount(changeVersion.getDepositAmount());
                    official.setRevenueShareConfig(revenueShareConfig);
                }
                shopInfoMapper.updateById(official);
            }
@@ -520,8 +562,19 @@
                changeVersion.setAuditTime(now);
                changeVersion.setAuditRemark(auditDTO.getAuditRemark());
                changeVersion.setAuditUserId(auditDTO.getAuditUser());
                changeVersion.setRevenueShareConfig(revenueShareConfig);
                changeVersion.setUpdateTime(now);
                shopInfoMapper.updateById(changeVersion);
                // 标记历史的变更版本为删除
                shopInfoMapper.update(null, new UpdateWrapper<ShopInfo>().lambda()
                        .eq(ShopInfo::getRegionMemberId, changeVersion.getRegionMemberId())
                        .eq(ShopInfo::getVersionType, Constants.ONE)
                        .eq(ShopInfo::getDeleted, Constants.ZERO)
                        .ne(ShopInfo::getId, changeVersion.getId())
                        .ne(ShopInfo::getId, officialId)
                        .set(ShopInfo::getDeleted, Constants.ONE)
                        .set(ShopInfo::getUpdateTime, now));
                // 同步变更版本数据到正式版本
                syncChangeToOfficial(changeVersion, official, now);
@@ -725,6 +778,7 @@
        shopInfo.setAliAccount(request.getAliAccount());
        shopInfo.setAliName(request.getAliName());
        shopInfo.setRevenueShareConfig(buildRevenueShareConfig(request.getLocalDeposit(), request.getRemoteDeposit(), request.getRemoteTake()));
        shopInfo.setLocationTagIds(request.getLocationTagIds());
        shopInfo.setUpdateTime(now);
        shopInfoMapper.updateById(shopInfo);
@@ -915,6 +969,19 @@
        vo.setDeliveryRange(shopInfo.getDeliveryArea());
        vo.setVersionType(shopInfo.getVersionType());
        vo.setRelationShopId(shopInfo.getRelationShopId());
        vo.setLocationTagIds(shopInfo.getLocationTagIds());
        // 位置标签名称
        if (StringUtils.isNotBlank(shopInfo.getLocationTagIds())) {
            List<String> tagNames = new ArrayList<>();
            for (String tagId : shopInfo.getLocationTagIds().split(",")) {
                Category tag = categoryMapper.selectById(Integer.valueOf(tagId.trim()));
                if (tag != null) {
                    tagNames.add(tag.getName());
                }
            }
            vo.setLocationTagNames(String.join(",", tagNames));
        }
        // 解析收益比例配置
        if (StringUtils.isNotBlank(shopInfo.getRevenueShareConfig())) {
            try {
@@ -945,6 +1012,13 @@
        vo.setIdcardImgBackUrl(StringUtils.isNotBlank(shopInfo.getIdcardImgBack()) ? imgPrefix + shopInfo.getIdcardImgBack() : null);
        vo.setBusinessImg(shopInfo.getBusinessImg());
        vo.setBusinessImgUrl(StringUtils.isNotBlank(shopInfo.getBusinessImg()) ? imgPrefix + shopInfo.getBusinessImg() : null);
        vo.setCoverImg(shopInfo.getCoverImg());
        vo.setCoverImgUrl(StringUtils.isNotBlank(shopInfo.getCoverImg()) ? imgPrefix + shopInfo.getCoverImg() : null);
        vo.setContent(shopInfo.getContent());
        vo.setDepositTypes(shopInfo.getDepositTypes());
        vo.setFeeStandard(shopInfo.getFeeStandard());
        vo.setShopHours(shopInfo.getShopHours());
        vo.setBusinessType(shopInfo.getBusinessType());
        // 查询附件
        QueryWrapper<Multifile> fileQw = new QueryWrapper<>();
@@ -1046,6 +1120,11 @@
                    "SELECT id FROM areas WHERE parent_id = " + dto.getCityId() + " AND isdeleted = 0");
        }
        // 位置标签筛选
        if (dto.getLocationTagId() != null) {
            qw.apply("FIND_IN_SET({0}, LOCATION_TAG_IDS) > 0", dto.getLocationTagId());
        }
        // 距离筛选(单位:米 → 转换为km比较)
        if (distanceMeter != null && distanceMeter > 0) {
            double maxKm = distanceMeter / 1000.0;
@@ -1082,6 +1161,8 @@
            vo.setLongitude(shop.getLongitude());
            // 门头照第一张
            vo.setCoverImg(getFirstImage(shop.getId(), Constants.FileType.STORE_FRONT.getKey(), imgPrefix));
            // 位置标签名称
            vo.setLocationTagNames(resolveLocationTagNames(shop.getLocationTagIds()));
            // 距离
            if (longitude != null && latitude != null && shop.getLongitude() != null && shop.getLatitude() != null) {
                double distKm = haversine(latitude, longitude, shop.getLatitude(), shop.getLongitude());
@@ -1096,6 +1177,53 @@
        pageData.setPage(result.getCurrent());
        pageData.setCapacity(result.getSize());
        return pageData;
    }
    @Override
    public List<LocationTagShopCountVO> countShopsByLocationTag(Integer cityId) {
        // 查询所有位置标签
        List<Category> tags = categoryMapper.selectList(new QueryWrapper<Category>().lambda()
                .eq(Category::getDeleted, Constants.ZERO)
                .eq(Category::getStatus, Constants.ZERO)
                .eq(Category::getType, Constants.FIVE)
                .orderByAsc(Category::getSortnum));
        if (tags.isEmpty()) {
            return new ArrayList<>();
        }
        // 构建门店基础查询条件
        QueryWrapper<ShopInfo> baseQw = new QueryWrapper<>();
        baseQw.lambda()
                .eq(ShopInfo::getDeleted, Constants.ZERO)
                .eq(ShopInfo::getStatus, Constants.ZERO)
                .eq(ShopInfo::getVersionType, Constants.ZERO)
                .eq(ShopInfo::getAuditStatus, Constants.THREE);
        if (cityId != null) {
            baseQw.inSql("AREA_ID",
                    "SELECT id FROM areas WHERE parent_id = " + cityId + " AND isdeleted = 0");
        }
        // 按标签统计
        List<LocationTagShopCountVO> result = new ArrayList<>();
        for (Category tag : tags) {
            QueryWrapper<ShopInfo> qw = baseQw.clone();
            qw.apply("FIND_IN_SET({0}, LOCATION_TAG_IDS) > 0", tag.getId());
            Long count = shopInfoMapper.selectCount(qw);
            LocationTagShopCountVO vo = new LocationTagShopCountVO();
            vo.setTagId(tag.getId());
            vo.setTagName(tag.getName());
            vo.setShopCount(count != null ? count.intValue() : 0);
            result.add(vo);
        }
        // 总数放在列表第一个
        Long totalCount = shopInfoMapper.selectCount(baseQw);
        LocationTagShopCountVO totalVo = new LocationTagShopCountVO();
        totalVo.setTagId(null);
        totalVo.setTagName("寄存点总数");
        totalVo.setShopCount(totalCount != null ? totalCount.intValue() : 0);
        result.add(0, totalVo);
        return result;
    }
    @Override
@@ -1115,6 +1243,7 @@
        vo.setLatitude(shop.getLatitude());
        vo.setLongitude(shop.getLongitude());
        vo.setLinkPhone(shop.getLinkPhone());
        vo.setLocationTagNames(resolveLocationTagNames(shop.getLocationTagIds()));
        // 门头照 + 内部照 全路径集合
        String imgPrefix = getShopPrefix();
@@ -1204,6 +1333,18 @@
    /**
     * 获取门店图片前缀
     */
    private String resolveLocationTagNames(String locationTagIds) {
        if (StringUtils.isBlank(locationTagIds)) return null;
        List<String> tagNames = new ArrayList<>();
        for (String tagId : locationTagIds.split(",")) {
            Category tag = categoryMapper.selectById(Integer.valueOf(tagId.trim()));
            if (tag != null) {
                tagNames.add(tag.getName());
            }
        }
        return tagNames.isEmpty() ? null : String.join(",", tagNames);
    }
    private String getShopPrefix() {
        try {
            return systemDictDataBiz.queryByCode(Constants.OSS, Constants.RESOURCE_PATH).getCode()
@@ -1391,6 +1532,128 @@
        return vo;
    }
    @Override
    public List<LuggageTypeItem> shopLuggageTypeList(Integer shopId, ShopRevenueQueryDTO query) {
        QueryWrapper<Orders> qw = new QueryWrapper<>();
        qw.lambda()
                .eq(Orders::getDeleted, Constants.ZERO)
                .in(Orders::getStatus,
                        Constants.OrderStatus.waitDeposit.getKey(),
                        Constants.OrderStatus.deposited.getKey(),
                        Constants.OrderStatus.accepted.getKey(),
                        Constants.OrderStatus.delivering.getKey(),
                        Constants.OrderStatus.arrived.getKey(),
                        Constants.OrderStatus.finished.getKey())
                .and(w -> w.eq(Orders::getDepositShopId, shopId).or().eq(Orders::getTakeShopId, shopId));
        if (query.getStartDate() != null) {
            qw.lambda().ge(Orders::getCreateTime, query.getStartDate());
        }
        if (query.getEndDate() != null) {
            qw.lambda().le(Orders::getCreateTime, Utils.Date.getEnd(query.getEndDate()));
        }
        List<Orders> orders = ordersMapper.selectList(qw);
        List<Integer> orderIds = orders.stream().map(Orders::getId).collect(Collectors.toList());
        return dataBoardService.buildLuggageTypeList(orderIds);
    }
    @Override
    public ShopKpiVO getShopKpi(Integer shopId, ShopRevenueQueryDTO query) {
        // 查询门店参与的订单(状态1-7,日期范围)
        QueryWrapper<Orders> qw = new QueryWrapper<>();
        qw.lambda()
                .eq(Orders::getDeleted, Constants.ZERO)
                .in(Orders::getStatus,
                        Constants.OrderStatus.waitDeposit.getKey(),
                        Constants.OrderStatus.deposited.getKey(),
                        Constants.OrderStatus.accepted.getKey(),
                        Constants.OrderStatus.delivering.getKey(),
                        Constants.OrderStatus.arrived.getKey(),
                        Constants.OrderStatus.finished.getKey())
                .and(w -> w.eq(Orders::getDepositShopId, shopId).or().eq(Orders::getTakeShopId, shopId));
        if (query.getStartDate() != null) {
            qw.lambda().ge(Orders::getCreateTime, query.getStartDate());
        }
        if (query.getEndDate() != null) {
            qw.lambda().le(Orders::getCreateTime, Utils.Date.getEnd(query.getEndDate()));
        }
        List<Orders> orders = ordersMapper.selectList(qw);
        ShopKpiVO vo = new ShopKpiVO();
        // 寄存订单量(就地)
        long localCount = orders.stream().filter(o -> Constants.equalsInteger(o.getType(), Constants.ZERO)).count();
        vo.setLocalOrderCount((int) localCount);
        // 寄送订单量(异地)
        long remoteCount = orders.stream().filter(o -> Constants.equalsInteger(o.getType(), Constants.ONE)).count();
        vo.setRemoteOrderCount((int) remoteCount);
        // 总订单量
        vo.setTotalOrderCount(orders.size());
        // 总完成订单量
        long finishedCount = orders.stream()
                .filter(o -> Constants.equalsInteger(o.getStatus(), Constants.OrderStatus.finished.getStatus()))
                .count();
        vo.setFinishedOrderCount((int) finishedCount);
        // 总营收金额 = sum(totalAmount - refundAmount)
        long totalRevenue = orders.stream()
                .mapToLong(o -> {
                    long total = o.getTotalAmount() != null ? o.getTotalAmount() : 0L;
                    long refund = o.getRefundAmount() != null ? o.getRefundAmount() : 0L;
                    return total - refund;
                }).sum();
        vo.setTotalRevenue(totalRevenue);
        // 门店分成金额
        long shopFee = 0L;
        for (Orders o : orders) {
            if (Constants.equalsInteger(o.getType(), Constants.ZERO)) {
                // 就地寄存:取存件门店分成
                if (Constants.equalsInteger(o.getDepositShopId(), shopId)) {
                    shopFee += o.getDepositShopFee() != null ? o.getDepositShopFee() : 0L;
                }
            } else if (Constants.equalsInteger(o.getType(), Constants.ONE)) {
                // 异地:作为存件门店取depositShopFee,作为取件门店取takeShopFee
                if (Constants.equalsInteger(o.getDepositShopId(), shopId)) {
                    shopFee += o.getDepositShopFee() != null ? o.getDepositShopFee() : 0L;
                }
                if (Constants.equalsInteger(o.getTakeShopId(), shopId)) {
                    shopFee += o.getTakeShopFee() != null ? o.getTakeShopFee() : 0L;
                }
            }
        }
        vo.setShopFeeTotal(shopFee);
        // 退款单数
        long refundCount = orders.stream()
                .filter(o -> o.getRefundAmount() != null && o.getRefundAmount() > 0)
                .count();
        vo.setRefundOrderCount((int) refundCount);
        // 责任扣款总额:Revenue memberId=shopId, memberType=2, type=4
        QueryWrapper<Revenue> revQw = new QueryWrapper<>();
        revQw.lambda()
                .eq(Revenue::getMemberId, shopId)
                .eq(Revenue::getMemberType, Constants.TWO)
                .eq(Revenue::getType, Constants.FOUR)
                .eq(Revenue::getDeleted, Constants.ZERO);
        if (query.getStartDate() != null) {
            revQw.lambda().ge(Revenue::getCreateTime, query.getStartDate());
        }
        if (query.getEndDate() != null) {
            revQw.lambda().le(Revenue::getCreateTime, Utils.Date.getEnd(query.getEndDate()));
        }
        List<Revenue> deductRecords = revenueMapper.selectList(revQw);
        long deductTotal = deductRecords.stream()
                .mapToLong(r -> r.getAmount() != null ? r.getAmount() : 0L)
                .sum();
        vo.setDeductTotal(deductTotal);
        return vo;
    }
    /**
     * 商户账号密码登录
     * @param dto