Merge branch 'test' of http://git.buggoglobal.com/java-project/baiyue-firstleg into dev
# Conflicts: # scripts/admin-test-env-modules/phase4-carrier-nodes.mjs
This commit is contained in:
@@ -12,7 +12,7 @@
|
|||||||
| 功能 | 入口 |
|
| 功能 | 入口 |
|
||||||
|---|---|
|
|---|---|
|
||||||
| 内部模块化基线与依赖分析 | `docs/lastleg-internal-modularization-baseline.md`、`scripts/analyze-lastleg-dependencies.mjs`、`scripts/analyze-lastleg-dependencies.test.mjs`、`src/test/resources/architecture/lastleg-slice-dependencies.json`;只约束本服务内部包依赖,不改变部署单元或数据库边界 |
|
| 内部模块化基线与依赖分析 | `docs/lastleg-internal-modularization-baseline.md`、`scripts/analyze-lastleg-dependencies.mjs`、`scripts/analyze-lastleg-dependencies.test.mjs`、`src/test/resources/architecture/lastleg-slice-dependencies.json`;只约束本服务内部包依赖,不改变部署单元或数据库边界 |
|
||||||
| 事件接收、平台订单列表/详情/导出、柜级进度、目的仓调整 | `src/main/java/com/baiyue/lastleg/controller/LastlegOrderController.java`;`/api/platform/v1/last-leg/orders/container-summaries` 按 `sourceContainerBookingId` 服务端聚合全柜订单、入仓码、计划数、未操作数和 `allPlansReady`,`/{sourceContainerBookingId}` 返回柜内全部订单/入仓码/计划状态且不受订单分页截断;`plannedArrivalAt` 只取装柜完成事件 `estimatedWarehouseArrivalAt` 冻结到 `ll_order.estimated_warehouse_at` 的正式事实,按其升序、null 最后、稳定柜 ID 排序;目的仓调整仅接受国内 Warehouse Service 平台公共 `ACTIVE + FBA` 主档,写入前由 `service/FbaWarehouseValidator.java` 经内部只读目录校验 ID/编码/名称一致 |
|
| 事件接收、平台订单列表/详情/导出、柜级进度、目的仓调整 | `src/main/java/com/baiyue/lastleg/controller/LastlegOrderController.java`;`/api/platform/v1/last-leg/orders/container-summaries` 按 `sourceContainerBookingId` 服务端聚合全柜订单、入仓码、计划数、未操作数和 `allPlansReady`,分页前排除 `allPlansReady=true`,`/{sourceContainerBookingId}` 返回柜内全部订单/入仓码/计划状态且不受订单分页截断;`plannedArrivalAt` 只取装柜完成事件 `estimatedWarehouseArrivalAt` 冻结到 `ll_order.estimated_warehouse_at` 的正式事实,按其升序、null 最后、稳定柜 ID 排序;目的仓调整仅接受国内 Warehouse Service 平台公共 `ACTIVE + FBA` 主档,写入前由 `service/FbaWarehouseValidator.java` 经内部只读目录校验 ID/编码/名称一致 |
|
||||||
| 客户尾程订单列表与详情 | `controller/MerchantLastlegOrderController.java`;客户范围只取认证身份,稳定类型/状态见 `model/LastlegOrderType.java`、`LastlegOrderStatus.java` |
|
| 客户尾程订单列表与详情 | `controller/MerchantLastlegOrderController.java`;客户范围只取认证身份,稳定类型/状态见 `model/LastlegOrderType.java`、`LastlegOrderStatus.java` |
|
||||||
| 装柜完成事件消费 | `service/LoadingCompletedConsumer.java`;当前生产信封为 `FIRST_LEG_LOADING_PLAN_COMPLETED` V2,消费端冻结 `destinationType`:FBA 可按箱数且不生成逐箱作业账,OVERSEAS 必须逐箱校验并生成箱级事实;历史缺字段事件仅按明确 FBA 单号或目的仓与接柜仓一致性安全推断,歧义直接拒绝 |
|
| 装柜完成事件消费 | `service/LoadingCompletedConsumer.java`;当前生产信封为 `FIRST_LEG_LOADING_PLAN_COMPLETED` V2,消费端冻结 `destinationType`:FBA 可按箱数且不生成逐箱作业账,OVERSEAS 必须逐箱校验并生成箱级事实;历史缺字段事件仅按明确 FBA 单号或目的仓与接柜仓一致性安全推断,歧义直接拒绝 |
|
||||||
| 尾程单号生成 | `service/LastlegOrderNoGenerator.java` |
|
| 尾程单号生成 | `service/LastlegOrderNoGenerator.java` |
|
||||||
|
|||||||
@@ -20,7 +20,13 @@ public interface LastlegOrderMapper extends BaseMapper<LastlegOrder> {
|
|||||||
"<if test='q.containerNo != null and q.containerNo != \"\"'>AND o.container_no LIKE CONCAT('%',#{q.containerNo},'%') </if>" +
|
"<if test='q.containerNo != null and q.containerNo != \"\"'>AND o.container_no LIKE CONCAT('%',#{q.containerNo},'%') </if>" +
|
||||||
"<if test='q.createdFrom != null'>AND o.created_at >=#{q.createdFrom} </if>" +
|
"<if test='q.createdFrom != null'>AND o.created_at >=#{q.createdFrom} </if>" +
|
||||||
"<if test='q.createdTo != null'>AND o.created_at <=#{q.createdTo} </if>" +
|
"<if test='q.createdTo != null'>AND o.created_at <=#{q.createdTo} </if>" +
|
||||||
"GROUP BY o.source_container_booking_id ";
|
"GROUP BY o.source_container_booking_id " +
|
||||||
|
"HAVING SUM(CASE WHEN NULLIF(TRIM(o.inbound_code),'') IS NULL THEN 1 ELSE 0 END)>0 " +
|
||||||
|
"OR SUM(CASE WHEN p.id IS NULL OR NULLIF(TRIM(p.plan_no),'') IS NULL " +
|
||||||
|
"OR NULLIF(TRIM(p.status),'') IS NULL THEN 1 ELSE 0 END)>0 " +
|
||||||
|
"OR COUNT(DISTINCT p.id)<>COUNT(*) " +
|
||||||
|
"OR COUNT(NULLIF(o.container_no,''))<>COUNT(*) " +
|
||||||
|
"OR COUNT(DISTINCT NULLIF(o.container_no,''))<>1 ";
|
||||||
|
|
||||||
String CONTAINER_PROGRESS_COLUMNS = "SELECT o.source_container_booking_id AS sourceContainerBookingId," +
|
String CONTAINER_PROGRESS_COLUMNS = "SELECT o.source_container_booking_id AS sourceContainerBookingId," +
|
||||||
"CASE WHEN COUNT(NULLIF(o.container_no,''))=COUNT(*) AND COUNT(DISTINCT NULLIF(o.container_no,''))=1 " +
|
"CASE WHEN COUNT(NULLIF(o.container_no,''))=COUNT(*) AND COUNT(DISTINCT NULLIF(o.container_no,''))=1 " +
|
||||||
|
|||||||
@@ -253,7 +253,7 @@ paths:
|
|||||||
/api/platform/v1/last-leg/orders/container-summaries:
|
/api/platform/v1/last-leg/orders/container-summaries:
|
||||||
get:
|
get:
|
||||||
summary: 分页查询柜级入库计划进度
|
summary: 分页查询柜级入库计划进度
|
||||||
description: 权限 platform:overseas-warehouse:order:list;平台跨客户按稳定 sourceContainerBookingId 服务端聚合,不受订单分页影响。containerNo 只返回装柜完成事件携带并规范化冻结的真实集装箱号,不从 cabinetNo、订单号或内部 ID 推导。plannedArrivalAt 仅来自装柜完成事件 estimatedWarehouseArrivalAt 冻结的 ll_order.estimated_warehouse_at;按该时间升序、null 最后、稳定柜 ID 升序。
|
description: 权限 platform:overseas-warehouse:order:list;平台跨客户按稳定 sourceContainerBookingId 服务端聚合,不受订单分页影响,仅返回 allPlansReady=false 的待处理柜,已全部生成有效独立计划的柜在分页前排除。containerNo 只返回装柜完成事件携带并规范化冻结的真实集装箱号,不从 cabinetNo、订单号或内部 ID 推导。plannedArrivalAt 仅来自装柜完成事件 estimatedWarehouseArrivalAt 冻结的 ll_order.estimated_warehouse_at;按该时间升序、null 最后、稳定柜 ID 升序。
|
||||||
security: [{ bearerAuth: [] }]
|
security: [{ bearerAuth: [] }]
|
||||||
parameters:
|
parameters:
|
||||||
- { name: page, in: query, schema: { type: integer, minimum: 1, default: 1 } }
|
- { name: page, in: query, schema: { type: integer, minimum: 1, default: 1 } }
|
||||||
|
|||||||
+4
@@ -38,6 +38,8 @@ class ContainerPlanProgressContractTest {
|
|||||||
String sql = String.join("", rows.value()).toLowerCase();
|
String sql = String.join("", rows.value()).toLowerCase();
|
||||||
assertTrue(sql.contains("left join ll_inbound_plan"));
|
assertTrue(sql.contains("left join ll_inbound_plan"));
|
||||||
assertTrue(sql.contains("o.estimated_warehouse_at"));
|
assertTrue(sql.contains("o.estimated_warehouse_at"));
|
||||||
|
assertTrue(sql.contains("having sum(case when nullif(trim(o.inbound_code),'') is null then 1 else 0 end)>0"));
|
||||||
|
assertTrue(sql.contains("or count(distinct p.id)<>count(*)"));
|
||||||
assertTrue(sql.contains("progress.plannedarrivalat asc"));
|
assertTrue(sql.contains("progress.plannedarrivalat asc"));
|
||||||
assertTrue(sql.contains("progress.sourcecontainerbookingid asc"));
|
assertTrue(sql.contains("progress.sourcecontainerbookingid asc"));
|
||||||
assertTrue(sql.contains("limit #{limit} offset #{offset}"));
|
assertTrue(sql.contains("limit #{limit} offset #{offset}"));
|
||||||
@@ -54,6 +56,7 @@ class ContainerPlanProgressContractTest {
|
|||||||
assertTrue(openApi.contains("/api/platform/v1/last-leg/orders/container-summaries/{sourceContainerBookingId}:"));
|
assertTrue(openApi.contains("/api/platform/v1/last-leg/orders/container-summaries/{sourceContainerBookingId}:"));
|
||||||
assertTrue(openApi.contains("ContainerPlanProgressSummary:"));
|
assertTrue(openApi.contains("ContainerPlanProgressSummary:"));
|
||||||
assertTrue(openApi.contains("allPlansReady:"));
|
assertTrue(openApi.contains("allPlansReady:"));
|
||||||
|
assertTrue(openApi.contains("仅返回 allPlansReady=false 的待处理柜"));
|
||||||
assertTrue(openApi.contains("ll_order.estimated_warehouse_at"));
|
assertTrue(openApi.contains("ll_order.estimated_warehouse_at"));
|
||||||
assertTrue(schema.contains("idx_ll_order_container_progress(source_container_booking_id,estimated_warehouse_at,created_at,id)"));
|
assertTrue(schema.contains("idx_ll_order_container_progress(source_container_booking_id,estimated_warehouse_at,created_at,id)"));
|
||||||
assertTrue(deployment.contains("idx_ll_order_container_progress(source_container_booking_id,estimated_warehouse_at,created_at,id)"));
|
assertTrue(deployment.contains("idx_ll_order_container_progress(source_container_booking_id,estimated_warehouse_at,created_at,id)"));
|
||||||
@@ -72,6 +75,7 @@ class ContainerPlanProgressContractTest {
|
|||||||
|
|
||||||
assertTrue(sql.contains("source_container_booking_id<>''"));
|
assertTrue(sql.contains("source_container_booking_id<>''"));
|
||||||
assertTrue(sql.contains("LEFT JOIN ll_inbound_plan"));
|
assertTrue(sql.contains("LEFT JOIN ll_inbound_plan"));
|
||||||
|
assertTrue(sql.contains("HAVING SUM(CASE WHEN NULLIF(TRIM(o.inbound_code),'') IS NULL THEN 1 ELSE 0 END)>0"));
|
||||||
assertTrue(!sql.contains("<"));
|
assertTrue(!sql.contains("<"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ async function post(context, path, body, operation) {
|
|||||||
return context.request('POST', path, body, context.token, { 'Idempotency-Key': key(context, operation) })
|
return context.request('POST', path, body, context.token, { 'Idempotency-Key': key(context, operation) })
|
||||||
}
|
}
|
||||||
|
|
||||||
async function uploadBillOfLading(context, appointmentId, index) {
|
async function uploadDispatchOrder(context, appointmentId, index) {
|
||||||
const path = `${TRUCK_BASE}/${appointmentId}/attachments/upload?attachmentType=BILL_OF_LADING`
|
const path = `${TRUCK_BASE}/${appointmentId}/attachments/upload?attachmentType=DISPATCH_ORDER`
|
||||||
const form = new FormData()
|
const form = new FormData()
|
||||||
const png = Uint8Array.from([137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,0,1,0,0,0,1,8,6,0,0,0,31,21,196,137,0,0,0,13,73,68,65,84,8,215,99,248,207,192,240,31,0,5,0,1,255,137,153,61,29,0,0,0,0,73,69,78,68,174,66,96,130])
|
const png = Uint8Array.from([137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,0,1,0,0,0,1,8,6,0,0,0,31,21,196,137,0,0,0,13,73,68,65,84,8,215,99,248,207,192,240,31,0,5,0,1,255,137,153,61,29,0,0,0,0,73,69,78,68,174,66,96,130])
|
||||||
form.append('file', new Blob([png], { type: 'image/png' }), `${context.marker}-truck-${index}.png`)
|
form.append('file', new Blob([png], { type: 'image/png' }), `${context.marker}-truck-${index}.png`)
|
||||||
@@ -77,10 +77,30 @@ async function oceanAction(context, value, endpoint, body = {}) {
|
|||||||
return next
|
return next
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function uploadBookingConfirmation(context, value, index) {
|
||||||
|
const bookingId = id(value?.oceanBooking?.id)
|
||||||
|
const form = new FormData()
|
||||||
|
form.append('expectedVersion', String(Number(value?.oceanBooking?.version)))
|
||||||
|
form.append('documentType', 'BOOKING_CONFIRMATION')
|
||||||
|
form.append('file', new Blob([`${context.marker}-BOOKING_CONFIRMATION-${index}`],
|
||||||
|
{ type: 'text/plain' }), `booking-confirmation-${suffix(context, index)}.txt`)
|
||||||
|
const response = await context.requestMultipart('POST',
|
||||||
|
`${OCEAN_BASE}/${bookingId}/documents/upload`, form, context.token,
|
||||||
|
{ 'Idempotency-Key': key(context, `ocean-booking-document-${index}`) })
|
||||||
|
const document = context.apiSuccess(MODULE, `订舱样本 ${index} 上传订舱确认文件`, response, '', OPTIONS)
|
||||||
|
if (!Number.isInteger(Number(document?.bookingVersion))) {
|
||||||
|
throw new Error(`订舱样本 ${index} 未返回有效文件版本`)
|
||||||
|
}
|
||||||
|
return { ...value, oceanBooking: { ...value.oceanBooking, version: Number(document.bookingVersion) } }
|
||||||
|
}
|
||||||
|
|
||||||
async function buildOcean(context, template, status, index) {
|
async function buildOcean(context, template, status, index) {
|
||||||
let value = await createOcean(context, template, index)
|
let value = await createOcean(context, template, index)
|
||||||
if (status === 'DRAFT') return value
|
if (status === 'DRAFT') return value
|
||||||
if (status === 'RELEASED') return oceanAction(context, value, 'release')
|
if (status === 'RELEASED') {
|
||||||
|
value = await uploadBookingConfirmation(context, value, index)
|
||||||
|
return oceanAction(context, value, 'release')
|
||||||
|
}
|
||||||
throw new Error(`unsupported ocean status ${status}`)
|
throw new Error(`unsupported ocean status ${status}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,7 +127,7 @@ async function createTruck(context, oceanBooking, index) {
|
|||||||
const response = await context.request('POST', TRUCK_BASE, truckPayload(context, oceanBooking, carrier, index))
|
const response = await context.request('POST', TRUCK_BASE, truckPayload(context, oceanBooking, carrier, index))
|
||||||
const value = context.apiSuccess(MODULE, `创建拖车状态样本 ${index}`, response, '', OPTIONS)
|
const value = context.apiSuccess(MODULE, `创建拖车状态样本 ${index}`, response, '', OPTIONS)
|
||||||
if (!value?.id) throw new Error(`创建拖车样本 ${index} 失败`)
|
if (!value?.id) throw new Error(`创建拖车样本 ${index} 失败`)
|
||||||
const billUpload = await uploadBillOfLading(context, id(value.id), index)
|
const billUpload = await uploadDispatchOrder(context, id(value.id), index)
|
||||||
const billAttachment = context.apiSuccess(MODULE, `拖车状态样本 ${index} 上传提单附件`, billUpload, '', OPTIONS)
|
const billAttachment = context.apiSuccess(MODULE, `拖车状态样本 ${index} 上传提单附件`, billUpload, '', OPTIONS)
|
||||||
if (!billAttachment?.id) throw new Error(`拖车样本 ${index} 未上传有效提单附件`)
|
if (!billAttachment?.id) throw new Error(`拖车样本 ${index} 未上传有效提单附件`)
|
||||||
return value
|
return value
|
||||||
|
|||||||
@@ -133,9 +133,18 @@ function routes(context, merchantToken) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function prepare(context) {
|
export async function prepare(context) {
|
||||||
|
const outerMarker = context.marker
|
||||||
const restore = await context.preparePhase3LoadingFixture('-CUSTOMS-MIXED', false, {
|
const restore = await context.preparePhase3LoadingFixture('-CUSTOMS-MIXED', false, {
|
||||||
primary: 'BUY_DECLARATION', incremental: 'TAX_REFUND',
|
primary: 'BUY_DECLARATION', incremental: 'TAX_REFUND',
|
||||||
})
|
})
|
||||||
|
const executionMarker = `${outerMarker}-CUSTOMS-MIXED`
|
||||||
|
context.marker = executionMarker
|
||||||
|
if (context.createdResources?.orchestrationFixtures?.phase3Loading) {
|
||||||
|
context.createdResources.orchestrationFixtures.phase3Loading.marker = executionMarker
|
||||||
|
}
|
||||||
|
if (context.createdResources?.orchestrationFixtures?.phase3LoadingIncremental) {
|
||||||
|
context.createdResources.orchestrationFixtures.phase3LoadingIncremental.marker = executionMarker
|
||||||
|
}
|
||||||
const loading = await import('./phase3-loading.mjs')
|
const loading = await import('./phase3-loading.mjs')
|
||||||
await context.executeModule(`${MODULE}-loading`, () => loading.run(context))
|
await context.executeModule(`${MODULE}-loading`, () => loading.run(context))
|
||||||
const finalize = await import('./phase3-loading-finalize.mjs')
|
const finalize = await import('./phase3-loading-finalize.mjs')
|
||||||
@@ -143,6 +152,7 @@ export async function prepare(context) {
|
|||||||
|
|
||||||
const source = context.createdResources?.phase3Loading
|
const source = context.createdResources?.phase3Loading
|
||||||
if (!source?.loadingPlanId || !source?.containerBookingId || source?.finalStatus !== 'LOADED') {
|
if (!source?.loadingPlanId || !source?.containerBookingId || source?.finalStatus !== 'LOADED') {
|
||||||
|
context.marker = outerMarker
|
||||||
restore()
|
restore()
|
||||||
throw new Error(`混合柜未进入LOADED: planId=${id(source?.loadingPlanId) || 'missing'}, status=${source?.finalStatus || 'missing'}`)
|
throw new Error(`混合柜未进入LOADED: planId=${id(source?.loadingPlanId) || 'missing'}, status=${source?.finalStatus || 'missing'}`)
|
||||||
}
|
}
|
||||||
@@ -157,6 +167,7 @@ export async function prepare(context) {
|
|||||||
buy.length > 0 && refund.length > 0 && inbounds.every((item) => /^\d{16,}$/.test(id(item.inboundId))),
|
buy.length > 0 && refund.length > 0 && inbounds.every((item) => /^\d{16,}$/.test(id(item.inboundId))),
|
||||||
`buy=${buy.length}, tax=${refund.length}, inbounds=${inbounds.length}`, OPTIONS)
|
`buy=${buy.length}, tax=${refund.length}, inbounds=${inbounds.length}`, OPTIONS)
|
||||||
if (!buy.length || !refund.length) {
|
if (!buy.length || !refund.length) {
|
||||||
|
context.marker = outerMarker
|
||||||
restore()
|
restore()
|
||||||
throw new Error(`正式装柜API未形成混合柜: buy=${buy.length}, tax=${refund.length}`)
|
throw new Error(`正式装柜API未形成混合柜: buy=${buy.length}, tax=${refund.length}`)
|
||||||
}
|
}
|
||||||
@@ -178,10 +189,11 @@ export async function prepare(context) {
|
|||||||
const buyInboundIds = new Set(buy.map((item) => id(item.inboundId)))
|
const buyInboundIds = new Set(buy.map((item) => id(item.inboundId)))
|
||||||
const planBuyRows = planItems.filter((item) => buyInboundIds.has(id(item.inboundId))).length
|
const planBuyRows = planItems.filter((item) => buyInboundIds.has(id(item.inboundId))).length
|
||||||
const expectedBuyRows = planBuyRows > 0 ? planBuyRows : buy.length
|
const expectedBuyRows = planBuyRows > 0 ? planBuyRows : buy.length
|
||||||
|
context.marker = outerMarker
|
||||||
context.createdResources.phase3ContainerCustomsFixtures = {
|
context.createdResources.phase3ContainerCustomsFixtures = {
|
||||||
marker: context.marker, requestedKinds: ['MIXED'],
|
marker: outerMarker, requestedKinds: ['MIXED'],
|
||||||
containers: [{
|
containers: [{
|
||||||
kind: 'MIXED', marker: context.marker, status: 'LOADED', autoDispatchMode: 'IMMEDIATE',
|
kind: 'MIXED', marker: outerMarker, status: 'LOADED', autoDispatchMode: 'IMMEDIATE',
|
||||||
loadingPlanId: id(source.loadingPlanId), containerBookingId: id(source.containerBookingId),
|
loadingPlanId: id(source.loadingPlanId), containerBookingId: id(source.containerBookingId),
|
||||||
oceanBookingId: id(source.oceanBookingId), containerNo: source.containerNo,
|
oceanBookingId: id(source.oceanBookingId), containerNo: source.containerNo,
|
||||||
inbounds: inbounds.map((item) => ({
|
inbounds: inbounds.map((item) => ({
|
||||||
@@ -194,6 +206,7 @@ export async function prepare(context) {
|
|||||||
context.context.phase3ContainerCustomsRoutes = routes(context, merchantToken)
|
context.context.phase3ContainerCustomsRoutes = routes(context, merchantToken)
|
||||||
return () => {
|
return () => {
|
||||||
delete context.context.phase3ContainerCustomsRoutes
|
delete context.context.phase3ContainerCustomsRoutes
|
||||||
|
context.marker = outerMarker
|
||||||
restore()
|
restore()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -251,7 +251,7 @@ export async function run(context) {
|
|||||||
const warehouseRemarkPath = `${smartLoadingBase}/plans/${loadingPlanId}/items/${id(assignedItem.id)}/warehouse-remark`
|
const warehouseRemarkPath = `${smartLoadingBase}/plans/${loadingPlanId}/items/${id(assignedItem.id)}/warehouse-remark`
|
||||||
const warehouseRemarkBody = Object.freeze({ warehouseRemark,
|
const warehouseRemarkBody = Object.freeze({ warehouseRemark,
|
||||||
expectedPlanVersion: Number(assignedLoadingPlan.version), expectedItemVersion: Number(assignedItem.version),
|
expectedPlanVersion: Number(assignedLoadingPlan.version), expectedItemVersion: Number(assignedItem.version),
|
||||||
operationId: `${context.marker}-warehouse-remark`.slice(0, 128) })
|
operationId: `${context.marker}-${loadingPlanId}-warehouse-remark`.slice(0, 128) })
|
||||||
const remarkResponse = await request(context, 'PATCH', warehouseRemarkPath, warehouseRemarkBody)
|
const remarkResponse = await request(context, 'PATCH', warehouseRemarkPath, warehouseRemarkBody)
|
||||||
const remarkedPlan = context.apiSuccess(MODULE, 'EDITING 装柜明细保存仓库备注', remarkResponse, '', OPTIONS)
|
const remarkedPlan = context.apiSuccess(MODULE, 'EDITING 装柜明细保存仓库备注', remarkResponse, '', OPTIONS)
|
||||||
const remarkedItem = remarkedPlan?.items?.find?.((item) => id(item.id) === id(assignedItem.id))
|
const remarkedItem = remarkedPlan?.items?.find?.((item) => id(item.id) === id(assignedItem.id))
|
||||||
@@ -274,7 +274,7 @@ export async function run(context) {
|
|||||||
const staleRemarkResponse = await request(context, 'PATCH',
|
const staleRemarkResponse = await request(context, 'PATCH',
|
||||||
`${smartLoadingBase}/plans/${loadingPlanId}/items/${id(assignedItem.id)}/warehouse-remark`, {
|
`${smartLoadingBase}/plans/${loadingPlanId}/items/${id(assignedItem.id)}/warehouse-remark`, {
|
||||||
warehouseRemark: `${warehouseRemark}-STALE`, expectedPlanVersion: Number(assignedLoadingPlan.version),
|
warehouseRemark: `${warehouseRemark}-STALE`, expectedPlanVersion: Number(assignedLoadingPlan.version),
|
||||||
expectedItemVersion: Number(assignedItem.version), operationId: `${context.marker}-warehouse-remark-stale`.slice(0, 128),
|
expectedItemVersion: Number(assignedItem.version), operationId: `${context.marker}-${loadingPlanId}-warehouse-remark-stale`.slice(0, 128),
|
||||||
})
|
})
|
||||||
context.addStep(MODULE, '仓库备注旧版本 A0409 且零写入', staleRemarkResponse,
|
context.addStep(MODULE, '仓库备注旧版本 A0409 且零写入', staleRemarkResponse,
|
||||||
staleRemarkResponse.http === 200 && codeOf(staleRemarkResponse) === 'A0409',
|
staleRemarkResponse.http === 200 && codeOf(staleRemarkResponse) === 'A0409',
|
||||||
@@ -290,7 +290,7 @@ export async function run(context) {
|
|||||||
const clearRemarkResponse = await request(context, 'PATCH',
|
const clearRemarkResponse = await request(context, 'PATCH',
|
||||||
`${smartLoadingBase}/plans/${loadingPlanId}/items/${id(remarkedItem.id)}/warehouse-remark`, {
|
`${smartLoadingBase}/plans/${loadingPlanId}/items/${id(remarkedItem.id)}/warehouse-remark`, {
|
||||||
warehouseRemark: ' ', expectedPlanVersion: Number(remarkedPlan.version),
|
warehouseRemark: ' ', expectedPlanVersion: Number(remarkedPlan.version),
|
||||||
expectedItemVersion: Number(remarkedItem.version), operationId: `${context.marker}-warehouse-remark-clear`.slice(0, 128),
|
expectedItemVersion: Number(remarkedItem.version), operationId: `${context.marker}-${loadingPlanId}-warehouse-remark-clear`.slice(0, 128),
|
||||||
})
|
})
|
||||||
const clearedPlan = context.apiSuccess(MODULE, 'EDITING 装柜明细空白仓库备注归一化为 null', clearRemarkResponse, '', OPTIONS)
|
const clearedPlan = context.apiSuccess(MODULE, 'EDITING 装柜明细空白仓库备注归一化为 null', clearRemarkResponse, '', OPTIONS)
|
||||||
const clearedItem = clearedPlan?.items?.find?.((item) => id(item.id) === id(remarkedItem.id))
|
const clearedItem = clearedPlan?.items?.find?.((item) => id(item.id) === id(remarkedItem.id))
|
||||||
@@ -302,7 +302,7 @@ export async function run(context) {
|
|||||||
const restoreRemarkResponse = await request(context, 'PATCH',
|
const restoreRemarkResponse = await request(context, 'PATCH',
|
||||||
`${smartLoadingBase}/plans/${loadingPlanId}/items/${id(clearedItem.id)}/warehouse-remark`, {
|
`${smartLoadingBase}/plans/${loadingPlanId}/items/${id(clearedItem.id)}/warehouse-remark`, {
|
||||||
warehouseRemark, expectedPlanVersion: Number(clearedPlan.version), expectedItemVersion: Number(clearedItem.version),
|
warehouseRemark, expectedPlanVersion: Number(clearedPlan.version), expectedItemVersion: Number(clearedItem.version),
|
||||||
operationId: `${context.marker}-warehouse-remark-restore`.slice(0, 128),
|
operationId: `${context.marker}-${loadingPlanId}-warehouse-remark-restore`.slice(0, 128),
|
||||||
})
|
})
|
||||||
const persistedRemarkPlan = context.apiSuccess(MODULE, '恢复真实仓库备注供装柜完成事件下发', restoreRemarkResponse, '', OPTIONS)
|
const persistedRemarkPlan = context.apiSuccess(MODULE, '恢复真实仓库备注供装柜完成事件下发', restoreRemarkResponse, '', OPTIONS)
|
||||||
if (!Number.isInteger(Number(persistedRemarkPlan?.version))) return
|
if (!Number.isInteger(Number(persistedRemarkPlan?.version))) return
|
||||||
|
|||||||
@@ -79,6 +79,22 @@ export async function run(context) {
|
|||||||
&& gateItems.every((item) => !['TRUCK_DEPARTED', 'SAILING_SCHEDULE'].includes(item.code)),
|
&& gateItems.every((item) => !['TRUCK_DEPARTED', 'SAILING_SCHEDULE'].includes(item.code)),
|
||||||
`truck=${aggregate?.truckAppointmentStatus || 'none'}, ocean=${aggregate?.oceanBookingStatus || 'none'}`, OPTIONS)
|
`truck=${aggregate?.truckAppointmentStatus || 'none'}, ocean=${aggregate?.oceanBookingStatus || 'none'}`, OPTIONS)
|
||||||
|
|
||||||
|
if (aggregate?.dispatchStatus === 'WAITING_FOR_LOADING') {
|
||||||
|
context.assert(MODULE, '整柜报关完成后系统已自动确认国内离仓', detailResponse,
|
||||||
|
aggregate?.inbounds?.every?.((item) => item?.inboundStatus === 'LOADED')
|
||||||
|
&& detail?.timeline?.some?.((item) => item.module === 'OUTBOUND_DISPATCH'
|
||||||
|
&& item.action === 'CONFIRM_DISPATCH'),
|
||||||
|
`dispatch=${aggregate.dispatchStatus}, inbounds=${aggregate?.inbounds?.length ?? 'missing'}`, OPTIONS)
|
||||||
|
context.createdResources.phase3OutboundDispatch = {
|
||||||
|
marker: context.marker, inboundId, containerBookingId,
|
||||||
|
orderId: id(inboundBinding?.orderId), tenantId: id(inboundBinding?.tenantId),
|
||||||
|
confirmedStatus: aggregate.dispatchStatus, confirmedInboundStatus: 'LOADED',
|
||||||
|
confirmedVersion: Number(aggregate.version), finalStatus: aggregate.dispatchStatus,
|
||||||
|
finalInboundStatus: 'LOADED', finalVersion: Number(aggregate.version),
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const expectedVersion = Number(aggregate.version)
|
const expectedVersion = Number(aggregate.version)
|
||||||
const confirmKey = `${context.marker}-outbound-confirm`.slice(0, 128)
|
const confirmKey = `${context.marker}-outbound-confirm`.slice(0, 128)
|
||||||
if (!gate?.passed) {
|
if (!gate?.passed) {
|
||||||
|
|||||||
@@ -257,7 +257,7 @@ async function awaitProducerConsumption(context, producers) {
|
|||||||
if (!expected.length) return true
|
if (!expected.length) return true
|
||||||
const consumed = new Set()
|
const consumed = new Set()
|
||||||
const lastResponses = new Map()
|
const lastResponses = new Map()
|
||||||
for (let attempt = 1; attempt <= 30; attempt += 1) {
|
for (let attempt = 1; attempt <= 90; attempt += 1) {
|
||||||
const missing = expected.filter((containerNo) => !consumed.has(containerNo))
|
const missing = expected.filter((containerNo) => !consumed.has(containerNo))
|
||||||
const results = await Promise.all(missing.map(async (containerNo) => {
|
const results = await Promise.all(missing.map(async (containerNo) => {
|
||||||
const response = await context.requestOverseas('GET',
|
const response = await context.requestOverseas('GET',
|
||||||
|
|||||||
Reference in New Issue
Block a user