小程序对接
This commit is contained in:
+42
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
*/
|
||||
package org.springblade.transport.pojo.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 在途打卡提交
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "在途打卡提交")
|
||||
public class EnrouteSubmitDTO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "运单ID", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private Long waybillId;
|
||||
|
||||
@Schema(description = "定位信息")
|
||||
private Location location;
|
||||
|
||||
@Schema(description = "货物照片URL")
|
||||
private String photo;
|
||||
|
||||
@Data
|
||||
@Schema(description = "定位")
|
||||
public static class Location implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
private Double longitude;
|
||||
private Double latitude;
|
||||
private String address;
|
||||
}
|
||||
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
*/
|
||||
package org.springblade.transport.pojo.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 过程节点打卡提交(到场/装货/卸货/签收等,不含在途)
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "过程节点打卡提交")
|
||||
public class NodeSubmitDTO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "运单ID", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private Long waybillId;
|
||||
|
||||
@Schema(description = "过程节点 key", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String nodeCode;
|
||||
|
||||
@Schema(description = "定位信息")
|
||||
private Location location;
|
||||
|
||||
@Schema(description = "凭证照片 URL 列表")
|
||||
private List<String> photos;
|
||||
|
||||
@Schema(description = "重量(吨)")
|
||||
private String weight;
|
||||
|
||||
@Schema(description = "体积(方)")
|
||||
private String volume;
|
||||
|
||||
@Schema(description = "数量(件)")
|
||||
private String quantity;
|
||||
|
||||
@Schema(description = "备注")
|
||||
private String remark;
|
||||
|
||||
@Schema(description = "是否异常")
|
||||
private Boolean exception;
|
||||
|
||||
@Data
|
||||
@Schema(description = "定位")
|
||||
public static class Location implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
private Double longitude;
|
||||
private Double latitude;
|
||||
private String address;
|
||||
}
|
||||
|
||||
}
|
||||
+23
@@ -22,6 +22,8 @@
|
||||
*/
|
||||
package org.springblade.transport.pojo.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.FieldStrategy;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
@@ -31,6 +33,7 @@ import org.springblade.core.tenant.mp.TenantEntity;
|
||||
import java.io.Serial;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 运单管理实体类
|
||||
@@ -139,6 +142,26 @@ public class Waybill extends TenantEntity {
|
||||
@Schema(description = "司机手机号")
|
||||
private String driverPhone;
|
||||
|
||||
@Schema(description = "司机接单状态:pending待接单/accepted已接单/rejected已拒绝")
|
||||
@TableField(updateStrategy = FieldStrategy.ALWAYS)
|
||||
private String driverAcceptStatus;
|
||||
|
||||
@Schema(description = "司机接单时间")
|
||||
@TableField(updateStrategy = FieldStrategy.ALWAYS)
|
||||
private Date driverAcceptTime;
|
||||
|
||||
@Schema(description = "接单司机ID")
|
||||
@TableField(updateStrategy = FieldStrategy.ALWAYS)
|
||||
private Long driverAcceptDriverId;
|
||||
|
||||
@Schema(description = "司机拒绝接单时间")
|
||||
@TableField(updateStrategy = FieldStrategy.ALWAYS)
|
||||
private Date driverRejectTime;
|
||||
|
||||
@Schema(description = "司机拒绝接单原因")
|
||||
@TableField(updateStrategy = FieldStrategy.ALWAYS)
|
||||
private String driverRejectReason;
|
||||
|
||||
@Schema(description = "车/船/航班/班列号")
|
||||
private String vehicleNo;
|
||||
|
||||
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
*/
|
||||
package org.springblade.transport.pojo.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.springblade.core.tenant.mp.TenantEntity;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 运单在途打卡记录
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("blade_waybill_enroute_punch")
|
||||
@Schema(description = "运单在途打卡记录")
|
||||
public class WaybillEnroutePunch extends TenantEntity {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "运单ID")
|
||||
private Long waybillId;
|
||||
|
||||
@Schema(description = "运单号")
|
||||
private String waybillNo;
|
||||
|
||||
@Schema(description = "打卡司机ID")
|
||||
private Long driverId;
|
||||
|
||||
@Schema(description = "打卡时间")
|
||||
private Date punchTime;
|
||||
|
||||
@Schema(description = "经度")
|
||||
private BigDecimal longitude;
|
||||
|
||||
@Schema(description = "纬度")
|
||||
private BigDecimal latitude;
|
||||
|
||||
@Schema(description = "打卡地址")
|
||||
private String address;
|
||||
|
||||
@Schema(description = "货物照片URL")
|
||||
private String photo;
|
||||
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
*/
|
||||
package org.springblade.transport.pojo.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.springblade.core.tenant.mp.TenantEntity;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 运单过程节点打卡记录
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("blade_waybill_node_punch")
|
||||
@Schema(description = "运单过程节点打卡记录")
|
||||
public class WaybillNodePunch extends TenantEntity {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "运单ID")
|
||||
private Long waybillId;
|
||||
|
||||
@Schema(description = "运单号")
|
||||
private String waybillNo;
|
||||
|
||||
@Schema(description = "打卡司机ID")
|
||||
private Long driverId;
|
||||
|
||||
@Schema(description = "过程节点 key")
|
||||
private String nodeCode;
|
||||
|
||||
@Schema(description = "过程节点名称")
|
||||
private String nodeName;
|
||||
|
||||
@Schema(description = "打卡时间")
|
||||
private Date punchTime;
|
||||
|
||||
@Schema(description = "经度")
|
||||
private BigDecimal longitude;
|
||||
|
||||
@Schema(description = "纬度")
|
||||
private BigDecimal latitude;
|
||||
|
||||
@Schema(description = "打卡地址")
|
||||
private String address;
|
||||
|
||||
@Schema(description = "凭证照片URL,多张逗号分隔")
|
||||
private String photos;
|
||||
|
||||
@Schema(description = "重量(吨)")
|
||||
private String weight;
|
||||
|
||||
@Schema(description = "体积(方)")
|
||||
private String volume;
|
||||
|
||||
@Schema(description = "数量(件)")
|
||||
private String quantity;
|
||||
|
||||
@Schema(description = "备注")
|
||||
private String remark;
|
||||
|
||||
@Schema(description = "是否异常:0否 1是")
|
||||
private Integer exceptionFlag;
|
||||
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
*/
|
||||
package org.springblade.transport.pojo.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 司机端在途打卡记录
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "司机端在途打卡记录")
|
||||
public class DriverEnrouteRecordVO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "打卡时间 HH:mm 或 yyyy-MM-dd HH:mm:ss")
|
||||
private String time;
|
||||
|
||||
@Schema(description = "打卡地址")
|
||||
private String address;
|
||||
|
||||
@Schema(description = "货物照片")
|
||||
private String photo;
|
||||
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
*/
|
||||
package org.springblade.transport.pojo.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 司机端过程节点打卡结果
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "司机端过程节点打卡结果")
|
||||
public class DriverNodePunchVO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "运单ID")
|
||||
private Long waybillId;
|
||||
|
||||
@Schema(description = "节点 key")
|
||||
private String nodeCode;
|
||||
|
||||
@Schema(description = "节点名称")
|
||||
private String nodeName;
|
||||
|
||||
@Schema(description = "打卡时间(ISO 或 yyyy-MM-dd HH:mm:ss)")
|
||||
private String checkinTime;
|
||||
|
||||
@Schema(description = "打卡地址")
|
||||
private String address;
|
||||
|
||||
@Schema(description = "凭证照片")
|
||||
private List<String> photos = new ArrayList<>();
|
||||
|
||||
@Schema(description = "重量")
|
||||
private String weight;
|
||||
|
||||
@Schema(description = "体积")
|
||||
private String volume;
|
||||
|
||||
@Schema(description = "数量")
|
||||
private String quantity;
|
||||
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
*/
|
||||
package org.springblade.transport.pojo.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 司机端过程打卡节点(过程配置 punch=是)
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "司机端过程打卡节点")
|
||||
public class DriverPunchNodeVO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "节点 key,如 arrive_scene / load / transit")
|
||||
private String key;
|
||||
|
||||
@Schema(description = "节点名称")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "是否在途节点")
|
||||
private Boolean transit;
|
||||
|
||||
@Schema(description = "是否已打卡(在途=今日已打)")
|
||||
private Boolean done;
|
||||
|
||||
@Schema(description = "当前是否可打卡(顺序门禁 + 在途频次时段)")
|
||||
private Boolean actionable;
|
||||
|
||||
@Schema(description = "是否展示该卡(在途可能因频次/时段隐藏)")
|
||||
private Boolean visible;
|
||||
|
||||
@Schema(description = "是否默认展开(仅第一个可打卡节点)")
|
||||
private Boolean defaultExpanded;
|
||||
|
||||
@Schema(description = "打卡时间展示")
|
||||
private String checkinTime;
|
||||
|
||||
@Schema(description = "打卡地点")
|
||||
private String checkinPlace;
|
||||
|
||||
@Schema(description = "重量(吨)")
|
||||
private String weight;
|
||||
|
||||
@Schema(description = "体积(方)")
|
||||
private String volume;
|
||||
|
||||
@Schema(description = "数量(件)")
|
||||
private String quantity;
|
||||
|
||||
@Schema(description = "已上传凭证图(已打卡回显)")
|
||||
private List<DriverPunchPhotoVO> photos = new ArrayList<>();
|
||||
|
||||
@Schema(description = "是否需要定位")
|
||||
private Boolean needLocation;
|
||||
|
||||
@Schema(description = "是否需要上传货量")
|
||||
private Boolean needCargo;
|
||||
|
||||
@Schema(description = "货量类型:重量/体积/数量")
|
||||
private List<String> cargoTypes = new ArrayList<>();
|
||||
|
||||
@Schema(description = "是否需要上传凭证")
|
||||
private Boolean needVoucher;
|
||||
|
||||
@Schema(description = "凭证类型")
|
||||
private List<String> voucherTypes = new ArrayList<>();
|
||||
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
*/
|
||||
package org.springblade.transport.pojo.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 司机端打卡凭证图
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "司机端打卡凭证图")
|
||||
public class DriverPunchPhotoVO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "凭证类型,如 委托单")
|
||||
private String type;
|
||||
|
||||
@Schema(description = "展示标签,如 装货-委托单")
|
||||
private String label;
|
||||
|
||||
@Schema(description = "图片 URL")
|
||||
private String url;
|
||||
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* 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.pojo.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 司机端车辆卡片(对齐小程序 VehicleAuthInfo)
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "司机端车辆卡片")
|
||||
public class DriverVehicleCardVO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "车辆ID")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "车牌号")
|
||||
private String plateNo;
|
||||
|
||||
@Schema(description = "车辆类型")
|
||||
private String vehicleType;
|
||||
|
||||
@Schema(description = "行驶证主页照片")
|
||||
private String licenseFrontUrl;
|
||||
|
||||
@Schema(description = "行驶证副页照片")
|
||||
private String licenseBackUrl;
|
||||
|
||||
@Schema(description = "道路运输证号")
|
||||
private String roadTransportNo;
|
||||
|
||||
@Schema(description = "道路运输证照片")
|
||||
private String roadTransportUrl;
|
||||
|
||||
@Schema(description = "车架号")
|
||||
private String vin;
|
||||
|
||||
@Schema(description = "发动机号")
|
||||
private String engineNo;
|
||||
|
||||
@Schema(description = "行驶证有效期止")
|
||||
private String licenseValidEnd;
|
||||
|
||||
@Schema(description = "认证状态:0认证中 1认证通过 2认证驳回")
|
||||
private Integer certificationStatus;
|
||||
|
||||
}
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
/**
|
||||
* 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.pojo.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 司机端运单卡片(首页当前任务 / 待接预览 / 列表项)
|
||||
* <p>
|
||||
* 字段对齐小程序 MockWaybillItem,status 为数字枚举:
|
||||
* 0 待接单 / 1 运输中 / 2 已完成 / 3 已取消
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "司机端运单卡片")
|
||||
public class DriverWaybillCardVO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "运单ID")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "运单号")
|
||||
private String waybillNo;
|
||||
|
||||
@Schema(description = "起点名称")
|
||||
private String fromName;
|
||||
|
||||
@Schema(description = "终点名称")
|
||||
private String toName;
|
||||
|
||||
@Schema(description = "起点地址")
|
||||
private String fromAddress;
|
||||
|
||||
@Schema(description = "终点地址")
|
||||
private String toAddress;
|
||||
|
||||
@Schema(description = "货物名称列表")
|
||||
private List<String> cargoNames;
|
||||
|
||||
@Schema(description = "货物类别")
|
||||
private String cargoCategory;
|
||||
|
||||
@Schema(description = "重量(带单位)")
|
||||
private String weight;
|
||||
|
||||
@Schema(description = "状态:0待接单/1运输中/2已完成/3已取消")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
private String createTime;
|
||||
|
||||
@Schema(description = "发布时间(兼容小程序 publishTime)")
|
||||
private String publishTime;
|
||||
|
||||
@Schema(description = "当前过程节点")
|
||||
private String currentNode;
|
||||
|
||||
@Schema(description = "计划/完成时间段展示")
|
||||
private String timeRange;
|
||||
|
||||
@Schema(description = "运费参考金额")
|
||||
private BigDecimal freight;
|
||||
|
||||
@Schema(description = "司机姓名")
|
||||
private String driverName;
|
||||
|
||||
@Schema(description = "司机手机号")
|
||||
private String driverPhone;
|
||||
|
||||
@Schema(description = "车牌号")
|
||||
private String vehicleNo;
|
||||
|
||||
@Schema(description = "是否需要司机确认接单")
|
||||
private Boolean requireAccept;
|
||||
|
||||
@Schema(description = "司机接单状态:pending待接单/accepted已接单/rejected已拒绝")
|
||||
private String acceptStatus;
|
||||
|
||||
@Schema(description = "司机拒绝接单原因")
|
||||
private String rejectReason;
|
||||
|
||||
@Schema(description = "过程配置是否启用在途打卡(在途节点 punch=是)")
|
||||
private Boolean transitPunchEnabled;
|
||||
|
||||
@Schema(description = "是否展示「今日在途打卡」面板(到期且在时段内,或今日已打)")
|
||||
private Boolean transitCheckinVisible;
|
||||
|
||||
@Schema(description = "今日是否需要在途打卡(到期且未打且在时段内)")
|
||||
private Boolean requireTransitCheckinToday;
|
||||
|
||||
@Schema(description = "今日是否已完成在途打卡")
|
||||
private Boolean transitCheckinDoneToday;
|
||||
|
||||
@Schema(description = "在途打卡频次(每 N 天 1 次)")
|
||||
private Integer transitFrequencyDays;
|
||||
|
||||
@Schema(description = "在途打卡时段开始 HH:mm")
|
||||
private String transitTimeStart;
|
||||
|
||||
@Schema(description = "在途打卡时段结束 HH:mm")
|
||||
private String transitTimeEnd;
|
||||
|
||||
@Schema(description = "在途打卡记录(详情返回)")
|
||||
private List<DriverEnrouteRecordVO> enrouteRecords;
|
||||
|
||||
@Schema(description = "过程配置中 punch=是 的打卡节点列表(详情返回)")
|
||||
private List<DriverPunchNodeVO> punchNodes;
|
||||
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* 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.pojo.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 司机端待接运单预览
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "司机端待接运单预览")
|
||||
public class DriverWaybillPreviewVO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "预览列表")
|
||||
private List<DriverWaybillCardVO> records = new ArrayList<>();
|
||||
|
||||
@Schema(description = "待接运单总数(角标)")
|
||||
private Long total = 0L;
|
||||
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* 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.pojo.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 司机端运单列表 Tab 统计
|
||||
* <p>
|
||||
* 对齐小程序 { all, pending, doing, done }
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "司机端运单 Tab 统计")
|
||||
public class DriverWaybillTabCountsVO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "全部(待接单+运输中+已完成)")
|
||||
private long all;
|
||||
|
||||
@Schema(description = "待接单")
|
||||
private long pending;
|
||||
|
||||
@Schema(description = "进行中(运输中)")
|
||||
private long doing;
|
||||
|
||||
@Schema(description = "已完成")
|
||||
private long done;
|
||||
|
||||
}
|
||||
+8
@@ -85,4 +85,12 @@ public class ExceptionDisposalVO extends ExceptionDisposal {
|
||||
@Schema(description = "扩展信息")
|
||||
private Map<String, Object> extra;
|
||||
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "运单路线:{start, end}")
|
||||
private Map<String, String> route;
|
||||
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "货物信息:{name, weight}")
|
||||
private Map<String, String> cargo;
|
||||
|
||||
}
|
||||
|
||||
+4
@@ -66,4 +66,8 @@ public class ProcessConfigVO extends ProcessConfig {
|
||||
@Schema(description = "当前运单是否有关联凭证")
|
||||
private Boolean hasRelatedVoucher;
|
||||
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "关联项目是否已有运单")
|
||||
private Boolean hasRelatedWaybill;
|
||||
|
||||
}
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
*/
|
||||
package org.springblade.transport.pojo.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 司机打卡上传图片(节点-凭证类型)
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "司机打卡上传图片")
|
||||
public class WaybillPunchPhotoVO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "展示标签,如 装货-委托单")
|
||||
private String label;
|
||||
|
||||
@Schema(description = "图片 URL")
|
||||
private String url;
|
||||
|
||||
@Schema(description = "节点名称")
|
||||
private String nodeName;
|
||||
|
||||
@Schema(description = "凭证类型")
|
||||
private String voucherType;
|
||||
|
||||
@Schema(description = "打卡时间")
|
||||
private String punchTime;
|
||||
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
*/
|
||||
package org.springblade.transport.pojo.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 管理端单条打卡记录
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "管理端单条打卡记录")
|
||||
public class WaybillPunchRecordItemVO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "记录ID")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "类型:node / enroute")
|
||||
private String type;
|
||||
|
||||
@Schema(description = "节点 key")
|
||||
private String nodeCode;
|
||||
|
||||
@Schema(description = "节点名称")
|
||||
private String nodeName;
|
||||
|
||||
@Schema(description = "打卡时间")
|
||||
private String punchTime;
|
||||
|
||||
@Schema(description = "打卡地址")
|
||||
private String address;
|
||||
|
||||
@Schema(description = "经度")
|
||||
private String longitude;
|
||||
|
||||
@Schema(description = "纬度")
|
||||
private String latitude;
|
||||
|
||||
@Schema(description = "重量")
|
||||
private String weight;
|
||||
|
||||
@Schema(description = "体积")
|
||||
private String volume;
|
||||
|
||||
@Schema(description = "数量")
|
||||
private String quantity;
|
||||
|
||||
@Schema(description = "备注")
|
||||
private String remark;
|
||||
|
||||
@Schema(description = "是否异常")
|
||||
private Boolean exceptionFlag;
|
||||
|
||||
@Schema(description = "是否已打卡")
|
||||
private Boolean punched;
|
||||
|
||||
@Schema(description = "状态文案:已打卡/未打卡")
|
||||
private String statusName;
|
||||
|
||||
@Schema(description = "本条打卡凭证图")
|
||||
private List<WaybillPunchPhotoVO> photos = new ArrayList<>();
|
||||
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
*/
|
||||
package org.springblade.transport.pojo.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 管理端运单打卡记录汇总
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "管理端运单打卡记录汇总")
|
||||
public class WaybillPunchRecordsVO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "打卡流水(按时间升序,含节点/在途)")
|
||||
private List<WaybillPunchRecordItemVO> records = new ArrayList<>();
|
||||
|
||||
@Schema(description = "司机上传凭证图(扁平列表,label=节点-凭证类型)")
|
||||
private List<WaybillPunchPhotoVO> driverUploads = new ArrayList<>();
|
||||
|
||||
}
|
||||
+4
@@ -66,6 +66,10 @@ public class WaybillVO extends Waybill {
|
||||
@Schema(description = "业务状态名称")
|
||||
private String businessStatusName;
|
||||
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "是否需要司机确认接单(过程配置接单节点)")
|
||||
private Boolean requireAccept;
|
||||
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "是否仅查询未配载运单")
|
||||
private Integer onlyUnassignedLoading;
|
||||
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* 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.controller;
|
||||
|
||||
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springblade.core.boot.ctrl.BladeController;
|
||||
import org.springblade.core.tool.api.R;
|
||||
import org.springblade.transport.pojo.vo.DriverVehicleCardVO;
|
||||
import org.springblade.transport.pojo.vo.DriverVO;
|
||||
import org.springblade.transport.service.IDriverAppService;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 司机端档案(小程序)
|
||||
* <p>
|
||||
* 对外路径:{@code /api/blade-transport/driver/**}
|
||||
* 同时兼容未去前缀直连 {@code /blade-transport/driver/**}。
|
||||
*/
|
||||
@RestController
|
||||
@AllArgsConstructor
|
||||
@RequestMapping({"/driver", "/blade-transport/driver"})
|
||||
@Tag(name = "司机端档案", description = "小程序司机个人档案")
|
||||
public class DriverAppController extends BladeController {
|
||||
|
||||
private final IDriverAppService driverAppService;
|
||||
|
||||
@GetMapping("/mine")
|
||||
@ApiOperationSupport(order = 1)
|
||||
@Operation(summary = "当前登录司机档案", description = "按手机号匹配 blade_transport_driver.mobile;可传 mobile,未传则从登录态解析")
|
||||
public R<DriverVO> mine(@RequestParam(required = false) String mobile) {
|
||||
return R.data(driverAppService.currentByPhone(mobile));
|
||||
}
|
||||
|
||||
@GetMapping("/vehicles")
|
||||
@ApiOperationSupport(order = 2)
|
||||
@Operation(summary = "当前司机车辆列表", description = "按司机 driving_vehicle 车牌匹配 blade_transport_vehicle")
|
||||
public R<List<DriverVehicleCardVO>> vehicles() {
|
||||
return R.data(driverAppService.myVehicles());
|
||||
}
|
||||
|
||||
}
|
||||
+156
@@ -0,0 +1,156 @@
|
||||
/**
|
||||
* 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.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springblade.core.boot.ctrl.BladeController;
|
||||
import org.springblade.core.tool.api.R;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.transport.pojo.dto.EnrouteSubmitDTO;
|
||||
import org.springblade.transport.pojo.dto.NodeSubmitDTO;
|
||||
import org.springblade.transport.pojo.vo.DriverEnrouteRecordVO;
|
||||
import org.springblade.transport.pojo.vo.DriverNodePunchVO;
|
||||
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.service.IDriverWaybillService;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 司机端运单接口(小程序)
|
||||
* <p>
|
||||
* 对外完整路径:{@code /api/blade-transport/waybill/**}
|
||||
* (网关 StripPrefix 去掉 {@code blade-transport} 后落入 {@code /waybill/**})。
|
||||
* 同时兼容未去前缀直连({@code /blade-transport/waybill/**}),避免 404。
|
||||
* 不挂管理端菜单鉴权,仅需登录态(Blade Secure)。
|
||||
*/
|
||||
@RestController
|
||||
@AllArgsConstructor
|
||||
@RequestMapping({"/waybill", "/blade-transport/waybill"})
|
||||
@Tag(name = "司机端运单", description = "小程序司机端运单")
|
||||
public class DriverWaybillController extends BladeController {
|
||||
|
||||
private final IDriverWaybillService driverWaybillService;
|
||||
|
||||
@GetMapping("/current-task")
|
||||
@ApiOperationSupport(order = 1)
|
||||
@Operation(summary = "首页:当前运输中任务", description = "当前司机绑定车牌下 businessStatus=running 的最新一条运单")
|
||||
public R<DriverWaybillCardVO> currentTask() {
|
||||
return R.data(driverWaybillService.currentTask());
|
||||
}
|
||||
|
||||
@GetMapping("/pending-preview")
|
||||
@ApiOperationSupport(order = 2)
|
||||
@Operation(summary = "首页:待接运单预览", description = "当前司机绑定车牌下 businessStatus=pending 的预览列表与总数")
|
||||
public R<DriverWaybillPreviewVO> pendingPreview(
|
||||
@Parameter(description = "预览条数,默认 2") @RequestParam(required = false) Integer size) {
|
||||
return R.data(driverWaybillService.pendingPreview(size));
|
||||
}
|
||||
|
||||
@GetMapping("/counts")
|
||||
@ApiOperationSupport(order = 3)
|
||||
@Operation(summary = "运单 Tab 统计", description = "仅统计当前司机绑定车牌对应的运单:全部 / 待接单 / 进行中 / 已完成")
|
||||
public R<DriverWaybillTabCountsVO> counts() {
|
||||
return R.data(driverWaybillService.tabCounts());
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@ApiOperationSupport(order = 4)
|
||||
@Operation(summary = "运单分页列表", description = "仅返回当前司机绑定车牌(driving_vehicle)匹配运单 vehicleNo/trailerVehicleNo 的数据;status:空=全部,0待接单,1运输中,2已完成")
|
||||
public R<IPage<DriverWaybillCardVO>> page(
|
||||
@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.page(current, size, statusCode, keyword));
|
||||
}
|
||||
|
||||
@GetMapping("/detail")
|
||||
@ApiOperationSupport(order = 5)
|
||||
@Operation(summary = "司机运单详情", description = "返回 requireAccept、在途打卡可见性(transitCheckinVisible / requireTransitCheckinToday)等字段")
|
||||
public R<DriverWaybillCardVO> detail(
|
||||
@Parameter(description = "运单ID", required = true) @RequestParam Long id) {
|
||||
return R.data(driverWaybillService.detail(id));
|
||||
}
|
||||
|
||||
@PostMapping("/accept")
|
||||
@ApiOperationSupport(order = 6)
|
||||
@Operation(summary = "司机确认接单", description = "过程配置接单为「是」时,司机确认接单后运单进入进行中")
|
||||
public R accept(@Parameter(description = "运单ID", required = true) @RequestParam Long id) {
|
||||
return R.status(driverWaybillService.accept(id));
|
||||
}
|
||||
|
||||
@PostMapping("/reject")
|
||||
@ApiOperationSupport(order = 7)
|
||||
@Operation(summary = "司机拒绝接单", description = "过程配置接单为「是」时,司机可拒绝接单,运单保持待执行并记录拒单")
|
||||
public R reject(
|
||||
@Parameter(description = "运单ID", required = true) @RequestParam Long id,
|
||||
@Parameter(description = "拒绝原因") @RequestParam(required = false) String reason) {
|
||||
return R.status(driverWaybillService.reject(id, reason));
|
||||
}
|
||||
|
||||
@PostMapping("/enroute/submit")
|
||||
@ApiOperationSupport(order = 8)
|
||||
@Operation(summary = "提交在途打卡", description = "过程配置在途节点 punch=是,且满足频次/时段时允许提交")
|
||||
public R<DriverEnrouteRecordVO> submitEnroute(@RequestBody EnrouteSubmitDTO dto) {
|
||||
return R.data(driverWaybillService.submitEnroute(dto));
|
||||
}
|
||||
|
||||
@PostMapping("/node/submit")
|
||||
@ApiOperationSupport(order = 9)
|
||||
@Operation(summary = "提交过程节点打卡", description = "到场/装货/发货/到货/卸货/签收等 punch=是;在途请走 /enroute/submit")
|
||||
public R<DriverNodePunchVO> submitNode(@RequestBody NodeSubmitDTO dto) {
|
||||
return R.data(driverWaybillService.submitNode(dto));
|
||||
}
|
||||
|
||||
@PostMapping("/complete")
|
||||
@ApiOperationSupport(order = 10)
|
||||
@Operation(summary = "司机完成运单", description = "校验司机归属后改状态为已完成,并检查生成应收应付明细(与管理端一致)")
|
||||
public R complete(@Parameter(description = "运单ID", required = true) @RequestParam Long id) {
|
||||
return R.status(driverWaybillService.complete(id));
|
||||
}
|
||||
|
||||
/** 前端可能传空字符串表示「全部」 */
|
||||
private Integer parseStatus(String status) {
|
||||
if (Func.isEmpty(status)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return Integer.valueOf(status.trim());
|
||||
} catch (NumberFormatException ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+19
-7
@@ -33,6 +33,7 @@ import org.springblade.core.mp.support.Query;
|
||||
import org.springblade.core.secure.annotation.PreAuth;
|
||||
import org.springblade.core.tool.api.R;
|
||||
import org.springblade.transport.pojo.dto.ExceptionDisposalFollowRequest;
|
||||
import org.springblade.transport.pojo.entity.ExceptionDisposal;
|
||||
import org.springblade.transport.pojo.vo.ExceptionDisposalVO;
|
||||
import org.springblade.transport.service.IExceptionDisposalService;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
@@ -44,13 +45,14 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 异常处置控制器
|
||||
*
|
||||
* @author Chill
|
||||
* <p>
|
||||
* 对外路径:{@code /api/blade-transport/exception-disposal/**}
|
||||
* 同时兼容未去前缀直连 {@code /blade-transport/exception-disposal/**}。
|
||||
* 司机上报(submit)/ 列表 / 详情仅需登录态;跟进与完成保留菜单鉴权。
|
||||
*/
|
||||
@RestController
|
||||
@AllArgsConstructor
|
||||
@PreAuth(menu = "exception_disposal")
|
||||
@RequestMapping("/exception-disposal")
|
||||
@RequestMapping({"/exception-disposal", "/blade-transport/exception-disposal"})
|
||||
@Tag(name = "异常处置", description = "异常处置")
|
||||
public class ExceptionDisposalController extends BladeController {
|
||||
|
||||
@@ -70,8 +72,16 @@ public class ExceptionDisposalController extends BladeController {
|
||||
return R.data(exceptionDisposalService.detail(id));
|
||||
}
|
||||
|
||||
@PostMapping("/follow")
|
||||
@PostMapping("/submit")
|
||||
@ApiOperationSupport(order = 3)
|
||||
@Operation(summary = "异常上报", description = "司机端上报异常;上报人取登录态,运单信息按 waybillId/waybillNo 回填")
|
||||
public R<ExceptionDisposalVO> submit(@RequestBody ExceptionDisposal request) {
|
||||
return R.data(exceptionDisposalService.submitReport(request));
|
||||
}
|
||||
|
||||
@PostMapping("/follow")
|
||||
@PreAuth(menu = "exception_disposal")
|
||||
@ApiOperationSupport(order = 4)
|
||||
@Operation(summary = "异常跟进")
|
||||
public R follow(@RequestBody ExceptionDisposalFollowRequest request) {
|
||||
exceptionDisposalService.follow(request);
|
||||
@@ -79,7 +89,8 @@ public class ExceptionDisposalController extends BladeController {
|
||||
}
|
||||
|
||||
@PostMapping("/complete")
|
||||
@ApiOperationSupport(order = 4)
|
||||
@PreAuth(menu = "exception_disposal")
|
||||
@ApiOperationSupport(order = 5)
|
||||
@Operation(summary = "完成异常")
|
||||
public R complete(@RequestBody ExceptionDisposalFollowRequest request) {
|
||||
exceptionDisposalService.complete(request.getId());
|
||||
@@ -87,7 +98,8 @@ public class ExceptionDisposalController extends BladeController {
|
||||
}
|
||||
|
||||
@PostMapping("/batch-complete")
|
||||
@ApiOperationSupport(order = 5)
|
||||
@PreAuth(menu = "exception_disposal")
|
||||
@ApiOperationSupport(order = 6)
|
||||
@Operation(summary = "批量完成异常")
|
||||
public R batchComplete(@RequestParam String ids) {
|
||||
exceptionDisposalService.batchComplete(ids);
|
||||
|
||||
+12
-3
@@ -53,6 +53,7 @@ import org.springblade.transport.pojo.dto.WaybillImportBatchRequest;
|
||||
import org.springblade.transport.pojo.vo.BusinessRemoveResultVO;
|
||||
import org.springblade.transport.pojo.vo.LoadingManageVO;
|
||||
import org.springblade.transport.pojo.vo.WaybillImportBatchVO;
|
||||
import org.springblade.transport.pojo.vo.WaybillPunchRecordsVO;
|
||||
import org.springblade.transport.pojo.vo.WaybillVO;
|
||||
import org.springblade.transport.pojo.dto.WaybillMileageRequest;
|
||||
import org.springblade.transport.service.IContractManageService;
|
||||
@@ -100,6 +101,14 @@ public class WaybillController extends BladeController {
|
||||
return R.data(waybillService.detail(id));
|
||||
}
|
||||
|
||||
@GetMapping("/punch-records")
|
||||
@ApiOperationSupport(order = 1)
|
||||
@Operation(summary = "打卡记录与司机上传", description = "返回节点/在途打卡流水,以及司机上传凭证图(label=节点-凭证类型)")
|
||||
public R<WaybillPunchRecordsVO> punchRecords(
|
||||
@Parameter(description = "运单ID", required = true) @RequestParam Long waybillId) {
|
||||
return R.data(waybillService.listPunchRecords(waybillId));
|
||||
}
|
||||
|
||||
@GetMapping("/list")
|
||||
@ApiOperationSupport(order = 2)
|
||||
@Operation(summary = "分页", description = "传入waybill")
|
||||
@@ -273,9 +282,9 @@ public class WaybillController extends BladeController {
|
||||
|
||||
@PostMapping("/reassign")
|
||||
@ApiOperationSupport(order = 20)
|
||||
@Operation(summary = "重新派单", description = "传入id")
|
||||
public R reassign(@Parameter(description = "主键", required = true) @RequestParam Long id) {
|
||||
return R.status(waybillService.reassign(id));
|
||||
@Operation(summary = "重新派单", description = "传入运单ID及新的司机、手机号、车牌")
|
||||
public R reassign(@RequestBody Waybill waybill) {
|
||||
return R.status(waybillService.reassign(waybill));
|
||||
}
|
||||
|
||||
@PostMapping("/complete")
|
||||
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
*/
|
||||
package org.springblade.transport.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.springblade.transport.pojo.entity.WaybillEnroutePunch;
|
||||
|
||||
/**
|
||||
* 运单在途打卡 Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface WaybillEnroutePunchMapper extends BaseMapper<WaybillEnroutePunch> {
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
*/
|
||||
package org.springblade.transport.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.springblade.transport.pojo.entity.WaybillNodePunch;
|
||||
|
||||
/**
|
||||
* 运单过程节点打卡 Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface WaybillNodePunchMapper extends BaseMapper<WaybillNodePunch> {
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
import org.springblade.transport.pojo.vo.DriverVehicleCardVO;
|
||||
import org.springblade.transport.pojo.vo.DriverVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 司机端档案服务(小程序)
|
||||
*/
|
||||
public interface IDriverAppService {
|
||||
|
||||
/**
|
||||
* 当前登录用户对应的司机档案(按手机号匹配 blade_transport_driver.mobile)
|
||||
*
|
||||
* @param mobile 小程序可显式传入当前用户手机号;为空时从登录态解析
|
||||
*/
|
||||
DriverVO currentByPhone(String mobile);
|
||||
|
||||
/**
|
||||
* 当前司机绑定的车辆列表(driver.driving_vehicle ↔ vehicle.plate_no)
|
||||
*/
|
||||
List<DriverVehicleCardVO> myVehicles();
|
||||
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import org.springblade.transport.pojo.dto.EnrouteSubmitDTO;
|
||||
import org.springblade.transport.pojo.dto.NodeSubmitDTO;
|
||||
import org.springblade.transport.pojo.vo.DriverEnrouteRecordVO;
|
||||
import org.springblade.transport.pojo.vo.DriverNodePunchVO;
|
||||
import org.springblade.transport.pojo.vo.DriverWaybillCardVO;
|
||||
import org.springblade.transport.pojo.vo.DriverWaybillPreviewVO;
|
||||
import org.springblade.transport.pojo.vo.DriverWaybillTabCountsVO;
|
||||
|
||||
/**
|
||||
* 司机端运单服务(小程序首页 / 列表)
|
||||
*/
|
||||
public interface IDriverWaybillService {
|
||||
|
||||
/**
|
||||
* 当前登录司机的运输中任务(最多一条)
|
||||
*/
|
||||
DriverWaybillCardVO currentTask();
|
||||
|
||||
/**
|
||||
* 当前登录司机的待接运单预览
|
||||
*
|
||||
* @param size 预览条数,默认 2
|
||||
*/
|
||||
DriverWaybillPreviewVO pendingPreview(Integer size);
|
||||
|
||||
/**
|
||||
* 列表 Tab 统计:全部 / 待接单 / 进行中 / 已完成
|
||||
*/
|
||||
DriverWaybillTabCountsVO tabCounts();
|
||||
|
||||
/**
|
||||
* 司机运单分页
|
||||
*
|
||||
* @param current 当前页,从 1 开始
|
||||
* @param size 每页条数
|
||||
* @param status 小程序状态:空=全部,0待接单,1运输中,2已完成
|
||||
* @param keyword 关键字(运单号 / 起终点,可选)
|
||||
*/
|
||||
IPage<DriverWaybillCardVO> page(Integer current, Integer size, Integer status, String keyword);
|
||||
|
||||
/**
|
||||
* 司机运单详情(含是否需要确认接单 requireAccept、在途打卡可见性等)
|
||||
*/
|
||||
DriverWaybillCardVO detail(Long id);
|
||||
|
||||
/**
|
||||
* 司机确认接单:过程配置要求接单且尚未接单时,写入接单记录并将运单改为进行中。
|
||||
*/
|
||||
boolean accept(Long id);
|
||||
|
||||
/**
|
||||
* 司机拒绝接单:过程配置要求接单且尚未接单时,写入拒单记录,运单保持待执行。
|
||||
*/
|
||||
boolean reject(Long id, String reason);
|
||||
|
||||
/**
|
||||
* 提交在途打卡(过程配置在途节点 punch=是,且满足频次/时段)。
|
||||
*/
|
||||
DriverEnrouteRecordVO submitEnroute(EnrouteSubmitDTO dto);
|
||||
|
||||
/**
|
||||
* 提交过程节点打卡(到场/装货/卸货/签收等 punch=是;在途请走 submitEnroute)。
|
||||
*/
|
||||
DriverNodePunchVO submitNode(NodeSubmitDTO dto);
|
||||
|
||||
/**
|
||||
* 司机完成运单:校验归属后改状态为已完成,并走与管理端相同的应收应付明细生成逻辑。
|
||||
*/
|
||||
boolean complete(Long id);
|
||||
|
||||
}
|
||||
+5
@@ -39,6 +39,11 @@ public interface IExceptionDisposalService extends BaseService<ExceptionDisposal
|
||||
|
||||
ExceptionDisposalVO detail(Long id);
|
||||
|
||||
/**
|
||||
* 司机端异常上报:写入 pending 记录,返回新建详情(含 id)
|
||||
*/
|
||||
ExceptionDisposalVO submitReport(ExceptionDisposal request);
|
||||
|
||||
void follow(ExceptionDisposalFollowRequest request);
|
||||
|
||||
void complete(Long id);
|
||||
|
||||
+16
-1
@@ -28,6 +28,7 @@ import org.springblade.transport.excel.WaybillExcel;
|
||||
import org.springblade.transport.pojo.entity.Waybill;
|
||||
import org.springblade.transport.pojo.vo.BusinessRemoveResultVO;
|
||||
import org.springblade.transport.pojo.vo.LoadingManageVO;
|
||||
import org.springblade.transport.pojo.vo.WaybillPunchRecordsVO;
|
||||
import org.springblade.transport.pojo.vo.WaybillVO;
|
||||
import org.springblade.transport.pojo.dto.WaybillMileageRequest;
|
||||
|
||||
@@ -42,6 +43,13 @@ public interface IWaybillService extends BaseService<Waybill> {
|
||||
|
||||
IPage<WaybillVO> selectWaybillPage(IPage<Waybill> page, WaybillVO waybill);
|
||||
WaybillVO detail(Long id);
|
||||
|
||||
/**
|
||||
* 管理端:运单打卡记录 + 司机上传凭证图(label=节点-凭证类型)
|
||||
*/
|
||||
WaybillPunchRecordsVO listPunchRecords(Long waybillId);
|
||||
|
||||
Waybill syncDriverAcceptState(Waybill waybill);
|
||||
boolean submit(Waybill waybill);
|
||||
boolean saveDraft(Waybill waybill);
|
||||
BusinessRemoveResultVO removeWaybill(String ids);
|
||||
@@ -51,8 +59,15 @@ public interface IWaybillService extends BaseService<Waybill> {
|
||||
boolean changeRoute(Waybill waybill);
|
||||
boolean maintainMileage(WaybillMileageRequest request);
|
||||
boolean cancel(Long id);
|
||||
boolean reassign(Long id);
|
||||
boolean reassign(Waybill waybill);
|
||||
boolean complete(Long id);
|
||||
|
||||
/**
|
||||
* 司机端完成运单:跳过管理端部门校验,其余逻辑与 {@link #complete(Long)} 一致
|
||||
* (改状态 + 生成应收应付明细 + 尝试完成配载单)。
|
||||
*/
|
||||
boolean completeWithoutDeptCheck(Long id);
|
||||
|
||||
BusinessRemoveResultVO batchComplete(String ids);
|
||||
LoadingManageVO roadLoading(String ids);
|
||||
|
||||
|
||||
+210
@@ -0,0 +1,210 @@
|
||||
/**
|
||||
* 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 com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springblade.core.log.exception.ServiceException;
|
||||
import org.springblade.core.secure.utils.AuthUtil;
|
||||
import org.springblade.core.tool.api.R;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.system.feign.IUserClient;
|
||||
import org.springblade.system.pojo.entity.User;
|
||||
import org.springblade.transport.pojo.entity.Driver;
|
||||
import org.springblade.transport.pojo.entity.TransportVehicle;
|
||||
import org.springblade.transport.pojo.vo.DriverVehicleCardVO;
|
||||
import org.springblade.transport.pojo.vo.DriverVO;
|
||||
import org.springblade.transport.service.IDriverAppService;
|
||||
import org.springblade.transport.service.IDriverService;
|
||||
import org.springblade.transport.service.ITransportVehicleService;
|
||||
import org.springblade.transport.wrapper.DriverWrapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 司机端档案服务实现
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class DriverAppServiceImpl implements IDriverAppService {
|
||||
|
||||
private final IDriverService driverService;
|
||||
private final ITransportVehicleService transportVehicleService;
|
||||
private final IUserClient userClient;
|
||||
|
||||
@Override
|
||||
public DriverVO currentByPhone(String mobile) {
|
||||
Driver driver = currentDriver(mobile);
|
||||
return driver == null ? null : DriverWrapper.build().entityVO(driver);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DriverVehicleCardVO> myVehicles() {
|
||||
Driver driver = currentDriver(null);
|
||||
if (driver == null || Func.isEmpty(driver.getDrivingVehicle())) {
|
||||
return List.of();
|
||||
}
|
||||
List<String> plates = splitPlates(driver.getDrivingVehicle());
|
||||
if (plates.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
// 精确匹配 + 规范化匹配(兼容库中带间隔符/横线的车牌)
|
||||
List<TransportVehicle> vehicles = transportVehicleService.list(Wrappers.<TransportVehicle>lambdaQuery()
|
||||
.and(w -> {
|
||||
w.in(TransportVehicle::getPlateNo, plates);
|
||||
for (String plate : plates) {
|
||||
w.or().apply(
|
||||
"REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(UPPER(plate_no),'·',''),'•',''),'.',''),'-',''),' ','') = {0}",
|
||||
plate
|
||||
);
|
||||
}
|
||||
})
|
||||
.orderByDesc(TransportVehicle::getUpdateTime));
|
||||
// 去重(精确与规范化可能命中同一条)
|
||||
Map<Long, TransportVehicle> uniq = new LinkedHashMap<>();
|
||||
for (TransportVehicle vehicle : vehicles) {
|
||||
if (vehicle.getId() != null) {
|
||||
uniq.putIfAbsent(vehicle.getId(), vehicle);
|
||||
}
|
||||
}
|
||||
return uniq.values().stream().map(this::toCard).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private Driver currentDriver(String mobileHint) {
|
||||
Long userId = AuthUtil.getUserId();
|
||||
if (userId == null || userId <= 0) {
|
||||
throw new ServiceException("未登录");
|
||||
}
|
||||
String phone = resolvePhone(userId, mobileHint);
|
||||
Driver driver = null;
|
||||
if (Func.isNotEmpty(phone)) {
|
||||
driver = driverService.getOne(Wrappers.<Driver>lambdaQuery()
|
||||
.eq(Driver::getMobile, phone)
|
||||
.orderByDesc(Driver::getUpdateTime)
|
||||
.last("LIMIT 1"));
|
||||
}
|
||||
if (driver == null) {
|
||||
driver = driverService.getOne(Wrappers.<Driver>lambdaQuery()
|
||||
.eq(Driver::getUserId, userId)
|
||||
.last("LIMIT 1"));
|
||||
}
|
||||
return driver;
|
||||
}
|
||||
|
||||
private List<String> splitPlates(String drivingVehicle) {
|
||||
String normalized = drivingVehicle.replace(",", ",").replace("、", ",").replace(";", ",")
|
||||
.replace(";", ",").replace("/", ",").replace("|", ",");
|
||||
Set<String> plates = new LinkedHashSet<>();
|
||||
for (String part : Func.toStrList(",", normalized)) {
|
||||
if (Func.isEmpty(part)) {
|
||||
continue;
|
||||
}
|
||||
String plate = normalizePlate(part);
|
||||
if (Func.isNotEmpty(plate)) {
|
||||
plates.add(plate);
|
||||
}
|
||||
}
|
||||
return new ArrayList<>(plates);
|
||||
}
|
||||
|
||||
/** 车牌规范化:去空格/横线/间隔符并转大写,便于与车辆表关联 */
|
||||
private String normalizePlate(String plateNo) {
|
||||
if (Func.isEmpty(plateNo)) {
|
||||
return "";
|
||||
}
|
||||
return plateNo.trim().replaceAll("[\\s\\-·•..]", "").toUpperCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
private DriverVehicleCardVO toCard(TransportVehicle vehicle) {
|
||||
DriverVehicleCardVO card = new DriverVehicleCardVO();
|
||||
card.setId(vehicle.getId());
|
||||
card.setPlateNo(vehicle.getPlateNo());
|
||||
card.setVehicleType(vehicle.getVehicleType());
|
||||
card.setLicenseFrontUrl(Func.toStr(vehicle.getDrivingLicenseImage(), ""));
|
||||
card.setLicenseBackUrl(firstNotEmpty(vehicle.getDrivingLicenseViceFront(), vehicle.getDrivingLicenseMainBack()));
|
||||
card.setRoadTransportNo(Func.toStr(vehicle.getRoadTransportCertNo(), ""));
|
||||
card.setRoadTransportUrl(Func.toStr(vehicle.getRoadTransportCertImage(), ""));
|
||||
card.setVin("");
|
||||
card.setEngineNo("");
|
||||
if (vehicle.getDrivingLicenseEndDate() != null) {
|
||||
card.setLicenseValidEnd(vehicle.getDrivingLicenseEndDate().toString());
|
||||
} else if (Integer.valueOf(1).equals(vehicle.getDrivingLicenseLongTerm())) {
|
||||
card.setLicenseValidEnd("长期");
|
||||
} else {
|
||||
card.setLicenseValidEnd("");
|
||||
}
|
||||
card.setCertificationStatus(vehicle.getCertificationStatus());
|
||||
return card;
|
||||
}
|
||||
|
||||
private String firstNotEmpty(String first, String second) {
|
||||
if (Func.isNotEmpty(first)) {
|
||||
return first;
|
||||
}
|
||||
return Func.toStr(second, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析用于匹配司机档案的手机号。
|
||||
* 优先级:前端传入且与本人一致的 mobile → JWT account(司机账号多为手机号)→ 用户中心 phone/account
|
||||
*/
|
||||
private String resolvePhone(Long userId, String mobileHint) {
|
||||
String selfPhone = resolveSelfPhone(userId);
|
||||
String hint = Func.isEmpty(mobileHint) ? null : mobileHint.trim();
|
||||
if (Func.isNotEmpty(hint)) {
|
||||
if (Func.isNotEmpty(selfPhone) && !selfPhone.equals(hint)) {
|
||||
throw new ServiceException("只能查询本人司机档案");
|
||||
}
|
||||
return hint;
|
||||
}
|
||||
return selfPhone;
|
||||
}
|
||||
|
||||
private String resolveSelfPhone(Long userId) {
|
||||
String account = AuthUtil.getUserAccount();
|
||||
if (Func.isNotEmpty(account) && account.matches("^1\\d{10}$")) {
|
||||
return account.trim();
|
||||
}
|
||||
R<User> result = userClient.userInfoById(userId);
|
||||
if (result == null || !R.isSuccess(result) || result.getData() == null) {
|
||||
return Func.isEmpty(account) ? null : account.trim();
|
||||
}
|
||||
User user = result.getData();
|
||||
if (Func.isNotEmpty(user.getPhone())) {
|
||||
return user.getPhone().trim();
|
||||
}
|
||||
if (Func.isNotEmpty(user.getAccount()) && user.getAccount().matches("^1\\d{10}$")) {
|
||||
return user.getAccount().trim();
|
||||
}
|
||||
return Func.isEmpty(account) ? null : account.trim();
|
||||
}
|
||||
|
||||
}
|
||||
+1069
File diff suppressed because it is too large
Load Diff
+124
-1
@@ -37,15 +37,22 @@ import org.springblade.transport.mapper.ExceptionDisposalMapper;
|
||||
import org.springblade.transport.pojo.dto.ExceptionDisposalFollowRequest;
|
||||
import org.springblade.transport.pojo.entity.ExceptionDisposal;
|
||||
import org.springblade.transport.pojo.entity.ExceptionDisposalFollowRecord;
|
||||
import org.springblade.transport.pojo.entity.Waybill;
|
||||
import org.springblade.transport.pojo.vo.ExceptionDisposalFollowRecordVO;
|
||||
import org.springblade.transport.pojo.vo.ExceptionDisposalVO;
|
||||
import org.springblade.transport.service.IExceptionDisposalService;
|
||||
import org.springblade.transport.service.IWaybillService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 异常处置服务实现类
|
||||
@@ -62,9 +69,12 @@ public class ExceptionDisposalServiceImpl
|
||||
private static final String STATUS_COMPLETED = "completed";
|
||||
|
||||
private final ExceptionDisposalFollowRecordMapper followRecordMapper;
|
||||
private final IWaybillService waybillService;
|
||||
|
||||
public ExceptionDisposalServiceImpl(ExceptionDisposalFollowRecordMapper followRecordMapper) {
|
||||
public ExceptionDisposalServiceImpl(ExceptionDisposalFollowRecordMapper followRecordMapper,
|
||||
IWaybillService waybillService) {
|
||||
this.followRecordMapper = followRecordMapper;
|
||||
this.waybillService = waybillService;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -82,6 +92,79 @@ public class ExceptionDisposalServiceImpl
|
||||
return vo;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public ExceptionDisposalVO submitReport(ExceptionDisposal request) {
|
||||
if (request == null) {
|
||||
throw new ServiceException("请填写异常信息");
|
||||
}
|
||||
if (Func.isBlank(request.getExceptionType())) {
|
||||
throw new ServiceException("请选择异常类型");
|
||||
}
|
||||
if (Func.isBlank(request.getReportDescription())) {
|
||||
throw new ServiceException("请填写上报说明");
|
||||
}
|
||||
if (request.getReportDescription().length() > 500) {
|
||||
throw new ServiceException("上报说明不能超过500字");
|
||||
}
|
||||
|
||||
ExceptionDisposal disposal = new ExceptionDisposal();
|
||||
disposal.setExceptionType(request.getExceptionType().trim());
|
||||
disposal.setExceptionReason(Func.isBlank(request.getExceptionReason())
|
||||
? null
|
||||
: request.getExceptionReason().trim());
|
||||
disposal.setReportDescription(request.getReportDescription().trim());
|
||||
disposal.setScenePhotos(Func.isBlank(request.getScenePhotos())
|
||||
? null
|
||||
: request.getScenePhotos().trim());
|
||||
disposal.setDisposalStatus(STATUS_PENDING);
|
||||
disposal.setReportTime(LocalDateTime.now());
|
||||
disposal.setReporterId(AuthUtil.getUserId());
|
||||
disposal.setReporterName(UserCache.getUserRealName(AuthUtil.getUserId()));
|
||||
|
||||
fillFromWaybill(disposal, request.getWaybillId(), request.getWaybillNo());
|
||||
|
||||
if (disposal.getWaybillId() == null) {
|
||||
throw new ServiceException("请关联运单后再上报");
|
||||
}
|
||||
|
||||
if (!save(disposal)) {
|
||||
throw new ServiceException("异常上报失败");
|
||||
}
|
||||
return toVO(disposal);
|
||||
}
|
||||
|
||||
/** 按运单补齐运单号 / 车牌 / 项目 / 承运商等展示字段 */
|
||||
private void fillFromWaybill(ExceptionDisposal disposal, Long waybillId, String waybillNo) {
|
||||
Waybill waybill = null;
|
||||
if (waybillId != null) {
|
||||
waybill = waybillService.getById(waybillId);
|
||||
}
|
||||
if (waybill == null && Func.isNotBlank(waybillNo)) {
|
||||
waybill = waybillService.getOne(Wrappers.<Waybill>lambdaQuery()
|
||||
.eq(Waybill::getWaybillNo, waybillNo)
|
||||
.eq(Waybill::getIsDeleted, 0)
|
||||
.last("LIMIT 1"));
|
||||
}
|
||||
if (waybill == null) {
|
||||
if (waybillId != null || Func.isNotBlank(waybillNo)) {
|
||||
throw new ServiceException("关联运单不存在");
|
||||
}
|
||||
return;
|
||||
}
|
||||
disposal.setWaybillId(waybill.getId());
|
||||
disposal.setWaybillNo(waybill.getWaybillNo());
|
||||
disposal.setVehicleNo(waybill.getVehicleNo());
|
||||
disposal.setProjectId(waybill.getProjectId());
|
||||
disposal.setProjectName(waybill.getProjectName());
|
||||
disposal.setCarrierId(waybill.getCarrierId());
|
||||
disposal.setCarrierName(waybill.getCarrierName());
|
||||
String loadingOrMaster = Func.isNotBlank(waybill.getLoadingNo())
|
||||
? waybill.getLoadingNo()
|
||||
: waybill.getMasterNo();
|
||||
disposal.setLoadingOrMasterNo(loadingOrMaster);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void follow(ExceptionDisposalFollowRequest request) {
|
||||
@@ -179,9 +262,49 @@ public class ExceptionDisposalServiceImpl
|
||||
vo.setCreateUserName(UserCache.getUserRealName(entity.getCreateUser()));
|
||||
vo.setUpdateUserName(UserCache.getUserRealName(entity.getUpdateUser()));
|
||||
vo.setDisposalStatusName(statusName(entity.getDisposalStatus()));
|
||||
vo.setScenePhotoList(splitPhotos(entity.getScenePhotos()));
|
||||
fillRouteAndCargo(vo, entity.getWaybillId());
|
||||
return vo;
|
||||
}
|
||||
|
||||
/** 按关联运单补齐详情页路线 / 货物展示字段 */
|
||||
private void fillRouteAndCargo(ExceptionDisposalVO vo, Long waybillId) {
|
||||
if (vo == null || waybillId == null) {
|
||||
return;
|
||||
}
|
||||
Waybill waybill = waybillService.getById(waybillId);
|
||||
if (waybill == null || Objects.equals(waybill.getIsDeleted(), 1)) {
|
||||
return;
|
||||
}
|
||||
Map<String, String> route = new HashMap<>(2);
|
||||
route.put("start", Func.toStr(waybill.getDepartureName(), ""));
|
||||
route.put("end", Func.toStr(waybill.getArrivalName(), ""));
|
||||
vo.setRoute(route);
|
||||
|
||||
Map<String, String> cargo = new HashMap<>(2);
|
||||
cargo.put("name", Func.toStr(waybill.getCargoName(), ""));
|
||||
cargo.put("weight", formatWeight(waybill.getQuantity(), waybill.getQuantityUnit()));
|
||||
vo.setCargo(cargo);
|
||||
}
|
||||
|
||||
private String formatWeight(BigDecimal quantity, String unit) {
|
||||
if (quantity == null) {
|
||||
return "";
|
||||
}
|
||||
String qty = quantity.stripTrailingZeros().toPlainString();
|
||||
return Func.isBlank(unit) ? qty : qty + unit;
|
||||
}
|
||||
|
||||
private List<String> splitPhotos(String scenePhotos) {
|
||||
if (Func.isBlank(scenePhotos)) {
|
||||
return List.of();
|
||||
}
|
||||
return Arrays.stream(scenePhotos.split(","))
|
||||
.map(String::trim)
|
||||
.filter(s -> Func.isNotBlank(s))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private List<ExceptionDisposalFollowRecordVO> followRecords(Long id) {
|
||||
List<ExceptionDisposalFollowRecord> records = followRecordMapper.selectList(Wrappers.<ExceptionDisposalFollowRecord>lambdaQuery()
|
||||
.eq(ExceptionDisposalFollowRecord::getDisposalId, id)
|
||||
|
||||
+77
-2
@@ -33,7 +33,9 @@ import org.springblade.system.cache.UserCache;
|
||||
import org.springblade.system.pojo.entity.Dept;
|
||||
import org.springblade.transport.excel.ProcessConfigExportExcel;
|
||||
import org.springblade.transport.mapper.ProcessConfigMapper;
|
||||
import org.springblade.transport.mapper.WaybillMapper;
|
||||
import org.springblade.transport.pojo.entity.ProcessConfig;
|
||||
import org.springblade.transport.pojo.entity.Waybill;
|
||||
import org.springblade.transport.pojo.vo.BusinessRemoveResultVO;
|
||||
import org.springblade.transport.pojo.vo.ProcessConfigVO;
|
||||
import org.springblade.transport.service.IProcessConfigService;
|
||||
@@ -43,8 +45,11 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 过程配置 服务实现类
|
||||
@@ -54,15 +59,25 @@ import java.util.Objects;
|
||||
@Service
|
||||
public class ProcessConfigServiceImpl extends BaseServiceImpl<ProcessConfigMapper, ProcessConfig> implements IProcessConfigService {
|
||||
|
||||
private final WaybillMapper waybillMapper;
|
||||
|
||||
public ProcessConfigServiceImpl(WaybillMapper waybillMapper) {
|
||||
this.waybillMapper = waybillMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IPage<ProcessConfigVO> selectProcessConfigPage(IPage<ProcessConfig> page, ProcessConfigVO processConfig) {
|
||||
IPage<ProcessConfig> entityPage = page(page, buildQuery(processConfig));
|
||||
return ProcessConfigWrapper.build().pageVO(entityPage);
|
||||
IPage<ProcessConfigVO> voPage = ProcessConfigWrapper.build().pageVO(entityPage);
|
||||
fillHasRelatedWaybill(voPage.getRecords());
|
||||
return voPage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProcessConfigVO detail(Long id) {
|
||||
return ProcessConfigWrapper.build().entityVO(loadEditable(id, false));
|
||||
ProcessConfigVO detail = ProcessConfigWrapper.build().entityVO(loadEditable(id, false));
|
||||
fillHasRelatedWaybill(List.of(detail));
|
||||
return detail;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -71,6 +86,7 @@ public class ProcessConfigServiceImpl extends BaseServiceImpl<ProcessConfigMappe
|
||||
boolean created = Func.isEmpty(processConfig.getId());
|
||||
if (!created) {
|
||||
ProcessConfig oldRecord = loadEditable(processConfig.getId(), true);
|
||||
assertNoRelatedWaybill(oldRecord);
|
||||
processConfig.setConfigCode(oldRecord.getConfigCode());
|
||||
processConfig.setDeptId(oldRecord.getDeptId());
|
||||
processConfig.setDeptName(oldRecord.getDeptName());
|
||||
@@ -176,6 +192,65 @@ public class ProcessConfigServiceImpl extends BaseServiceImpl<ProcessConfigMappe
|
||||
}
|
||||
}
|
||||
|
||||
private void assertNoRelatedWaybill(ProcessConfig processConfig) {
|
||||
if (hasRelatedWaybill(processConfig.getProjectIds())) {
|
||||
throw new ServiceException("该项目已有运单,过程配置不可修改");
|
||||
}
|
||||
}
|
||||
|
||||
private void fillHasRelatedWaybill(List<ProcessConfigVO> records) {
|
||||
if (Func.isEmpty(records)) {
|
||||
return;
|
||||
}
|
||||
Set<Long> allProjectIds = new HashSet<>();
|
||||
for (ProcessConfigVO record : records) {
|
||||
allProjectIds.addAll(parseProjectIds(record.getProjectIds()));
|
||||
}
|
||||
Set<Long> projectIdsWithWaybill = findProjectIdsWithWaybill(allProjectIds);
|
||||
for (ProcessConfigVO record : records) {
|
||||
List<Long> projectIds = parseProjectIds(record.getProjectIds());
|
||||
record.setHasRelatedWaybill(projectIds.stream().anyMatch(projectIdsWithWaybill::contains));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasRelatedWaybill(String projectIds) {
|
||||
return !findProjectIdsWithWaybill(new HashSet<>(parseProjectIds(projectIds))).isEmpty();
|
||||
}
|
||||
|
||||
private Set<Long> findProjectIdsWithWaybill(Set<Long> projectIds) {
|
||||
if (Func.isEmpty(projectIds)) {
|
||||
return Set.of();
|
||||
}
|
||||
Set<Long> result = new HashSet<>();
|
||||
for (Long projectId : projectIds) {
|
||||
if (waybillMapper.selectCount(Wrappers.<Waybill>lambdaQuery()
|
||||
.eq(Waybill::getProjectId, projectId)
|
||||
.eq(Waybill::getIsDeleted, 0)) > 0) {
|
||||
result.add(projectId);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<Long> parseProjectIds(String projectIds) {
|
||||
if (Func.isEmpty(projectIds)) {
|
||||
return List.of();
|
||||
}
|
||||
return Arrays.stream(projectIds.split(","))
|
||||
.map(String::trim)
|
||||
.filter(Func::isNotEmpty)
|
||||
.map(item -> {
|
||||
try {
|
||||
return Long.valueOf(item);
|
||||
} catch (NumberFormatException ex) {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.distinct()
|
||||
.toList();
|
||||
}
|
||||
|
||||
private LambdaQueryWrapper<ProcessConfig> buildQuery(ProcessConfigVO processConfig) {
|
||||
TransportBusinessSupport.validateAllDept(processConfig.getAllDept(), "过程配置");
|
||||
LambdaQueryWrapper<ProcessConfig> queryWrapper = Wrappers.<ProcessConfig>lambdaQuery().eq(ProcessConfig::getIsDeleted, 0);
|
||||
|
||||
+456
-24
@@ -36,15 +36,22 @@ import org.springblade.transport.excel.WaybillExcel;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Arrays;
|
||||
import org.springblade.transport.mapper.WaybillEnroutePunchMapper;
|
||||
import org.springblade.transport.mapper.WaybillMapper;
|
||||
import org.springblade.transport.mapper.WaybillNodePunchMapper;
|
||||
import org.springblade.transport.pojo.entity.LoadingManage;
|
||||
import org.springblade.transport.pojo.entity.ContractManage;
|
||||
import org.springblade.transport.pojo.entity.ProcessConfig;
|
||||
import org.springblade.transport.pojo.entity.ProjectApply;
|
||||
import org.springblade.transport.pojo.entity.Waybill;
|
||||
import org.springblade.transport.pojo.entity.WaybillEnroutePunch;
|
||||
import org.springblade.transport.pojo.entity.WaybillNodePunch;
|
||||
import org.springblade.transport.pojo.dto.WaybillMileageRequest;
|
||||
import org.springblade.transport.pojo.vo.BusinessRemoveResultVO;
|
||||
import org.springblade.transport.pojo.vo.LoadingManageVO;
|
||||
import org.springblade.transport.pojo.vo.WaybillPunchPhotoVO;
|
||||
import org.springblade.transport.pojo.vo.WaybillPunchRecordItemVO;
|
||||
import org.springblade.transport.pojo.vo.WaybillPunchRecordsVO;
|
||||
import org.springblade.transport.pojo.vo.WaybillVO;
|
||||
import org.springblade.transport.service.ILoadingManageService;
|
||||
import org.springblade.transport.service.IProcessConfigService;
|
||||
@@ -53,6 +60,7 @@ import org.springblade.transport.service.IReceivablePayableDetailService;
|
||||
import org.springblade.transport.service.IContractManageService;
|
||||
import org.springblade.transport.service.IWaybillService;
|
||||
import org.springblade.transport.support.TransportBusinessSupport;
|
||||
import org.springblade.transport.support.WaybillProcessSupport;
|
||||
import org.springblade.transport.wrapper.WaybillWrapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@@ -60,8 +68,11 @@ import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
@@ -94,9 +105,16 @@ public class WaybillServiceImpl extends BaseServiceImpl<WaybillMapper, Waybill>
|
||||
@org.springframework.context.annotation.Lazy
|
||||
private IReceivablePayableDetailService receivablePayableDetailService;
|
||||
|
||||
@jakarta.annotation.Resource
|
||||
private WaybillNodePunchMapper waybillNodePunchMapper;
|
||||
|
||||
@jakarta.annotation.Resource
|
||||
private WaybillEnroutePunchMapper waybillEnroutePunchMapper;
|
||||
|
||||
@Override
|
||||
public IPage<WaybillVO> selectWaybillPage(IPage<Waybill> page, WaybillVO waybill) {
|
||||
IPage<Waybill> entityPage = page(page, buildQuery(waybill));
|
||||
entityPage.getRecords().forEach(this::syncDriverAcceptState);
|
||||
IPage<WaybillVO> result = WaybillWrapper.build().pageVO(entityPage);
|
||||
fillMileageMaintainable(result.getRecords());
|
||||
return result;
|
||||
@@ -104,12 +122,327 @@ public class WaybillServiceImpl extends BaseServiceImpl<WaybillMapper, Waybill>
|
||||
|
||||
@Override
|
||||
public WaybillVO detail(Long id) {
|
||||
WaybillVO result = WaybillWrapper.build().entityVO(loadEditable(id, false));
|
||||
WaybillVO result = WaybillWrapper.build().entityVO(syncDriverAcceptState(loadEditable(id, false)));
|
||||
fillCustomerNameFromContract(result);
|
||||
fillMileageMaintainable(List.of(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WaybillPunchRecordsVO listPunchRecords(Long waybillId) {
|
||||
WaybillPunchRecordsVO vo = new WaybillPunchRecordsVO();
|
||||
if (waybillId == null) {
|
||||
return vo;
|
||||
}
|
||||
Waybill waybill = getById(waybillId);
|
||||
if (waybill == null || Objects.equals(waybill.getIsDeleted(), 1)) {
|
||||
throw new ServiceException("运单不存在");
|
||||
}
|
||||
String processJson = resolveLiveProcessJson(waybill);
|
||||
Map<String, List<String>> voucherTypesByNode = buildVoucherTypesIndex(processJson);
|
||||
|
||||
List<WaybillNodePunch> nodePunches = waybillNodePunchMapper.selectList(Wrappers.<WaybillNodePunch>lambdaQuery()
|
||||
.eq(WaybillNodePunch::getWaybillId, waybillId)
|
||||
.orderByAsc(WaybillNodePunch::getPunchTime)
|
||||
.orderByAsc(WaybillNodePunch::getId));
|
||||
Map<String, WaybillNodePunch> latestNodePunch = new LinkedHashMap<>();
|
||||
for (WaybillNodePunch punch : nodePunches) {
|
||||
String code = Func.toStr(punch.getNodeCode(), "").trim();
|
||||
String name = Func.toStr(punch.getNodeName(), "").trim();
|
||||
if (Func.isNotEmpty(code)) {
|
||||
latestNodePunch.put(code.toLowerCase(Locale.ROOT), punch);
|
||||
}
|
||||
if (Func.isNotEmpty(name)) {
|
||||
latestNodePunch.put(name.toLowerCase(Locale.ROOT), punch);
|
||||
}
|
||||
}
|
||||
|
||||
List<WaybillEnroutePunch> enroutePunches = waybillEnroutePunchMapper.selectList(Wrappers.<WaybillEnroutePunch>lambdaQuery()
|
||||
.eq(WaybillEnroutePunch::getWaybillId, waybillId)
|
||||
.orderByAsc(WaybillEnroutePunch::getPunchTime)
|
||||
.orderByAsc(WaybillEnroutePunch::getId));
|
||||
WaybillEnroutePunch latestEnroute = enroutePunches.isEmpty() ? null : enroutePunches.get(enroutePunches.size() - 1);
|
||||
List<String> transitTypes = voucherTypesByNode.getOrDefault("transit", List.of("货物照片"));
|
||||
|
||||
List<WaybillPunchRecordItemVO> records = new ArrayList<>();
|
||||
List<WaybillPunchPhotoVO> uploads = new ArrayList<>();
|
||||
List<Map<String, Object>> processNodes = WaybillProcessSupport.listEnabledProcessNodes(processJson);
|
||||
if (processNodes.isEmpty()) {
|
||||
processNodes = WaybillProcessSupport.listDriverPunchNodes(processJson);
|
||||
}
|
||||
|
||||
for (Map<String, Object> node : processNodes) {
|
||||
String nodeCode = WaybillProcessSupport.nodeKey(node);
|
||||
String nodeName = WaybillProcessSupport.nodeName(node);
|
||||
boolean isTransit = WaybillProcessSupport.isTransitNodePublic(node);
|
||||
WaybillPunchRecordItemVO item = new WaybillPunchRecordItemVO();
|
||||
item.setNodeCode(nodeCode);
|
||||
item.setNodeName(nodeName);
|
||||
item.setType(isTransit ? "enroute" : "node");
|
||||
item.setExceptionFlag(false);
|
||||
item.setPhotos(new ArrayList<>());
|
||||
|
||||
if (isTransit) {
|
||||
if (latestEnroute != null) {
|
||||
item.setId(latestEnroute.getId());
|
||||
item.setPunched(true);
|
||||
item.setStatusName("已打卡");
|
||||
item.setPunchTime(formatPunchTime(latestEnroute.getPunchTime()));
|
||||
item.setAddress(Func.toStr(latestEnroute.getAddress(), ""));
|
||||
item.setLongitude(decimalText(latestEnroute.getLongitude()));
|
||||
item.setLatitude(decimalText(latestEnroute.getLatitude()));
|
||||
if (Func.isNotEmpty(latestEnroute.getPhoto())) {
|
||||
String voucherType = transitTypes.isEmpty() ? "货物照片" : transitTypes.get(0);
|
||||
WaybillPunchPhotoVO photo = buildPhoto(
|
||||
nodeName, voucherType, latestEnroute.getPhoto().trim(), item.getPunchTime());
|
||||
item.getPhotos().add(photo);
|
||||
}
|
||||
// 司机上传:展示全部在途照片(不仅最新一条)
|
||||
for (WaybillEnroutePunch punch : enroutePunches) {
|
||||
if (Func.isEmpty(punch.getPhoto())) {
|
||||
continue;
|
||||
}
|
||||
String voucherType = transitTypes.isEmpty() ? "货物照片" : transitTypes.get(0);
|
||||
uploads.add(buildPhoto(nodeName, voucherType, punch.getPhoto().trim(), formatPunchTime(punch.getPunchTime())));
|
||||
}
|
||||
} else {
|
||||
item.setPunched(false);
|
||||
item.setStatusName("未打卡");
|
||||
item.setPunchTime("");
|
||||
}
|
||||
records.add(item);
|
||||
continue;
|
||||
}
|
||||
|
||||
WaybillNodePunch punched = findLatestNodePunch(latestNodePunch, nodeCode, nodeName);
|
||||
if (punched != null) {
|
||||
List<String> types = resolveNodeVoucherTypes(voucherTypesByNode, punched.getNodeCode(), nodeName);
|
||||
item.setId(punched.getId());
|
||||
item.setPunched(true);
|
||||
item.setStatusName("已打卡");
|
||||
item.setPunchTime(formatPunchTime(punched.getPunchTime()));
|
||||
item.setAddress(Func.toStr(punched.getAddress(), ""));
|
||||
item.setLongitude(decimalText(punched.getLongitude()));
|
||||
item.setLatitude(decimalText(punched.getLatitude()));
|
||||
item.setWeight(punched.getWeight());
|
||||
item.setVolume(punched.getVolume());
|
||||
item.setQuantity(punched.getQuantity());
|
||||
item.setRemark(punched.getRemark());
|
||||
item.setExceptionFlag(Objects.equals(punched.getExceptionFlag(), 1));
|
||||
List<WaybillPunchPhotoVO> photos = decodePunchPhotos(punched.getPhotos(), nodeName, types, item.getPunchTime());
|
||||
item.setPhotos(photos);
|
||||
uploads.addAll(photos);
|
||||
} else {
|
||||
item.setPunched(false);
|
||||
item.setStatusName("未打卡");
|
||||
item.setPunchTime("");
|
||||
}
|
||||
records.add(item);
|
||||
}
|
||||
|
||||
vo.setRecords(records);
|
||||
vo.setDriverUploads(uploads);
|
||||
return vo;
|
||||
}
|
||||
|
||||
private WaybillNodePunch findLatestNodePunch(Map<String, WaybillNodePunch> index, String nodeCode, String nodeName) {
|
||||
if (index == null || index.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
if (Func.isNotEmpty(nodeCode)) {
|
||||
WaybillNodePunch hit = index.get(nodeCode.toLowerCase(Locale.ROOT));
|
||||
if (hit != null) {
|
||||
return hit;
|
||||
}
|
||||
}
|
||||
if (Func.isNotEmpty(nodeName)) {
|
||||
return index.get(nodeName.toLowerCase(Locale.ROOT));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** 动态过程配置优先,回退运单快照 */
|
||||
private String resolveLiveProcessJson(Waybill waybill) {
|
||||
if (waybill.getProjectId() != null) {
|
||||
String projectId = String.valueOf(waybill.getProjectId());
|
||||
String live = processConfigService.list(Wrappers.<ProcessConfig>lambdaQuery()
|
||||
.eq(ProcessConfig::getStatus, 1)
|
||||
.eq(ProcessConfig::getIsDeleted, 0)
|
||||
.like(ProcessConfig::getProjectIds, projectId)
|
||||
.orderByDesc(ProcessConfig::getUpdateTime)
|
||||
.orderByDesc(ProcessConfig::getCreateTime))
|
||||
.stream()
|
||||
.filter(cfg -> containsProjectId(cfg.getProjectIds(), projectId))
|
||||
.map(ProcessConfig::getNodeConfigJson)
|
||||
.filter(Func::isNotEmpty)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
if (Func.isNotEmpty(live)) {
|
||||
return live;
|
||||
}
|
||||
}
|
||||
return waybill.getProcessJson();
|
||||
}
|
||||
|
||||
private Map<String, List<String>> buildVoucherTypesIndex(String processJson) {
|
||||
Map<String, List<String>> map = new LinkedHashMap<>();
|
||||
for (Map<String, Object> node : WaybillProcessSupport.listDriverPunchNodes(processJson)) {
|
||||
String key = WaybillProcessSupport.nodeKey(node);
|
||||
String name = WaybillProcessSupport.nodeName(node);
|
||||
List<String> types = WaybillProcessSupport.nodeStringList(node, "voucherTypes");
|
||||
if (Func.isNotEmpty(key)) {
|
||||
map.put(key.toLowerCase(Locale.ROOT), types);
|
||||
}
|
||||
if (Func.isNotEmpty(name)) {
|
||||
map.put(name.toLowerCase(Locale.ROOT), types);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
private List<String> resolveNodeVoucherTypes(Map<String, List<String>> index, String nodeCode, String nodeName) {
|
||||
if (index == null || index.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
if (Func.isNotEmpty(nodeCode)) {
|
||||
List<String> hit = index.get(nodeCode.toLowerCase(Locale.ROOT));
|
||||
if (hit != null) {
|
||||
return hit;
|
||||
}
|
||||
}
|
||||
if (Func.isNotEmpty(nodeName)) {
|
||||
List<String> hit = index.get(nodeName.toLowerCase(Locale.ROOT));
|
||||
if (hit != null) {
|
||||
return hit;
|
||||
}
|
||||
}
|
||||
return List.of();
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析打卡 photos:
|
||||
* 1) JSON 数组 [{"type":"委托单","url":"..."}]
|
||||
* 2) 逗号分隔 URL,按 voucherTypes 下标回推类型
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private List<WaybillPunchPhotoVO> decodePunchPhotos(
|
||||
String raw,
|
||||
String nodeName,
|
||||
List<String> voucherTypes,
|
||||
String punchTime
|
||||
) {
|
||||
List<WaybillPunchPhotoVO> out = new ArrayList<>();
|
||||
if (Func.isEmpty(raw)) {
|
||||
return out;
|
||||
}
|
||||
String text = raw.trim();
|
||||
if (text.startsWith("[")) {
|
||||
try {
|
||||
List<?> list = JsonUtil.parse(text, List.class);
|
||||
if (list != null) {
|
||||
int i = 0;
|
||||
for (Object item : list) {
|
||||
if (item instanceof Map<?, ?> map) {
|
||||
String url = Func.toStr(map.get("url"), "").trim();
|
||||
if (Func.isEmpty(url)) {
|
||||
continue;
|
||||
}
|
||||
String type = Func.toStr(map.get("type"), "").trim();
|
||||
if (Func.isEmpty(type) && voucherTypes != null && i < voucherTypes.size()) {
|
||||
type = voucherTypes.get(i);
|
||||
}
|
||||
if (Func.isEmpty(type)) {
|
||||
type = "凭证" + (i + 1);
|
||||
}
|
||||
out.add(buildPhoto(nodeName, type, url, punchTime));
|
||||
i++;
|
||||
} else if (item != null) {
|
||||
String url = String.valueOf(item).trim();
|
||||
if (Func.isEmpty(url)) {
|
||||
continue;
|
||||
}
|
||||
String type = (voucherTypes != null && i < voucherTypes.size())
|
||||
? voucherTypes.get(i)
|
||||
: ("凭证" + (i + 1));
|
||||
out.add(buildPhoto(nodeName, type, url, punchTime));
|
||||
i++;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
// fall through to comma split
|
||||
}
|
||||
}
|
||||
String[] urls = text.split(",");
|
||||
for (int i = 0; i < urls.length; i++) {
|
||||
String url = urls[i].trim();
|
||||
if (Func.isEmpty(url)) {
|
||||
continue;
|
||||
}
|
||||
String type = (voucherTypes != null && i < voucherTypes.size())
|
||||
? voucherTypes.get(i)
|
||||
: ("凭证" + (i + 1));
|
||||
out.add(buildPhoto(nodeName, type, url, punchTime));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private WaybillPunchPhotoVO buildPhoto(String nodeName, String voucherType, String url, String punchTime) {
|
||||
WaybillPunchPhotoVO photo = new WaybillPunchPhotoVO();
|
||||
photo.setNodeName(nodeName);
|
||||
photo.setVoucherType(voucherType);
|
||||
photo.setUrl(url);
|
||||
photo.setPunchTime(punchTime);
|
||||
photo.setLabel(nodeName + "-" + voucherType);
|
||||
return photo;
|
||||
}
|
||||
|
||||
private String formatPunchTime(Date time) {
|
||||
if (time == null) {
|
||||
return "";
|
||||
}
|
||||
return org.springblade.core.tool.utils.DateUtil.format(time, org.springblade.core.tool.utils.DateUtil.PATTERN_DATETIME);
|
||||
}
|
||||
|
||||
private String decimalText(BigDecimal value) {
|
||||
return value == null ? "" : value.stripTrailingZeros().toPlainString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Waybill syncDriverAcceptState(Waybill waybill) {
|
||||
if (waybill == null || waybill.getId() == null) {
|
||||
return waybill;
|
||||
}
|
||||
if (WaybillProcessSupport.isTerminalBusinessStatus(waybill.getBusinessStatus())) {
|
||||
return waybill;
|
||||
}
|
||||
// 与司机端一致:优先项目最新过程配置,再回退运单快照
|
||||
String processJson = resolveLiveProcessJson(waybill);
|
||||
if (Func.isNotEmpty(processJson)) {
|
||||
waybill.setProcessJson(processJson);
|
||||
}
|
||||
boolean requireAccept = WaybillProcessSupport.requiresDriverAcceptConfirmation(processJson);
|
||||
String acceptStatus = waybill.getDriverAcceptStatus();
|
||||
if (requireAccept && Func.isEmpty(acceptStatus)) {
|
||||
acceptStatus = WaybillProcessSupport.ACCEPT_PENDING;
|
||||
}
|
||||
String nextStatus = WaybillProcessSupport.normalizeBusinessStatus(
|
||||
waybill.getBusinessStatus(), processJson, acceptStatus);
|
||||
boolean acceptChanged = !Objects.equals(acceptStatus, waybill.getDriverAcceptStatus());
|
||||
boolean statusChanged = !Objects.equals(nextStatus, waybill.getBusinessStatus());
|
||||
if (!acceptChanged && !statusChanged) {
|
||||
return waybill;
|
||||
}
|
||||
waybill.setDriverAcceptStatus(acceptStatus);
|
||||
waybill.setBusinessStatus(nextStatus);
|
||||
update(Wrappers.<Waybill>lambdaUpdate()
|
||||
.set(Waybill::getBusinessStatus, nextStatus)
|
||||
.set(Waybill::getDriverAcceptStatus, acceptStatus)
|
||||
.eq(Waybill::getId, waybill.getId()));
|
||||
return waybill;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean submit(Waybill waybill) {
|
||||
@@ -128,20 +461,31 @@ public class WaybillServiceImpl extends BaseServiceImpl<WaybillMapper, Waybill>
|
||||
|
||||
private void prepareForSave(Waybill waybill) {
|
||||
boolean created = Func.isEmpty(waybill.getId());
|
||||
Waybill oldRecord = null;
|
||||
if (!created) {
|
||||
Waybill oldRecord = loadEditable(waybill.getId(), true);
|
||||
oldRecord = loadEditable(waybill.getId(), true);
|
||||
assertNotLoaded(oldRecord);
|
||||
waybill.setWaybillNo(oldRecord.getWaybillNo());
|
||||
waybill.setLoadingNo(oldRecord.getLoadingNo());
|
||||
waybill.setDeptId(oldRecord.getDeptId());
|
||||
waybill.setDeptName(oldRecord.getDeptName());
|
||||
waybill.setMileageRemark(oldRecord.getMileageRemark());
|
||||
if (Func.isEmpty(waybill.getProcessJson())) {
|
||||
waybill.setProcessJson(oldRecord.getProcessJson());
|
||||
}
|
||||
} else {
|
||||
waybill.setLoadingNo(null);
|
||||
waybill.setMileageRemark(null);
|
||||
fillProjectProcessConfig(waybill);
|
||||
}
|
||||
// 无过程快照时按项目回填;再按接单设置决定 pending / running
|
||||
fillProjectProcessConfig(waybill);
|
||||
prepare(waybill);
|
||||
if (oldRecord != null) {
|
||||
preserveOrResetDriverAccept(waybill, oldRecord);
|
||||
} else {
|
||||
clearDriverAcceptRecord(waybill);
|
||||
}
|
||||
applyDriverAcceptBusinessStatus(waybill);
|
||||
fillCustomerName(waybill);
|
||||
if (created && Func.isEmpty(waybill.getWaybillNo())) {
|
||||
waybill.setWaybillNo(nextCode());
|
||||
@@ -180,21 +524,60 @@ public class WaybillServiceImpl extends BaseServiceImpl<WaybillMapper, Waybill>
|
||||
}
|
||||
|
||||
private void fillProjectProcessConfig(Waybill waybill) {
|
||||
if (Func.isNotEmpty(waybill.getProcessJson()) || Func.isEmpty(waybill.getProjectId())) {
|
||||
String live = resolveLiveProcessJson(waybill);
|
||||
if (Func.isNotEmpty(live)) {
|
||||
waybill.setProcessJson(live);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 无过程配置,或接单设置为「无需确认接单」时,运单直接进入进行中(running);
|
||||
* 需要司机确认接单且尚未接单时保持待执行(pending)。
|
||||
*/
|
||||
private void applyDriverAcceptBusinessStatus(Waybill waybill) {
|
||||
if (WaybillProcessSupport.isTerminalBusinessStatus(waybill.getBusinessStatus())) {
|
||||
return;
|
||||
}
|
||||
String projectId = String.valueOf(waybill.getProjectId());
|
||||
processConfigService.list(Wrappers.<ProcessConfig>lambdaQuery()
|
||||
.eq(ProcessConfig::getStatus, 1)
|
||||
.eq(ProcessConfig::getIsDeleted, 0)
|
||||
.like(ProcessConfig::getProjectIds, projectId)
|
||||
.orderByDesc(ProcessConfig::getCreateTime))
|
||||
.stream()
|
||||
.filter(processConfig -> containsProjectId(processConfig.getProjectIds(), projectId))
|
||||
.map(ProcessConfig::getNodeConfigJson)
|
||||
.filter(Func::isNotEmpty)
|
||||
.findFirst()
|
||||
.ifPresent(waybill::setProcessJson);
|
||||
String processJson = resolveLiveProcessJson(waybill);
|
||||
if (Func.isNotEmpty(processJson)) {
|
||||
waybill.setProcessJson(processJson);
|
||||
}
|
||||
boolean requireAccept = WaybillProcessSupport.requiresDriverAcceptConfirmation(processJson);
|
||||
if (requireAccept && Func.isEmpty(waybill.getDriverAcceptStatus())) {
|
||||
waybill.setDriverAcceptStatus(WaybillProcessSupport.ACCEPT_PENDING);
|
||||
}
|
||||
waybill.setBusinessStatus(WaybillProcessSupport.normalizeBusinessStatus(
|
||||
waybill.getBusinessStatus(), processJson, waybill.getDriverAcceptStatus()));
|
||||
}
|
||||
|
||||
private void preserveOrResetDriverAccept(Waybill waybill, Waybill oldRecord) {
|
||||
if (driverAssignmentChanged(waybill, oldRecord)) {
|
||||
clearDriverAcceptRecord(waybill);
|
||||
return;
|
||||
}
|
||||
waybill.setDriverAcceptStatus(oldRecord.getDriverAcceptStatus());
|
||||
waybill.setDriverAcceptTime(oldRecord.getDriverAcceptTime());
|
||||
waybill.setDriverAcceptDriverId(oldRecord.getDriverAcceptDriverId());
|
||||
waybill.setDriverRejectTime(oldRecord.getDriverRejectTime());
|
||||
waybill.setDriverRejectReason(oldRecord.getDriverRejectReason());
|
||||
}
|
||||
|
||||
private boolean driverAssignmentChanged(Waybill waybill, Waybill oldRecord) {
|
||||
return !Objects.equals(waybill.getDriverId(), oldRecord.getDriverId())
|
||||
|| !Objects.equals(
|
||||
TransportBusinessSupport.trimToNull(waybill.getDriverPhone()),
|
||||
TransportBusinessSupport.trimToNull(oldRecord.getDriverPhone()))
|
||||
|| !Objects.equals(
|
||||
TransportBusinessSupport.trimToNull(waybill.getVehicleNo()),
|
||||
TransportBusinessSupport.trimToNull(oldRecord.getVehicleNo()));
|
||||
}
|
||||
|
||||
private void clearDriverAcceptRecord(Waybill waybill) {
|
||||
waybill.setDriverAcceptStatus(null);
|
||||
waybill.setDriverAcceptTime(null);
|
||||
waybill.setDriverAcceptDriverId(null);
|
||||
waybill.setDriverRejectTime(null);
|
||||
waybill.setDriverRejectReason(null);
|
||||
}
|
||||
|
||||
private boolean containsProjectId(String projectIds, String projectId) {
|
||||
@@ -254,7 +637,10 @@ public class WaybillServiceImpl extends BaseServiceImpl<WaybillMapper, Waybill>
|
||||
excel.setUpdateUserName(UserCache.getUserRealName(record.getUpdateUser()));
|
||||
excel.setCreateTime(record.getCreateTime());
|
||||
excel.setUpdateTime(record.getUpdateTime());
|
||||
excel.setBusinessStatus(WaybillWrapper.businessStatusName(record.getBusinessStatus()));
|
||||
String processJson = resolveLiveProcessJson(record);
|
||||
excel.setBusinessStatus(WaybillWrapper.businessStatusName(
|
||||
WaybillProcessSupport.normalizeBusinessStatus(
|
||||
record.getBusinessStatus(), processJson, record.getDriverAcceptStatus())));
|
||||
return excel;
|
||||
}).toList();
|
||||
}
|
||||
@@ -352,8 +738,10 @@ public class WaybillServiceImpl extends BaseServiceImpl<WaybillMapper, Waybill>
|
||||
target.setRemark(source.getRemark());
|
||||
target.setBusinessStatus("pending");
|
||||
target.setWaybillNo(nextCode());
|
||||
clearDriverAcceptRecord(target);
|
||||
fillProjectProcessConfig(target);
|
||||
prepare(target);
|
||||
applyDriverAcceptBusinessStatus(target);
|
||||
validate(target);
|
||||
save(target);
|
||||
return detail(target.getId());
|
||||
@@ -418,20 +806,61 @@ public class WaybillServiceImpl extends BaseServiceImpl<WaybillMapper, Waybill>
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean reassign(Long id) {
|
||||
Waybill waybill = loadEditable(id, true);
|
||||
assertNotLoaded(waybill);
|
||||
if (!"pending".equals(waybill.getBusinessStatus())) {
|
||||
throw new ServiceException("仅待执行运单允许重新派单");
|
||||
public boolean reassign(Waybill request) {
|
||||
if (request == null || Func.isEmpty(request.getId())) {
|
||||
throw new ServiceException("运单ID不能为空");
|
||||
}
|
||||
waybill.setBusinessStatus("pending");
|
||||
Waybill waybill = loadEditable(request.getId(), true);
|
||||
assertNotLoaded(waybill);
|
||||
if (!"pending".equals(waybill.getBusinessStatus()) && !"running".equals(waybill.getBusinessStatus())) {
|
||||
throw new ServiceException("仅待执行/进行中运单允许重新派单");
|
||||
}
|
||||
String driverName = TransportBusinessSupport.trimToNull(request.getDriverName());
|
||||
String driverPhone = TransportBusinessSupport.trimToNull(request.getDriverPhone());
|
||||
String vehicleNo = TransportBusinessSupport.trimToNull(request.getVehicleNo());
|
||||
TransportBusinessSupport.validateRequired(driverName, "司机不能为空");
|
||||
TransportBusinessSupport.validateRequired(driverPhone, "手机号不能为空");
|
||||
TransportBusinessSupport.validateRequired(vehicleNo, "车牌号不能为空");
|
||||
|
||||
waybill.setDriverId(request.getDriverId());
|
||||
waybill.setDriverName(driverName);
|
||||
waybill.setDriverPhone(driverPhone);
|
||||
waybill.setVehicleNo(vehicleNo);
|
||||
// 同步承运/任务 JSON,避免列表与表单读到旧司机
|
||||
waybill.setCarrierJson(buildImportCarrierJson(waybill));
|
||||
waybill.setTaskInfoJson(buildTaskInfoJson(waybill));
|
||||
|
||||
fillProjectProcessConfig(waybill);
|
||||
clearDriverAcceptRecord(waybill);
|
||||
// 清空后重新进入待接单
|
||||
waybill.setDriverAcceptStatus(WaybillProcessSupport.ACCEPT_PENDING);
|
||||
applyDriverAcceptBusinessStatus(waybill);
|
||||
return updateById(waybill);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean complete(Long id) {
|
||||
Waybill waybill = loadEditable(id, true);
|
||||
return doComplete(loadEditable(id, true));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean completeWithoutDeptCheck(Long id) {
|
||||
if (Func.isEmpty(id)) {
|
||||
throw new ServiceException("运单ID不能为空");
|
||||
}
|
||||
Waybill waybill = getById(id);
|
||||
if (Func.isEmpty(waybill) || Objects.equals(waybill.getIsDeleted(), 1)) {
|
||||
throw new ServiceException("运单不存在");
|
||||
}
|
||||
return doComplete(waybill);
|
||||
}
|
||||
|
||||
/**
|
||||
* 完成运单核心逻辑:状态改为 completed,并检查生成应收应付明细。
|
||||
*/
|
||||
private boolean doComplete(Waybill waybill) {
|
||||
if ("completed".equals(waybill.getBusinessStatus()) || "cancelled".equals(waybill.getBusinessStatus())) {
|
||||
throw new ServiceException("当前状态不允许完成");
|
||||
}
|
||||
@@ -682,6 +1111,8 @@ public class WaybillServiceImpl extends BaseServiceImpl<WaybillMapper, Waybill>
|
||||
waybill.setCarrierJson(TransportBusinessSupport.trimToNull(waybill.getCarrierJson()));
|
||||
waybill.setTaskInfoJson(TransportBusinessSupport.trimToNull(waybill.getTaskInfoJson()));
|
||||
waybill.setProcessJson(TransportBusinessSupport.trimToNull(waybill.getProcessJson()));
|
||||
waybill.setDriverAcceptStatus(TransportBusinessSupport.trimToNull(waybill.getDriverAcceptStatus()));
|
||||
waybill.setDriverRejectReason(TransportBusinessSupport.trimToNull(waybill.getDriverRejectReason()));
|
||||
waybill.setRouteJson(TransportBusinessSupport.trimToNull(waybill.getRouteJson()));
|
||||
waybill.setFreightJson(TransportBusinessSupport.trimToNull(waybill.getFreightJson()));
|
||||
waybill.setAttachmentsJson(TransportBusinessSupport.trimToNull(waybill.getAttachmentsJson()));
|
||||
@@ -694,6 +1125,7 @@ public class WaybillServiceImpl extends BaseServiceImpl<WaybillMapper, Waybill>
|
||||
waybill.setDeptName(dept.getDeptName());
|
||||
}
|
||||
if (waybill.getStatus() == null) { waybill.setStatus(1); }
|
||||
// 默认 pending;最终 pending/running 由 applyDriverAcceptBusinessStatus 按过程配置校正
|
||||
if (Func.isEmpty(waybill.getBusinessStatus())) { waybill.setBusinessStatus("pending"); }
|
||||
}
|
||||
|
||||
|
||||
+472
@@ -0,0 +1,472 @@
|
||||
/**
|
||||
* 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.support;
|
||||
|
||||
import org.springblade.core.tool.jackson.JsonUtil;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 运单过程配置解析(对齐 web 端 waybill-manage / process-config)
|
||||
*/
|
||||
public final class WaybillProcessSupport {
|
||||
|
||||
public static final String STATUS_PENDING = "pending";
|
||||
public static final String STATUS_RUNNING = "running";
|
||||
public static final String ACCEPT_PENDING = "pending";
|
||||
public static final String ACCEPT_ACCEPTED = "accepted";
|
||||
public static final String ACCEPT_REJECTED = "rejected";
|
||||
private static final String CONFIRM_YES = "yes";
|
||||
private static final String CONFIRM_NO_ACCEPT = "no_confirm_accept";
|
||||
private static final String NODE_TRANSIT = "transit";
|
||||
private static final String NODE_TRANSIT_NAME = "在途";
|
||||
private static final DateTimeFormatter HM = DateTimeFormatter.ofPattern("H:mm");
|
||||
private static final DateTimeFormatter HM_PADDED = DateTimeFormatter.ofPattern("HH:mm");
|
||||
|
||||
private WaybillProcessSupport() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 在途打卡判定结果(供司机端「今日在途打卡」面板使用)。
|
||||
*/
|
||||
public record TransitCheckinDecision(
|
||||
boolean punchEnabled,
|
||||
boolean visible,
|
||||
boolean dueToday,
|
||||
boolean doneToday,
|
||||
int frequencyDays,
|
||||
String timeStart,
|
||||
String timeEnd
|
||||
) {
|
||||
public static TransitCheckinDecision hidden() {
|
||||
return new TransitCheckinDecision(false, false, false, false, 1, "00:00", "23:59");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否需要接单确认。
|
||||
* <p>
|
||||
* 存在启用的接单节点,且 confirmMode=yes(是否确认=是)即为需要接单;
|
||||
* 不依赖 confirmDriver(是否勾选司机)——只要尚未接单或已拒绝,业务状态均为待执行。
|
||||
* <p>
|
||||
* 无过程配置 / 接单节点为「无需确认接单」→ false。
|
||||
*/
|
||||
public static boolean requiresDriverAcceptConfirmation(String processJson) {
|
||||
List<Map<String, Object>> nodes = parseProcessNodes(processJson);
|
||||
if (nodes.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
for (Map<String, Object> node : nodes) {
|
||||
if (!isAcceptNode(node) || !isEnabled(node)) {
|
||||
continue;
|
||||
}
|
||||
String confirmMode = stringVal(node.get("confirmMode"));
|
||||
if (CONFIRM_NO_ACCEPT.equals(confirmMode)) {
|
||||
return false;
|
||||
}
|
||||
if (CONFIRM_YES.equals(confirmMode) || Func.isEmpty(confirmMode)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据过程配置决定司机侧初始业务状态:
|
||||
* 需要确认接单 → pending(待执行/待接单);否则 → running(进行中)。
|
||||
*/
|
||||
public static String resolveDriverFacingStatus(String processJson) {
|
||||
return requiresDriverAcceptConfirmation(processJson) ? STATUS_PENDING : STATUS_RUNNING;
|
||||
}
|
||||
|
||||
public static boolean isAccepted(String driverAcceptStatus) {
|
||||
return ACCEPT_ACCEPTED.equalsIgnoreCase(stringVal(driverAcceptStatus));
|
||||
}
|
||||
|
||||
public static boolean isRejected(String driverAcceptStatus) {
|
||||
return ACCEPT_REJECTED.equalsIgnoreCase(stringVal(driverAcceptStatus));
|
||||
}
|
||||
|
||||
public static boolean isTerminalBusinessStatus(String businessStatus) {
|
||||
return "draft".equals(businessStatus)
|
||||
|| "completed".equals(businessStatus)
|
||||
|| "cancelled".equals(businessStatus)
|
||||
|| "waiting_dispatch".equals(businessStatus)
|
||||
|| "dispatching".equals(businessStatus);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校正业务状态。
|
||||
* <p>
|
||||
* 需要接单(接单节点 confirmMode=yes)且尚未接单(未响应 / 已拒绝)→ pending(待执行);
|
||||
* 已接单 → running(进行中);不需要接单 → running(仅当当前为空或 pending 时提升)。
|
||||
* draft / completed / cancelled 等终态或调度中间态不改动。
|
||||
*/
|
||||
public static String normalizeBusinessStatus(String businessStatus, String processJson) {
|
||||
return normalizeBusinessStatus(businessStatus, processJson, null);
|
||||
}
|
||||
|
||||
public static String normalizeBusinessStatus(String businessStatus, String processJson, String driverAcceptStatus) {
|
||||
if (isTerminalBusinessStatus(businessStatus)) {
|
||||
return businessStatus;
|
||||
}
|
||||
if (requiresDriverAcceptConfirmation(processJson)) {
|
||||
return isAccepted(driverAcceptStatus) ? STATUS_RUNNING : STATUS_PENDING;
|
||||
}
|
||||
if (Func.isEmpty(businessStatus) || STATUS_PENDING.equals(businessStatus)) {
|
||||
return STATUS_RUNNING;
|
||||
}
|
||||
return businessStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* 过程配置是否启用在途打卡:在途节点 enabled 且 punch=是。
|
||||
*/
|
||||
public static boolean isTransitPunchEnabled(String processJson) {
|
||||
Map<String, Object> transit = findTransitNode(processJson);
|
||||
return transit != null && isEnabled(transit) && isTruthy(transit.get("punch"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算「今日在途打卡」是否展示 / 是否到期。
|
||||
* <p>
|
||||
* 规则:
|
||||
* <ul>
|
||||
* <li>在途节点未启用或 punch≠是 → 不展示</li>
|
||||
* <li>运单非进行中(running)→ 不展示</li>
|
||||
* <li>频次:每 N 天打卡 1 次;无历史 → 到期;上次打卡日 + N ≤ 今日 → 到期</li>
|
||||
* <li>时段:到期时须落在 timeStart~timeEnd(支持跨午夜);今日已打则仍展示(已打卡态)</li>
|
||||
* </ul>
|
||||
*/
|
||||
public static TransitCheckinDecision evaluateTransitCheckin(
|
||||
String processJson,
|
||||
String businessStatus,
|
||||
Date lastPunchAt,
|
||||
LocalDateTime now
|
||||
) {
|
||||
Map<String, Object> transit = findTransitNode(processJson);
|
||||
if (transit == null || !isEnabled(transit) || !isTruthy(transit.get("punch"))) {
|
||||
return TransitCheckinDecision.hidden();
|
||||
}
|
||||
int frequencyDays = parsePositiveInt(transit.get("frequencyDays"), 1);
|
||||
String timeStart = normalizeHm(stringVal(transit.get("timeStart")), "00:00");
|
||||
String timeEnd = normalizeHm(stringVal(transit.get("timeEnd")), "23:59");
|
||||
if (!STATUS_RUNNING.equals(businessStatus)) {
|
||||
return new TransitCheckinDecision(true, false, false, false, frequencyDays, timeStart, timeEnd);
|
||||
}
|
||||
|
||||
LocalDateTime current = now == null ? LocalDateTime.now() : now;
|
||||
LocalDate today = current.toLocalDate();
|
||||
LocalDate lastDate = toLocalDate(lastPunchAt);
|
||||
boolean doneToday = lastDate != null && lastDate.equals(today);
|
||||
boolean dueToday;
|
||||
if (lastDate == null) {
|
||||
dueToday = true;
|
||||
} else {
|
||||
LocalDate nextDue = lastDate.plusDays(frequencyDays);
|
||||
dueToday = !today.isBefore(nextDue);
|
||||
}
|
||||
boolean inWindow = isWithinTimeWindow(current.toLocalTime(), timeStart, timeEnd);
|
||||
boolean visible = doneToday || (dueToday && inWindow);
|
||||
return new TransitCheckinDecision(true, visible, dueToday && inWindow && !doneToday, doneToday, frequencyDays, timeStart, timeEnd);
|
||||
}
|
||||
|
||||
public static Map<String, Object> findTransitNode(String processJson) {
|
||||
List<Map<String, Object>> nodes = parseProcessNodes(processJson);
|
||||
for (Map<String, Object> node : nodes) {
|
||||
if (isTransitNode(node)) {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 司机端应展示的打卡节点:enabled 且 punch=是,排除接单/回单。
|
||||
*/
|
||||
public static List<Map<String, Object>> listDriverPunchNodes(String processJson) {
|
||||
List<Map<String, Object>> result = new ArrayList<>();
|
||||
for (Map<String, Object> node : parseProcessNodes(processJson)) {
|
||||
if (!isEnabled(node) || !isTruthy(node.get("punch"))) {
|
||||
continue;
|
||||
}
|
||||
if (isAcceptNode(node) || isReturnNode(node)) {
|
||||
continue;
|
||||
}
|
||||
result.add(node);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用中的过程节点(按配置顺序,含接单/回单)。
|
||||
*/
|
||||
public static List<Map<String, Object>> listEnabledProcessNodes(String processJson) {
|
||||
List<Map<String, Object>> result = new ArrayList<>();
|
||||
for (Map<String, Object> node : parseProcessNodes(processJson)) {
|
||||
if (isEnabled(node)) {
|
||||
result.add(node);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前过程节点在启用节点列表中的下标;找不到返回 0(视为从首个开始)。
|
||||
*/
|
||||
public static int indexOfCurrentNode(List<Map<String, Object>> enabledNodes, String currentProcessNode) {
|
||||
if (enabledNodes == null || enabledNodes.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
String current = stringVal(currentProcessNode);
|
||||
if (Func.isEmpty(current)) {
|
||||
// 无当前节点:定位到第一个非接单节点
|
||||
for (int i = 0; i < enabledNodes.size(); i++) {
|
||||
if (!isAcceptNode(enabledNodes.get(i))) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
for (int i = 0; i < enabledNodes.size(); i++) {
|
||||
Map<String, Object> node = enabledNodes.get(i);
|
||||
String key = stringVal(node.get("key"));
|
||||
String name = stringVal(node.get("name"));
|
||||
if (current.equalsIgnoreCase(key) || current.equals(name) || name.contains(current) || current.contains(name)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static boolean isTransitNodePublic(Map<String, Object> node) {
|
||||
return isTransitNode(node);
|
||||
}
|
||||
|
||||
public static boolean nodeNeedLocation(Map<String, Object> node) {
|
||||
return isTruthy(node.get("location"));
|
||||
}
|
||||
|
||||
public static boolean nodeNeedCargo(Map<String, Object> node) {
|
||||
return isTruthy(node.get("uploadCargo"));
|
||||
}
|
||||
|
||||
public static boolean nodeNeedVoucher(Map<String, Object> node) {
|
||||
return isTruthy(node.get("uploadVoucher"));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static List<String> nodeStringList(Map<String, Object> node, String field) {
|
||||
Object raw = node.get(field);
|
||||
if (raw instanceof List<?> list) {
|
||||
List<String> out = new ArrayList<>();
|
||||
for (Object item : list) {
|
||||
if (item != null && Func.isNotEmpty(String.valueOf(item).trim())) {
|
||||
out.add(String.valueOf(item).trim());
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
if (raw instanceof String str && Func.isNotEmpty(str)) {
|
||||
String[] parts = str.split("[,,]");
|
||||
List<String> out = new ArrayList<>();
|
||||
for (String part : parts) {
|
||||
if (Func.isNotEmpty(part.trim())) {
|
||||
out.add(part.trim());
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
public static String nodeKey(Map<String, Object> node) {
|
||||
return stringVal(node.get("key"));
|
||||
}
|
||||
|
||||
public static String nodeName(Map<String, Object> node) {
|
||||
String name = stringVal(node.get("name"));
|
||||
return Func.isEmpty(name) ? nodeKey(node) : name;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static List<Map<String, Object>> parseProcessNodes(String processJson) {
|
||||
if (Func.isEmpty(processJson)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
try {
|
||||
Object parsed = JsonUtil.parse(processJson, Object.class);
|
||||
if (parsed instanceof List<?> list) {
|
||||
return castNodeList(list);
|
||||
}
|
||||
if (parsed instanceof Map<?, ?> map) {
|
||||
Object nodes = map.get("nodes");
|
||||
if (nodes instanceof List<?> list) {
|
||||
return castNodeList(list);
|
||||
}
|
||||
Object nodeConfigJson = map.get("nodeConfigJson");
|
||||
if (nodeConfigJson instanceof String str && Func.isNotEmpty(str)) {
|
||||
return parseProcessNodes(str);
|
||||
}
|
||||
if (nodeConfigJson instanceof List<?> list) {
|
||||
return castNodeList(list);
|
||||
}
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static List<Map<String, Object>> castNodeList(List<?> list) {
|
||||
return list.stream()
|
||||
.filter(Map.class::isInstance)
|
||||
.map(item -> (Map<String, Object>) item)
|
||||
.toList();
|
||||
}
|
||||
|
||||
private static boolean isReturnNode(Map<String, Object> node) {
|
||||
String key = stringVal(node.get("key"));
|
||||
String name = stringVal(node.get("name"));
|
||||
return "return".equals(key) || "回单".equals(name);
|
||||
}
|
||||
|
||||
private static boolean isAcceptNode(Map<String, Object> node) {
|
||||
String key = stringVal(node.get("key"));
|
||||
String name = stringVal(node.get("name"));
|
||||
return "accept".equals(key) || "接单".equals(name);
|
||||
}
|
||||
|
||||
private static boolean isTransitNode(Map<String, Object> node) {
|
||||
String key = stringVal(node.get("key"));
|
||||
String name = stringVal(node.get("name"));
|
||||
String type = stringVal(node.get("type"));
|
||||
return NODE_TRANSIT.equals(key) || NODE_TRANSIT_NAME.equals(name) || NODE_TRANSIT.equals(type);
|
||||
}
|
||||
|
||||
private static boolean isEnabled(Map<String, Object> node) {
|
||||
Object enabled = node.get("enabled");
|
||||
if (enabled == null) {
|
||||
return true;
|
||||
}
|
||||
if (enabled instanceof Boolean bool) {
|
||||
return bool;
|
||||
}
|
||||
String text = String.valueOf(enabled).trim();
|
||||
return !("false".equalsIgnoreCase(text) || "0".equals(text));
|
||||
}
|
||||
|
||||
private static boolean isTruthy(Object value) {
|
||||
if (value == null) {
|
||||
return false;
|
||||
}
|
||||
if (value instanceof Boolean bool) {
|
||||
return bool;
|
||||
}
|
||||
String text = String.valueOf(value).trim();
|
||||
return "true".equalsIgnoreCase(text) || "1".equals(text) || "yes".equalsIgnoreCase(text);
|
||||
}
|
||||
|
||||
private static String stringVal(Object value) {
|
||||
return value == null ? "" : String.valueOf(value).trim();
|
||||
}
|
||||
|
||||
private static int parsePositiveInt(Object value, int defaultVal) {
|
||||
if (value == null) {
|
||||
return defaultVal;
|
||||
}
|
||||
try {
|
||||
int n = Integer.parseInt(String.valueOf(value).trim());
|
||||
return n < 1 ? defaultVal : n;
|
||||
} catch (NumberFormatException ex) {
|
||||
return defaultVal;
|
||||
}
|
||||
}
|
||||
|
||||
private static String normalizeHm(String value, String fallback) {
|
||||
LocalTime t = parseHm(value);
|
||||
if (t == null) {
|
||||
return fallback;
|
||||
}
|
||||
return t.format(HM_PADDED);
|
||||
}
|
||||
|
||||
private static LocalTime parseHm(String value) {
|
||||
if (Func.isEmpty(value)) {
|
||||
return null;
|
||||
}
|
||||
String text = value.trim();
|
||||
try {
|
||||
return LocalTime.parse(text, HM_PADDED);
|
||||
} catch (DateTimeParseException ignored) {
|
||||
// fallthrough
|
||||
}
|
||||
try {
|
||||
return LocalTime.parse(text, HM);
|
||||
} catch (DateTimeParseException ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否在打卡时段内(按 HH:mm 分钟含端点);timeStart > timeEnd 视为跨午夜。
|
||||
*/
|
||||
public static boolean isWithinTimeWindow(LocalTime now, String timeStart, String timeEnd) {
|
||||
LocalTime start = parseHm(timeStart);
|
||||
LocalTime end = parseHm(timeEnd);
|
||||
if (start == null || end == null || now == null) {
|
||||
return true;
|
||||
}
|
||||
int nowM = now.getHour() * 60 + now.getMinute();
|
||||
int startM = start.getHour() * 60 + start.getMinute();
|
||||
int endM = end.getHour() * 60 + end.getMinute();
|
||||
if (startM == endM) {
|
||||
return true;
|
||||
}
|
||||
if (startM < endM) {
|
||||
return nowM >= startM && nowM <= endM;
|
||||
}
|
||||
// 跨午夜:如 22:00-06:00
|
||||
return nowM >= startM || nowM <= endM;
|
||||
}
|
||||
|
||||
private static LocalDate toLocalDate(Date date) {
|
||||
if (date == null) {
|
||||
return null;
|
||||
}
|
||||
return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
|
||||
}
|
||||
|
||||
}
|
||||
+6
-1
@@ -30,6 +30,7 @@ import org.springblade.system.cache.UserCache;
|
||||
import org.springblade.transport.pojo.entity.Waybill;
|
||||
import org.springblade.transport.pojo.vo.WaybillVO;
|
||||
import org.springblade.transport.support.TransportBusinessSupport;
|
||||
import org.springblade.transport.support.WaybillProcessSupport;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@@ -52,7 +53,11 @@ public class WaybillWrapper extends BaseEntityWrapper<Waybill, WaybillVO> {
|
||||
waybillVO.setDataSource(TransportBusinessSupport.normalizeWaybillDataSource(waybill.getDataSource()));
|
||||
Long currentDeptId = Func.firstLong(AuthUtil.getDeptId());
|
||||
waybillVO.setReadonly(currentDeptId != null && !Objects.equals(waybill.getDeptId(), currentDeptId));
|
||||
waybillVO.setBusinessStatusName(businessStatusName(waybill.getBusinessStatus()));
|
||||
String displayStatus = WaybillProcessSupport.normalizeBusinessStatus(
|
||||
waybill.getBusinessStatus(), waybill.getProcessJson(), waybill.getDriverAcceptStatus());
|
||||
waybillVO.setBusinessStatus(displayStatus);
|
||||
waybillVO.setBusinessStatusName(businessStatusName(displayStatus));
|
||||
waybillVO.setRequireAccept(WaybillProcessSupport.requiresDriverAcceptConfirmation(waybill.getProcessJson()));
|
||||
return waybillVO;
|
||||
}
|
||||
|
||||
|
||||
@@ -254,6 +254,11 @@ CREATE TABLE `blade_waybill` (
|
||||
`driver_id` bigint(20) DEFAULT NULL COMMENT '司机ID',
|
||||
`driver_name` varchar(100) DEFAULT NULL COMMENT '司机姓名',
|
||||
`driver_phone` varchar(50) DEFAULT NULL COMMENT '司机手机号',
|
||||
`driver_accept_status` varchar(32) DEFAULT NULL COMMENT '司机接单状态:pending待接单/accepted已接单/rejected已拒绝',
|
||||
`driver_accept_time` datetime DEFAULT NULL COMMENT '司机接单时间',
|
||||
`driver_accept_driver_id` bigint(20) DEFAULT NULL COMMENT '接单司机ID',
|
||||
`driver_reject_time` datetime DEFAULT NULL COMMENT '司机拒绝接单时间',
|
||||
`driver_reject_reason` varchar(200) DEFAULT NULL COMMENT '司机拒绝接单原因',
|
||||
`vehicle_no` varchar(100) DEFAULT NULL COMMENT '车/船/航班/班列号',
|
||||
`captain_name` varchar(20) DEFAULT NULL COMMENT '船长',
|
||||
`cabin_no` varchar(30) DEFAULT NULL COMMENT '舱位',
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
ALTER TABLE `blade_waybill`
|
||||
ADD COLUMN `driver_accept_status` varchar(32) DEFAULT NULL COMMENT '司机接单状态:pending待接单/accepted已接单/rejected已拒绝' AFTER `driver_phone`,
|
||||
ADD COLUMN `driver_accept_time` datetime DEFAULT NULL COMMENT '司机接单时间' AFTER `driver_accept_status`,
|
||||
ADD COLUMN `driver_accept_driver_id` bigint(20) DEFAULT NULL COMMENT '接单司机ID' AFTER `driver_accept_time`,
|
||||
ADD COLUMN `driver_reject_time` datetime DEFAULT NULL COMMENT '司机拒绝接单时间' AFTER `driver_accept_driver_id`,
|
||||
ADD COLUMN `driver_reject_reason` varchar(200) DEFAULT NULL COMMENT '司机拒绝接单原因' AFTER `driver_reject_time`;
|
||||
@@ -0,0 +1,23 @@
|
||||
-- 运单在途打卡记录(过程配置 transit 节点 punch=是)
|
||||
CREATE TABLE IF NOT EXISTS `blade_waybill_enroute_punch` (
|
||||
`id` bigint(20) NOT NULL COMMENT '主键',
|
||||
`tenant_id` varchar(12) DEFAULT '000000' COMMENT '租户ID',
|
||||
`waybill_id` bigint(20) NOT NULL COMMENT '运单ID',
|
||||
`waybill_no` varchar(64) DEFAULT NULL COMMENT '运单号',
|
||||
`driver_id` bigint(20) DEFAULT NULL COMMENT '打卡司机ID',
|
||||
`punch_time` datetime NOT NULL COMMENT '打卡时间',
|
||||
`longitude` decimal(12, 8) DEFAULT NULL COMMENT '经度',
|
||||
`latitude` decimal(12, 8) DEFAULT NULL COMMENT '纬度',
|
||||
`address` varchar(500) DEFAULT NULL COMMENT '打卡地址',
|
||||
`photo` varchar(1000) DEFAULT NULL COMMENT '货物照片URL',
|
||||
`create_user` bigint(20) DEFAULT NULL COMMENT '创建人',
|
||||
`create_dept` bigint(20) DEFAULT NULL COMMENT '创建部门',
|
||||
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
|
||||
`update_user` bigint(20) DEFAULT NULL COMMENT '修改人',
|
||||
`update_time` datetime DEFAULT NULL COMMENT '修改时间',
|
||||
`status` int(11) DEFAULT '1' COMMENT '状态',
|
||||
`is_deleted` int(11) DEFAULT '0' COMMENT '是否已删除',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
KEY `idx_enroute_punch_waybill` (`waybill_id`, `punch_time`) USING BTREE,
|
||||
KEY `idx_enroute_punch_driver` (`driver_id`) USING BTREE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='运单在途打卡记录';
|
||||
@@ -0,0 +1,30 @@
|
||||
-- 运单过程节点打卡记录(到场/装货/发货/到货/卸货/签收等 punch=是,不含在途)
|
||||
CREATE TABLE IF NOT EXISTS `blade_waybill_node_punch` (
|
||||
`id` bigint(20) NOT NULL COMMENT '主键',
|
||||
`tenant_id` varchar(12) DEFAULT '000000' COMMENT '租户ID',
|
||||
`waybill_id` bigint(20) NOT NULL COMMENT '运单ID',
|
||||
`waybill_no` varchar(64) DEFAULT NULL COMMENT '运单号',
|
||||
`driver_id` bigint(20) DEFAULT NULL COMMENT '打卡司机ID',
|
||||
`node_code` varchar(64) NOT NULL COMMENT '过程节点 key',
|
||||
`node_name` varchar(64) DEFAULT NULL COMMENT '过程节点名称',
|
||||
`punch_time` datetime NOT NULL COMMENT '打卡时间',
|
||||
`longitude` decimal(12, 8) DEFAULT NULL COMMENT '经度',
|
||||
`latitude` decimal(12, 8) DEFAULT NULL COMMENT '纬度',
|
||||
`address` varchar(500) DEFAULT NULL COMMENT '打卡地址',
|
||||
`photos` varchar(2000) DEFAULT NULL COMMENT '凭证照片URL,多张逗号分隔',
|
||||
`weight` varchar(32) DEFAULT NULL COMMENT '重量(吨)',
|
||||
`volume` varchar(32) DEFAULT NULL COMMENT '体积(方)',
|
||||
`quantity` varchar(32) DEFAULT NULL COMMENT '数量(件)',
|
||||
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
|
||||
`exception_flag` int(11) DEFAULT '0' COMMENT '是否异常:0否 1是',
|
||||
`create_user` bigint(20) DEFAULT NULL COMMENT '创建人',
|
||||
`create_dept` bigint(20) DEFAULT NULL COMMENT '创建部门',
|
||||
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
|
||||
`update_user` bigint(20) DEFAULT NULL COMMENT '修改人',
|
||||
`update_time` datetime DEFAULT NULL COMMENT '修改时间',
|
||||
`status` int(11) DEFAULT '1' COMMENT '状态',
|
||||
`is_deleted` int(11) DEFAULT '0' COMMENT '是否已删除',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
KEY `idx_node_punch_waybill` (`waybill_id`, `node_code`, `punch_time`) USING BTREE,
|
||||
KEY `idx_node_punch_driver` (`driver_id`) USING BTREE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='运单过程节点打卡记录';
|
||||
Reference in New Issue
Block a user