Merge remote-tracking branch 'origin/test' into dev

This commit is contained in:
2026-09-09 13:13:51 +08:00
11 changed files with 179 additions and 19 deletions
@@ -164,12 +164,11 @@ public class TerminalDrayageService {
WorkContext context = requiredLockedContext(containerBookingId);
View replay = replay(context, action, key, request);
if (replay != null) return replay;
TerminalDrayageEntity value = requiredAggregate(containerBookingId);
TerminalDrayageEntity value = mapper.lock(containerBookingId);
requireVersion(value, request.expectedVersion());
LocalDateTime pickupAt = actualPickupAt(context, value);
if (pickupAt == null) throw conflict("实际提柜后才能确认还柜");
if (context.base().unloadingCompletedAt() == null) throw conflict("海外卸柜收货完成后才能确认还柜");
LocalDateTime earliest = pickupAt.isAfter(context.base().unloadingCompletedAt())
LocalDateTime earliest = pickupAt != null && pickupAt.isAfter(context.base().unloadingCompletedAt())
? pickupAt : context.base().unloadingCompletedAt();
if (request.actualAt().isBefore(earliest)) throw badRequest("实际还柜时间不能早于提柜或卸柜完成时间");
if (actualEmptyReturnAt(context, value) != null) throw conflict("实际还柜时间已确认");
@@ -177,6 +176,7 @@ public class TerminalDrayageService {
request.remark(), operatorId, operatorName);
int fromVersion = version(value);
String fromStatus = status(context, value);
if (value == null) value = create(containerBookingId, operatorId, operatorName);
applyAuthoritativeDeadline(context, value);
value.setActualEmptyReturnAt(request.actualAt());
persist(value, fromVersion, operatorId, operatorName);
@@ -277,14 +277,14 @@ public class TerminalDrayageService {
if (current != null && current.sequence() >= CarrierScheduleNode.PICKUP_READY.sequence()
&& value != null && value.getFleetSupplierId() != null && value.getPickupAppointmentAt() != null
&& actualPickupAt(context, value) == null) result.add("RECORD_PICKUP");
if (value != null && context.base().unloadingCompletedAt() != null && actualPickupAt(context, value) != null
if (context.base().unloadingCompletedAt() != null
&& actualEmptyReturnAt(context, value) == null) result.add("RECORD_EMPTY_RETURN");
return List.copyOf(result);
}
private String status(WorkContext context, TerminalDrayageEntity value) {
if (actualEmptyReturnAt(context, value) != null) return "EMPTY_RETURNED";
if (context.base().unloadingCompletedAt() != null && actualPickupAt(context, value) != null) return "UNLOADED";
if (context.base().unloadingCompletedAt() != null) return "UNLOADED";
if (actualPickupAt(context, value) != null) return "PICKED_UP";
if (value != null && value.getPickupAppointmentAt() != null) return "PICKUP_SCHEDULED";
CarrierScheduleNode current = node(context.facts().latestActualNode());
@@ -4,8 +4,8 @@ USE baiyue_firstleg;
CREATE TABLE IF NOT EXISTS fl_terminal_drayage (
id BIGINT UNSIGNED NOT NULL COMMENT '目的港码头拖车聚合ID',
container_booking_id BIGINT UNSIGNED NOT NULL COMMENT '唯一归属物理货柜ID',
fleet_supplier_id BIGINT UNSIGNED NOT NULL COMMENT 'Warehouse ACTIVE/TRUCKING供应商ID快照',
fleet_supplier_name VARCHAR(128) NOT NULL COMMENT 'Warehouse供应商名称快照',
fleet_supplier_id BIGINT UNSIGNED NULL COMMENT 'Warehouse ACTIVE/TRUCKING供应商ID快照',
fleet_supplier_name VARCHAR(128) NULL COMMENT 'Warehouse供应商名称快照',
pickup_appointment_at DATETIME(3) NULL COMMENT '预约提柜时间',
actual_pickup_at DATETIME(3) NULL COMMENT '实际提柜时间',
estimated_empty_return_at DATETIME(3) NULL COMMENT '服务端按卸柜完成时间加72小时计算的预计还柜时间',
@@ -47,6 +47,7 @@ paths:
/api/platform/v1/terminal-drayages/{containerBookingId}/empty-return:
post:
summary: 海外卸柜收货完成后确认实际还柜并推进EMPTY_RETURNED节点
description: 以海外卸柜完成事实开放还柜;无需已有车队、预约或实际提柜记录。无拖车记录时提交 expectedVersion=0,由命令创建记录,不补造提柜时间。实际还柜时间不得早于卸柜完成时间及已有实际提柜时间。
x-permission: platform:firstleg:terminal-drayage:empty-return:confirm
parameters: [{$ref: '#/components/parameters/ContainerBookingId'}, {$ref: '#/components/parameters/IdempotencyKey'}]
requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/ActualTimeRequest'}}}}