package com.doumee.service.business.impl;
|
|
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.biz.system.AreasBiz;
|
import com.doumee.biz.system.OperationConfigBiz;
|
import com.doumee.biz.system.SystemDictDataBiz;
|
import com.doumee.config.wx.WxMiniConfig;
|
import com.doumee.config.wx.WxMiniUtilService;
|
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.DateUtil;
|
import com.doumee.core.utils.Tencent.MapUtil;
|
import com.doumee.core.utils.Utils;
|
import com.doumee.dao.business.*;
|
import com.doumee.dao.business.model.*;
|
import com.doumee.dao.system.SystemUserMapper;
|
import com.doumee.dao.system.model.SystemUser;
|
import com.doumee.dao.dto.CalculateLocalPriceDTO;
|
import com.doumee.dao.dto.CalculateRemotePriceDTO;
|
import com.doumee.dao.dto.CreateOrderDTO;
|
import com.doumee.dao.dto.DispatchDTO;
|
import com.doumee.dao.dto.MyOrderDTO;
|
import com.doumee.dao.dto.OrderItemDTO;
|
import com.doumee.dao.vo.*;
|
import com.doumee.service.business.OrderLogService;
|
import com.doumee.service.business.OrdersService;
|
import com.github.binarywang.wxpay.bean.request.BaseWxPayRequest;
|
import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
|
import com.github.binarywang.wxpay.exception.WxPayException;
|
import com.github.xiaoymin.knife4j.core.util.CollectionUtils;
|
import com.github.yulichang.wrapper.MPJLambdaWrapper;
|
import com.alibaba.fastjson.JSONObject;
|
import org.apache.commons.lang3.StringUtils;
|
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.data.redis.core.RedisTemplate;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.web.context.request.RequestContextHolder;
|
import org.springframework.web.context.request.ServletRequestAttributes;
|
|
import javax.servlet.http.HttpServletRequest;
|
import java.math.BigDecimal;
|
import java.math.RoundingMode;
|
import java.net.InetAddress;
|
import java.net.UnknownHostException;
|
import java.util.*;
|
import java.util.concurrent.TimeUnit;
|
import java.util.stream.Collectors;
|
|
/**
|
* 寄存订单信息Service实现
|
* @author rk
|
* @date 2026/04/10
|
*/
|
@Service
|
public class OrdersServiceImpl implements OrdersService {
|
|
@Autowired
|
private OrdersMapper ordersMapper;
|
|
@Autowired
|
private MemberMapper memberMapper;
|
|
@Autowired
|
private ShopInfoMapper shopInfoMapper;
|
|
@Autowired
|
private DriverInfoMapper driverInfoMapper;
|
|
@Autowired
|
private CategoryMapper categoryMapper;
|
|
@Autowired
|
private MultifileMapper multifileMapper;
|
|
@Autowired
|
private OrdersDetailMapper ordersDetailMapper;
|
|
@Autowired
|
private SystemDictDataBiz systemDictDataBiz;
|
|
@Autowired
|
private OrderLogService orderLogService;
|
|
@Autowired
|
private OrdersRefundMapper ordersRefundMapper;
|
|
@Autowired
|
private WxMiniUtilService wxMiniUtilService;
|
|
@Autowired
|
private SystemUserMapper systemUserMapper;
|
|
@Autowired
|
private PricingRuleMapper pricingRuleMapper;
|
@Autowired
|
private RedisTemplate<String, Object> redisTemplate;
|
|
@Autowired
|
private AreasBiz areasBiz;
|
|
@Autowired
|
private OperationConfigBiz operationConfigBiz;
|
|
@Override
|
public Integer create(Orders orders) {
|
ordersMapper.insert(orders);
|
return orders.getId();
|
}
|
|
@Override
|
public void deleteById(Integer id) {
|
ordersMapper.update(new UpdateWrapper<Orders>().lambda()
|
.set(Orders::getDeleted, Constants.ONE)
|
.eq(Orders::getId, id));
|
}
|
|
@Override
|
public void delete(Orders orders) {
|
UpdateWrapper<Orders> deleteWrapper = new UpdateWrapper<>(orders);
|
ordersMapper.delete(deleteWrapper);
|
}
|
|
@Override
|
public void deleteByIdInBatch(List<Integer> ids) {
|
if (ids == null || ids.isEmpty()) {
|
return;
|
}
|
ordersMapper.deleteBatchIds(ids);
|
}
|
|
@Override
|
public void updateById(Orders orders) {
|
ordersMapper.updateById(orders);
|
}
|
|
@Override
|
public void updateByIdInBatch(List<Orders> ordersList) {
|
if (ordersList == null || ordersList.isEmpty()) {
|
return;
|
}
|
for (Orders orders : ordersList) {
|
this.updateById(orders);
|
}
|
}
|
|
@Override
|
public Orders findById(Integer id) {
|
Orders orders = ordersMapper.selectById(id);
|
if (Objects.isNull(orders)) {
|
throw new BusinessException(ResponseStatus.DATA_EMPTY);
|
}
|
return orders;
|
}
|
|
@Override
|
public Orders findOne(Orders orders) {
|
QueryWrapper<Orders> wrapper = new QueryWrapper<>(orders);
|
return ordersMapper.selectOne(wrapper);
|
}
|
|
@Override
|
public List<Orders> findList(Orders orders) {
|
QueryWrapper<Orders> wrapper = new QueryWrapper<>(orders);
|
return ordersMapper.selectList(wrapper);
|
}
|
|
@Override
|
public PageData<Orders> findPage(PageWrap<Orders> pageWrap) {
|
IPage<Orders> page = new Page<>(pageWrap.getPage(), pageWrap.getCapacity());
|
MPJLambdaWrapper<Orders> queryWrapper = new MPJLambdaWrapper<Orders>()
|
.selectAll(Orders.class)
|
.selectAs(Category::getDetail, Orders::getOrderLevel)
|
.select("s1.name", Orders::getDepositShopName)
|
.leftJoin(Category.class, Category::getId, Orders::getGoodType)
|
.leftJoin(DriverInfo.class, DriverInfo::getId, Orders::getAcceptDriver)
|
.leftJoin("shop_info s1 on s1.id = t.DEPOSIT_SHOP_ID")
|
.leftJoin("shop_info s2 on s2.id = t.TAKE_SHOP_ID");
|
;
|
Utils.MP.blankToNull(pageWrap.getModel());
|
pageWrap.getModel().setDeleted(Constants.ZERO);
|
queryWrapper.eq(pageWrap.getModel().getDeleted() != null, Orders::getDeleted, pageWrap.getModel().getDeleted());
|
queryWrapper.like(StringUtils.isNotBlank(pageWrap.getModel().getCode()), Orders::getCode, pageWrap.getModel().getCode());
|
queryWrapper.like(StringUtils.isNotBlank(pageWrap.getModel().getDepositShopName()), "s1.name", pageWrap.getModel().getDepositShopName());
|
queryWrapper.like(StringUtils.isNotBlank(pageWrap.getModel().getTakeShopName()), "s2.name", pageWrap.getModel().getTakeShopName());
|
queryWrapper.like(StringUtils.isNotBlank(pageWrap.getModel().getGoodsInfo()), Orders::getGoodsInfo, pageWrap.getModel().getGoodsInfo());
|
queryWrapper.ge(pageWrap.getModel().getCreateStartTime() != null, Orders::getCreateTime, Utils.Date.getStart(pageWrap.getModel().getCreateStartTime()));
|
queryWrapper.le(pageWrap.getModel().getCreateEndTime() != null, Orders::getCreateTime, Utils.Date.getEnd(pageWrap.getModel().getCreateEndTime()));
|
queryWrapper.eq(pageWrap.getModel().getDepositShopId() != null, Orders::getDepositShopId, pageWrap.getModel().getDepositShopId());
|
queryWrapper.eq(pageWrap.getModel().getType() != null, Orders::getType, pageWrap.getModel().getType());
|
queryWrapper.eq(pageWrap.getModel().getStatus() != null, Orders::getStatus, pageWrap.getModel().getStatus());
|
queryWrapper.eq(pageWrap.getModel().getTakeShopId() != null, Orders::getTakeShopId, pageWrap.getModel().getTakeShopId());
|
queryWrapper.and(pageWrap.getModel().getDriverKeyword() != null, i->i.like(DriverInfo::getName, pageWrap.getModel().getDriverKeyword())
|
.or().like(DriverInfo::getTelephone, pageWrap.getModel().getDriverKeyword()));
|
for (PageWrap.SortData sortData : pageWrap.getSorts()) {
|
if (sortData.getDirection().equalsIgnoreCase(PageWrap.DESC)) {
|
queryWrapper.orderByDesc(sortData.getProperty());
|
} else {
|
queryWrapper.orderByAsc(sortData.getProperty());
|
}
|
}
|
return PageData.from(ordersMapper.selectJoinPage(page, Orders.class, queryWrapper));
|
}
|
|
@Override
|
public OrderSummaryVO findSummary(PageWrap<Orders> pageWrap) {
|
// 构建与findPage相同的查询条件
|
MPJLambdaWrapper<Orders> queryWrapper = new MPJLambdaWrapper<Orders>()
|
.leftJoin(Category.class, Category::getId, Orders::getGoodType)
|
.leftJoin(DriverInfo.class, DriverInfo::getId, Orders::getAcceptDriver)
|
.leftJoin("shop_info s1 on s1.id = t.DEPOSIT_SHOP_ID")
|
.leftJoin("shop_info s2 on s2.id = t.TAKE_SHOP_ID");
|
Utils.MP.blankToNull(pageWrap.getModel());
|
pageWrap.getModel().setDeleted(Constants.ZERO);
|
queryWrapper.eq(pageWrap.getModel().getDeleted() != null, Orders::getDeleted, pageWrap.getModel().getDeleted());
|
queryWrapper.like(StringUtils.isNotBlank(pageWrap.getModel().getCode()), Orders::getCode, pageWrap.getModel().getCode());
|
queryWrapper.like(StringUtils.isNotBlank(pageWrap.getModel().getDepositShopName()), "s1.name", pageWrap.getModel().getDepositShopName());
|
queryWrapper.like(StringUtils.isNotBlank(pageWrap.getModel().getTakeShopName()), "s2.name", pageWrap.getModel().getTakeShopName());
|
queryWrapper.like(StringUtils.isNotBlank(pageWrap.getModel().getGoodsInfo()), Orders::getGoodsInfo, pageWrap.getModel().getGoodsInfo());
|
queryWrapper.ge(pageWrap.getModel().getCreateStartTime() != null, Orders::getCreateTime, Utils.Date.getStart(pageWrap.getModel().getCreateStartTime()));
|
queryWrapper.le(pageWrap.getModel().getCreateEndTime() != null, Orders::getCreateTime, Utils.Date.getEnd(pageWrap.getModel().getCreateEndTime()));
|
queryWrapper.eq(pageWrap.getModel().getDepositShopId() != null, Orders::getDepositShopId, pageWrap.getModel().getDepositShopId());
|
queryWrapper.eq(pageWrap.getModel().getType() != null, Orders::getType, pageWrap.getModel().getType());
|
queryWrapper.eq(pageWrap.getModel().getStatus() != null, Orders::getStatus, pageWrap.getModel().getStatus());
|
queryWrapper.eq(pageWrap.getModel().getTakeShopId() != null, Orders::getTakeShopId, pageWrap.getModel().getTakeShopId());
|
queryWrapper.and(pageWrap.getModel().getDriverKeyword() != null, i->i.like(DriverInfo::getName, pageWrap.getModel().getDriverKeyword())
|
.or().like(DriverInfo::getTelephone, pageWrap.getModel().getDriverKeyword()));
|
|
queryWrapper.select(
|
"IFNULL(SUM(t.total_amount), 0) as total_amount_sum",
|
"IFNULL(SUM(CASE WHEN t.settlement_status = 1 THEN t.total_amount ELSE 0 END), 0) as settled_total_amount_sum",
|
"IFNULL(SUM(t.driver_fee), 0) as driver_fee_sum",
|
"IFNULL(SUM(CASE WHEN t.settlement_status = 1 THEN t.driver_fee ELSE 0 END), 0) as settled_driver_fee_sum"
|
);
|
queryWrapper.groupBy("1=1");
|
|
List<Map<String, Object>> result = ordersMapper.selectJoinMaps(queryWrapper);
|
OrderSummaryVO vo = new OrderSummaryVO();
|
if (result != null && !result.isEmpty()) {
|
Map<String, Object> row = result.get(0);
|
vo.setTotalAmountSum(toLong(row.get("total_amount_sum")));
|
vo.setSettledTotalAmountSum(toLong(row.get("settled_total_amount_sum")));
|
vo.setDriverFeeSum(toLong(row.get("driver_fee_sum")));
|
vo.setSettledDriverFeeSum(toLong(row.get("settled_driver_fee_sum")));
|
} else {
|
vo.setTotalAmountSum(0L);
|
vo.setSettledTotalAmountSum(0L);
|
vo.setDriverFeeSum(0L);
|
vo.setSettledDriverFeeSum(0L);
|
}
|
return vo;
|
}
|
|
private Long toLong(Object val) {
|
if (val == null) return 0L;
|
if (val instanceof Number) return ((Number) val).longValue();
|
return Long.parseLong(val.toString());
|
}
|
|
@Override
|
public BigDecimal calculateInsuranceFee(BigDecimal declaredValue) {
|
if (declaredValue == null || declaredValue.compareTo(BigDecimal.ZERO) <= 0) {
|
return BigDecimal.ZERO;
|
}
|
String rateStr = systemDictDataBiz.queryByCode(Constants.OPERATION_CONFIG, Constants.OP_INSURANCE_RATE).getCode();
|
BigDecimal rate = new BigDecimal(rateStr);
|
return declaredValue.multiply(rate).setScale(2, BigDecimal.ROUND_HALF_UP);
|
}
|
|
/**
|
* 计算就地存取预估费用
|
*
|
* 计算规则:
|
* 1. 根据城市+物品类型 查询 pricing_rule(type=0),fieldA=categoryId, fieldB=单价(分/天)
|
* 2. 每项小计 = 单价 × 数量 × 天数
|
* 3. 物品价格 = 各项小计之和
|
* 4. 保价费用 = 报价金额 × 保价费率(字典 INSURANCE_RATE),元转分
|
* 5. 总价格 = 物品价格 + 保价费用
|
*
|
* @param dto 就地存取计价请求参数
|
* @return 价格计算结果
|
*/
|
@Override
|
public PriceCalculateVO calculateLocalPrice(CalculateLocalPriceDTO dto) {
|
// 天数校验,最少1天
|
int days = dto.getEstimatedDepositDays() != null && dto.getEstimatedDepositDays() > 0
|
? dto.getEstimatedDepositDays() : 1;
|
|
// 收集所有物品类型ID
|
List<Integer> categoryIds = new ArrayList<>();
|
for (OrderItemDTO item : dto.getItems()) {
|
categoryIds.add(item.getCategoryId());
|
}
|
|
// 批量查询计价规则 pricing_rule type=0:fieldA=categoryId, fieldB=单价(分/天)
|
List<String> fieldAList = new ArrayList<>();
|
for (Integer cid : categoryIds) {
|
fieldAList.add(String.valueOf(cid));
|
}
|
List<PricingRule> rules = pricingRuleMapper.selectList(new QueryWrapper<PricingRule>().lambda()
|
.eq(PricingRule::getDeleted, Constants.ZERO)
|
.eq(PricingRule::getType, Constants.ZERO)
|
.eq(PricingRule::getCityId, dto.getCityId())
|
.in(PricingRule::getFieldA, fieldAList));
|
Map<String, PricingRule> ruleMap = new HashMap<>();
|
for (PricingRule r : rules) {
|
ruleMap.put(r.getFieldA(), r);
|
}
|
|
// 批量查询物品类型名称
|
List<Category> categories = categoryMapper.selectBatchIds(categoryIds);
|
Map<Integer, String> categoryNameMap = new HashMap<>();
|
Map<Integer, String> categoryDetailMap = new HashMap<>();
|
for (Category c : categories) {
|
categoryNameMap.put(c.getId(), c.getName());
|
categoryDetailMap.put(c.getId(), c.getDetail());
|
}
|
|
// 计算每项物品费用:小计 = 单价 × 数量 × 天数
|
List<ItemPriceVO> itemList = new ArrayList<>();
|
long itemPriceTotal = 0L;
|
|
for (OrderItemDTO item : dto.getItems()) {
|
PricingRule rule = ruleMap.get(String.valueOf(item.getCategoryId()));
|
if (rule == null) {
|
throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(),
|
"未找到该城市物品类型的计价规则");
|
}
|
|
long unitPrice = Long.parseLong(rule.getFieldB());
|
long subtotal = unitPrice * item.getQuantity() * days;
|
|
ItemPriceVO vo = new ItemPriceVO();
|
vo.setCategoryId(item.getCategoryId());
|
vo.setCategoryName(categoryNameMap.getOrDefault(item.getCategoryId(), ""));
|
vo.setDetail(categoryDetailMap.get(item.getCategoryId()));
|
vo.setQuantity(item.getQuantity());
|
vo.setUnitPrice(unitPrice);
|
vo.setLocallyPrice(unitPrice);
|
vo.setSubtotal(subtotal);
|
itemList.add(vo);
|
|
itemPriceTotal += subtotal;
|
}
|
|
// 保价费用:报价金额 × 保价费率(字典 INSURANCE_RATE),元→分
|
long insuranceFeeFen = 0L;
|
if (Boolean.TRUE.equals(dto.getInsured()) && dto.getDeclaredAmount() != null) {
|
BigDecimal insuranceFeeYuan = calculateInsuranceFee(dto.getDeclaredAmount());
|
insuranceFeeFen = insuranceFeeYuan.multiply(new BigDecimal(100)).longValue();
|
}
|
|
// 总价格 = 物品价格 + 保价费用
|
long totalPrice = itemPriceTotal + insuranceFeeFen;
|
|
PriceCalculateVO result = new PriceCalculateVO();
|
result.setItemList(itemList);
|
result.setItemPrice(itemPriceTotal);
|
result.setInsuranceFee(insuranceFeeFen);
|
result.setTotalPrice(totalPrice);
|
result.setDays(days);
|
result.setUrgentFee(0L);
|
return result;
|
}
|
|
/**
|
* 计算异地存取预估费用
|
*
|
* 计算规则:
|
* 1. 调用腾讯地图API计算寄件点与取件点的驾车距离(米→公里)
|
* 2. 根据城市+物品类型 查询 pricing_rule(type=1):
|
* fieldB=起步距离(km), fieldC=起步价(分), fieldD=超出距离单位(km), fieldE=超出距离单价(分)
|
* 3. 每项运费单价:
|
* - 距离 ≤ 起步距离 → 单价 = 起步价
|
* - 距离 > 起步距离 → 单价 = 起步价 + ceil((距离-起步距离)/超出距离单位) × 超出距离单价
|
* 4. 小计 = 运费单价 × 数量
|
* 5. 物品价格 = 各项小计之和
|
* 6. 保价费用 = 报价金额 × 保价费率(字典 INSURANCE_RATE),元转分
|
* 7. 加急费用 = 物品价格 × 加急系数(字典 URGENT_COEFFICIENT)
|
* 8. 总价格 = 物品价格 + 保价费用 + 加急费用
|
*
|
* @param dto 异地存取计价请求参数
|
* @return 价格计算结果
|
*/
|
@Override
|
public PriceCalculateVO calculateRemotePrice(CalculateRemotePriceDTO dto) {
|
// 1. 调用腾讯地图距离矩阵API计算驾车距离
|
String from = dto.getFromLat() + "," + dto.getFromLgt();
|
String to = dto.getToLat() + "," + dto.getToLgt();
|
JSONObject distanceResult = MapUtil.distanceSingle("driving", from, to);
|
BigDecimal distance = distanceResult.getBigDecimal("distance");
|
// distance 单位为米,转为公里
|
BigDecimal distanceKm = distance.divide(new BigDecimal(1000), 2, RoundingMode.HALF_UP);
|
|
// 收集所有物品类型ID
|
List<Integer> categoryIds = new ArrayList<>();
|
for (OrderItemDTO item : dto.getItems()) {
|
categoryIds.add(item.getCategoryId());
|
}
|
|
// 2. 批量查询配送计价规则 pricing_rule type=1
|
List<String> fieldAList = new ArrayList<>();
|
for (Integer cid : categoryIds) {
|
fieldAList.add(String.valueOf(cid));
|
}
|
List<PricingRule> rules = pricingRuleMapper.selectList(new QueryWrapper<PricingRule>().lambda()
|
.eq(PricingRule::getDeleted, Constants.ZERO)
|
.eq(PricingRule::getType, Constants.ONE)
|
.eq(PricingRule::getCityId, dto.getCityId())
|
.in(PricingRule::getFieldA, fieldAList));
|
Map<String, PricingRule> ruleMap = new HashMap<>();
|
for (PricingRule r : rules) {
|
ruleMap.put(r.getFieldA(), r);
|
}
|
|
// 查询就地存取计价规则 pricing_rule type=0,用于获取 locallyPrice
|
List<PricingRule> localRules = pricingRuleMapper.selectList(new QueryWrapper<PricingRule>().lambda()
|
.eq(PricingRule::getDeleted, Constants.ZERO)
|
.eq(PricingRule::getType, Constants.ZERO)
|
.eq(PricingRule::getCityId, dto.getCityId())
|
.in(PricingRule::getFieldA, fieldAList));
|
Map<String, PricingRule> localRuleMap = new HashMap<>();
|
for (PricingRule r : localRules) {
|
localRuleMap.put(r.getFieldA(), r);
|
}
|
|
// 批量查询物品类型名称
|
List<Category> categories = categoryMapper.selectBatchIds(categoryIds);
|
Map<Integer, String> categoryNameMap = new HashMap<>();
|
Map<Integer, String> categoryDetailMap = new HashMap<>();
|
for (Category c : categories) {
|
categoryNameMap.put(c.getId(), c.getName());
|
categoryDetailMap.put(c.getId(), c.getDetail());
|
}
|
|
// 3. 逐项计算运费:起步价 + 超出部分阶梯价
|
List<ItemPriceVO> itemList = new ArrayList<>();
|
long itemPriceTotal = 0L;
|
|
for (OrderItemDTO item : dto.getItems()) {
|
PricingRule rule = ruleMap.get(String.valueOf(item.getCategoryId()));
|
if (rule == null) {
|
throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(),
|
"未找到该城市物品类型的配送计价规则");
|
}
|
|
// fieldB=起步距离(km), fieldC=起步价(分), fieldD=超出距离单位(km), fieldE=超出距离单价(分)
|
BigDecimal startDistance = new BigDecimal(rule.getFieldB());
|
long startPrice = Long.parseLong(rule.getFieldC());
|
BigDecimal extraDistanceUnit = new BigDecimal(rule.getFieldD());
|
long extraPricePerUnit = Long.parseLong(rule.getFieldE());
|
|
// 阶梯计价:距离 ≤ 起步距离取起步价,超出按 ceil(超出距离/单位) × 单价累加
|
long unitPrice;
|
if (distanceKm.compareTo(startDistance) <= 0) {
|
unitPrice = startPrice;
|
} else {
|
BigDecimal extraKm = distanceKm.subtract(startDistance);
|
BigDecimal extraCount = extraKm.divide(extraDistanceUnit, 0, RoundingMode.CEILING);
|
unitPrice = startPrice + extraCount.longValue() * extraPricePerUnit;
|
}
|
|
long subtotal = unitPrice * item.getQuantity();
|
|
// 就地存取单价
|
PricingRule localRule = localRuleMap.get(String.valueOf(item.getCategoryId()));
|
Long locallyPrice = localRule != null ? Long.parseLong(localRule.getFieldB()) : null;
|
|
ItemPriceVO vo = new ItemPriceVO();
|
vo.setCategoryId(item.getCategoryId());
|
vo.setCategoryName(categoryNameMap.getOrDefault(item.getCategoryId(), ""));
|
vo.setDetail(categoryDetailMap.get(item.getCategoryId()));
|
vo.setQuantity(item.getQuantity());
|
vo.setUnitPrice(unitPrice);
|
vo.setLocallyPrice(locallyPrice);
|
vo.setSubtotal(subtotal);
|
vo.setStartDistance(startDistance);
|
vo.setStartPrice(startPrice);
|
vo.setExtraDistance(extraDistanceUnit);
|
vo.setExtraPrice(extraPricePerUnit);
|
itemList.add(vo);
|
|
itemPriceTotal += subtotal;
|
}
|
|
// 4. 保价费用:报价金额 × 保价费率(字典 INSURANCE_RATE),元→分
|
long insuranceFeeFen = 0L;
|
if (Boolean.TRUE.equals(dto.getInsured()) && dto.getDeclaredAmount() != null) {
|
BigDecimal insuranceFeeYuan = calculateInsuranceFee(dto.getDeclaredAmount());
|
insuranceFeeFen = insuranceFeeYuan.multiply(new BigDecimal(100)).longValue();
|
}
|
|
// 5. 加急费用:物品价格 × 加急系数(字典 URGENT_COEFFICIENT)
|
long urgentFeeFen = 0L;
|
if (Boolean.TRUE.equals(dto.getUrgent())) {
|
String urgentRateStr = systemDictDataBiz.queryByCode(
|
Constants.OPERATION_CONFIG, Constants.OP_URGENT_COEFFICIENT).getCode();
|
BigDecimal urgentRate = new BigDecimal(urgentRateStr);
|
urgentFeeFen = new BigDecimal(itemPriceTotal).multiply(urgentRate)
|
.setScale(0, RoundingMode.HALF_UP).longValue();
|
}
|
|
// 6. 总价格 = 物品价格 + 保价费用 + 加急费用
|
long totalPrice = itemPriceTotal + insuranceFeeFen + urgentFeeFen;
|
|
PriceCalculateVO result = new PriceCalculateVO();
|
result.setItemList(itemList);
|
result.setItemPrice(itemPriceTotal);
|
result.setInsuranceFee(insuranceFeeFen);
|
result.setUrgentFee(urgentFeeFen);
|
result.setTotalPrice(totalPrice);
|
result.setDistance(distanceKm);
|
return result;
|
}
|
|
@Override
|
public long count(Orders orders) {
|
QueryWrapper<Orders> wrapper = new QueryWrapper<>(orders);
|
return ordersMapper.selectCount(wrapper);
|
}
|
|
/**
|
* 创建订单
|
*
|
* 业务流程:
|
* 1. 参数校验:必填字段、时间顺序、物品不重复、异地必填服务时效
|
* 2. 查询寄件店铺信息(获取经纬度、地址)
|
* 3. 调用计价接口计算费用(calculateLocalPrice / calculateRemotePrice)
|
* 4. 生成订单编号:JC + yyyyMMddHHmmss + 4位随机数
|
* 5. 创建订单主表 Orders(状态=待支付)
|
* 6. 创建订单明细表 OrdersDetail(每项物品尺寸)
|
* 7. 创建附件记录 Multifile(物品图片 objType=12)
|
*
|
* @param dto 创建订单请求参数
|
* @param memberId 当前登录会员ID
|
* @return 订单ID
|
*/
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public PayResponse createOrder(CreateOrderDTO dto, Integer memberId) {
|
String lockKey = Constants.GOODS_ORDER_CREATE_LOCK + memberId;
|
//判断前端是否在同一页面创建了两次订单
|
if (redisTemplate.hasKey(lockKey)) {
|
throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(),"正在创建订单,请勿重复调用!");
|
} else {
|
redisTemplate.opsForValue().set(lockKey, "", 5, TimeUnit.SECONDS);
|
}
|
Date now = new Date();
|
// ========== 1. 参数校验 ==========
|
// 预计到店存件时间必须小于预计到店取件时间
|
java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm");
|
Date depositTime;
|
Date takeTime;
|
try {
|
depositTime = sdf.parse(dto.getExpectedDepositTime());
|
takeTime = sdf.parse(dto.getExpectedTakeTime());
|
} catch (java.text.ParseException e) {
|
throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "时间格式错误,正确格式:yyyy-MM-dd HH:mm");
|
}
|
if (!depositTime.before(takeTime)) {
|
throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "预计到店存件时间必须小于预计到店取件时间");
|
}
|
|
// 物品尺寸不能重复
|
List<Integer> categoryIds = new ArrayList<>();
|
for (OrderItemDTO item : dto.getItems()) {
|
if (categoryIds.contains(item.getCategoryId())) {
|
throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "物品尺寸不能重复");
|
}
|
categoryIds.add(item.getCategoryId());
|
}
|
|
// 物品图片最多3张
|
if (dto.getGoodsImages() != null && dto.getGoodsImages().size() > 3) {
|
throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "物品图片最多3张");
|
}
|
|
// ========== 2. 校验物品类型 ==========
|
Category goodTypeCategory = categoryMapper.selectById(dto.getGoodType());
|
if (goodTypeCategory == null || Constants.equalsInteger(goodTypeCategory.getDeleted(), Constants.ONE)) {
|
throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "物品类型不存在");
|
}
|
if (!Constants.equalsInteger(goodTypeCategory.getType(), Constants.TWO)) {
|
throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "物品类型参数错误");
|
}
|
|
// ========== 3. 查询寄件店铺信息 ==========
|
ShopInfo depositShop = shopInfoMapper.selectById(dto.getDepositShopId());
|
if (depositShop == null) {
|
throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "寄件店铺不存在");
|
}
|
|
// ========== 4. 计算费用 ==========
|
|
// 异地寄存:校验取件点
|
BigDecimal takeLat = null;
|
BigDecimal takeLgt = null;
|
String takeLocationValue = null;
|
ShopInfo takeShop = null;
|
if (Constants.ONE.equals(dto.getType())) {
|
// 异地必填服务时效
|
if (dto.getIsUrgent() == null) {
|
throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "异地寄存服务时效不能为空");
|
}
|
// 取件点:店铺 or 自选点,至少提供一组
|
if (dto.getTakeShopId() != null) {
|
takeShop = shopInfoMapper.selectById(dto.getTakeShopId());
|
if (takeShop == null) {
|
throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "取件店铺不存在");
|
}
|
takeLat = BigDecimal.valueOf(takeShop.getLatitude());
|
takeLgt = BigDecimal.valueOf(takeShop.getLongitude());
|
takeLocationValue = takeShop.getAddress();
|
} else if (dto.getTakeLat() != null && dto.getTakeLgt() != null && StringUtils.isNotBlank(dto.getTakeLocation())) {
|
takeLat = dto.getTakeLat();
|
takeLgt = dto.getTakeLgt();
|
takeLocationValue = dto.getTakeLocation();
|
} else {
|
throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "请选择取件店铺或输入自选取件地址");
|
}
|
} else {
|
// 就地存取:取件门店同寄件门店
|
takeShop = depositShop;
|
}
|
|
// ========== 3. 计算费用 ==========
|
PriceCalculateVO priceResult;
|
if (Constants.ZERO.equals(dto.getType())) {
|
// 就地寄存:计算天数
|
long diffMs = takeTime.getTime() - depositTime.getTime();
|
int days = (int) Math.max(1, (diffMs / (1000 * 60 * 60 * 24)) + 1);
|
|
CalculateLocalPriceDTO priceDTO = new CalculateLocalPriceDTO();
|
priceDTO.setCityId(dto.getCityId());
|
priceDTO.setEstimatedDepositDays(days);
|
priceDTO.setItems(dto.getItems());
|
priceDTO.setInsured(dto.getDeclaredAmount() != null && dto.getDeclaredAmount().compareTo(BigDecimal.ZERO) > 0);
|
priceDTO.setDeclaredAmount(dto.getDeclaredAmount());
|
priceResult = calculateLocalPrice(priceDTO);
|
} else {
|
// 异地寄存
|
CalculateRemotePriceDTO priceDTO = new CalculateRemotePriceDTO();
|
priceDTO.setCityId(dto.getCityId());
|
priceDTO.setFromLat(BigDecimal.valueOf(depositShop.getLatitude()));
|
priceDTO.setFromLgt(BigDecimal.valueOf(depositShop.getLongitude()));
|
priceDTO.setToLat(takeLat);
|
priceDTO.setToLgt(takeLgt);
|
priceDTO.setItems(dto.getItems());
|
priceDTO.setInsured(dto.getDeclaredAmount() != null && dto.getDeclaredAmount().compareTo(BigDecimal.ZERO) > 0);
|
priceDTO.setDeclaredAmount(dto.getDeclaredAmount());
|
priceDTO.setUrgent(Constants.ONE.equals(dto.getIsUrgent()));
|
priceResult = calculateRemotePrice(priceDTO);
|
}
|
|
// ========== 5. 生成订单编号 ==========
|
String orderCode = "JC" + new java.text.SimpleDateFormat("yyyyMMddHHmmss").format(now)
|
+ String.format("%04d", new java.util.Random().nextInt(10000));
|
// 生成32位唯一第三方订单编号
|
String orderTradeNo = generateOrderTradeNo();
|
|
// ========== 6. 创建订单主表 ==========
|
Orders orders = new Orders();
|
orders.setCode(orderCode);
|
orders.setOutTradeNo(orderTradeNo);
|
orders.setMemberId(memberId);
|
orders.setType(dto.getType());
|
orders.setCityId(String.valueOf(dto.getCityId()));
|
orders.setStatus(Constants.ZERO); // 待支付
|
orders.setPayStatus(Constants.ZERO); // 未支付
|
orders.setCommentStatus(Constants.ZERO); // 未评价
|
orders.setSettlementStatus(Constants.ZERO); // 未结算
|
orders.setDeleted(Constants.ZERO);
|
orders.setCreateTime(now);
|
orders.setUpdateTime(now);
|
|
// 寄件信息
|
orders.setDepositShopId(dto.getDepositShopId());
|
// 存件地点:省市区全路径 + 地址描述
|
String depositLocationRemark = depositShop.getAddress();
|
if (depositShop.getAreaId() != null) {
|
Areas depositArea = areasBiz.resolveArea(depositShop.getAreaId());
|
if (depositArea != null) {
|
depositLocationRemark = depositArea.getProvinceName() + depositArea.getCityName() + depositArea.getName() + depositShop.getAddress();
|
}
|
}
|
orders.setDepositLocation(depositLocationRemark);
|
orders.setDepositLocationRemark(depositShop.getAddress());
|
orders.setDepositLat(BigDecimal.valueOf(depositShop.getLatitude()));
|
orders.setDepositLgt(BigDecimal.valueOf(depositShop.getLongitude()));
|
|
// 取件信息
|
orders.setTakeUser(dto.getTakeUser());
|
orders.setTakePhone(dto.getTakePhone());
|
orders.setExpectedDepositTime(depositTime);
|
orders.setExpectedTakeTime(takeTime);
|
// 计算预计存放天数
|
long dayDiff = (takeTime.getTime() - depositTime.getTime()) / (1000 * 60 * 60 * 24);
|
orders.setEstimatedDepositDays((int) Math.max(1, dayDiff + 1));
|
|
if (Constants.ONE.equals(dto.getType())) {
|
// 异地:取件点信息
|
orders.setTakeShopId(dto.getTakeShopId());
|
orders.setTakeLocation(takeLocationValue);
|
orders.setTakeLat(takeLat);
|
orders.setTakeLgt(takeLgt);
|
orders.setIsUrgent(dto.getIsUrgent());
|
} else {
|
// 就地:取件点同寄件店铺
|
orders.setTakeShopId(dto.getDepositShopId());
|
orders.setTakeLocation(depositShop.getAddress());
|
orders.setTakeLat(BigDecimal.valueOf(depositShop.getLatitude()));
|
orders.setTakeLgt(BigDecimal.valueOf(depositShop.getLongitude()));
|
orders.setIsUrgent(Constants.ZERO);
|
}
|
|
// 物品信息
|
orders.setGoodType(dto.getGoodType());
|
// 拼接物品信息:物品类型名称、尺寸名称*数量(数组字符串)
|
List<String> goodsParts = new ArrayList<>();
|
for (ItemPriceVO itemVO : priceResult.getItemList()) {
|
goodsParts.add(itemVO.getCategoryName() + "*" + itemVO.getQuantity());
|
}
|
orders.setGoodsInfo(goodTypeCategory.getName() + "、" + String.join(",", goodsParts));
|
orders.setRemark(dto.getRemark());
|
orders.setSelfTake(Constants.ZERO);
|
|
// 费用信息(分)
|
orders.setBasicAmount(priceResult.getItemPrice());
|
orders.setEstimatedAmount(priceResult.getTotalPrice());
|
orders.setTotalAmount(priceResult.getTotalPrice());
|
orders.setUrgentAmount(priceResult.getUrgentFee());
|
if (dto.getDeclaredAmount() != null && dto.getDeclaredAmount().compareTo(BigDecimal.ZERO) > 0) {
|
orders.setDeclaredAmount(dto.getDeclaredAmount().multiply(new BigDecimal(100)).longValue());
|
} else {
|
orders.setDeclaredAmount(0L);
|
}
|
orders.setDeclaredFee(priceResult.getInsuranceFee());
|
orders.setPrice(priceResult.getItemPrice());
|
|
// 薪酬计算与占比存储
|
calculateAndSetFeeAllocation(orders, depositShop, takeShop);
|
|
ordersMapper.insert(orders);
|
Integer orderId = orders.getId();
|
|
// ========== 7. 创建订单明细 ==========
|
for (ItemPriceVO itemVO : priceResult.getItemList()) {
|
OrdersDetail detail = new OrdersDetail();
|
detail.setOrderId(orderId);
|
detail.setLuggageId(itemVO.getCategoryId());
|
detail.setLuggageName(itemVO.getCategoryName());
|
detail.setLuggageDetail(itemVO.getDetail());
|
detail.setNum(itemVO.getQuantity());
|
detail.setUnitPrice(itemVO.getUnitPrice());
|
detail.setStartDistance(itemVO.getStartDistance());
|
detail.setStartPrice(itemVO.getStartPrice());
|
detail.setExtraDistance(itemVO.getExtraDistance());
|
detail.setExtraPrice(itemVO.getExtraPrice());
|
detail.setLocallyPrice(itemVO.getLocallyPrice());
|
detail.setDeleted(Constants.ZERO);
|
detail.setCreateTime(now);
|
ordersDetailMapper.insert(detail);
|
}
|
// ========== 8. 保存物品图片附件 ==========
|
if (dto.getGoodsImages() != null && !dto.getGoodsImages().isEmpty()) {
|
int sortNum = 1;
|
for (String imgUrl : dto.getGoodsImages()) {
|
Multifile multifile = new Multifile();
|
multifile.setObjId(orderId);
|
multifile.setObjType(Constants.FileType.ORDER_FILE.getKey());
|
multifile.setType(Constants.ZERO);
|
multifile.setFileurl(imgUrl);
|
multifile.setIsdeleted(Constants.ZERO);
|
multifile.setCreateDate(now);
|
multifile.setSortnum(sortNum++);
|
multifileMapper.insert(multifile);
|
}
|
}
|
// ========== 9. 唤起微信支付 ==========
|
Member member = memberMapper.selectById(memberId);
|
if (member == null || StringUtils.isBlank(member.getOpenid())) {
|
throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "用户信息异常,无法发起支付");
|
}
|
PayResponse payResponse = wxPay(orders, member.getOpenid(), Constants.OrdersAttach.STORAGE_ORDER);
|
payResponse.setLockKey(lockKey);
|
return payResponse;
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public PayResponse continuePay(Integer orderId, Integer memberId) {
|
// 1. 查询订单
|
Orders orders = ordersMapper.selectById(orderId);
|
if (Objects.isNull(orders) || Constants.equalsInteger(orders.getDeleted(), Constants.ONE)) {
|
throw new BusinessException(ResponseStatus.DATA_EMPTY);
|
}
|
// 2. 校验订单归属
|
if (!Constants.equalsInteger(orders.getMemberId(), memberId)) {
|
throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "无权操作该订单");
|
}
|
// 3. 校验订单状态:仅待支付可继续支付
|
if (!Constants.equalsInteger(orders.getStatus(), Constants.ZERO)) {
|
throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "当前订单状态不支持继续支付");
|
}
|
// 4. 校验支付金额
|
if (orders.getTotalAmount() == null || orders.getTotalAmount() <= 0) {
|
throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "订单金额异常,无法发起支付");
|
}
|
// 5. 重新生成第三方订单编号(避免重复)
|
String orderTradeNo = generateOrderTradeNo();
|
orders.setOutTradeNo(orderTradeNo);
|
orders.setUpdateTime(new Date());
|
ordersMapper.updateById(orders);
|
// 6. 唤起微信支付
|
Member member = memberMapper.selectById(memberId);
|
if (member == null || StringUtils.isBlank(member.getOpenid())) {
|
throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "用户信息异常,无法发起支付");
|
}
|
return wxPay(orders, member.getOpenid(), Constants.OrdersAttach.STORAGE_ORDER);
|
}
|
|
/**
|
* 唤起微信小程序支付
|
*
|
* @param orders 订单实体(需要 code、totalAmount)
|
* @param openid 用户微信openid
|
* @param ordersAttach 订单支付类型
|
* @return PayResponse 包含微信调起参数和订单主键
|
*/
|
private PayResponse wxPay(Orders orders, String openid, Constants.OrdersAttach ordersAttach) {
|
try {
|
WxPayUnifiedOrderRequest request = new WxPayUnifiedOrderRequest();
|
request.setBody(ordersAttach.getName());
|
request.setAttach(ordersAttach.getKey());
|
request.setOutTradeNo(orders.getOutTradeNo());
|
// totalAmount 单位为分,WeChat Pay setTotalFee 也是分,直接转int
|
long totalFee = orders.getTotalAmount() != null ? orders.getTotalAmount() : 0L;
|
request.setTotalFee((int) totalFee);
|
request.setTimeStart(DateUtil.DateToString(new Date(), "yyyyMMddHHmmss"));
|
request.setSpbillCreateIp(Constants.getIpAddr());
|
request.setOpenid(openid);
|
|
Object response = WxMiniConfig.wxPayService.createOrder(request);
|
|
PayResponse payResponse = new PayResponse();
|
payResponse.setResponse(response);
|
payResponse.setOrderId(orders.getId());
|
return payResponse;
|
} catch (WxPayException e) {
|
throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "支付调起失败:" + e.getMessage());
|
}
|
}
|
|
|
|
|
|
@Override
|
public OrderDetailVO findDetail(Integer id) {
|
Orders order = ordersMapper.selectById(id);
|
if (Objects.isNull(order)) {
|
throw new BusinessException(ResponseStatus.DATA_EMPTY);
|
}
|
|
OrderDetailVO vo = new OrderDetailVO();
|
vo.setOrder(order);
|
|
// 图片路径前缀
|
String imgPrefix = getOrdersPrefix();
|
|
// 下单图片 (type=12)
|
vo.setOrderFiles(getFileUrls(id, Constants.FileType.ORDER_FILE.getKey(), imgPrefix));
|
|
// 会员信息
|
if (order.getMemberId() != null) {
|
Member member = memberMapper.selectById(order.getMemberId());
|
if (member != null) {
|
vo.setMemberName(member.getName());
|
vo.setMemberPhone(member.getTelephone());
|
}
|
}
|
|
// 寄存门店信息
|
if (order.getDepositShopId() != null) {
|
ShopInfo depositShop = shopInfoMapper.selectById(order.getDepositShopId());
|
if (depositShop != null) {
|
vo.setDepositShopName(depositShop.getName());
|
vo.setDepositShopPhone(depositShop.getLinkPhone());
|
}
|
}
|
|
// 取件门店信息
|
if (order.getTakeShopId() != null) {
|
ShopInfo takeShop = shopInfoMapper.selectById(order.getTakeShopId());
|
if (takeShop != null) {
|
vo.setTakeShopName(takeShop.getName());
|
vo.setTakeShopAddress(takeShop.getAddress());
|
vo.setTakeShopPhone(takeShop.getLinkPhone());
|
}
|
}
|
|
// 接单司机信息
|
if (order.getAcceptDriver() != null) {
|
DriverInfo driverInfo = driverInfoMapper.selectById(order.getAcceptDriver());
|
if (driverInfo != null) {
|
vo.setDriverName(driverInfo.getName());
|
}
|
}
|
|
// 配送附件图片
|
vo.setDepositImages(getFileUrls(id, Constants.FileType.ORDER_DEPOSIT.getKey(), imgPrefix));
|
vo.setStoreInImages(getFileUrls(id, Constants.FileType.ORDER_TAKE.getKey(), imgPrefix));
|
vo.setDriverTakeImages(getFileUrls(id, Constants.FileType.DRIVER_TAKE.getKey(), imgPrefix));
|
vo.setDriverDoneImages(getFileUrls(id, Constants.FileType.DRIVER_DONE.getKey(), imgPrefix));
|
vo.setStoreOutImages(getFileUrls(id, Constants.FileType.STORE_OUT.getKey(), imgPrefix));
|
// 物品明细
|
vo.setDetailList(buildDetailList(id));
|
Category category = categoryMapper.selectById(order.getGoodType());
|
if(CollectionUtils.isNotEmpty(vo.getDetailList())&&Objects.nonNull(category)){
|
for (OrderItemVO v:vo.getDetailList()) {
|
v.setTypeName(category.getName());
|
}
|
}
|
|
// 取消/退款状态时查询退款记录
|
Integer status = order.getStatus();
|
if (status != null && (status == Constants.OrderStatus.overdue.getStatus()
|
|| status == Constants.OrderStatus.closed.getStatus()
|
|| status == Constants.OrderStatus.cancelOverdue.getStatus()
|
|| status == Constants.OrderStatus.cancelling.getStatus()
|
|| status == Constants.OrderStatus.cancelled.getStatus())) {
|
OrdersRefund ordersRefund = ordersRefundMapper.selectOne(
|
new QueryWrapper<OrdersRefund>().lambda()
|
.eq(OrdersRefund::getOrderId, id)
|
.eq(OrdersRefund::getDeleted, Constants.ZERO)
|
.orderByDesc(OrdersRefund::getCreateTime)
|
.last("limit 1"));
|
if (ordersRefund != null) {
|
vo.setOrdersRefund(ordersRefund);
|
// 退款方式:1=平台直接取消 → 返回平台操作人名称
|
if (Constants.equalsInteger(ordersRefund.getType(), Constants.ONE) && ordersRefund.getUserId() != null) {
|
// userId 关联 system_user 表,查询操作人名称
|
vo.setPlatformUserName(getPlatformUserName(ordersRefund.getUserId()));
|
}
|
// 退款方式:2=已存件申请取消 → 返回退款取件图片 (multifile objType=14)
|
if (Constants.equalsInteger(ordersRefund.getType(), Constants.TWO)) {
|
vo.setRefundTakeImages(getFileUrls(id, Constants.FileType.REFUND_TAKE.getKey(), imgPrefix));
|
}
|
}
|
}
|
|
return vo;
|
}
|
|
@Override
|
public OrderDispatchVO findDispatchInfo(Integer id) {
|
Orders order = ordersMapper.selectById(id);
|
if (Objects.isNull(order)) {
|
throw new BusinessException(ResponseStatus.DATA_EMPTY);
|
}
|
|
OrderDispatchVO vo = new OrderDispatchVO();
|
vo.setCode(order.getCode());
|
vo.setPayAmountYuan(order.getPayAmount() != null ? Constants.getFormatMoney(order.getPayAmount()) : 0);
|
vo.setType(order.getType());
|
vo.setTypeDesc(order.getType() != null && order.getType() == Constants.ONE ? "异地存取" : "就地存取");
|
vo.setDetailList(buildDetailList(id));
|
|
return vo;
|
}
|
|
@Override
|
@Transactional(rollbackFor = {Exception.class, BusinessException.class})
|
public void dispatch(DispatchDTO dto) {
|
// 参数校验
|
if (dto == null || dto.getOrderId() == null || dto.getUrgentFee() == null) {
|
throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "订单主键和加急费用不能为空");
|
}
|
|
Orders order = ordersMapper.selectById(dto.getOrderId());
|
if (Objects.isNull(order)) {
|
throw new BusinessException(ResponseStatus.DATA_EMPTY);
|
}
|
|
// 前置条件校验:异地存取 + 已寄存
|
if (!Constants.ONE.equals(order.getType())) {
|
throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "仅支持异地存取订单派单");
|
}
|
if (!Integer.valueOf(Constants.OrderStatus.deposited.getStatus()).equals(order.getStatus())) {
|
throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "仅已寄存状态订单可派单");
|
}
|
|
String optUserName = getCurrentUserName();
|
|
// 加急费日志(每次单独记录本次加急费)
|
Constants.OrderLogType urgentLogType = Constants.OrderLogType.urgent;
|
OrderLog feeLog = new OrderLog();
|
feeLog.setOrderId(order.getId());
|
feeLog.setTitle(urgentLogType.getTitle());
|
feeLog.setLogInfo(urgentLogType.getStatusInfo().replace("{param}", dto.getUrgentFee().toPlainString()));
|
feeLog.setObjType(urgentLogType.getStatus());
|
feeLog.setOrderStatus(order.getStatus());
|
feeLog.setOptUserType(3);
|
feeLog.setOptUserName(optUserName);
|
feeLog.setCreateTime(new Date());
|
feeLog.setDeleted(Constants.ZERO);
|
orderLogService.create(feeLog);
|
|
// 加急费用 元→分
|
long urgentFeeFen = dto.getUrgentFee().multiply(new BigDecimal(100)).longValue();
|
|
// 使用 UpdateWrapper 精确更新,不影响其他字段
|
com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper<Orders> updateWrapper =
|
new UpdateWrapper<Orders>().lambda()
|
.eq(Orders::getId, order.getId())
|
.set(Orders::getIsUrgent, Constants.ONE)
|
.set(Orders::getPlatformRewardAmount, urgentFeeFen)
|
.set(Orders::getUpdateTime, new Date());
|
|
// 异地寄存且有取件门店时,生成司机核销码
|
if (order.getTakeShopId() != null) {
|
String driverVerifyCode = generateVerifyCode();
|
updateWrapper.set(Orders::getDriverVerifyCode, driverVerifyCode);
|
}
|
|
// 备注
|
if (StringUtils.isNotBlank(dto.getRemark())) {
|
updateWrapper.set(Orders::getRemark, dto.getRemark());
|
}
|
|
// 指派司机(非必填)
|
if (dto.getDriverId() != null) {
|
updateWrapper.set(Orders::getAssignDriverId, dto.getDriverId());
|
|
Member driver = memberMapper.selectById(dto.getDriverId());
|
String driverName = driver != null ? driver.getName() : String.valueOf(dto.getDriverId());
|
|
Constants.OrderLogType dispatchLogType = Constants.OrderLogType.dispatch;
|
OrderLog driverLog = new OrderLog();
|
driverLog.setOrderId(order.getId());
|
driverLog.setTitle(dispatchLogType.getTitle());
|
driverLog.setLogInfo(dispatchLogType.getStatusInfo().replace("{param}", driverName));
|
driverLog.setObjType(dispatchLogType.getStatus());
|
driverLog.setOrderStatus(order.getStatus());
|
driverLog.setOptUserType(3);
|
driverLog.setOptUserName(optUserName);
|
driverLog.setCreateTime(new Date());
|
driverLog.setDeleted(Constants.ZERO);
|
orderLogService.create(driverLog);
|
}
|
|
ordersMapper.update(updateWrapper);
|
}
|
|
private String getCurrentUserName() {
|
try {
|
com.doumee.core.model.LoginUserInfo user =
|
(com.doumee.core.model.LoginUserInfo) org.apache.shiro.SecurityUtils.getSubject().getPrincipal();
|
return user != null ? user.getUsername() : "系统";
|
} catch (Exception e) {
|
return "系统";
|
}
|
}
|
|
/**
|
* 构建订单物品明细列表
|
*/
|
private List<OrderItemVO> buildDetailList(Integer orderId) {
|
List<OrdersDetail> details = ordersDetailMapper.selectList(
|
new QueryWrapper<OrdersDetail>().lambda()
|
.eq(OrdersDetail::getOrderId, orderId)
|
.eq(OrdersDetail::getDeleted, Constants.ZERO));
|
return buildDetailList(details);
|
}
|
|
/**
|
* 根据已查询的明细构建物品列表(避免重复查询)
|
*/
|
private List<OrderItemVO> buildDetailList(List<OrdersDetail> details) {
|
List<OrderItemVO> items = new ArrayList<>();
|
if (details != null) {
|
for (OrdersDetail d : details) {
|
OrderItemVO item = new OrderItemVO();
|
item.setLuggageName(d.getLuggageName());
|
item.setLuggageDetail(d.getLuggageDetail());
|
item.setNum(d.getNum());
|
double unitPriceYuan = d.getUnitPrice() != null ? Constants.getFormatMoney(d.getUnitPrice()) : 0;
|
item.setUnitPriceYuan(unitPriceYuan);
|
item.setSubtotal(unitPriceYuan * (d.getNum() != null ? d.getNum() : 0));
|
items.add(item);
|
}
|
}
|
return items;
|
}
|
|
/**
|
* 生成32位唯一第三方订单编号(时间戳17位 + 随机数15位)
|
*/
|
private String generateOrderTradeNo() {
|
return new java.text.SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date())
|
+ String.format("%015d", Math.abs(new java.util.Random().nextLong() % 1000000000000000L));
|
}
|
|
/**
|
* 生成6位数字核销码(Redis验重)
|
* 使用 SETNX 抢占,保证唯一;使用完毕后调用 releaseVerifyCode 从 Redis 移除
|
*/
|
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(), "核销码生成失败,请重试");
|
}
|
|
/**
|
* 释放核销码占位(核销完成后调用,移除 Redis 中的 key)
|
*/
|
public void releaseVerifyCode(String code) {
|
if (StringUtils.isNotBlank(code)) {
|
redisTemplate.delete(Constants.REDIS_VERIFY_CODE_KEY + code);
|
}
|
}
|
|
private String getOrdersPrefix() {
|
try {
|
return systemDictDataBiz.queryByCode(Constants.SYSTEM, Constants.RESOURCE_PATH).getCode()
|
+ systemDictDataBiz.queryByCode(Constants.SYSTEM, Constants.ORDERS_FILES).getCode();
|
} catch (Exception e) {
|
return "";
|
}
|
}
|
|
private String getPlatformUserName(Integer userId) {
|
SystemUser user = systemUserMapper.selectById(userId);
|
return user != null ? user.getRealname() : null;
|
}
|
|
private List<String> getFileUrls(Integer orderId, int objType, String prefix) {
|
List<Multifile> files = multifileMapper.selectList(
|
new QueryWrapper<Multifile>().lambda()
|
.eq(Multifile::getObjId, orderId)
|
.eq(Multifile::getObjType, objType)
|
.eq(Multifile::getIsdeleted, Constants.ZERO)
|
.orderByAsc(Multifile::getSortnum));
|
List<String> urls = new ArrayList<>();
|
if (files != null) {
|
for (Multifile f : files) {
|
if (StringUtils.isNotBlank(f.getFileurl())) {
|
urls.add(prefix + f.getFileurl());
|
}
|
}
|
}
|
return urls;
|
}
|
|
/**
|
* 计算并设置订单薪酬分配(司机、存件门店、取件门店)
|
* 从 pricing_rule (type=4) 读取分成比例,根据门店企业/个人类型区分
|
*
|
* @param orders 订单实体(需要 totalAmount、cityId 已设置)
|
* @param depositShop 寄件门店(需要 companyType)
|
* @param takeShop 取件门店(需要 companyType,就地存取时与 depositShop 相同)
|
*/
|
private void calculateAndSetFeeAllocation(Orders orders, ShopInfo depositShop, ShopInfo takeShop) {
|
Long totalAmount = orders.getTotalAmount() != null ? orders.getTotalAmount() : 0L;
|
if (totalAmount <= 0) {
|
orders.setDriverFee(0L);
|
orders.setDepositShopFee(0L);
|
orders.setTakeShopFee(0L);
|
orders.setDriverFeeRata(BigDecimal.ZERO);
|
orders.setDepositShopFeeRata(BigDecimal.ZERO);
|
orders.setTakeShopFeeRata(BigDecimal.ZERO);
|
return;
|
}
|
Integer cityId = Integer.valueOf(orders.getCityId());
|
|
// 司机占比:fieldA=4(配送员)
|
BigDecimal driverRata = getRevenueShareRata(cityId, Constants.FOUR);
|
// 寄件门店占比:fieldA=0(企业寄)/1(个人寄)
|
int depositFieldA = Constants.equalsInteger(depositShop.getCompanyType(), Constants.ONE) ? Constants.ZERO : Constants.ONE;
|
BigDecimal depositShopRata = getRevenueShareRata(cityId, depositFieldA);
|
// 取件门店占比:fieldA=2(企业取)/3(个人取)
|
int takeFieldA = Constants.equalsInteger(takeShop.getCompanyType(), Constants.ONE) ? Constants.TWO : Constants.THREE;
|
BigDecimal takeShopRata = getRevenueShareRata(cityId, takeFieldA);
|
|
// 计算薪酬(分):totalAmount 为分,rata 为比例值(如 0.15 表示 15%)
|
long driverFee = new BigDecimal(totalAmount).multiply(driverRata).longValue();
|
long depositShopFee = new BigDecimal(totalAmount).multiply(depositShopRata).longValue();
|
long takeShopFee = totalAmount - driverFee - depositShopFee;
|
|
orders.setDriverFee(driverFee);
|
orders.setDepositShopFee(depositShopFee);
|
orders.setTakeShopFee(takeShopFee);
|
orders.setDriverFeeRata(driverRata);
|
orders.setDepositShopFeeRata(depositShopRata);
|
orders.setTakeShopFeeRata(takeShopRata);
|
}
|
|
/**
|
* 从 pricing_rule 表获取分成比例(type=4)
|
*
|
* @param cityId 城市主键
|
* @param fieldA 类型:0=企业寄, 1=个人寄, 2=企业取, 3=个人取, 4=配送员
|
* @return 分成比例(如 0.15 表示 15%)
|
*/
|
private BigDecimal getRevenueShareRata(Integer cityId, int fieldA) {
|
PricingRule rule = pricingRuleMapper.selectOne(new QueryWrapper<PricingRule>().lambda()
|
.eq(PricingRule::getDeleted, Constants.ZERO)
|
.eq(PricingRule::getType, Constants.FOUR)
|
.eq(PricingRule::getCityId, cityId)
|
.eq(PricingRule::getFieldA, String.valueOf(fieldA))
|
.last("limit 1"));
|
if (rule != null && StringUtils.isNotBlank(rule.getFieldC())) {
|
return new BigDecimal(rule.getFieldC());
|
}
|
return BigDecimal.ZERO;
|
}
|
|
@Override
|
public PageData<MyOrderVO> findMyOrderPage(PageWrap<MyOrderDTO> pageWrap, Integer memberId) {
|
MyOrderDTO model = pageWrap.getModel();
|
Integer status = model != null ? model.getStatus() : null;
|
Integer combinedStatus = model != null ? model.getCombinedStatus() : null;
|
|
// 解析合并状态为具体状态列表
|
List<Integer> statusList = null;
|
if (combinedStatus != null) {
|
Constants.OrderCombinedStatus combined = Constants.OrderCombinedStatus.getByKey(combinedStatus);
|
if (combined != null) {
|
statusList = new ArrayList<>();
|
for (int s : combined.getStatuses()) {
|
statusList.add(s);
|
}
|
}
|
}
|
|
IPage<Orders> p = new Page<>(pageWrap.getPage(), pageWrap.getCapacity());
|
MPJLambdaWrapper<Orders> wrapper = new MPJLambdaWrapper<Orders>()
|
.selectAll(Orders.class)
|
.select("s1.name", Orders::getDepositShopName)
|
.select("s1.link_name", Orders::getDepositShopLinkName)
|
.select("s1.link_phone", Orders::getDepositShopLinkPhone)
|
.select("s2.name", Orders::getTakeShopName)
|
.select("s2.address", Orders::getTakeShopAddress)
|
.leftJoin("shop_info s1 on s1.id = t.DEPOSIT_SHOP_ID")
|
.leftJoin("shop_info s2 on s2.id = t.TAKE_SHOP_ID")
|
.eq(Orders::getDeleted, Constants.ZERO)
|
.eq(Orders::getMemberId, memberId)
|
.eq(status != null, Orders::getStatus, status)
|
.in(statusList != null, Orders::getStatus, statusList)
|
.orderByDesc(Orders::getCreateTime);
|
|
IPage<Orders> orderPage = ordersMapper.selectJoinPage(p, Orders.class, wrapper);
|
List<MyOrderVO> voList = new ArrayList<>();
|
if (orderPage != null && orderPage.getRecords() != null) {
|
|
for (Orders o : orderPage.getRecords()) {
|
MyOrderVO vo = new MyOrderVO();
|
vo.setId(o.getId());
|
vo.setCode(o.getCode());
|
vo.setType(o.getType());
|
vo.setStatus(o.getStatus());
|
vo.setCreateTime(o.getCreateTime());
|
vo.setExpectedTakeTime(o.getExpectedTakeTime());
|
|
// 存件门店(关联查询直接取值)
|
vo.setDepositShopName(o.getDepositShopName());
|
vo.setDepositShopLinkName(o.getDepositShopLinkName());
|
vo.setDepositShopPhone(o.getDepositShopLinkPhone());
|
|
// 取件信息:有取件门店取门店,无则取用户自选取件点
|
if (o.getTakeShopId() != null) {
|
vo.setTakeShopName(o.getTakeShopName());
|
vo.setTakeShopAddress(o.getTakeShopAddress());
|
} else {
|
vo.setTakeLocation(o.getTakeLocation());
|
vo.setTakeLocationRemark(o.getTakeLocationRemark());
|
}
|
|
// 取件联系人
|
vo.setTakeUser(o.getTakeUser());
|
vo.setTakePhone(o.getTakePhone());
|
|
// 费用(分)
|
vo.setDeclaredFee(o.getDeclaredFee());
|
vo.setEstimatedAmount(o.getEstimatedAmount());
|
|
// 查询物品明细(一次查询,同时用于物品列表和逾期计算)
|
List<OrdersDetail> details = ordersDetailMapper.selectList(
|
new QueryWrapper<OrdersDetail>().lambda()
|
.eq(OrdersDetail::getOrderId, o.getId())
|
.eq(OrdersDetail::getDeleted, Constants.ZERO));
|
|
// 物品明细
|
vo.setDetailList(buildDetailList(details));
|
|
// 逾期信息(仅待取件状态计算)
|
if (Integer.valueOf(Constants.OrderStatus.arrived.getStatus()).equals(o.getStatus())) {
|
OverdueFeeVO overdueInfo = calculateOverdueFeeInternal(o, details);
|
vo.setOverdue(overdueInfo.getOverdue());
|
vo.setOverdueDays(overdueInfo.getOverdueDays());
|
vo.setOverdueFee(overdueInfo.getOverdueFee());
|
}
|
voList.add(vo);
|
}
|
}
|
|
IPage<MyOrderVO> vPage = new Page<>(pageWrap.getPage(), pageWrap.getCapacity());
|
PageData<MyOrderVO> pageData = PageData.from(vPage);
|
pageData.setRecords(voList);
|
pageData.setTotal(orderPage.getTotal());
|
pageData.setPage(orderPage.getCurrent());
|
pageData.setCapacity(orderPage.getSize());
|
return pageData;
|
}
|
|
@Override
|
public MyOrderDetailVO findMyOrderDetail(Integer id, Integer memberId) {
|
Orders order = ordersMapper.selectOne(new QueryWrapper<Orders>().lambda()
|
.eq(Orders::getId, id)
|
.eq(Orders::getMemberId, memberId)
|
.eq(Orders::getDeleted, Constants.ZERO));
|
if (order == null) {
|
throw new BusinessException(ResponseStatus.DATA_EMPTY);
|
}
|
return buildOrderDetailVO(order, true);
|
}
|
|
@Override
|
public MyOrderDetailVO findShopOrderDetail(Integer orderId, String verifyCode) {
|
Orders order = null;
|
if (orderId != null) {
|
order = ordersMapper.selectOne(new QueryWrapper<Orders>().lambda()
|
.eq(Orders::getId, orderId)
|
.eq(Orders::getDeleted, Constants.ZERO));
|
} else if (StringUtils.isNotBlank(verifyCode)) {
|
order = ordersMapper.selectOne(new QueryWrapper<Orders>().lambda()
|
.eq(Orders::getMemberVerifyCode, verifyCode)
|
.eq(Orders::getDeleted, Constants.ZERO)
|
.last("limit 1"));
|
if (order == null) {
|
order = ordersMapper.selectOne(new QueryWrapper<Orders>().lambda()
|
.eq(Orders::getDriverVerifyCode, verifyCode)
|
.eq(Orders::getDeleted, Constants.ZERO)
|
.last("limit 1"));
|
}
|
}
|
if (order == null) {
|
throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "订单不存在");
|
}
|
return buildOrderDetailVO(order, false);
|
}
|
|
/**
|
* 构建订单详情VO(会员端/门店端复用)
|
*
|
* @param order 订单实体
|
* @param memberViewMode true=会员端(按条件返回核销码),false=门店端(始终返回核销码)
|
*/
|
private MyOrderDetailVO buildOrderDetailVO(Orders order, boolean memberViewMode) {
|
MyOrderDetailVO vo = new MyOrderDetailVO();
|
vo.setId(order.getId());
|
vo.setStatus(order.getStatus());
|
vo.setType(order.getType());
|
vo.setCode(order.getCode());
|
vo.setOutTradeNo(order.getOutTradeNo());
|
vo.setRemark(order.getRemark());
|
vo.setCreateTime(order.getCreateTime());
|
vo.setPayTime(order.getPayTime());
|
vo.setExpectedDepositTime(order.getExpectedDepositTime());
|
vo.setExpectedTakeTime(order.getExpectedTakeTime());
|
vo.setArriveTime(order.getArriveTime());
|
|
// 费用(分)
|
vo.setBasicAmount(order.getBasicAmount());
|
vo.setDeclaredAmount(order.getDeclaredAmount());
|
vo.setDeclaredFee(order.getDeclaredFee());
|
vo.setUrgentAmount(order.getUrgentAmount());
|
vo.setActualPayAmount(order.getPayAmount());
|
|
// 标记
|
vo.setExceptionStatus(order.getExceptionStatus());
|
|
// 是否超出取件时间
|
vo.setPastTakeTime(order.getExpectedTakeTime() != null && new Date().after(order.getExpectedTakeTime()));
|
|
// 订单状态描述 + 倒计时
|
vo.setStatusDesc(buildStatusDesc(order));
|
if (Constants.equalsInteger(order.getStatus(), Constants.OrderStatus.waitPay.getStatus())) {
|
vo.setPayCountdownMs(calcPayCountdownMs(order));
|
}
|
|
// 存件门店
|
if (order.getDepositShopId() != null) {
|
ShopInfo depositShop = shopInfoMapper.selectById(order.getDepositShopId());
|
if (depositShop != null) {
|
vo.setDepositShopName(depositShop.getName());
|
vo.setDepositShopLinkName(depositShop.getLinkName());
|
vo.setDepositShopPhone(depositShop.getLinkPhone());
|
vo.setDepositShopAddress(depositShop.getAddress());
|
}
|
}
|
|
// 取件信息
|
if (order.getTakeShopId() != null) {
|
ShopInfo takeShop = shopInfoMapper.selectById(order.getTakeShopId());
|
if (takeShop != null) {
|
vo.setTakeShopName(takeShop.getName());
|
vo.setTakeShopAddress(takeShop.getAddress());
|
}
|
} else {
|
vo.setTakeLocation(order.getTakeLocation());
|
vo.setTakeLocationRemark(order.getTakeLocationRemark());
|
}
|
|
// 取件联系人
|
vo.setTakeUser(order.getTakeUser());
|
vo.setTakePhone(order.getTakePhone());
|
|
// 物品类型名称
|
if (order.getGoodType() != null) {
|
Category category = categoryMapper.selectById(order.getGoodType());
|
if (category != null) {
|
vo.setGoodTypeName(category.getName());
|
}
|
}
|
|
// 下单照片
|
String imgPrefix = getOrdersPrefix();
|
vo.setOrderImages(getFileUrls(order.getId(), Constants.FileType.ORDER_FILE.getKey(), imgPrefix));
|
|
// 物品明细
|
List<OrdersDetail> details = ordersDetailMapper.selectList(
|
new QueryWrapper<OrdersDetail>().lambda()
|
.eq(OrdersDetail::getOrderId, order.getId())
|
.eq(OrdersDetail::getDeleted, Constants.ZERO));
|
vo.setDetailList(buildDetailList(details));
|
|
// 逾期信息
|
OverdueFeeVO overdueInfo = calculateOverdueFeeInternal(order, details);
|
vo.setOverdue(overdueInfo.getOverdue());
|
vo.setOverdueDays(overdueInfo.getOverdueDays());
|
vo.setOverdueFee(overdueInfo.getOverdueFee());
|
|
// 核销码
|
Integer status = order.getStatus();
|
if (memberViewMode) {
|
// 会员端:待寄存(1)返回;待取件(5)时,就地寄存无取件门店不返回
|
boolean returnCode = false;
|
if (Constants.equalsInteger(status, Constants.OrderStatus.waitDeposit.getStatus())) {
|
returnCode = true;
|
} else if (Constants.equalsInteger(status, Constants.OrderStatus.arrived.getStatus())) {
|
if (!(Constants.equalsInteger(order.getType(), Constants.ZERO) && order.getTakeShopId() == null)) {
|
returnCode = true;
|
}
|
}
|
if (returnCode) {
|
vo.setMemberVerifyCode(order.getMemberVerifyCode());
|
}
|
} else {
|
// 门店端:始终返回会员核销码
|
vo.setMemberVerifyCode(order.getMemberVerifyCode());
|
}
|
|
return vo;
|
}
|
|
@Override
|
@Transactional(rollbackFor = {Exception.class, BusinessException.class})
|
public void cancelOrder(Integer orderId, Integer memberId, String reason) {
|
Orders order = ordersMapper.selectOne(new QueryWrapper<Orders>().lambda()
|
.eq(Orders::getId, orderId)
|
.eq(Orders::getMemberId, memberId)
|
.eq(Orders::getDeleted, Constants.ZERO));
|
if (order == null) {
|
throw new BusinessException(ResponseStatus.DATA_EMPTY);
|
}
|
|
// 仅异地寄存可取消
|
if (!Constants.equalsInteger(order.getType(), Constants.ONE)) {
|
throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "仅异地寄存订单可取消");
|
}
|
|
Integer status = order.getStatus();
|
if (status == null) {
|
throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "订单状态异常");
|
}
|
|
Date now = new Date();
|
|
// 待支付:直接取消
|
if (Constants.equalsInteger(status, Constants.OrderStatus.waitPay.getStatus())) {
|
order.setStatus(Constants.OrderStatus.cancelled.getStatus());
|
order.setCancelTime(now);
|
ordersMapper.updateById(order);
|
saveCancelLog(order, "会员取消订单(待支付)", reason, memberId);
|
return;
|
}
|
|
// 待寄存:直接取消,全额退款
|
if (Constants.equalsInteger(status, Constants.OrderStatus.waitDeposit.getStatus())) {
|
// 记录退款信息
|
OrdersRefund refund = new OrdersRefund();
|
refund.setOrderId(orderId);
|
refund.setType(0); // 未寄存直接取消
|
refund.setCancelInfo(reason);
|
refund.setCreateTime(now);
|
refund.setDeleted(Constants.ZERO);
|
|
// 调用微信退款,全额退款
|
String refundCode = wxMiniUtilService.wxRefund(order.getOutTradeNo(), order.getPayAmount(), order.getPayAmount());
|
refund.setRefundCode(refundCode);
|
refund.setRefundTime(new Date());
|
ordersRefundMapper.insert(refund);
|
|
order.setStatus(Constants.OrderStatus.cancelled.getStatus());
|
order.setCancelTime(now);
|
order.setRefundAmount(order.getPayAmount());
|
ordersMapper.updateById(order);
|
|
saveCancelLog(order, "会员取消订单(待寄存,全额退款)", reason, memberId);
|
return;
|
}
|
|
// 已寄存/已接单:进入取消中状态
|
if (Constants.equalsInteger(status, Constants.OrderStatus.deposited.getStatus())
|
|| Constants.equalsInteger(status, Constants.OrderStatus.accepted.getStatus())) {
|
order.setStatus(Constants.OrderStatus.cancelling.getStatus());
|
order.setCancelTime(now);
|
ordersMapper.updateById(order);
|
saveCancelLog(order, "会员申请取消订单(已寄存/已接单)", reason, memberId);
|
return;
|
}
|
|
throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "当前订单状态不允许取消");
|
}
|
|
/**
|
* 保存取消订单操作日志
|
*/
|
private void saveCancelLog(Orders order, String title, String reason, Integer memberId) {
|
OrderLog log = new OrderLog();
|
log.setOrderId(order.getId());
|
log.setTitle(title);
|
log.setLogInfo(reason);
|
log.setObjType(Constants.ORDER_LOG_CANCEL);
|
log.setOrderStatus(order.getStatus());
|
log.setOptUserId(memberId);
|
log.setOptUserType(0); // 0=用户
|
log.setCreateTime(new Date());
|
log.setDeleted(Constants.ZERO);
|
orderLogService.create(log);
|
}
|
|
/**
|
* 保存门店核销日志
|
*/
|
private void saveShopVerifyLog(Orders order, String title, String logInfo, String remark, Integer shopId) {
|
OrderLog log = new OrderLog();
|
log.setOrderId(order.getId());
|
log.setTitle(title);
|
log.setLogInfo(logInfo);
|
log.setRemark(remark);
|
log.setOrderStatus(order.getStatus());
|
log.setOptUserId(shopId);
|
log.setOptUserType(2); // 2=门店
|
log.setCreateTime(new Date());
|
log.setDeleted(Constants.ZERO);
|
orderLogService.create(log);
|
}
|
|
@Override
|
@Transactional(rollbackFor = {Exception.class, BusinessException.class})
|
public void handleStorageOrderPayNotify(String outTradeNo, String wxTradeNo) {
|
Orders order = ordersMapper.selectOne(new QueryWrapper<Orders>().lambda()
|
.eq(Orders::getOutTradeNo, outTradeNo)
|
.eq(Orders::getDeleted, Constants.ZERO)
|
.last("limit 1"));
|
if (order == null) {
|
throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "订单不存在: " + outTradeNo);
|
}
|
// 幂等:已支付则跳过
|
if (Constants.equalsInteger(order.getPayStatus(), Constants.ONE)) {
|
return;
|
}
|
Date now = new Date();
|
order.setStatus(Constants.OrderStatus.waitDeposit.getStatus()); // 待寄存
|
order.setPayStatus(Constants.ONE); // 已支付
|
order.setPayTime(now);
|
order.setWxExternalNo(wxTradeNo);
|
order.setUpdateTime(now);
|
// 生成会员核销码
|
order.setMemberVerifyCode(generateVerifyCode());
|
ordersMapper.updateById(order);
|
}
|
|
@Override
|
@Transactional(rollbackFor = {Exception.class, BusinessException.class})
|
public void shopVerifyOrder(String verifyCode, Integer shopId, List<String> images, String remark) {
|
if (StringUtils.isBlank(verifyCode)) {
|
throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "核销码不能为空");
|
}
|
// 根据核销码查找订单(会员核销码)
|
Orders order = ordersMapper.selectOne(new QueryWrapper<Orders>().lambda()
|
.eq(Orders::getMemberVerifyCode, verifyCode)
|
.eq(Orders::getDeleted, Constants.ZERO)
|
.last("limit 1"));
|
if (order == null) {
|
throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "核销码无效");
|
}
|
|
// 查询门店名称用于日志
|
String shopName = "";
|
ShopInfo shopInfo = shopInfoMapper.selectById(shopId);
|
if (shopInfo != null) {
|
shopName = shopInfo.getName() != null ? shopInfo.getName() : "";
|
}
|
|
Integer status = order.getStatus();
|
Date now = new Date();
|
if (Constants.equalsInteger(status, Constants.OrderStatus.waitDeposit.getStatus())) {
|
// 待寄存(1) → 已寄存(2),两种类型通用
|
// 校验当前门店是否为订单的存件门店
|
if (!shopId.equals(order.getDepositShopId())) {
|
throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "该订单不属于当前门店,无法核销");
|
}
|
order.setStatus(Constants.OrderStatus.deposited.getStatus());
|
order.setDepositTime(now);
|
// 释放当前核销码,生成新的核销码供取件时使用
|
releaseVerifyCode(verifyCode);
|
order.setMemberVerifyCode(generateVerifyCode());
|
ordersMapper.updateById(order);
|
// 保存寄存图片(obj_type=2 订单寄存图片,最多3张)
|
saveVerifyImages(order.getId(), images, Constants.FileType.ORDER_DEPOSIT.getKey(), shopId);
|
// 记录订单日志
|
saveShopVerifyLog(order, "门店确认寄存", "门店【" + shopName + "】确认寄存", remark, shopId);
|
} else if (Constants.equalsInteger(status, Constants.OrderStatus.arrived.getStatus())) {
|
// 异地寄存 + 无取件门店 → 无法核销(客户自取,无门店操作)
|
if (Constants.equalsInteger(order.getType(), Constants.ONE) && order.getTakeShopId() == null) {
|
throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "该订单无取件门店,无法核销");
|
}
|
// 校验取件门店与当前登录门店一致
|
if (!shopId.equals(order.getTakeShopId())) {
|
throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "该订单不属于当前门店,无法核销");
|
}
|
// 待取件(5) → 已完成(7)
|
order.setStatus(Constants.OrderStatus.finished.getStatus());
|
order.setConfirmArriveTime(now);
|
ordersMapper.updateById(order);
|
// 订单完成,释放核销码
|
releaseVerifyCode(verifyCode);
|
// 保存出库图片(obj_type=13 门店出库图片,最多3张)
|
saveVerifyImages(order.getId(), images, Constants.FileType.STORE_OUT.getKey(), shopId);
|
// 记录订单日志
|
saveShopVerifyLog(order, "门店确认取件", "门店【" + shopName + "】确认取件,订单完成", remark, shopId);
|
} else {
|
throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "当前订单状态不允许核销");
|
}
|
}
|
|
@Override
|
@Transactional(rollbackFor = {Exception.class, BusinessException.class})
|
public void driverVerifyOrder(String verifyCode, List<String> images, String remark, Integer driverId) {
|
if (StringUtils.isBlank(verifyCode)) {
|
throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "核销码不能为空");
|
}
|
// 根据司机核销码查找订单
|
Orders order = ordersMapper.selectOne(new QueryWrapper<Orders>().lambda()
|
.eq(Orders::getDriverVerifyCode, verifyCode)
|
.eq(Orders::getDeleted, Constants.ZERO)
|
.last("limit 1"));
|
if (order == null) {
|
throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "核销码无效");
|
}
|
|
// 仅异地寄存 + 有取件门店 + 派送中(4) 可核销
|
if (!Constants.equalsInteger(order.getType(), Constants.ONE)) {
|
throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "仅异地寄存订单支持司机核销");
|
}
|
if (order.getTakeShopId() == null) {
|
throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "该订单无取件门店,无需司机核销");
|
}
|
if (!Constants.equalsInteger(order.getStatus(), Constants.OrderStatus.delivering.getStatus())) {
|
throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "当前订单状态不允许核销");
|
}
|
|
// 派送中(4) → 已到店(5)
|
order.setStatus(Constants.OrderStatus.arrived.getStatus());
|
order.setArriveTime(new Date());
|
if (StringUtils.isNotBlank(remark)) {
|
order.setRemark(remark);
|
}
|
ordersMapper.updateById(order);
|
|
// 释放司机核销码
|
releaseVerifyCode(verifyCode);
|
|
// 保存附件(obj_type=3 门店入库图片,最多3张)
|
saveVerifyImages(order.getId(), images, Constants.FileType.ORDER_TAKE.getKey(), driverId);
|
}
|
|
/**
|
* 保存核销附件到 multifile 表
|
*
|
* @param orderId 订单主键
|
* @param images 图片地址列表(最多3张)
|
* @param objType 附件类型
|
* @param creator 创建人编码
|
*/
|
private void saveVerifyImages(Integer orderId, List<String> images, int objType, Integer creator) {
|
if (images == null || images.isEmpty()) return;
|
List<String> saveImages = images.size() > 3 ? images.subList(0, 3) : images;
|
Date now = new Date();
|
int sortNum = 1;
|
for (String imgUrl : saveImages) {
|
if (StringUtils.isBlank(imgUrl)) continue;
|
Multifile multifile = new Multifile();
|
multifile.setObjId(orderId);
|
multifile.setObjType(objType);
|
multifile.setFileurl(imgUrl);
|
multifile.setType(Constants.ZERO);
|
multifile.setCreator(creator);
|
multifile.setCreateDate(now);
|
multifile.setIsdeleted(Constants.ZERO);
|
multifile.setSortnum(sortNum++);
|
multifileMapper.insert(multifile);
|
}
|
}
|
|
@Override
|
@Transactional(rollbackFor = {Exception.class, BusinessException.class})
|
public void confirmCustomerArrived(Integer orderId, Integer shopId) {
|
// 1. 查询订单
|
Orders order = ordersMapper.selectById(orderId);
|
if (order == null || Constants.equalsInteger(order.getDeleted(), Constants.ONE)) {
|
throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "订单不存在");
|
}
|
|
// 2. 校验订单状态:待取件(5)
|
if (!Constants.equalsInteger(order.getStatus(), Constants.OrderStatus.arrived.getStatus())) {
|
throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "当前订单状态不允许该操作");
|
}
|
|
// 3. 校验门店与订单关系
|
if (Constants.equalsInteger(order.getType(), Constants.ONE) && order.getTakeShopId() != null) {
|
// 异地寄存有取件门店:校验取件门店
|
if (!shopId.equals(order.getTakeShopId())) {
|
throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "该订单不属于当前门店");
|
}
|
} else if (Constants.equalsInteger(order.getType(), Constants.ZERO)) {
|
// 就地寄存:校验存件门店(取件门店同存件门店)
|
if (!shopId.equals(order.getDepositShopId())) {
|
throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "该订单不属于当前门店");
|
}
|
} else {
|
throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "该订单无取件门店,无法确认到店");
|
}
|
|
// 4. 查询门店名称(用于日志)
|
String shopName = "";
|
ShopInfo shopInfo = shopInfoMapper.selectById(shopId);
|
if (shopInfo != null) {
|
shopName = shopInfo.getName() != null ? shopInfo.getName() : "";
|
}
|
|
// 5. 计算逾期费用
|
List<OrdersDetail> details = ordersDetailMapper.selectList(
|
new QueryWrapper<OrdersDetail>().lambda()
|
.eq(OrdersDetail::getOrderId, orderId)
|
.eq(OrdersDetail::getDeleted, Constants.ZERO));
|
OverdueFeeVO overdueInfo = calculateOverdueFeeInternal(order, details);
|
|
Date now = new Date();
|
|
if (overdueInfo.getOverdue() && overdueInfo.getOverdueDays() > 0) {
|
// 存在逾期:标记逾期状态,订单保持当前状态
|
order.setConfirmArriveTime(now);
|
order.setOverdueStatus(Constants.ONE);
|
order.setOverdueDays(overdueInfo.getOverdueDays());
|
order.setOverdueAmount(overdueInfo.getOverdueFee());
|
order.setUpdateTime(now);
|
ordersMapper.updateById(order);
|
|
// 记录订单日志
|
saveShopVerifyLog(order, "确认顾客到店(逾期)",
|
"门店【" + shopName + "】确认顾客到店,逾期" + overdueInfo.getOverdueDays()
|
+ "天,逾期费用" + Constants.getFormatMoney(overdueInfo.getOverdueFee()) + "元",
|
null, shopId);
|
} else {
|
// 未逾期:完成订单
|
order.setStatus(Constants.OrderStatus.finished.getStatus());
|
order.setConfirmArriveTime(now);
|
order.setFinishTime(now);
|
order.setOverdueStatus(Constants.ZERO);
|
order.setUpdateTime(now);
|
ordersMapper.updateById(order);
|
|
// 释放核销码
|
if (StringUtils.isNotBlank(order.getMemberVerifyCode())) {
|
releaseVerifyCode(order.getMemberVerifyCode());
|
}
|
|
// 记录订单日志
|
saveShopVerifyLog(order, "确认顾客到店",
|
"门店【" + shopName + "】确认顾客到店,订单完成",
|
null, shopId);
|
}
|
}
|
|
/**
|
* 构建订单状态描述
|
*/
|
private String buildStatusDesc(Orders order) {
|
boolean isLocal = Constants.equalsInteger(order.getType(), Constants.ZERO);
|
Integer status = order.getStatus();
|
if (status == null) return "";
|
|
if (Constants.equalsInteger(status, Constants.OrderStatus.waitPay.getStatus())) {
|
String minutes = "";
|
try {
|
minutes = operationConfigBiz.getConfig().getAutoCancelTime();
|
} catch (Exception ignored) {}
|
return "请在" + (StringUtils.isNotBlank(minutes) ? minutes : "") + "分钟内完成支付,超时订单将自动取消";
|
}
|
if (Constants.equalsInteger(status, Constants.OrderStatus.waitDeposit.getStatus())) {
|
return isLocal ? "订单已支付,请等待门店确认接单" : "订单已支付,请等待门店确认接单";
|
}
|
if (Constants.equalsInteger(status, Constants.OrderStatus.deposited.getStatus())) {
|
return isLocal ? "行李已寄存,请凭取件码前往指定门店取件" : "门店已接单,正在为您安排取件司机";
|
}
|
if (Constants.equalsInteger(status, Constants.OrderStatus.accepted.getStatus())) {
|
return isLocal ? "行李已寄存,请凭取件码前往指定门店取件" : "已有司机抢单,正前往取件地点";
|
}
|
if (Constants.equalsInteger(status, Constants.OrderStatus.delivering.getStatus())) {
|
return "司机已取件,正运往目的地";
|
}
|
if (Constants.equalsInteger(status, Constants.OrderStatus.arrived.getStatus())) {
|
return "行李已送达服务点,请及时前往取件";
|
}
|
if (Constants.equalsInteger(status, Constants.OrderStatus.finished.getStatus())) {
|
if (Constants.equalsInteger(order.getCommentStatus(), Constants.ONE)) {
|
return "感谢您的用心评价,祝您出行顺利,旅途愉快!";
|
}
|
return "订单已完成,感谢您的支持,请对本次服务做出评价";
|
}
|
if (Constants.equalsInteger(status, Constants.OrderStatus.cancelled.getStatus())) {
|
return "订单已取消,感谢您的支持,欢迎下次再会!";
|
}
|
if (Constants.equalsInteger(status, Constants.OrderStatus.cancelling.getStatus())) {
|
return "退款申请已提交,平台会尽快为您处理退款";
|
}
|
if (Constants.equalsInteger(status, Constants.OrderStatus.closed.getStatus())) {
|
return "退款已成功原路返回,请注意查收";
|
}
|
return "";
|
}
|
|
/**
|
* 计算支付倒计时毫秒
|
*/
|
private Long calcPayCountdownMs(Orders order) {
|
try {
|
String minutesStr = operationConfigBiz.getConfig().getAutoCancelTime();
|
if (StringUtils.isBlank(minutesStr)) return -1L;
|
int minutes = Integer.parseInt(minutesStr);
|
long deadline = order.getCreateTime().getTime() + minutes * 60 * 1000L;
|
long remaining = deadline - System.currentTimeMillis();
|
return remaining > 0 ? remaining : -1L;
|
} catch (Exception e) {
|
return -1L;
|
}
|
}
|
public OverdueFeeVO calculateOverdueFee(Integer orderId) {
|
Orders order = ordersMapper.selectById(orderId);
|
if (order == null || Constants.equalsInteger(order.getDeleted(), Constants.ONE)) {
|
throw new BusinessException(ResponseStatus.DATA_EMPTY);
|
}
|
|
// 查询订单明细
|
List<OrdersDetail> details = ordersDetailMapper.selectList(
|
new QueryWrapper<OrdersDetail>().lambda()
|
.eq(OrdersDetail::getOrderId, orderId)
|
.eq(OrdersDetail::getDeleted, Constants.ZERO));
|
|
return calculateOverdueFeeInternal(order, details);
|
}
|
|
/**
|
* 逾期费用内部计算(不查库,接受预查询的数据)
|
* 供分页等已查询明细的业务场景复用,避免重复查询
|
*/
|
private OverdueFeeVO calculateOverdueFeeInternal(Orders order, List<OrdersDetail> details) {
|
if (CollectionUtils.isEmpty(details)) {
|
OverdueFeeVO vo = new OverdueFeeVO();
|
vo.setOverdue(false);
|
vo.setOverdueDays(0);
|
vo.setOverdueFee(0L);
|
vo.setDailyBaseFee(0L);
|
return vo;
|
}
|
|
// 物品基础日费用 = Σ(单价 × 数量)
|
long dailyBaseFee = 0L;
|
for (OrdersDetail d : details) {
|
dailyBaseFee += (d.getUnitPrice() != null ? d.getUnitPrice() : 0L)
|
* (d.getNum() != null ? d.getNum() : 0);
|
}
|
|
Date now = new Date();
|
int overdueDays;
|
long overdueFee;
|
|
if (Constants.equalsInteger(order.getType(), Constants.ZERO)) {
|
// ========== 就地寄存 ==========
|
overdueDays = calcLocalOverdueDays(now, order.getExpectedTakeTime());
|
overdueFee = (long) overdueDays * dailyBaseFee;
|
|
OverdueFeeVO vo = new OverdueFeeVO();
|
vo.setOverdue(overdueDays > 0);
|
vo.setOverdueDays(overdueDays);
|
vo.setOverdueFee(overdueFee);
|
vo.setDailyBaseFee(dailyBaseFee);
|
return vo;
|
|
} else {
|
// ========== 异地寄存 ==========
|
// 条件:存在取件门店 且 订单处于已到店状态(5)
|
if (order.getTakeShopId() == null
|
|| !Constants.equalsInteger(order.getStatus(), Constants.FIVE)) {
|
OverdueFeeVO vo = new OverdueFeeVO();
|
vo.setOverdue(false);
|
vo.setOverdueDays(0);
|
vo.setOverdueFee(0L);
|
vo.setDailyBaseFee(dailyBaseFee);
|
vo.setDiscountRate(null);
|
return vo;
|
}
|
|
overdueDays = calcRemoteOverdueDays(now, order.getArriveTime());
|
|
// 折扣比率
|
String discountStr = operationConfigBiz.getConfig().getUnpickedDiscount();
|
BigDecimal discountRate = StringUtils.isNotBlank(discountStr)
|
? new BigDecimal(discountStr) : BigDecimal.ONE;
|
|
overdueFee = new BigDecimal(overdueDays)
|
.multiply(new BigDecimal(dailyBaseFee))
|
.multiply(discountRate)
|
.setScale(0, RoundingMode.HALF_UP)
|
.longValue();
|
|
OverdueFeeVO vo = new OverdueFeeVO();
|
vo.setOverdue(overdueDays > 0);
|
vo.setOverdueDays(overdueDays);
|
vo.setOverdueFee(overdueFee);
|
vo.setDailyBaseFee(dailyBaseFee);
|
vo.setDiscountRate(discountStr);
|
return vo;
|
}
|
}
|
|
/**
|
* 就地寄存逾期天数计算
|
* 过了预计取件时间当天的12点后才算一天
|
*/
|
private int calcLocalOverdueDays(Date now, Date expectedTakeTime) {
|
if (expectedTakeTime == null || !now.after(expectedTakeTime)) {
|
return 0;
|
}
|
// 基准时间 = 预计取件日期的12:00
|
Calendar baseCal = Calendar.getInstance();
|
baseCal.setTime(expectedTakeTime);
|
baseCal.set(Calendar.HOUR_OF_DAY, 12);
|
baseCal.set(Calendar.MINUTE, 0);
|
baseCal.set(Calendar.SECOND, 0);
|
baseCal.set(Calendar.MILLISECOND, 0);
|
Date baseTime = baseCal.getTime();
|
|
if (!now.after(baseTime)) {
|
return 0;
|
}
|
// 逾期天数 = 当前日期 - 基准日期(按天取差)
|
Calendar nowCal = Calendar.getInstance();
|
nowCal.setTime(now);
|
nowCal.set(Calendar.HOUR_OF_DAY, 0);
|
nowCal.set(Calendar.MINUTE, 0);
|
nowCal.set(Calendar.SECOND, 0);
|
nowCal.set(Calendar.MILLISECOND, 0);
|
|
Calendar baseDateCal = Calendar.getInstance();
|
baseDateCal.setTime(baseTime);
|
baseDateCal.set(Calendar.HOUR_OF_DAY, 0);
|
baseDateCal.set(Calendar.MINUTE, 0);
|
baseDateCal.set(Calendar.SECOND, 0);
|
baseDateCal.set(Calendar.MILLISECOND, 0);
|
|
long diffMs = nowCal.getTimeInMillis() - baseDateCal.getTimeInMillis();
|
int days = (int) (diffMs / (1000 * 60 * 60 * 24));
|
return Math.max(days, 0);
|
}
|
|
/**
|
* 异地寄存逾期天数计算
|
* 过了转移到店时间当天的晚上12点(24:00)后才算第一天
|
*/
|
private int calcRemoteOverdueDays(Date now, Date arriveTime) {
|
if (arriveTime == null || !now.after(arriveTime)) {
|
return 0;
|
}
|
// 基准时间 = 转移到店日期的次日 00:00(即当天24:00)
|
Calendar baseCal = Calendar.getInstance();
|
baseCal.setTime(arriveTime);
|
baseCal.set(Calendar.HOUR_OF_DAY, 0);
|
baseCal.set(Calendar.MINUTE, 0);
|
baseCal.set(Calendar.SECOND, 0);
|
baseCal.set(Calendar.MILLISECOND, 0);
|
baseCal.add(Calendar.DAY_OF_MONTH, 1); // 次日00:00 = 当天24:00
|
Date baseTime = baseCal.getTime();
|
|
if (!now.after(baseTime)) {
|
return 0;
|
}
|
// 逾期天数 = 当前日期 - 基准日期
|
Calendar nowCal = Calendar.getInstance();
|
nowCal.setTime(now);
|
nowCal.set(Calendar.HOUR_OF_DAY, 0);
|
nowCal.set(Calendar.MINUTE, 0);
|
nowCal.set(Calendar.SECOND, 0);
|
nowCal.set(Calendar.MILLISECOND, 0);
|
|
Calendar baseDateCal = Calendar.getInstance();
|
baseDateCal.setTime(baseTime);
|
baseDateCal.set(Calendar.HOUR_OF_DAY, 0);
|
baseDateCal.set(Calendar.MINUTE, 0);
|
baseDateCal.set(Calendar.SECOND, 0);
|
baseDateCal.set(Calendar.MILLISECOND, 0);
|
|
long diffMs = nowCal.getTimeInMillis() - baseDateCal.getTimeInMillis();
|
int days = (int) (diffMs / (1000 * 60 * 60 * 24));
|
return Math.max(days, 0);
|
}
|
|
}
|