1、完善发货模板

2、完善运输计划
3、完善运单管理
This commit is contained in:
2026-08-05 15:51:44 +08:00
parent d47d6b4b23
commit 58726a675e
18 changed files with 544 additions and 35 deletions

View File

@@ -0,0 +1,24 @@
package org.springblade.transport.pojo.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springblade.transport.pojo.entity.Waybill;
import java.util.List;
/**
* 运输计划调度请求。
*/
@Data
@Schema(description = "运输计划调度请求")
public class TransportPlanDispatchRequest {
@Schema(description = "运输计划主键")
private Long id;
@Schema(description = "调度方式draft 或 submit")
private String mode;
@Schema(description = "本次生成的运单")
private List<Waybill> waybills;
}

View File

@@ -31,7 +31,6 @@ import org.springblade.core.tenant.mp.TenantEntity;
import java.io.Serial; import java.io.Serial;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime;
/** /**
* 运单管理实体类 * 运单管理实体类
@@ -152,11 +151,11 @@ public class Waybill extends TenantEntity {
@Schema(description = "里程") @Schema(description = "里程")
private BigDecimal mileage; private BigDecimal mileage;
@Schema(description = "预计发货时间") @Schema(description = "预计发货日期")
private LocalDateTime estimatedStartTime; private LocalDate estimatedStartTime;
@Schema(description = "预计完成时间") @Schema(description = "预计完成日期")
private LocalDateTime estimatedEndTime; private LocalDate estimatedEndTime;
@Schema(description = "单价") @Schema(description = "单价")
private BigDecimal unitPrice; private BigDecimal unitPrice;
@@ -200,6 +199,9 @@ public class Waybill extends TenantEntity {
@Schema(description = "运单批次号") @Schema(description = "运单批次号")
private String batchNo; private String batchNo;
@Schema(description = "导入批次ID")
private Long importBatchId;
@Schema(description = "关联单号") @Schema(description = "关联单号")
private String relationNo; private String relationNo;

View File

@@ -72,6 +72,9 @@
<if test="portTerminal.category != null and portTerminal.category != ''"> <if test="portTerminal.category != null and portTerminal.category != ''">
AND pt.category = #{portTerminal.category} AND pt.category = #{portTerminal.category}
</if> </if>
<if test="portTerminal.parentId != null">
AND pt.parent_id = #{portTerminal.parentId}
</if>
<if test="portTerminal.dataSource != null and portTerminal.dataSource != ''"> <if test="portTerminal.dataSource != null and portTerminal.dataSource != ''">
<choose> <choose>
<when test="portTerminal.dataSource == '初始化导入'"> <when test="portTerminal.dataSource == '初始化导入'">

View File

@@ -80,22 +80,29 @@ public class ShippingTemplateController extends BladeController {
return R.data(shippingTemplateService.selectShippingTemplatePage(Condition.getPage(query), shippingTemplate)); return R.data(shippingTemplateService.selectShippingTemplatePage(Condition.getPage(query), shippingTemplate));
} }
@PostMapping("/submit") @GetMapping("/next-code")
@ApiOperationSupport(order = 3) @ApiOperationSupport(order = 3)
@Operation(summary = "下一个模板编号")
public R<String> nextCode() {
return R.data(shippingTemplateService.nextTemplateCode());
}
@PostMapping("/submit")
@ApiOperationSupport(order = 4)
@Operation(summary = "新增或修改", description = "传入shippingTemplate") @Operation(summary = "新增或修改", description = "传入shippingTemplate")
public R submit(@RequestBody ShippingTemplate shippingTemplate) { public R submit(@RequestBody ShippingTemplate shippingTemplate) {
return R.status(shippingTemplateService.submit(shippingTemplate)); return R.status(shippingTemplateService.submit(shippingTemplate));
} }
@PostMapping("/remove") @PostMapping("/remove")
@ApiOperationSupport(order = 4) @ApiOperationSupport(order = 5)
@Operation(summary = "逻辑删除", description = "传入ids") @Operation(summary = "逻辑删除", description = "传入ids")
public R<BusinessRemoveResultVO> remove(@Parameter(description = "主键集合", required = true) @RequestParam String ids) { public R<BusinessRemoveResultVO> remove(@Parameter(description = "主键集合", required = true) @RequestParam String ids) {
return R.data(shippingTemplateService.removeShippingTemplate(ids)); return R.data(shippingTemplateService.removeShippingTemplate(ids));
} }
@GetMapping("/export-shipping-template") @GetMapping("/export-shipping-template")
@ApiOperationSupport(order = 5) @ApiOperationSupport(order = 6)
@Operation(summary = "导出发货模板") @Operation(summary = "导出发货模板")
public void exportShippingTemplate(ShippingTemplateVO shippingTemplate, @RequestParam(required = false) String ids, HttpServletResponse response) { public void exportShippingTemplate(ShippingTemplateVO shippingTemplate, @RequestParam(required = false) String ids, HttpServletResponse response) {
List<ShippingTemplateExcel> list = shippingTemplateService.exportShippingTemplate(shippingTemplate, ids); List<ShippingTemplateExcel> list = shippingTemplateService.exportShippingTemplate(shippingTemplate, ids);
@@ -103,7 +110,7 @@ public class ShippingTemplateController extends BladeController {
} }
@PostMapping("/copy") @PostMapping("/copy")
@ApiOperationSupport(order = 6) @ApiOperationSupport(order = 7)
@Operation(summary = "复制", description = "传入id") @Operation(summary = "复制", description = "传入id")
public R<ShippingTemplateVO> copy(@Parameter(description = "主键", required = true) @RequestParam Long id) { public R<ShippingTemplateVO> copy(@Parameter(description = "主键", required = true) @RequestParam Long id) {
return R.data(shippingTemplateService.copy(id)); return R.data(shippingTemplateService.copy(id));

View File

@@ -37,7 +37,10 @@ import org.springblade.core.secure.annotation.PreAuth;
import org.springblade.core.tool.api.R; import org.springblade.core.tool.api.R;
import org.springblade.core.tool.utils.DateUtil; import org.springblade.core.tool.utils.DateUtil;
import org.springblade.core.tool.utils.Func; import org.springblade.core.tool.utils.Func;
import org.springblade.common.excel.ImportFailureExcelUtil;
import org.springblade.transport.excel.TransportPlanExcel; import org.springblade.transport.excel.TransportPlanExcel;
import org.springblade.transport.excel.TransportPlanImportExcel;
import org.springblade.transport.pojo.dto.TransportPlanDispatchRequest;
import org.springblade.transport.pojo.entity.TransportPlan; import org.springblade.transport.pojo.entity.TransportPlan;
import org.springblade.transport.pojo.vo.BusinessRemoveResultVO; import org.springblade.transport.pojo.vo.BusinessRemoveResultVO;
import org.springblade.transport.pojo.vo.TransportPlanVO; import org.springblade.transport.pojo.vo.TransportPlanVO;
@@ -48,8 +51,10 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.util.ArrayList; import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List; import java.util.List;
/** /**
@@ -102,22 +107,63 @@ public class TransportPlanController extends BladeController {
ExcelUtil.export(response, "运输计划" + DateUtil.time(), "运输计划", list, TransportPlanExcel.class); ExcelUtil.export(response, "运输计划" + DateUtil.time(), "运输计划", list, TransportPlanExcel.class);
} }
@PostMapping("/copy") @GetMapping("/export-template")
@ApiOperationSupport(order = 6) @ApiOperationSupport(order = 6)
@Operation(summary = "导出运输计划模板")
public void exportTemplate(HttpServletResponse response) {
TransportPlanImportExcel template = new TransportPlanImportExcel();
template.setPlanName("示例运输计划");
template.setTransportType("公路运输");
template.setPlanStartDate(LocalDate.now().toString());
template.setPlanEndDate(LocalDate.now().plusDays(1).toString());
template.setCargoName("示例货物");
template.setCargoType("示例货物类型");
template.setQuantity(new BigDecimal("10"));
template.setQuantityUnit("");
template.setPackageType("袋装");
template.setDepartureAddress("示例发货地址");
template.setArrivalAddress("示例收货地址");
ExcelUtil.export(response, "运输计划模板", "运输计划导入模板", List.of(template), TransportPlanImportExcel.class);
}
@PostMapping("/import-transport-plan")
@ApiOperationSupport(order = 7)
@Operation(summary = "导入运输计划", description = "传入 Excel、项目和客户合同")
public R importTransportPlan(MultipartFile file, @RequestParam Long projectId, @RequestParam String projectName,
@RequestParam Long contractId, @RequestParam String contractName, @RequestParam String customerName,
HttpServletResponse response) {
List<TransportPlanImportExcel> failureList = transportPlanService.importTransportPlan(
ExcelUtil.read(file, TransportPlanImportExcel.class), projectId, projectName, contractId, contractName, customerName);
if (Func.isNotEmpty(failureList)) {
ImportFailureExcelUtil.export(response, "运输计划导入失败明细" + DateUtil.time(), "导入失败明细", failureList, TransportPlanImportExcel.class);
return null;
}
return R.success("导入数据成功");
}
@PostMapping("/copy")
@ApiOperationSupport(order = 8)
@Operation(summary = "复制", description = "传入id") @Operation(summary = "复制", description = "传入id")
public R<TransportPlanVO> copy(@Parameter(description = "主键", required = true) @RequestParam Long id) { public R<TransportPlanVO> copy(@Parameter(description = "主键", required = true) @RequestParam Long id) {
return R.data(transportPlanService.copy(id)); return R.data(transportPlanService.copy(id));
} }
@PostMapping("/dispatch")
@ApiOperationSupport(order = 9)
@Operation(summary = "计划调度并生成运单")
public R<Integer> dispatch(@RequestBody TransportPlanDispatchRequest request) {
return R.data(transportPlanService.dispatch(request));
}
@PostMapping("/cancel") @PostMapping("/cancel")
@ApiOperationSupport(order = 7) @ApiOperationSupport(order = 10)
@Operation(summary = "取消", description = "传入id") @Operation(summary = "取消", description = "传入id")
public R cancel(@Parameter(description = "主键", required = true) @RequestParam Long id) { public R cancel(@Parameter(description = "主键", required = true) @RequestParam Long id) {
return R.status(transportPlanService.cancel(id)); return R.status(transportPlanService.cancel(id));
} }
@PostMapping("/complete") @PostMapping("/complete")
@ApiOperationSupport(order = 8) @ApiOperationSupport(order = 11)
@Operation(summary = "完成", description = "传入id") @Operation(summary = "完成", description = "传入id")
public R complete(@Parameter(description = "主键", required = true) @RequestParam Long id) { public R complete(@Parameter(description = "主键", required = true) @RequestParam Long id) {
return R.status(transportPlanService.complete(id)); return R.status(transportPlanService.complete(id));

View File

@@ -39,9 +39,18 @@ import org.springblade.core.tool.api.R;
import org.springblade.core.tool.utils.DateUtil; import org.springblade.core.tool.utils.DateUtil;
import org.springblade.core.tool.utils.Func; import org.springblade.core.tool.utils.Func;
import org.springblade.transport.excel.WaybillExcel; import org.springblade.transport.excel.WaybillExcel;
import org.springblade.transport.excel.WaybillImportBatchExcel;
import org.springblade.transport.pojo.entity.ContractManage;
import org.springblade.transport.pojo.entity.CustomerArchive;
import org.springblade.transport.pojo.entity.ProjectApply;
import org.springblade.transport.pojo.entity.TransportPlan;
import org.springblade.transport.pojo.entity.Waybill; import org.springblade.transport.pojo.entity.Waybill;
import org.springblade.transport.pojo.vo.BusinessRemoveResultVO; import org.springblade.transport.pojo.vo.BusinessRemoveResultVO;
import org.springblade.transport.pojo.vo.WaybillVO; import org.springblade.transport.pojo.vo.WaybillVO;
import org.springblade.transport.service.IContractManageService;
import org.springblade.transport.service.ICustomerArchiveService;
import org.springblade.transport.service.IProjectApplyService;
import org.springblade.transport.service.ITransportPlanService;
import org.springblade.transport.service.IWaybillService; import org.springblade.transport.service.IWaybillService;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@@ -52,7 +61,9 @@ import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* 运单管理 控制器 * 运单管理 控制器
@@ -67,6 +78,10 @@ import java.util.List;
public class WaybillController extends BladeController { public class WaybillController extends BladeController {
private final IWaybillService waybillService; private final IWaybillService waybillService;
private final IProjectApplyService projectApplyService;
private final IContractManageService contractManageService;
private final ICustomerArchiveService customerArchiveService;
private final ITransportPlanService transportPlanService;
@GetMapping("/detail") @GetMapping("/detail")
@ApiOperationSupport(order = 1) @ApiOperationSupport(order = 1)
@@ -82,22 +97,46 @@ public class WaybillController extends BladeController {
return R.data(waybillService.selectWaybillPage(Condition.getPage(query), waybill)); return R.data(waybillService.selectWaybillPage(Condition.getPage(query), waybill));
} }
@PostMapping("/submit") @GetMapping("/import-batch/options")
@ApiOperationSupport(order = 3) @ApiOperationSupport(order = 3)
@Operation(summary = "运单批量导入选项")
public R<Map<String, List<Map<String, Object>>>> importBatchOptions() {
Map<String, List<Map<String, Object>>> options = new LinkedHashMap<>();
options.put("projects", projectApplyService.list().stream()
.filter(project -> "approved".equals(project.getApprovalStatus()))
.map(project -> option(project.getId(), "projectName", project.getProjectName(), "customerName", project.getCustomerNames()))
.toList());
options.put("contracts", contractManageService.list().stream()
.filter(contract -> "approved".equals(contract.getApprovalStatus()))
.map(contract -> option(contract.getId(), "contractName", contract.getContractName(), "projectId", contract.getProjectId()))
.toList());
options.put("carriers", customerArchiveService.list().stream()
.filter(customer -> "承运商".equals(customer.getCustomerType()) && Integer.valueOf(1).equals(customer.getStatus()))
.map(customer -> option(customer.getId(), "name", customer.getFullName(), null, null))
.toList());
options.put("creators", new ArrayList<>());
options.put("plans", transportPlanService.list().stream()
.map(plan -> option(plan.getId(), "planName", plan.getPlanName(), "projectId", plan.getProjectId()))
.toList());
return R.data(options);
}
@PostMapping("/submit")
@ApiOperationSupport(order = 4)
@Operation(summary = "新增或修改", description = "传入waybill") @Operation(summary = "新增或修改", description = "传入waybill")
public R submit(@RequestBody Waybill waybill) { public R submit(@RequestBody Waybill waybill) {
return R.status(waybillService.submit(waybill)); return R.status(waybillService.submit(waybill));
} }
@PostMapping("/remove") @PostMapping("/remove")
@ApiOperationSupport(order = 4) @ApiOperationSupport(order = 5)
@Operation(summary = "逻辑删除", description = "传入ids") @Operation(summary = "逻辑删除", description = "传入ids")
public R<BusinessRemoveResultVO> remove(@Parameter(description = "主键集合", required = true) @RequestParam String ids) { public R<BusinessRemoveResultVO> remove(@Parameter(description = "主键集合", required = true) @RequestParam String ids) {
return R.data(waybillService.removeWaybill(ids)); return R.data(waybillService.removeWaybill(ids));
} }
@GetMapping("/export-waybill-manage") @GetMapping("/export-waybill-manage")
@ApiOperationSupport(order = 5) @ApiOperationSupport(order = 6)
@Operation(summary = "导出运单管理") @Operation(summary = "导出运单管理")
public void exportWaybill(WaybillVO waybill, @RequestParam(required = false) String ids, HttpServletResponse response) { public void exportWaybill(WaybillVO waybill, @RequestParam(required = false) String ids, HttpServletResponse response) {
List<WaybillExcel> list = waybillService.exportWaybill(waybill, ids); List<WaybillExcel> list = waybillService.exportWaybill(waybill, ids);
@@ -105,7 +144,7 @@ public class WaybillController extends BladeController {
} }
@PostMapping("/import-waybill-manage") @PostMapping("/import-waybill-manage")
@ApiOperationSupport(order = 6) @ApiOperationSupport(order = 7)
@Operation(summary = "导入运单管理", description = "传入excel") @Operation(summary = "导入运单管理", description = "传入excel")
public R importWaybill(MultipartFile file, HttpServletResponse response) { public R importWaybill(MultipartFile file, HttpServletResponse response) {
List<WaybillExcel> failureList = waybillService.importWaybill(ExcelUtil.read(file, WaybillExcel.class)); List<WaybillExcel> failureList = waybillService.importWaybill(ExcelUtil.read(file, WaybillExcel.class));
@@ -117,45 +156,62 @@ public class WaybillController extends BladeController {
} }
@GetMapping("/export-template") @GetMapping("/export-template")
@ApiOperationSupport(order = 7) @ApiOperationSupport(order = 8)
@Operation(summary = "导出模板") @Operation(summary = "导出模板")
public void exportTemplate(HttpServletResponse response) { public void exportTemplate(HttpServletResponse response) {
ExcelUtil.export(response, "运单管理模板", "运单管理导入模板", new ArrayList<WaybillExcel>(), WaybillExcel.class); ExcelUtil.export(response, "运单管理模板", "运单管理导入模板", new ArrayList<WaybillExcel>(), WaybillExcel.class);
} }
@GetMapping("/import-batch/export-template")
@ApiOperationSupport(order = 9)
@Operation(summary = "导出运单批量导入模板")
public void exportImportBatchTemplate(HttpServletResponse response) {
ExcelUtil.export(response, "运单批量导入模板", "运单批量导入模板", new ArrayList<WaybillImportBatchExcel>(), WaybillImportBatchExcel.class);
}
@PostMapping("/copy") @PostMapping("/copy")
@ApiOperationSupport(order = 8) @ApiOperationSupport(order = 10)
@Operation(summary = "复制", description = "传入id") @Operation(summary = "复制", description = "传入id")
public R<WaybillVO> copy(@Parameter(description = "主键", required = true) @RequestParam Long id) { public R<WaybillVO> copy(@Parameter(description = "主键", required = true) @RequestParam Long id) {
return R.data(waybillService.copy(id)); return R.data(waybillService.copy(id));
} }
@PostMapping("/cancel") @PostMapping("/cancel")
@ApiOperationSupport(order = 9) @ApiOperationSupport(order = 11)
@Operation(summary = "取消", description = "传入id") @Operation(summary = "取消", description = "传入id")
public R cancel(@Parameter(description = "主键", required = true) @RequestParam Long id) { public R cancel(@Parameter(description = "主键", required = true) @RequestParam Long id) {
return R.status(waybillService.cancel(id)); return R.status(waybillService.cancel(id));
} }
@PostMapping("/reassign") @PostMapping("/reassign")
@ApiOperationSupport(order = 10) @ApiOperationSupport(order = 12)
@Operation(summary = "重新派单", description = "传入id") @Operation(summary = "重新派单", description = "传入id")
public R reassign(@Parameter(description = "主键", required = true) @RequestParam Long id) { public R reassign(@Parameter(description = "主键", required = true) @RequestParam Long id) {
return R.status(waybillService.reassign(id)); return R.status(waybillService.reassign(id));
} }
@PostMapping("/complete") @PostMapping("/complete")
@ApiOperationSupport(order = 11) @ApiOperationSupport(order = 13)
@Operation(summary = "完成", description = "传入id") @Operation(summary = "完成", description = "传入id")
public R complete(@Parameter(description = "主键", required = true) @RequestParam Long id) { public R complete(@Parameter(description = "主键", required = true) @RequestParam Long id) {
return R.status(waybillService.complete(id)); return R.status(waybillService.complete(id));
} }
@PostMapping("/batch-complete") @PostMapping("/batch-complete")
@ApiOperationSupport(order = 12) @ApiOperationSupport(order = 14)
@Operation(summary = "批量完成", description = "传入ids") @Operation(summary = "批量完成", description = "传入ids")
public R<BusinessRemoveResultVO> batchComplete(@Parameter(description = "主键集合", required = true) @RequestParam String ids) { public R<BusinessRemoveResultVO> batchComplete(@Parameter(description = "主键集合", required = true) @RequestParam String ids) {
return R.data(waybillService.batchComplete(ids)); return R.data(waybillService.batchComplete(ids));
} }
private Map<String, Object> option(Long id, String nameKey, String name, String extraKey, Object extraValue) {
Map<String, Object> option = new LinkedHashMap<>();
option.put("id", id);
option.put(nameKey, name);
if (Func.isNotEmpty(extraKey)) {
option.put(extraKey, extraValue);
}
return option;
}
} }

View File

@@ -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>
* 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.excel;
import cn.idev.excel.annotation.ExcelIgnore;
import cn.idev.excel.annotation.ExcelProperty;
import cn.idev.excel.annotation.write.style.ColumnWidth;
import cn.idev.excel.annotation.write.style.ContentRowHeight;
import cn.idev.excel.annotation.write.style.HeadRowHeight;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
/**
* 运输计划导入 Excel
*
* @author Chill
*/
@Data
@ColumnWidth(22)
@HeadRowHeight(20)
@ContentRowHeight(18)
public class TransportPlanImportExcel implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@ExcelProperty("*计划名称")
private String planName;
@ExcelProperty("*运输方式")
private String transportType;
@ExcelProperty("*计划开始日期")
private String planStartDate;
@ExcelProperty("*计划结束日期")
private String planEndDate;
@ExcelProperty("货物名称")
private String cargoName;
@ExcelProperty("*货物类型")
private String cargoType;
@ExcelProperty("数量")
private BigDecimal quantity;
@ExcelProperty("数量单位")
private String quantityUnit;
@ExcelProperty("包装")
private String packageType;
@ExcelProperty("品牌")
private String brand;
@ExcelProperty("规格")
private String specification;
@ExcelProperty("型号")
private String model;
@ExcelProperty("物料编码")
private String materialCode;
@ExcelProperty("设备编码")
private String deviceCode;
@ExcelProperty("*发货地址")
private String departureAddress;
@ExcelProperty("发货联系人")
private String departureContact;
@ExcelProperty("发货联系方式")
private String departurePhone;
@ExcelProperty("*收货地址")
private String arrivalAddress;
@ExcelProperty("收货联系人")
private String arrivalContact;
@ExcelProperty("收货联系方式")
private String arrivalPhone;
@ExcelProperty("备注")
private String remark;
@ExcelIgnore
private String errorMessage;
}

