✨ 司机端小程序接口按运次下发列表与详情,并支持整单接单与拒单
新增 /waybill/task/{current,pending-preview,counts,page,detail,accept,reject}:
列表一页若干个运次,详情按是否配载单下发整趟车站点(route_json 顺序,
装N/卸N 指向配载单运单集合里的第 N 张子运单,每站货物取子运单);
一次接单把该运次未走完的子运单一起置为已接单,一次拒单整单拒绝。
旧的运单级端点与写入口语义不变。
This commit is contained in:
+59
@@ -105,6 +105,65 @@ public class DriverWaybillController extends BladeController {
|
||||
return R.data(driverWaybillService.detail(id));
|
||||
}
|
||||
|
||||
@GetMapping("/task/current")
|
||||
@ApiOperationSupport(order = 21)
|
||||
@Operation(summary = "首页:当前运输中运次", description = "当前用户手机号可见的运输中运次(有配载单的一趟车算一个)最新一条")
|
||||
public R<DriverWaybillCardVO> tripCurrentTask() {
|
||||
return R.data(driverWaybillService.tripCurrentTask());
|
||||
}
|
||||
|
||||
@GetMapping("/task/pending-preview")
|
||||
@ApiOperationSupport(order = 22)
|
||||
@Operation(summary = "首页:待接运次预览", description = "当前用户手机号可见的待接运次预览与总数(按运次计)")
|
||||
public R<DriverWaybillPreviewVO> tripPendingPreview(
|
||||
@Parameter(description = "预览条数,默认 2") @RequestParam(required = false) Integer size) {
|
||||
return R.data(driverWaybillService.tripPendingPreview(size));
|
||||
}
|
||||
|
||||
@GetMapping("/task/counts")
|
||||
@ApiOperationSupport(order = 23)
|
||||
@Operation(summary = "运次 Tab 统计", description = "按运次统计:全部 = 待接单 + 进行中 + 已完成(不含已取消)")
|
||||
public R<DriverWaybillTabCountsVO> tripCounts() {
|
||||
return R.data(driverWaybillService.tripTabCounts());
|
||||
}
|
||||
|
||||
@GetMapping("/task/page")
|
||||
@ApiOperationSupport(order = 24)
|
||||
@Operation(summary = "运次分页列表", description = "一页即若干个运次;status:空=全部,0待接单,1运输中,2已完成;keyword 支持运单号/配载单号/起终点/车牌")
|
||||
public R<IPage<DriverWaybillCardVO>> tripPage(
|
||||
@Parameter(description = "当前页") @RequestParam(required = false) Integer current,
|
||||
@Parameter(description = "每页运次数") @RequestParam(required = false) Integer size,
|
||||
@Parameter(description = "状态:0待接单/1运输中/2已完成,不传为全部") @RequestParam(required = false) String status,
|
||||
@Parameter(description = "关键字:运单号/配载单号/起终点/车牌") @RequestParam(required = false) String keyword) {
|
||||
Integer statusCode = parseStatus(status);
|
||||
return R.data(driverWaybillService.tripPage(current, size, statusCode, keyword));
|
||||
}
|
||||
|
||||
@GetMapping("/task/detail")
|
||||
@ApiOperationSupport(order = 25)
|
||||
@Operation(summary = "司机运次详情", description = "按运次主键(字符串)取整趟车;有配载单时下发站点 ①②③(stations)与每站货物条目")
|
||||
public R<DriverWaybillCardVO> tripDetail(
|
||||
@Parameter(description = "运次主键(配载单主键或运单主键)", required = true) @RequestParam String id) {
|
||||
return R.data(driverWaybillService.tripDetail(id));
|
||||
}
|
||||
|
||||
@PostMapping("/task/accept")
|
||||
@ApiOperationSupport(order = 26)
|
||||
@Operation(summary = "运次确认接单", description = "一次接单把该运次全部子运单置为已接单;任一子单已接单即视为整单已接单")
|
||||
public R tripAccept(
|
||||
@Parameter(description = "运次主键(配载单主键或运单主键)", required = true) @RequestParam String id) {
|
||||
return R.status(driverWaybillService.tripAccept(id));
|
||||
}
|
||||
|
||||
@PostMapping("/task/reject")
|
||||
@ApiOperationSupport(order = 27)
|
||||
@Operation(summary = "运次拒绝接单", description = "整单拒绝:该运次全部未终态子运单一起置为已拒绝,此后该运次对本人不可见")
|
||||
public R tripReject(
|
||||
@Parameter(description = "运次主键(配载单主键或运单主键)", required = true) @RequestParam String id,
|
||||
@Parameter(description = "拒绝原因") @RequestParam(required = false) String reason) {
|
||||
return R.status(driverWaybillService.tripReject(id, reason));
|
||||
}
|
||||
|
||||
@PostMapping("/track")
|
||||
@ApiOperationSupport(order = 11)
|
||||
@Operation(summary = "司机端行驶轨迹", description = "按当前用户手机号校验归属后,根据打卡记录/计划时间自动推导起止日期并查询 LBS 轨迹(WGS-84)")
|
||||
|
||||
+47
@@ -71,6 +71,53 @@ public interface IDriverWaybillService {
|
||||
*/
|
||||
DriverWaybillCardVO detail(Long id);
|
||||
|
||||
/**
|
||||
* 当前登录司机的运输中运次(一趟车,最多一条)
|
||||
*/
|
||||
DriverWaybillCardVO tripCurrentTask();
|
||||
|
||||
/**
|
||||
* 当前登录司机的待接运次预览
|
||||
*
|
||||
* @param size 预览条数,默认 2
|
||||
*/
|
||||
DriverWaybillPreviewVO tripPendingPreview(Integer size);
|
||||
|
||||
/**
|
||||
* 运次 Tab 统计:全部 / 待接单 / 进行中 / 已完成(全部 = 前三者之和,不含已取消)
|
||||
*/
|
||||
DriverWaybillTabCountsVO tripTabCounts();
|
||||
|
||||
/**
|
||||
* 司机运次分页:一页即若干个运次(有配载单的一趟车算一个)
|
||||
*
|
||||
* @param current 当前页,从 1 开始
|
||||
* @param size 每页条数(按运次,默认 10)
|
||||
* @param status 小程序状态:空=全部,0待接单,1运输中,2已完成
|
||||
* @param keyword 关键字(运单号 / 配载单号 / 起终点 / 车牌,可选)
|
||||
*/
|
||||
IPage<DriverWaybillCardVO> tripPage(Integer current, Integer size, Integer status, String keyword);
|
||||
|
||||
/**
|
||||
* 司机运次详情:按运次主键(字符串)取整趟车,有配载单时下发站点 ①②③
|
||||
*/
|
||||
DriverWaybillCardVO tripDetail(String tripId);
|
||||
|
||||
/**
|
||||
* 运次确认接单:一次把该运次全部子运单置为已接单,司机字段不动
|
||||
*
|
||||
* @param tripId 运次主键(配载单主键或运单主键)
|
||||
*/
|
||||
boolean tripAccept(String tripId);
|
||||
|
||||
/**
|
||||
* 运次拒绝接单:整单拒绝,该运次此后对本人不可见,等调度重新派单
|
||||
*
|
||||
* @param tripId 运次主键(配载单主键或运单主键)
|
||||
* @param reason 拒绝原因,可空,最长 200 字
|
||||
*/
|
||||
boolean tripReject(String tripId, String reason);
|
||||
|
||||
/**
|
||||
* 司机端运单行驶轨迹:按打卡记录 / 计划时间自动推导起止日期后查询 LBS 轨迹。
|
||||
* <p>
|
||||
|
||||
+317
@@ -59,10 +59,15 @@ import org.springblade.transport.pojo.vo.DriverPunchPhotoVO;
|
||||
import org.springblade.transport.pojo.vo.DriverWaybillCardVO;
|
||||
import org.springblade.transport.pojo.vo.DriverWaybillPreviewVO;
|
||||
import org.springblade.transport.pojo.vo.DriverWaybillTabCountsVO;
|
||||
import org.springblade.transport.pojo.vo.TripQueryVO;
|
||||
import org.springblade.transport.pojo.vo.TripStationVO;
|
||||
import org.springblade.transport.pojo.vo.TripTabCountsVO;
|
||||
import org.springblade.transport.pojo.vo.TripVO;
|
||||
import org.springblade.transport.pojo.vo.WaybillTrackVO;
|
||||
import org.springblade.transport.service.IDriverService;
|
||||
import org.springblade.transport.service.IDriverWaybillService;
|
||||
import org.springblade.transport.service.IProcessConfigService;
|
||||
import org.springblade.transport.service.ITripQueryService;
|
||||
import org.springblade.transport.service.IWaybillService;
|
||||
import org.springblade.transport.support.WaybillProcessSupport;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -130,6 +135,10 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService {
|
||||
/** 被异常阻断时的不可打卡原因(司机端展示) */
|
||||
private static final String LOCK_REASON_EXCEPTION = "该运单存在未处理异常,处理完成后才能打卡";
|
||||
|
||||
/** 运次状态码:0 待接单 / 1 运输中 / 2 已完成 / 3 已取消,与运单侧同一套 */
|
||||
private static final int TRIP_STATUS_PENDING = 0;
|
||||
private static final int TRIP_STATUS_RUNNING = 1;
|
||||
|
||||
private final IWaybillService waybillService;
|
||||
private final IDriverService driverService;
|
||||
private final IMpUserClient mpUserClient;
|
||||
@@ -139,6 +148,7 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService {
|
||||
private final WaybillEnroutePunchMapper enroutePunchMapper;
|
||||
private final WaybillNodePunchMapper nodePunchMapper;
|
||||
private final ExceptionDisposalMapper exceptionDisposalMapper;
|
||||
private final ITripQueryService tripQueryService;
|
||||
|
||||
@Override
|
||||
public DriverWaybillCardVO currentTask() {
|
||||
@@ -252,6 +262,81 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService {
|
||||
return toCard(normalizeAcceptStatus(waybill), true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DriverWaybillCardVO tripCurrentTask() {
|
||||
String phone = currentUserPhone();
|
||||
if (Func.isEmpty(phone)) {
|
||||
return null;
|
||||
}
|
||||
TripQueryVO query = TripQueryVO.forDriver(phone);
|
||||
query.setStatus(TRIP_STATUS_RUNNING);
|
||||
query.setCurrent(1);
|
||||
query.setSize(1);
|
||||
IPage<TripVO> page = tripQueryService.page(query);
|
||||
return page.getRecords().isEmpty() ? null : toTripCard(page.getRecords().get(0), false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DriverWaybillPreviewVO tripPendingPreview(Integer size) {
|
||||
DriverWaybillPreviewVO preview = new DriverWaybillPreviewVO();
|
||||
String phone = currentUserPhone();
|
||||
if (Func.isEmpty(phone)) {
|
||||
return preview;
|
||||
}
|
||||
TripQueryVO query = TripQueryVO.forDriver(phone);
|
||||
query.setStatus(TRIP_STATUS_PENDING);
|
||||
query.setCurrent(1);
|
||||
query.setSize(normalizePreviewSize(size));
|
||||
IPage<TripVO> page = tripQueryService.page(query);
|
||||
preview.setTotal(page.getTotal());
|
||||
preview.setRecords(page.getRecords().stream()
|
||||
.map(trip -> toTripCard(trip, false))
|
||||
.collect(Collectors.toList()));
|
||||
return preview;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DriverWaybillTabCountsVO tripTabCounts() {
|
||||
DriverWaybillTabCountsVO counts = new DriverWaybillTabCountsVO();
|
||||
String phone = currentUserPhone();
|
||||
if (Func.isEmpty(phone)) {
|
||||
return counts;
|
||||
}
|
||||
TripTabCountsVO tripCounts = tripQueryService.tabCounts(TripQueryVO.forDriver(phone));
|
||||
counts.setPending(tripCounts.getPending());
|
||||
counts.setDoing(tripCounts.getDoing());
|
||||
counts.setDone(tripCounts.getDone());
|
||||
counts.setAll(tripCounts.getAll());
|
||||
return counts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IPage<DriverWaybillCardVO> tripPage(Integer current, Integer size, Integer status, String keyword) {
|
||||
String phone = currentUserPhone();
|
||||
if (Func.isEmpty(phone)) {
|
||||
return new Page<>(normalizePageNo(current), normalizePageSize(size));
|
||||
}
|
||||
TripQueryVO query = TripQueryVO.forDriver(phone);
|
||||
query.setStatus(status);
|
||||
query.setKeyword(keyword);
|
||||
query.setCurrent(current);
|
||||
query.setSize(size);
|
||||
IPage<TripVO> trips = tripQueryService.page(query);
|
||||
Page<DriverWaybillCardVO> page = new Page<>(trips.getCurrent(), trips.getSize(), trips.getTotal());
|
||||
page.setRecords(trips.getRecords().stream()
|
||||
.map(trip -> toTripCard(trip, false))
|
||||
.collect(Collectors.toList()));
|
||||
return page;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DriverWaybillCardVO tripDetail(String tripId) {
|
||||
requireCurrentDriver();
|
||||
TripQueryVO query = TripQueryVO.forDriver(currentUserPhone());
|
||||
query.setTripId(tripId);
|
||||
return toTripCard(tripQueryService.detail(query), true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WaybillTrackVO track(Long id) {
|
||||
requireCurrentDriver();
|
||||
@@ -557,6 +642,118 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean tripAccept(String tripId) {
|
||||
Driver driver = requireCurrentDriver();
|
||||
// 认证门槛按整车一次过:一次接单只校验一次司机与车辆
|
||||
assertCertified(driver);
|
||||
List<TripVO.TripWaybillVO> children = tripChildren(tripId);
|
||||
assertTripAcceptable(children);
|
||||
List<Long> pendingIds = writeCandidateIds(children, "当前运单状态不允许接单");
|
||||
Date now = new Date();
|
||||
return waybillService.update(Wrappers.<Waybill>lambdaUpdate()
|
||||
.set(Waybill::getDriverAcceptStatus, WaybillProcessSupport.ACCEPT_ACCEPTED)
|
||||
.set(Waybill::getDriverAcceptTime, now)
|
||||
.set(Waybill::getDriverAcceptDriverId, driver.getId())
|
||||
.set(Waybill::getDriverRejectTime, null)
|
||||
.set(Waybill::getDriverRejectReason, null)
|
||||
.set(Waybill::getBusinessStatus, STATUS_RUNNING)
|
||||
.in(Waybill::getId, pendingIds));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean tripReject(String tripId, String reason) {
|
||||
requireCurrentDriver();
|
||||
List<TripVO.TripWaybillVO> children = tripChildren(tripId);
|
||||
assertTripRejectable(children);
|
||||
String rejectReason = Func.isEmpty(reason) ? null : reason.trim();
|
||||
if (Func.isNotEmpty(rejectReason) && rejectReason.length() > 200) {
|
||||
throw new ServiceException("拒绝原因不能超过200字");
|
||||
}
|
||||
List<Long> pendingIds = writeCandidateIds(children, "当前运单状态不允许拒绝接单");
|
||||
Date now = new Date();
|
||||
// 整单拒绝落在每张未终态子运单上,一次一条语句写完,接单时刻与拒单时刻全趟车一致
|
||||
return waybillService.update(Wrappers.<Waybill>lambdaUpdate()
|
||||
.set(Waybill::getDriverAcceptStatus, WaybillProcessSupport.ACCEPT_REJECTED)
|
||||
.set(Waybill::getDriverAcceptTime, null)
|
||||
.set(Waybill::getDriverAcceptDriverId, null)
|
||||
.set(Waybill::getDriverRejectTime, now)
|
||||
.set(Waybill::getDriverRejectReason, rejectReason)
|
||||
.set(Waybill::getBusinessStatus, STATUS_PENDING)
|
||||
.in(Waybill::getId, pendingIds));
|
||||
}
|
||||
|
||||
/**
|
||||
* 运次 → 子运单集合:配载的运次走运次读服务解析(配载单主键命中后按配载单号取子运单),
|
||||
* 未配载的就是那张运单本身。收口与列表一致——别人的运次在这里已经被挡住。
|
||||
* <p>
|
||||
* 子单集合是司机可见的那一批:已拒单的子单不在其中,否则接单会把本人拒掉的单重新拉回来。
|
||||
*/
|
||||
private List<TripVO.TripWaybillVO> tripChildren(String tripId) {
|
||||
TripQueryVO query = TripQueryVO.forDriver(currentUserPhone());
|
||||
query.setTripId(tripId);
|
||||
TripVO trip = tripQueryService.detail(query);
|
||||
List<TripVO.TripWaybillVO> children = trip.getWaybills() == null ? List.of() : trip.getWaybills();
|
||||
if (children.isEmpty()) {
|
||||
throw new ServiceException("运次不存在或无权操作");
|
||||
}
|
||||
return children;
|
||||
}
|
||||
|
||||
/**
|
||||
* 接单校验按整车:有子单没走完才可接单,任一子单的项目要接单整趟车就要接单,任一子单已接单即整单已接单
|
||||
*/
|
||||
private void assertTripAcceptable(List<TripVO.TripWaybillVO> children) {
|
||||
if (children.stream().allMatch(this::isTerminalChild)) {
|
||||
throw new ServiceException("当前运单状态不允许接单");
|
||||
}
|
||||
if (children.stream().noneMatch(child -> Boolean.TRUE.equals(child.getRequireAccept()))) {
|
||||
throw new ServiceException("该运单无需确认接单");
|
||||
}
|
||||
if (children.stream().anyMatch(child -> WaybillProcessSupport.isAccepted(child.getAcceptStatus()))) {
|
||||
throw new ServiceException("该运单已接单");
|
||||
}
|
||||
if (children.stream().anyMatch(child -> WaybillProcessSupport.isRejected(child.getAcceptStatus()))) {
|
||||
throw new ServiceException("该运单已拒单,请联系调度重新派单");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 拒单校验按整车:有子单没走完才可拒绝,任一子单已接单则整单不能拒绝
|
||||
*/
|
||||
private void assertTripRejectable(List<TripVO.TripWaybillVO> children) {
|
||||
if (children.stream().allMatch(this::isTerminalChild)) {
|
||||
throw new ServiceException("当前运单状态不允许拒绝接单");
|
||||
}
|
||||
if (children.stream().noneMatch(child -> Boolean.TRUE.equals(child.getRequireAccept()))) {
|
||||
throw new ServiceException("该运单无需确认接单");
|
||||
}
|
||||
if (children.stream().anyMatch(child -> WaybillProcessSupport.isAccepted(child.getAcceptStatus()))) {
|
||||
throw new ServiceException("该运单已接单,无法拒绝");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 可写入的子运单:已走完的子单不动——把已完成/已取消的子单改回运输中或待执行等于回退历史状态
|
||||
*/
|
||||
private List<Long> writeCandidateIds(List<TripVO.TripWaybillVO> children, String emptyMessage) {
|
||||
List<Long> ids = children.stream()
|
||||
.filter(child -> !isTerminalChild(child))
|
||||
.map(child -> parseId(child.getId()))
|
||||
.filter(Objects::nonNull)
|
||||
.toList();
|
||||
if (ids.isEmpty()) {
|
||||
throw new ServiceException(emptyMessage);
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
private boolean isTerminalChild(TripVO.TripWaybillVO child) {
|
||||
return WaybillProcessSupport.isTerminalBusinessStatus(child.getBusinessStatus());
|
||||
}
|
||||
|
||||
private Waybill loadDriverWaybill(Long id) {
|
||||
return loadDriverWaybill(id, false);
|
||||
}
|
||||
@@ -828,6 +1025,126 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService {
|
||||
return List.of(load, unload);
|
||||
}
|
||||
|
||||
/**
|
||||
* 运次卡片
|
||||
* <p>
|
||||
* 未配载的运次就是一张运单:详情照运单详情下发(打卡节点、在途打卡、过程配置都还在),
|
||||
* 只是状态按运次口径只读推导,不走会落库的接单态归一化。
|
||||
* 配载的运次按配载单下发整趟车的信息,运单级字段(运费、当前节点、打卡节点)留空——
|
||||
* 一趟车里每张子单各有各的项目配置与打卡节点。
|
||||
*/
|
||||
private DriverWaybillCardVO toTripCard(TripVO trip, boolean withDetail) {
|
||||
DriverWaybillCardVO card = Func.isEmpty(trip.getLoadingNo()) && withDetail
|
||||
? unloadedTripDetail(trip)
|
||||
: new DriverWaybillCardVO();
|
||||
fillTripFields(card, trip, withDetail);
|
||||
return card;
|
||||
}
|
||||
|
||||
/**
|
||||
* 未配载运次的详情照运单详情拼,只读:不调用 normalizeAcceptStatus
|
||||
*/
|
||||
private DriverWaybillCardVO unloadedTripDetail(TripVO trip) {
|
||||
Long waybillId = parseId(trip.getId());
|
||||
Waybill waybill = waybillId == null ? null : waybillService.getById(waybillId);
|
||||
return waybill == null ? new DriverWaybillCardVO() : toCard(waybill, true);
|
||||
}
|
||||
|
||||
private void fillTripFields(DriverWaybillCardVO card, TripVO trip, boolean withDetail) {
|
||||
List<TripVO.TripWaybillVO> children = trip.getWaybills() == null ? List.of() : trip.getWaybills();
|
||||
card.setTripId(trip.getId());
|
||||
card.setTripNo(Func.toStr(trip.getTripNo(), ""));
|
||||
card.setLoadingNo(Func.toStr(trip.getLoadingNo(), ""));
|
||||
// 旧字段 id / waybillNo 保留原语义:未配载就是这张运单,配载取下第一张可见子运单(运次操作请用 tripId)
|
||||
card.setId(parseId(children.isEmpty() ? trip.getId() : children.get(0).getId()));
|
||||
card.setWaybillNo(children.isEmpty() ? card.getTripNo() : Func.toStr(children.get(0).getWaybillNo(), ""));
|
||||
card.setStatus(trip.getStatus());
|
||||
card.setRequireAccept(trip.getRequireAccept());
|
||||
card.setAcceptStatus(trip.getAcceptStatus());
|
||||
card.setTransportOrgType(trip.getTransportOrgType());
|
||||
card.setTransportType(toTransportTypeLabel(trip.getTransportType()));
|
||||
card.setDriverName(Func.toStr(trip.getDriverName(), ""));
|
||||
card.setDriverPhone(Func.toStr(trip.getDriverPhone(), ""));
|
||||
card.setVehicleNo(Func.toStr(trip.getVehicleNo(), ""));
|
||||
card.setCreateTime(formatDateTime(trip.getSortTime()));
|
||||
card.setPublishTime(card.getCreateTime());
|
||||
// 计划时间与备注:配载运次取配载单,未配载的照运单卡片自己的值,不回填空
|
||||
if (Func.isNotEmpty(card.getLoadingNo())) {
|
||||
card.setPlanShipTime(formatLocalDateYmd(trip.getEstimatedStartDate()));
|
||||
card.setPlanFinishTime(formatLocalDateYmd(trip.getEstimatedEndDate()));
|
||||
card.setRemark(Func.toStr(trip.getTaskRemark(), ""));
|
||||
}
|
||||
card.setFromName(Func.toStrWithEmpty(trip.getDepartureName(), Func.toStr(trip.getDepartureAddress(), "")));
|
||||
card.setFromAddress(Func.toStrWithEmpty(trip.getDepartureAddress(), card.getFromName()));
|
||||
card.setToName(Func.toStrWithEmpty(trip.getArrivalName(), Func.toStr(trip.getArrivalAddress(), "")));
|
||||
card.setToAddress(Func.toStrWithEmpty(trip.getArrivalAddress(), card.getToName()));
|
||||
card.setViaName(Func.toStr(trip.getTransitName(), ""));
|
||||
card.setStationCount(trip.getStationCount());
|
||||
card.setPickupAddress(card.getFromAddress());
|
||||
card.setUnloadAddress(card.getToAddress());
|
||||
// 货物名可能是一格里的多个名字,按既有拆分口径摊平后再去重
|
||||
List<String> cargoNames = children.stream()
|
||||
.flatMap(child -> splitCargoNames(child.getCargoName()).stream())
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
card.setCargoNames(cargoNames);
|
||||
card.setCargoName(String.join("、", cargoNames));
|
||||
card.setCargoCategory(children.stream()
|
||||
.map(TripVO.TripWaybillVO::getCargoType)
|
||||
.filter(Func::isNotEmpty)
|
||||
.distinct()
|
||||
.collect(Collectors.joining("、")));
|
||||
card.setCargoSummary(Func.toStr(trip.getCargoSummary(), ""));
|
||||
String weight = trip.getTotalQuantity() == null
|
||||
? card.getCargoSummary()
|
||||
: formatWeight(trip.getTotalQuantity(), trip.getQuantityUnit());
|
||||
card.setWeight(weight);
|
||||
card.setCargoQuantity(weight);
|
||||
card.setTotalWeight(weight);
|
||||
if (withDetail) {
|
||||
card.setStations(trip.getStations());
|
||||
card.setRoutePoints(toTripRoutePoints(trip.getStations()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 详情里的点位列:整趟车的全部站点,站序即途经顺序
|
||||
*/
|
||||
private List<DriverWaybillCardVO.DriverRoutePointVO> toTripRoutePoints(List<TripStationVO> stations) {
|
||||
if (Func.isEmpty(stations)) {
|
||||
return List.of();
|
||||
}
|
||||
return stations.stream().map(station -> {
|
||||
DriverWaybillCardVO.DriverRoutePointVO point = new DriverWaybillCardVO.DriverRoutePointVO();
|
||||
point.setName(Func.toStr(station.getName(), ""));
|
||||
point.setAddress(Func.toStrWithEmpty(station.getAddress(), point.getName()));
|
||||
point.setStatus("pending");
|
||||
return point;
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private Long parseId(String value) {
|
||||
if (Func.isEmpty(value)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return Long.valueOf(value.trim());
|
||||
} catch (NumberFormatException ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private int normalizePageNo(Integer current) {
|
||||
return current == null || current < 1 ? 1 : current;
|
||||
}
|
||||
|
||||
private int normalizePageSize(Integer size) {
|
||||
if (size == null || size < 1) {
|
||||
return DEFAULT_PAGE_SIZE;
|
||||
}
|
||||
return Math.min(size, MAX_PAGE_SIZE);
|
||||
}
|
||||
|
||||
private String toTransportTypeLabel(String transportType) {
|
||||
if (Func.isBlank(transportType)) {
|
||||
return "";
|
||||
|
||||
+2
-2
@@ -135,13 +135,13 @@ final class TripAggregation {
|
||||
for (Waybill child : children) {
|
||||
String status = child == null ? null : child.getBusinessStatus();
|
||||
total++;
|
||||
if (TERMINAL_STATUSES.contains(status)) {
|
||||
if (status != null && TERMINAL_STATUSES.contains(status)) {
|
||||
terminal++;
|
||||
}
|
||||
if (COMPLETED.equals(status)) {
|
||||
completed++;
|
||||
}
|
||||
if (PENDING_STATUSES.contains(status)) {
|
||||
if (status != null && PENDING_STATUSES.contains(status)) {
|
||||
pending++;
|
||||
}
|
||||
}
|
||||
|
||||
+98
-9
@@ -32,6 +32,7 @@ import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.transport.pojo.entity.LoadingManage;
|
||||
import org.springblade.transport.pojo.entity.Waybill;
|
||||
import org.springblade.transport.pojo.vo.TripQueryVO;
|
||||
import org.springblade.transport.pojo.vo.TripStationVO;
|
||||
import org.springblade.transport.pojo.vo.TripTabCountsVO;
|
||||
import org.springblade.transport.pojo.vo.TripVO;
|
||||
import org.springblade.transport.service.ILoadingManageService;
|
||||
@@ -40,6 +41,7 @@ import org.springblade.transport.service.IWaybillService;
|
||||
import org.springblade.transport.support.WaybillProcessSupport;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
@@ -129,7 +131,7 @@ public class TripQueryServiceImpl implements ITripQueryService {
|
||||
}
|
||||
LoadingManage loading = visibleLoading(id, condition);
|
||||
if (loading != null) {
|
||||
List<Waybill> children = loadChildren(Set.of(loading.getLoadingNo()))
|
||||
List<Waybill> children = loadChildren(Set.of(loading.getLoadingNo()), Set.of())
|
||||
.getOrDefault(loading.getLoadingNo(), List.of());
|
||||
if (children.isEmpty()) {
|
||||
// 子单全部被拒单(或已释放)的配载运次对司机不可见,与列表口径一致
|
||||
@@ -218,6 +220,9 @@ public class TripQueryServiceImpl implements ITripQueryService {
|
||||
|
||||
/**
|
||||
* 把这一页的运次行组装成运次:先批量取配载单与子运单,再逐行拼装
|
||||
* <p>
|
||||
* 页里的行只带分组键与排序键,子运单要按主键/配载单号回查一次:未配载的运次就是那张运单本身,
|
||||
* 状态、接单需求与货物都得从那行真身来,不能拿分组行凑。
|
||||
*/
|
||||
private List<TripVO> assemble(List<Waybill> rows) {
|
||||
if (Func.isEmpty(rows)) {
|
||||
@@ -227,13 +232,25 @@ public class TripQueryServiceImpl implements ITripQueryService {
|
||||
.map(Waybill::getLoadingNo)
|
||||
.filter(Func::isNotEmpty)
|
||||
.collect(Collectors.toCollection(LinkedHashSet::new));
|
||||
Set<Long> loneIds = rows.stream()
|
||||
.filter(row -> Func.isEmpty(row.getLoadingNo()))
|
||||
.map(Waybill::getId)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toCollection(LinkedHashSet::new));
|
||||
Map<String, LoadingManage> loadings = loadLoadings(loadingNos);
|
||||
Map<String, List<Waybill>> children = loadChildren(loadingNos);
|
||||
Map<String, List<Waybill>> children = loadChildren(loadingNos, loneIds);
|
||||
Map<Long, Waybill> loneWaybills = new LinkedHashMap<>();
|
||||
children.values().forEach(group -> group.forEach(child -> loneWaybills.put(child.getId(), child)));
|
||||
List<TripVO> trips = new ArrayList<>(rows.size());
|
||||
for (Waybill row : rows) {
|
||||
String loadingNo = Func.toStr(row.getLoadingNo(), "");
|
||||
if (Func.isEmpty(loadingNo)) {
|
||||
trips.add(waybillTrip(row, false));
|
||||
Waybill waybill = loneWaybills.get(row.getId());
|
||||
if (waybill == null) {
|
||||
// 运单在两次查询之间被删或被拒单:这一页不给出没有子单的运次
|
||||
continue;
|
||||
}
|
||||
trips.add(waybillTrip(waybill, false));
|
||||
continue;
|
||||
}
|
||||
LoadingManage loading = loadings.get(loadingNo);
|
||||
@@ -265,24 +282,26 @@ public class TripQueryServiceImpl implements ITripQueryService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 段二:按本页运次的配载单号一次取回子运单,不逐条查
|
||||
* 段二:按本页运次的配载单号与未配载运单主键一次取回子运单,不逐条查
|
||||
* <p>
|
||||
* 子单归属一律以配载单号为准:库层就是按配载单号分组聚合的,服务层复核用别的关联关系,
|
||||
* 同一页里就会出现「Tab 说它在进行中、卡片按子单算出来是待接单」。
|
||||
* 配载单上的运单号集合只是派单时的快照,子单被释放(配载单号清空)后不再属于该运次。
|
||||
*/
|
||||
private Map<String, List<Waybill>> loadChildren(Set<String> loadingNos) {
|
||||
if (loadingNos.isEmpty()) {
|
||||
private Map<String, List<Waybill>> loadChildren(Set<String> loadingNos, Set<Long> loneIds) {
|
||||
if (loadingNos.isEmpty() && loneIds.isEmpty()) {
|
||||
return Map.of();
|
||||
}
|
||||
List<Waybill> children = waybillService.list(Wrappers.<Waybill>lambdaQuery()
|
||||
.in(Waybill::getLoadingNo, loadingNos)
|
||||
.in(Waybill::getBusinessStatus, TripAggregation.CHILD_STATUSES)
|
||||
.and(visible -> visible.isNull(Waybill::getDriverAcceptStatus)
|
||||
.or().ne(Waybill::getDriverAcceptStatus, WaybillProcessSupport.ACCEPT_REJECTED)));
|
||||
.or().ne(Waybill::getDriverAcceptStatus, WaybillProcessSupport.ACCEPT_REJECTED))
|
||||
.and(scope -> scope.in(Func.isNotEmpty(loadingNos), Waybill::getLoadingNo, loadingNos)
|
||||
.or(Func.isNotEmpty(loneIds), lone -> lone.in(Waybill::getId, loneIds))));
|
||||
Map<String, List<Waybill>> grouped = new LinkedHashMap<>();
|
||||
for (Waybill child : children) {
|
||||
grouped.computeIfAbsent(child.getLoadingNo(), key -> new ArrayList<>()).add(child);
|
||||
String key = Func.isEmpty(child.getLoadingNo()) ? String.valueOf(child.getId()) : child.getLoadingNo();
|
||||
grouped.computeIfAbsent(key, ignored -> new ArrayList<>()).add(child);
|
||||
}
|
||||
grouped.values().forEach(group -> group.sort(
|
||||
Comparator.comparing(Waybill::getCreateTime, Comparator.nullsLast(Comparator.naturalOrder()))));
|
||||
@@ -310,11 +329,15 @@ public class TripQueryServiceImpl implements ITripQueryService {
|
||||
trip.setDepartureAddress(Func.toStr(loading.getDepartureAddress(), ""));
|
||||
trip.setTransitAddress(Func.toStr(loading.getTransitAddress(), ""));
|
||||
trip.setArrivalAddress(Func.toStr(loading.getArrivalAddress(), ""));
|
||||
trip.setEstimatedStartDate(loading.getEstimatedStartDate());
|
||||
trip.setEstimatedEndDate(loading.getEstimatedEndDate());
|
||||
trip.setTaskRemark(Func.toStr(loading.getTaskRemark(), ""));
|
||||
if (withPayload) {
|
||||
trip.setRouteJson(loading.getRouteJson());
|
||||
trip.setGoodsJson(loading.getGoodsJson());
|
||||
}
|
||||
fillAggregation(trip, children);
|
||||
fillRouteAndCargo(trip, loading, children, withPayload);
|
||||
return trip;
|
||||
}
|
||||
|
||||
@@ -336,11 +359,13 @@ public class TripQueryServiceImpl implements ITripQueryService {
|
||||
trip.setCargoType(Func.toStr(waybill.getCargoType(), ""));
|
||||
trip.setDepartureAddress(Func.toStr(waybill.getDepartureAddress(), ""));
|
||||
trip.setArrivalAddress(Func.toStr(waybill.getArrivalAddress(), ""));
|
||||
trip.setTaskRemark(Func.toStr(waybill.getTaskRemark(), ""));
|
||||
if (withPayload) {
|
||||
trip.setRouteJson(waybill.getRouteJson());
|
||||
trip.setGoodsJson(waybill.getGoodsJson());
|
||||
}
|
||||
fillAggregation(trip, List.of(waybill));
|
||||
fillRouteAndCargo(trip, null, List.of(waybill), withPayload);
|
||||
return trip;
|
||||
}
|
||||
|
||||
@@ -359,6 +384,70 @@ public class TripQueryServiceImpl implements ITripQueryService {
|
||||
trip.setWaybills(children.stream().map(this::toWaybillVO).toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 站点与货物都按整趟车算:站点明细只在详情下发,列表带走起/经/终、站点数与货物汇总
|
||||
*/
|
||||
private void fillRouteAndCargo(TripVO trip, LoadingManage loading, List<Waybill> children, boolean withPayload) {
|
||||
List<TripStationVO> stations = TripStationAssembler.assemble(loading, children);
|
||||
trip.setDepartureName(TripStationAssembler.departureName(stations));
|
||||
trip.setTransitName(TripStationAssembler.transitName(stations));
|
||||
trip.setArrivalName(TripStationAssembler.arrivalName(stations));
|
||||
trip.setStationCount(stations.size());
|
||||
if (withPayload) {
|
||||
trip.setStations(stations);
|
||||
}
|
||||
trip.setCargoSummary(cargoSummary(children));
|
||||
fillTotalQuantity(trip, children);
|
||||
}
|
||||
|
||||
/**
|
||||
* 货物汇总:同名货物合并且单位一致时取数量合计,没有数量的只列货名
|
||||
*/
|
||||
private String cargoSummary(List<Waybill> children) {
|
||||
Map<String, Map<String, BigDecimal>> grouped = new LinkedHashMap<>();
|
||||
for (Waybill child : children) {
|
||||
String name = Func.toStrWithEmpty(child.getCargoName(), "货物");
|
||||
String unit = Func.toStr(child.getQuantityUnit(), "");
|
||||
Map<String, BigDecimal> byUnit = grouped.computeIfAbsent(name, key -> new LinkedHashMap<>());
|
||||
BigDecimal quantity = child.getQuantity() == null ? BigDecimal.ZERO : child.getQuantity();
|
||||
byUnit.merge(unit, quantity, BigDecimal::add);
|
||||
}
|
||||
List<String> parts = new ArrayList<>();
|
||||
grouped.forEach((name, byUnit) -> byUnit.forEach((unit, quantity) ->
|
||||
parts.add(quantity.signum() == 0 ? name : name + " " + formatQuantity(quantity, unit))));
|
||||
return String.join("、", parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* 合计重量:只有全部子运单同单位时才能相加,单位不一致时留空(汇总文案里仍分单位列出)
|
||||
*/
|
||||
private void fillTotalQuantity(TripVO trip, List<Waybill> children) {
|
||||
String unit = null;
|
||||
BigDecimal total = BigDecimal.ZERO;
|
||||
for (Waybill child : children) {
|
||||
String childUnit = Func.toStr(child.getQuantityUnit(), "");
|
||||
if (child.getQuantity() == null || Func.isEmpty(childUnit)) {
|
||||
return;
|
||||
}
|
||||
if (unit == null) {
|
||||
unit = childUnit;
|
||||
} else if (!unit.equals(childUnit)) {
|
||||
return;
|
||||
}
|
||||
total = total.add(child.getQuantity());
|
||||
}
|
||||
if (unit == null) {
|
||||
return;
|
||||
}
|
||||
trip.setTotalQuantity(total);
|
||||
trip.setQuantityUnit(unit);
|
||||
}
|
||||
|
||||
private String formatQuantity(BigDecimal quantity, String unit) {
|
||||
String value = quantity.stripTrailingZeros().toPlainString();
|
||||
return Func.isEmpty(unit) ? value : value + unit;
|
||||
}
|
||||
|
||||
private TripVO.TripWaybillVO toWaybillVO(Waybill waybill) {
|
||||
TripVO.TripWaybillVO child = new TripVO.TripWaybillVO();
|
||||
child.setId(tripIdOf(waybill.getId()));
|
||||
|
||||
+342
@@ -0,0 +1,342 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.service.impl;
|
||||
|
||||
import org.springblade.core.tool.jackson.JsonUtil;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.transport.pojo.entity.LoadingManage;
|
||||
import org.springblade.transport.pojo.entity.Waybill;
|
||||
import org.springblade.transport.pojo.vo.TripStationVO;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 运次站点拼装:把一趟车的实际途经顺序与每站的装卸、货物条目拼出来
|
||||
* <p>
|
||||
* 站点顺序优先取配载单的路线信息(有序站点数组);站点标记「装N / 卸N」的 N 指向配载单运单集合里的第 N 张子运单。
|
||||
* 没有路线信息时回退到子运单的起讫点去重成站(保留首次出现顺序),再退到配载单自己的发货/途经/到货地址;
|
||||
* 都没有则给空站点数组,前端按空站点渲染而不是崩。
|
||||
* <p>
|
||||
* 货物条目取子运单而不是配载单的货物信息:配载单的货物条数与子运单不一定同数,行序也对不上站点编号。
|
||||
*/
|
||||
final class TripStationAssembler {
|
||||
|
||||
private static final String LOAD = "load";
|
||||
private static final String UNLOAD = "unload";
|
||||
private static final String LOAD_UNLOAD = "load_unload";
|
||||
/** 途经站点之间的连接符 */
|
||||
private static final String TRANSIT_JOINER = " → ";
|
||||
/** 站点去重用的键分隔符:站名与地址都为空的两个点不该被当成同一个 */
|
||||
private static final String KEY_SEPARATOR = "\u0000";
|
||||
|
||||
private TripStationAssembler() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 拼装整趟车的站点,站序从 1 开始
|
||||
*
|
||||
* @param loading 配载单,未配载传 null
|
||||
* @param children 该运次的可见子运单
|
||||
*/
|
||||
static List<TripStationVO> assemble(LoadingManage loading, List<Waybill> children) {
|
||||
List<TripStationVO> stations = fromRoute(loading, children);
|
||||
if (stations.isEmpty()) {
|
||||
stations = fromWaybillPoints(children);
|
||||
}
|
||||
if (stations.isEmpty() && loading != null) {
|
||||
stations = fromLoadingAddresses(loading);
|
||||
}
|
||||
for (int i = 0; i < stations.size(); i++) {
|
||||
stations.get(i).setIndex(i + 1);
|
||||
}
|
||||
return stations;
|
||||
}
|
||||
|
||||
/**
|
||||
* 起点名称:首站站名,没有站点时为空
|
||||
*/
|
||||
static String departureName(List<TripStationVO> stations) {
|
||||
return stations.isEmpty() ? "" : Func.toStr(stations.get(0).getName(), "");
|
||||
}
|
||||
|
||||
/**
|
||||
* 终点名称:末站站名,没有站点时为空
|
||||
*/
|
||||
static String arrivalName(List<TripStationVO> stations) {
|
||||
return stations.isEmpty() ? "" : Func.toStr(stations.get(stations.size() - 1).getName(), "");
|
||||
}
|
||||
|
||||
/**
|
||||
* 途经站点:中间各站按顺序连接,不足三站为空
|
||||
*/
|
||||
static String transitName(List<TripStationVO> stations) {
|
||||
if (stations.size() < 3) {
|
||||
return "";
|
||||
}
|
||||
List<String> names = new ArrayList<>();
|
||||
for (int i = 1; i < stations.size() - 1; i++) {
|
||||
String name = Func.toStr(stations.get(i).getName(), "");
|
||||
if (Func.isNotEmpty(name)) {
|
||||
names.add(name);
|
||||
}
|
||||
}
|
||||
return String.join(TRANSIT_JOINER, names);
|
||||
}
|
||||
|
||||
/**
|
||||
* 站点来自配载单的路线信息:按数组顺序成站,标记里第 N 张子运单按配载单运单集合的顺序取
|
||||
*/
|
||||
private static List<TripStationVO> fromRoute(LoadingManage loading, List<Waybill> children) {
|
||||
if (loading == null) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
List<Map<String, Object>> route = objectList(loading.getRouteJson());
|
||||
if (route.isEmpty()) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
List<String> waybillIds = stringList(loading.getWaybillIdsJson());
|
||||
Map<String, Waybill> byId = new LinkedHashMap<>();
|
||||
for (Waybill child : children) {
|
||||
if (child.getId() != null) {
|
||||
byId.put(String.valueOf(child.getId()), child);
|
||||
}
|
||||
}
|
||||
List<TripStationVO> stations = new ArrayList<>();
|
||||
for (Map<String, Object> element : route) {
|
||||
String address = stringValue(element.get("address"));
|
||||
String display = stringValue(element.get("displayAddress"));
|
||||
TripStationVO station = new TripStationVO();
|
||||
station.setName(Func.toStrWithEmpty(display, address));
|
||||
station.setAddress(Func.toStrWithEmpty(address, station.getName()));
|
||||
List<String> tags = stringList(element.get("tags"));
|
||||
station.setTags(tags);
|
||||
List<TripStationVO.TripCargoVO> cargos = new ArrayList<>();
|
||||
boolean load = false;
|
||||
boolean unload = false;
|
||||
for (String tag : tags) {
|
||||
boolean loadTag = tag.contains("装");
|
||||
boolean unloadTag = tag.contains("卸");
|
||||
if (!loadTag && !unloadTag) {
|
||||
continue;
|
||||
}
|
||||
Waybill child = childOfTag(tag, waybillIds, byId);
|
||||
if (child == null) {
|
||||
continue;
|
||||
}
|
||||
load |= loadTag;
|
||||
unload |= unloadTag;
|
||||
addCargo(cargos, child, loadTag ? LOAD : UNLOAD);
|
||||
if (loadTag && unloadTag) {
|
||||
addCargo(cargos, child, UNLOAD);
|
||||
}
|
||||
}
|
||||
fillOperation(station, load, unload);
|
||||
station.setCargos(cargos);
|
||||
stations.add(station);
|
||||
}
|
||||
return stations;
|
||||
}
|
||||
|
||||
/**
|
||||
* 站点回退到子运单的起讫点:按子运单顺序每张的起点、终点依次成站,同名同址只留首次出现
|
||||
*/
|
||||
private static List<TripStationVO> fromWaybillPoints(List<Waybill> children) {
|
||||
Map<String, TripStationVO> stations = new LinkedHashMap<>();
|
||||
Map<String, boolean[]> operations = new LinkedHashMap<>();
|
||||
for (Waybill child : children) {
|
||||
String fromName = pointName(child.getDepartureName(), child.getDepartureAddress());
|
||||
String fromAddress = Func.toStrWithEmpty(child.getDepartureAddress(), fromName);
|
||||
String toName = pointName(child.getArrivalName(), child.getArrivalAddress());
|
||||
String toAddress = Func.toStrWithEmpty(child.getArrivalAddress(), toName);
|
||||
point(stations, operations, fromName, fromAddress, true, child);
|
||||
point(stations, operations, toName, toAddress, false, child);
|
||||
}
|
||||
operations.forEach((key, flags) -> fillOperation(stations.get(key), flags[0], flags[1]));
|
||||
return new ArrayList<>(stations.values());
|
||||
}
|
||||
|
||||
private static void point(Map<String, TripStationVO> stations, Map<String, boolean[]> operations,
|
||||
String name, String address, boolean load, Waybill child) {
|
||||
if (Func.isEmpty(name) && Func.isEmpty(address)) {
|
||||
return;
|
||||
}
|
||||
String key = name + KEY_SEPARATOR + address;
|
||||
TripStationVO station = stations.get(key);
|
||||
if (station == null) {
|
||||
station = new TripStationVO();
|
||||
station.setName(name);
|
||||
station.setAddress(address);
|
||||
station.setTags(new ArrayList<>());
|
||||
station.setCargos(new ArrayList<>());
|
||||
stations.put(key, station);
|
||||
operations.put(key, new boolean[2]);
|
||||
}
|
||||
boolean[] flags = operations.get(key);
|
||||
flags[0] |= load;
|
||||
flags[1] |= !load;
|
||||
addCargo(station.getCargos(), child, load ? LOAD : UNLOAD);
|
||||
}
|
||||
|
||||
/**
|
||||
* 站点再退到配载单自己的发货 / 途经 / 到货地址
|
||||
*/
|
||||
private static List<TripStationVO> fromLoadingAddresses(LoadingManage loading) {
|
||||
List<TripStationVO> stations = new ArrayList<>();
|
||||
for (String address : List.of(
|
||||
Func.toStr(loading.getDepartureAddress(), ""),
|
||||
Func.toStr(loading.getTransitAddress(), ""),
|
||||
Func.toStr(loading.getArrivalAddress(), ""))) {
|
||||
if (Func.isEmpty(address)) {
|
||||
continue;
|
||||
}
|
||||
TripStationVO station = new TripStationVO();
|
||||
station.setName(address);
|
||||
station.setAddress(address);
|
||||
station.setTags(new ArrayList<>());
|
||||
station.setCargos(new ArrayList<>());
|
||||
stations.add(station);
|
||||
}
|
||||
return stations;
|
||||
}
|
||||
|
||||
/**
|
||||
* 站点标记里第 N 张子运单:N 是配载单运单集合里的位次,不是可见子单里的位次
|
||||
*/
|
||||
private static Waybill childOfTag(String tag, List<String> waybillIds, Map<String, Waybill> byId) {
|
||||
String digits = tag.replaceAll("\\D+", "");
|
||||
if (Func.isEmpty(digits)) {
|
||||
return null;
|
||||
}
|
||||
int order = Func.toInt(digits, 0);
|
||||
if (order < 1 || order > waybillIds.size()) {
|
||||
return null;
|
||||
}
|
||||
return byId.get(waybillIds.get(order - 1));
|
||||
}
|
||||
|
||||
private static void addCargo(List<TripStationVO.TripCargoVO> cargos, Waybill child, String operation) {
|
||||
if (child == null) {
|
||||
return;
|
||||
}
|
||||
String waybillId = child.getId() == null ? null : String.valueOf(child.getId());
|
||||
boolean exists = cargos.stream().anyMatch(cargo ->
|
||||
Objects.equals(cargo.getWaybillId(), waybillId) && Objects.equals(cargo.getOperation(), operation));
|
||||
if (exists) {
|
||||
return;
|
||||
}
|
||||
TripStationVO.TripCargoVO cargo = new TripStationVO.TripCargoVO();
|
||||
cargo.setWaybillId(waybillId);
|
||||
cargo.setWaybillNo(child.getWaybillNo());
|
||||
cargo.setCargoName(child.getCargoName());
|
||||
cargo.setCustomerName(child.getCustomerName());
|
||||
cargo.setQuantity(child.getQuantity());
|
||||
cargo.setQuantityUnit(child.getQuantityUnit());
|
||||
cargo.setOperation(operation);
|
||||
cargo.setProjectName(child.getProjectName());
|
||||
cargos.add(cargo);
|
||||
}
|
||||
|
||||
private static void fillOperation(TripStationVO station, boolean load, boolean unload) {
|
||||
if (load && unload) {
|
||||
station.setOperationType(LOAD_UNLOAD);
|
||||
station.setOperationTypeLabel("装卸");
|
||||
} else if (load) {
|
||||
station.setOperationType(LOAD);
|
||||
station.setOperationTypeLabel("装货");
|
||||
} else if (unload) {
|
||||
station.setOperationType(UNLOAD);
|
||||
station.setOperationTypeLabel("卸货");
|
||||
} else {
|
||||
station.setOperationType(null);
|
||||
station.setOperationTypeLabel("");
|
||||
}
|
||||
}
|
||||
|
||||
private static String pointName(String name, String address) {
|
||||
return Func.toStrWithEmpty(name, Func.toStr(address, ""));
|
||||
}
|
||||
|
||||
private static List<Map<String, Object>> objectList(String json) {
|
||||
if (Func.isEmpty(json)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
try {
|
||||
Object parsed = JsonUtil.parse(json, List.class);
|
||||
if (parsed instanceof List<?> list) {
|
||||
List<Map<String, Object>> result = new ArrayList<>();
|
||||
for (Object item : list) {
|
||||
if (item instanceof Map<?, ?> map) {
|
||||
Map<String, Object> element = new LinkedHashMap<>();
|
||||
map.forEach((key, value) -> element.put(String.valueOf(key), value));
|
||||
result.add(element);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
// 路线信息坏了不该让整趟车读不出来,按没有路线信息回退
|
||||
}
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
private static List<String> stringList(Object value) {
|
||||
List<String> result = new ArrayList<>();
|
||||
if (value instanceof List<?> list) {
|
||||
for (Object item : list) {
|
||||
String text = stringValue(item);
|
||||
if (Func.isNotEmpty(text)) {
|
||||
result.add(text);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
String text = stringValue(value);
|
||||
if (Func.isEmpty(text)) {
|
||||
return result;
|
||||
}
|
||||
try {
|
||||
Object parsed = JsonUtil.parse(text, List.class);
|
||||
if (parsed instanceof List<?> list) {
|
||||
for (Object item : list) {
|
||||
String item2 = stringValue(item);
|
||||
if (Func.isNotEmpty(item2)) {
|
||||
result.add(item2);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
// 不是 JSON 数组就当逗号分隔处理
|
||||
}
|
||||
return Func.toStrList(",", text);
|
||||
}
|
||||
|
||||
private static String stringValue(Object value) {
|
||||
return value == null ? "" : String.valueOf(value).trim();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user