This commit is contained in:
2026-08-13 16:58:17 +08:00
parent bd523036d0
commit 38e8494216
15 changed files with 344 additions and 17 deletions
@@ -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<InboundFeeDetailEntity> {
@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);
}
@@ -165,10 +165,8 @@ public class FeeItemRepository {
.eq(InboundFeeDetailEntity::getId, detail.getId()));
}
public int deleteDetail(long tenantId, long detailId) {
return feeDetails.delete(new LambdaQueryWrapper<InboundFeeDetailEntity>()
.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,
@@ -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
@@ -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")
@@ -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");
}
}
@@ -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<InboundFeeDetailEntity>> wrapper = ArgumentCaptor.forClass(Wrapper.class);
verify(feeDetails).delete(wrapper.capture());
assertTenantAndId(wrapper.getValue());
verify(feeDetails).logicallyDelete(TENANT, 77L, ID);
}
@Test
@@ -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