提交
This commit is contained in:
+6
-1
@@ -27,10 +27,13 @@ public class DispatchCompletionService implements DispatchCompletionPort {
|
||||
private final DispatchDeliveryPort delivery;
|
||||
private final DispatchSignalPort signals;
|
||||
private final ObjectMapper json;
|
||||
private final com.baiyue.lastleg.fleet.trip.FleetTripCompletionService completion;
|
||||
/** 仅依赖共同端口,禁止持签收子锁回调父流程。 */
|
||||
public DispatchCompletionService(DispatchAggregateRepository repository,DispatchExecutionRepository reads,FleetTripExecutionService fleet,
|
||||
DispatchExecutionPort execution,DispatchDeliveryPort delivery,DispatchSignalPort signals,ObjectMapper json) {
|
||||
DispatchExecutionPort execution,DispatchDeliveryPort delivery,DispatchSignalPort signals,ObjectMapper json,
|
||||
com.baiyue.lastleg.fleet.trip.FleetTripCompletionService completion) {
|
||||
this.repository=repository;this.reads=reads;this.fleet=fleet;this.execution=execution;this.delivery=delivery;this.signals=signals;this.json=json;
|
||||
this.completion=completion;
|
||||
}
|
||||
/** 新事务先父锁后普通读全部已提交事实,失败回滚后才由worker记录退避。 */
|
||||
@Override @Transactional(propagation=Propagation.REQUIRES_NEW,isolation=Isolation.READ_COMMITTED)
|
||||
@@ -50,6 +53,8 @@ public class DispatchCompletionService implements DispatchCompletionPort {
|
||||
if(repository.history(IdWorker.getId(),row,"NORMAL_DELIVERY_COMPLETED",SYSTEM.operatorId(),null,write(Map.of("dispatch",row,"normalDeliveryFacts",facts)),now)!=1)throw new IllegalStateException("正常签收完成证据未保存");
|
||||
changed=true;
|
||||
}
|
||||
// 全部正常签收后直接结案释放,不再等待人工二次确认。
|
||||
if (facts.allNormallySigned()) completion.completeDelivered(identity.tripNo(), SYSTEM.operatorId());
|
||||
// 不完整的合法部分签收也只确认本版本通知,未来签收产生自己的新信号。
|
||||
signals.acknowledge(signal,Instant.now());
|
||||
return new CompletionResult(row.getDispatchNo(),Stage.valueOf(row.getStage()),row.getRecordVersion(),changed,facts.allNormallySigned(),
|
||||
|
||||
@@ -17,12 +17,8 @@ public final class DriverModels {
|
||||
String ownership, String region, String licenseState,
|
||||
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Instant expectedEnd,
|
||||
@Size(max = 64) String dispatchNo) {
|
||||
/** 保留唯一规范构造器供 MVC 绑定,缺省分页使用第一页、每页二十条。 */
|
||||
public Query { page = page == null ? 1 : page; size = size == null ? 20 : size; }
|
||||
/** 无排车上下文的档案查询保持原有语义。 */
|
||||
public Query(Integer page, Integer size, String keyword, Boolean active, String reviewStatus,
|
||||
String ownership, String region, String licenseState, Instant expectedEnd) {
|
||||
this(page, size, keyword, active, reviewStatus, ownership, region, licenseState, expectedEnd, null);
|
||||
}
|
||||
}
|
||||
|
||||
public record Draft(@NotBlank @Size(max = 128) String name,
|
||||
|
||||
+1
-1
@@ -189,7 +189,7 @@ public class FleetPickupService {
|
||||
trips.recordExecution(trip,request.recordVersion(),"PICKUP_ARRIVED",request.reason(),operator);
|
||||
repository.command(number,request.idempotencyKey(),hash);return tasks(number);
|
||||
}
|
||||
/** 全部送仓后结案运输,车辆和司机仍沿原有入口分别结束任务。 */
|
||||
/** 全部送仓后结案运输,并在同一事务内自动释放车辆和司机。 */
|
||||
@Transactional
|
||||
public FleetTripModels.Detail complete(String number,String operator,FleetPickupModels.Command request) {
|
||||
var trip=pickupTrip(number,true);String hash=commandHash("COMPLETE",request);
|
||||
|
||||
+16
-1
@@ -58,11 +58,26 @@ public class FleetTripCompletionService {
|
||||
Instant completedAt = inspection.outcomes().stream().map(FleetCompletionModels.Outcome::terminalAt).max(Instant::compareTo).orElseThrow();
|
||||
trip.setStatus("COMPLETED"); trip.setCompletedAt(LocalDateTime.ofInstant(completedAt, ZoneOffset.UTC));
|
||||
trip.setCompletionJson(write(new FleetCompletionModels.Completed(request.idempotencyKey(), hash, completedAt, Instant.now(), operator, inspection.outcomes())));
|
||||
// Completion freezes cargo outcomes. It deliberately does not release a driver still returning.
|
||||
// 结案保存全部货物事实,并由统一执行入口同时释放车辆和司机。
|
||||
history.recordExecution(trip, request.recordVersion(), "COMPLETE", request.reason(), operator);
|
||||
return history.detail(tripNo);
|
||||
}
|
||||
|
||||
/** 正常派送汇总后重核全车终态,未全部履约时继续保留占用。 */
|
||||
@Transactional
|
||||
public void completeDelivered(String tripNo, String operator) {
|
||||
var trip = required(tripNo, true);
|
||||
if ("COMPLETED".equals(trip.getStatus())) {
|
||||
if (trip.getVehicleReleaseJson() == null || trip.getDriverReleaseJson() == null)
|
||||
history.recordExecution(trip, trip.getRecordVersion(), "AUTO_RELEASE", "派送完成自动释放", operator);
|
||||
return;
|
||||
}
|
||||
var inspection = inspect(trip, true);
|
||||
if (!inspection.canComplete()) return;
|
||||
complete(tripNo, operator, new FleetCompletionModels.Complete(trip.getRecordVersion(),
|
||||
"AUTO_COMPLETE:" + tripNo, "全部派送完成自动结案"));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public FleetTripModels.Detail release(String tripNo, String operator, FleetCompletionModels.Release request) {
|
||||
var trip = required(tripNo, true);
|
||||
|
||||
@@ -681,8 +681,27 @@ public class FleetTripService {
|
||||
/** 在调用方已有事务内保存执行事实及对应历史。 */
|
||||
@Transactional(propagation = org.springframework.transaction.annotation.Propagation.MANDATORY)
|
||||
public void recordExecution(FleetTripEntity trip, long version, String action, String reason, String operator) {
|
||||
// 完成运输与释放占用同事务保存,任一步失败均不得留下半完成状态。
|
||||
if ("COMPLETED".equals(trip.getStatus())) releaseCompletedResources(trip, operator);
|
||||
persist(trip, version, action, reason, operator);
|
||||
}
|
||||
/** 仅删除本车次占用;保留车辆主档状态及已有释放事实,不影响后续车次。 */
|
||||
private void releaseCompletedResources(FleetTripEntity trip, String operator) {
|
||||
if (trip.getCompletedAt() == null || trip.getCompletionJson() == null) throw conflict("缺少运输完成依据");
|
||||
var stops = read(trip.getStopsJson(), new TypeReference<List<FleetTripModels.Stop>>() {});
|
||||
String location = stops.isEmpty() ? null : stops.get(stops.size()-1).name();
|
||||
if (location == null || location.isBlank()) throw conflict("缺少运输终点");
|
||||
for (String kind : List.of("VEHICLE", "DRIVER")) {
|
||||
boolean vehicle = "VEHICLE".equals(kind);
|
||||
if ((vehicle ? trip.getVehicleReleaseJson() : trip.getDriverReleaseJson()) != null) continue;
|
||||
Long id = vehicle ? trip.getVehicleId() : trip.getDriverId();
|
||||
if (id != null) repository.release(kind, id, trip.getId());
|
||||
String key = "AUTO_COMPLETE:" + trip.getTripNo() + ":" + kind;
|
||||
var released = new FleetCompletionModels.Released(key, hash(key), instant(trip.getCompletedAt()),
|
||||
location, null, operator, clock.instant());
|
||||
if (vehicle) trip.setVehicleReleaseJson(write(released)); else trip.setDriverReleaseJson(write(released));
|
||||
}
|
||||
}
|
||||
/** 按指定模型解析历史 JSON,损坏记录明确报错。 */
|
||||
private <T> T read(String value, Class<T> type) { try { return json.readValue(value, type); } catch (JsonProcessingException e) { throw new IllegalStateException("车次历史读取失败", e); } }
|
||||
/** 按显式集合类型读取资源站点,禁止损坏数据回退为假路线。 */
|
||||
|
||||
@@ -11,7 +11,7 @@ info:
|
||||
车辆和司机资格、容量与实际占用由服务端复验;候选可用不表示已预留,临时外配证据依排车主单规则核验。
|
||||
调度审核后经理货审核才可真实扫码;PDA拣货核平后,平台和PDA均可确认装车完成及实际发车。
|
||||
发车前整车派送安排须完整,正常签收汇总推进排车派送完成;既有异常业务和附加资源能力保留。
|
||||
货物结案不自动释放车辆或司机;各资源任务分别提交实际结束时间、地点和原因,车辆另确认结束状态。
|
||||
全车派送完成自动结案并释放车辆和司机,不需要二次确认;提货全部到仓及本土卡车完成同事务释放,保留车辆主档状态。
|
||||
版本、幂等、客户隔离及事务要求以各实际端点及排车公共契约为准;历史数量不授权当前分配。
|
||||
security:
|
||||
- bearerAuth: []
|
||||
@@ -290,14 +290,14 @@ paths:
|
||||
parameters: [{$ref: '#/components/parameters/TripNo'}]
|
||||
post:
|
||||
summary: 核对全部货物并结案车次
|
||||
description: '权限:platform:fleet:trip:complete;仅IN_TRANSIT,实发数量由全部已签收或已真实退回派送单覆盖;拒收、待退回、取消不算终态完成。不改写客户单、不新增收费、不释放资源;同键同内容重放,旧版本或不同内容拒绝。'
|
||||
description: '权限:platform:fleet:trip:complete;仅IN_TRANSIT,实发数量由全部已签收或已真实退回派送单覆盖;拒收、待退回、取消不算终态完成。不改写客户单、不新增收费、同事务自动释放车辆和司机;同键同内容重放,旧版本或不同内容拒绝。'
|
||||
requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/Complete'}}}}
|
||||
responses: {'200': {$ref: '#/components/responses/Detail'}}
|
||||
/api/platform/v1/last-leg/fleet/trips/{tripNo}/resources/release:
|
||||
parameters: [{$ref: '#/components/parameters/TripNo'}]
|
||||
post:
|
||||
summary: 单独结束车辆或司机本次任务并释放占用
|
||||
description: '权限:platform:fleet:trip:release;货物结案后才允许;实际结束时间不早于货物完成且不晚于当前时间,地点及原因必填。车辆状态与释放同事务更新;司机可继续返程而保持占用。原请求重放不释放后来另一个车次的占用。'
|
||||
description: '权限:platform:fleet:trip:release;货物结案后才允许;实际结束时间不早于货物完成且不晚于当前时间,地点及原因必填。该入口仅保留处理未释放记录,当前完成流程自动释放,不要求二次确认。原请求重放不释放后来另一个车次的占用。'
|
||||
requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/Release'}}}}
|
||||
responses: {'200': {$ref: '#/components/responses/Detail'}}
|
||||
components:
|
||||
|
||||
Reference in New Issue
Block a user