1、新增总单
2、fix bug
This commit is contained in:
+17
@@ -0,0 +1,17 @@
|
||||
package org.springblade.transport.pojo.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 多联总单调度请求。
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "多联总单调度请求")
|
||||
public class MasterOrderDispatchRequest {
|
||||
private Long id;
|
||||
private List<Map<String, Object>> dispatches;
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* 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.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 多联总单实体类
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("blade_master_order")
|
||||
@Schema(description = "多联总单")
|
||||
public class MasterOrder extends TenantEntity {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
private String masterNo;
|
||||
private Long projectId;
|
||||
private String projectName;
|
||||
private Long contractId;
|
||||
private String contractName;
|
||||
private String customerName;
|
||||
private String transportOrganizationType;
|
||||
private String businessStatus;
|
||||
private String departureName;
|
||||
private String departureAddress;
|
||||
private String departureContact;
|
||||
private String departurePhone;
|
||||
private String arrivalName;
|
||||
private String arrivalAddress;
|
||||
private String arrivalContact;
|
||||
private String arrivalPhone;
|
||||
private LocalDateTime planStartTime;
|
||||
private LocalDateTime planEndTime;
|
||||
private String routeJson;
|
||||
private String goodsJson;
|
||||
private String attachmentsJson;
|
||||
private String remark;
|
||||
}
|
||||
+6
@@ -48,6 +48,12 @@ public class TransportPlan extends TenantEntity {
|
||||
@Schema(description = "计划单号")
|
||||
private String planNo;
|
||||
|
||||
@Schema(description = "多联总单号")
|
||||
private String masterNo;
|
||||
|
||||
@Schema(description = "总单分段号")
|
||||
private String relationNo;
|
||||
|
||||
@Schema(description = "计划名称")
|
||||
private String planName;
|
||||
|
||||
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
/** BladeX Commercial License Agreement */
|
||||
package org.springblade.transport.pojo.vo;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.springblade.transport.pojo.entity.MasterOrder;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 多联总单视图实体类
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class MasterOrderVO extends MasterOrder {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
@TableField(exist = false)
|
||||
private String masterNos;
|
||||
@TableField(exist = false)
|
||||
private String cargoName;
|
||||
@TableField(exist = false)
|
||||
private String cargoType;
|
||||
@TableField(exist = false)
|
||||
private String contractNo;
|
||||
@TableField(exist = false)
|
||||
private List<Map<String, Object>> routes;
|
||||
@TableField(exist = false)
|
||||
private List<Map<String, Object>> goods;
|
||||
@TableField(exist = false)
|
||||
private List<Map<String, Object>> routeProgress;
|
||||
@TableField(exist = false)
|
||||
private BigDecimal totalQuantity;
|
||||
@TableField(exist = false)
|
||||
private String createUserName;
|
||||
@TableField(exist = false)
|
||||
private String updateUserName;
|
||||
}
|
||||
+4
@@ -63,6 +63,10 @@ public class ProjectApplyVO extends ProjectApply {
|
||||
@Schema(description = "审批状态名称")
|
||||
private String approvalStatusName;
|
||||
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "审批状态集合,多个状态使用逗号分隔")
|
||||
private String approvalStatuses;
|
||||
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "生效类型名称")
|
||||
private String effectiveTypeName;
|
||||
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/** BladeX Commercial License Agreement */
|
||||
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.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springblade.core.boot.ctrl.BladeController;
|
||||
import org.springblade.core.excel.util.ExcelUtil;
|
||||
import org.springblade.core.mp.support.Condition;
|
||||
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.MasterOrderDispatchRequest;
|
||||
import org.springblade.transport.pojo.vo.MasterOrderVO;
|
||||
import org.springblade.transport.service.IMasterOrderService;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 多联总单控制器
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@RestController
|
||||
@AllArgsConstructor
|
||||
@PreAuth(menu = "master_order")
|
||||
@RequestMapping("/master-order")
|
||||
@Tag(name = "多联总单", description = "多联总单管理")
|
||||
public class MasterOrderController extends BladeController {
|
||||
|
||||
private final IMasterOrderService masterOrderService;
|
||||
@GetMapping("/detail") @ApiOperationSupport(order = 1) @Operation(summary = "详情") public R<MasterOrderVO> detail(@RequestParam Long id) { return R.data(masterOrderService.detail(id)); }
|
||||
@GetMapping("/list") @ApiOperationSupport(order = 2) @Operation(summary = "分页") public R<IPage<MasterOrderVO>> list(MasterOrderVO query, Query page) { return R.data(masterOrderService.selectPage(Condition.getPage(page), query)); }
|
||||
@PostMapping("/submit") @ApiOperationSupport(order = 3) @Operation(summary = "确认创建或编辑") public R<MasterOrderVO> submit(@RequestBody MasterOrderVO data) { return R.data(masterOrderService.submit(data, false)); }
|
||||
@PostMapping("/draft") @ApiOperationSupport(order = 4) @Operation(summary = "暂存草稿") public R<MasterOrderVO> draft(@RequestBody MasterOrderVO data) { return R.data(masterOrderService.submit(data, true)); }
|
||||
@PostMapping("/copy") @ApiOperationSupport(order = 5) @Operation(summary = "复制") public R<MasterOrderVO> copy(@RequestParam Long id) { return R.data(masterOrderService.copy(id)); }
|
||||
@PostMapping("/remove") @ApiOperationSupport(order = 6) @Operation(summary = "删除") public R status(@RequestParam Long id) { return R.status(masterOrderService.removeMasterOrder(id)); }
|
||||
@PostMapping("/close-dispatch") @ApiOperationSupport(order = 7) @Operation(summary = "关闭调度") public R closeDispatch(@RequestParam Long id) { return R.status(masterOrderService.closeDispatch(id)); }
|
||||
@PostMapping("/dispatch") @ApiOperationSupport(order = 8) @Operation(summary = "确认调度") public R<MasterOrderVO> dispatch(@RequestBody MasterOrderDispatchRequest data) { return R.data(masterOrderService.dispatch(data)); }
|
||||
@GetMapping("/export") @ApiOperationSupport(order = 9) @Operation(summary = "按运单导出") public void export(MasterOrderVO query, HttpServletResponse response) { ExcelUtil.export(response, "总单运单明细", "运单明细", masterOrderService.exportWaybills(query), MasterOrderVO.class); }
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
/** BladeX Commercial License Agreement */
|
||||
package org.springblade.transport.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.springblade.transport.pojo.entity.MasterOrder;
|
||||
|
||||
/**
|
||||
* 多联总单 Mapper 接口
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
public interface MasterOrderMapper extends BaseMapper<MasterOrder> {
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
/** BladeX Commercial License Agreement */
|
||||
package org.springblade.transport.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import org.springblade.core.mp.base.BaseService;
|
||||
import org.springblade.transport.pojo.dto.MasterOrderDispatchRequest;
|
||||
import org.springblade.transport.pojo.entity.MasterOrder;
|
||||
import org.springblade.transport.pojo.vo.MasterOrderVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 多联总单 服务类
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
public interface IMasterOrderService extends BaseService<MasterOrder> {
|
||||
IPage<MasterOrderVO> selectPage(IPage<MasterOrder> page, MasterOrderVO query);
|
||||
MasterOrderVO detail(Long id);
|
||||
MasterOrderVO submit(MasterOrderVO masterOrder, boolean draft);
|
||||
MasterOrderVO copy(Long id);
|
||||
boolean removeMasterOrder(Long id);
|
||||
boolean closeDispatch(Long id);
|
||||
MasterOrderVO dispatch(MasterOrderDispatchRequest request);
|
||||
List<MasterOrderVO> exportWaybills(MasterOrderVO query);
|
||||
}
|
||||
+366
@@ -0,0 +1,366 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
*/
|
||||
package org.springblade.transport.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import org.springblade.core.log.exception.ServiceException;
|
||||
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.Func;
|
||||
import org.springblade.system.cache.UserCache;
|
||||
import org.springblade.transport.mapper.MasterOrderMapper;
|
||||
import org.springblade.transport.pojo.dto.MasterOrderDispatchRequest;
|
||||
import org.springblade.transport.pojo.entity.MasterOrder;
|
||||
import org.springblade.transport.pojo.entity.ContractManage;
|
||||
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.vo.MasterOrderVO;
|
||||
import org.springblade.transport.service.IMasterOrderService;
|
||||
import org.springblade.transport.service.IContractManageService;
|
||||
import org.springblade.transport.service.IProjectApplyService;
|
||||
import org.springblade.transport.service.ITransportPlanService;
|
||||
import org.springblade.transport.service.IWaybillService;
|
||||
import org.springblade.transport.support.TransportBusinessSupport;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 多联总单 服务实现类
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Service
|
||||
public class MasterOrderServiceImpl extends BaseServiceImpl<MasterOrderMapper, MasterOrder> implements IMasterOrderService {
|
||||
|
||||
private final IWaybillService waybillService;
|
||||
private final ITransportPlanService transportPlanService;
|
||||
private final IProjectApplyService projectApplyService;
|
||||
private final IContractManageService contractManageService;
|
||||
|
||||
public MasterOrderServiceImpl(IWaybillService waybillService, ITransportPlanService transportPlanService, IProjectApplyService projectApplyService,
|
||||
IContractManageService contractManageService) {
|
||||
this.waybillService = waybillService;
|
||||
this.transportPlanService = transportPlanService;
|
||||
this.projectApplyService = projectApplyService;
|
||||
this.contractManageService = contractManageService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IPage<MasterOrderVO> selectPage(IPage<MasterOrder> page, MasterOrderVO query) {
|
||||
IPage<MasterOrder> records = page(page, buildQuery(query));
|
||||
return records.convert(this::toVO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MasterOrderVO detail(Long id) {
|
||||
MasterOrder masterOrder = getRequired(id);
|
||||
return toVO(masterOrder);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public MasterOrderVO submit(MasterOrderVO request, boolean draft) {
|
||||
boolean created = Func.isEmpty(request.getId());
|
||||
MasterOrder target = created ? new MasterOrder() : getRequired(request.getId());
|
||||
if (!created && ("dispatching".equals(target.getBusinessStatus()) || "completed".equals(target.getBusinessStatus()))) {
|
||||
assertRestrictedEdit(target, request);
|
||||
}
|
||||
if (!created && "closed".equals(target.getBusinessStatus())) throw new ServiceException("调度关闭的总单不能编辑");
|
||||
copyWritable(request, target);
|
||||
if (created) target.setMasterNo(nextCode());
|
||||
target.setTransportOrganizationType("多式联运");
|
||||
target.setBusinessStatus(draft ? "draft" : ("dispatching".equals(target.getBusinessStatus()) ? "dispatching" : "waiting_dispatch"));
|
||||
validate(target, draft);
|
||||
saveOrUpdate(target);
|
||||
return detail(target.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public MasterOrderVO copy(Long id) {
|
||||
MasterOrder source = getRequired(id);
|
||||
MasterOrder target = new MasterOrder();
|
||||
BeanUtil.copyProperties(source, target);
|
||||
target.setId(null);
|
||||
target.setMasterNo(nextCode());
|
||||
target.setBusinessStatus("draft");
|
||||
target.setStatus(1);
|
||||
save(target);
|
||||
return detail(target.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean removeMasterOrder(Long id) {
|
||||
MasterOrder masterOrder = getRequired(id);
|
||||
if (!"draft".equals(masterOrder.getBusinessStatus()) && !"waiting_dispatch".equals(masterOrder.getBusinessStatus())) {
|
||||
throw new ServiceException("当前状态不允许删除");
|
||||
}
|
||||
return deleteLogic(Collections.singletonList(id));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean closeDispatch(Long id) {
|
||||
MasterOrder masterOrder = getRequired(id);
|
||||
if (!"dispatching".equals(masterOrder.getBusinessStatus()) && !"completed".equals(masterOrder.getBusinessStatus())) {
|
||||
throw new ServiceException("当前状态不允许关闭调度");
|
||||
}
|
||||
masterOrder.setBusinessStatus("closed");
|
||||
return updateById(masterOrder);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public MasterOrderVO dispatch(MasterOrderDispatchRequest request) {
|
||||
MasterOrder masterOrder = getRequired(request.getId());
|
||||
if (!"waiting_dispatch".equals(masterOrder.getBusinessStatus()) && !"dispatching".equals(masterOrder.getBusinessStatus())) {
|
||||
throw new ServiceException("当前状态不允许调度");
|
||||
}
|
||||
if (Func.isEmpty(request.getDispatches())) throw new ServiceException("请至少加入一条调度记录");
|
||||
validateDispatches(masterOrder, request.getDispatches());
|
||||
Map<String, List<Map<String, Object>>> planGroups = new LinkedHashMap<>();
|
||||
Map<String, List<Map<String, Object>>> waybillGroups = new LinkedHashMap<>();
|
||||
for (Map<String, Object> dispatch : request.getDispatches()) {
|
||||
if ("计划单".equals(string(dispatch, "documentType"))) {
|
||||
if (!string(dispatch, "transportType", "").contains("公路")) throw new ServiceException("仅公路运输可生成计划单");
|
||||
planGroups.computeIfAbsent(string(dispatch, "batchNo", string(dispatch, "relationNo")), key -> new ArrayList<>()).add(dispatch);
|
||||
} else {
|
||||
waybillGroups.computeIfAbsent(waybillGroupKey(dispatch), key -> new ArrayList<>()).add(dispatch);
|
||||
}
|
||||
}
|
||||
for (List<Map<String, Object>> dispatches : planGroups.values()) createTransportPlan(masterOrder, dispatches);
|
||||
for (List<Map<String, Object>> dispatches : waybillGroups.values()) createWaybill(masterOrder, dispatches);
|
||||
refreshStatus(masterOrder);
|
||||
return detail(masterOrder.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MasterOrderVO> exportWaybills(MasterOrderVO query) {
|
||||
List<MasterOrderVO> result = new ArrayList<>();
|
||||
for (MasterOrder masterOrder : list(buildQuery(query))) {
|
||||
for (Waybill waybill : waybillService.list(new LambdaQueryWrapper<Waybill>().eq(Waybill::getMasterNo, masterOrder.getMasterNo()))) {
|
||||
MasterOrderVO row = toVO(masterOrder);
|
||||
row.setCargoName(waybill.getCargoName());
|
||||
row.setCargoType(waybill.getCargoType());
|
||||
row.setMasterNo(waybill.getWaybillNo());
|
||||
result.add(row);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void createWaybill(MasterOrder masterOrder, List<Map<String, Object>> dispatches) {
|
||||
Map<String, Object> dispatch = dispatches.get(0);
|
||||
Waybill waybill = new Waybill();
|
||||
waybill.setProjectId(masterOrder.getProjectId()); waybill.setProjectName(masterOrder.getProjectName());
|
||||
waybill.setContractId(masterOrder.getContractId()); waybill.setContractName(masterOrder.getContractName());
|
||||
waybill.setCustomerName(masterOrder.getCustomerName()); waybill.setMasterNo(masterOrder.getMasterNo());
|
||||
waybill.setTransportType(string(dispatch, "transportType")); waybill.setCarrierType(string(dispatch, "carrierType"));
|
||||
waybill.setCarrierName(string(dispatch, "carrierName")); waybill.setDriverName(string(dispatch, "driverName"));
|
||||
waybill.setDriverPhone(string(dispatch, "driverPhone")); waybill.setVehicleNo(string(dispatch, "vehicleNo")); waybill.setTrailerVehicleNo(string(dispatch, "trailerVehicleNo"));
|
||||
waybill.setEscortName(string(dispatch, "escortName")); waybill.setEscortPhone(string(dispatch, "escortPhone")); waybill.setMileage(decimal(dispatch, "mileage"));
|
||||
waybill.setDepartureName(string(dispatch, "departureName")); waybill.setDepartureAddress(string(dispatch, "departureAddress"));
|
||||
waybill.setArrivalName(string(dispatch, "arrivalName")); waybill.setArrivalAddress(string(dispatch, "arrivalAddress"));
|
||||
waybill.setCargoName(joinGoodsField(dispatches, "cargoName")); waybill.setCargoType(joinGoodsField(dispatches, "cargoType"));
|
||||
BigDecimal quantity = dispatches.stream().map(item -> decimal(item, "quantity")).reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
BigDecimal unitPrice = decimal(dispatch, "unitPrice");
|
||||
BigDecimal freightTotal = dispatches.stream().map(item -> decimal(item, "quantity").multiply(decimal(item, "unitPrice"))).reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
waybill.setQuantity(quantity); waybill.setQuantityUnit(string(dispatch, "quantityUnit", "吨"));
|
||||
waybill.setEstimatedStartTime(date(dispatch, "estimatedStartTime")); waybill.setEstimatedEndTime(date(dispatch, "estimatedEndTime"));
|
||||
waybill.setUnitPrice(unitPrice); waybill.setPriceUnit(string(dispatch, "priceUnit"));
|
||||
waybill.setGoodsJson(JsonUtil.toJson(dispatches));
|
||||
waybill.setFreightJson(JsonUtil.toJson(Map.of("weightTotal", quantity, "freightTotal", freightTotal))); waybill.setBusinessStatus("pending");
|
||||
waybill.setTaskEntryMode(string(dispatch, "documentType", "运单")); waybill.setRelationNo(string(dispatch, "relationNo")); waybill.setCarrierJson(JsonUtil.toJson(dispatch));
|
||||
waybillService.submit(waybill);
|
||||
}
|
||||
|
||||
private void createTransportPlan(MasterOrder masterOrder, List<Map<String, Object>> dispatches) {
|
||||
Map<String, Object> first = dispatches.get(0);
|
||||
TransportPlan plan = new TransportPlan();
|
||||
plan.setMasterNo(masterOrder.getMasterNo()); plan.setRelationNo(string(first, "segmentNo"));
|
||||
plan.setPlanName(masterOrder.getMasterNo() + "-" + string(first, "segmentNo") + "运输计划");
|
||||
plan.setProjectId(masterOrder.getProjectId()); plan.setProjectName(masterOrder.getProjectName());
|
||||
plan.setContractId(masterOrder.getContractId()); plan.setContractName(masterOrder.getContractName()); plan.setCustomerName(masterOrder.getCustomerName());
|
||||
plan.setTransportType(string(first, "transportType"));
|
||||
plan.setDepartureName(string(first, "departureName")); plan.setDepartureAddress(string(first, "departureAddress"));
|
||||
plan.setDepartureContact(string(first, "departureContact")); plan.setDeparturePhone(string(first, "departurePhone"));
|
||||
plan.setArrivalName(string(first, "arrivalName")); plan.setArrivalAddress(string(first, "arrivalAddress"));
|
||||
plan.setArrivalContact(string(first, "arrivalContact")); plan.setArrivalPhone(string(first, "arrivalPhone"));
|
||||
plan.setPlanStartDate(date(first, "estimatedStartTime")); plan.setPlanEndDate(date(first, "estimatedEndTime"));
|
||||
plan.setGoodsJson(JsonUtil.toJson(dispatches)); plan.setDataSource("多联总单调度"); plan.setBusinessStatus("waiting_dispatch");
|
||||
plan.setRemark(string(first, "remark"));
|
||||
transportPlanService.submit(plan);
|
||||
}
|
||||
|
||||
private void refreshStatus(MasterOrder masterOrder) {
|
||||
Map<String, BigDecimal> available = availableGoods(masterOrder);
|
||||
List<Map<String, Object>> routes = parseArray(masterOrder.getRouteJson());
|
||||
boolean completed = !routes.isEmpty() && !available.isEmpty() && routes.stream().allMatch(route -> isSegmentComplete(masterOrder.getMasterNo(), string(route, "segmentNo"), available));
|
||||
masterOrder.setBusinessStatus(completed ? "completed" : "dispatching");
|
||||
updateById(masterOrder);
|
||||
}
|
||||
|
||||
private LambdaQueryWrapper<MasterOrder> buildQuery(MasterOrderVO query) {
|
||||
LambdaQueryWrapper<MasterOrder> wrapper = new LambdaQueryWrapper<>();
|
||||
if (Func.isNotEmpty(query.getMasterNos())) wrapper.in(MasterOrder::getMasterNo, Arrays.asList(query.getMasterNos().split(",")));
|
||||
if (Func.isNotEmpty(query.getProjectName())) wrapper.like(MasterOrder::getProjectName, query.getProjectName());
|
||||
if (Func.isNotEmpty(query.getCustomerName())) wrapper.like(MasterOrder::getCustomerName, query.getCustomerName());
|
||||
if (Func.isNotEmpty(query.getBusinessStatus())) wrapper.eq(MasterOrder::getBusinessStatus, query.getBusinessStatus());
|
||||
if (Func.isNotEmpty(query.getCargoName())) wrapper.like(MasterOrder::getGoodsJson, query.getCargoName());
|
||||
if (Func.isNotEmpty(query.getCargoType())) wrapper.like(MasterOrder::getGoodsJson, query.getCargoType());
|
||||
if (Func.isNotEmpty(query.getDepartureAddress())) wrapper.like(MasterOrder::getDepartureAddress, query.getDepartureAddress());
|
||||
if (Func.isNotEmpty(query.getArrivalAddress())) wrapper.like(MasterOrder::getArrivalAddress, query.getArrivalAddress());
|
||||
if (query.getPlanStartTime() != null) wrapper.ge(MasterOrder::getPlanStartTime, query.getPlanStartTime());
|
||||
if (query.getPlanEndTime() != null) wrapper.le(MasterOrder::getPlanEndTime, query.getPlanEndTime());
|
||||
return wrapper.orderByDesc(MasterOrder::getCreateTime);
|
||||
}
|
||||
|
||||
private MasterOrderVO toVO(MasterOrder entity) {
|
||||
MasterOrderVO vo = Objects.requireNonNull(BeanUtil.copyProperties(entity, MasterOrderVO.class));
|
||||
ContractManage contract = resolveContract(entity);
|
||||
vo.setContractNo(contract == null ? null : contract.getContractNo());
|
||||
vo.setRoutes(parseArray(entity.getRouteJson())); vo.setGoods(parseArray(entity.getGoodsJson())); vo.setTotalQuantity(totalQuantity(vo.getGoods()));
|
||||
vo.setRouteProgress(buildProgress(entity, vo.getRoutes()));
|
||||
vo.setCreateUserName(UserCache.getUserRealName(entity.getCreateUser())); vo.setUpdateUserName(UserCache.getUserRealName(entity.getUpdateUser()));
|
||||
return vo;
|
||||
}
|
||||
|
||||
private ContractManage resolveContract(MasterOrder masterOrder) {
|
||||
if (Func.isNotEmpty(masterOrder.getContractId())) {
|
||||
ContractManage contract = contractManageService.getById(masterOrder.getContractId());
|
||||
if (contract != null) return contract;
|
||||
}
|
||||
if (Func.isEmpty(masterOrder.getContractName())) return null;
|
||||
LambdaQueryWrapper<ContractManage> query = new LambdaQueryWrapper<ContractManage>().eq(ContractManage::getContractName, masterOrder.getContractName());
|
||||
if (Func.isNotEmpty(masterOrder.getProjectId())) query.eq(ContractManage::getProjectId, masterOrder.getProjectId());
|
||||
return contractManageService.getOne(query, false);
|
||||
}
|
||||
|
||||
private List<Map<String, Object>> buildProgress(MasterOrder masterOrder, List<Map<String, Object>> routes) {
|
||||
List<Waybill> waybills = waybillService.list(new LambdaQueryWrapper<Waybill>().eq(Waybill::getMasterNo, masterOrder.getMasterNo()));
|
||||
List<TransportPlan> plans = transportPlanService.list(new LambdaQueryWrapper<TransportPlan>().eq(TransportPlan::getMasterNo, masterOrder.getMasterNo()));
|
||||
for (Map<String, Object> route : routes) {
|
||||
String segmentNo = string(route, "segmentNo");
|
||||
route.put("dispatchedQuantity", dispatchedQuantity(masterOrder.getMasterNo(), segmentNo));
|
||||
route.put("dispatchedGoods", dispatchedGoods(masterOrder.getMasterNo(), segmentNo));
|
||||
route.put("arrivedQuantity", waybills.stream().filter(item -> Objects.equals(segmentNo, item.getRelationNo()) && "completed".equals(item.getBusinessStatus())).map(Waybill::getQuantity).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add));
|
||||
route.put("waybills", waybills.stream().filter(item -> Objects.equals(segmentNo, item.getRelationNo())).toList());
|
||||
route.put("transportPlans", plans.stream().filter(item -> Objects.equals(segmentNo, item.getRelationNo())).toList());
|
||||
}
|
||||
return routes;
|
||||
}
|
||||
|
||||
private void copyWritable(MasterOrderVO source, MasterOrder target) {
|
||||
target.setProjectId(source.getProjectId()); target.setProjectName(source.getProjectName()); target.setContractId(source.getContractId()); target.setContractName(source.getContractName()); target.setCustomerName(source.getCustomerName());
|
||||
target.setDepartureName(source.getDepartureName()); target.setDepartureAddress(source.getDepartureAddress()); target.setDepartureContact(source.getDepartureContact()); target.setDeparturePhone(source.getDeparturePhone());
|
||||
target.setArrivalName(source.getArrivalName()); target.setArrivalAddress(source.getArrivalAddress()); target.setArrivalContact(source.getArrivalContact()); target.setArrivalPhone(source.getArrivalPhone());
|
||||
target.setPlanStartTime(source.getPlanStartTime()); target.setPlanEndTime(source.getPlanEndTime()); target.setRouteJson(JsonUtil.toJson(source.getRoutes())); target.setGoodsJson(JsonUtil.toJson(source.getGoods())); target.setAttachmentsJson(source.getAttachmentsJson()); target.setRemark(source.getRemark());
|
||||
}
|
||||
|
||||
private void assertRestrictedEdit(MasterOrder oldRecord, MasterOrderVO request) {
|
||||
if (!Objects.equals(oldRecord.getProjectId(), request.getProjectId()) || !Objects.equals(oldRecord.getRouteJson(), JsonUtil.toJson(request.getRoutes()))) throw new ServiceException("调度中或调度完成的总单不能修改基本信息和路线");
|
||||
}
|
||||
|
||||
private void validate(MasterOrder masterOrder, boolean draft) {
|
||||
TransportBusinessSupport.validateRequired(masterOrder.getProjectName(), "项目不能为空");
|
||||
if (Func.isEmpty(masterOrder.getProjectId())) throw new ServiceException("项目不能为空");
|
||||
ProjectApply project = projectApplyService.getById(masterOrder.getProjectId());
|
||||
if (project == null || (!"approved".equals(project.getApprovalStatus()) && !"change_approved".equals(project.getApprovalStatus()))) {
|
||||
throw new ServiceException("仅审批通过或变更审批通过的项目可创建总单");
|
||||
}
|
||||
if (!draft) {
|
||||
if (Func.isEmpty(masterOrder.getContractId())) throw new ServiceException("客户合同不能为空");
|
||||
ContractManage contract = contractManageService.getById(masterOrder.getContractId());
|
||||
if (contract == null || !Objects.equals(contract.getProjectId(), project.getId())) throw new ServiceException("客户合同不属于当前项目");
|
||||
}
|
||||
TransportBusinessSupport.validateRequired(masterOrder.getDepartureAddress(), "发货地址不能为空");
|
||||
TransportBusinessSupport.validateRequired(masterOrder.getArrivalAddress(), "收货地址不能为空");
|
||||
TransportBusinessSupport.validateLength(masterOrder.getRemark(), 200, "备注不能超过200字");
|
||||
TransportBusinessSupport.validatePhone(masterOrder.getDeparturePhone(), "发货联系方式格式不正确"); TransportBusinessSupport.validatePhone(masterOrder.getArrivalPhone(), "收货联系方式格式不正确");
|
||||
if (!draft && totalQuantity(parseArray(masterOrder.getGoodsJson())).compareTo(BigDecimal.ZERO) <= 0) throw new ServiceException("请至少维护一条数量大于0的货物");
|
||||
}
|
||||
|
||||
private MasterOrder getRequired(Long id) { MasterOrder item = getById(id); if (item == null) throw new ServiceException("总单不存在"); return item; }
|
||||
private List<Map<String, Object>> parseArray(String json) { if (Func.isEmpty(json)) return new ArrayList<>(); try { return JsonUtil.parse(json, List.class); } catch (Exception exception) { return new ArrayList<>(); } }
|
||||
private void validateDispatches(MasterOrder masterOrder, List<Map<String, Object>> dispatches) {
|
||||
Map<String, BigDecimal> available = availableGoods(masterOrder);
|
||||
Map<String, Map<String, BigDecimal>> usedBySegment = new LinkedHashMap<>();
|
||||
for (Map<String, Object> dispatch : dispatches) {
|
||||
String segmentNo = string(dispatch, "segmentNo");
|
||||
if (Func.isEmpty(segmentNo)) throw new ServiceException("调度分段不能为空");
|
||||
String key = goodsKey(dispatch);
|
||||
BigDecimal quantity = decimal(dispatch, "quantity");
|
||||
if (quantity.compareTo(BigDecimal.ZERO) <= 0) throw new ServiceException("本次数量必须大于0");
|
||||
if (!available.containsKey(key)) throw new ServiceException("调度货物必须来自总单配置");
|
||||
LocalDate startDate = date(dispatch, "estimatedStartTime"); LocalDate endDate = date(dispatch, "estimatedEndTime");
|
||||
if (startDate == null || endDate == null) throw new ServiceException("预计发货日期和预计完成日期不能为空");
|
||||
if (startDate.isAfter(endDate)) throw new ServiceException("预计发货日期不能晚于预计完成日期");
|
||||
if ("运单".equals(string(dispatch, "documentType", "运单"))) validateCarrier(dispatch);
|
||||
Map<String, BigDecimal> used = usedBySegment.computeIfAbsent(segmentNo, value -> dispatchedGoods(masterOrder.getMasterNo(), value));
|
||||
used.merge(key, quantity, BigDecimal::add);
|
||||
if (used.get(key).compareTo(available.get(key)) > 0) throw new ServiceException("货物【" + string(dispatch, "cargoName") + "】的调度数量超过可调度数量");
|
||||
}
|
||||
}
|
||||
private Map<String, BigDecimal> availableGoods(MasterOrder masterOrder) {
|
||||
Map<String, BigDecimal> available = new LinkedHashMap<>();
|
||||
for (Map<String, Object> goods : parseArray(masterOrder.getGoodsJson())) available.merge(goodsKey(goods), decimal(goods, "quantity"), BigDecimal::add);
|
||||
return available;
|
||||
}
|
||||
private boolean isSegmentComplete(String masterNo, String segmentNo, Map<String, BigDecimal> available) {
|
||||
Map<String, BigDecimal> dispatched = dispatchedGoods(masterNo, segmentNo);
|
||||
return available.entrySet().stream().allMatch(item -> dispatched.getOrDefault(item.getKey(), BigDecimal.ZERO).compareTo(item.getValue()) >= 0);
|
||||
}
|
||||
private void validateCarrier(Map<String, Object> dispatch) {
|
||||
String carrierType = string(dispatch, "carrierType", "承运商");
|
||||
if (Func.isEmpty(string(dispatch, "vehicleNo"))) throw new ServiceException("运单车牌号不能为空");
|
||||
if ("承运商".equals(carrierType)) {
|
||||
if (Func.isEmpty(string(dispatch, "carrierName"))) throw new ServiceException("运单承运商不能为空");
|
||||
return;
|
||||
}
|
||||
if (Func.isEmpty(string(dispatch, "driverName")) || Func.isEmpty(string(dispatch, "driverPhone")) || Func.isEmpty(string(dispatch, "trailerVehicleNo")) || Func.isEmpty(string(dispatch, "escortName")) || Func.isEmpty(string(dispatch, "escortPhone")) || Func.isEmpty(string(dispatch, "mileage")) || decimal(dispatch, "mileage").compareTo(BigDecimal.ZERO) < 0) throw new ServiceException("自运或网货平台的车辆与人员信息不完整");
|
||||
}
|
||||
private BigDecimal dispatchedQuantity(String masterNo, String segmentNo) {
|
||||
return dispatchedGoods(masterNo, segmentNo).values().stream().reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
}
|
||||
private Map<String, BigDecimal> dispatchedGoods(String masterNo, String segmentNo) {
|
||||
Map<String, BigDecimal> result = new LinkedHashMap<>();
|
||||
LambdaQueryWrapper<Waybill> billQuery = new LambdaQueryWrapper<Waybill>().eq(Waybill::getMasterNo, masterNo);
|
||||
if (Func.isNotEmpty(segmentNo)) billQuery.eq(Waybill::getRelationNo, segmentNo);
|
||||
for (Waybill bill : waybillService.list(billQuery)) {
|
||||
List<Map<String, Object>> goods = parseArray(bill.getGoodsJson());
|
||||
if (goods.isEmpty()) goods = List.of(Map.of("cargoName", bill.getCargoName(), "cargoType", bill.getCargoType(), "quantity", bill.getQuantity()));
|
||||
for (Map<String, Object> goodsItem : goods) result.merge(goodsKey(goodsItem), decimal(goodsItem, "quantity"), BigDecimal::add);
|
||||
}
|
||||
LambdaQueryWrapper<TransportPlan> planQuery = new LambdaQueryWrapper<TransportPlan>().eq(TransportPlan::getMasterNo, masterNo);
|
||||
if (Func.isNotEmpty(segmentNo)) planQuery.eq(TransportPlan::getRelationNo, segmentNo);
|
||||
for (TransportPlan plan : transportPlanService.list(planQuery)) for (Map<String, Object> goods : parseArray(plan.getGoodsJson())) result.merge(goodsKey(goods), decimal(goods, "quantity"), BigDecimal::add);
|
||||
return result;
|
||||
}
|
||||
private BigDecimal totalQuantity(List<Map<String, Object>> goods) { return goods.stream().map(item -> decimal(item, "quantity")).reduce(BigDecimal.ZERO, BigDecimal::add); }
|
||||
private String waybillGroupKey(Map<String, Object> dispatch) { return String.join("\u0000", string(dispatch, "segmentNo", ""), string(dispatch, "carrierType", ""), string(dispatch, "carrierName", ""), string(dispatch, "driverName", ""), string(dispatch, "vehicleNo", "")); }
|
||||
private String joinGoodsField(List<Map<String, Object>> dispatches, String field) { return dispatches.stream().map(item -> string(item, field, "")).filter(Func::isNotEmpty).distinct().reduce((left, right) -> left + "、" + right).orElse(""); }
|
||||
private BigDecimal decimal(Map<String, Object> values, String key) { try { return new BigDecimal(string(values, key, "0")); } catch (Exception exception) { return BigDecimal.ZERO; } }
|
||||
private String goodsKey(Map<String, Object> values) { return goodsKey(string(values, "cargoName"), string(values, "cargoType")); }
|
||||
private String goodsKey(String cargoName, String cargoType) { return String.valueOf(cargoName) + "\u0000" + String.valueOf(cargoType); }
|
||||
private LocalDate date(Map<String, Object> values, String key) { String value = string(values, key); if (Func.isEmpty(value)) return null; try { return LocalDate.parse(value.substring(0, 10)); } catch (Exception exception) { throw new ServiceException("日期格式不正确"); } }
|
||||
private String string(Map<String, Object> values, String key) { return string(values, key, null); }
|
||||
private String string(Map<String, Object> values, String key, String fallback) { Object value = values.get(key); return value == null ? fallback : String.valueOf(value); }
|
||||
private synchronized String nextCode() { String prefix = "DL" + LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE); long count = count(new LambdaQueryWrapper<MasterOrder>().likeRight(MasterOrder::getMasterNo, prefix)); return prefix + String.format("%04d", count + 1); }
|
||||
}
|
||||
+11
@@ -47,6 +47,7 @@ import java.math.RoundingMode;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@@ -277,6 +278,16 @@ public class ProjectApplyServiceImpl extends BaseServiceImpl<ProjectApplyMapper,
|
||||
.eq(Func.isNotEmpty(projectApply.getApprovalStatus()), ProjectApply::getApprovalStatus, projectApply.getApprovalStatus())
|
||||
.eq(Func.isNotEmpty(projectApply.getEffectiveType()), ProjectApply::getEffectiveType, projectApply.getEffectiveType())
|
||||
.orderByDesc(ProjectApply::getCreateTime);
|
||||
if (Func.isNotEmpty(projectApply.getApprovalStatuses())) {
|
||||
List<String> approvalStatusList = Arrays.stream(projectApply.getApprovalStatuses().split(","))
|
||||
.map(String::trim)
|
||||
.filter(Func::isNotEmpty)
|
||||
.distinct()
|
||||
.toList();
|
||||
if (!approvalStatusList.isEmpty()) {
|
||||
queryWrapper.in(ProjectApply::getApprovalStatus, approvalStatusList);
|
||||
}
|
||||
}
|
||||
if (Boolean.TRUE.equals(projectApply.getTemporaryCreditLimitSelectable())) {
|
||||
queryWrapper.eq(ProjectApply::getEffectiveType, EFFECTIVE_FORMAL)
|
||||
.in(ProjectApply::getApprovalStatus, STATUS_APPROVED, "change_approved");
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
CREATE TABLE IF NOT EXISTS `blade_master_order` (
|
||||
`id` bigint NOT NULL COMMENT '主键', `tenant_id` varchar(12) DEFAULT '000000' COMMENT '租户ID',
|
||||
`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` int DEFAULT 1,
|
||||
`is_deleted` int DEFAULT 0, `master_no` varchar(64) NOT NULL COMMENT '总单号',
|
||||
`project_id` bigint DEFAULT NULL, `project_name` varchar(100) DEFAULT NULL, `contract_id` bigint DEFAULT NULL,
|
||||
`contract_name` varchar(100) DEFAULT NULL, `customer_name` varchar(100) DEFAULT NULL,
|
||||
`transport_organization_type` varchar(32) NOT NULL DEFAULT '多式联运', `business_status` varchar(32) NOT NULL DEFAULT 'draft',
|
||||
`departure_name` varchar(100) DEFAULT NULL, `departure_address` varchar(500) DEFAULT NULL, `departure_contact` varchar(100) DEFAULT NULL, `departure_phone` varchar(32) DEFAULT NULL,
|
||||
`arrival_name` varchar(100) DEFAULT NULL, `arrival_address` varchar(500) DEFAULT NULL, `arrival_contact` varchar(100) DEFAULT NULL, `arrival_phone` varchar(32) DEFAULT NULL,
|
||||
`plan_start_time` datetime DEFAULT NULL, `plan_end_time` datetime DEFAULT NULL, `route_json` text, `goods_json` text, `attachments_json` text, `remark` varchar(200) DEFAULT NULL,
|
||||
PRIMARY KEY (`id`), UNIQUE KEY `uk_master_order_no` (`master_no`), KEY `idx_master_order_status` (`business_status`), KEY `idx_master_order_create_time` (`create_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='多联总单';
|
||||
|
||||
ALTER TABLE `blade_transport_plan`
|
||||
ADD COLUMN `master_no` varchar(100) DEFAULT NULL COMMENT '多联总单号' AFTER `plan_no`,
|
||||
ADD COLUMN `relation_no` varchar(100) DEFAULT NULL COMMENT '总单分段号' AFTER `master_no`;
|
||||
|
||||
INSERT IGNORE INTO `blade_menu` (`id`,`parent_id`,`code`,`name`,`alias`,`path`,`source`,`sort`,`category`,`action`,`is_open`,`component`,`remark`,`is_deleted`) VALUES
|
||||
(2090000000000000800,2090000000000000000,'master_order','总单管理','master_order','/business/master-order','iconfont icon-caidanguanli',80,1,0,1,NULL,'',0),
|
||||
(2090000000000000801,2090000000000000800,'master_order_view','查看','master_order_view','', '',1,2,0,1,NULL,'',0),
|
||||
(2090000000000000802,2090000000000000800,'master_order_add','新增','master_order_add','', '',2,2,0,1,NULL,'',0),
|
||||
(2090000000000000803,2090000000000000800,'master_order_edit','编辑','master_order_edit','', '',3,2,0,1,NULL,'',0),
|
||||
(2090000000000000804,2090000000000000800,'master_order_delete','删除','master_order_delete','', '',4,2,0,1,NULL,'',0),
|
||||
(2090000000000000805,2090000000000000800,'master_order_export','批量导出','master_order_export','', '',5,2,0,1,NULL,'',0),
|
||||
(2090000000000000806,2090000000000000800,'master_order_copy','复制','master_order_copy','', '',6,2,0,1,NULL,'',0),
|
||||
(2090000000000000807,2090000000000000800,'master_order_dispatch','调度','master_order_dispatch','', '',7,2,0,1,NULL,'',0),
|
||||
(2090000000000000808,2090000000000000800,'master_order_close_dispatch','关闭调度','master_order_close_dispatch','', '',8,2,0,1,NULL,'',0);
|
||||
@@ -165,6 +165,8 @@ CREATE TABLE `blade_transport_plan` (
|
||||
`status` int(11) DEFAULT '1' COMMENT '状态',
|
||||
`is_deleted` int(11) DEFAULT '0' COMMENT '是否已删除',
|
||||
`plan_no` varchar(100) DEFAULT NULL COMMENT '计划单号',
|
||||
`master_no` varchar(100) DEFAULT NULL COMMENT '多联总单号',
|
||||
`relation_no` varchar(100) DEFAULT NULL COMMENT '总单分段号',
|
||||
`plan_name` varchar(100) DEFAULT NULL COMMENT '计划名称',
|
||||
`project_id` bigint(20) DEFAULT NULL COMMENT '项目ID',
|
||||
`project_name` varchar(100) DEFAULT NULL COMMENT '项目',
|
||||
|
||||
Reference in New Issue
Block a user