| | |
| | | import com.doumee.dao.business.OrderLogMapper; |
| | | import com.doumee.dao.dto.*; |
| | | import com.doumee.dao.vo.AccountResponse; |
| | | import com.doumee.dao.vo.DriverActiveOrderCountVO; |
| | | import com.doumee.dao.vo.DriverCancelLimitVO; |
| | | import com.doumee.dao.vo.DriverCenterVO; |
| | | import com.doumee.dao.vo.DriverGrabOrderVO; |
| | | import com.doumee.dao.vo.DriverOrderDetailVO; |
| | |
| | | import org.springframework.util.CollectionUtils; |
| | | |
| | | import java.util.*; |
| | | import java.util.concurrent.TimeUnit; |
| | | import java.util.stream.Collectors; |
| | | |
| | | /** |
| | |
| | | Long todayOrderCount = ordersMapper.selectCount(new QueryWrapper<Orders>().lambda() |
| | | .eq(Orders::getAcceptDriver, driver.getId()) |
| | | .eq(Orders::getDeleted, Constants.ZERO) |
| | | .ge(Orders::getFinishTime, todayStart)); |
| | | .ge(Orders::getAcceptTime, todayStart)); |
| | | vo.setTodayOrderCount(todayOrderCount.intValue()); |
| | | |
| | | // 待取货(已接单=3) |
| | |
| | | goodTypeIds = cats.stream().map(Category::getId).collect(Collectors.toList()); |
| | | } |
| | | |
| | | // 3. Haversine SQL公式:司机到存件门店距离(km),使用Orders自带坐标 |
| | | String depositDist = "(6371 * acos(cos(radians(" + driverLat + ")) * cos(radians(t.DEPOSIT_LGT)) " |
| | | + "* cos(radians(t.DEPOSIT_LAT) - radians(" + driverLng + ")) " |
| | | + "+ sin(radians(" + driverLat + ")) * sin(radians(t.DEPOSIT_LGT))))"; |
| | | // 3. ST_Distance_Sphere计算司机到存件门店距离(km),使用Orders自带坐标 |
| | | String depositDist = "(ST_Distance_Sphere(POINT(" + driverLng + ", " + driverLat + "), POINT(t.DEPOSIT_LGT, t.DEPOSIT_LAT)) / 1000)"; |
| | | |
| | | // 4. 构造MPJ查询 |
| | | IPage<Orders> page = new Page<>(pageWrap.getPage(), pageWrap.getCapacity()); |
| | |
| | | .select("s2.link_phone as takeShopLinkPhone") |
| | | // 物品等级贵重标识 |
| | | .select("c2.other_field as c2OtherField") |
| | | .select("c2.name as goodLevelName") |
| | | // 是否存在特大尺寸 |
| | | .select("IF(EXISTS(SELECT 1 FROM orders_detail od JOIN category c3 ON c3.id = od.LUGGAGE_ID AND c3.TYPE = 4 AND c3.OTHER_FIELD = '1' WHERE od.ORDER_ID = t.ID AND od.DELETED = 0), 1, 0) as hasOversized") |
| | | // JOIN |
| | | .leftJoin("shop_info s1 on s1.id = t.DEPOSIT_SHOP_ID and s1.DELETED = 0") |
| | | .leftJoin("shop_info s2 on s2.id = t.TAKE_SHOP_ID and s2.DELETED = 0") |
| | |
| | | .select("s2.address", Orders::getTakeShopAddress) |
| | | .select("s2.link_phone as takeShopLinkPhone") |
| | | .select("c2.other_field as c2OtherField") |
| | | .select("c2.name as goodLevelName") |
| | | .select("IF(EXISTS(SELECT 1 FROM orders_detail od JOIN category c3 ON c3.id = od.LUGGAGE_ID AND c3.TYPE = 4 AND c3.OTHER_FIELD = '1' WHERE od.ORDER_ID = t.ID AND od.DELETED = 0), 1, 0) as hasOversized") |
| | | .leftJoin("shop_info s1 on s1.id = t.DEPOSIT_SHOP_ID and s1.DELETED = 0") |
| | | .leftJoin("shop_info s2 on s2.id = t.TAKE_SHOP_ID and s2.DELETED = 0") |
| | | .leftJoin("category c1 on c1.id = t.GOOD_TYPE and c1.DELETED = 0") |
| | | .leftJoin("category c2 on c2.id = c1.RELATION_ID and c2.DELETED = 0 and c2.TYPE = 3") |
| | | .eq(Orders::getAcceptDriver, driver.getId()) |
| | | .eq(Orders::getType, Constants.ONE) |
| | | .eq(Orders::getStatus, dto.getStatus()) |
| | | .eq(Objects.nonNull(dto.getStatus()),Orders::getStatus, dto.getStatus()) |
| | | .eq(Orders::getDeleted, Constants.ZERO) |
| | | .orderByAsc(Orders::getAcceptTime); |
| | | |
| | |
| | | .select("s2.address", Orders::getTakeShopAddress) |
| | | .select("s2.link_phone as takeShopLinkPhone") |
| | | .select("c2.other_field as c2OtherField") |
| | | .select("IF(EXISTS(SELECT 1 FROM orders_detail od JOIN category c3 ON c3.id = od.LUGGAGE_ID AND c3.TYPE = 4 AND c3.OTHER_FIELD = '1' WHERE od.ORDER_ID = t.ID AND od.DELETED = 0), 1, 0) as hasOversized") |
| | | .leftJoin("shop_info s1 on s1.id = t.DEPOSIT_SHOP_ID and s1.DELETED = 0") |
| | | .leftJoin("shop_info s2 on s2.id = t.TAKE_SHOP_ID and s2.DELETED = 0") |
| | | .leftJoin("category c1 on c1.id = t.GOOD_TYPE and c1.DELETED = 0") |
| | |
| | | vo.setDepositShopAddress(base.getDepositShopAddress()); |
| | | vo.setDepositDistance(base.getDepositDistance()); |
| | | vo.setTakeName(base.getTakeName()); |
| | | vo.setTakeAddress(base.getTakeAddress()); |
| | | vo.setTakeShopId(base.getTakeShopId()); |
| | | |
| | | vo.setTakeDistance(base.getTakeDistance()); |
| | | vo.setContactPhone(base.getContactPhone()); |
| | | vo.setDriverFee(base.getDriverFee()); |
| | | vo.setUrgentAmount(base.getUrgentAmount()); |
| | | vo.setIsValuable(base.getIsValuable()); |
| | | vo.setGoodLevelName(base.getGoodLevelName()); |
| | | vo.setHasOversized(base.getHasOversized()); |
| | | vo.setDriverVerifyCode(base.getDriverVerifyCode()); |
| | | |
| | | // 物品明细(转换类型) |
| | | List<DriverOrderDetailVO.OrderItem> detailItems = new ArrayList<>(); |
| | |
| | | DriverOrderDetailVO.OrderItem item = new DriverOrderDetailVO.OrderItem(); |
| | | item.setName(src.getName()); |
| | | item.setQuantity(src.getQuantity()); |
| | | item.setIsOversized(src.getIsOversized()); |
| | | detailItems.add(item); |
| | | } |
| | | } |
| | |
| | | |
| | | // 5. 原子更新:带 status=2 条件防止并发重复抢单 |
| | | Date now = new Date(); |
| | | String driverVerifyCode = generateVerifyCode(); |
| | | int rows = ordersMapper.update(new UpdateWrapper<Orders>().lambda() |
| | | .set(Orders::getAcceptDriver, driverId) |
| | | .set(Orders::getAcceptTime, now) |
| | | .set(Orders::getAcceptType, 0) // 0=手动抢单 |
| | | .set(Orders::getStatus, Constants.OrderStatus.accepted.getStatus()) |
| | | .set(Orders::getDriverVerifyCode, driverVerifyCode) |
| | | .set(Orders::getUpdateTime, now) |
| | | .eq(Orders::getId, orderId) |
| | | .eq(Orders::getStatus, Constants.TWO)); |
| | |
| | | log.setOptUserId(driver.getMemberId()); |
| | | log.setOptUserType(Constants.ONE); |
| | | log.setOrderStatus(Constants.OrderStatus.delivering.getStatus()); |
| | | log.setRemark(dto.getRemark()); |
| | | log.setCreateTime(now); |
| | | log.setDeleted(Constants.ZERO); |
| | | orderLogMapper.insert(log); |
| | |
| | | return String.format("%.1fkm", km); |
| | | } |
| | | |
| | | /** |
| | | * 生成6位数字核销码(Redis SETNX 保证唯一) |
| | | */ |
| | | private String generateVerifyCode() { |
| | | Random random = new Random(); |
| | | String redisKey = Constants.REDIS_VERIFY_CODE_KEY; |
| | | for (int i = 0; i < 200; i++) { |
| | | String code = String.format("%06d", random.nextInt(1000000)); |
| | | Boolean success = redisTemplate.opsForValue().setIfAbsent(redisKey + code, "1", 24, TimeUnit.HOURS); |
| | | if (success != null && success) { |
| | | return code; |
| | | } |
| | | } |
| | | throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "核销码生成失败,请重试"); |
| | | } |
| | | |
| | | private double haversine(double lat1, double lng1, double lat2, double lng2) { |
| | | double R = 6371; |
| | | double dLat = Math.toRadians(lat2 - lat1); |
| | |
| | | boolean hasTakeShop = order.getTakeShopId() != null && StringUtils.isNotBlank(order.getTakeShopName()); |
| | | if (hasTakeShop) { |
| | | vo.setTakeName(order.getTakeShopName()); |
| | | vo.setTakeAddress(order.getTakeShopAddress()); |
| | | vo.setTakeShopId(order.getTakeShopId()); |
| | | vo.setContactPhone(order.getTakeShopLinkPhone()); |
| | | } else { |
| | | vo.setTakeName(order.getTakeLocation()); |
| | | vo.setTakeAddress(order.getTakeLocationRemark()); |
| | | vo.setContactPhone(order.getTakePhone()); |
| | | } |
| | | if (driverLat != null && driverLng != null |
| | |
| | | |
| | | // 贵重物品 |
| | | vo.setIsValuable("1".equals(order.getC2OtherField())); |
| | | vo.setGoodLevelName(order.getGoodLevelName()); |
| | | vo.setHasOversized(order.getHasOversized()); |
| | | |
| | | // 待取货状态(status=3)返回司机取货码 |
| | | if (Constants.equalsInteger(order.getStatus(), Constants.THREE)||Constants.equalsInteger(order.getStatus(), Constants.FOUR)) { |
| | | vo.setDriverVerifyCode(order.getDriverVerifyCode()); |
| | | } |
| | | |
| | | // 物品明细 |
| | | List<OrdersDetail> details = detailMap.getOrDefault(order.getId(), Collections.emptyList()); |
| | | // 批量查询涉及到的 luggageId 对应的 category,判断是否大件 |
| | | Set<Integer> luggageIds = details.stream() |
| | | .map(OrdersDetail::getLuggageId) |
| | | .filter(Objects::nonNull) |
| | | .collect(Collectors.toSet()); |
| | | Set<Integer> oversizedIds = new HashSet<>(); |
| | | if (!luggageIds.isEmpty()) { |
| | | categoryMapper.selectList(new QueryWrapper<Category>().lambda() |
| | | .in(Category::getId, luggageIds) |
| | | .eq(Category::getType, Constants.FOUR) |
| | | .eq(Category::getOtherField, "1") |
| | | .eq(Category::getDeleted, Constants.ZERO)) |
| | | .forEach(c -> oversizedIds.add(c.getId())); |
| | | } |
| | | List<DriverGrabOrderVO.OrderItem> items = new ArrayList<>(); |
| | | for (OrdersDetail detail : details) { |
| | | DriverGrabOrderVO.OrderItem item = new DriverGrabOrderVO.OrderItem(); |
| | | item.setName(detail.getLuggageName()); |
| | | item.setQuantity(detail.getNum()); |
| | | item.setIsOversized(oversizedIds.contains(detail.getLuggageId()) ? Constants.ONE : Constants.ZERO); |
| | | items.add(item); |
| | | } |
| | | vo.setItems(items); |
| | |
| | | } |
| | | |
| | | @Override |
| | | public Map<String, Integer> getActiveOrderCount(Integer driverId) { |
| | | public DriverActiveOrderCountVO getActiveOrderCount(Integer driverId) { |
| | | // 已抢单(status=3)数量 |
| | | Long grabbed = ordersMapper.selectCount(new QueryWrapper<Orders>().lambda() |
| | | .eq(Orders::getAcceptDriver, driverId) |
| | |
| | | .eq(Orders::getAcceptDriver, driverId) |
| | | .eq(Orders::getStatus, Constants.OrderStatus.delivering.getStatus()) |
| | | .eq(Orders::getDeleted, Constants.ZERO)); |
| | | Map<String, Integer> result = new HashMap<>(); |
| | | result.put("grabbedCount", grabbed != null ? grabbed.intValue() : 0); |
| | | result.put("deliveringCount", delivering != null ? delivering.intValue() : 0); |
| | | return result; |
| | | DriverActiveOrderCountVO vo = new DriverActiveOrderCountVO(); |
| | | vo.setGrabbedCount(grabbed != null ? grabbed.intValue() : 0); |
| | | vo.setDeliveringCount(delivering != null ? delivering.intValue() : 0); |
| | | return vo; |
| | | } |
| | | |
| | | @Override |
| | | public PageData<DriverGrabOrderVO> driverOrderPage(Integer driverId, PageWrap<DriverOrderPageDTO> pageWrap) { |
| | | DriverInfo driver = driverInfoMapper.selectById(driverId); |
| | | if (driver == null) { |
| | | throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "司机信息不存在"); |
| | | } |
| | | |
| | | DriverOrderPageDTO model = pageWrap.getModel(); |
| | | Integer status = model != null ? model.getStatus() : null; |
| | | |
| | | // 合法状态校验 |
| | | List<Integer> validStatuses = Arrays.asList( |
| | | Constants.OrderStatus.accepted.getStatus(), |
| | | Constants.OrderStatus.delivering.getStatus(), |
| | | Constants.OrderStatus.finished.getStatus()); |
| | | if (status != null && !validStatuses.contains(status)) { |
| | | throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "状态只能为3(待取件)、4(配送中)、7(已完成)"); |
| | | } |
| | | |
| | | IPage<Orders> p = new Page<>(pageWrap.getPage(), pageWrap.getCapacity()); |
| | | MPJLambdaWrapper<Orders> wrapper = new MPJLambdaWrapper<>(); |
| | | wrapper.selectAll(Orders.class) |
| | | .select("s1.name", Orders::getDepositShopName) |
| | | .select("s1.address", Orders::getDepositShopAddress) |
| | | .select("s2.name", Orders::getTakeShopName) |
| | | .select("s2.address", Orders::getTakeShopAddress) |
| | | .select("s2.link_phone as takeShopLinkPhone") |
| | | .select("c2.other_field as c2OtherField") |
| | | .select("c2.name as goodLevelName") |
| | | .select("IF(EXISTS(SELECT 1 FROM orders_detail od JOIN category c3 ON c3.id = od.LUGGAGE_ID AND c3.TYPE = 4 AND c3.OTHER_FIELD = '1' WHERE od.ORDER_ID = t.ID AND od.DELETED = 0), 1, 0) as hasOversized") |
| | | .leftJoin("shop_info s1 on s1.id = t.DEPOSIT_SHOP_ID and s1.DELETED = 0") |
| | | .leftJoin("shop_info s2 on s2.id = t.TAKE_SHOP_ID and s2.DELETED = 0") |
| | | .leftJoin("category c1 on c1.id = t.GOOD_TYPE and c1.DELETED = 0") |
| | | .leftJoin("category c2 on c2.id = c1.RELATION_ID and c2.DELETED = 0 and c2.TYPE = 3") |
| | | .eq(Orders::getAcceptDriver, driverId) |
| | | .in(status == null, Orders::getStatus, validStatuses) |
| | | .eq(status != null, Orders::getStatus, status) |
| | | .eq(Orders::getDeleted, Constants.ZERO) |
| | | .orderByDesc(Orders::getAcceptTime); |
| | | |
| | | IPage<Orders> orderPage = ordersMapper.selectJoinPage(p, Orders.class, wrapper); |
| | | |
| | | List<DriverGrabOrderVO> voList = new ArrayList<>(); |
| | | if (orderPage != null && orderPage.getRecords() != null) { |
| | | // 批量查物品明细 |
| | | List<Integer> orderIds = orderPage.getRecords().stream().map(Orders::getId).collect(Collectors.toList()); |
| | | Map<Integer, List<OrdersDetail>> detailMap = new HashMap<>(); |
| | | if (!orderIds.isEmpty()) { |
| | | List<OrdersDetail> allDetails = ordersDetailMapper.selectList( |
| | | new QueryWrapper<OrdersDetail>().lambda() |
| | | .in(OrdersDetail::getOrderId, orderIds)); |
| | | for (OrdersDetail d : allDetails) { |
| | | detailMap.computeIfAbsent(d.getOrderId(), k -> new ArrayList<>()).add(d); |
| | | } |
| | | } |
| | | |
| | | Double driverLat = driver.getLatitude(); |
| | | Double driverLng = driver.getLongitude(); |
| | | Date now = new Date(); |
| | | for (Orders order : orderPage.getRecords()) { |
| | | boolean needDepositDist = Constants.equalsInteger(order.getStatus(), Constants.OrderStatus.accepted.getStatus()); |
| | | voList.add(buildDriverOrderVO(order, driverLat, driverLng, needDepositDist, now, detailMap)); |
| | | } |
| | | } |
| | | |
| | | IPage<DriverGrabOrderVO> vPage = new Page<>(pageWrap.getPage(), pageWrap.getCapacity()); |
| | | PageData<DriverGrabOrderVO> pageData = PageData.from(vPage); |
| | | pageData.setRecords(voList); |
| | | if (orderPage != null) { |
| | | pageData.setTotal(orderPage.getTotal()); |
| | | pageData.setPage(orderPage.getCurrent()); |
| | | pageData.setCapacity(orderPage.getSize()); |
| | | } |
| | | return pageData; |
| | | } |
| | | |
| | | @Override |
| | | public DriverCancelLimitVO getTodayCancelLimit(Integer driverId) { |
| | | DriverInfo driver = driverInfoMapper.selectById(driverId); |
| | | if (driver == null) { |
| | | throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "司机信息不存在"); |
| | | } |
| | | |
| | | // 每日取消上限 |
| | | String limitStr = operationConfigBiz.getConfig().getDriverDailyCancelLimit(); |
| | | int limit = 3; |
| | | if (StringUtils.isNotBlank(limitStr)) { |
| | | try { limit = Integer.parseInt(limitStr); } catch (NumberFormatException ignored) {} |
| | | } |
| | | |
| | | // 今日已取消次数 |
| | | Calendar cal = Calendar.getInstance(); |
| | | cal.set(Calendar.HOUR_OF_DAY, 0); |
| | | cal.set(Calendar.MINUTE, 0); |
| | | cal.set(Calendar.SECOND, 0); |
| | | cal.set(Calendar.MILLISECOND, 0); |
| | | Date todayStart = cal.getTime(); |
| | | Long todayCancelCount = orderLogMapper.selectCount(new QueryWrapper<OrderLog>().lambda() |
| | | .eq(OrderLog::getOptUserId, driver.getMemberId()) |
| | | .eq(OrderLog::getObjType, Constants.OrderLogType.driverCancel.getStatus()) |
| | | .eq(OrderLog::getOptUserType, Constants.ONE) |
| | | .ge(OrderLog::getCreateTime, todayStart)); |
| | | int used = todayCancelCount != null ? todayCancelCount.intValue() : 0; |
| | | |
| | | DriverCancelLimitVO vo = new DriverCancelLimitVO(); |
| | | vo.setLimit(limit); |
| | | vo.setUsed(used); |
| | | vo.setRemain(Math.max(limit - used, 0)); |
| | | return vo; |
| | | } |
| | | |
| | | } |