diff --git a/.codex-fee-test.err b/.codex-fee-test.err new file mode 100644 index 000000000..fb51512d1 --- /dev/null +++ b/.codex-fee-test.err @@ -0,0 +1 @@ +OpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended diff --git a/.codex-fee-test.out b/.codex-fee-test.out new file mode 100644 index 000000000..aada49400 --- /dev/null +++ b/.codex-fee-test.out @@ -0,0 +1,208 @@ +[INFO] Scanning for projects... +[INFO] ------------------------------------------------------------------------ +[INFO] Reactor Build Order: +[INFO] +[INFO] °ØÔÃͷ³Ìƽ̨ [pom] +[INFO] baiyue-common-core [jar] +[INFO] baiyue-common-web [jar] +[INFO] baiyue-common-security [jar] +[INFO] baiyue-common-openapi [jar] +[INFO] baiyue-common-audit [jar] +[INFO] firstleg-service [jar] +[INFO] +[INFO] ------------< com.baiyue.firstleg:baiyue-firstleg-platform >------------ +[INFO] Building °ØÔÃͷ³Ìƽ̨ 1.0.0-SNAPSHOT [1/7] +[INFO] from pom.xml +[INFO] --------------------------------[ pom ]--------------------------------- +[INFO] +[INFO] --- enforcer:3.4.1:enforce (УÑ鹹½¨»·¾³) @ baiyue-firstleg-platform --- +[INFO] Rule 0: org.apache.maven.enforcer.rules.version.RequireJavaVersion passed +[INFO] Rule 1: org.apache.maven.enforcer.rules.version.RequireMavenVersion passed +[INFO] +[INFO] ----------------< com.baiyue.shared:baiyue-common-core >---------------- +[INFO] Building baiyue-common-core 1.0.0-SNAPSHOT [2/7] +[INFO] from common-core\pom.xml +[INFO] --------------------------------[ jar ]--------------------------------- +[INFO] +[INFO] --- enforcer:3.4.1:enforce (УÑ鹹½¨»·¾³) @ baiyue-common-core --- +[INFO] +[INFO] --- resources:3.3.1:resources (default-resources) @ baiyue-common-core --- +[INFO] skip non existing resourceDirectory C:\Users\BY-OY\Desktop\code\chongchong-global\baiyue-firstleg\common-core\src\main\resources +[INFO] skip non existing resourceDirectory C:\Users\BY-OY\Desktop\code\chongchong-global\baiyue-firstleg\common-core\src\main\resources +[INFO] +[INFO] --- compiler:3.13.0:compile (default-compile) @ baiyue-common-core --- +[INFO] Nothing to compile - all classes are up to date. +[INFO] +[INFO] --- resources:3.3.1:testResources (default-testResources) @ baiyue-common-core --- +[INFO] skip non existing resourceDirectory C:\Users\BY-OY\Desktop\code\chongchong-global\baiyue-firstleg\common-core\src\test\resources +[INFO] +[INFO] --- compiler:3.13.0:testCompile (default-testCompile) @ baiyue-common-core --- +[INFO] Nothing to compile - all classes are up to date. +[INFO] +[INFO] --- surefire:3.1.2:test (default-test) @ baiyue-common-core --- +[INFO] +[INFO] ----------------< com.baiyue.shared:baiyue-common-web >----------------- +[INFO] Building baiyue-common-web 1.0.0-SNAPSHOT [3/7] +[INFO] from common-web\pom.xml +[INFO] --------------------------------[ jar ]--------------------------------- +[INFO] +[INFO] --- enforcer:3.4.1:enforce (УÑ鹹½¨»·¾³) @ baiyue-common-web --- +[INFO] +[INFO] --- resources:3.3.1:resources (default-resources) @ baiyue-common-web --- +[INFO] Copying 0 resource from src\main\resources to target\classes +[INFO] Copying 1 resource from src\main\resources to target\classes +[INFO] +[INFO] --- compiler:3.13.0:compile (default-compile) @ baiyue-common-web --- +[INFO] Nothing to compile - all classes are up to date. +[INFO] +[INFO] --- resources:3.3.1:testResources (default-testResources) @ baiyue-common-web --- +[INFO] skip non existing resourceDirectory C:\Users\BY-OY\Desktop\code\chongchong-global\baiyue-firstleg\common-web\src\test\resources +[INFO] +[INFO] --- compiler:3.13.0:testCompile (default-testCompile) @ baiyue-common-web --- +[INFO] Nothing to compile - all classes are up to date. +[INFO] +[INFO] --- surefire:3.1.2:test (default-test) @ baiyue-common-web --- +[INFO] +[INFO] --------------< com.baiyue.shared:baiyue-common-security >-------------- +[INFO] Building baiyue-common-security 1.0.0-SNAPSHOT [4/7] +[INFO] from common-security\pom.xml +[INFO] --------------------------------[ jar ]--------------------------------- +[INFO] +[INFO] --- enforcer:3.4.1:enforce (УÑ鹹½¨»·¾³) @ baiyue-common-security --- +[INFO] +[INFO] --- resources:3.3.1:resources (default-resources) @ baiyue-common-security --- +[INFO] Copying 0 resource from src\main\resources to target\classes +[INFO] Copying 1 resource from src\main\resources to target\classes +[INFO] +[INFO] --- compiler:3.13.0:compile (default-compile) @ baiyue-common-security --- +[INFO] Nothing to compile - all classes are up to date. +[INFO] +[INFO] --- resources:3.3.1:testResources (default-testResources) @ baiyue-common-security --- +[INFO] skip non existing resourceDirectory C:\Users\BY-OY\Desktop\code\chongchong-global\baiyue-firstleg\common-security\src\test\resources +[INFO] +[INFO] --- compiler:3.13.0:testCompile (default-testCompile) @ baiyue-common-security --- +[INFO] Nothing to compile - all classes are up to date. +[INFO] +[INFO] --- surefire:3.1.2:test (default-test) @ baiyue-common-security --- +[INFO] +[INFO] --------------< com.baiyue.shared:baiyue-common-openapi >--------------- +[INFO] Building baiyue-common-openapi 1.0.0-SNAPSHOT [5/7] +[INFO] from common-openapi\pom.xml +[INFO] --------------------------------[ jar ]--------------------------------- +[INFO] +[INFO] --- enforcer:3.4.1:enforce (УÑ鹹½¨»·¾³) @ baiyue-common-openapi --- +[INFO] +[INFO] --- resources:3.3.1:resources (default-resources) @ baiyue-common-openapi --- +[INFO] Copying 0 resource from src\main\resources to target\classes +[INFO] Copying 1 resource from src\main\resources to target\classes +[INFO] +[INFO] --- compiler:3.13.0:compile (default-compile) @ baiyue-common-openapi --- +[INFO] Nothing to compile - all classes are up to date. +[INFO] +[INFO] --- resources:3.3.1:testResources (default-testResources) @ baiyue-common-openapi --- +[INFO] skip non existing resourceDirectory C:\Users\BY-OY\Desktop\code\chongchong-global\baiyue-firstleg\common-openapi\src\test\resources +[INFO] +[INFO] --- compiler:3.13.0:testCompile (default-testCompile) @ baiyue-common-openapi --- +[INFO] Nothing to compile - all classes are up to date. +[INFO] +[INFO] --- surefire:3.1.2:test (default-test) @ baiyue-common-openapi --- +[INFO] +[INFO] ---------------< com.baiyue.shared:baiyue-common-audit >---------------- +[INFO] Building baiyue-common-audit 1.0.0-SNAPSHOT [6/7] +[INFO] from common-audit\pom.xml +[INFO] --------------------------------[ jar ]--------------------------------- +[WARNING] 1 problem was encountered while building the effective model for org.javassist:javassist:jar:3.21.0-GA during dependency collection step for project (use -X to see details) +[INFO] +[INFO] --- enforcer:3.4.1:enforce (УÑ鹹½¨»·¾³) @ baiyue-common-audit --- +[INFO] +[INFO] --- resources:3.3.1:resources (default-resources) @ baiyue-common-audit --- +[INFO] Copying 0 resource from src\main\resources to target\classes +[INFO] Copying 1 resource from src\main\resources to target\classes +[INFO] +[INFO] --- compiler:3.13.0:compile (default-compile) @ baiyue-common-audit --- +[INFO] Nothing to compile - all classes are up to date. +[INFO] +[INFO] --- resources:3.3.1:testResources (default-testResources) @ baiyue-common-audit --- +[INFO] skip non existing resourceDirectory C:\Users\BY-OY\Desktop\code\chongchong-global\baiyue-firstleg\common-audit\src\test\resources +[INFO] +[INFO] --- compiler:3.13.0:testCompile (default-testCompile) @ baiyue-common-audit --- +[INFO] Nothing to compile - all classes are up to date. +[INFO] +[INFO] --- surefire:3.1.2:test (default-test) @ baiyue-common-audit --- +[INFO] +[INFO] ----------------< com.baiyue.firstleg:firstleg-service >---------------- +[INFO] Building firstleg-service 1.0.0-SNAPSHOT [7/7] +[INFO] from firstleg\pom.xml +[INFO] --------------------------------[ jar ]--------------------------------- +[INFO] +[INFO] --- enforcer:3.4.1:enforce (УÑ鹹½¨»·¾³) @ firstleg-service --- +[INFO] +[INFO] --- resources:3.3.1:resources (default-resources) @ firstleg-service --- +[INFO] Copying 3 resources from src\main\resources to target\classes +[INFO] Copying 48 resources from src\main\resources to target\classes +[INFO] +[INFO] --- compiler:3.13.0:compile (default-compile) @ firstleg-service --- +[INFO] Nothing to compile - all classes are up to date. +[INFO] +[INFO] --- resources:3.3.1:testResources (default-testResources) @ firstleg-service --- +[INFO] skip non existing resourceDirectory C:\Users\BY-OY\Desktop\code\chongchong-global\baiyue-firstleg\firstleg\src\test\resources +[INFO] +[INFO] --- compiler:3.13.0:testCompile (default-testCompile) @ firstleg-service --- +[INFO] Nothing to compile - all classes are up to date. +[INFO] +[INFO] --- surefire:3.1.2:test (default-test) @ firstleg-service --- +[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider +[INFO] +[INFO] ------------------------------------------------------- +[INFO] T E S T S +[INFO] ------------------------------------------------------- +[INFO] Running com.baiyue.firstleg.repository.FinanceFeeSourceMigrationContractTest +[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.116 s -- in com.baiyue.firstleg.repository.FinanceFeeSourceMigrationContractTest +[INFO] Running com.baiyue.firstleg.repository.Phase3FinalUnifiedSqlContractTest +[INFO] Tests run: 10, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.170 s -- in com.baiyue.firstleg.repository.Phase3FinalUnifiedSqlContractTest +[INFO] Running com.baiyue.firstleg.service.FbaPricingServiceTest +Standard Commons Logging discovery in action with spring-jcl: please remove commons-logging.jar from classpath in order to avoid potential conflicts +16:57:57.743 [main] WARN org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer -- + +Found multiple occurrences of org.json.JSONObject on the class path: + + jar:file:/C:/Users/BY-OY/.m2/repository/org/json/json/20240303/json-20240303.jar!/org/json/JSONObject.class + jar:file:/C:/Users/BY-OY/.m2/repository/com/vaadin/external/google/android-json/0.0.20131108.vaadin1/android-json-0.0.20131108.vaadin1.jar!/org/json/JSONObject.class + +You may wish to exclude one of them to ensure predictable runtime behavior + +[INFO] Tests run: 12, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 2.182 s -- in com.baiyue.firstleg.service.FbaPricingServiceTest +[INFO] Running com.baiyue.firstleg.service.FeeManagementServiceTest +16:57:59.556 [main] WARN org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer -- + +Found multiple occurrences of org.json.JSONObject on the class path: + + jar:file:/C:/Users/BY-OY/.m2/repository/org/json/json/20240303/json-20240303.jar!/org/json/JSONObject.class + jar:file:/C:/Users/BY-OY/.m2/repository/com/vaadin/external/google/android-json/0.0.20131108.vaadin1/android-json-0.0.20131108.vaadin1.jar!/org/json/JSONObject.class + +You may wish to exclude one of them to ensure predictable runtime behavior + +[INFO] Tests run: 15, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.114 s -- in com.baiyue.firstleg.service.FeeManagementServiceTest +[INFO] Running com.baiyue.firstleg.service.FinanceFeeSourceOutboxServiceTest +[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.484 s -- in com.baiyue.firstleg.service.FinanceFeeSourceOutboxServiceTest +[INFO] +[INFO] Results: +[INFO] +[INFO] Tests run: 41, Failures: 0, Errors: 0, Skipped: 0 +[INFO] +[INFO] ------------------------------------------------------------------------ +[INFO] Reactor Summary for °ØÔÃͷ³Ìƽ̨ 1.0.0-SNAPSHOT: +[INFO] +[INFO] °ØÔÃͷ³Ìƽ̨ ............................................. SUCCESS [ 0.320 s] +[INFO] baiyue-common-core ................................. SUCCESS [ 0.975 s] +[INFO] baiyue-common-web .................................. SUCCESS [ 0.297 s] +[INFO] baiyue-common-security ............................. SUCCESS [ 0.281 s] +[INFO] baiyue-common-openapi .............................. SUCCESS [ 0.216 s] +[INFO] baiyue-common-audit ................................ SUCCESS [ 0.577 s] +[INFO] firstleg-service ................................... SUCCESS [ 5.090 s] +[INFO] ------------------------------------------------------------------------ +[INFO] BUILD SUCCESS +[INFO] ------------------------------------------------------------------------ +[INFO] Total time: 8.377 s +[INFO] Finished at: 2026-08-13T16:58:00+08:00 +[INFO] ------------------------------------------------------------------------ diff --git a/firstleg/src/main/java/com/baiyue/firstleg/mapper/InboundFeeDetailMapper.java b/firstleg/src/main/java/com/baiyue/firstleg/mapper/InboundFeeDetailMapper.java index 145da65e2..89364f961 100644 --- a/firstleg/src/main/java/com/baiyue/firstleg/mapper/InboundFeeDetailMapper.java +++ b/firstleg/src/main/java/com/baiyue/firstleg/mapper/InboundFeeDetailMapper.java @@ -3,7 +3,20 @@ package com.baiyue.firstleg.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baiyue.firstleg.entity.InboundFeeDetailEntity; import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Update; @Mapper public interface InboundFeeDetailMapper extends BaseMapper { + @Update(""" + UPDATE fl_inbound_fee_detail + SET deleted = 1, updated_at = CURRENT_TIMESTAMP(3) + WHERE tenant_id = #{tenantId} + AND inbound_id = #{inboundId} + AND id = #{detailId} + AND deleted = 0 + """) + int logicallyDelete(@Param("tenantId") long tenantId, + @Param("inboundId") long inboundId, + @Param("detailId") long detailId); } diff --git a/firstleg/src/main/java/com/baiyue/firstleg/repository/FeeItemRepository.java b/firstleg/src/main/java/com/baiyue/firstleg/repository/FeeItemRepository.java index 8e12bce84..22ea7eac0 100644 --- a/firstleg/src/main/java/com/baiyue/firstleg/repository/FeeItemRepository.java +++ b/firstleg/src/main/java/com/baiyue/firstleg/repository/FeeItemRepository.java @@ -165,10 +165,8 @@ public class FeeItemRepository { .eq(InboundFeeDetailEntity::getId, detail.getId())); } - public int deleteDetail(long tenantId, long detailId) { - return feeDetails.delete(new LambdaQueryWrapper() - .eq(InboundFeeDetailEntity::getTenantId, tenantId) - .eq(InboundFeeDetailEntity::getId, detailId)); + public int deleteDetail(long tenantId, long inboundId, long detailId) { + return feeDetails.logicallyDelete(tenantId, inboundId, detailId); } public int review(long tenantId, long inboundId, long detailId, String targetStatus, diff --git a/firstleg/src/main/java/com/baiyue/firstleg/service/InboundFeeDetailService.java b/firstleg/src/main/java/com/baiyue/firstleg/service/InboundFeeDetailService.java index de30e5117..442c19d28 100644 --- a/firstleg/src/main/java/com/baiyue/firstleg/service/InboundFeeDetailService.java +++ b/firstleg/src/main/java/com/baiyue/firstleg/service/InboundFeeDetailService.java @@ -159,7 +159,9 @@ public class InboundFeeDetailService { @Transactional public void delete(long tenantId, long inboundId, long detailId) { requiredEditable(tenantId, inboundId, detailId); - if (repository.deleteDetail(tenantId, detailId) != 1) throw conflict("费用明细删除冲突"); + if (repository.deleteDetail(tenantId, inboundId, detailId) != 1) { + throw conflict("费用明细删除冲突"); + } } @Transactional diff --git a/firstleg/src/test/java/com/baiyue/firstleg/repository/FinanceFeeSourceMigrationContractTest.java b/firstleg/src/test/java/com/baiyue/firstleg/repository/FinanceFeeSourceMigrationContractTest.java index 05d6f9b8b..f74e30658 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/repository/FinanceFeeSourceMigrationContractTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/repository/FinanceFeeSourceMigrationContractTest.java @@ -8,6 +8,22 @@ import java.nio.charset.StandardCharsets; import static org.assertj.core.api.Assertions.assertThat; class FinanceFeeSourceMigrationContractTest { + @Test + void freshSchemaAndMigrationExposeInboundFeePartnerSnapshotsUsedByAllFeeFlows() throws Exception { + String baseline = new ClassPathResource("db/phase2-fee-item-and-inbound-detail.sql") + .getContentAsString(StandardCharsets.UTF_8); + String migration = new ClassPathResource("db/finance-fee-source-migration.sql") + .getContentAsString(StandardCharsets.UTF_8); + + assertThat(baseline).contains( + "partner_id BIGINT UNSIGNED NULL", + "partner_name VARCHAR(200) NULL"); + assertThat(migration).contains( + "firstleg_finance_add_column('fl_inbound_fee_detail', 'partner_id'", + "firstleg_finance_add_column('fl_inbound_fee_detail', 'partner_name'", + "Firstleg Finance迁移失败:入库费用往来单位快照字段不完整"); + } + @Test void migrationDefinesIdempotentOutboxEnvelopeAndContainerFeeModel() throws Exception { String sql = new ClassPathResource("db/finance-fee-source-migration.sql") diff --git a/firstleg/src/test/java/com/baiyue/firstleg/repository/InboundFeeDetailMapperContractTest.java b/firstleg/src/test/java/com/baiyue/firstleg/repository/InboundFeeDetailMapperContractTest.java new file mode 100644 index 000000000..16cbac3fc --- /dev/null +++ b/firstleg/src/test/java/com/baiyue/firstleg/repository/InboundFeeDetailMapperContractTest.java @@ -0,0 +1,26 @@ +package com.baiyue.firstleg.repository; + +import com.baiyue.firstleg.mapper.InboundFeeDetailMapper; +import org.apache.ibatis.annotations.Update; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Method; + +import static org.assertj.core.api.Assertions.assertThat; + +class InboundFeeDetailMapperContractTest { + @Test + void logicalDeleteIsScopedToCustomerInboundDetailAndActiveRow() throws Exception { + Method method = InboundFeeDetailMapper.class.getMethod( + "logicallyDelete", long.class, long.class, long.class); + String sql = String.join(" ", method.getAnnotation(Update.class).value()) + .replaceAll("\\s+", " ").toLowerCase(); + + assertThat(sql) + .contains("set deleted = 1") + .contains("tenant_id = #{tenantid}") + .contains("inbound_id = #{inboundid}") + .contains("id = #{detailid}") + .contains("deleted = 0"); + } +} diff --git a/firstleg/src/test/java/com/baiyue/firstleg/repository/TenantScopedMutationRepositoryTest.java b/firstleg/src/test/java/com/baiyue/firstleg/repository/TenantScopedMutationRepositoryTest.java index 681291bf9..9d2ef2219 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/repository/TenantScopedMutationRepositoryTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/repository/TenantScopedMutationRepositoryTest.java @@ -112,15 +112,12 @@ class TenantScopedMutationRepositoryTest { } @Test - @SuppressWarnings({"rawtypes", "unchecked"}) void feeDetailDeleteMatchesTenantAndId() { - when(feeDetails.delete(any(Wrapper.class))).thenReturn(1); + when(feeDetails.logicallyDelete(TENANT, 77L, ID)).thenReturn(1); - assertThat(feeRepository.deleteDetail(TENANT, ID)).isEqualTo(1); + assertThat(feeRepository.deleteDetail(TENANT, 77L, ID)).isEqualTo(1); - ArgumentCaptor> wrapper = ArgumentCaptor.forClass(Wrapper.class); - verify(feeDetails).delete(wrapper.capture()); - assertTenantAndId(wrapper.getValue()); + verify(feeDetails).logicallyDelete(TENANT, 77L, ID); } @Test diff --git a/firstleg/src/test/java/com/baiyue/firstleg/service/FeeManagementServiceTest.java b/firstleg/src/test/java/com/baiyue/firstleg/service/FeeManagementServiceTest.java index 37ab8e57f..6971a2f77 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/service/FeeManagementServiceTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/service/FeeManagementServiceTest.java @@ -56,7 +56,20 @@ class FeeManagementServiceTest { .hasMessageContaining("费用明细不存在"); verify(repository).findDetail(10002L, 21L, 31L); - verify(repository, never()).deleteDetail(10002L, 31L); + verify(repository, never()).deleteDetail(10002L, 21L, 31L); + } + + @Test + @DisplayName("待审核费用明细按客户和入库单作用域删除") + void deletesPendingFeeDetailInInboundScope() { + InboundFeeDetailEntity pending = new InboundFeeDetailEntity(); + pending.setReviewStatus("PENDING"); + when(repository.findDetail(10001L, 21L, 31L)).thenReturn(pending); + when(repository.deleteDetail(10001L, 21L, 31L)).thenReturn(1); + + details.delete(10001L, 21L, 31L); + + verify(repository).deleteDetail(10001L, 21L, 31L); } @Test diff --git a/scripts/admin-test-env-acceptance.mjs b/scripts/admin-test-env-acceptance.mjs index c3c8b270d..acd2d0ce3 100644 --- a/scripts/admin-test-env-acceptance.mjs +++ b/scripts/admin-test-env-acceptance.mjs @@ -416,7 +416,18 @@ export class AcceptanceRun { } const customerAudit = this.apiSuccess(module, '按客户查询操作日志', customerAuditResponse) const customerAuditRow = customerAudit?.records?.find((item) => item.eventType === 'PLATFORM_TENANT_UPDATE' && String(item.accountId) === tenantId) - this.assert(module, '客户操作日志包含本轮编辑事件', customerAuditResponse, Boolean(customerAuditRow), `tenantId=${tenantId}, event=${customerAuditRow?.eventType || 'missing'}`) + let auditDiagnostic = 'event=missing' + if (!customerAuditRow) { + const unscopedResponse = await this.request('GET', `/api/platform/v1/audit/logs?eventType=PLATFORM_TENANT_UPDATE&resourceType=TENANT&resourceId=${tenantId}&page=1&size=20`) + const unscopedPage = unscopedResponse.http === 200 && String(unscopedResponse.body?.code) === '200' + ? unscopedResponse.body?.data : null + const misplaced = unscopedPage?.records?.find((item) => item.eventType === 'PLATFORM_TENANT_UPDATE') + auditDiagnostic = misplaced + ? `eventAccountId=${misplaced.accountId ?? 'missing'}, expectedAccountId=${tenantId}` + : `event=not-delivered, editRequestId=${editResponse.requestId || 'missing'}, editTraceId=${editResponse.traceId || 'missing'}` + } + this.assert(module, '客户操作日志包含本轮编辑事件', customerAuditResponse, Boolean(customerAuditRow), + `tenantId=${tenantId}, ${customerAuditRow ? `event=${customerAuditRow.eventType}` : auditDiagnostic}`) if (customerAuditRow?.id) { const auditDetailResponse = await this.request('GET', `/api/platform/v1/audit/logs/${customerAuditRow.id}`) const auditDetail = this.apiSuccess(module, '查询客户操作日志详情', auditDetailResponse) @@ -434,7 +445,7 @@ export class AcceptanceRun { const qualificationImage = Uint8Array.from(Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=', 'base64')) qualificationPackage.append('businessLicense', new Blob([qualificationImage], { type: 'image/png' }), `business-license-${suffix}.png`) qualificationPackage.append('legalPersonId', new Blob([qualificationImage], { type: 'image/png' }), `legal-person-id-${suffix}.png`) - qualificationPackage.append('unifiedSocialCreditCode', `91440300${suffix.replace(/[^0-9A-Z]/gi, '').toUpperCase().padStart(10, '0').slice(-10)}`) + qualificationPackage.append('unifiedSocialCreditCode', `91440301${suffix.replace(/[^0-9A-Z]/gi, '').toUpperCase().padStart(10, '0').slice(-10)}`) const packageResponse = await this.requestWithBody('POST', `/api/platform/v1/files/qualifications/${tenantId}/required-package`, qualificationPackage) this.apiSuccess(module, '上传审核所需客户资质包', packageResponse, `tenantId=${tenantId}`) const qualificationResponse = await this.request('GET', `${detailPath}/qualifications`); const qualifications = this.apiSuccess(module, '业务概况查询资质', qualificationResponse) diff --git a/scripts/admin-test-env-modules/customer-system-contract.test.mjs b/scripts/admin-test-env-modules/customer-system-contract.test.mjs index 759cb0ffc..f1d57e6a2 100644 --- a/scripts/admin-test-env-modules/customer-system-contract.test.mjs +++ b/scripts/admin-test-env-modules/customer-system-contract.test.mjs @@ -36,6 +36,13 @@ test('客户验收覆盖客户维度操作日志列表和详情快照', () => { assert.match(acceptance, /audit\/logs\/\$\{customerAuditRow\.id\}/) assert.match(acceptance, /JSON\.parse\(auditDetail\[field\]\)/) assert.match(acceptance, /snapshotsAreRedacted/) + assert.match(acceptance, /eventAccountId=.*expectedAccountId=/) + assert.match(acceptance, /event=not-delivered, editRequestId=/) +}) + +test('客户资质场景使用独立社会信用代码命名空间', () => { + assert.match(acceptance, /unifiedSocialCreditCode', `91440301/) + assert.doesNotMatch(operatingStatistics, /unifiedSocialCreditCode/) }) test('客户详情经营统计模块登记三项独立正式接口与精度字段', () => { diff --git a/scripts/admin-test-env-modules/fee-items-contract.test.mjs b/scripts/admin-test-env-modules/fee-items-contract.test.mjs index b8b150715..e34bc070a 100644 --- a/scripts/admin-test-env-modules/fee-items-contract.test.mjs +++ b/scripts/admin-test-env-modules/fee-items-contract.test.mjs @@ -2,7 +2,7 @@ import assert from 'node:assert/strict' import { readFileSync } from 'node:fs' import test from 'node:test' -import { cleanupSucceeded } from './fee-items.mjs' +import { cleanupSucceeded, isAcceptanceFeeItem } from './fee-items.mjs' const source = readFileSync(new URL('./fee-items.mjs', import.meta.url), 'utf8') @@ -20,3 +20,14 @@ test('cleanup treats successful deletion and an already absent resource as clean assert.equal(cleanupSucceeded({ http: 404, body: {} }), true) assert.equal(cleanupSucceeded({ http: 200, body: { code: 'A0422' } }), false) }) + +test('stale cleanup only recognizes the acceptance-owned code and name pair', () => { + assert.equal(isAcceptanceFeeItem({ feeCode: 'ACPT_813083345572', feeTypeName: '全量验收费用-813083345572-已编辑' }), true) + assert.equal(isAcceptanceFeeItem({ feeCode: 'FEE_TYPE_001', feeTypeName: '全量验收费用-001' }), false) + assert.equal(isAcceptanceFeeItem({ feeCode: 'ACPT_813083345572', feeTypeName: '正式费用项目' }), false) +}) + +test('baseline checks run only after stale acceptance cleanup', () => { + assert.ok(source.indexOf('cleanupStaleAcceptanceItems(context)') + < source.indexOf("'分页总数110且默认20条对应6页'")) +}) diff --git a/scripts/admin-test-env-modules/fee-items.mjs b/scripts/admin-test-env-modules/fee-items.mjs index 68f351a86..4f488ebe4 100644 --- a/scripts/admin-test-env-modules/fee-items.mjs +++ b/scripts/admin-test-env-modules/fee-items.mjs @@ -44,6 +44,10 @@ function payload(suffix, overrides = {}) { function expectedCodes() { return Array.from({ length: 110 }, (_, index) => `FEE_TYPE_${String(index + 1).padStart(3, '0')}`) } +export function isAcceptanceFeeItem(row) { + return /^ACPT_\d+$/.test(String(row?.feeCode || '')) + && String(row?.feeTypeName || '').startsWith('全量验收费用-') +} function fixture(context) { const candidates = [context.createdResources?.orders, context.createdResources?.testInbound, context.createdResources?.inboundMeasurement, context.context?.testInbound] @@ -57,7 +61,26 @@ async function listAll(context) { return { response, page, rows: records(page) } } +async function cleanupStaleAcceptanceItems(context) { + const listed = await listAll(context) + const stale = listed.rows.filter(isAcceptanceFeeItem) + const failed = [] + for (const row of stale) { + const response = await context.request('DELETE', `${BASE}/${encodeURIComponent(id(row.id))}`) + if (!cleanupSucceeded(response)) failed.push({ row, response }) + } + const response = failed[0]?.response || listed.response + context.assert(MODULE, '前置清理历史验收临时费用项目', response, failed.length === 0, + failed.length === 0 + ? `stale=${stale.length}, cleaned=${stale.length}` + : `failed=${failed.map(({ row, response: failure }) => `${row.feeCode}:${failure.body?.code || failure.http}`).join(',')}`, + OPTIONS) + return failed.length === 0 +} + export async function run(context) { + if (!await cleanupStaleAcceptanceItems(context)) return + const initialPageResponse = await context.request('GET', query({ page: 1, size: 20 })) const initialPage = context.apiSuccess(MODULE, '费用项目查看权限与分页查询', initialPageResponse, 'page=1,size=20', OPTIONS) diff --git a/scripts/admin-test-env-modules/finance-contract.test.mjs b/scripts/admin-test-env-modules/finance-contract.test.mjs index 627720221..29d60e523 100644 --- a/scripts/admin-test-env-modules/finance-contract.test.mjs +++ b/scripts/admin-test-env-modules/finance-contract.test.mjs @@ -42,5 +42,6 @@ test('客户详情财务账单验收覆盖客户过滤、详情结构与查看 assert.match(finance, /statementId\(statementDetail\) === createdStatementId/) assert.match(finance, /Array\.isArray\(statementDetail\?\.lines\)/) assert.match(finance, /Array\.isArray\(statementDetail\?\.revisions\)/) - assert.match(finance, /platform:finance:bill:view/) + assert.match(finance, /grantedPermissions\.has\(['"]platform:finance:customer-statement:list['"]\)/) + assert.doesNotMatch(finance, /platform:finance:bill:view/) }) diff --git a/scripts/admin-test-env-modules/finance.mjs b/scripts/admin-test-env-modules/finance.mjs index fdab5e0c3..82141dad2 100644 --- a/scripts/admin-test-env-modules/finance.mjs +++ b/scripts/admin-test-env-modules/finance.mjs @@ -83,7 +83,7 @@ export async function run(context) { const permissions = context.apiSuccess(MODULE, '客户详情财务账单查看权限链可用', permissionResponse, '', OPTIONS) const grantedPermissions = new Set(rows(permissions).map((item) => id(item?.permissionCode ?? item))) context.assert(MODULE, '财务账单列表查看权限已授予', permissionResponse, - grantedPermissions.has('platform:finance:bill:view') || grantedPermissions.has('platform:finance:customer-statement:list'), + grantedPermissions.has('platform:finance:customer-statement:list'), `permissions=${[...grantedPermissions].filter((item) => item.includes('finance')).join(',')}`, OPTIONS) const statementListResponse = await context.request('GET', `${BASE}/statements?customerId=${encodeURIComponent(customerId)}`)