格式修改
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
package com.baiyue.firstleg.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.time.Clock;
|
||||
|
||||
@Configuration
|
||||
public class TimeConfiguration {
|
||||
|
||||
@Bean
|
||||
public Clock systemClock() {
|
||||
return Clock.systemDefaultZone();
|
||||
}
|
||||
}
|
||||
+11
-6
@@ -1,5 +1,8 @@
|
||||
package com.baiyue.firstleg.controller;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
|
||||
import com.baiyue.common.core.api.ApiResponse;
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
@@ -23,13 +26,15 @@ import java.security.MessageDigest;
|
||||
@RestController
|
||||
@RequestMapping("/internal/firstleg/v1/inbounds")
|
||||
public class InternalInboundOrderController {
|
||||
private final InboundOrderCreationService service;
|
||||
private final byte[] expectedToken;
|
||||
@Resource
|
||||
private InboundOrderCreationService service;
|
||||
@Value("${baiyue.security.internal-service-token}")
|
||||
private String internalToken;
|
||||
private byte[] expectedToken;
|
||||
|
||||
public InternalInboundOrderController(InboundOrderCreationService service,
|
||||
@Value("${baiyue.security.internal-service-token}") String token) {
|
||||
this.service = service;
|
||||
this.expectedToken = token.getBytes(StandardCharsets.UTF_8);
|
||||
@PostConstruct
|
||||
void initializeToken() {
|
||||
expectedToken = internalToken.getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
@PostMapping("/order-approved")
|
||||
|
||||
+4
-5
@@ -1,5 +1,7 @@
|
||||
package com.baiyue.firstleg.controller;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import com.baiyue.common.core.api.ApiResponse;
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
@@ -22,11 +24,8 @@ import org.springframework.http.ResponseEntity;
|
||||
@RestController
|
||||
@RequestMapping("/api/merchant/v1/first-leg/orders")
|
||||
public class MerchantInboundBillingController {
|
||||
private final MerchantInboundBillingService service;
|
||||
|
||||
public MerchantInboundBillingController(MerchantInboundBillingService service) {
|
||||
this.service = service;
|
||||
}
|
||||
@Resource
|
||||
private MerchantInboundBillingService service;
|
||||
|
||||
@GetMapping("/{orderId}/inbound-billing")
|
||||
@PreAuthorize("hasAuthority('merchant:order:view')")
|
||||
|
||||
+4
-5
@@ -1,5 +1,7 @@
|
||||
package com.baiyue.firstleg.controller;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import com.baiyue.common.core.api.ApiResponse;
|
||||
import com.baiyue.firstleg.dto.OpenApiMeasurementDtos.EventResponse;
|
||||
import com.baiyue.firstleg.security.MeasurementRequestHeaders;
|
||||
@@ -32,11 +34,8 @@ import org.springframework.web.multipart.MultipartFile;
|
||||
@SecurityRequirement(name = "measurementNonce")
|
||||
@SecurityRequirement(name = "measurementSignature")
|
||||
public class OpenApiInboundMeasurementController {
|
||||
private final InboundMeasurementOpenApiService service;
|
||||
|
||||
public OpenApiInboundMeasurementController(InboundMeasurementOpenApiService service) {
|
||||
this.service = service;
|
||||
}
|
||||
@Resource
|
||||
private InboundMeasurementOpenApiService service;
|
||||
|
||||
@Operation(summary = "提交一条第三方箱规测量结果和图片",
|
||||
description = "multipart 包含 data 原始JSON和 image 图片;签名 body 为 data原文、换行、图片SHA-256,外层仍按 METHOD、PATH、时间戳、Nonce、Idempotency-Key、body SHA-256 六行签名")
|
||||
|
||||
+4
-3
@@ -1,5 +1,7 @@
|
||||
package com.baiyue.firstleg.controller;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import com.baiyue.common.audit.BusinessAudit;
|
||||
import com.baiyue.common.core.api.ApiResponse;
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
@@ -32,9 +34,8 @@ import java.util.List;
|
||||
@RestController
|
||||
@RequestMapping("/api/platform/v1/fba-pricing")
|
||||
public class PlatformFbaMasterDataController {
|
||||
private final FbaMasterDataService service;
|
||||
|
||||
public PlatformFbaMasterDataController(FbaMasterDataService service) { this.service = service; }
|
||||
@Resource
|
||||
private FbaMasterDataService service;
|
||||
|
||||
@GetMapping("/customer-rules")
|
||||
@PreAuthorize("hasAuthority('firstleg:fba-pricing:rule:view')")
|
||||
|
||||
+4
-3
@@ -1,5 +1,7 @@
|
||||
package com.baiyue.firstleg.controller;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import com.baiyue.common.audit.BusinessAudit;
|
||||
import com.baiyue.common.core.api.ApiResponse;
|
||||
import com.baiyue.common.security.AuthenticatedUser;
|
||||
@@ -28,9 +30,8 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
@RestController
|
||||
@RequestMapping("/api/platform/v1/inbounds/{inboundId}/fees")
|
||||
public class PlatformFbaPricingController {
|
||||
private final FbaPricingService service;
|
||||
|
||||
public PlatformFbaPricingController(FbaPricingService service) { this.service = service; }
|
||||
@Resource
|
||||
private FbaPricingService service;
|
||||
|
||||
@PostMapping("/calculate")
|
||||
@PreAuthorize("hasAuthority('firstleg:fba-pricing:calculate')")
|
||||
|
||||
+4
-8
@@ -1,5 +1,7 @@
|
||||
package com.baiyue.firstleg.controller;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import com.baiyue.common.audit.BusinessAudit;
|
||||
import com.baiyue.common.core.api.ApiResponse;
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
@@ -28,14 +30,8 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
@RestController
|
||||
@RequestMapping("/api/platform/v1/first-leg/inbounds/{inboundId}")
|
||||
public class PlatformInboundMeasurementController {
|
||||
private final InboundMeasurementService service;
|
||||
private final MeasurementSubmissionService submission;
|
||||
|
||||
public PlatformInboundMeasurementController(InboundMeasurementService service,
|
||||
MeasurementSubmissionService submission) {
|
||||
this.service = service;
|
||||
this.submission = submission;
|
||||
}
|
||||
@Resource private InboundMeasurementService service;
|
||||
@Resource private MeasurementSubmissionService submission;
|
||||
|
||||
@Operation(summary = "查询入库测量详情", description = "返回原单、实测箱规、审核快照和 OCR 重量结果。")
|
||||
@GetMapping("/measurement")
|
||||
|
||||
+4
-8
@@ -1,5 +1,7 @@
|
||||
package com.baiyue.firstleg.controller;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import com.baiyue.common.audit.BusinessAudit;
|
||||
import com.baiyue.common.core.api.ApiResponse;
|
||||
import com.baiyue.common.core.api.PageResult;
|
||||
@@ -34,14 +36,8 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
@RestController
|
||||
@RequestMapping("/api/platform/v1/inbounds")
|
||||
public class PlatformInboundMeasurementReviewController {
|
||||
private final MeasurementReviewService service;
|
||||
private final AutomaticInboundCompletionService automaticCompletion;
|
||||
|
||||
public PlatformInboundMeasurementReviewController(MeasurementReviewService service,
|
||||
AutomaticInboundCompletionService automaticCompletion) {
|
||||
this.service = service;
|
||||
this.automaticCompletion = automaticCompletion;
|
||||
}
|
||||
@Resource private MeasurementReviewService service;
|
||||
@Resource private AutomaticInboundCompletionService automaticCompletion;
|
||||
|
||||
@BusinessAudit(event = "INBOUND_MEASURE_APPROVE", resourceType = "INBOUND",
|
||||
resourceId = "#inboundId", description = "测量审核通过")
|
||||
|
||||
+3
-5
@@ -1,5 +1,7 @@
|
||||
package com.baiyue.firstleg.controller;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import com.baiyue.common.core.api.ApiResponse;
|
||||
import com.baiyue.common.core.api.PageResult;
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
@@ -29,11 +31,7 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
@RestController
|
||||
@RequestMapping("/api/platform/inbounds")
|
||||
public class PlatformInboundOrderController {
|
||||
private final InboundOrderQueryService service;
|
||||
|
||||
public PlatformInboundOrderController(InboundOrderQueryService service) {
|
||||
this.service = service;
|
||||
}
|
||||
@Resource private InboundOrderQueryService service;
|
||||
|
||||
@Operation(summary = "分页查询入库单",
|
||||
description = "默认分页查询全部企业入库单;可按目标租户和二期状态页签筛选入库单快照。")
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.baiyue.firstleg.repository;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baiyue.firstleg.entity.CarrierFbaRateEntity;
|
||||
@@ -24,27 +26,20 @@ import java.util.List;
|
||||
/** FBA 计费仓储;平台基础资料列表可跨企业读取,其他业务操作强制带租户条件。 */
|
||||
@Repository
|
||||
public class FbaPricingRepository {
|
||||
private final CustomerChargeRuleMapper rules;
|
||||
private final CarrierFbaRateMapper rates;
|
||||
private final InboundPricingMapper inbounds;
|
||||
private final InboundFeeMapper fees;
|
||||
private final InboundFeeItemMapper items;
|
||||
private final InboundPricingIdempotencyMapper idempotencies;
|
||||
private final InboundOutboxMapper outbox;
|
||||
|
||||
public FbaPricingRepository(CustomerChargeRuleMapper rules, CarrierFbaRateMapper rates,
|
||||
InboundPricingMapper inbounds, InboundFeeMapper fees,
|
||||
InboundFeeItemMapper items,
|
||||
InboundPricingIdempotencyMapper idempotencies,
|
||||
InboundOutboxMapper outbox) {
|
||||
this.rules = rules;
|
||||
this.rates = rates;
|
||||
this.inbounds = inbounds;
|
||||
this.fees = fees;
|
||||
this.items = items;
|
||||
this.idempotencies = idempotencies;
|
||||
this.outbox = outbox;
|
||||
}
|
||||
@Resource
|
||||
private CustomerChargeRuleMapper rules;
|
||||
@Resource
|
||||
private CarrierFbaRateMapper rates;
|
||||
@Resource
|
||||
private InboundPricingMapper inbounds;
|
||||
@Resource
|
||||
private InboundFeeMapper fees;
|
||||
@Resource
|
||||
private InboundFeeItemMapper items;
|
||||
@Resource
|
||||
private InboundPricingIdempotencyMapper idempotencies;
|
||||
@Resource
|
||||
private InboundOutboxMapper outbox;
|
||||
|
||||
public void insertRule(CustomerChargeRuleEntity rule) { rules.insert(rule); }
|
||||
public void insertRate(CarrierFbaRateEntity rate) { rates.insert(rate); }
|
||||
|
||||
+4
-7
@@ -1,5 +1,7 @@
|
||||
package com.baiyue.firstleg.repository;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import com.baiyue.common.core.api.PageResult;
|
||||
import com.baiyue.firstleg.dto.FulfillmentTaskQuery;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
@@ -16,13 +18,8 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
@Repository
|
||||
public class FulfillmentTaskRepository {
|
||||
private final FulfillmentTaskMapper tasks;
|
||||
private final AssignmentHistoryMapper histories;
|
||||
|
||||
public FulfillmentTaskRepository(FulfillmentTaskMapper tasks, AssignmentHistoryMapper histories) {
|
||||
this.tasks = tasks;
|
||||
this.histories = histories;
|
||||
}
|
||||
@Resource private FulfillmentTaskMapper tasks;
|
||||
@Resource private AssignmentHistoryMapper histories;
|
||||
|
||||
public FulfillmentTaskEntity findOwned(long tenantId, long taskId) {
|
||||
return tasks.selectOne(new LambdaQueryWrapper<FulfillmentTaskEntity>()
|
||||
|
||||
+8
-11
@@ -1,5 +1,7 @@
|
||||
package com.baiyue.firstleg.repository;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baiyue.firstleg.entity.InboundMeasurementEntity;
|
||||
import com.baiyue.firstleg.entity.InboundMeasurementItemEntity;
|
||||
@@ -12,17 +14,12 @@ import org.springframework.stereotype.Repository;
|
||||
/** 第三方测量开放接口仓储,仅允许读取主单并写测量、明细和幂等记录。 */
|
||||
@Repository
|
||||
public class InboundMeasurementOpenApiRepository {
|
||||
private final InboundOrderLookupMapper inboundOrders;
|
||||
private final InboundMeasurementMapper measurements;
|
||||
private final InboundMeasurementItemMapper items;
|
||||
|
||||
public InboundMeasurementOpenApiRepository(InboundOrderLookupMapper inboundOrders,
|
||||
InboundMeasurementMapper measurements,
|
||||
InboundMeasurementItemMapper items) {
|
||||
this.inboundOrders = inboundOrders;
|
||||
this.measurements = measurements;
|
||||
this.items = items;
|
||||
}
|
||||
@Resource
|
||||
private InboundOrderLookupMapper inboundOrders;
|
||||
@Resource
|
||||
private InboundMeasurementMapper measurements;
|
||||
@Resource
|
||||
private InboundMeasurementItemMapper items;
|
||||
|
||||
public InboundOrderLookupEntity findInbound(long tenantId, String inboundCode) {
|
||||
return inboundOrders.selectOne(new LambdaQueryWrapper<InboundOrderLookupEntity>()
|
||||
|
||||
+14
-20
@@ -1,5 +1,7 @@
|
||||
package com.baiyue.firstleg.repository;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baiyue.firstleg.entity.InboundMeasurementEntity;
|
||||
import com.baiyue.firstleg.entity.InboundMeasurementItemEntity;
|
||||
@@ -20,26 +22,18 @@ import java.util.List;
|
||||
/** 入库测量仓储,所有读取都同时带租户和入库单边界。 */
|
||||
@Repository
|
||||
public class InboundMeasurementRepository {
|
||||
private final InboundOrderMapper inboundOrders;
|
||||
private final InboundMeasurementMapper measurements;
|
||||
private final InboundMeasurementItemMapper items;
|
||||
private final InboundWeightResultMapper weights;
|
||||
private final InboundWeightRevisionMapper revisions;
|
||||
private final MeasureReviewRecordMapper reviews;
|
||||
|
||||
public InboundMeasurementRepository(InboundOrderMapper inboundOrders,
|
||||
InboundMeasurementMapper measurements,
|
||||
InboundMeasurementItemMapper items,
|
||||
InboundWeightResultMapper weights,
|
||||
InboundWeightRevisionMapper revisions,
|
||||
MeasureReviewRecordMapper reviews) {
|
||||
this.inboundOrders = inboundOrders;
|
||||
this.measurements = measurements;
|
||||
this.items = items;
|
||||
this.weights = weights;
|
||||
this.revisions = revisions;
|
||||
this.reviews = reviews;
|
||||
}
|
||||
@Resource
|
||||
private InboundOrderMapper inboundOrders;
|
||||
@Resource
|
||||
private InboundMeasurementMapper measurements;
|
||||
@Resource
|
||||
private InboundMeasurementItemMapper items;
|
||||
@Resource
|
||||
private InboundWeightResultMapper weights;
|
||||
@Resource
|
||||
private InboundWeightRevisionMapper revisions;
|
||||
@Resource
|
||||
private MeasureReviewRecordMapper reviews;
|
||||
|
||||
public InboundOrderEntity findInbound(long tenantId, long inboundId) {
|
||||
return inboundOrders.selectOne(new LambdaQueryWrapper<InboundOrderEntity>()
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.baiyue.firstleg.repository;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baiyue.common.core.api.PageResult;
|
||||
@@ -29,27 +31,18 @@ import java.util.stream.Collectors;
|
||||
/** 入库单只读仓储;平台分页可跨企业查询,详情继续强制租户隔离。 */
|
||||
@Repository
|
||||
public class InboundOrderRepository {
|
||||
private final InboundOrderMapper orders;
|
||||
private final InboundTransportSnapshotMapper transports;
|
||||
private final InboundPalletAllocationMapper pallets;
|
||||
private final InboundFileMapper files;
|
||||
private final InboundInternalNoteMapper notes;
|
||||
private final InboundStatusHistoryMapper histories;
|
||||
|
||||
public InboundOrderRepository(
|
||||
InboundOrderMapper orders,
|
||||
InboundTransportSnapshotMapper transports,
|
||||
InboundPalletAllocationMapper pallets,
|
||||
InboundFileMapper files,
|
||||
InboundInternalNoteMapper notes,
|
||||
InboundStatusHistoryMapper histories) {
|
||||
this.orders = orders;
|
||||
this.transports = transports;
|
||||
this.pallets = pallets;
|
||||
this.files = files;
|
||||
this.notes = notes;
|
||||
this.histories = histories;
|
||||
}
|
||||
@Resource
|
||||
private InboundOrderMapper orders;
|
||||
@Resource
|
||||
private InboundTransportSnapshotMapper transports;
|
||||
@Resource
|
||||
private InboundPalletAllocationMapper pallets;
|
||||
@Resource
|
||||
private InboundFileMapper files;
|
||||
@Resource
|
||||
private InboundInternalNoteMapper notes;
|
||||
@Resource
|
||||
private InboundStatusHistoryMapper histories;
|
||||
|
||||
public PageResult<InboundOrderSummary> page(Long tenantId, InboundOrderQuery query) {
|
||||
LambdaQueryWrapper<InboundOrderEntity> wrapper = new LambdaQueryWrapper<InboundOrderEntity>()
|
||||
|
||||
+3
-5
@@ -1,5 +1,7 @@
|
||||
package com.baiyue.firstleg.repository;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baiyue.firstleg.client.WarehousePalletLocationClient.BatchResult;
|
||||
import com.baiyue.firstleg.entity.InboundPalletAllocationEntity;
|
||||
@@ -15,11 +17,7 @@ import java.util.stream.Collectors;
|
||||
|
||||
@Repository
|
||||
public class InboundPalletAllocationRepository {
|
||||
private final InboundPalletAllocationMapper mapper;
|
||||
|
||||
public InboundPalletAllocationRepository(InboundPalletAllocationMapper mapper) {
|
||||
this.mapper = mapper;
|
||||
}
|
||||
@Resource private InboundPalletAllocationMapper mapper;
|
||||
|
||||
@Transactional
|
||||
public void replace(long tenantId, long inboundId, long warehouseId, long warehouseResourceTenantId,
|
||||
|
||||
+18
-26
@@ -1,5 +1,7 @@
|
||||
package com.baiyue.firstleg.repository;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baiyue.common.core.api.PageResult;
|
||||
@@ -23,32 +25,22 @@ import org.springframework.stereotype.Repository;
|
||||
/** 入库审核和完成编排仓储;所有业务查询强制携带租户。 */
|
||||
@Repository
|
||||
public class InboundWorkflowRepository {
|
||||
private final InboundOrderMapper orders;
|
||||
private final InboundMeasurementMapper measurements;
|
||||
private final MeasureReviewRecordMapper reviews;
|
||||
private final InboundStatusHistoryMapper histories;
|
||||
private final InboundIdempotencyMapper idempotencies;
|
||||
private final InboundFeeMapper fees;
|
||||
private final InboundOutboxMapper outboxes;
|
||||
private final InboundAllocationQueryMapper allocations;
|
||||
|
||||
public InboundWorkflowRepository(InboundOrderMapper orders,
|
||||
InboundMeasurementMapper measurements,
|
||||
MeasureReviewRecordMapper reviews,
|
||||
InboundStatusHistoryMapper histories,
|
||||
InboundIdempotencyMapper idempotencies,
|
||||
InboundFeeMapper fees,
|
||||
InboundOutboxMapper outboxes,
|
||||
InboundAllocationQueryMapper allocations) {
|
||||
this.orders = orders;
|
||||
this.measurements = measurements;
|
||||
this.reviews = reviews;
|
||||
this.histories = histories;
|
||||
this.idempotencies = idempotencies;
|
||||
this.fees = fees;
|
||||
this.outboxes = outboxes;
|
||||
this.allocations = allocations;
|
||||
}
|
||||
@Resource
|
||||
private InboundOrderMapper orders;
|
||||
@Resource
|
||||
private InboundMeasurementMapper measurements;
|
||||
@Resource
|
||||
private MeasureReviewRecordMapper reviews;
|
||||
@Resource
|
||||
private InboundStatusHistoryMapper histories;
|
||||
@Resource
|
||||
private InboundIdempotencyMapper idempotencies;
|
||||
@Resource
|
||||
private InboundFeeMapper fees;
|
||||
@Resource
|
||||
private InboundOutboxMapper outboxes;
|
||||
@Resource
|
||||
private InboundAllocationQueryMapper allocations;
|
||||
|
||||
public InboundOrderEntity findOwned(long tenantId, long inboundId) {
|
||||
return orders.selectOne(new LambdaQueryWrapper<InboundOrderEntity>()
|
||||
|
||||
+8
-18
@@ -1,9 +1,10 @@
|
||||
package com.baiyue.firstleg.security;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.firstleg.config.MeasurementOpenApiProperties;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@@ -21,23 +22,12 @@ import java.util.Map;
|
||||
@Component
|
||||
public class MeasurementRequestAuthenticator {
|
||||
private static final String HMAC_ALGORITHM = "HmacSHA256";
|
||||
private final MeasurementOpenApiProperties properties;
|
||||
private final NonceReplayGuard nonceReplayGuard;
|
||||
private final Clock clock;
|
||||
|
||||
@Autowired
|
||||
public MeasurementRequestAuthenticator(MeasurementOpenApiProperties properties,
|
||||
NonceReplayGuard nonceReplayGuard) {
|
||||
this(properties, nonceReplayGuard, Clock.systemUTC());
|
||||
}
|
||||
|
||||
MeasurementRequestAuthenticator(MeasurementOpenApiProperties properties,
|
||||
NonceReplayGuard nonceReplayGuard,
|
||||
Clock clock) {
|
||||
this.properties = properties;
|
||||
this.nonceReplayGuard = nonceReplayGuard;
|
||||
this.clock = clock;
|
||||
}
|
||||
@Resource
|
||||
private MeasurementOpenApiProperties properties;
|
||||
@Resource
|
||||
private NonceReplayGuard nonceReplayGuard;
|
||||
@Resource
|
||||
private Clock clock;
|
||||
|
||||
public AuthenticatedMeasurementApp authenticate(String method, String path,
|
||||
MeasurementRequestHeaders headers,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.baiyue.firstleg.security;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@@ -9,11 +11,8 @@ import java.time.Duration;
|
||||
@Component
|
||||
public class RedisNonceReplayGuard implements NonceReplayGuard {
|
||||
private static final String KEY_PREFIX = "baiyue:firstleg:measurement-openapi:nonce:";
|
||||
private final StringRedisTemplate redisTemplate;
|
||||
|
||||
public RedisNonceReplayGuard(StringRedisTemplate redisTemplate) {
|
||||
this.redisTemplate = redisTemplate;
|
||||
}
|
||||
@Resource
|
||||
private StringRedisTemplate redisTemplate;
|
||||
|
||||
@Override
|
||||
public boolean claim(String appKey, String nonce, Duration ttl) {
|
||||
|
||||
+8
-11
@@ -1,5 +1,7 @@
|
||||
package com.baiyue.firstleg.service;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.firstleg.dto.FbaPricingDtos.FeeSnapshotResponse;
|
||||
@@ -9,17 +11,12 @@ import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class AutomaticInboundCompletionService {
|
||||
private final FbaPricingService pricing;
|
||||
private final InboundWorkflowRepository workflow;
|
||||
private final InboundCompletionOrchestrator completion;
|
||||
|
||||
public AutomaticInboundCompletionService(FbaPricingService pricing,
|
||||
InboundWorkflowRepository workflow,
|
||||
InboundCompletionOrchestrator completion) {
|
||||
this.pricing = pricing;
|
||||
this.workflow = workflow;
|
||||
this.completion = completion;
|
||||
}
|
||||
@Resource
|
||||
private FbaPricingService pricing;
|
||||
@Resource
|
||||
private InboundWorkflowRepository workflow;
|
||||
@Resource
|
||||
private InboundCompletionOrchestrator completion;
|
||||
|
||||
public FeeSnapshotResponse completeApproved(long tenantId, long inboundId, long operatorId,
|
||||
String operatorName, long measurementId,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.baiyue.firstleg.service;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.firstleg.dto.FbaPricingDtos.CarrierRateVersionRequest;
|
||||
@@ -17,9 +19,8 @@ import java.util.Objects;
|
||||
/** 客户规则和船司价格版本管理。版本内容创建后不可原地修改。 */
|
||||
@Service
|
||||
public class FbaMasterDataService {
|
||||
private final FbaPricingRepository repository;
|
||||
|
||||
public FbaMasterDataService(FbaPricingRepository repository) { this.repository = repository; }
|
||||
@Resource
|
||||
private FbaPricingRepository repository;
|
||||
|
||||
@Transactional
|
||||
public CustomerChargeRuleEntity createRuleVersion(long tenantId, long operatorId,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.baiyue.firstleg.service;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.firstleg.dto.FbaPricingDtos.FeeSnapshotResponse;
|
||||
@@ -12,7 +14,6 @@ import com.baiyue.firstleg.entity.InboundOutboxEntity;
|
||||
import com.baiyue.firstleg.entity.InboundPricingEntity;
|
||||
import com.baiyue.firstleg.entity.InboundPricingIdempotencyEntity;
|
||||
import com.baiyue.firstleg.repository.FbaPricingRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -32,18 +33,10 @@ import java.util.List;
|
||||
@Service
|
||||
public class FbaPricingService {
|
||||
private static final int MONEY_SCALE = 2;
|
||||
private final FbaPricingRepository repository;
|
||||
private final Clock clock;
|
||||
|
||||
@Autowired
|
||||
public FbaPricingService(FbaPricingRepository repository) {
|
||||
this(repository, Clock.systemDefaultZone());
|
||||
}
|
||||
|
||||
FbaPricingService(FbaPricingRepository repository, Clock clock) {
|
||||
this.repository = repository;
|
||||
this.clock = clock;
|
||||
}
|
||||
@Resource
|
||||
private FbaPricingRepository repository;
|
||||
@Resource
|
||||
private Clock clock;
|
||||
|
||||
@Transactional
|
||||
public FeeSnapshotResponse calculate(long tenantId, long operatorId, long inboundId,
|
||||
|
||||
+6
-8
@@ -1,5 +1,7 @@
|
||||
package com.baiyue.firstleg.service;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.firstleg.entity.InboundFeeEntity;
|
||||
@@ -21,14 +23,10 @@ import java.time.LocalDateTime;
|
||||
*/
|
||||
@Service
|
||||
public class InboundCompletionOrchestrator {
|
||||
private final InboundWorkflowRepository repository;
|
||||
private final InboundIdempotencyService idempotency;
|
||||
|
||||
public InboundCompletionOrchestrator(InboundWorkflowRepository repository,
|
||||
InboundIdempotencyService idempotency) {
|
||||
this.repository = repository;
|
||||
this.idempotency = idempotency;
|
||||
}
|
||||
@Resource
|
||||
private InboundWorkflowRepository repository;
|
||||
@Resource
|
||||
private InboundIdempotencyService idempotency;
|
||||
|
||||
/**
|
||||
* 任务5对接方法一:锁定发送动作。允许前置状态仅为 FEE_CONFIRMING + CONFIRMED。
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.baiyue.firstleg.service;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.firstleg.entity.InboundIdempotencyEntity;
|
||||
@@ -17,11 +19,8 @@ import java.util.HexFormat;
|
||||
/** 入库写操作的统一持久化幂等边界。 */
|
||||
@Service
|
||||
public class InboundIdempotencyService {
|
||||
private final InboundWorkflowRepository repository;
|
||||
|
||||
public InboundIdempotencyService(InboundWorkflowRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
@Resource
|
||||
private InboundWorkflowRepository repository;
|
||||
|
||||
public Context begin(long tenantId, long operatorId, long inboundId,
|
||||
String operation, String idempotencyKey, String canonicalRequest) {
|
||||
|
||||
+20
-30
@@ -1,5 +1,7 @@
|
||||
package com.baiyue.firstleg.service;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
|
||||
import com.baiyue.common.core.api.ApiResponse;
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
@@ -50,36 +52,24 @@ public class InboundMeasurementOpenApiService {
|
||||
private static final BigDecimal VOLUME_WEIGHT_FACTOR = new BigDecimal("167");
|
||||
private static final String CALCULATION_RULE_VERSION = "V1_CBM_CEILING_4_GIRTH_L2WH";
|
||||
|
||||
private final InboundMeasurementOpenApiRepository repository;
|
||||
private final MeasurementRequestAuthenticator authenticator;
|
||||
private final ThirdPartyCallLogService callLogService;
|
||||
private final OrderCargoClient orderCargoClient;
|
||||
private final MeasurementImageClient imageClient;
|
||||
private final String internalToken;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final Validator validator;
|
||||
private final TransactionTemplate transactionTemplate;
|
||||
|
||||
public InboundMeasurementOpenApiService(
|
||||
InboundMeasurementOpenApiRepository repository,
|
||||
MeasurementRequestAuthenticator authenticator,
|
||||
ThirdPartyCallLogService callLogService,
|
||||
OrderCargoClient orderCargoClient,
|
||||
MeasurementImageClient imageClient,
|
||||
@Value("${baiyue.security.internal-service-token}") String internalToken,
|
||||
ObjectMapper objectMapper,
|
||||
Validator validator,
|
||||
TransactionTemplate transactionTemplate) {
|
||||
this.repository = repository;
|
||||
this.authenticator = authenticator;
|
||||
this.callLogService = callLogService;
|
||||
this.orderCargoClient = orderCargoClient;
|
||||
this.imageClient = imageClient;
|
||||
this.internalToken = internalToken;
|
||||
this.objectMapper = objectMapper;
|
||||
this.validator = validator;
|
||||
this.transactionTemplate = transactionTemplate;
|
||||
}
|
||||
@Resource
|
||||
private InboundMeasurementOpenApiRepository repository;
|
||||
@Resource
|
||||
private MeasurementRequestAuthenticator authenticator;
|
||||
@Resource
|
||||
private ThirdPartyCallLogService callLogService;
|
||||
@Resource
|
||||
private OrderCargoClient orderCargoClient;
|
||||
@Resource
|
||||
private MeasurementImageClient imageClient;
|
||||
@Value("${baiyue.security.internal-service-token}")
|
||||
private String internalToken;
|
||||
@Resource
|
||||
private ObjectMapper objectMapper;
|
||||
@Resource
|
||||
private Validator validator;
|
||||
@Resource
|
||||
private TransactionTemplate transactionTemplate;
|
||||
|
||||
public EventResponse receive(MeasurementRequestHeaders headers, String rawData, MultipartFile image) {
|
||||
requireImage(image);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.baiyue.firstleg.service;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.firstleg.dto.InboundMeasurementRequests;
|
||||
@@ -36,14 +38,10 @@ public class InboundMeasurementService {
|
||||
Set.of("WAIT_MEASURE", "MEASURING", "MEASURE_REJECTED");
|
||||
private static final BigDecimal LB_TO_KG = new BigDecimal("0.45359237");
|
||||
|
||||
private final InboundMeasurementRepository repository;
|
||||
private final MeasurementBatchService batchService;
|
||||
|
||||
public InboundMeasurementService(InboundMeasurementRepository repository,
|
||||
MeasurementBatchService batchService) {
|
||||
this.repository = repository;
|
||||
this.batchService = batchService;
|
||||
}
|
||||
@Resource
|
||||
private InboundMeasurementRepository repository;
|
||||
@Resource
|
||||
private MeasurementBatchService batchService;
|
||||
|
||||
public InboundMeasurementResponses.Detail detail(long tenantId, long inboundId) {
|
||||
InboundOrderEntity inbound = requireInbound(tenantId, inboundId);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.baiyue.firstleg.service;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
|
||||
import com.baiyue.firstleg.dto.OrderApprovedInboundCommand;
|
||||
import com.baiyue.firstleg.dto.InboundOrderReference;
|
||||
@@ -16,13 +18,10 @@ import java.util.List;
|
||||
/** 将已审核的订单快照幂等转换为待测量入库单。 */
|
||||
@Service
|
||||
public class InboundOrderCreationService {
|
||||
private final InboundOrderMapper orders;
|
||||
private final InboundStatusHistoryMapper histories;
|
||||
|
||||
public InboundOrderCreationService(InboundOrderMapper orders, InboundStatusHistoryMapper histories) {
|
||||
this.orders = orders;
|
||||
this.histories = histories;
|
||||
}
|
||||
@Resource
|
||||
private InboundOrderMapper orders;
|
||||
@Resource
|
||||
private InboundStatusHistoryMapper histories;
|
||||
|
||||
@Transactional
|
||||
public long createFromApprovedOrder(OrderApprovedInboundCommand command) {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.baiyue.firstleg.service;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import com.baiyue.common.core.api.PageResult;
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
@@ -15,11 +17,8 @@ import org.springframework.util.StringUtils;
|
||||
/** 入库单列表和详情只读应用服务,不执行任何状态推进。 */
|
||||
@Service
|
||||
public class InboundOrderQueryService {
|
||||
private final InboundOrderRepository repository;
|
||||
|
||||
public InboundOrderQueryService(InboundOrderRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
@Resource
|
||||
private InboundOrderRepository repository;
|
||||
|
||||
public PageResult<InboundOrderSummary> page(Long tenantId, InboundOrderQuery query) {
|
||||
validateQuery(query);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.baiyue.firstleg.service;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.firstleg.dto.MeasurementBatchCommand;
|
||||
@@ -31,11 +33,8 @@ public class MeasurementBatchService {
|
||||
static final BigDecimal VOLUME_WEIGHT_FACTOR = new BigDecimal("167");
|
||||
static final String CALCULATION_RULE_VERSION = "V1_CBM_CEILING_4_GIRTH_L2WH";
|
||||
|
||||
private final InboundMeasurementRepository repository;
|
||||
|
||||
public MeasurementBatchService(InboundMeasurementRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
@Resource
|
||||
private InboundMeasurementRepository repository;
|
||||
|
||||
@Transactional
|
||||
public InboundMeasurementEntity saveBatch(MeasurementBatchCommand command) {
|
||||
|
||||
+12
-16
@@ -1,5 +1,7 @@
|
||||
package com.baiyue.firstleg.service;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import com.baiyue.common.core.api.ApiResponse;
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
@@ -18,22 +20,16 @@ import java.time.LocalDateTime;
|
||||
@Service
|
||||
public class MeasurementLocationOccupationService {
|
||||
private static final String SOURCE_SERVICE = "firstleg-service";
|
||||
private final WarehousePalletLocationClient warehouse;
|
||||
private final InboundPalletAllocationRepository allocations;
|
||||
private final InboundWorkflowRepository workflow;
|
||||
private final InboundProperties properties;
|
||||
private final String internalToken;
|
||||
|
||||
public MeasurementLocationOccupationService(WarehousePalletLocationClient warehouse,
|
||||
InboundPalletAllocationRepository allocations, InboundWorkflowRepository workflow,
|
||||
InboundProperties properties,
|
||||
@Value("${baiyue.security.internal-service-token}") String internalToken) {
|
||||
this.warehouse = warehouse;
|
||||
this.allocations = allocations;
|
||||
this.workflow = workflow;
|
||||
this.properties = properties;
|
||||
this.internalToken = internalToken;
|
||||
}
|
||||
@Resource
|
||||
private WarehousePalletLocationClient warehouse;
|
||||
@Resource
|
||||
private InboundPalletAllocationRepository allocations;
|
||||
@Resource
|
||||
private InboundWorkflowRepository workflow;
|
||||
@Resource
|
||||
private InboundProperties properties;
|
||||
@Value("${baiyue.security.internal-service-token}")
|
||||
private String internalToken;
|
||||
|
||||
public void occupy(long tenantId, long inboundId, long operatorId, long measurementId,
|
||||
int measurementVersion) {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.baiyue.firstleg.service;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import com.baiyue.common.core.api.PageResult;
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
@@ -22,17 +24,12 @@ import java.time.LocalDateTime;
|
||||
*/
|
||||
@Service
|
||||
public class MeasurementReviewService {
|
||||
private final InboundWorkflowRepository repository;
|
||||
private final InboundIdempotencyService idempotency;
|
||||
private final InboundProperties properties;
|
||||
|
||||
public MeasurementReviewService(InboundWorkflowRepository repository,
|
||||
InboundIdempotencyService idempotency,
|
||||
InboundProperties properties) {
|
||||
this.repository = repository;
|
||||
this.idempotency = idempotency;
|
||||
this.properties = properties;
|
||||
}
|
||||
@Resource
|
||||
private InboundWorkflowRepository repository;
|
||||
@Resource
|
||||
private InboundIdempotencyService idempotency;
|
||||
@Resource
|
||||
private InboundProperties properties;
|
||||
|
||||
@Transactional
|
||||
public void approve(long tenantId, long operatorId, String operatorName, long inboundId,
|
||||
|
||||
+6
-8
@@ -1,5 +1,7 @@
|
||||
package com.baiyue.firstleg.service;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import com.baiyue.firstleg.dto.InboundMeasurementRequests;
|
||||
import com.baiyue.firstleg.dto.InboundMeasurementResponses;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -7,14 +9,10 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
public class MeasurementSubmissionService {
|
||||
private final InboundMeasurementService measurements;
|
||||
private final MeasurementLocationOccupationService locations;
|
||||
|
||||
public MeasurementSubmissionService(InboundMeasurementService measurements,
|
||||
MeasurementLocationOccupationService locations) {
|
||||
this.measurements = measurements;
|
||||
this.locations = locations;
|
||||
}
|
||||
@Resource
|
||||
private InboundMeasurementService measurements;
|
||||
@Resource
|
||||
private MeasurementLocationOccupationService locations;
|
||||
|
||||
@Transactional
|
||||
public InboundMeasurementResponses.MeasurementSubmitted submit(
|
||||
|
||||
+13
-17
@@ -1,5 +1,8 @@
|
||||
package com.baiyue.firstleg.service;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.firstleg.dto.MerchantInboundBillingResponse;
|
||||
@@ -15,23 +18,16 @@ import java.util.List;
|
||||
|
||||
@Service
|
||||
public class MerchantInboundBillingService {
|
||||
private final InboundOrderRepository orders;
|
||||
private final FbaPricingService pricing;
|
||||
private final InboundMeasurementService measurements;
|
||||
private final MeasurementImageClient imageClient;
|
||||
private final String internalToken;
|
||||
|
||||
public MerchantInboundBillingService(InboundOrderRepository orders, FbaPricingService pricing,
|
||||
InboundMeasurementService measurements,
|
||||
MeasurementImageClient imageClient,
|
||||
@org.springframework.beans.factory.annotation.Value(
|
||||
"${baiyue.security.internal-service-token}") String internalToken) {
|
||||
this.orders = orders;
|
||||
this.pricing = pricing;
|
||||
this.measurements = measurements;
|
||||
this.imageClient = imageClient;
|
||||
this.internalToken = internalToken;
|
||||
}
|
||||
@Resource
|
||||
private InboundOrderRepository orders;
|
||||
@Resource
|
||||
private FbaPricingService pricing;
|
||||
@Resource
|
||||
private InboundMeasurementService measurements;
|
||||
@Resource
|
||||
private MeasurementImageClient imageClient;
|
||||
@Value("${baiyue.security.internal-service-token}")
|
||||
private String internalToken;
|
||||
|
||||
public MerchantInboundBillingResponse find(long tenantId, long orderId) {
|
||||
InboundOrderEntity inbound = orders.findByOrderId(tenantId, orderId);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.baiyue.firstleg.service;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baiyue.firstleg.config.MeasurementOpenApiProperties;
|
||||
@@ -17,14 +19,10 @@ import java.time.LocalDateTime;
|
||||
/** 将每次第三方 API 调用独立留档,业务事务回滚也不会丢失失败记录。 */
|
||||
@Service
|
||||
public class ThirdPartyCallLogService {
|
||||
private final ThirdPartyCallLogMapper mapper;
|
||||
private final MeasurementOpenApiProperties properties;
|
||||
|
||||
public ThirdPartyCallLogService(ThirdPartyCallLogMapper mapper,
|
||||
MeasurementOpenApiProperties properties) {
|
||||
this.mapper = mapper;
|
||||
this.properties = properties;
|
||||
}
|
||||
@Resource
|
||||
private ThirdPartyCallLogMapper mapper;
|
||||
@Resource
|
||||
private MeasurementOpenApiProperties properties;
|
||||
|
||||
@Transactional(propagation = Propagation.REQUIRES_NEW)
|
||||
public long start(String method, String path, MeasurementRequestHeaders headers, String rawBody) {
|
||||
|
||||
@@ -48,6 +48,6 @@ logging:
|
||||
rollingpolicy:
|
||||
file-name-pattern: "${LOGGING_ROLLING_FILE_NAME_PATTERN:${LOG_PATH:./logs}/${spring.application.name}.%d{yyyy-MM-dd}.%i.log.gz}"
|
||||
max-file-size: "${LOGGING_ROLLING_MAX_FILE_SIZE:100MB}"
|
||||
max-history: "${LOGGING_ROLLING_MAX_HISTORY:30}"
|
||||
max-history: "${LOGGING_ROLLING_MAX_HISTORY:7}"
|
||||
total-size-cap: "${LOGGING_ROLLING_TOTAL_SIZE_CAP:5GB}"
|
||||
clean-history-on-start: "${LOGGING_ROLLING_CLEAN_HISTORY_ON_START:false}"
|
||||
|
||||
+12
-5
@@ -3,7 +3,11 @@ package com.baiyue.firstleg.controller;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.firstleg.dto.InboundOrderReference;
|
||||
import com.baiyue.firstleg.service.InboundOrderCreationService;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -13,13 +17,16 @@ import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@SpringJUnitConfig(InternalInboundOrderController.class)
|
||||
@TestPropertySource(properties = "baiyue.security.internal-service-token=internal-token")
|
||||
class InternalInboundOrderReferenceControllerTest {
|
||||
private final InboundOrderCreationService service = mock(InboundOrderCreationService.class);
|
||||
private final InternalInboundOrderController controller =
|
||||
new InternalInboundOrderController(service, "internal-token");
|
||||
@MockBean
|
||||
private InboundOrderCreationService service;
|
||||
@Resource
|
||||
private InternalInboundOrderController controller;
|
||||
|
||||
@Test
|
||||
void 使用内部令牌按租户批量查询订单入仓编码() {
|
||||
void shouldHandleCase01() {
|
||||
var request = new InboundOrderReference.BatchRequest(10001L, List.of(77L, 77L, 78L));
|
||||
when(service.findReferences(10001L, List.of(77L, 78L))).thenReturn(List.of(
|
||||
new InboundOrderReference.Item(77L, 88L, "RK-77", "WAIT_MEASURE")));
|
||||
@@ -32,7 +39,7 @@ class InternalInboundOrderReferenceControllerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void 错误内部令牌不能查询任意租户的入仓编码() {
|
||||
void shouldHandleCase02() {
|
||||
var request = new InboundOrderReference.BatchRequest(10001L, List.of(77L));
|
||||
assertThatThrownBy(() -> controller.references("bad-token", request))
|
||||
.isInstanceOf(BizException.class);
|
||||
|
||||
+11
-5
@@ -3,8 +3,11 @@ package com.baiyue.firstleg.controller;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.common.security.AuthenticatedUser;
|
||||
import com.baiyue.firstleg.service.MerchantInboundBillingService;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
@@ -12,12 +15,15 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
@SpringJUnitConfig(MerchantInboundBillingController.class)
|
||||
class MerchantInboundBillingControllerContractTest {
|
||||
private final MerchantInboundBillingController controller =
|
||||
new MerchantInboundBillingController(mock(MerchantInboundBillingService.class));
|
||||
@MockBean
|
||||
private MerchantInboundBillingService service;
|
||||
@Resource
|
||||
private MerchantInboundBillingController controller;
|
||||
|
||||
@Test
|
||||
void 客户费用查询复用订单查看权限且不提供确认接口() throws Exception {
|
||||
void shouldHandleCase01() throws Exception {
|
||||
var method = MerchantInboundBillingController.class.getDeclaredMethod(
|
||||
"find", AuthenticatedUser.class, long.class);
|
||||
assertThat(method.getAnnotation(PreAuthorize.class).value())
|
||||
@@ -27,13 +33,13 @@ class MerchantInboundBillingControllerContractTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void 平台身份不能越权调用客户费用接口() {
|
||||
void shouldHandleCase02() {
|
||||
var platform = new AuthenticatedUser(1L, 0L, 0L, 1L,
|
||||
"admin", "PLATFORM", "WEB", Set.of());
|
||||
assertThatThrownBy(() -> controller.find(platform, 77L)).isInstanceOf(BizException.class);
|
||||
}
|
||||
@Test
|
||||
void 商户测量图片复用订单查看权限() throws Exception {
|
||||
void shouldHandleCase03() throws Exception {
|
||||
var method = MerchantInboundBillingController.class.getDeclaredMethod(
|
||||
"image", AuthenticatedUser.class, long.class, long.class);
|
||||
assertThat(method.getAnnotation(PreAuthorize.class).value())
|
||||
|
||||
+14
-18
@@ -16,6 +16,9 @@ import org.apache.ibatis.session.Configuration;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
@@ -25,7 +28,16 @@ import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@SpringJUnitConfig(FbaPricingRepository.class)
|
||||
class FbaPricingRepositoryRuleListTest {
|
||||
@MockBean private CustomerChargeRuleMapper rules;
|
||||
@MockBean private CarrierFbaRateMapper rates;
|
||||
@MockBean private InboundPricingMapper pricing;
|
||||
@MockBean private InboundFeeMapper fees;
|
||||
@MockBean private InboundFeeItemMapper feeItems;
|
||||
@MockBean private InboundPricingIdempotencyMapper idempotency;
|
||||
@MockBean private InboundOutboxMapper outbox;
|
||||
@Resource private FbaPricingRepository repository;
|
||||
@BeforeAll
|
||||
static void initializeEntityMetadata() {
|
||||
TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new Configuration(), "test"),
|
||||
@@ -57,11 +69,9 @@ class FbaPricingRepositoryRuleListTest {
|
||||
|
||||
@Test
|
||||
void internalVersionCalculationKeepsTenantAndRuleCodeFilters() {
|
||||
CustomerChargeRuleMapper rules = mock(CustomerChargeRuleMapper.class);
|
||||
CustomerChargeRuleEntity current = new CustomerChargeRuleEntity();
|
||||
current.setRuleVersion(3);
|
||||
when(rules.selectList(org.mockito.ArgumentMatchers.any())).thenReturn(List.of(current));
|
||||
FbaPricingRepository repository = repository(rules);
|
||||
|
||||
int result = repository.nextRuleVersion(10001L, "CR-1");
|
||||
|
||||
@@ -75,10 +85,8 @@ class FbaPricingRepositoryRuleListTest {
|
||||
assertThat(result).isEqualTo(4);
|
||||
}
|
||||
|
||||
private static Collection<Object> queryValues(Long tenantId, String ruleCode) {
|
||||
CustomerChargeRuleMapper rules = mock(CustomerChargeRuleMapper.class);
|
||||
private Collection<Object> queryValues(Long tenantId, String ruleCode) {
|
||||
when(rules.selectList(org.mockito.ArgumentMatchers.any())).thenReturn(List.of());
|
||||
FbaPricingRepository repository = repository(rules);
|
||||
|
||||
repository.listRules(tenantId, ruleCode);
|
||||
|
||||
@@ -90,10 +98,8 @@ class FbaPricingRepositoryRuleListTest {
|
||||
return wrapper.getValue().getParamNameValuePairs().values();
|
||||
}
|
||||
|
||||
private static Collection<Object> rateQueryValues(Long tenantId, String rateCode) {
|
||||
CarrierFbaRateMapper rates = mock(CarrierFbaRateMapper.class);
|
||||
private Collection<Object> rateQueryValues(Long tenantId, String rateCode) {
|
||||
when(rates.selectList(org.mockito.ArgumentMatchers.any())).thenReturn(List.of());
|
||||
FbaPricingRepository repository = repository(mock(CustomerChargeRuleMapper.class), rates);
|
||||
|
||||
repository.listRates(tenantId, rateCode);
|
||||
|
||||
@@ -105,14 +111,4 @@ class FbaPricingRepositoryRuleListTest {
|
||||
return wrapper.getValue().getParamNameValuePairs().values();
|
||||
}
|
||||
|
||||
private static FbaPricingRepository repository(CustomerChargeRuleMapper rules) {
|
||||
return repository(rules, mock(CarrierFbaRateMapper.class));
|
||||
}
|
||||
|
||||
private static FbaPricingRepository repository(
|
||||
CustomerChargeRuleMapper rules, CarrierFbaRateMapper rates) {
|
||||
return new FbaPricingRepository(rules, rates,
|
||||
mock(InboundPricingMapper.class), mock(InboundFeeMapper.class), mock(InboundFeeItemMapper.class),
|
||||
mock(InboundPricingIdempotencyMapper.class), mock(InboundOutboxMapper.class));
|
||||
}
|
||||
}
|
||||
|
||||
+11
-23
@@ -18,6 +18,9 @@ import com.baiyue.firstleg.mapper.InboundTransportSnapshotMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.apache.ibatis.builder.MapperBuilderAssistant;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
@@ -26,22 +29,22 @@ import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@SpringJUnitConfig(InboundOrderRepository.class)
|
||||
class InboundOrderRepositoryTest {
|
||||
@MockBean private InboundOrderMapper orders;
|
||||
@MockBean private InboundTransportSnapshotMapper transports;
|
||||
@MockBean private InboundPalletAllocationMapper pallets;
|
||||
@MockBean private InboundFileMapper files;
|
||||
@MockBean private InboundInternalNoteMapper notes;
|
||||
@MockBean private InboundStatusHistoryMapper histories;
|
||||
@Resource private InboundOrderRepository repository;
|
||||
@Test
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
void shouldApplyTenantCompanyAndWarehouseFilters() {
|
||||
TableInfoHelper.initTableInfo(
|
||||
new MapperBuilderAssistant(new MybatisConfiguration(), "test"), InboundOrderEntity.class);
|
||||
InboundOrderMapper orders = mock(InboundOrderMapper.class);
|
||||
when(orders.selectPage(any(Page.class), any(Wrapper.class)))
|
||||
.thenReturn(new Page<InboundOrderEntity>(1, 20));
|
||||
InboundOrderRepository repository = new InboundOrderRepository(
|
||||
orders,
|
||||
mock(InboundTransportSnapshotMapper.class),
|
||||
mock(InboundPalletAllocationMapper.class),
|
||||
mock(InboundFileMapper.class),
|
||||
mock(InboundInternalNoteMapper.class),
|
||||
mock(InboundStatusHistoryMapper.class));
|
||||
InboundOrderQuery query = new InboundOrderQuery(
|
||||
null, null, null, null, null, null, null, null,
|
||||
null, null, null, null, 20001L, 30001L, null, null, null, 1, 20);
|
||||
@@ -69,8 +72,6 @@ class InboundOrderRepositoryTest {
|
||||
TableInfoHelper.initTableInfo(
|
||||
new MapperBuilderAssistant(new MybatisConfiguration(), "test"),
|
||||
InboundPalletAllocationEntity.class);
|
||||
InboundOrderMapper orders = mock(InboundOrderMapper.class);
|
||||
InboundPalletAllocationMapper pallets = mock(InboundPalletAllocationMapper.class);
|
||||
Page<InboundOrderEntity> page = new Page<>(1, 20);
|
||||
InboundOrderEntity first = new InboundOrderEntity();
|
||||
first.setId(1L);
|
||||
@@ -84,13 +85,6 @@ class InboundOrderRepositoryTest {
|
||||
allocation(1L, 1, "A0"),
|
||||
allocation(1L, 2, "A2"),
|
||||
allocation(2L, 1, "B1")));
|
||||
InboundOrderRepository repository = new InboundOrderRepository(
|
||||
orders,
|
||||
mock(InboundTransportSnapshotMapper.class),
|
||||
pallets,
|
||||
mock(InboundFileMapper.class),
|
||||
mock(InboundInternalNoteMapper.class),
|
||||
mock(InboundStatusHistoryMapper.class));
|
||||
|
||||
var result = repository.page(10001L, new InboundOrderQuery(
|
||||
null, null, null, null, null, null, null, null,
|
||||
@@ -118,9 +112,6 @@ class InboundOrderRepositoryTest {
|
||||
TableInfoHelper.initTableInfo(
|
||||
new MapperBuilderAssistant(new MybatisConfiguration(), "test"),
|
||||
InboundPalletAllocationEntity.class);
|
||||
InboundOrderMapper orders = mock(InboundOrderMapper.class);
|
||||
InboundTransportSnapshotMapper transports = mock(InboundTransportSnapshotMapper.class);
|
||||
InboundPalletAllocationMapper pallets = mock(InboundPalletAllocationMapper.class);
|
||||
Page<InboundOrderEntity> page = new Page<>(1, 20);
|
||||
page.setRecords(java.util.List.of(order(1L, 10001L), order(2L, 10002L)));
|
||||
when(orders.selectPage(any(Page.class), any(Wrapper.class))).thenReturn(page);
|
||||
@@ -132,9 +123,6 @@ class InboundOrderRepositoryTest {
|
||||
allocation(1L, 10001L, 1, "A1"),
|
||||
allocation(2L, 10002L, 1, "B1"),
|
||||
allocation(1L, 10002L, 2, "WRONG")));
|
||||
InboundOrderRepository repository = new InboundOrderRepository(
|
||||
orders, transports, pallets, mock(InboundFileMapper.class),
|
||||
mock(InboundInternalNoteMapper.class), mock(InboundStatusHistoryMapper.class));
|
||||
|
||||
var result = repository.page(null, new InboundOrderQuery(
|
||||
null, null, null, null, null, null, null, null,
|
||||
|
||||
+39
-20
@@ -3,8 +3,14 @@ package com.baiyue.firstleg.security;
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.firstleg.config.MeasurementOpenApiProperties;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.Instant;
|
||||
@@ -13,19 +19,34 @@ import java.util.Map;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@DisplayName("第三方测量请求签名校验")
|
||||
@SpringJUnitConfig(classes = {MeasurementRequestAuthenticator.class,
|
||||
MeasurementRequestAuthenticatorTest.TestConfig.class})
|
||||
class MeasurementRequestAuthenticatorTest {
|
||||
private static final Instant NOW = Instant.parse("2026-08-01T02:30:00Z");
|
||||
private static final String SECRET = "test-secret-at-least-32-bytes-long";
|
||||
@MockBean private NonceReplayGuard guard;
|
||||
@Resource private MeasurementOpenApiProperties properties;
|
||||
@Resource private MeasurementRequestAuthenticator authenticator;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
properties.setEnabled(true);
|
||||
MeasurementOpenApiProperties.AppCredential credential = new MeasurementOpenApiProperties.AppCredential();
|
||||
credential.setSecret(SECRET);
|
||||
credential.setTenantId(10001L);
|
||||
credential.setSubjectId(90001L);
|
||||
credential.setWarehouseBindings(Map.of("CN-YW-01", 30001L));
|
||||
properties.setApps(Map.of("device-provider", credential));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("合法签名按六行规范串校验并占用Nonce")
|
||||
void shouldAcceptValidSignatureAndClaimNonce() {
|
||||
MeasurementOpenApiProperties properties = properties();
|
||||
NonceReplayGuard guard = (appKey, nonce, ttl) -> true;
|
||||
MeasurementRequestAuthenticator authenticator = new MeasurementRequestAuthenticator(
|
||||
properties, guard, Clock.fixed(NOW, ZoneOffset.UTC));
|
||||
when(guard.claim(any(), any(), any())).thenReturn(true);
|
||||
String body = "{\"eventNo\":\"M1\"}";
|
||||
String timestamp = String.valueOf(NOW.toEpochMilli());
|
||||
String canonical = MeasurementRequestAuthenticator.canonicalString("POST",
|
||||
@@ -46,12 +67,10 @@ class MeasurementRequestAuthenticatorTest {
|
||||
@DisplayName("非法签名返回统一未认证业务码且不占用Nonce")
|
||||
void shouldRejectInvalidSignatureBeforeClaimingNonce() {
|
||||
int[] claims = {0};
|
||||
NonceReplayGuard guard = (appKey, nonce, ttl) -> {
|
||||
when(guard.claim(any(), any(), any())).thenAnswer(invocation -> {
|
||||
claims[0]++;
|
||||
return true;
|
||||
};
|
||||
MeasurementRequestAuthenticator authenticator = new MeasurementRequestAuthenticator(
|
||||
properties(), guard, Clock.fixed(NOW, ZoneOffset.UTC));
|
||||
});
|
||||
|
||||
assertThatThrownBy(() -> authenticator.authenticate("POST", "/openapi/v1/inbound-measurements",
|
||||
new MeasurementRequestHeaders("device-provider", String.valueOf(NOW.toEpochMilli()),
|
||||
@@ -65,8 +84,7 @@ class MeasurementRequestAuthenticatorTest {
|
||||
@Test
|
||||
@DisplayName("相同应用重复Nonce被拒绝")
|
||||
void shouldRejectReplayedNonce() {
|
||||
MeasurementRequestAuthenticator authenticator = new MeasurementRequestAuthenticator(
|
||||
properties(), (appKey, nonce, ttl) -> false, Clock.fixed(NOW, ZoneOffset.UTC));
|
||||
when(guard.claim(any(), any(), any())).thenReturn(false);
|
||||
String timestamp = String.valueOf(NOW.toEpochMilli());
|
||||
String body = "{}";
|
||||
String canonical = MeasurementRequestAuthenticator.canonicalString("POST",
|
||||
@@ -81,15 +99,16 @@ class MeasurementRequestAuthenticatorTest {
|
||||
.isEqualTo(ResultCode.CONFLICT));
|
||||
}
|
||||
|
||||
private MeasurementOpenApiProperties properties() {
|
||||
MeasurementOpenApiProperties properties = new MeasurementOpenApiProperties();
|
||||
properties.setEnabled(true);
|
||||
MeasurementOpenApiProperties.AppCredential credential = new MeasurementOpenApiProperties.AppCredential();
|
||||
credential.setSecret(SECRET);
|
||||
credential.setTenantId(10001L);
|
||||
credential.setSubjectId(90001L);
|
||||
credential.setWarehouseBindings(Map.of("CN-YW-01", 30001L));
|
||||
properties.setApps(Map.of("device-provider", credential));
|
||||
return properties;
|
||||
@Configuration
|
||||
static class TestConfig {
|
||||
@Bean
|
||||
MeasurementOpenApiProperties properties() {
|
||||
return new MeasurementOpenApiProperties();
|
||||
}
|
||||
|
||||
@Bean
|
||||
Clock clock() {
|
||||
return Clock.fixed(NOW, ZoneOffset.UTC);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+11
-8
@@ -3,7 +3,10 @@ package com.baiyue.firstleg.service;
|
||||
import com.baiyue.firstleg.dto.FbaPricingDtos.FeeSnapshotResponse;
|
||||
import com.baiyue.firstleg.entity.InboundOrderEntity;
|
||||
import com.baiyue.firstleg.repository.InboundWorkflowRepository;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
@@ -13,15 +16,15 @@ import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@SpringJUnitConfig(AutomaticInboundCompletionService.class)
|
||||
class AutomaticInboundCompletionServiceTest {
|
||||
private final FbaPricingService pricing = mock(FbaPricingService.class);
|
||||
private final InboundWorkflowRepository workflow = mock(InboundWorkflowRepository.class);
|
||||
private final InboundCompletionOrchestrator completion = mock(InboundCompletionOrchestrator.class);
|
||||
private final AutomaticInboundCompletionService service =
|
||||
new AutomaticInboundCompletionService(pricing, workflow, completion);
|
||||
@MockBean private FbaPricingService pricing;
|
||||
@MockBean private InboundWorkflowRepository workflow;
|
||||
@MockBean private InboundCompletionOrchestrator completion;
|
||||
@Resource private AutomaticInboundCompletionService service;
|
||||
|
||||
@Test
|
||||
void 客服审核通过后自动计费发布并完成入库() {
|
||||
void shouldHandleCase01() {
|
||||
FeeSnapshotResponse fee = fee(501L);
|
||||
when(pricing.latest(10001L, 88L)).thenReturn(fee);
|
||||
when(workflow.findOwned(10001L, 88L)).thenReturn(
|
||||
@@ -43,7 +46,7 @@ class AutomaticInboundCompletionServiceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void 已完成入库的审核重放只返回最新费用() {
|
||||
void shouldHandleCase02() {
|
||||
FeeSnapshotResponse fee = fee(501L);
|
||||
when(workflow.findOwned(10001L, 88L)).thenReturn(order("INBOUNDED", "SENT", 8));
|
||||
when(pricing.latest(10001L, 88L)).thenReturn(fee);
|
||||
@@ -58,7 +61,7 @@ class AutomaticInboundCompletionServiceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void 中断在入库处理中时续跑完成而不重复计费() {
|
||||
void shouldHandleCase03() {
|
||||
FeeSnapshotResponse fee = fee(501L);
|
||||
when(pricing.latest(10001L, 88L)).thenReturn(fee);
|
||||
when(workflow.findOwned(10001L, 88L)).thenReturn(
|
||||
|
||||
@@ -4,8 +4,11 @@ import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.firstleg.dto.FbaPricingDtos.CustomerRuleVersionRequest;
|
||||
import com.baiyue.firstleg.entity.CustomerChargeRuleEntity;
|
||||
import com.baiyue.firstleg.repository.FbaPricingRepository;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
@@ -20,18 +23,22 @@ import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@DisplayName("FBA计费基础资料版本")
|
||||
@SpringJUnitConfig(FbaMasterDataService.class)
|
||||
class FbaMasterDataServiceTest {
|
||||
private static final LocalDateTime AUGUST = LocalDateTime.of(2026, 8, 1, 0, 0);
|
||||
|
||||
@MockBean
|
||||
private FbaPricingRepository repository;
|
||||
@Resource
|
||||
private FbaMasterDataService service;
|
||||
|
||||
@Test
|
||||
@DisplayName("平台列表不传企业时委托仓储查询全部企业规则")
|
||||
void listsRulesAcrossTenantsWithoutTenantFilter() {
|
||||
FbaPricingRepository repository = mock(FbaPricingRepository.class);
|
||||
List<CustomerChargeRuleEntity> expected = List.of(activeRule(AUGUST, null));
|
||||
when(repository.listRules(null, "CR-1")).thenReturn(expected);
|
||||
|
||||
List<CustomerChargeRuleEntity> result =
|
||||
new FbaMasterDataService(repository).listRules(null, "CR-1");
|
||||
List<CustomerChargeRuleEntity> result = service.listRules(null, "CR-1");
|
||||
|
||||
assertThat(result).isSameAs(expected);
|
||||
verify(repository).listRules(null, "CR-1");
|
||||
@@ -40,7 +47,6 @@ class FbaMasterDataServiceTest {
|
||||
@Test
|
||||
@DisplayName("半开有效期首尾相接不视为重叠")
|
||||
void acceptsAdjacentHalfOpenPeriods() {
|
||||
FbaPricingRepository repository = mock(FbaPricingRepository.class);
|
||||
CustomerChargeRuleEntity old = activeRule(AUGUST.minusMonths(1), AUGUST);
|
||||
when(repository.listRules(1L, null)).thenReturn(List.of(old));
|
||||
when(repository.listRules(1L, "CR-1")).thenReturn(List.of(old));
|
||||
@@ -48,8 +54,8 @@ class FbaMasterDataServiceTest {
|
||||
doAnswer(invocation -> { ((CustomerChargeRuleEntity) invocation.getArgument(0)).setId(10L); return null; })
|
||||
.when(repository).insertRule(any());
|
||||
|
||||
CustomerChargeRuleEntity created = new FbaMasterDataService(repository)
|
||||
.createRuleVersion(1L, 2L, request(AUGUST, AUGUST.plusMonths(1)));
|
||||
CustomerChargeRuleEntity created = service.createRuleVersion(
|
||||
1L, 2L, request(AUGUST, AUGUST.plusMonths(1)));
|
||||
|
||||
assertThat(created.getId()).isEqualTo(10L);
|
||||
assertThat(created.getRuleVersion()).isEqualTo(2);
|
||||
@@ -58,12 +64,11 @@ class FbaMasterDataServiceTest {
|
||||
@Test
|
||||
@DisplayName("任意重叠有效期在保存阶段拒绝避免多规则匹配")
|
||||
void rejectsOverlappingPeriods() {
|
||||
FbaPricingRepository repository = mock(FbaPricingRepository.class);
|
||||
when(repository.listRules(1L, null)).thenReturn(List.of(
|
||||
activeRule(AUGUST, AUGUST.plusMonths(1))));
|
||||
|
||||
assertThatThrownBy(() -> new FbaMasterDataService(repository)
|
||||
.createRuleVersion(1L, 2L, request(AUGUST.plusDays(1), AUGUST.plusMonths(2))))
|
||||
assertThatThrownBy(() -> service.createRuleVersion(
|
||||
1L, 2L, request(AUGUST.plusDays(1), AUGUST.plusMonths(2))))
|
||||
.isInstanceOf(BizException.class)
|
||||
.hasMessageContaining("有效期重叠");
|
||||
}
|
||||
|
||||
@@ -10,9 +10,14 @@ import com.baiyue.firstleg.entity.InboundFeeItemEntity;
|
||||
import com.baiyue.firstleg.entity.InboundPricingEntity;
|
||||
import com.baiyue.firstleg.entity.InboundPricingIdempotencyEntity;
|
||||
import com.baiyue.firstleg.repository.FbaPricingRepository;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.Clock;
|
||||
@@ -35,16 +40,19 @@ import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@DisplayName("FBA费用状态机")
|
||||
@SpringJUnitConfig(classes = {FbaPricingService.class, FbaPricingServiceTest.TestConfig.class})
|
||||
class FbaPricingServiceTest {
|
||||
private static final long TENANT = 10001L;
|
||||
private static final long OPERATOR = 20001L;
|
||||
private static final long INBOUND = 30001L;
|
||||
private static final LocalDateTime NOW = LocalDateTime.of(2026, 8, 3, 10, 0);
|
||||
@MockBean private FbaPricingRepository repository;
|
||||
@Resource private FbaPricingService service;
|
||||
|
||||
@Test
|
||||
@DisplayName("包税KG按配置系数取体积重较大值并按规则向上舍入")
|
||||
void calculatesTaxIncludedKgWithConfiguredFactorAndPrecision() {
|
||||
FbaPricingRepository repository = readyRepository();
|
||||
readyRepository();
|
||||
InboundPricingEntity inbound = inbound("100.000", "1.0001");
|
||||
CustomerChargeRuleEntity rule = rule("KG", "167.000000", 3, "CEILING");
|
||||
CarrierFbaRateEntity rate = rate("KG", "2.345678", "10.00");
|
||||
@@ -52,7 +60,7 @@ class FbaPricingServiceTest {
|
||||
when(repository.matchRules(inbound, NOW)).thenReturn(List.of(rule));
|
||||
when(repository.matchRates(inbound, "KG", NOW)).thenReturn(List.of(rate));
|
||||
|
||||
FeeSnapshotResponse response = service(repository).calculate(TENANT, OPERATOR, INBOUND, NOW, "calc-1");
|
||||
FeeSnapshotResponse response = service.calculate(TENANT, OPERATOR, INBOUND, NOW, "calc-1");
|
||||
|
||||
assertThat(response.convertedQuantity()).isEqualByComparingTo("167.017");
|
||||
assertThat(response.chargeableQuantity()).isEqualByComparingTo("167.017");
|
||||
@@ -64,7 +72,7 @@ class FbaPricingServiceTest {
|
||||
@Test
|
||||
@DisplayName("不包税CBM按配置系数折算且精度边界向上取整")
|
||||
void calculatesTaxExcludedCbmAtRoundingBoundary() {
|
||||
FbaPricingRepository repository = readyRepository();
|
||||
readyRepository();
|
||||
InboundPricingEntity inbound = inbound("363.001", "0.5000");
|
||||
CustomerChargeRuleEntity rule = rule("CBM", "363.000000", 4, "CEILING");
|
||||
CarrierFbaRateEntity rate = rate("CBM", "100.000000", "0.00");
|
||||
@@ -72,7 +80,7 @@ class FbaPricingServiceTest {
|
||||
when(repository.matchRules(inbound, NOW)).thenReturn(List.of(rule));
|
||||
when(repository.matchRates(inbound, "CBM", NOW)).thenReturn(List.of(rate));
|
||||
|
||||
FeeSnapshotResponse response = service(repository).calculate(TENANT, OPERATOR, INBOUND, NOW, "calc-2");
|
||||
FeeSnapshotResponse response = service.calculate(TENANT, OPERATOR, INBOUND, NOW, "calc-2");
|
||||
|
||||
assertThat(response.convertedQuantity()).isEqualByComparingTo("1.0001");
|
||||
assertThat(response.chargeableQuantity()).isEqualByComparingTo("1.0001");
|
||||
@@ -82,12 +90,12 @@ class FbaPricingServiceTest {
|
||||
@Test
|
||||
@DisplayName("审核未通过的测量快照禁止计费")
|
||||
void rejectsUnapprovedMeasurementSnapshot() {
|
||||
FbaPricingRepository repository = readyRepository();
|
||||
readyRepository();
|
||||
InboundPricingEntity inbound = inbound("10", "1");
|
||||
inbound.setMeasureReviewStatus("REJECTED");
|
||||
when(repository.findInbound(TENANT, INBOUND)).thenReturn(inbound);
|
||||
|
||||
assertThatThrownBy(() -> service(repository).calculate(TENANT, OPERATOR, INBOUND, NOW, "calc-3"))
|
||||
assertThatThrownBy(() -> service.calculate(TENANT, OPERATOR, INBOUND, NOW, "calc-3"))
|
||||
.isInstanceOf(BizException.class)
|
||||
.hasMessageContaining("审核通过");
|
||||
verify(repository, never()).matchRules(any(), any());
|
||||
@@ -96,25 +104,25 @@ class FbaPricingServiceTest {
|
||||
@Test
|
||||
@DisplayName("无客户规则和多客户规则都明确拒绝")
|
||||
void rejectsMissingAndAmbiguousRules() {
|
||||
FbaPricingRepository missing = readyRepository();
|
||||
readyRepository();
|
||||
InboundPricingEntity inbound = inbound("10", "1");
|
||||
when(missing.findInbound(TENANT, INBOUND)).thenReturn(inbound);
|
||||
when(missing.matchRules(inbound, NOW)).thenReturn(List.of());
|
||||
assertThatThrownBy(() -> service(missing).calculate(TENANT, OPERATOR, INBOUND, NOW, "none"))
|
||||
when(repository.findInbound(TENANT, INBOUND)).thenReturn(inbound);
|
||||
when(repository.matchRules(inbound, NOW)).thenReturn(List.of());
|
||||
assertThatThrownBy(() -> service.calculate(TENANT, OPERATOR, INBOUND, NOW, "none"))
|
||||
.isInstanceOf(BizException.class).hasMessageContaining("未匹配到有效客户计费规则");
|
||||
|
||||
FbaPricingRepository ambiguous = readyRepository();
|
||||
when(ambiguous.findInbound(TENANT, INBOUND)).thenReturn(inbound);
|
||||
when(ambiguous.matchRules(inbound, NOW)).thenReturn(List.of(
|
||||
org.mockito.Mockito.reset(repository);
|
||||
readyRepository();
|
||||
when(repository.findInbound(TENANT, INBOUND)).thenReturn(inbound);
|
||||
when(repository.matchRules(inbound, NOW)).thenReturn(List.of(
|
||||
rule("KG", "167", 3, "CEILING"), rule("KG", "168", 3, "CEILING")));
|
||||
assertThatThrownBy(() -> service(ambiguous).calculate(TENANT, OPERATOR, INBOUND, NOW, "many"))
|
||||
assertThatThrownBy(() -> service.calculate(TENANT, OPERATOR, INBOUND, NOW, "many"))
|
||||
.isInstanceOf(BizException.class).hasMessageContaining("多条有效客户计费规则");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("同一幂等键重复试算直接返回原费用快照")
|
||||
void returnsOriginalSnapshotForRepeatedCalculation() {
|
||||
FbaPricingRepository repository = mock(FbaPricingRepository.class);
|
||||
InboundPricingIdempotencyEntity completed = completed("FEE_CALCULATE", 501L);
|
||||
when(repository.findIdempotency(TENANT, OPERATOR, "FEE_CALCULATE", "same")).thenReturn(completed);
|
||||
when(repository.findFee(TENANT, 501L)).thenReturn(savedFee("CALCULATED"));
|
||||
@@ -122,7 +130,7 @@ class FbaPricingServiceTest {
|
||||
InboundFeeItemEntity item = new InboundFeeItemEntity(); item.setUnitPrice(new BigDecimal("2.00"));
|
||||
when(repository.findBaseItem(TENANT, 501L)).thenReturn(item);
|
||||
|
||||
FeeSnapshotResponse response = service(repository).calculate(TENANT, OPERATOR, INBOUND, NOW, "same");
|
||||
FeeSnapshotResponse response = service.calculate(TENANT, OPERATOR, INBOUND, NOW, "same");
|
||||
|
||||
assertThat(response.feeId()).isEqualTo(501L);
|
||||
verify(repository, never()).matchRules(any(), any());
|
||||
@@ -132,7 +140,6 @@ class FbaPricingServiceTest {
|
||||
@Test
|
||||
@DisplayName("重复发送只返回首次结果且不会重复写Outbox")
|
||||
void sendIsIdempotentAndDoesNotDuplicateOutbox() {
|
||||
FbaPricingRepository repository = mock(FbaPricingRepository.class);
|
||||
InboundPricingEntity inbound = inbound("10", "1");
|
||||
InboundFeeEntity confirmed = savedFee("CONFIRMED");
|
||||
when(repository.findInbound(TENANT, INBOUND)).thenReturn(inbound);
|
||||
@@ -148,7 +155,6 @@ class FbaPricingServiceTest {
|
||||
doAnswer(invocation -> { ((InboundPricingIdempotencyEntity) invocation.getArgument(0)).setId(900L); return null; })
|
||||
.when(repository).insertIdempotency(any());
|
||||
|
||||
FbaPricingService service = service(repository);
|
||||
service.send(TENANT, OPERATOR, INBOUND, "send-once");
|
||||
service.send(TENANT, OPERATOR, INBOUND, "send-once");
|
||||
|
||||
@@ -159,12 +165,11 @@ class FbaPricingServiceTest {
|
||||
@Test
|
||||
@DisplayName("同一幂等键用于不同试算时点会冲突")
|
||||
void rejectsReusedIdempotencyKeyWithDifferentRequest() {
|
||||
FbaPricingRepository repository = mock(FbaPricingRepository.class);
|
||||
InboundPricingIdempotencyEntity completed = completed("FEE_CALCULATE", 501L);
|
||||
completed.setRequestHash("different");
|
||||
when(repository.findIdempotency(TENANT, OPERATOR, "FEE_CALCULATE", "same")).thenReturn(completed);
|
||||
|
||||
assertThatThrownBy(() -> service(repository).calculate(TENANT, OPERATOR, INBOUND, NOW, "same"))
|
||||
assertThatThrownBy(() -> service.calculate(TENANT, OPERATOR, INBOUND, NOW, "same"))
|
||||
.isInstanceOf(BizException.class)
|
||||
.extracting(exception -> ((BizException) exception).getResultCode())
|
||||
.isEqualTo(ResultCode.CONFLICT);
|
||||
@@ -173,7 +178,6 @@ class FbaPricingServiceTest {
|
||||
@Test
|
||||
@DisplayName("已发布费用可生成新版本且不回退已入库状态")
|
||||
void adjustsPublishedFeeWithoutRollingBackInbound() {
|
||||
FbaPricingRepository repository = mock(FbaPricingRepository.class);
|
||||
InboundPricingEntity inbound = inbound("10", "1");
|
||||
inbound.setProcessStatus("INBOUNDED");
|
||||
inbound.setFeeStatus("SENT");
|
||||
@@ -195,7 +199,7 @@ class FbaPricingServiceTest {
|
||||
when(repository.completeIdempotency(any(), eq(502L), eq(NOW))).thenReturn(1);
|
||||
when(repository.findFee(TENANT, 502L)).thenAnswer(invocation -> inserted.get());
|
||||
|
||||
FeeSnapshotResponse response = service(repository).adjustPublished(TENANT, OPERATOR, INBOUND,
|
||||
FeeSnapshotResponse response = service.adjustPublished(TENANT, OPERATOR, INBOUND,
|
||||
new com.baiyue.firstleg.dto.FbaPricingDtos.AdjustFeeRequest(
|
||||
new BigDecimal("100.00"), new BigDecimal("20.00"), "客服修正"), "adjust-1");
|
||||
|
||||
@@ -208,8 +212,7 @@ class FbaPricingServiceTest {
|
||||
verify(repository).insertOutbox(any());
|
||||
}
|
||||
|
||||
private FbaPricingRepository readyRepository() {
|
||||
FbaPricingRepository repository = mock(FbaPricingRepository.class);
|
||||
private void readyRepository() {
|
||||
when(repository.findIdempotency(anyLong(), anyLong(), any(), any())).thenReturn(null);
|
||||
doAnswer(invocation -> { ((InboundPricingIdempotencyEntity) invocation.getArgument(0)).setId(900L); return null; })
|
||||
.when(repository).insertIdempotency(any());
|
||||
@@ -218,12 +221,14 @@ class FbaPricingServiceTest {
|
||||
when(repository.nextFeeVersion(TENANT, INBOUND)).thenReturn(1);
|
||||
when(repository.markInboundCalculated(eq(TENANT), eq(INBOUND), any(), any())).thenReturn(1);
|
||||
when(repository.completeIdempotency(any(), eq(501L), eq(NOW))).thenReturn(1);
|
||||
return repository;
|
||||
}
|
||||
|
||||
private FbaPricingService service(FbaPricingRepository repository) {
|
||||
return new FbaPricingService(repository,
|
||||
Clock.fixed(Instant.parse("2026-08-03T02:00:00Z"), ZoneId.of("Asia/Shanghai")));
|
||||
@Configuration
|
||||
static class TestConfig {
|
||||
@Bean
|
||||
Clock clock() {
|
||||
return Clock.fixed(Instant.parse("2026-08-03T02:00:00Z"), ZoneId.of("Asia/Shanghai"));
|
||||
}
|
||||
}
|
||||
|
||||
private InboundPricingEntity inbound(String weight, String volume) {
|
||||
|
||||
@@ -6,9 +6,12 @@ import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.firstleg.dto.FulfillmentTaskQuery;
|
||||
import com.baiyue.firstleg.entity.FulfillmentTaskEntity;
|
||||
import com.baiyue.firstleg.repository.FulfillmentTaskRepository;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -19,24 +22,24 @@ import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@DisplayName("履约任务服务")
|
||||
@SpringJUnitConfig(FulfillmentTaskService.class)
|
||||
class FulfillmentTaskServiceTest {
|
||||
@MockBean private FulfillmentTaskRepository repository;
|
||||
@Resource private FulfillmentTaskService service;
|
||||
@Test
|
||||
@DisplayName("列表查询保留租户和分页条件")
|
||||
void shouldPreserveTenantAndPaginationInPageQuery() {
|
||||
FulfillmentTaskRepository repository = mock(FulfillmentTaskRepository.class);
|
||||
FulfillmentTaskQuery query = new FulfillmentTaskQuery(null, null, null, 1, 20);
|
||||
PageResult<FulfillmentTaskEntity> expected = new PageResult<>(List.of(), 1, 20, 0, 0);
|
||||
when(repository.page(10001L, query)).thenReturn(expected);
|
||||
|
||||
assertThat(service(repository).page(10001L, query)).isSameAs(expected);
|
||||
assertThat(service.page(10001L, query)).isSameAs(expected);
|
||||
verify(repository).page(10001L, query);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("详情和按订单查询不存在时返回404")
|
||||
void shouldReturnNotFoundForMissingTaskAndOrder() {
|
||||
FulfillmentTaskRepository repository = mock(FulfillmentTaskRepository.class);
|
||||
FulfillmentTaskService service = service(repository);
|
||||
|
||||
assertThatThrownBy(() -> service.detail(10001L, 1L))
|
||||
.isInstanceOf(BizException.class)
|
||||
@@ -51,20 +54,14 @@ class FulfillmentTaskServiceTest {
|
||||
@Test
|
||||
@DisplayName("查询分配历史前先校验任务归属租户")
|
||||
void shouldVerifyTaskTenantBeforeLoadingAssignmentHistory() {
|
||||
FulfillmentTaskRepository repository = mock(FulfillmentTaskRepository.class);
|
||||
FulfillmentTaskEntity task = new FulfillmentTaskEntity();
|
||||
when(repository.findOwned(10001L, 1L)).thenReturn(task);
|
||||
PageResult<com.baiyue.firstleg.entity.AssignmentHistoryEntity> expected =
|
||||
new PageResult<>(List.of(), 1, 20, 0, 0);
|
||||
when(repository.pageHistory(10001L, 1L, 1, 20)).thenReturn(expected);
|
||||
|
||||
assertThat(service(repository).assignmentHistory(10001L, 1L, 1, 20)).isSameAs(expected);
|
||||
assertThat(service.assignmentHistory(10001L, 1L, 1, 20)).isSameAs(expected);
|
||||
verify(repository).findOwned(10001L, 1L);
|
||||
}
|
||||
|
||||
private FulfillmentTaskService service(FulfillmentTaskRepository repository) {
|
||||
FulfillmentTaskService service = new FulfillmentTaskService();
|
||||
ReflectionTestUtils.setField(service, "repository", repository);
|
||||
return service;
|
||||
}
|
||||
}
|
||||
|
||||
+11
-3
@@ -5,8 +5,11 @@ import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.firstleg.entity.InboundFeeEntity;
|
||||
import com.baiyue.firstleg.entity.InboundOrderEntity;
|
||||
import com.baiyue.firstleg.repository.InboundWorkflowRepository;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
@@ -19,7 +22,14 @@ import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@DisplayName("发送费用并入库状态编排")
|
||||
@SpringJUnitConfig(InboundCompletionOrchestrator.class)
|
||||
class InboundCompletionOrchestratorTest {
|
||||
@MockBean
|
||||
private InboundWorkflowRepository repository;
|
||||
@MockBean
|
||||
private InboundIdempotencyService idempotency;
|
||||
@Resource
|
||||
private InboundCompletionOrchestrator service;
|
||||
@Test
|
||||
@DisplayName("审核通过且费用和库位引用已准备时进入INBOUNDING")
|
||||
void shouldBeginInboundAfterAllGuardsPass() {
|
||||
@@ -104,8 +114,6 @@ class InboundCompletionOrchestratorTest {
|
||||
}
|
||||
|
||||
private Fixture fixture(String reviewStatus, boolean feeRelease) {
|
||||
InboundWorkflowRepository repository = mock(InboundWorkflowRepository.class);
|
||||
InboundIdempotencyService idempotency = mock(InboundIdempotencyService.class);
|
||||
when(idempotency.begin(anyLong(), anyLong(), anyLong(), anyString(), anyString(), anyString()))
|
||||
.thenReturn(new InboundIdempotencyService.Context(1L, false));
|
||||
InboundOrderEntity order = new InboundOrderEntity();
|
||||
@@ -117,7 +125,7 @@ class InboundCompletionOrchestratorTest {
|
||||
when(repository.findOwned(10001L, 10L)).thenReturn(order);
|
||||
when(repository.findConfirmedFee(10001L, 10L, 30L)).thenReturn(fee);
|
||||
when(repository.countPreparedAllocations(10001L, 10L)).thenReturn(1L);
|
||||
return new Fixture(new InboundCompletionOrchestrator(repository, idempotency), repository, order);
|
||||
return new Fixture(service, repository, order);
|
||||
}
|
||||
|
||||
private record Fixture(InboundCompletionOrchestrator service,
|
||||
|
||||
+8
-6
@@ -4,9 +4,12 @@ import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.firstleg.entity.InboundIdempotencyEntity;
|
||||
import com.baiyue.firstleg.repository.InboundWorkflowRepository;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
@@ -17,16 +20,19 @@ import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@DisplayName("入库写操作幂等边界")
|
||||
@SpringJUnitConfig(InboundIdempotencyService.class)
|
||||
class InboundIdempotencyServiceTest {
|
||||
@MockBean
|
||||
private InboundWorkflowRepository repository;
|
||||
@Resource
|
||||
private InboundIdempotencyService service;
|
||||
@Test
|
||||
@DisplayName("同键同请求成功记录可重放")
|
||||
void shouldReplayCompletedRequest() {
|
||||
InboundWorkflowRepository repository = mock(InboundWorkflowRepository.class);
|
||||
doThrow(new DuplicateKeyException("duplicate")).when(repository).insertIdempotency(any());
|
||||
InboundIdempotencyEntity existing = existing("SUCCESS");
|
||||
when(repository.findIdempotency(10001L, "MEASURE_APPROVE", 9L, "key-1"))
|
||||
.thenReturn(existing);
|
||||
InboundIdempotencyService service = new InboundIdempotencyService(repository);
|
||||
|
||||
InboundIdempotencyService.Context context = service.begin(
|
||||
10001L, 9L, 10L, "MEASURE_APPROVE", "key-1", "10|20|3|");
|
||||
@@ -37,13 +43,11 @@ class InboundIdempotencyServiceTest {
|
||||
@Test
|
||||
@DisplayName("同键不同请求返回冲突")
|
||||
void shouldRejectDifferentRequestForSameKey() {
|
||||
InboundWorkflowRepository repository = mock(InboundWorkflowRepository.class);
|
||||
doThrow(new DuplicateKeyException("duplicate")).when(repository).insertIdempotency(any());
|
||||
InboundIdempotencyEntity existing = existing("SUCCESS");
|
||||
existing.setRequestHash("different-hash");
|
||||
when(repository.findIdempotency(10001L, "MEASURE_APPROVE", 9L, "key-1"))
|
||||
.thenReturn(existing);
|
||||
InboundIdempotencyService service = new InboundIdempotencyService(repository);
|
||||
|
||||
assertThatThrownBy(() -> service.begin(
|
||||
10001L, 9L, 10L, "MEASURE_APPROVE", "key-1", "10|20|4|"))
|
||||
@@ -55,9 +59,7 @@ class InboundIdempotencyServiceTest {
|
||||
@Test
|
||||
@DisplayName("新请求完成时只允许PROCESSING条件更新一次")
|
||||
void shouldCompleteProcessingRecordOnce() {
|
||||
InboundWorkflowRepository repository = mock(InboundWorkflowRepository.class);
|
||||
when(repository.completeIdempotency(1L, "{\"inboundId\":\"10\"}")).thenReturn(1);
|
||||
InboundIdempotencyService service = new InboundIdempotencyService(repository);
|
||||
|
||||
service.complete(new InboundIdempotencyService.Context(1L, false), 10L);
|
||||
|
||||
|
||||
+30
-13
@@ -13,6 +13,8 @@ import com.baiyue.firstleg.security.AuthenticatedMeasurementApp;
|
||||
import com.baiyue.firstleg.security.MeasurementRequestAuthenticator;
|
||||
import com.baiyue.firstleg.security.MeasurementRequestHeaders;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.Validator;
|
||||
import jakarta.validation.Validation;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -20,6 +22,11 @@ import org.springframework.transaction.TransactionStatus;
|
||||
import org.springframework.transaction.support.TransactionCallback;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
import java.math.BigDecimal;
|
||||
@@ -41,6 +48,9 @@ import static org.mockito.Mockito.doThrow;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
@DisplayName("第三方测量开放接口服务")
|
||||
@SpringJUnitConfig(classes = {InboundMeasurementOpenApiService.class,
|
||||
InboundMeasurementOpenApiServiceTest.TestConfig.class})
|
||||
@TestPropertySource(properties = "baiyue.security.internal-service-token=internal-token")
|
||||
class InboundMeasurementOpenApiServiceTest {
|
||||
private static final String BODY = """
|
||||
{"eventNo":"M202608010001","warehouseCode":"CN-YW-01","inboundCode":"RK202608010001",
|
||||
@@ -50,6 +60,14 @@ class InboundMeasurementOpenApiServiceTest {
|
||||
"boxSpecification":"50.2x40.1x30cm"}
|
||||
""";
|
||||
private static final byte[] IMAGE = "image-content".getBytes(java.nio.charset.StandardCharsets.UTF_8);
|
||||
@MockBean private InboundMeasurementOpenApiRepository repository;
|
||||
@MockBean private MeasurementRequestAuthenticator authenticator;
|
||||
@MockBean private ThirdPartyCallLogService logs;
|
||||
@MockBean private TransactionTemplate transactions;
|
||||
@MockBean private OrderCargoClient orderCargo;
|
||||
@MockBean private MeasurementImageClient images;
|
||||
@Resource private ObjectMapper objectMapper;
|
||||
@Resource private InboundMeasurementOpenApiService service;
|
||||
|
||||
@Test
|
||||
@DisplayName("回调只写批次明细和幂等结果,不存在入库主单update入口")
|
||||
@@ -85,12 +103,11 @@ class InboundMeasurementOpenApiServiceTest {
|
||||
@DisplayName("重复Idempotency-Key且报文一致时返回首次结果,不重复写测量")
|
||||
void shouldReturnFirstResultForDuplicateCallback() throws Exception {
|
||||
Fixture fixture = fixture();
|
||||
ObjectMapper mapper = objectMapper();
|
||||
EventResponse first = new EventResponse("M202608010001", "RK202608010001", true,
|
||||
"RECEIVED", OffsetDateTime.parse("2026-08-01T02:30:00Z"));
|
||||
ThirdPartyCallLogEntity existing = new ThirdPartyCallLogEntity();
|
||||
existing.setRequestHash(requestHash());
|
||||
existing.setResultJson(mapper.writeValueAsString(first));
|
||||
existing.setResultJson(objectMapper.writeValueAsString(first));
|
||||
existing.setProcessingStatus("ACCEPTED");
|
||||
when(fixture.logs.claimIdempotency(1L)).thenReturn(false);
|
||||
when(fixture.logs.findIdempotencyOwner(10001L, "device-provider", "idem-1")).thenReturn(existing);
|
||||
@@ -137,12 +154,6 @@ class InboundMeasurementOpenApiServiceTest {
|
||||
}
|
||||
|
||||
private Fixture fixture() {
|
||||
InboundMeasurementOpenApiRepository repository = mock(InboundMeasurementOpenApiRepository.class);
|
||||
MeasurementRequestAuthenticator authenticator = mock(MeasurementRequestAuthenticator.class);
|
||||
ThirdPartyCallLogService logs = mock(ThirdPartyCallLogService.class);
|
||||
TransactionTemplate transactions = mock(TransactionTemplate.class);
|
||||
OrderCargoClient orderCargo = mock(OrderCargoClient.class);
|
||||
MeasurementImageClient images = mock(MeasurementImageClient.class);
|
||||
when(logs.start(anyString(), anyString(), any(), anyString(), anyString())).thenReturn(1L);
|
||||
when(logs.claimIdempotency(1L)).thenReturn(true);
|
||||
when(authenticator.authenticate(anyString(), anyString(), any(), anyString(), anyBoolean()))
|
||||
@@ -161,14 +172,20 @@ class InboundMeasurementOpenApiServiceTest {
|
||||
TransactionCallback<EventResponse> callback = invocation.getArgument(0);
|
||||
return callback.doInTransaction(mock(TransactionStatus.class));
|
||||
});
|
||||
InboundMeasurementOpenApiService service = new InboundMeasurementOpenApiService(repository,
|
||||
authenticator, logs, orderCargo, images, "internal-token", objectMapper(),
|
||||
Validation.buildDefaultValidatorFactory().getValidator(), transactions);
|
||||
return new Fixture(repository, logs, orderCargo, images, service);
|
||||
}
|
||||
|
||||
private ObjectMapper objectMapper() {
|
||||
return new ObjectMapper().findAndRegisterModules();
|
||||
@Configuration
|
||||
static class TestConfig {
|
||||
@Bean
|
||||
ObjectMapper objectMapper() {
|
||||
return new ObjectMapper().findAndRegisterModules();
|
||||
}
|
||||
|
||||
@Bean
|
||||
Validator validator() {
|
||||
return Validation.buildDefaultValidatorFactory().getValidator();
|
||||
}
|
||||
}
|
||||
|
||||
private InboundOrderLookupEntity inbound() {
|
||||
|
||||
+7
-8
@@ -9,10 +9,13 @@ import com.baiyue.firstleg.entity.InboundOrderEntity;
|
||||
import com.baiyue.firstleg.entity.InboundWeightResultEntity;
|
||||
import com.baiyue.firstleg.entity.InboundWeightRevisionEntity;
|
||||
import com.baiyue.firstleg.repository.InboundMeasurementRepository;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
@@ -26,15 +29,11 @@ import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@DisplayName("平台入库测量应用服务")
|
||||
@SpringJUnitConfig(InboundMeasurementService.class)
|
||||
class InboundMeasurementServiceTest {
|
||||
private InboundMeasurementRepository repository;
|
||||
private InboundMeasurementService service;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
repository = mock(InboundMeasurementRepository.class);
|
||||
service = new InboundMeasurementService(repository, mock(MeasurementBatchService.class));
|
||||
}
|
||||
@MockBean private InboundMeasurementRepository repository;
|
||||
@MockBean private MeasurementBatchService batchService;
|
||||
@Resource private InboundMeasurementService service;
|
||||
|
||||
@Test
|
||||
void correctingWeightKeepsOcrOriginalAndWritesImmutableRevision() {
|
||||
|
||||
+15
-11
@@ -9,7 +9,10 @@ import com.baiyue.firstleg.dto.InboundOrderQuery;
|
||||
import com.baiyue.firstleg.dto.InboundOrderSummary;
|
||||
import com.baiyue.firstleg.entity.InboundOrderEntity;
|
||||
import com.baiyue.firstleg.repository.InboundOrderRepository;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
@@ -20,15 +23,20 @@ import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@SpringJUnitConfig(InboundOrderQueryService.class)
|
||||
class InboundOrderQueryServiceTest {
|
||||
@MockBean
|
||||
private InboundOrderRepository repository;
|
||||
@Resource
|
||||
private InboundOrderQueryService service;
|
||||
|
||||
@Test
|
||||
void shouldQueryAllPlatformTenantsWhenTenantFilterIsMissing() {
|
||||
InboundOrderRepository repository = mock(InboundOrderRepository.class);
|
||||
InboundOrderQuery query = query(null, null, null, null);
|
||||
PageResult<InboundOrderSummary> expected = new PageResult<>(List.of(), 1, 20, 0, 0);
|
||||
when(repository.page(null, query)).thenReturn(expected);
|
||||
|
||||
assertThat(new InboundOrderQueryService(repository).page(null, query)).isSameAs(expected);
|
||||
assertThat(service.page(null, query)).isSameAs(expected);
|
||||
verify(repository).page(null, query);
|
||||
}
|
||||
|
||||
@@ -46,9 +54,8 @@ class InboundOrderQueryServiceTest {
|
||||
|
||||
@Test
|
||||
void shouldRejectConflictingTabAndStatus() {
|
||||
InboundOrderRepository repository = mock(InboundOrderRepository.class);
|
||||
InboundOrderQuery query = query("WAIT_MEASURE", InboundListTab.WAIT_MEASURE, null, null);
|
||||
assertThatThrownBy(() -> new InboundOrderQueryService(repository).page(10001L, query))
|
||||
assertThatThrownBy(() -> service.page(10001L, query))
|
||||
.isInstanceOf(BizException.class)
|
||||
.extracting(exception -> ((BizException) exception).getResultCode())
|
||||
.isEqualTo(ResultCode.BAD_REQUEST);
|
||||
@@ -56,31 +63,28 @@ class InboundOrderQueryServiceTest {
|
||||
|
||||
@Test
|
||||
void shouldRejectReversedArrivalRange() {
|
||||
InboundOrderRepository repository = mock(InboundOrderRepository.class);
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
assertThatThrownBy(() -> new InboundOrderQueryService(repository)
|
||||
.page(10001L, query(null, null, now, now.minusDays(1))))
|
||||
assertThatThrownBy(() -> service.page(
|
||||
10001L, query(null, null, now, now.minusDays(1))))
|
||||
.isInstanceOf(BizException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldLoadDetailOnlyAfterTenantOwnedOrderExists() {
|
||||
InboundOrderRepository repository = mock(InboundOrderRepository.class);
|
||||
InboundOrderEntity order = new InboundOrderEntity();
|
||||
order.setId(9L);
|
||||
InboundOrderDetail detail = new InboundOrderDetail(null, null, List.of(), List.of(), List.of(), List.of());
|
||||
when(repository.findOwned(10001L, 9L)).thenReturn(order);
|
||||
when(repository.detail(10001L, order)).thenReturn(detail);
|
||||
|
||||
assertThat(new InboundOrderQueryService(repository).detail(10001L, 9L)).isSameAs(detail);
|
||||
assertThat(service.detail(10001L, 9L)).isSameAs(detail);
|
||||
verify(repository).findOwned(10001L, 9L);
|
||||
verify(repository).detail(10001L, order);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnBusinessNotFoundForCrossTenantOrMissingOrder() {
|
||||
InboundOrderRepository repository = mock(InboundOrderRepository.class);
|
||||
assertThatThrownBy(() -> new InboundOrderQueryService(repository).detail(10002L, 9L))
|
||||
assertThatThrownBy(() -> service.detail(10002L, 9L))
|
||||
.isInstanceOf(BizException.class)
|
||||
.extracting(exception -> ((BizException) exception).getResultCode())
|
||||
.isEqualTo(ResultCode.NOT_FOUND);
|
||||
|
||||
@@ -6,10 +6,13 @@ import com.baiyue.firstleg.entity.InboundMeasurementEntity;
|
||||
import com.baiyue.firstleg.entity.InboundMeasurementItemEntity;
|
||||
import com.baiyue.firstleg.entity.InboundOrderEntity;
|
||||
import com.baiyue.firstleg.repository.InboundMeasurementRepository;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
@@ -24,14 +27,13 @@ import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@DisplayName("测量批次共享领域服务")
|
||||
@SpringJUnitConfig(MeasurementBatchService.class)
|
||||
class MeasurementBatchServiceTest {
|
||||
private InboundMeasurementRepository repository;
|
||||
private MeasurementBatchService service;
|
||||
@MockBean private InboundMeasurementRepository repository;
|
||||
@Resource private MeasurementBatchService service;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
repository = mock(InboundMeasurementRepository.class);
|
||||
service = new MeasurementBatchService(repository);
|
||||
when(repository.findInbound(1L, 10L)).thenReturn(inbound());
|
||||
org.mockito.Mockito.doAnswer(invocation -> {
|
||||
InboundMeasurementEntity entity = invocation.getArgument(0);
|
||||
|
||||
+22
-6
@@ -6,8 +6,14 @@ import com.baiyue.firstleg.config.InboundProperties;
|
||||
import com.baiyue.firstleg.entity.InboundOrderEntity;
|
||||
import com.baiyue.firstleg.repository.InboundPalletAllocationRepository;
|
||||
import com.baiyue.firstleg.repository.InboundWorkflowRepository;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
@@ -19,6 +25,9 @@ import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@SpringJUnitConfig(classes = {MeasurementLocationOccupationService.class,
|
||||
MeasurementLocationOccupationServiceTest.TestConfig.class})
|
||||
@TestPropertySource(properties = "baiyue.security.internal-service-token=internal-token")
|
||||
class MeasurementLocationOccupationServiceTest {
|
||||
private static final long BUSINESS_TENANT = 10001L;
|
||||
private static final long RESOURCE_TENANT = 90001L;
|
||||
@@ -27,12 +36,11 @@ class MeasurementLocationOccupationServiceTest {
|
||||
private static final long OPERATOR = 9L;
|
||||
private static final long MEASUREMENT = 30L;
|
||||
|
||||
private final WarehousePalletLocationClient warehouse = mock(WarehousePalletLocationClient.class);
|
||||
private final InboundPalletAllocationRepository allocations = mock(InboundPalletAllocationRepository.class);
|
||||
private final InboundWorkflowRepository workflow = mock(InboundWorkflowRepository.class);
|
||||
private final InboundProperties properties = properties();
|
||||
private final MeasurementLocationOccupationService service =
|
||||
new MeasurementLocationOccupationService(warehouse, allocations, workflow, properties, "internal-token");
|
||||
@MockBean private WarehousePalletLocationClient warehouse;
|
||||
@MockBean private InboundPalletAllocationRepository allocations;
|
||||
@MockBean private InboundWorkflowRepository workflow;
|
||||
@Resource private InboundProperties properties;
|
||||
@Resource private MeasurementLocationOccupationService service;
|
||||
|
||||
@Test
|
||||
void sharedWarehouseResourcesUseResourceTenantWhileSnapshotsRemainInBusinessTenant() {
|
||||
@@ -119,6 +127,14 @@ class MeasurementLocationOccupationServiceTest {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class TestConfig {
|
||||
@Bean
|
||||
InboundProperties inboundProperties() {
|
||||
return properties();
|
||||
}
|
||||
}
|
||||
|
||||
private static InboundOrderEntity inbound() {
|
||||
InboundOrderEntity value = new InboundOrderEntity();
|
||||
value.setTenantId(BUSINESS_TENANT);
|
||||
|
||||
+10
-6
@@ -8,8 +8,11 @@ import com.baiyue.firstleg.dto.MeasurementReviewRequest;
|
||||
import com.baiyue.firstleg.entity.InboundMeasurementEntity;
|
||||
import com.baiyue.firstleg.entity.InboundOrderEntity;
|
||||
import com.baiyue.firstleg.repository.InboundWorkflowRepository;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
@@ -31,7 +34,12 @@ import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@DisplayName("入库测量审核服务")
|
||||
@SpringJUnitConfig(MeasurementReviewService.class)
|
||||
class MeasurementReviewServiceTest {
|
||||
@MockBean private InboundWorkflowRepository repository;
|
||||
@MockBean private InboundIdempotencyService idempotency;
|
||||
@MockBean private InboundProperties properties;
|
||||
@Resource private MeasurementReviewService service;
|
||||
@Test
|
||||
@DisplayName("审核通过是测量到计费的唯一正常推进关口")
|
||||
void shouldApproveOnlyPendingSubmittedMeasurement() {
|
||||
@@ -138,12 +146,9 @@ class MeasurementReviewServiceTest {
|
||||
}
|
||||
|
||||
private Fixture fixture(boolean allowRelease) {
|
||||
InboundWorkflowRepository repository = mock(InboundWorkflowRepository.class);
|
||||
InboundIdempotencyService idempotency = mock(InboundIdempotencyService.class);
|
||||
when(idempotency.begin(anyLong(), anyLong(), anyLong(), anyString(), anyString(), anyString()))
|
||||
.thenReturn(new InboundIdempotencyService.Context(1L, false));
|
||||
InboundProperties properties = new InboundProperties();
|
||||
properties.setAllowRejectedMeasurement(allowRelease);
|
||||
when(properties.isAllowRejectedMeasurement()).thenReturn(allowRelease);
|
||||
InboundOrderEntity order = new InboundOrderEntity();
|
||||
order.setProcessStatus("MEASURE_PENDING");
|
||||
order.setMeasureReviewStatus("PENDING");
|
||||
@@ -151,8 +156,7 @@ class MeasurementReviewServiceTest {
|
||||
measurement.setStatus("SUBMITTED");
|
||||
when(repository.findOwned(10001L, 10L)).thenReturn(order);
|
||||
when(repository.findMeasurement(10001L, 10L, 20L)).thenReturn(measurement);
|
||||
return new Fixture(new MeasurementReviewService(repository, idempotency, properties),
|
||||
repository, idempotency, order, measurement);
|
||||
return new Fixture(service, repository, idempotency, order, measurement);
|
||||
}
|
||||
|
||||
private record Fixture(MeasurementReviewService service, InboundWorkflowRepository repository,
|
||||
|
||||
+10
-6
@@ -2,7 +2,10 @@ package com.baiyue.firstleg.service;
|
||||
|
||||
import com.baiyue.firstleg.dto.InboundMeasurementRequests;
|
||||
import com.baiyue.firstleg.dto.InboundMeasurementResponses;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -12,13 +15,14 @@ import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@SpringJUnitConfig(MeasurementSubmissionService.class)
|
||||
class MeasurementSubmissionServiceTest {
|
||||
private final InboundMeasurementService measurements = mock(InboundMeasurementService.class);
|
||||
private final MeasurementLocationOccupationService locations = mock(MeasurementLocationOccupationService.class);
|
||||
private final MeasurementSubmissionService service = new MeasurementSubmissionService(measurements, locations);
|
||||
@MockBean private InboundMeasurementService measurements;
|
||||
@MockBean private MeasurementLocationOccupationService locations;
|
||||
@Resource private MeasurementSubmissionService service;
|
||||
|
||||
@Test
|
||||
void 测量提交与库位占用处于同一事务边界() throws Exception {
|
||||
void shouldHandleCase01() throws Exception {
|
||||
assertThat(MeasurementSubmissionService.class
|
||||
.getDeclaredMethod("submit", long.class, long.class, long.class,
|
||||
InboundMeasurementRequests.SubmitRequest.class)
|
||||
@@ -26,7 +30,7 @@ class MeasurementSubmissionServiceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void 测量完成后立即占用库位再返回待客服审核() {
|
||||
void shouldHandleCase02() {
|
||||
var request = new InboundMeasurementRequests.SubmitRequest(30L, 2);
|
||||
var submitted = new InboundMeasurementResponses.MeasurementSubmitted(
|
||||
30L, 3, "MEASURE_PENDING", "PENDING", "STANDARD", null, null, null);
|
||||
@@ -38,7 +42,7 @@ class MeasurementSubmissionServiceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void 库位占用失败向上抛出以触发测量事务回滚() {
|
||||
void shouldHandleCase03() {
|
||||
var request = new InboundMeasurementRequests.SubmitRequest(30L, 2);
|
||||
var submitted = new InboundMeasurementResponses.MeasurementSubmitted(
|
||||
30L, 3, "MEASURE_PENDING", "PENDING", "STANDARD", null, null, null);
|
||||
|
||||
+19
-12
@@ -7,7 +7,11 @@ import com.baiyue.firstleg.dto.InboundMeasurementResponses;
|
||||
import com.baiyue.firstleg.client.MeasurementImageClient;
|
||||
import com.baiyue.firstleg.entity.InboundOrderEntity;
|
||||
import com.baiyue.firstleg.repository.InboundOrderRepository;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
@@ -19,16 +23,17 @@ import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@SpringJUnitConfig(MerchantInboundBillingService.class)
|
||||
@TestPropertySource(properties = "baiyue.internal.token=internal-token")
|
||||
class MerchantInboundBillingServiceTest {
|
||||
private final InboundOrderRepository orders = mock(InboundOrderRepository.class);
|
||||
private final FbaPricingService pricing = mock(FbaPricingService.class);
|
||||
private final InboundMeasurementService measurements = mock(InboundMeasurementService.class);
|
||||
private final MeasurementImageClient imageClient = mock(MeasurementImageClient.class);
|
||||
private final MerchantInboundBillingService service = new MerchantInboundBillingService(
|
||||
orders, pricing, measurements, imageClient, "internal-token");
|
||||
@MockBean private InboundOrderRepository orders;
|
||||
@MockBean private FbaPricingService pricing;
|
||||
@MockBean private InboundMeasurementService measurements;
|
||||
@MockBean private MeasurementImageClient imageClient;
|
||||
@Resource private MerchantInboundBillingService service;
|
||||
|
||||
@Test
|
||||
void 客户只能看到本租户已完成入库且已发布的费用() {
|
||||
void shouldHandleCase01() {
|
||||
InboundOrderEntity inbound = inbound("INBOUNDED", "SENT");
|
||||
InboundOrderDetail detail = new InboundOrderDetail(mock(com.baiyue.firstleg.dto.InboundOrderSummary.class),
|
||||
"客户备注", List.of(), List.of(
|
||||
@@ -64,7 +69,7 @@ class MerchantInboundBillingServiceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void 客服尚未完成发布时客户不可见中间费用() {
|
||||
void shouldHandleCase02() {
|
||||
InboundOrderEntity inbound = inbound("FEE_CONFIRMING", "CONFIRMED");
|
||||
when(orders.findByOrderId(10001L, 77L)).thenReturn(inbound);
|
||||
|
||||
@@ -75,7 +80,7 @@ class MerchantInboundBillingServiceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void 使用租户条件查询防止跨租户读取订单费用() {
|
||||
void shouldHandleCase03() {
|
||||
when(orders.findByOrderId(20002L, 77L)).thenReturn(null);
|
||||
|
||||
assertThatThrownBy(() -> service.find(20002L, 77L)).isInstanceOf(BizException.class);
|
||||
@@ -85,7 +90,7 @@ class MerchantInboundBillingServiceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void 只能下载已发布计费明细实际引用的图片() {
|
||||
void shouldHandleCase04() {
|
||||
InboundOrderEntity inbound = inbound("INBOUNDED", "SENT");
|
||||
InboundMeasurementResponses.Detail measurement = mock(InboundMeasurementResponses.Detail.class);
|
||||
InboundMeasurementResponses.Item item = mock(InboundMeasurementResponses.Item.class);
|
||||
@@ -94,7 +99,9 @@ class MerchantInboundBillingServiceTest {
|
||||
when(measurement.weightResults()).thenReturn(List.of());
|
||||
when(orders.findByOrderId(10001L, 77L)).thenReturn(inbound);
|
||||
when(measurements.detail(10001L, 88L)).thenReturn(measurement);
|
||||
when(imageClient.download("internal-token", 10001L, 88L, 41L)).thenReturn(
|
||||
when(imageClient.download(org.mockito.ArgumentMatchers.anyString(),
|
||||
org.mockito.ArgumentMatchers.eq(10001L), org.mockito.ArgumentMatchers.eq(88L),
|
||||
org.mockito.ArgumentMatchers.eq(41L))).thenReturn(
|
||||
org.springframework.http.ResponseEntity.ok()
|
||||
.contentType(org.springframework.http.MediaType.IMAGE_PNG)
|
||||
.body(new byte[]{1, 2, 3}));
|
||||
@@ -106,7 +113,7 @@ class MerchantInboundBillingServiceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void 拒绝下载未被当前计费明细引用的图片() {
|
||||
void shouldHandleCase05() {
|
||||
InboundOrderEntity inbound = inbound("INBOUNDED", "SENT");
|
||||
InboundMeasurementResponses.Detail measurement = mock(InboundMeasurementResponses.Detail.class);
|
||||
when(measurement.items()).thenReturn(List.of());
|
||||
|
||||
Reference in New Issue
Block a user