View File

@@ -34,6 +34,7 @@ import java.io.Serializable;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.LocalDate;
/** /**
* 运单管理 Excel * 运单管理 Excel
@@ -107,10 +108,10 @@ public class WaybillExcel implements Serializable {
private String escortPhone; private String escortPhone;
@ExcelProperty("里程(km)") @ExcelProperty("里程(km)")
private BigDecimal mileage; private BigDecimal mileage;
@ExcelProperty("预计发货时间") @ExcelProperty("预计发货日期")
private LocalDateTime estimatedStartTime; private LocalDate estimatedStartTime;
@ExcelProperty("预计完成时间") @ExcelProperty("预计完成日期")
private LocalDateTime estimatedEndTime; private LocalDate estimatedEndTime;
@ExcelProperty("单价") @ExcelProperty("单价")
private BigDecimal unitPrice; private BigDecimal unitPrice;
@ExcelProperty("计价单位") @ExcelProperty("计价单位")

View File

@@ -0,0 +1,66 @@
package org.springblade.transport.excel;
import cn.idev.excel.annotation.ExcelProperty;
import cn.idev.excel.annotation.write.style.ColumnWidth;
import cn.idev.excel.annotation.write.style.ContentRowHeight;
import cn.idev.excel.annotation.write.style.HeadRowHeight;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
/**
* 运单批量导入模板。
*/
@Data
@ColumnWidth(22)
@HeadRowHeight(28)
@ContentRowHeight(18)
public class WaybillImportBatchExcel implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@ExcelProperty("原始单号")
private String originalNo;
@ExcelProperty("*车牌号/航班号/船号/班列号")
private String vehicleNo;
@ExcelProperty("*司机/船长")
private String driverName;
@ExcelProperty("*运输类型")
private String transportType;
@ExcelProperty("*货物名称")
private String cargoName;
@ExcelProperty("*货物类型")
private String cargoType;
@ExcelProperty("重量")
private BigDecimal quantity;
@ExcelProperty("*发货地址")
private String departureAddress;
@ExcelProperty("*发货联系人")
private String departureContact;
@ExcelProperty("*发货联系人电话")
private String departurePhone;
@ExcelProperty("*到货地址")
private String arrivalAddress;
@ExcelProperty("*到货联系人")
private String arrivalContact;
@ExcelProperty("*收货联系人电话")
private String arrivalPhone;
@ExcelProperty("*开始时间")
private String startDate;
@ExcelProperty("*结束时间")
private String endDate;
@ExcelProperty("*单价")
private BigDecimal unitPrice;
@ExcelProperty("*运费")
private BigDecimal freight;
@ExcelProperty("其他费用合计")
private BigDecimal otherFeeTotal;
@ExcelProperty("运费合计")
private BigDecimal freightTotal;
@ExcelProperty("备注")
private String remark;
}

