diff --git a/firstleg/src/main/java/com/baiyue/firstleg/service/CustomerDimensionConfigService.java b/firstleg/src/main/java/com/baiyue/firstleg/service/CustomerDimensionConfigService.java index 321663e43..dc594e632 100644 --- a/firstleg/src/main/java/com/baiyue/firstleg/service/CustomerDimensionConfigService.java +++ b/firstleg/src/main/java/com/baiyue/firstleg/service/CustomerDimensionConfigService.java @@ -63,7 +63,7 @@ public class CustomerDimensionConfigService { throw staleConfiguration(); } } - return response(tenantId, existing); + return response(tenantId, requireCompleteRules(repository.findAll(tenantId))); } @Transactional diff --git a/firstleg/src/test/java/com/baiyue/firstleg/service/CustomerDimensionConfigServiceTest.java b/firstleg/src/test/java/com/baiyue/firstleg/service/CustomerDimensionConfigServiceTest.java index 554ebd313..6f605dee0 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/service/CustomerDimensionConfigServiceTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/service/CustomerDimensionConfigServiceTest.java @@ -140,6 +140,20 @@ class CustomerDimensionConfigServiceTest { verify(repository, times(2)).update(any()); } + @Test + void returnsPersistedVersionsAfterUpdatingAllRules() { + List existing = defaultConfigs("registration-1"); + List persisted = defaultConfigs("registration-1"); + persisted.forEach(value -> value.setVersion(1)); + when(repository.findAll(8L)).thenReturn(existing, persisted); + when(repository.update(any())).thenReturn(1); + + GroupResponse result = service.update(8L, 100L, updateRequest(0)); + + assertThat(result.rules()).extracting(rule -> rule.version()).containsOnly(1); + verify(repository, times(2)).findAll(8L); + } + @Test void savesAndReturnsTheCustomerDefaultCalculationMode() { List existing = defaultConfigs("registration-1"); diff --git a/scripts/acceptance-finance-full-contract.test.mjs b/scripts/acceptance-finance-full-contract.test.mjs index 3f77ae4ac..0437ffede 100644 --- a/scripts/acceptance-finance-full-contract.test.mjs +++ b/scripts/acceptance-finance-full-contract.test.mjs @@ -9,14 +9,13 @@ import { AcceptanceRun } from './admin-test-env-acceptance.mjs' const here = path.dirname(fileURLToPath(import.meta.url)) const source = fs.readFileSync(path.join(here, 'admin-test-env-acceptance.mjs'), 'utf8') -test('默认全量按 fixture、reconciliation、settlement、downloads 顺序接入且不重复旧 finance', () => { +test('默认全量按 fixture、reconciliation、settlement、downloads 顺序接入且不包含旧 finance', () => { const match = source.match(/const CONFIGURED_MODULES = \[(.*?)\]/s) assert.ok(match) const modules = [...match[1].matchAll(/'([^']+)'/g)].map((item) => item[1]) const financeStart = modules.indexOf('finance-full-fixtures') assert.deepEqual(modules.slice(financeStart, financeStart + 4), ['finance-full-fixtures', 'finance-reconciliation-full', 'finance-settlement-full', 'finance-downloads']) - assert.equal(modules.filter((item) => item === 'finance').length, 1) - assert.ok(modules.indexOf('finance') < modules.indexOf('finance-full-fixtures')) + assert.equal(modules.includes('finance'), false) assert.match(source, /fullFinanceChain/) }) diff --git a/scripts/admin-test-env-acceptance.mjs b/scripts/admin-test-env-acceptance.mjs index beaef6a74..ff537ff9f 100644 --- a/scripts/admin-test-env-acceptance.mjs +++ b/scripts/admin-test-env-acceptance.mjs @@ -5,7 +5,7 @@ import { dirname, resolve } from 'node:path' import { pathToFileURL } from 'node:url' import process from 'node:process' -const CONFIGURED_MODULES = ['merchant-qualification-fixture', 'customers', 'overseas-storage-fees', 'system', 'notifications', 'master-data', 'suppliers', 'domestic-container', 'orders', 'fee-items', 'pricing', 'downloads', 'inbound-weight-ocr', 'inbound-measurement', 'inbound-fees', 'phase3-container-loading', 'phase3-container-overview', 'phase3-loading', 'phase3-loading-model-bridge', 'phase3-pda-convergence', 'phase3-evidence-gate', 'supplemental', 'supplemental-boundaries', 'ocean-bookings', 'truck-appointments', 'service-center-flow', 'phase3-loading-finalize', 'phase3-container-documents', 'phase3-customs', 'phase3-container-customs', 'phase3-port-schedules', 'phase3-carrier-schedule-queries', 'phase3-outbound-dispatch', 'phase3-outbound-aggregate', 'phase3-dispatch-gate', 'phase3-waiting-for-loading', 'phase3-warehouse-rbac', 'phase3-order-splitting', 'manual-state-matrix', 'phase4-overseas-warehouse', 'phase4-carrier-pickup-ready', 'phase4-overseas-pda', 'phase4-overseas-pda-operations', 'phase4-overseas-inbound-operations', 'phase4-overseas-client-services', 'phase4-overseas-dispatch', 'phase5-overseas-fulfillment', 'phase4-cross-region-events', 'phase4-overseas-business', 'phase4-overseas-master-data', 'phase4-carrier-nodes', 'finance', 'finance-full-fixtures', 'finance-reconciliation-full', 'finance-settlement-full', 'finance-downloads', 'firstleg-container-fee-allocation', 'finance-direct-paid-isolation', 'phase4-overseas-finance-cross-region', 'customer-operating-statistics'] +const CONFIGURED_MODULES = ['merchant-qualification-fixture', 'customers', 'overseas-storage-fees', 'system', 'notifications', 'master-data', 'suppliers', 'domestic-container', 'orders', 'fee-items', 'pricing', 'downloads', 'inbound-weight-ocr', 'inbound-measurement', 'inbound-fees', 'phase3-container-loading', 'phase3-container-overview', 'phase3-loading', 'phase3-loading-model-bridge', 'phase3-pda-convergence', 'phase3-evidence-gate', 'supplemental', 'supplemental-boundaries', 'ocean-bookings', 'truck-appointments', 'service-center-flow', 'phase3-loading-finalize', 'phase3-container-documents', 'phase3-customs', 'phase3-container-customs', 'phase3-port-schedules', 'phase3-carrier-schedule-queries', 'phase3-outbound-dispatch', 'phase3-outbound-aggregate', 'phase3-dispatch-gate', 'phase3-waiting-for-loading', 'phase3-warehouse-rbac', 'phase3-order-splitting', 'manual-state-matrix', 'phase4-overseas-warehouse', 'phase4-carrier-pickup-ready', 'phase4-overseas-pda', 'phase4-overseas-pda-operations', 'phase4-overseas-inbound-operations', 'phase4-overseas-client-services', 'phase4-overseas-dispatch', 'phase5-overseas-fulfillment', 'phase4-cross-region-events', 'phase4-overseas-business', 'phase4-overseas-master-data', 'phase4-carrier-nodes', 'finance-full-fixtures', 'finance-reconciliation-full', 'finance-settlement-full', 'finance-downloads', 'firstleg-container-fee-allocation', 'finance-direct-paid-isolation', 'phase4-overseas-finance-cross-region', 'customer-operating-statistics'] const OPTIONAL_MODULES = ['manual-order-splitting-fixture', 'manual-order-splitting-verify', 'manual-preload-push-fixture', 'manual-preload-push-verify', 'manual-pda-ready-fixture', 'manual-pda-ready-verify', @@ -452,9 +452,9 @@ function hasFields(value, fields) { return fields.every((field) => own(value, fi function numberEquals(actual, expected) { return Math.abs(Number(actual) - Number(expected)) < 0.0005 } function halfUp3(value) { return Math.round((Number(value) + Number.EPSILON) * 1000) / 1000 } function derived(length, width, height, edge) { - const outerLength = Number(length) + Number(edge) - const outerWidth = Number(width) + Number(edge) - const outerHeight = Number(height) + Number(edge) + const outerLength = Number(length) + Number(edge) * 2 + const outerWidth = Number(width) + Number(edge) * 2 + const outerHeight = Number(height) + Number(edge) * 2 return { outerLength, outerWidth, outerHeight, volume: halfUp3(outerLength * outerWidth * outerHeight / 1_000_000) } } diff --git a/scripts/admin-test-env-modules/phase3-producer-chain-contract.test.mjs b/scripts/admin-test-env-modules/phase3-producer-chain-contract.test.mjs index 25f023468..e3c44c4bd 100644 --- a/scripts/admin-test-env-modules/phase3-producer-chain-contract.test.mjs +++ b/scripts/admin-test-env-modules/phase3-producer-chain-contract.test.mjs @@ -418,7 +418,7 @@ test('container loading permits manual cross-channel allocation and keeps stable assert.match(value, /id\(replacedTask\?\.cabinetNo\) === stableIdentity\.cabinetNo/) assert.match(value, /afterReplacementSnapshot\?\.snapshotToken === beforeReplacementSnapshot\?\.snapshotToken/) assert.match(value, /pdaIdentityStable: true/) - assert.match(value, /category=BOOKING&status=ACTIVE/) + assert.match(value, /resolveOceanBookingAgent\(context, MODULE, 'MATSON', OPTIONS\)/) assert.match(value, /shippingCompanyId: id\(shippingCarrier\.id\), shippingCompanyName: shippingCarrier\.name, carrierCode: 'MATSON'/) assert.match(value, /bookingAgentId: id\(bookingAgent\.id\), bookingAgentName: bookingAgent\.name/) assert.match(value, /const frozenTransport = Object\.fromEntries\(transportFields\.map/) @@ -502,7 +502,8 @@ test('PDA completion acceptance verifies immutable BATCH and COMPLETE_LOADING au } assert.match(loading, /expectedBatchOperationIds = \[batchKey, skipBatchKey, partialBatchKey, dynamicBatchKey\]/) assert.match(loading, /skipBatchVerified, partialBatchVerified, dynamicIncrementVerified/) - assert.match(convergence, /expectedBatchOperationIds\.length === 4/) + assert.match(convergence, /expectedBatchCount = fixture\?\.fulfillmentType === 'ONE_CLICK_DROPSHIP' \? 1 : 4/) + assert.match(convergence, /expectedBatchOperationIds\.length === expectedBatchCount/) assert.doesNotMatch(convergence, /expectedBatchCount = fixture\?\.dynamicIncrementVerified/) }) @@ -518,14 +519,14 @@ test('phase3 loading proves mixed-line rollback across task, all items and opera assert.match(loading, /taskItemsSignature\(unchangedAfterInvalid\) === taskItemsSignature\(beforeInvalidTask\)/) assert.match(loading, /operationsSignature\(afterInvalidOperations\) === operationsSignature\(beforeInvalidOperations\)/) assert.doesNotMatch(loading, /tasks\?containerNo=/) - assert.equal((loading.match(/tasks\?loadingPlanId=/g) || []).length >= 7, true) + assert.equal((loading.match(/tasks\?loadingPlanId=/g) || []).length >= 6, true) }) test('phase3 loading proves stale inventory zero-write and defers final completion audit', () => { const loading = source('./phase3-loading.mjs') assert.match(loading, /计划详情由服务端返回权威总箱数和剩余箱数/) assert.match(loading, /authoritativeRemaining === authoritativeTotal - assignedPackageCount/) - assert.match(loading, /调整箱数超过服务端权威总箱数返回A0400/) + assert.match(loading, /调整箱数超过服务端权威总箱数返回A0422/) assert.match(loading, /超权威总箱数失败后计划和明细零写入/) assert.match(loading, /taskItemsSignature\(taskAfterStale\) === taskItemsSignature\(incrementedTask\)/) assert.match(loading, /动态新增计划明细按 inboundDetailId 唯一定位/) @@ -590,7 +591,7 @@ test('phase3 loading fixture paginates A0 candidates and refuses mass location m const orders = source('./orders.mjs') assertOrder(entry, [ 'disableAvailableLocationsForA0(warehouseId, requiredPalletCount)', - 'this.context.phase3LoadingRequireSharedOverflow = isolation.forced === true', + 'this.context.phase3LoadingRequireSharedOverflow = requireSharedOverflow', "executeModule('inbound-measurement-phase3-loading-fixture'", 'restoreAvailableLocationsAfterA0(warehouseId, isolation)', ]) @@ -707,7 +708,7 @@ test('container fixture isolates order idempotency while publishing the run mark test('measurement fixture polls asynchronous completion before exporting loading identity', () => { const measurement = source('./inbound-measurement.mjs') assert.match(measurement, /waitForCompletedMeasurement/) - assert.match(measurement, /measurement\?\.processStatus === 'WAIT_SPLIT'/) + assert.match(measurement, /expectedStatuses\.includes\(measurement\?\.processStatus\)/) assert.match(measurement, /setTimeout\(resolve, 1_000\)/) })