优化
This commit is contained in:
+24
@@ -0,0 +1,24 @@
|
||||
package com.baiyue.lastleg.dispatch.fleet;
|
||||
|
||||
import com.baiyue.lastleg.dispatch.port.DispatchContracts;
|
||||
import com.baiyue.lastleg.dispatch.port.DispatchDetailReader;
|
||||
import com.baiyue.lastleg.dispatch.port.DispatchViews;
|
||||
import com.baiyue.lastleg.dispatch.service.DispatchAggregateService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/** 只读委托既有排车应用服务,运营域不引用主流程实现或持久化实体。 */
|
||||
@Component
|
||||
public class DispatchDetailReadAdapter implements DispatchDetailReader {
|
||||
private final DispatchAggregateService dispatch;
|
||||
|
||||
/** 复用唯一原排车详情提供者,不建立第二套查询规则。 */
|
||||
public DispatchDetailReadAdapter(DispatchAggregateService dispatch) {
|
||||
this.dispatch = dispatch;
|
||||
}
|
||||
|
||||
/** 原单资格与可见性仍由原应用服务核对。 */
|
||||
@Override
|
||||
public DispatchViews.Detail detail(DispatchContracts.Actor actor, String dispatchNo) {
|
||||
return dispatch.detail(actor, dispatchNo);
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package com.baiyue.lastleg.dispatch.port;
|
||||
|
||||
/** 原排车详情的只读应用端口,供运营视图组合真实履约事实。 */
|
||||
public interface DispatchDetailReader {
|
||||
/** 沿原排车读取及身份守卫,不创建单据、不推进状态。 */
|
||||
DispatchViews.Detail detail(DispatchContracts.Actor actor, String dispatchNo);
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package com.baiyue.lastleg.dispatch.repository;
|
||||
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import java.time.LocalDate;
|
||||
|
||||
/** 整车单号独立日流水;独立短事务归还序列锁后才进入车辆、司机及货物锁。 */
|
||||
@Repository
|
||||
public class DispatchNumberSequence {
|
||||
private final JdbcTemplate jdbc;
|
||||
|
||||
/** 流水与排车主单使用相同服务数据库。 */
|
||||
public DispatchNumberSequence(JdbcTemplate jdbc) { this.jdbc = jdbc; }
|
||||
|
||||
/** 唯一日期行原子递增,连接内取回本次值;失败不复用已分配的业务号。 */
|
||||
@Transactional(propagation = Propagation.REQUIRES_NEW)
|
||||
public long next(LocalDate date) {
|
||||
jdbc.update("INSERT INTO ll_dispatch_daily_sequence(business_date,last_sequence) VALUES(?,LAST_INSERT_ID(1)) "
|
||||
+ "ON DUPLICATE KEY UPDATE last_sequence=LAST_INSERT_ID(last_sequence+1)", date);
|
||||
Long value = jdbc.queryForObject("SELECT LAST_INSERT_ID()", Long.class);
|
||||
if (value == null || value < 1) throw new IllegalStateException("排车日流水分配失败");
|
||||
if (value > 9999) throw new BizException(ResultCode.CONFLICT, "当日排车单四位流水号已用满");
|
||||
return value;
|
||||
}
|
||||
}
|
||||
+4
-2
@@ -10,6 +10,7 @@ import com.baiyue.lastleg.dispatch.model.DispatchPalletCandidate;
|
||||
import com.baiyue.lastleg.dispatch.port.*;
|
||||
import com.baiyue.lastleg.dispatch.repository.DispatchAggregateRepository;
|
||||
import com.baiyue.lastleg.dispatch.repository.DispatchSourceRepository;
|
||||
import com.baiyue.lastleg.fleet.ownedbusiness.port.OwnedBusinessOwnershipCapture;
|
||||
import com.baiyue.lastleg.fleet.trip.FleetExecutionModels;
|
||||
import com.baiyue.lastleg.fleet.trip.FleetTripExecutionService;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
@@ -31,6 +32,7 @@ import static com.baiyue.lastleg.dispatch.port.DispatchContracts.*;
|
||||
/** 真整车创建、加货与查询;客户只属于明细,不属于整车头。 */
|
||||
@Service
|
||||
public class DispatchAggregateService implements DispatchExecutionPort,DispatchAppointmentAmendmentPort {
|
||||
@jakarta.annotation.Resource private DispatchNumberGenerator numbers;
|
||||
@jakarta.annotation.Resource private DispatchAppointmentAllocations appointments;
|
||||
@jakarta.annotation.Resource private DispatchPhysicalClaimService physicalClaims;
|
||||
@jakarta.annotation.Resource private DispatchItemSourceService relabelSources;
|
||||
@@ -47,7 +49,7 @@ public class DispatchAggregateService implements DispatchExecutionPort,DispatchA
|
||||
private final ObjectMapper json;
|
||||
@jakarta.annotation.Resource private com.baiyue.lastleg.dispatch.gateway.DispatchEvidenceGateway evidence;
|
||||
@jakarta.annotation.Resource private com.baiyue.lastleg.dispatch.repository.DispatchExecutionRepository executionReads;
|
||||
@jakarta.annotation.Resource private com.baiyue.lastleg.fleet.ownedbusiness.port.OwnedBusinessOwnershipCapture ownedBusinessOwnership;
|
||||
@jakarta.annotation.Resource private OwnedBusinessOwnershipCapture ownedBusinessOwnership;
|
||||
|
||||
/** 领域编排只访问Repository和提供者应用入口。 */
|
||||
public DispatchAggregateService(DispatchAggregateRepository repository, DispatchSourceRepository sources,
|
||||
@@ -268,7 +270,7 @@ public class DispatchAggregateService implements DispatchExecutionPort,DispatchA
|
||||
var replay = commands.replay(actor,null,identity);
|
||||
if (replay.isPresent()) return replay.get();
|
||||
long id=IdWorker.getId();
|
||||
String number="DS-"+UUID.randomUUID().toString().replace("-","").toUpperCase(Locale.ROOT);
|
||||
String number=numbers.next();
|
||||
var shell=fleet.createDispatchShell(actor.operatorId(),new FleetExecutionModels.DispatchShellRequest(id,number,
|
||||
Long.parseLong(request.warehouseId()),request.idempotencyKey(),identity.commandHash(),request.plannedDepartureAt(),
|
||||
fleetVehicle(request.vehicle()),fleetDriver(request.driver()),request.remark()));
|
||||
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package com.baiyue.lastleg.dispatch.service;
|
||||
|
||||
import com.baiyue.lastleg.dispatch.repository.DispatchNumberSequence;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import java.time.Clock;
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Locale;
|
||||
|
||||
/** 平台排车单号:PC、北京时间日期与每日独立四位流水。 */
|
||||
@Component
|
||||
public class DispatchNumberGenerator {
|
||||
private static final ZoneId BUSINESS_ZONE = ZoneId.of("Asia/Shanghai");
|
||||
private final DispatchNumberSequence sequences;
|
||||
private final Clock clock;
|
||||
|
||||
/** 正式运行按统一业务时区取当天日期。 */
|
||||
@Autowired
|
||||
public DispatchNumberGenerator(DispatchNumberSequence sequences) { this(sequences, Clock.systemUTC()); }
|
||||
|
||||
/** 固定时钟用于核实跨午夜归零边界。 */
|
||||
DispatchNumberGenerator(DispatchNumberSequence sequences, Clock clock) { this.sequences = sequences; this.clock = clock; }
|
||||
|
||||
/** 已有命令重放由上层账本恢复原号,本方法只用于首次建单。 */
|
||||
public String next() {
|
||||
LocalDate date = LocalDate.now(clock.withZone(BUSINESS_ZONE));
|
||||
long value = sequences.next(date);
|
||||
if (value < 1 || value > 9999) throw new IllegalStateException("排车日流水不在四位范围内");
|
||||
return "PC" + date.format(DateTimeFormatter.BASIC_ISO_DATE) + String.format(Locale.ROOT, "%04d", value);
|
||||
}
|
||||
}
|
||||
+6
-1
@@ -11,7 +11,8 @@ import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.client.RestClient;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Set;
|
||||
import static com.baiyue.lastleg.fleet.ownedbusiness.service.OwnedBusinessSupport.*;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
|
||||
/** File保存照片,本域另行授权原排车及分类引用。 */
|
||||
@Component
|
||||
@@ -76,6 +77,10 @@ public class HttpOwnedBusinessFileGateway implements OwnedBusinessFileGateway {
|
||||
byte[] png={(byte)137,80,78,71,13,10,26,10};if(!"image/png".equals(type) || bytes.length<png.length)return false;
|
||||
for(int i=0;i<png.length;i++)if(bytes[i]!=png[i])return false;return true;
|
||||
}
|
||||
/** 基础设施错误不反向依赖应用Service,保持单向包依赖。 */
|
||||
private static BizException bad(String message){return new BizException(ResultCode.BAD_REQUEST,message);}
|
||||
/** 不明确的文件结果保留为业务冲突,由上传账本记录。 */
|
||||
private static BizException conflict(String message){return new BizException(ResultCode.CONFLICT,message);}
|
||||
public record Envelope(String code,Metadata data) {}
|
||||
public record Metadata(Long fileId,Long tenantId,String businessType,String businessId,String fileName,String contentType,Long sizeBytes,LocalDateTime createdAt) {}
|
||||
}
|
||||
|
||||
+6
-4
@@ -1,6 +1,5 @@
|
||||
package com.baiyue.lastleg.fleet.ownedbusiness.mapper;
|
||||
|
||||
import com.baiyue.lastleg.dispatch.entity.DispatchItem;
|
||||
import com.baiyue.lastleg.fleet.ownedbusiness.model.OwnedBusinessRows.*;
|
||||
import org.apache.ibatis.annotations.*;
|
||||
import java.util.List;
|
||||
@@ -9,7 +8,10 @@ import java.util.List;
|
||||
@Mapper
|
||||
public interface OwnedBusinessQueryMapper {
|
||||
String SOURCE="""
|
||||
SELECT d.*,o.vehicle_id AS owned_vehicle_id,o.resource_mode AS owned_resource_mode,
|
||||
SELECT d.id,d.dispatch_no,d.status,d.stage,d.record_version,d.overseas_warehouse_id,
|
||||
d.overseas_warehouse_code,d.overseas_warehouse_name,d.planned_departure_at,d.loading_completed_at,
|
||||
d.departed_at,d.delivery_completed_at,d.loading_completion_hash,d.loading_confirmation_json,d.resources_json,
|
||||
o.vehicle_id AS owned_vehicle_id,o.resource_mode AS owned_resource_mode,
|
||||
o.vehicle_ownership AS owned_vehicle_ownership,o.vehicle_record_version AS owned_vehicle_record_version,
|
||||
o.loading_completion_hash AS owned_loading_completion_hash,o.loading_completed_at AS owned_loading_completed_at
|
||||
FROM ll_dispatch_order d LEFT JOIN ll_fleet_owned_business_ownership o ON o.dispatch_id=d.id
|
||||
@@ -38,8 +40,8 @@ public interface OwnedBusinessQueryMapper {
|
||||
@Select(SOURCE+" WHERE d.dispatch_no=#{number}")
|
||||
Source find(String number);
|
||||
/** 页内全部货物一次读取,各客户保持原归属。 */
|
||||
@Select("<script>SELECT * FROM ll_dispatch_item WHERE dispatch_order_id IN <foreach collection='ids' item='id' open='(' separator=',' close=')'>#{id}</foreach> ORDER BY dispatch_order_id,id</script>")
|
||||
List<DispatchItem> cargo(@Param("ids") List<Long> ids);
|
||||
@Select("<script>SELECT id,dispatch_order_id,warehouse_pallet_id,customer_id,customer_name,source_key,order_no,inbound_code,pallet_no,destination_type,destination_warehouse_code,destination_warehouse_name,fba_warehouse_code,delivery_address,postal_code,package_count FROM ll_dispatch_item WHERE dispatch_order_id IN <foreach collection='ids' item='id' open='(' separator=',' close=')'>#{id}</foreach> ORDER BY dispatch_order_id,id</script>")
|
||||
List<CargoRow> cargo(@Param("ids") List<Long> ids);
|
||||
/** 只聚合真实FBA分配,目的仓与数量保持原事实,不按客户重复整车。 */
|
||||
@Select("""
|
||||
<script>SELECT a.dispatch_id,a.destination_key,p.appointment_no,p.confirmation_no,
|
||||
|
||||
+12
-2
@@ -1,6 +1,5 @@
|
||||
package com.baiyue.lastleg.fleet.ownedbusiness.model;
|
||||
|
||||
import com.baiyue.lastleg.dispatch.entity.DispatchAggregate;
|
||||
import lombok.Data;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
@@ -14,11 +13,22 @@ public final class OwnedBusinessRows {
|
||||
private String resourceMode,vehicleOwnership,loadingCompletionHash;
|
||||
private LocalDateTime ownershipCapturedAt,loadingCompletedAt;
|
||||
}
|
||||
@Data public static class Source extends DispatchAggregate {
|
||||
@Data public static class Source {
|
||||
private Long id,recordVersion;
|
||||
private String dispatchNo,status,stage,overseasWarehouseId,overseasWarehouseCode,overseasWarehouseName;
|
||||
private String loadingCompletionHash,loadingConfirmationJson,resourcesJson;
|
||||
private LocalDateTime plannedDepartureAt,loadingCompletedAt,departedAt,deliveryCompletedAt;
|
||||
private String ownedResourceMode,ownedVehicleOwnership,ownedLoadingCompletionHash;
|
||||
private Long ownedVehicleId,ownedVehicleRecordVersion;
|
||||
private LocalDateTime ownedLoadingCompletedAt;
|
||||
}
|
||||
/** 原表货物只读字段;不是可写排车实体,不复制来源状态机。 */
|
||||
@Data public static class CargoRow {
|
||||
private Long id,dispatchOrderId,warehousePalletId;
|
||||
private String customerId,customerName,sourceKey,orderNo,inboundCode,palletNo;
|
||||
private String destinationType,destinationWarehouseCode,destinationWarehouseName,fbaWarehouseCode,deliveryAddress,postalCode;
|
||||
private Integer packageCount;
|
||||
}
|
||||
@Data public static class Record {
|
||||
private Long id,dispatchId,recordVersion;
|
||||
private String currency,arrivalsJson,photosJson,createdBy,updatedBy;
|
||||
|
||||
+1
-2
@@ -4,7 +4,6 @@ import com.baiyue.lastleg.fleet.ownedbusiness.mapper.OwnedBusinessMapper;
|
||||
import com.baiyue.lastleg.fleet.ownedbusiness.mapper.OwnedBusinessQueryMapper;
|
||||
import com.baiyue.lastleg.fleet.ownedbusiness.model.OwnedBusinessRows.*;
|
||||
import com.baiyue.lastleg.fleet.ownedbusiness.model.OwnedBusinessRows.Record;
|
||||
import com.baiyue.lastleg.dispatch.entity.DispatchItem;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import java.util.List;
|
||||
|
||||
@@ -26,7 +25,7 @@ public class OwnedBusinessRepository {
|
||||
/** 读取唯一原排车身份及归属证据。 */
|
||||
public Source source(String number){return queries.find(number);}
|
||||
/** 页内货物批量查询。 */
|
||||
public List<DispatchItem> cargo(List<Long> ids){return ids.isEmpty()?List.of():queries.cargo(ids);}
|
||||
public List<CargoRow> cargo(List<Long> ids){return ids.isEmpty()?List.of():queries.cargo(ids);}
|
||||
/** 页内实际预约分配批量查询。 */
|
||||
public List<Batch> batches(List<Long> ids){return ids.isEmpty()?List.of():queries.batches(ids);}
|
||||
/** GET只读,无记录保留虚拟0版本。 */
|
||||
|
||||
+1
@@ -30,6 +30,7 @@ public class OwnedBusinessOwnershipService implements OwnedBusinessOwnershipCapt
|
||||
var row=new Ownership();row.setDispatchId(dispatchId);row.setVehicleId(vehicleId);row.setResourceMode(resourceMode);
|
||||
if("MASTER".equals(resourceMode)) {
|
||||
var vehicle=vehicles.findById(vehicleId).orElseThrow(()->conflict("原车辆主档不存在"));
|
||||
if(vehicle.id()!=vehicleId.longValue())throw conflict("车辆归属提供者返回的身份不一致");
|
||||
if(vehicle.recordVersion()<0)throw conflict("原车辆档案版本无效");
|
||||
if(!"OWNED".equals(vehicle.ownership()) && !"DISPATCHED".equals(vehicle.ownership()))throw conflict("车辆归属未明确");
|
||||
row.setVehicleOwnership(vehicle.ownership());row.setVehicleRecordVersion(vehicle.recordVersion());
|
||||
|
||||
+4
-4
@@ -1,7 +1,7 @@
|
||||
package com.baiyue.lastleg.fleet.ownedbusiness.service;
|
||||
|
||||
import com.baiyue.lastleg.dispatch.entity.DispatchItem;
|
||||
import com.baiyue.lastleg.dispatch.port.DispatchContracts.*;
|
||||
import com.baiyue.lastleg.dispatch.port.DispatchContracts.ConfirmedLoad;
|
||||
import com.baiyue.lastleg.dispatch.port.DispatchContracts.Resources;
|
||||
import com.baiyue.lastleg.fleet.trip.FleetDispatchGateway;
|
||||
import com.baiyue.lastleg.fleet.ownedbusiness.model.OwnedBusinessModels.*;
|
||||
import com.baiyue.lastleg.fleet.ownedbusiness.model.OwnedBusinessRows.*;
|
||||
@@ -27,7 +27,7 @@ public class OwnedBusinessProjection {
|
||||
/** 照片已绑定才出现在当前记录,无文件不生成占位图。 */
|
||||
public List<Photo> photos(Record record){return record==null?List.of():codec.list(record.getPhotosJson(),Photo.class);}
|
||||
/** 使用原货物确定目的地key,保持与排车及到达登记一致。 */
|
||||
public String destinationKey(DispatchItem item) {
|
||||
public String destinationKey(CargoRow item) {
|
||||
String key=FleetDispatchGateway.destinationKey(item.getDestinationType(),item.getDestinationWarehouseCode(),
|
||||
item.getFbaWarehouseCode(),item.getDeliveryAddress(),item.getPostalCode());
|
||||
if(key==null || key.isBlank())throw OwnedBusinessSupport.conflict("原货物目的地事实缺失");return key;
|
||||
@@ -44,7 +44,7 @@ public class OwnedBusinessProjection {
|
||||
return ids.size();
|
||||
}
|
||||
/** 一车一行;目的地与预约只行内展开,不重复整车金额或托数。 */
|
||||
public Row row(Source source,Record record,List<DispatchItem> cargo,List<Batch> batches,Actor actor) {
|
||||
public Row row(Source source,Record record,List<CargoRow> cargo,List<Batch> batches,Actor actor) {
|
||||
var resources=codec.read(source.getResourcesJson(),Resources.class);
|
||||
if(resources.vehicle()==null)throw OwnedBusinessSupport.conflict("原车辆快照缺失");
|
||||
var ops=operations(record);var photos=photos(record);int pallets=palletCount(source);
|
||||
|
||||
+3
-3
@@ -5,7 +5,7 @@ import com.baiyue.lastleg.fleet.ownedbusiness.model.OwnedBusinessRows.Source;
|
||||
import com.baiyue.lastleg.fleet.ownedbusiness.model.OwnedBusinessRows.Search;
|
||||
import com.baiyue.lastleg.fleet.ownedbusiness.model.OwnedBusinessRows.Record;
|
||||
import com.baiyue.lastleg.fleet.ownedbusiness.repository.OwnedBusinessRepository;
|
||||
import com.baiyue.lastleg.dispatch.service.DispatchAggregateService;
|
||||
import com.baiyue.lastleg.dispatch.port.DispatchDetailReader;
|
||||
import com.baiyue.lastleg.dispatch.port.DispatchContracts;
|
||||
import com.baiyue.lastleg.dispatch.port.DispatchTypes.ActorMode;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -22,10 +22,10 @@ public class OwnedBusinessQueryService {
|
||||
private final OwnedBusinessRepository repository;
|
||||
private final OwnedBusinessProjection projection;
|
||||
private final OwnedBusinessSupport codec;
|
||||
private final DispatchAggregateService dispatch;
|
||||
private final DispatchDetailReader dispatch;
|
||||
/** 主单详情沿既有真实提供者,列表使用本域批量投影。 */
|
||||
public OwnedBusinessQueryService(OwnedBusinessRepository repository,OwnedBusinessProjection projection,
|
||||
OwnedBusinessSupport codec,DispatchAggregateService dispatch){this.repository=repository;this.projection=projection;this.codec=codec;this.dispatch=dispatch;}
|
||||
OwnedBusinessSupport codec,DispatchDetailReader dispatch){this.repository=repository;this.projection=projection;this.codec=codec;this.dispatch=dispatch;}
|
||||
/** 四项统计与分页使用同一过滤和快照,GET不写库。 */
|
||||
@Transactional(readOnly=true,isolation=Isolation.REPEATABLE_READ)
|
||||
public ListResult list(Actor actor,Query query) {
|
||||
|
||||
+13
-1
@@ -60,7 +60,19 @@ public class OwnedBusinessRecordService {
|
||||
photos.put(slot,new Photo(slot,slot.category(),verified.fileId(),verified.fileName(),verified.contentType(),
|
||||
verified.sizeBytes(),verified.uploadedAt(),Instant.now().toString()));
|
||||
row.setPhotosJson(codec.write(List.copyOf(photos.values())));
|
||||
return commit(source,row,actor,version,"BIND_PHOTO",request.idempotencyKey(),identity,"保存"+slot.name()+"照片");
|
||||
return commit(source,row,actor,version,"BIND_PHOTO",request.idempotencyKey(),identity,photoReason(slot));
|
||||
}
|
||||
/** 操作记录使用准确中文槽位,不向用户展示内部枚举。 */
|
||||
private String photoReason(PhotoSlot slot) {
|
||||
return switch(slot) {
|
||||
case LOADING_SCENE -> "保存装车现场照片";
|
||||
case LOADING_LIST -> "保存装车清单照片";
|
||||
case PRE_DEPARTURE_CHECK -> "保存出车前检查照片";
|
||||
case DRIVER_LICENSE -> "保存驾驶证附件照片";
|
||||
case RECEIPT -> "保存签收凭证照片";
|
||||
case FUEL_START -> "保存起始油耗表盘照片";
|
||||
case FUEL_END -> "保存结束油耗表盘照片";
|
||||
};
|
||||
}
|
||||
/** 照片框也参与摘要,不能换URL框位复用原键。 */
|
||||
public record PhotoBinding(PhotoSlot slot,BindPhoto request) {}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.baiyue.lastleg.fleet.trip;
|
||||
|
||||
/** 日历专用只读查询投影,真实排车号不属于车次持久化字段。 */
|
||||
public class FleetTripCalendarRow extends FleetTripEntity {
|
||||
private String dispatchNo;
|
||||
|
||||
/** 返回关联整车主单的业务号。 */
|
||||
public String getDispatchNo() { return dispatchNo; }
|
||||
|
||||
/** 仅供日历SQL结果映射,不写入车次表。 */
|
||||
public void setDispatchNo(String dispatchNo) { this.dispatchNo = dispatchNo; }
|
||||
}
|
||||
+5
-4
@@ -11,10 +11,10 @@ import org.springframework.web.server.ResponseStatusException;
|
||||
import java.time.*;
|
||||
import java.util.*;
|
||||
|
||||
/** Read-only transport plans. A trip is never evidence of a receiving appointment confirmation. */
|
||||
/** 排车日历沿真实整车主单读取运输时间,不代表已取得目的仓收货确认。 */
|
||||
@Service
|
||||
public class FleetTripCalendarService {
|
||||
public record Entry(String sourceType, String tripNo, String status, String originWarehouseId,
|
||||
public record Entry(String sourceType, String dispatchNo, String status, String originWarehouseId,
|
||||
String originWarehouseCode, String originWarehouseName, String originCountryCode,
|
||||
String originTimezone, String originWarehouseStatus, Instant plannedDepartureAt,
|
||||
Instant expectedDriverEndAt, Instant departedAt, Instant completedAt,
|
||||
@@ -31,12 +31,13 @@ public class FleetTripCalendarService {
|
||||
this.trips=trips; this.warehouses=warehouses; this.json=json; this.token=token;
|
||||
}
|
||||
|
||||
/** 一次读取真实排车及一次仓库资料查询,按业务地点判定日历归属。 */
|
||||
@Transactional(readOnly=true)
|
||||
public Result calendar(Instant from, Instant to) {
|
||||
if (from==null || to==null || !to.isAfter(from) || Duration.between(from,to).compareTo(Duration.ofDays(44))>0)
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST,"查询须为不超过44天的UTC时间范围");
|
||||
var rows=trips.calendar(LocalDateTime.ofInstant(from,ZoneOffset.UTC),LocalDateTime.ofInstant(to,ZoneOffset.UTC));
|
||||
if (rows.size()>2000) throw new ResponseStatusException(HttpStatus.BAD_REQUEST,"车次过多,请缩小查询日期范围");
|
||||
if (rows.size()>2000) throw new ResponseStatusException(HttpStatus.BAD_REQUEST,"排车过多,请缩小查询日期范围");
|
||||
if (rows.isEmpty()) return new Result(List.of(),List.of());
|
||||
var ids=rows.stream().map(FleetTripEntity::getWarehouseId).filter(Objects::nonNull).filter(id->id>0).distinct().sorted().toList();
|
||||
var metadata=metadata(ids);
|
||||
@@ -60,7 +61,7 @@ public class FleetTripCalendarService {
|
||||
throw new IllegalArgumentException("incomplete route");
|
||||
stops=List.copyOf(stops);
|
||||
} catch (Exception invalid) { stops=List.of(); reasons.add("ROUTE_INCOMPLETE"); }
|
||||
var entry=new Entry("TRIP_PLAN",row.getTripNo(),row.getStatus(),row.getWarehouseId()==null?null:row.getWarehouseId().toString(),
|
||||
var entry=new Entry("DISPATCH",row.getDispatchNo(),row.getStatus(),row.getWarehouseId()==null?null:row.getWarehouseId().toString(),
|
||||
warehouse==null?row.getWarehouseCode():warehouse.warehouseCode(),warehouse==null?row.getWarehouseName():warehouse.warehouseName(),
|
||||
country,zone,warehouse==null?null:warehouse.status(),instant(row.getPlannedDepartureAt()),instant(row.getExpectedDriverEndAt()),
|
||||
instant(row.getDepartedAt()),instant(row.getCompletedAt()),stops,List.copyOf(reasons));
|
||||
|
||||
@@ -7,17 +7,18 @@ import org.apache.ibatis.annotations.Select;
|
||||
|
||||
@Mapper
|
||||
public interface FleetTripMapper extends BaseMapper<FleetTripEntity> {
|
||||
/** 预约日历只读海外仓出库派送,取货入仓车次不属于排车类型。 */
|
||||
/** 日历只读真实整车主单的出库派送,一单一条;独立车次及取消主单不进入排车日历。 */
|
||||
@Select("""
|
||||
SELECT trip_no,status,warehouse_id,warehouse_code,warehouse_name,planned_departure_at,
|
||||
expected_driver_end_at,departed_at,completed_at,stops_json
|
||||
FROM ll_fleet_trip
|
||||
WHERE trip_purpose='DELIVERY' AND (
|
||||
(departed_at IS NOT NULL AND departed_at>=#{from} AND departed_at<#{to})
|
||||
OR (departed_at IS NULL AND planned_departure_at>=#{from} AND planned_departure_at<#{to}))
|
||||
ORDER BY COALESCE(departed_at,planned_departure_at),trip_no LIMIT 2001
|
||||
SELECT d.dispatch_no,t.trip_no,t.status,t.warehouse_id,t.warehouse_code,t.warehouse_name,t.planned_departure_at,
|
||||
t.expected_driver_end_at,t.departed_at,t.completed_at,t.stops_json
|
||||
FROM ll_dispatch_order d JOIN ll_fleet_trip t ON t.id=d.trip_id
|
||||
WHERE d.status='ACTIVE' AND d.dispatch_no IS NOT NULL AND TRIM(d.dispatch_no)<>''
|
||||
AND t.trip_purpose='DELIVERY' AND (
|
||||
(t.departed_at IS NOT NULL AND t.departed_at>=#{from} AND t.departed_at<#{to})
|
||||
OR (t.departed_at IS NULL AND t.planned_departure_at>=#{from} AND t.planned_departure_at<#{to}))
|
||||
ORDER BY COALESCE(t.departed_at,t.planned_departure_at),d.dispatch_no LIMIT 2001
|
||||
""")
|
||||
java.util.List<FleetTripEntity> calendar(@Param("from") java.time.LocalDateTime from,@Param("to") java.time.LocalDateTime to);
|
||||
java.util.List<FleetTripCalendarRow> calendar(@Param("from") java.time.LocalDateTime from,@Param("to") java.time.LocalDateTime to);
|
||||
@Select("SELECT * FROM ll_fleet_trip WHERE trip_no=#{tripNo} FOR UPDATE")
|
||||
FleetTripEntity lockByNo(@Param("tripNo") String tripNo);
|
||||
@Select("SELECT * FROM ll_fleet_trip WHERE trip_no=#{tripNo}")
|
||||
|
||||
+3
-2
@@ -18,13 +18,14 @@ import org.springframework.web.server.ResponseStatusException;
|
||||
@RequestMapping("/api/platform/v1/last-leg/fleet/trips")
|
||||
public class PlatformFleetTripController {
|
||||
@jakarta.annotation.Resource private FleetTripCalendarService calendar;
|
||||
/** 预约排车日历沿用真实排车列表读取权限,不要求已退出菜单的车次管理权限。 */
|
||||
@GetMapping("/calendar")
|
||||
@PreAuthorize("hasAuthority('platform:fleet:trip:list')")
|
||||
@PreAuthorize("hasAuthority('platform:overseas-warehouse:dispatch:list')")
|
||||
public ApiResponse<FleetTripCalendarService.Result> calendar(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@RequestParam java.time.Instant from,@RequestParam java.time.Instant to) {
|
||||
operator(user);
|
||||
if (!"PLATFORM".equals(user.appCode()) || !Long.valueOf(0).equals(user.tenantId()))
|
||||
throw new ResponseStatusException(HttpStatus.FORBIDDEN,"仅平台主体可读取车次日历");
|
||||
throw new ResponseStatusException(HttpStatus.FORBIDDEN,"仅平台主体可读取排车日历");
|
||||
return ApiResponse.success(calendar.calendar(from,to));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user