优化
This commit is contained in:
@@ -10371,8 +10371,6 @@ CALL firstleg_finance_add_column('fl_inbound_outbox', 'locked_by',
|
|||||||
CALL firstleg_finance_add_column('fl_inbound_outbox', 'last_error',
|
CALL firstleg_finance_add_column('fl_inbound_outbox', 'last_error',
|
||||||
"VARCHAR(500) NULL COMMENT '最近一次投递失败摘要,不记录令牌' AFTER locked_by");
|
"VARCHAR(500) NULL COMMENT '最近一次投递失败摘要,不记录令牌' AFTER locked_by");
|
||||||
|
|
||||||
DROP PROCEDURE IF EXISTS firstleg_finance_add_column;
|
|
||||||
|
|
||||||
DROP PROCEDURE IF EXISTS firstleg_finance_add_index;
|
DROP PROCEDURE IF EXISTS firstleg_finance_add_index;
|
||||||
DELIMITER $$
|
DELIMITER $$
|
||||||
CREATE PROCEDURE firstleg_finance_add_index(
|
CREATE PROCEDURE firstleg_finance_add_index(
|
||||||
@@ -10407,6 +10405,8 @@ CREATE TABLE IF NOT EXISTS fl_container_fee_detail (
|
|||||||
fee_item_code_snapshot VARCHAR(50) NOT NULL COMMENT '费用项目稳定编码快照',
|
fee_item_code_snapshot VARCHAR(50) NOT NULL COMMENT '费用项目稳定编码快照',
|
||||||
fee_item_name_snapshot VARCHAR(128) NOT NULL COMMENT '费用项目名称快照',
|
fee_item_name_snapshot VARCHAR(128) NOT NULL COMMENT '费用项目名称快照',
|
||||||
direction VARCHAR(16) NOT NULL COMMENT '费用方向:RECEIVABLE应收、PAYABLE应付',
|
direction VARCHAR(16) NOT NULL COMMENT '费用方向:RECEIVABLE应收、PAYABLE应付',
|
||||||
|
partner_id BIGINT UNSIGNED NULL COMMENT '应付往来单位ID快照;应收为空',
|
||||||
|
partner_name VARCHAR(200) NULL COMMENT '应付往来单位名称快照;应收为空',
|
||||||
business_date DATE NOT NULL COMMENT '费用业务日期',
|
business_date DATE NOT NULL COMMENT '费用业务日期',
|
||||||
amount DECIMAL(18,2) NOT NULL COMMENT 'CNY费用金额',
|
amount DECIMAL(18,2) NOT NULL COMMENT 'CNY费用金额',
|
||||||
currency CHAR(3) NOT NULL DEFAULT 'CNY' COMMENT '币种,首期固定CNY',
|
currency CHAR(3) NOT NULL DEFAULT 'CNY' COMMENT '币种,首期固定CNY',
|
||||||
@@ -10433,6 +10433,12 @@ CREATE TABLE IF NOT EXISTS fl_container_fee_detail (
|
|||||||
CONSTRAINT chk_container_fee_source CHECK (source_status IN ('ACTIVE', 'VOIDED'))
|
CONSTRAINT chk_container_fee_source CHECK (source_status IN ('ACTIVE', 'VOIDED'))
|
||||||
) ENGINE=InnoDB COMMENT='柜次级人工应收应付费用来源;审核通过且参与结算后同步Finance';
|
) ENGINE=InnoDB COMMENT='柜次级人工应收应付费用来源;审核通过且参与结算后同步Finance';
|
||||||
|
|
||||||
|
CALL firstleg_finance_add_column('fl_container_fee_detail', 'partner_id',
|
||||||
|
"BIGINT UNSIGNED NULL COMMENT '应付往来单位ID快照;应收为空' AFTER direction");
|
||||||
|
CALL firstleg_finance_add_column('fl_container_fee_detail', 'partner_name',
|
||||||
|
"VARCHAR(200) NULL COMMENT '应付往来单位名称快照;应收为空' AFTER partner_id");
|
||||||
|
DROP PROCEDURE IF EXISTS firstleg_finance_add_column;
|
||||||
|
|
||||||
DROP PROCEDURE IF EXISTS firstleg_finance_assert_schema;
|
DROP PROCEDURE IF EXISTS firstleg_finance_assert_schema;
|
||||||
DELIMITER $$
|
DELIMITER $$
|
||||||
CREATE PROCEDURE firstleg_finance_assert_schema()
|
CREATE PROCEDURE firstleg_finance_assert_schema()
|
||||||
@@ -10447,6 +10453,11 @@ BEGIN
|
|||||||
WHERE table_schema = DATABASE() AND table_name = 'fl_container_fee_detail') THEN
|
WHERE table_schema = DATABASE() AND table_name = 'fl_container_fee_detail') THEN
|
||||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Firstleg Finance迁移失败:柜次费用表不存在';
|
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Firstleg Finance迁移失败:柜次费用表不存在';
|
||||||
END IF;
|
END IF;
|
||||||
|
IF (SELECT COUNT(*) FROM information_schema.columns
|
||||||
|
WHERE table_schema = DATABASE() AND table_name = 'fl_container_fee_detail'
|
||||||
|
AND column_name IN ('partner_id', 'partner_name')) <> 2 THEN
|
||||||
|
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Firstleg Finance迁移失败:柜次费用往来单位快照字段不完整';
|
||||||
|
END IF;
|
||||||
END$$
|
END$$
|
||||||
DELIMITER ;
|
DELIMITER ;
|
||||||
CALL firstleg_finance_assert_schema();
|
CALL firstleg_finance_assert_schema();
|
||||||
@@ -11532,6 +11543,8 @@ CALL queue_column_comment('baiyue_firstleg', 'fl_container_fee_detail', 'created
|
|||||||
CALL queue_column_comment('baiyue_firstleg', 'fl_container_fee_detail', 'created_by', '创建人ID');
|
CALL queue_column_comment('baiyue_firstleg', 'fl_container_fee_detail', 'created_by', '创建人ID');
|
||||||
CALL queue_column_comment('baiyue_firstleg', 'fl_container_fee_detail', 'currency', '币种,首期固定CNY');
|
CALL queue_column_comment('baiyue_firstleg', 'fl_container_fee_detail', 'currency', '币种,首期固定CNY');
|
||||||
CALL queue_column_comment('baiyue_firstleg', 'fl_container_fee_detail', 'direction', '费用方向:RECEIVABLE应收、PAYABLE应付');
|
CALL queue_column_comment('baiyue_firstleg', 'fl_container_fee_detail', 'direction', '费用方向:RECEIVABLE应收、PAYABLE应付');
|
||||||
|
CALL queue_column_comment('baiyue_firstleg', 'fl_container_fee_detail', 'partner_id', '应付往来单位ID快照;应收为空');
|
||||||
|
CALL queue_column_comment('baiyue_firstleg', 'fl_container_fee_detail', 'partner_name', '应付往来单位名称快照;应收为空');
|
||||||
CALL queue_column_comment('baiyue_firstleg', 'fl_container_fee_detail', 'fee_item_code_snapshot', '费用项目稳定编码快照');
|
CALL queue_column_comment('baiyue_firstleg', 'fl_container_fee_detail', 'fee_item_code_snapshot', '费用项目稳定编码快照');
|
||||||
CALL queue_column_comment('baiyue_firstleg', 'fl_container_fee_detail', 'fee_item_id', '平台公共费用项目ID');
|
CALL queue_column_comment('baiyue_firstleg', 'fl_container_fee_detail', 'fee_item_id', '平台公共费用项目ID');
|
||||||
CALL queue_column_comment('baiyue_firstleg', 'fl_container_fee_detail', 'fee_item_name_snapshot', '费用项目名称快照');
|
CALL queue_column_comment('baiyue_firstleg', 'fl_container_fee_detail', 'fee_item_name_snapshot', '费用项目名称快照');
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
import static org.mockito.ArgumentMatchers.*;
|
import static org.mockito.ArgumentMatchers.*;
|
||||||
import static org.mockito.Mockito.*;
|
import static org.mockito.Mockito.*;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
|
||||||
class FinanceFeeSourceEventServiceTest {
|
class FinanceFeeSourceEventServiceTest {
|
||||||
private final JdbcTemplate jdbc = mock(JdbcTemplate.class);
|
private final JdbcTemplate jdbc = mock(JdbcTemplate.class);
|
||||||
@@ -58,6 +59,26 @@ class FinanceFeeSourceEventServiceTest {
|
|||||||
verify(jdbc).update(contains("status='SUPERSEDED'"), eq(31L));
|
verify(jdbc).update(contains("status='SUPERSEDED'"), eq(31L));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void importsContainerPayableWithCounterpartySnapshotForApSettlement() {
|
||||||
|
FinanceFeeSourceEvent event = containerEvent("container-payable-1", "FIRSTLEG_FEE_PUBLISHED", 1, 7);
|
||||||
|
givenNewEvent();
|
||||||
|
var snapshot = new FirstlegFeeSourceClient.Snapshot(7, "CONTAINER_FEE", 31, 1,
|
||||||
|
null, null, null, 41L, 61L, "C-41", 7, "客户", 71L, "现有往来单位",
|
||||||
|
61L, "TRUCKING", "拖车费", "PAYABLE", LocalDate.of(2026, 8, 11),
|
||||||
|
"CNY", new BigDecimal("300.00"), "APPROVED", "PUBLISHED", true,
|
||||||
|
LocalDateTime.of(2026, 8, 11, 1, 0), null, null, null, null,
|
||||||
|
"source-hash-payable", 1);
|
||||||
|
when(firstleg.container("test-token", 41, 31)).thenReturn(ApiResponse.success(snapshot));
|
||||||
|
|
||||||
|
assertThat(service.consume(event)).isEqualTo(101L);
|
||||||
|
|
||||||
|
ArgumentCaptor<Object[]> values = ArgumentCaptor.forClass(Object[].class);
|
||||||
|
verify(jdbc).update(startsWith("INSERT INTO fin_fee_source"), values.capture());
|
||||||
|
assertThat(values.getValue()[14].toString())
|
||||||
|
.contains("\"partnerId\":71", "\"partnerName\":\"现有往来单位\"");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void adjustmentCreatesNewImmutableVersionAndSupersedesAvailableVersion() {
|
void adjustmentCreatesNewImmutableVersionAndSupersedesAvailableVersion() {
|
||||||
FinanceFeeSourceEvent event = inboundEvent("adjusted-2", "FIRSTLEG_FEE_ADJUSTED", 2, 7);
|
FinanceFeeSourceEvent event = inboundEvent("adjusted-2", "FIRSTLEG_FEE_ADJUSTED", 2, 7);
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ public final class ContainerFeeDetailDtos {
|
|||||||
@NotNull @DecimalMin("0.00") BigDecimal amount,
|
@NotNull @DecimalMin("0.00") BigDecimal amount,
|
||||||
@NotBlank @Pattern(regexp = "CNY") String currency,
|
@NotBlank @Pattern(regexp = "CNY") String currency,
|
||||||
@NotNull Boolean settlementIncluded,
|
@NotNull Boolean settlementIncluded,
|
||||||
|
Long partnerId,
|
||||||
|
@Size(max = 200) String partnerName,
|
||||||
@Size(max = 500) String remark
|
@Size(max = 500) String remark
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
@@ -33,11 +35,13 @@ public final class ContainerFeeDetailDtos {
|
|||||||
@NotNull @DecimalMin("0.00") BigDecimal amount,
|
@NotNull @DecimalMin("0.00") BigDecimal amount,
|
||||||
@NotBlank @Pattern(regexp = "CNY") String currency,
|
@NotBlank @Pattern(regexp = "CNY") String currency,
|
||||||
@NotNull Boolean settlementIncluded,
|
@NotNull Boolean settlementIncluded,
|
||||||
|
Long partnerId,
|
||||||
|
@Size(max = 200) String partnerName,
|
||||||
@Size(max = 500) String remark
|
@Size(max = 500) String remark
|
||||||
) {
|
) {
|
||||||
public SaveRequest values() {
|
public SaveRequest values() {
|
||||||
return new SaveRequest(feeItemId, direction, businessDate, amount, currency,
|
return new SaveRequest(feeItemId, direction, businessDate, amount, currency,
|
||||||
settlementIncluded, remark);
|
settlementIncluded, partnerId, partnerName, remark);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ public class ContainerFeeDetailEntity {
|
|||||||
private String feeItemCodeSnapshot;
|
private String feeItemCodeSnapshot;
|
||||||
private String feeItemNameSnapshot;
|
private String feeItemNameSnapshot;
|
||||||
private String direction;
|
private String direction;
|
||||||
|
private Long partnerId;
|
||||||
|
private String partnerName;
|
||||||
private LocalDate businessDate;
|
private LocalDate businessDate;
|
||||||
private BigDecimal amount;
|
private BigDecimal amount;
|
||||||
private String currency;
|
private String currency;
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import com.baiyue.firstleg.repository.SmartLoadingRepository;
|
|||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
import java.math.RoundingMode;
|
import java.math.RoundingMode;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
@@ -140,10 +141,17 @@ public class ContainerFeeDetailService {
|
|||||||
|
|
||||||
private static void apply(ContainerFeeDetailEntity value, FeeItemEntity item,
|
private static void apply(ContainerFeeDetailEntity value, FeeItemEntity item,
|
||||||
SaveRequest request, long operatorId) {
|
SaveRequest request, long operatorId) {
|
||||||
|
if ("PAYABLE".equals(request.direction())
|
||||||
|
&& (request.partnerId() == null || request.partnerId() <= 0
|
||||||
|
|| !StringUtils.hasText(request.partnerName()))) {
|
||||||
|
throw new BizException(ResultCode.VALIDATION_FAILED, "应付柜次费用必须选择有效往来单位");
|
||||||
|
}
|
||||||
value.setFeeItemId(item.getId());
|
value.setFeeItemId(item.getId());
|
||||||
value.setFeeItemCodeSnapshot(item.getFeeCode());
|
value.setFeeItemCodeSnapshot(item.getFeeCode());
|
||||||
value.setFeeItemNameSnapshot(item.getFeeTypeName());
|
value.setFeeItemNameSnapshot(item.getFeeTypeName());
|
||||||
value.setDirection(request.direction());
|
value.setDirection(request.direction());
|
||||||
|
value.setPartnerId("PAYABLE".equals(request.direction()) ? request.partnerId() : null);
|
||||||
|
value.setPartnerName("PAYABLE".equals(request.direction()) ? request.partnerName().trim() : null);
|
||||||
value.setBusinessDate(request.businessDate());
|
value.setBusinessDate(request.businessDate());
|
||||||
value.setAmount(request.amount().setScale(2, RoundingMode.HALF_UP));
|
value.setAmount(request.amount().setScale(2, RoundingMode.HALF_UP));
|
||||||
value.setCurrency(request.currency());
|
value.setCurrency(request.currency());
|
||||||
|
|||||||
+3
-1
@@ -61,11 +61,13 @@ public class FinanceFeeSourceSnapshotService {
|
|||||||
String canonical = String.join("|", "CONTAINER_FEE", String.valueOf(feeId),
|
String canonical = String.join("|", "CONTAINER_FEE", String.valueOf(feeId),
|
||||||
String.valueOf(version), String.valueOf(detail.getTenantId()), String.valueOf(containerId),
|
String.valueOf(version), String.valueOf(detail.getTenantId()), String.valueOf(containerId),
|
||||||
nullSafe(detail.getFeeItemCodeSnapshot()), nullSafe(detail.getDirection()),
|
nullSafe(detail.getFeeItemCodeSnapshot()), nullSafe(detail.getDirection()),
|
||||||
|
nullSafe(detail.getPartnerId()), nullSafe(detail.getPartnerName()),
|
||||||
detail.getAmount().toPlainString(), nullSafe(detail.getCurrency()),
|
detail.getAmount().toPlainString(), nullSafe(detail.getCurrency()),
|
||||||
String.valueOf(detail.getSettlementIncluded()));
|
String.valueOf(detail.getSettlementIncluded()));
|
||||||
return new FinanceFeeSourceSnapshot(detail.getTenantId(), "CONTAINER_FEE", feeId,
|
return new FinanceFeeSourceSnapshot(detail.getTenantId(), "CONTAINER_FEE", feeId,
|
||||||
version, null, null, null, containerId, detail.getContainerBookingId(),
|
version, null, null, null, containerId, detail.getContainerBookingId(),
|
||||||
detail.getContainerNo(), detail.getTenantId(), null, null, null, detail.getFeeItemId(),
|
detail.getContainerNo(), detail.getTenantId(), null, detail.getPartnerId(),
|
||||||
|
detail.getPartnerName(), detail.getFeeItemId(),
|
||||||
detail.getFeeItemCodeSnapshot(), detail.getFeeItemNameSnapshot(), detail.getDirection(),
|
detail.getFeeItemCodeSnapshot(), detail.getFeeItemNameSnapshot(), detail.getDirection(),
|
||||||
detail.getBusinessDate(), detail.getCurrency(), detail.getAmount(), detail.getReviewStatus(),
|
detail.getBusinessDate(), detail.getCurrency(), detail.getAmount(), detail.getReviewStatus(),
|
||||||
publicationStatus, Boolean.TRUE.equals(detail.getSettlementIncluded()),
|
publicationStatus, Boolean.TRUE.equals(detail.getSettlementIncluded()),
|
||||||
|
|||||||
@@ -37,8 +37,6 @@ CALL firstleg_finance_add_column('fl_inbound_outbox', 'locked_by',
|
|||||||
CALL firstleg_finance_add_column('fl_inbound_outbox', 'last_error',
|
CALL firstleg_finance_add_column('fl_inbound_outbox', 'last_error',
|
||||||
"VARCHAR(500) NULL COMMENT '最近一次投递失败摘要,不记录令牌' AFTER locked_by");
|
"VARCHAR(500) NULL COMMENT '最近一次投递失败摘要,不记录令牌' AFTER locked_by");
|
||||||
|
|
||||||
DROP PROCEDURE IF EXISTS firstleg_finance_add_column;
|
|
||||||
|
|
||||||
DROP PROCEDURE IF EXISTS firstleg_finance_add_index;
|
DROP PROCEDURE IF EXISTS firstleg_finance_add_index;
|
||||||
DELIMITER $$
|
DELIMITER $$
|
||||||
CREATE PROCEDURE firstleg_finance_add_index(
|
CREATE PROCEDURE firstleg_finance_add_index(
|
||||||
@@ -73,6 +71,8 @@ CREATE TABLE IF NOT EXISTS fl_container_fee_detail (
|
|||||||
fee_item_code_snapshot VARCHAR(50) NOT NULL COMMENT '费用项目稳定编码快照',
|
fee_item_code_snapshot VARCHAR(50) NOT NULL COMMENT '费用项目稳定编码快照',
|
||||||
fee_item_name_snapshot VARCHAR(128) NOT NULL COMMENT '费用项目名称快照',
|
fee_item_name_snapshot VARCHAR(128) NOT NULL COMMENT '费用项目名称快照',
|
||||||
direction VARCHAR(16) NOT NULL COMMENT '费用方向:RECEIVABLE应收、PAYABLE应付',
|
direction VARCHAR(16) NOT NULL COMMENT '费用方向:RECEIVABLE应收、PAYABLE应付',
|
||||||
|
partner_id BIGINT UNSIGNED NULL COMMENT '应付往来单位ID快照;应收为空',
|
||||||
|
partner_name VARCHAR(200) NULL COMMENT '应付往来单位名称快照;应收为空',
|
||||||
business_date DATE NOT NULL COMMENT '费用业务日期',
|
business_date DATE NOT NULL COMMENT '费用业务日期',
|
||||||
amount DECIMAL(18,2) NOT NULL COMMENT 'CNY费用金额',
|
amount DECIMAL(18,2) NOT NULL COMMENT 'CNY费用金额',
|
||||||
currency CHAR(3) NOT NULL DEFAULT 'CNY' COMMENT '币种,首期固定CNY',
|
currency CHAR(3) NOT NULL DEFAULT 'CNY' COMMENT '币种,首期固定CNY',
|
||||||
@@ -99,6 +99,12 @@ CREATE TABLE IF NOT EXISTS fl_container_fee_detail (
|
|||||||
CONSTRAINT chk_container_fee_source CHECK (source_status IN ('ACTIVE', 'VOIDED'))
|
CONSTRAINT chk_container_fee_source CHECK (source_status IN ('ACTIVE', 'VOIDED'))
|
||||||
) ENGINE=InnoDB COMMENT='柜次级人工应收应付费用来源;审核通过且参与结算后同步Finance';
|
) ENGINE=InnoDB COMMENT='柜次级人工应收应付费用来源;审核通过且参与结算后同步Finance';
|
||||||
|
|
||||||
|
CALL firstleg_finance_add_column('fl_container_fee_detail', 'partner_id',
|
||||||
|
"BIGINT UNSIGNED NULL COMMENT '应付往来单位ID快照;应收为空' AFTER direction");
|
||||||
|
CALL firstleg_finance_add_column('fl_container_fee_detail', 'partner_name',
|
||||||
|
"VARCHAR(200) NULL COMMENT '应付往来单位名称快照;应收为空' AFTER partner_id");
|
||||||
|
DROP PROCEDURE IF EXISTS firstleg_finance_add_column;
|
||||||
|
|
||||||
DROP PROCEDURE IF EXISTS firstleg_finance_assert_schema;
|
DROP PROCEDURE IF EXISTS firstleg_finance_assert_schema;
|
||||||
DELIMITER $$
|
DELIMITER $$
|
||||||
CREATE PROCEDURE firstleg_finance_assert_schema()
|
CREATE PROCEDURE firstleg_finance_assert_schema()
|
||||||
@@ -113,6 +119,11 @@ BEGIN
|
|||||||
WHERE table_schema = DATABASE() AND table_name = 'fl_container_fee_detail') THEN
|
WHERE table_schema = DATABASE() AND table_name = 'fl_container_fee_detail') THEN
|
||||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Firstleg Finance迁移失败:柜次费用表不存在';
|
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Firstleg Finance迁移失败:柜次费用表不存在';
|
||||||
END IF;
|
END IF;
|
||||||
|
IF (SELECT COUNT(*) FROM information_schema.columns
|
||||||
|
WHERE table_schema = DATABASE() AND table_name = 'fl_container_fee_detail'
|
||||||
|
AND column_name IN ('partner_id', 'partner_name')) <> 2 THEN
|
||||||
|
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Firstleg Finance迁移失败:柜次费用往来单位快照字段不完整';
|
||||||
|
END IF;
|
||||||
END$$
|
END$$
|
||||||
DELIMITER ;
|
DELIMITER ;
|
||||||
CALL firstleg_finance_assert_schema();
|
CALL firstleg_finance_assert_schema();
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import org.springframework.security.access.prepost.PreAuthorize;
|
|||||||
import org.yaml.snakeyaml.Yaml;
|
import org.yaml.snakeyaml.Yaml;
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.RecordComponent;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -39,6 +40,17 @@ class FeeManagementContractTest {
|
|||||||
assertPermission("delete", "firstleg:fee-item:delete");
|
assertPermission("delete", "firstleg:fee-item:delete");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("柜次费用新增和调整请求均携带往来单位快照")
|
||||||
|
void containerFeeRequestsCarryCounterpartySnapshot() {
|
||||||
|
for (Class<?> requestType : List.of(
|
||||||
|
com.baiyue.firstleg.dto.ContainerFeeDetailDtos.SaveRequest.class,
|
||||||
|
com.baiyue.firstleg.dto.ContainerFeeDetailDtos.AdjustRequest.class)) {
|
||||||
|
assertThat(Arrays.stream(requestType.getRecordComponents()).map(RecordComponent::getName))
|
||||||
|
.contains("partnerId", "partnerName");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("平台费用项目接口不接受客户作用域")
|
@DisplayName("平台费用项目接口不接受客户作用域")
|
||||||
void platformFeeItemEndpointsDoNotExposeAccountId() {
|
void platformFeeItemEndpointsDoNotExposeAccountId() {
|
||||||
|
|||||||
+2
-1
@@ -16,7 +16,8 @@ class FinanceFeeSourceMigrationContractTest {
|
|||||||
assertThat(sql).contains("information_schema.columns", "event_key", "payload_hash",
|
assertThat(sql).contains("information_schema.columns", "event_key", "payload_hash",
|
||||||
"uk_inbound_outbox_event_key", "next_retry_at", "locked_at", "locked_by",
|
"uk_inbound_outbox_event_key", "next_retry_at", "locked_at", "locked_by",
|
||||||
"idx_inbound_outbox_finance_relay", "CREATE TABLE IF NOT EXISTS fl_container_fee_detail",
|
"idx_inbound_outbox_finance_relay", "CREATE TABLE IF NOT EXISTS fl_container_fee_detail",
|
||||||
"settlement_included", "source_version", "RECEIVABLE", "PAYABLE",
|
"settlement_included", "source_version", "partner_id", "partner_name",
|
||||||
|
"firstleg_finance_add_column('fl_container_fee_detail', 'partner_id'", "RECEIVABLE", "PAYABLE",
|
||||||
"chk_container_fee_currency", "currency = 'CNY'", "firstleg_finance_assert_schema");
|
"chk_container_fee_currency", "currency = 'CNY'", "firstleg_finance_assert_schema");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+34
-1
@@ -85,6 +85,38 @@ class ContainerFeeDetailServiceTest {
|
|||||||
verify(repository).adjust(captor.capture(), eq(1));
|
verify(repository).adjust(captor.capture(), eq(1));
|
||||||
assertThat(captor.getValue().getSourceVersion()).isEqualTo(2);
|
assertThat(captor.getValue().getSourceVersion()).isEqualTo(2);
|
||||||
assertThat(captor.getValue().getReviewStatus()).isEqualTo("PENDING");
|
assertThat(captor.getValue().getReviewStatus()).isEqualTo("PENDING");
|
||||||
|
assertThat(captor.getValue().getPartnerId()).isEqualTo(71L);
|
||||||
|
assertThat(captor.getValue().getPartnerName()).isEqualTo("现有往来单位");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void payableRequiresCounterpartySnapshot() {
|
||||||
|
ownedPlan();
|
||||||
|
when(feeItems.findItem(11L)).thenReturn(item());
|
||||||
|
|
||||||
|
var missingPartner = new ContainerFeeDetailDtos.SaveRequest(11L, "PAYABLE",
|
||||||
|
LocalDate.of(2026, 8, 11), new BigDecimal("300.00"), "CNY",
|
||||||
|
true, null, null, "整柜拖车费");
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> service.create(10001L, 41L, 9L, missingPartner))
|
||||||
|
.isInstanceOf(BizException.class).hasMessageContaining("必须选择有效往来单位");
|
||||||
|
verify(repository, never()).insert(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void receivableClearsCounterpartyFields() {
|
||||||
|
ownedPlan();
|
||||||
|
when(feeItems.findItem(11L)).thenReturn(item());
|
||||||
|
|
||||||
|
var request = new ContainerFeeDetailDtos.SaveRequest(11L, "RECEIVABLE",
|
||||||
|
LocalDate.of(2026, 8, 11), new BigDecimal("300.00"), "CNY",
|
||||||
|
true, 71L, "不应保留", "整柜操作费");
|
||||||
|
service.create(10001L, 41L, 9L, request);
|
||||||
|
|
||||||
|
ArgumentCaptor<ContainerFeeDetailEntity> captor = ArgumentCaptor.forClass(ContainerFeeDetailEntity.class);
|
||||||
|
verify(repository).insert(captor.capture());
|
||||||
|
assertThat(captor.getValue().getPartnerId()).isNull();
|
||||||
|
assertThat(captor.getValue().getPartnerName()).isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ownedPlan() {
|
private void ownedPlan() {
|
||||||
@@ -101,6 +133,7 @@ class ContainerFeeDetailServiceTest {
|
|||||||
value.setId(11L);
|
value.setId(11L);
|
||||||
value.setFeeCode("TRUCKING");
|
value.setFeeCode("TRUCKING");
|
||||||
value.setFeeTypeName("拖车费");
|
value.setFeeTypeName("拖车费");
|
||||||
|
value.setWarehouseReceivable(true);
|
||||||
value.setWarehousePayable(true);
|
value.setWarehousePayable(true);
|
||||||
value.setStatus("ACTIVE");
|
value.setStatus("ACTIVE");
|
||||||
return value;
|
return value;
|
||||||
@@ -109,6 +142,6 @@ class ContainerFeeDetailServiceTest {
|
|||||||
private static ContainerFeeDetailDtos.SaveRequest request(boolean settlementIncluded) {
|
private static ContainerFeeDetailDtos.SaveRequest request(boolean settlementIncluded) {
|
||||||
return new ContainerFeeDetailDtos.SaveRequest(11L, "PAYABLE",
|
return new ContainerFeeDetailDtos.SaveRequest(11L, "PAYABLE",
|
||||||
LocalDate.of(2026, 8, 11), new BigDecimal("300.00"), "CNY",
|
LocalDate.of(2026, 8, 11), new BigDecimal("300.00"), "CNY",
|
||||||
settlementIncluded, "整柜拖车费");
|
settlementIncluded, 71L, " 现有往来单位 ", "整柜拖车费");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+31
@@ -3,6 +3,7 @@ package com.baiyue.firstleg.service;
|
|||||||
import com.baiyue.common.core.exception.BizException;
|
import com.baiyue.common.core.exception.BizException;
|
||||||
import com.baiyue.firstleg.dto.FinanceFeeSourceSnapshot;
|
import com.baiyue.firstleg.dto.FinanceFeeSourceSnapshot;
|
||||||
import com.baiyue.firstleg.entity.FeeItemEntity;
|
import com.baiyue.firstleg.entity.FeeItemEntity;
|
||||||
|
import com.baiyue.firstleg.entity.ContainerFeeDetailEntity;
|
||||||
import com.baiyue.firstleg.entity.InboundFeeDetailEntity;
|
import com.baiyue.firstleg.entity.InboundFeeDetailEntity;
|
||||||
import com.baiyue.firstleg.entity.InboundOrderEntity;
|
import com.baiyue.firstleg.entity.InboundOrderEntity;
|
||||||
import com.baiyue.firstleg.repository.ContainerFeeDetailRepository;
|
import com.baiyue.firstleg.repository.ContainerFeeDetailRepository;
|
||||||
@@ -68,4 +69,34 @@ class FinanceFeeSourceSnapshotServiceTest {
|
|||||||
assertThatThrownBy(() -> service.inbound(21L, 31L))
|
assertThatThrownBy(() -> service.inbound(21L, 31L))
|
||||||
.isInstanceOf(BizException.class).hasMessageContaining("不存在");
|
.isInstanceOf(BizException.class).hasMessageContaining("不存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void containerSnapshotPublishesPayableCounterpartyIdentity() {
|
||||||
|
ContainerFeeDetailEntity detail = new ContainerFeeDetailEntity();
|
||||||
|
detail.setId(51L);
|
||||||
|
detail.setTenantId(10001L);
|
||||||
|
detail.setLoadingPlanId(41L);
|
||||||
|
detail.setContainerBookingId(61L);
|
||||||
|
detail.setContainerNo("CONT-41");
|
||||||
|
detail.setFeeItemId(11L);
|
||||||
|
detail.setFeeItemCodeSnapshot("TRUCKING");
|
||||||
|
detail.setFeeItemNameSnapshot("拖车费");
|
||||||
|
detail.setDirection("PAYABLE");
|
||||||
|
detail.setPartnerId(71L);
|
||||||
|
detail.setPartnerName("现有往来单位");
|
||||||
|
detail.setBusinessDate(java.time.LocalDate.of(2026, 8, 12));
|
||||||
|
detail.setAmount(new BigDecimal("300.00"));
|
||||||
|
detail.setCurrency("CNY");
|
||||||
|
detail.setReviewStatus("APPROVED");
|
||||||
|
detail.setSourceStatus("ACTIVE");
|
||||||
|
detail.setSourceVersion(2);
|
||||||
|
detail.setSettlementIncluded(true);
|
||||||
|
when(containerFees.find(41L, 51L)).thenReturn(detail);
|
||||||
|
|
||||||
|
FinanceFeeSourceSnapshot snapshot = service.container(41L, 51L);
|
||||||
|
|
||||||
|
assertThat(snapshot.partnerId()).isEqualTo(71L);
|
||||||
|
assertThat(snapshot.partnerName()).isEqualTo("现有往来单位");
|
||||||
|
assertThat(snapshot.sourceHash()).matches("[0-9a-f]{64}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user