View File

@@ -40,6 +40,7 @@ public interface IShippingTemplateService extends BaseService<ShippingTemplate>
IPage<ShippingTemplateVO> selectShippingTemplatePage(IPage<ShippingTemplate> page, ShippingTemplateVO shippingTemplate); IPage<ShippingTemplateVO> selectShippingTemplatePage(IPage<ShippingTemplate> page, ShippingTemplateVO shippingTemplate);
ShippingTemplateVO detail(Long id); ShippingTemplateVO detail(Long id);
String nextTemplateCode();
boolean submit(ShippingTemplate shippingTemplate); boolean submit(ShippingTemplate shippingTemplate);
BusinessRemoveResultVO removeShippingTemplate(String ids); BusinessRemoveResultVO removeShippingTemplate(String ids);
List<ShippingTemplateExcel> exportShippingTemplate(ShippingTemplateVO shippingTemplate, String ids); List<ShippingTemplateExcel> exportShippingTemplate(ShippingTemplateVO shippingTemplate, String ids);

View File

@@ -25,6 +25,8 @@ package org.springblade.transport.service;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import org.springblade.core.mp.base.BaseService; import org.springblade.core.mp.base.BaseService;
import org.springblade.transport.excel.TransportPlanExcel; import org.springblade.transport.excel.TransportPlanExcel;
import org.springblade.transport.excel.TransportPlanImportExcel;
import org.springblade.transport.pojo.dto.TransportPlanDispatchRequest;
import org.springblade.transport.pojo.entity.TransportPlan; import org.springblade.transport.pojo.entity.TransportPlan;
import org.springblade.transport.pojo.vo.BusinessRemoveResultVO; import org.springblade.transport.pojo.vo.BusinessRemoveResultVO;
import org.springblade.transport.pojo.vo.TransportPlanVO; import org.springblade.transport.pojo.vo.TransportPlanVO;
@@ -43,7 +45,9 @@ public interface ITransportPlanService extends BaseService<TransportPlan> {
boolean submit(TransportPlan transportPlan); boolean submit(TransportPlan transportPlan);
BusinessRemoveResultVO removeTransportPlan(String ids); BusinessRemoveResultVO removeTransportPlan(String ids);
List<TransportPlanExcel> exportTransportPlan(TransportPlanVO transportPlan, String ids); List<TransportPlanExcel> exportTransportPlan(TransportPlanVO transportPlan, String ids);
List<TransportPlanImportExcel> importTransportPlan(List<TransportPlanImportExcel> data, Long projectId, String projectName, Long contractId, String contractName, String customerName);
TransportPlanVO copy(Long id); TransportPlanVO copy(Long id);
int dispatch(TransportPlanDispatchRequest request);
boolean cancel(Long id); boolean cancel(Long id);
boolean complete(Long id); boolean complete(Long id);

View File

@@ -67,6 +67,11 @@ public class ShippingTemplateServiceImpl extends BaseServiceImpl<ShippingTemplat
return ShippingTemplateWrapper.build().entityVO(loadEditable(id, false)); return ShippingTemplateWrapper.build().entityVO(loadEditable(id, false));
} }
@Override
public String nextTemplateCode() {
return nextCode();
}
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public boolean submit(ShippingTemplate shippingTemplate) { public boolean submit(ShippingTemplate shippingTemplate) {
@@ -78,8 +83,8 @@ public class ShippingTemplateServiceImpl extends BaseServiceImpl<ShippingTemplat
shippingTemplate.setDeptName(oldRecord.getDeptName()); shippingTemplate.setDeptName(oldRecord.getDeptName());
} }
prepare(shippingTemplate); prepare(shippingTemplate);
if (created && Func.isEmpty(shippingTemplate.getTemplateCode())) { if (created) {
shippingTemplate.setTemplateCode(nextCode()); shippingTemplate.setTemplateCode(nextTemplateCode());
} }
validate(shippingTemplate); validate(shippingTemplate);
return saveOrUpdate(shippingTemplate); return saveOrUpdate(shippingTemplate);
@@ -292,7 +297,7 @@ public class ShippingTemplateServiceImpl extends BaseServiceImpl<ShippingTemplat
} }
private synchronized String nextCode() { private synchronized String nextCode() {
String prefix = "MBJH" + LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE); String prefix = "MBJH-" + LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE);
List<ShippingTemplate> latestList = list(Wrappers.<ShippingTemplate>lambdaQuery() List<ShippingTemplate> latestList = list(Wrappers.<ShippingTemplate>lambdaQuery()
.select(ShippingTemplate::getTemplateCode) .select(ShippingTemplate::getTemplateCode)
.likeRight(ShippingTemplate::getTemplateCode, prefix) .likeRight(ShippingTemplate::getTemplateCode, prefix)

View File

@@ -25,26 +25,33 @@ package org.springblade.transport.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.AllArgsConstructor;
import org.springblade.core.log.exception.ServiceException; import org.springblade.core.log.exception.ServiceException;
import org.springblade.core.mp.base.BaseServiceImpl; import org.springblade.core.mp.base.BaseServiceImpl;
import org.springblade.core.tool.jackson.JsonUtil;
import org.springblade.core.tool.utils.BeanUtil; import org.springblade.core.tool.utils.BeanUtil;
import org.springblade.core.tool.utils.Func; import org.springblade.core.tool.utils.Func;
import org.springblade.system.cache.UserCache; import org.springblade.system.cache.UserCache;
import org.springblade.system.pojo.entity.Dept; import org.springblade.system.pojo.entity.Dept;
import org.springblade.transport.excel.TransportPlanExcel; import org.springblade.transport.excel.TransportPlanExcel;
import org.springblade.transport.excel.TransportPlanImportExcel;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import org.springblade.transport.mapper.TransportPlanMapper; import org.springblade.transport.mapper.TransportPlanMapper;
import org.springblade.transport.pojo.dto.TransportPlanDispatchRequest;
import org.springblade.transport.pojo.entity.TransportPlan; import org.springblade.transport.pojo.entity.TransportPlan;
import org.springblade.transport.pojo.entity.Waybill;
import org.springblade.transport.pojo.vo.BusinessRemoveResultVO; import org.springblade.transport.pojo.vo.BusinessRemoveResultVO;
import org.springblade.transport.pojo.vo.TransportPlanVO; import org.springblade.transport.pojo.vo.TransportPlanVO;
import org.springblade.transport.service.ITransportPlanService; import org.springblade.transport.service.ITransportPlanService;
import org.springblade.transport.service.IWaybillService;
import org.springblade.transport.support.TransportBusinessSupport; import org.springblade.transport.support.TransportBusinessSupport;
import org.springblade.transport.wrapper.TransportPlanWrapper; import org.springblade.transport.wrapper.TransportPlanWrapper;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
@@ -54,8 +61,11 @@ import java.util.Objects;
* @author Chill * @author Chill
*/ */
@Service @Service
@AllArgsConstructor
public class TransportPlanServiceImpl extends BaseServiceImpl<TransportPlanMapper, TransportPlan> implements ITransportPlanService { public class TransportPlanServiceImpl extends BaseServiceImpl<TransportPlanMapper, TransportPlan> implements ITransportPlanService {
private final IWaybillService waybillService;
@Override @Override
public IPage<TransportPlanVO> selectTransportPlanPage(IPage<TransportPlan> page, TransportPlanVO transportPlan) { public IPage<TransportPlanVO> selectTransportPlanPage(IPage<TransportPlan> page, TransportPlanVO transportPlan) {
IPage<TransportPlan> entityPage = page(page, buildQuery(transportPlan)); IPage<TransportPlan> entityPage = page(page, buildQuery(transportPlan));
@@ -125,6 +135,106 @@ public class TransportPlanServiceImpl extends BaseServiceImpl<TransportPlanMappe
}).toList(); }).toList();
} }
@Override
@Transactional(rollbackFor = Exception.class)
public List<TransportPlanImportExcel> importTransportPlan(List<TransportPlanImportExcel> data, Long projectId, String projectName, Long contractId, String contractName, String customerName) {
if (Func.isEmpty(data)) {
throw new ServiceException("导入数据不能为空");
}
if (Func.isEmpty(projectId)) {
throw new ServiceException("项目不能为空");
}
TransportBusinessSupport.validateRequired(projectName, "项目不能为空");
if (Func.isEmpty(contractId)) {
throw new ServiceException("客户合同不能为空");
}
TransportBusinessSupport.validateRequired(contractName, "客户合同不能为空");
List<TransportPlanImportExcel> errorList = new ArrayList<>();
for (int index = 0; index < data.size(); index++) {
TransportPlanImportExcel excel = data.get(index);
try {
LocalDate planStartDate = parseImportDate(excel.getPlanStartDate(), "计划开始日期");
LocalDate planEndDate = parseImportDate(excel.getPlanEndDate(), "计划结束日期");
validateImportExcel(excel, planStartDate, planEndDate);
TransportPlan transportPlan = new TransportPlan();
transportPlan.setProjectId(projectId);
transportPlan.setProjectName(projectName);
transportPlan.setContractId(contractId);
transportPlan.setContractName(contractName);
transportPlan.setCustomerName(customerName);
transportPlan.setPlanName(excel.getPlanName());
transportPlan.setTransportType(excel.getTransportType());
transportPlan.setPlanStartDate(planStartDate);
transportPlan.setPlanEndDate(planEndDate);
transportPlan.setDepartureAddress(excel.getDepartureAddress());
transportPlan.setDepartureContact(excel.getDepartureContact());
transportPlan.setDeparturePhone(excel.getDeparturePhone());
transportPlan.setArrivalAddress(excel.getArrivalAddress());
transportPlan.setArrivalContact(excel.getArrivalContact());
transportPlan.setArrivalPhone(excel.getArrivalPhone());
transportPlan.setRemark(excel.getRemark());
transportPlan.setGoodsJson(JsonUtil.toJson(List.of(importGoods(excel))));
transportPlan.setDataSource("批量导入");
transportPlan.setBusinessStatus("waiting_dispatch");
submit(transportPlan);
} catch (Exception exception) {
excel.setErrorMessage("" + (index + 2) + "行:" + exception.getMessage());
errorList.add(excel);
}
}
return errorList;
}
private LinkedHashMap<String, Object> importGoods(TransportPlanImportExcel excel) {
LinkedHashMap<String, Object> goods = new LinkedHashMap<>();
goods.put("cargoName", excel.getCargoName());
goods.put("cargoType", excel.getCargoType());
goods.put("quantity", excel.getQuantity());
goods.put("quantityUnit", excel.getQuantityUnit());
goods.put("packageType", excel.getPackageType());
goods.put("brand", excel.getBrand());
goods.put("specification", excel.getSpecification());
goods.put("model", excel.getModel());
goods.put("materialCode", excel.getMaterialCode());
goods.put("deviceCode", excel.getDeviceCode());
return goods;
}
private void validateImportExcel(TransportPlanImportExcel excel, LocalDate planStartDate, LocalDate planEndDate) {
TransportBusinessSupport.validateRequired(excel.getPlanName(), "计划名称不能为空");
TransportBusinessSupport.validateRequired(excel.getTransportType(), "运输方式不能为空");
TransportBusinessSupport.validateRequired(excel.getCargoType(), "货物类型不能为空");
TransportBusinessSupport.validateRequired(excel.getDepartureAddress(), "发货地址不能为空");
TransportBusinessSupport.validateRequired(excel.getArrivalAddress(), "收货地址不能为空");
validateImportLength(excel.getPlanName(), 50, "计划名称");
validateImportLength(excel.getDepartureAddress(), 255, "发货地址");
validateImportLength(excel.getArrivalAddress(), 255, "收货地址");
validateImportLength(excel.getDepartureContact(), 50, "发货联系人");
validateImportLength(excel.getArrivalContact(), 50, "收货联系人");
validateImportLength(excel.getDeparturePhone(), 50, "发货联系方式");
validateImportLength(excel.getArrivalPhone(), 50, "收货联系方式");
validateImportLength(excel.getRemark(), 200, "备注");
TransportBusinessSupport.validatePhone(excel.getDeparturePhone(), "发货联系方式格式不正确");
TransportBusinessSupport.validatePhone(excel.getArrivalPhone(), "收货联系方式格式不正确");
TransportBusinessSupport.validateNonNegative(excel.getQuantity(), "数量");
TransportBusinessSupport.validateDateRange(planStartDate, planEndDate, "计划结束日期不能早于计划开始日期");
}
private LocalDate parseImportDate(String value, String fieldName) {
TransportBusinessSupport.validateRequired(value, fieldName + "不能为空");
try {
return LocalDate.parse(value.trim(), DateTimeFormatter.ISO_LOCAL_DATE);
} catch (Exception exception) {
throw new ServiceException(fieldName + "格式必须为 YYYY-MM-DD");
}
}
private void validateImportLength(String value, int maxLength, String fieldName) {
if (Func.isNotEmpty(value) && value.length() > maxLength) {
throw new ServiceException(fieldName + "不能超过" + maxLength + "个字符");
}
}
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public TransportPlanVO copy(Long id) { public TransportPlanVO copy(Long id) {
@@ -163,6 +273,58 @@ public class TransportPlanServiceImpl extends BaseServiceImpl<TransportPlanMappe
return detail(target.getId()); return detail(target.getId());
} }
@Override
@Transactional(rollbackFor = Exception.class)
public int dispatch(TransportPlanDispatchRequest request) {
if (request == null || Func.isEmpty(request.getId())) {
throw new ServiceException("运输计划主键不能为空");
}
if (Func.isEmpty(request.getWaybills())) {
throw new ServiceException("请至少添加一条调度明细");
}
TransportPlan plan = loadEditable(request.getId(), true);
if ("completed".equals(plan.getBusinessStatus()) || "cancelled".equals(plan.getBusinessStatus())) {
throw new ServiceException("当前运输计划状态不允许调度");
}
if ("draft".equals(request.getMode())) {
return 0;
}
for (Waybill waybill : request.getWaybills()) {
if (waybill == null) {
throw new ServiceException("调度明细不能为空");
}
waybill.setId(null);
waybill.setPlanId(plan.getId());
waybill.setPlanName(plan.getPlanName());
waybill.setProjectId(plan.getProjectId());
waybill.setProjectName(plan.getProjectName());
waybill.setContractId(plan.getContractId());
waybill.setContractName(plan.getContractName());
waybill.setCustomerName(plan.getCustomerName());
if (Func.isEmpty(waybill.getDepartureAddress())) {
waybill.setDepartureAddressId(plan.getDepartureAddressId());
waybill.setDepartureName(plan.getDepartureName());
waybill.setDepartureAddress(plan.getDepartureAddress());
waybill.setDepartureContact(plan.getDepartureContact());
waybill.setDeparturePhone(plan.getDeparturePhone());
}
if (Func.isEmpty(waybill.getArrivalAddress())) {
waybill.setArrivalAddressId(plan.getArrivalAddressId());
waybill.setArrivalName(plan.getArrivalName());
waybill.setArrivalAddress(plan.getArrivalAddress());
waybill.setArrivalContact(plan.getArrivalContact());
waybill.setArrivalPhone(plan.getArrivalPhone());
}
waybill.setAttachmentsJson(plan.getAttachmentsJson());
waybill.setDataSource("计划调度");
waybill.setBusinessStatus("pending");
waybillService.submit(waybill);
}
plan.setBusinessStatus("dispatching");
updateById(plan);
return request.getWaybills().size();
}
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public boolean cancel(Long id) { public boolean cancel(Long id) {

View File

@@ -496,7 +496,7 @@ public class WaybillServiceImpl extends BaseServiceImpl<WaybillMapper, Waybill>
if (waybill.getEstimatedStartTime() != null if (waybill.getEstimatedStartTime() != null
&& waybill.getEstimatedEndTime() != null && waybill.getEstimatedEndTime() != null
&& waybill.getEstimatedEndTime().isBefore(waybill.getEstimatedStartTime())) { && waybill.getEstimatedEndTime().isBefore(waybill.getEstimatedStartTime())) {
throw new ServiceException("预计完成时间不能早于预计发货时间"); throw new ServiceException("预计完成日期不能早于预计发货日期");
} }
} }

