| | |
| | | import com.doumee.core.utils.Utils; |
| | | import com.doumee.biz.system.AreasBiz; |
| | | import com.doumee.biz.system.SystemDictDataBiz; |
| | | import com.doumee.core.utils.aliyun.AliSmsService; |
| | | 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.model.SystemDictData; |
| | | 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.ShopSalesStatsVO; |
| | | import com.doumee.dao.vo.ShopWebDetailVO; |
| | | import com.doumee.service.business.AreasService; |
| | | import com.doumee.service.business.ShopInfoService; |
| | | import com.doumee.dao.business.SmsrecordMapper; |
| | | import com.doumee.dao.business.model.Smsrecord; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.apache.commons.collections.CollectionUtils; |
| | | import org.apache.commons.lang3.StringUtils; |
| | | import org.apache.commons.lang3.RandomStringUtils; |
| | |
| | | * @author rk |
| | | * @date 2026/04/08 |
| | | */ |
| | | @Slf4j |
| | | @Service |
| | | public class ShopInfoServiceImpl implements ShopInfoService { |
| | | |
| | |
| | | |
| | | @Autowired |
| | | private RedisTemplate<String,Object> redisTemplate; |
| | | |
| | | @Autowired |
| | | private PricingRuleMapper pricingRuleMapper; |
| | | |
| | | @Autowired |
| | | private OrdersMapper ordersMapper; |
| | | |
| | | @Autowired |
| | | private AreasService areasService; |
| | | |
| | | @Autowired |
| | | private SmsrecordMapper smsrecordMapper; |
| | | @Override |
| | | public Integer create(ShopInfo shopInfo) { |
| | | shopInfoMapper.insert(shopInfo); |
| | |
| | | if (pageWrap.getModel().getAreaId() != null) { |
| | | queryWrapper.lambda().eq(ShopInfo::getAreaId, pageWrap.getModel().getAreaId()); |
| | | } |
| | | if (pageWrap.getModel().getAddress() != null) { |
| | | queryWrapper.lambda().like(ShopInfo::getAddress, pageWrap.getModel().getAddress()); |
| | | } |
| | | if (pageWrap.getModel().getAuditStatus() != null) { |
| | | queryWrapper.lambda().eq(ShopInfo::getAuditStatus, pageWrap.getModel().getAuditStatus()); |
| | | } |
| | |
| | | if (pageWrap.getModel().getOpenid() != null) { |
| | | queryWrapper.lambda().like(ShopInfo::getOpenid, pageWrap.getModel().getOpenid()); |
| | | } |
| | | for (PageWrap.SortData sortData : pageWrap.getSorts()) { |
| | | if (sortData.getDirection().equalsIgnoreCase(PageWrap.DESC)) { |
| | | queryWrapper.orderByDesc(sortData.getProperty()); |
| | | } else { |
| | | queryWrapper.orderByAsc(sortData.getProperty()); |
| | | } |
| | | } |
| | | queryWrapper.lambda().orderByDesc(ShopInfo::getId); |
| | | return PageData.from(shopInfoMapper.selectPage(page, queryWrapper)); |
| | | } |
| | | |
| | |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void applyShop(ShopApplyDTO request, Member member) { |
| | | Integer memberId = member.getId(); |
| | | public void applyShop(ShopApplyDTO request) { |
| | | Member member = memberMapper.selectById(request.getMemberId()); |
| | | |
| | | // 1. 校验门店手机号唯一性(shop_info.telephone) |
| | | checkTelephoneUnique(request.getTelephone(), null); |
| | | |
| | | // 2. 根据类型校验附件 |
| | | if (Constants.equalsInteger(request.getCompanyType(), Constants.ZERO)) { |
| | |
| | | // 3. 查询该会员是否已有门店记录 |
| | | QueryWrapper<ShopInfo> existQw = new QueryWrapper<>(); |
| | | existQw.lambda() |
| | | .eq(ShopInfo::getRegionMemberId, memberId) |
| | | .eq(ShopInfo::getRegionMemberId, member.getId()) |
| | | .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)) { |
| | | if (!Constants.equalsInteger(existing.getAuditStatus(), Constants.TWO)) { |
| | | throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "当前门店状态不允许修改"); |
| | | } |
| | | // 校验openid匹配:当前登录会员的openid必须与门店的openid一致 |
| | |
| | | existing.setLegalPersonCard(request.getLegalPersonCard()); |
| | | existing.setPassword(encryptedPassword); |
| | | existing.setSalt(salt); |
| | | existing.setAuditStatus(Constants.ZERO); |
| | | existing.setAliAccount(request.getAliAccount()); |
| | | existing.setAliName(request.getAliName()); |
| | | existing.setUpdateTime(now); |
| | | existing.setUpdateUser(memberId); |
| | | existing.setAuditRemark(null); |
| | | existing.setAuditTime(null); |
| | | existing.setAuditUserId(null); |
| | | existing.setAuditStatus(Constants.ZERO); |
| | | // 读取押金金额 |
| | | setDepositAmountFromPricingRule(existing); |
| | | shopInfoMapper.updateById(existing); |
| | | shopId = existing.getId(); |
| | | } else { |
| | | // 1. 校验门店手机号唯一性(shop_info.telephone) |
| | | checkTelephoneUnique(request.getTelephone(), null); |
| | | // 新建 |
| | | ShopInfo shopInfo = new ShopInfo(); |
| | | shopInfo.setCompanyType(request.getCompanyType()); |
| | |
| | | shopInfo.setLegalPersonCard(request.getLegalPersonCard()); |
| | | shopInfo.setPassword(encryptedPassword); |
| | | shopInfo.setSalt(salt); |
| | | shopInfo.setOpenid(member.getOpenid()); |
| | | shopInfo.setAliAccount(request.getAliAccount()); |
| | | shopInfo.setAliName(request.getAliName()); |
| | | shopInfo.setAuditStatus(Constants.ZERO); |
| | | shopInfo.setStatus(Constants.ZERO); |
| | | shopInfo.setDeleted(Constants.ZERO); |
| | | shopInfo.setCreateTime(now); |
| | | shopInfo.setUpdateTime(now); |
| | | shopInfo.setCreateUser(memberId); |
| | | shopInfo.setRegionMemberId(memberId); |
| | | shopInfo.setRegionMemberId(member.getId()); |
| | | // 读取押金金额 |
| | | setDepositAmountFromPricingRule(shopInfo); |
| | | // 设置默认配送范围 |
| | | setDefaultDeliveryRange(shopInfo); |
| | | shopInfoMapper.insert(shopInfo); |
| | | shopId = shopInfo.getId(); |
| | | } |
| | |
| | | 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读取押金金额(审批时更新) |
| | | setDepositAmountFromPricingRule(shopInfo); |
| | | } |
| | | shopInfoMapper.updateById(shopInfo); |
| | | |
| | | // 短信通知 |
| | | if (Constants.equalsInteger(newAuditStatus, Constants.ONE)) { |
| | | // 审核通过 → 通知缴纳押金 |
| | | String depositMoney = shopInfo.getDepositAmount() != null |
| | | ? new java.math.BigDecimal(shopInfo.getDepositAmount()) |
| | | .divide(new java.math.BigDecimal(100), 2, java.math.RoundingMode.HALF_UP).toPlainString() : "0"; |
| | | sendSmsNotify(shopInfo.getTelephone(), |
| | | Constants.SmsNotify.SHOP_AUTH_APPROVED_DEPOSIT, |
| | | "storeName", shopInfo.getName(), |
| | | "money", depositMoney); |
| | | } else if (Constants.equalsInteger(newAuditStatus, Constants.TWO)) { |
| | | // 审核驳回 |
| | | sendSmsNotify(shopInfo.getTelephone(), |
| | | Constants.SmsNotify.SHOP_AUTH_REJECTED, |
| | | "storeName", shopInfo.getName(), |
| | | "reason", auditDTO.getAuditRemark() != null ? auditDTO.getAuditRemark() : ""); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | |
| | | shopInfo.setLegalPersonName(request.getLegalPersonName()); |
| | | shopInfo.setLegalPersonPhone(request.getLegalPersonPhone()); |
| | | shopInfo.setLegalPersonCard(request.getLegalPersonCard()); |
| | | shopInfo.setAliAccount(request.getAliAccount()); |
| | | shopInfo.setAliName(request.getAliName()); |
| | | shopInfo.setUpdateTime(now); |
| | | shopInfoMapper.updateById(shopInfo); |
| | | |
| | |
| | | vo.setPayStatus(shopInfo.getPayStatus()); |
| | | vo.setScore(shopInfo.getScore()); |
| | | vo.setCreateTime(shopInfo.getCreateTime()); |
| | | vo.setAliAccount(shopInfo.getAliAccount()); |
| | | vo.setAliName(shopInfo.getAliName()); |
| | | vo.setDepositAmount(shopInfo.getDepositAmount()); |
| | | |
| | | // 拼接图片前缀 |
| | | String imgPrefix = ""; |
| | | try { |
| | | imgPrefix = systemDictDataBiz.queryByCode(Constants.SYSTEM, Constants.RESOURCE_PATH).getCode() |
| | | + systemDictDataBiz.queryByCode(Constants.SYSTEM, Constants.SHOP_FILES).getCode(); |
| | | imgPrefix = systemDictDataBiz.queryByCode(Constants.OSS, Constants.RESOURCE_PATH).getCode() |
| | | + systemDictDataBiz.queryByCode(Constants.OSS, Constants.SHOP_FILES).getCode(); |
| | | } catch (Exception e) { |
| | | // 未配置时忽略 |
| | | } |
| | |
| | | if (payMember != null && StringUtils.isNotBlank(payMember.getCoverImage())) { |
| | | String memberPrefix = ""; |
| | | try { |
| | | memberPrefix = systemDictDataBiz.queryByCode(Constants.SYSTEM, Constants.RESOURCE_PATH).getCode() |
| | | + systemDictDataBiz.queryByCode(Constants.SYSTEM, Constants.MEMBER_FILES).getCode(); |
| | | memberPrefix = systemDictDataBiz.queryByCode(Constants.OSS, Constants.RESOURCE_PATH).getCode() |
| | | + systemDictDataBiz.queryByCode(Constants.OSS, Constants.MEMBER_FILES).getCode(); |
| | | } catch (Exception e) { |
| | | // 未配置时忽略 |
| | | } |
| | | vo.setPayMemberCoverImage(memberPrefix + payMember.getCoverImage()); |
| | | } |
| | | } |
| | | |
| | | // 门店头像:优先使用 coverImg,为空则取门头照第一张 |
| | | if (StringUtils.isNotBlank(shopInfo.getCoverImg())) { |
| | | vo.setShopAvatar(imgPrefix + shopInfo.getCoverImg()); |
| | | } else if (!CollectionUtils.isEmpty(vo.getStoreFrontImgUrls())) { |
| | | vo.setShopAvatar(vo.getStoreFrontImgUrls().get(0)); |
| | | } |
| | | |
| | | return vo; |
| | |
| | | |
| | | // 门店名称模糊查询 |
| | | if (StringUtils.isNotBlank(dto.getName())) { |
| | | qw.lambda().like(ShopInfo::getName, dto.getName()); |
| | | qw.lambda().and(q -> q.like(ShopInfo::getName, dto.getName()).or() |
| | | .like(ShopInfo::getAddress, dto.getName())); |
| | | } |
| | | |
| | | // 城市筛选(areaId是区县,需匹配其parentId等于城市ID) |
| | | if (dto.getCityId() != null) { |
| | | qw.inSql("AREA_ID", |
| | | "SELECT id FROM areas WHERE parent_id = " + dto.getCityId() + " AND isdeleted = 0"); |
| | | } |
| | | |
| | | // 距离筛选(单位:米 → 转换为km比较) |
| | |
| | | vo.setShopHours(shop.getShopHours()); |
| | | vo.setAddress(shop.getAddress()); |
| | | vo.setScore(shop.getScore()); |
| | | vo.setLatitude(shop.getLatitude()); |
| | | vo.setLongitude(shop.getLongitude()); |
| | | // 门头照第一张 |
| | | vo.setCoverImg(getFirstImage(shop.getId(), Constants.FileType.STORE_FRONT.getKey(), imgPrefix)); |
| | | // 距离 |
| | |
| | | vo.setName(shop.getName()); |
| | | vo.setAddress(shop.getAddress()); |
| | | vo.setContent(shop.getContent()); |
| | | vo.setDepositTypes(shop.getDepositTypes()); |
| | | vo.setFeeStandard(shop.getFeeStandard()); |
| | | vo.setLatitude(shop.getLatitude()); |
| | | vo.setLongitude(shop.getLongitude()); |
| | | |
| | | // 门头照 + 内部照 全路径集合 |
| | | String imgPrefix = getShopPrefix(); |
| | |
| | | } |
| | | |
| | | @Override |
| | | public ShopInfoMaintainDTO getShopMaintainInfo(Integer memberId) { |
| | | ShopInfo shop = shopInfoMapper.selectById(memberId); |
| | | public ShopInfoMaintainDTO getShopMaintainInfo(Integer shopId) { |
| | | ShopInfo shop = shopInfoMapper.selectById(shopId); |
| | | if (Objects.isNull(shop) || Constants.equalsInteger(shop.getDeleted(), Constants.ONE)) { |
| | | return null; |
| | | } |
| | |
| | | dto.setDeliveryArea(shop.getDeliveryArea()); |
| | | dto.setShopHours(shop.getShopHours()); |
| | | dto.setBusinessType(shop.getBusinessType()); |
| | | // 头像全路径 |
| | | if (StringUtils.isNotBlank(shop.getCoverImg())) { |
| | | dto.setCoverImgUrl(getShopPrefix() + shop.getCoverImg()); |
| | | } |
| | | return dto; |
| | | } |
| | | |
| | |
| | | */ |
| | | private String getShopPrefix() { |
| | | try { |
| | | return systemDictDataBiz.queryByCode(Constants.SYSTEM, Constants.RESOURCE_PATH).getCode() |
| | | + systemDictDataBiz.queryByCode(Constants.SYSTEM, Constants.SHOP_FILES).getCode(); |
| | | return systemDictDataBiz.queryByCode(Constants.OSS, Constants.RESOURCE_PATH).getCode() |
| | | + systemDictDataBiz.queryByCode(Constants.OSS, Constants.SHOP_FILES).getCode(); |
| | | } catch (Exception e) { |
| | | return ""; |
| | | } |
| | |
| | | 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.OSS, 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()); |
| | | // 支付宝提现账号 |
| | | vo.setAliAccount(shop.getAliAccount()); |
| | | vo.setAliName(shop.getAliName()); |
| | | return vo; |
| | | } |
| | | |
| | | @Override |
| | | public ShopSalesStatsVO getShopSalesStats(Integer shopId, Integer period) { |
| | | // 计算时间范围 0=今日 1=本月 2=上月 |
| | | Calendar cal = Calendar.getInstance(); |
| | | Date startTime; |
| | | Date endTime; |
| | | |
| | | if (Constants.equalsInteger(period, 2)) { |
| | | // 上月:上月1号00:00:00 ~ 本月1号00:00:00 |
| | | cal.set(Calendar.DAY_OF_MONTH, 1); |
| | | cal.set(Calendar.HOUR_OF_DAY, 0); |
| | | cal.set(Calendar.MINUTE, 0); |
| | | cal.set(Calendar.SECOND, 0); |
| | | cal.set(Calendar.MILLISECOND, 0); |
| | | endTime = cal.getTime(); |
| | | cal.add(Calendar.MONTH, -1); |
| | | startTime = cal.getTime(); |
| | | } else if (Constants.equalsInteger(period, 1)) { |
| | | // 本月:本月1号00:00:00 ~ 下月1号00:00:00 |
| | | cal.set(Calendar.DAY_OF_MONTH, 1); |
| | | cal.set(Calendar.HOUR_OF_DAY, 0); |
| | | cal.set(Calendar.MINUTE, 0); |
| | | cal.set(Calendar.SECOND, 0); |
| | | cal.set(Calendar.MILLISECOND, 0); |
| | | startTime = cal.getTime(); |
| | | cal.add(Calendar.MONTH, 1); |
| | | endTime = cal.getTime(); |
| | | } else { |
| | | // 今日:今天00:00:00 ~ 明天00:00:00 |
| | | cal.set(Calendar.HOUR_OF_DAY, 0); |
| | | cal.set(Calendar.MINUTE, 0); |
| | | cal.set(Calendar.SECOND, 0); |
| | | cal.set(Calendar.MILLISECOND, 0); |
| | | startTime = cal.getTime(); |
| | | cal.add(Calendar.DAY_OF_MONTH, 1); |
| | | endTime = cal.getTime(); |
| | | } |
| | | |
| | | ShopSalesStatsVO vo = new ShopSalesStatsVO(); |
| | | |
| | | // 1. 销售额 + 订单数:按订单创建时间,存件门店或取件门店是本门店 |
| | | // 存件门店 |
| | | List<Orders> depositSalesOrders = ordersMapper.selectList(new QueryWrapper<Orders>().lambda() |
| | | .eq(Orders::getDeleted, Constants.ZERO) |
| | | .ge(Orders::getCreateTime, startTime) |
| | | .lt(Orders::getCreateTime, endTime) |
| | | .eq(Orders::getDepositShopId, shopId)); |
| | | // 取件门店 |
| | | List<Orders> takeSalesOrders = ordersMapper.selectList(new QueryWrapper<Orders>().lambda() |
| | | .eq(Orders::getDeleted, Constants.ZERO) |
| | | .eq(Orders::getType,Constants.ONE) |
| | | .ge(Orders::getCreateTime, startTime) |
| | | .lt(Orders::getCreateTime, endTime) |
| | | .eq(Orders::getTakeShopId, shopId)); |
| | | long salesAmount = depositSalesOrders.stream().mapToLong(o -> o.getTotalAmount() != null ? o.getTotalAmount() : 0L).sum() |
| | | + takeSalesOrders.stream().mapToLong(o -> o.getTotalAmount() != null ? o.getTotalAmount() : 0L).sum(); |
| | | vo.setSalesAmount(salesAmount); |
| | | vo.setOrderCount(depositSalesOrders.size() + takeSalesOrders.size()); |
| | | |
| | | // 2. 结算利润:按结算时间,根据门店角色取depositShopFee或takeShopFee |
| | | // 存件门店 = 本门店 的订单,取 depositShopFee |
| | | List<Orders> depositSettleOrders = ordersMapper.selectList(new QueryWrapper<Orders>().lambda() |
| | | .eq(Orders::getDeleted, Constants.ZERO) |
| | | .eq(Orders::getSettlementStatus, Constants.ONE) |
| | | .ge(Orders::getSettlementTime, startTime) |
| | | .lt(Orders::getSettlementTime, endTime) |
| | | .eq(Orders::getDepositShopId, shopId)); |
| | | // 取件门店 = 本门店 的订单,取 takeShopFee |
| | | List<Orders> takeSettleOrders = ordersMapper.selectList(new QueryWrapper<Orders>().lambda() |
| | | .eq(Orders::getDeleted, Constants.ZERO) |
| | | .eq(Orders::getSettlementStatus, Constants.ONE) |
| | | .ge(Orders::getSettlementTime, startTime) |
| | | .lt(Orders::getSettlementTime, endTime) |
| | | .eq(Orders::getTakeShopId, shopId)); |
| | | long depositFee = depositSettleOrders.stream() |
| | | .mapToLong(o -> o.getDepositShopFee() != null ? o.getDepositShopFee() : 0L).sum(); |
| | | long takeFee = takeSettleOrders.stream() |
| | | .mapToLong(o -> o.getTakeShopFee() != null ? o.getTakeShopFee() : 0L).sum(); |
| | | vo.setSettlementProfit(depositFee + takeFee); |
| | | |
| | | // 3. 在库订单数 |
| | | // 3.1 就地订单:存件门店=本门店,type=0,status in (2已寄存, 5待取件) |
| | | Long localStorageCount = ordersMapper.selectCount(new QueryWrapper<Orders>().lambda() |
| | | .eq(Orders::getDeleted, Constants.ZERO) |
| | | .eq(Orders::getDepositShopId, shopId) |
| | | .eq(Orders::getType, Constants.ZERO) |
| | | .in(Orders::getStatus, |
| | | Constants.OrderStatus.deposited.getStatus(), |
| | | Constants.OrderStatus.arrived.getStatus())); |
| | | // 3.2 异地订单:存件门店=本门店,type=1,status=2已寄存 |
| | | Long remoteStorageCount = ordersMapper.selectCount(new QueryWrapper<Orders>().lambda() |
| | | .eq(Orders::getDeleted, Constants.ZERO) |
| | | .eq(Orders::getDepositShopId, shopId) |
| | | .eq(Orders::getType, Constants.ONE) |
| | | .eq(Orders::getStatus, Constants.OrderStatus.deposited.getStatus())); |
| | | // 3.3 取件门店=本门店,异地订单,status = 5待取件 |
| | | Long takeStorageCount = ordersMapper.selectCount(new QueryWrapper<Orders>().lambda() |
| | | .eq(Orders::getDeleted, Constants.ZERO) |
| | | .eq(Orders::getType,Constants.ONE) |
| | | .eq(Orders::getTakeShopId, shopId) |
| | | .eq(Orders::getStatus, Constants.OrderStatus.arrived.getStatus())); |
| | | vo.setStorageCount(localStorageCount.intValue() + remoteStorageCount.intValue() + takeStorageCount.intValue()); |
| | | |
| | | return vo; |
| | | } |
| | | |
| | | /** |
| | | * 商户账号密码登录 |
| | |
| | | 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(),"用户名或密码不能为空"); |
| | | } |
| | |
| | | ); |
| | | if(shop==null){ |
| | | throw new BusinessException(ResponseStatus.ACCOUNT_INCORRECT); |
| | | } |
| | | if(!Constants.equalsInteger(shop.getAuditStatus(),Constants.THREE)){ |
| | | if(Constants.equalsInteger(shop.getAuditStatus(),Constants.ONE)){ |
| | | throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(),"请完成审批流程 - 支付押金"); |
| | | } |
| | | throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(),"门店审批流程未完成,请完成审批流程"); |
| | | } |
| | | //加密密码 |
| | | String pwd = Utils.Secure.encryptPassword( dto.getPassword(), shop.getSalt()); |
| | |
| | | |
| | | // 创建token(generateTokenForRedis 已自动清除该用户旧token,保证唯一有效) |
| | | String token = JwtTokenUtil.generateTokenForRedis(shop.getId(), Constants.TWO, JSONObject.toJSONString(shop), redisTemplate); |
| | | |
| | | // 构建响应 |
| | | ShopLoginVO vo = new ShopLoginVO(); |
| | | vo.setToken(token); |
| | |
| | | } |
| | | |
| | | @Override |
| | | public ShopLoginVO shopSilentLogin(String openid) { |
| | | if (StringUtils.isBlank(openid)) { |
| | | throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "openid不能为空"); |
| | | public ShopLoginVO shopSilentLogin(Integer memberId) { |
| | | Member member = memberMapper.selectById(memberId); |
| | | if(Objects.isNull(member)||StringUtils.isBlank(member.getOpenid())){ |
| | | throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "当前登录会员身份异常,请联系管理员!"); |
| | | } |
| | | ShopInfo shop = shopInfoMapper.selectOne(new QueryWrapper<ShopInfo>().lambda() |
| | | .eq(ShopInfo::getOpenid, openid) |
| | | .eq(ShopInfo::getOpenid, member.getOpenid()) |
| | | .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); |
| | | |
| | |
| | | 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; |
| | | } |
| | | |
| | | /** |
| | | * 从PricingRule读取押金金额并赋值到门店记录 |
| | | */ |
| | | private void setDepositAmountFromPricingRule(ShopInfo shopInfo) { |
| | | if (shopInfo.getCompanyType() == null) { |
| | | return; |
| | | } |
| | | Areas areas = areasService.findById(shopInfo.getAreaId()); |
| | | PricingRule pricingRule = pricingRuleMapper.selectOne(new QueryWrapper<PricingRule>().lambda() |
| | | .eq(PricingRule::getDeleted, Constants.ZERO) |
| | | .eq(PricingRule::getType, Constants.THREE) |
| | | .eq(PricingRule::getFieldA, String.valueOf(Constants.equalsInteger(shopInfo.getCompanyType(),Constants.ZERO)?Constants.ONE:Constants.ZERO)) |
| | | .eq(PricingRule::getCityId, areas.getParentId()) |
| | | .last("limit 1")); |
| | | if (pricingRule != null && StringUtils.isNotBlank(pricingRule.getFieldB())) { |
| | | shopInfo.setDepositAmount(Long.parseLong(pricingRule.getFieldB())); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 从运营配置读取默认配送范围并赋值到门店记录 |
| | | */ |
| | | private void setDefaultDeliveryRange(ShopInfo shopInfo) { |
| | | SystemDictData data = systemDictDataBiz.queryByCode(Constants.OPERATION_CONFIG, Constants.OP_DEFAULT_DELIVERY_RANGE); |
| | | if (data != null && StringUtils.isNotBlank(data.getCode())) { |
| | | shopInfo.setDeliveryArea(new java.math.BigDecimal(data.getCode())); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 发送短信通知(失败不影响主业务) |
| | | */ |
| | | private void sendSmsNotify(String phone, Constants.SmsNotify smsNotify, String... paramPairs) { |
| | | if (StringUtils.isBlank(phone)) { |
| | | return; |
| | | } |
| | | String content = smsNotify.format(paramPairs); |
| | | try { |
| | | JSONObject templateParam = new JSONObject(); |
| | | for (int i = 0; i < paramPairs.length - 1; i += 2) { |
| | | templateParam.put(paramPairs[i], paramPairs[i + 1]); |
| | | } |
| | | String error = AliSmsService.sendSms(phone, smsNotify.getTemplateCode(), |
| | | templateParam.toJSONString()); |
| | | Smsrecord smsRecord = new Smsrecord(); |
| | | smsRecord.setPhone(phone); |
| | | smsRecord.setContent(content); |
| | | smsRecord.setType(Constants.ONE); |
| | | smsRecord.setStatus(error == null ? Constants.ONE : Constants.ZERO); |
| | | if (error != null) { |
| | | smsRecord.setRemark(error); |
| | | } |
| | | smsRecord.setCreateTime(new Date()); |
| | | smsRecord.setDeleted(Constants.ZERO); |
| | | smsrecordMapper.insert(smsRecord); |
| | | } catch (Exception e) { |
| | | log.error("短信发送异常: phone={}, template={}, error={}", phone, smsNotify.name(), e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void changePassword(Integer shopId, String oldPassword, String newPassword, String token) { |
| | | if (StringUtils.isBlank(oldPassword)) { |
| | | throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "旧密码不能为空"); |
| | | } |
| | | if (StringUtils.isBlank(newPassword)) { |
| | | throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "新密码不能为空"); |
| | | } |
| | | // 校验新密码必须同时包含字母和数字 |
| | | boolean hasLetter = newPassword.chars().anyMatch(Character::isLetter); |
| | | boolean hasDigit = newPassword.chars().anyMatch(Character::isDigit); |
| | | if (!hasLetter || !hasDigit) { |
| | | throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "密码必须同时包含字母和数字"); |
| | | } |
| | | // 查询门店 |
| | | ShopInfo shopInfo = shopInfoMapper.selectById(shopId); |
| | | if (shopInfo == null || Constants.equalsInteger(shopInfo.getDeleted(), Constants.ONE)) { |
| | | throw new BusinessException(ResponseStatus.DATA_EMPTY); |
| | | } |
| | | // 校验旧密码 |
| | | String oldEncryptPwd = Utils.Secure.encryptPassword(oldPassword, shopInfo.getSalt()); |
| | | if (!oldEncryptPwd.equals(shopInfo.getPassword())) { |
| | | throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "旧密码错误"); |
| | | } |
| | | // 重新生成salt,加密新密码并更新 |
| | | String salt = RandomStringUtils.randomAlphabetic(6); |
| | | shopInfo.setPassword(Utils.Secure.encryptPassword(newPassword, salt)); |
| | | shopInfo.setSalt(salt); |
| | | shopInfoMapper.updateById(shopInfo); |
| | | // 清除token,强制重新登录 |
| | | if (StringUtils.isNotBlank(token)) { |
| | | redisTemplate.delete(token); |
| | | } |
| | | } |
| | | |
| | | @Autowired |
| | | private com.doumee.core.utils.xpyun.XpyunPrintService xpyunPrintService; |
| | | |
| | | @Override |
| | | public void bindPrinter(com.doumee.dao.dto.PrinterBindDTO dto) { |
| | | ShopInfo shop = shopInfoMapper.selectById(dto.getShopId()); |
| | | if (shop == null || Constants.equalsInteger(shop.getDeleted(), Constants.ONE)) { |
| | | throw new BusinessException(ResponseStatus.DATA_EMPTY); |
| | | } |
| | | // 调用芯烨云注册打印机 |
| | | com.doumee.core.utils.xpyun.XpyunResponse resp = xpyunPrintService.addPrinter( |
| | | dto.getPrinterSn(), shop.getName()); |
| | | if (resp.getCode() != null && resp.getCode() == 0) { |
| | | log.info("芯烨云打印机注册成功: sn={}", dto.getPrinterSn()); |
| | | } else { |
| | | log.warn("芯烨云打印机注册返回非0: sn={}, code={}, msg={}", |
| | | dto.getPrinterSn(), resp.getCode(), resp.getMsg()); |
| | | } |
| | | // 无论云端是否成功,都绑定到门店(可后续重试云端注册) |
| | | ShopInfo update = new ShopInfo(); |
| | | update.setId(dto.getShopId()); |
| | | update.setPrinterSn(dto.getPrinterSn()); |
| | | shopInfoMapper.updateById(update); |
| | | } |
| | | |
| | | @Override |
| | | public void unbindPrinter(Integer shopId) { |
| | | ShopInfo shop = shopInfoMapper.selectById(shopId); |
| | | if (shop == null || Constants.equalsInteger(shop.getDeleted(), Constants.ONE)) { |
| | | throw new BusinessException(ResponseStatus.DATA_EMPTY); |
| | | } |
| | | if (StringUtils.isNotBlank(shop.getPrinterSn())) { |
| | | xpyunPrintService.deletePrinter(shop.getPrinterSn()); |
| | | } |
| | | ShopInfo update = new ShopInfo(); |
| | | update.setId(shopId); |
| | | update.setPrinterSn(null); |
| | | shopInfoMapper.updateById(update); |
| | | } |
| | | |
| | | @Override |
| | | public Object queryPrinterStatus(Integer shopId) { |
| | | ShopInfo shop = shopInfoMapper.selectById(shopId); |
| | | if (shop == null || Constants.equalsInteger(shop.getDeleted(), Constants.ONE)) { |
| | | throw new BusinessException(ResponseStatus.DATA_EMPTY); |
| | | } |
| | | if (StringUtils.isBlank(shop.getPrinterSn())) { |
| | | throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "该门店未绑定打印机"); |
| | | } |
| | | return xpyunPrintService.queryPrinterStatus(shop.getPrinterSn()); |
| | | } |
| | | |
| | | } |