View File

@@ -245,8 +245,8 @@ CREATE TABLE `blade_waybill` (
`escort_name` varchar(100) DEFAULT NULL COMMENT '押运人', `escort_name` varchar(100) DEFAULT NULL COMMENT '押运人',
`escort_phone` varchar(50) DEFAULT NULL COMMENT '押运人手机号', `escort_phone` varchar(50) DEFAULT NULL COMMENT '押运人手机号',
`mileage` decimal(18,2) DEFAULT NULL COMMENT '里程', `mileage` decimal(18,2) DEFAULT NULL COMMENT '里程',
`estimated_start_time` datetime DEFAULT NULL COMMENT '预计发货时间', `estimated_start_time` date DEFAULT NULL COMMENT '预计发货日期',
`estimated_end_time` datetime DEFAULT NULL COMMENT '预计完成时间', `estimated_end_time` date DEFAULT NULL COMMENT '预计完成日期',
`unit_price` decimal(18,2) DEFAULT NULL COMMENT '单价', `unit_price` decimal(18,2) DEFAULT NULL COMMENT '单价',
`price_unit` varchar(50) DEFAULT NULL COMMENT '计价单位', `price_unit` varchar(50) DEFAULT NULL COMMENT '计价单位',
`other_fee_total` decimal(18,2) DEFAULT NULL COMMENT '其他费用合计', `other_fee_total` decimal(18,2) DEFAULT NULL COMMENT '其他费用合计',

View File

@@ -0,0 +1,3 @@
ALTER TABLE `blade_waybill`
MODIFY COLUMN `estimated_start_time` date DEFAULT NULL COMMENT '预计发货日期',
MODIFY COLUMN `estimated_end_time` date DEFAULT NULL COMMENT '预计完成日期';

View File

@@ -0,0 +1,33 @@
CREATE TABLE IF NOT EXISTS `blade_waybill_import_batch` (
`id` bigint NOT NULL,
`tenant_id` varchar(12) NOT NULL DEFAULT '000000',
`batch_no` varchar(64) NOT NULL COMMENT '运单批次号',
`project_id` bigint DEFAULT NULL,
`project_name` varchar(200) DEFAULT NULL,
`customer_name` varchar(200) DEFAULT NULL,
`contract_id` bigint DEFAULT NULL,
`contract_name` varchar(200) DEFAULT NULL,
`carrier_type` varchar(32) DEFAULT NULL,
`carrier_ids` varchar(1000) DEFAULT NULL,
`carrier_name` varchar(500) DEFAULT NULL,
`import_type` varchar(32) NOT NULL COMMENT 'waybill/settlement',
`import_status` varchar(32) NOT NULL COMMENT 'draft/processing/completed',
`plan_id` bigint DEFAULT NULL,
`plan_name` varchar(200) DEFAULT NULL,
`waybill_count` int NOT NULL DEFAULT 0,
`remark` varchar(500) DEFAULT NULL,
`create_user` bigint DEFAULT NULL,
`create_dept` bigint DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
`update_user` bigint DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
`status` tinyint NOT NULL DEFAULT 1,
`is_deleted` tinyint NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_waybill_import_batch_no` (`tenant_id`,`batch_no`),
KEY `idx_waybill_import_batch_project` (`project_id`),
KEY `idx_waybill_import_batch_create_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='运单批量导入批次记录';
ALTER TABLE `blade_waybill` ADD COLUMN `import_batch_id` bigint DEFAULT NULL COMMENT '运单导入批次ID';
ALTER TABLE `blade_waybill` ADD KEY `idx_waybill_import_batch_id` (`import_batch_id`);

View File

@@ -20,8 +20,8 @@ ALTER TABLE `blade_waybill`
ADD COLUMN `escort_name` varchar(100) DEFAULT NULL COMMENT '押运人' AFTER `trailer_vehicle_no`, ADD COLUMN `escort_name` varchar(100) DEFAULT NULL COMMENT '押运人' AFTER `trailer_vehicle_no`,
ADD COLUMN `escort_phone` varchar(50) DEFAULT NULL COMMENT '押运人手机号' AFTER `escort_name`, ADD COLUMN `escort_phone` varchar(50) DEFAULT NULL COMMENT '押运人手机号' AFTER `escort_name`,
ADD COLUMN `mileage` decimal(18,2) DEFAULT NULL COMMENT '里程' AFTER `escort_phone`, ADD COLUMN `mileage` decimal(18,2) DEFAULT NULL COMMENT '里程' AFTER `escort_phone`,
ADD COLUMN `estimated_start_time` datetime DEFAULT NULL COMMENT '预计发货时间' AFTER `mileage`, ADD COLUMN `estimated_start_time` date DEFAULT NULL COMMENT '预计发货日期' AFTER `mileage`,
ADD COLUMN `estimated_end_time` datetime DEFAULT NULL COMMENT '预计完成时间' AFTER `estimated_start_time`, ADD COLUMN `estimated_end_time` date DEFAULT NULL COMMENT '预计完成日期' AFTER `estimated_start_time`,
ADD COLUMN `unit_price` decimal(18,2) DEFAULT NULL COMMENT '单价' AFTER `estimated_end_time`, ADD COLUMN `unit_price` decimal(18,2) DEFAULT NULL COMMENT '单价' AFTER `estimated_end_time`,
ADD COLUMN `price_unit` varchar(50) DEFAULT NULL COMMENT '计价单位' AFTER `unit_price`, ADD COLUMN `price_unit` varchar(50) DEFAULT NULL COMMENT '计价单位' AFTER `unit_price`,
ADD COLUMN `other_fee_total` decimal(18,2) DEFAULT NULL COMMENT '其他费用合计' AFTER `price_unit`, ADD COLUMN `other_fee_total` decimal(18,2) DEFAULT NULL COMMENT '其他费用合计' AFTER `price_unit`,