This commit is contained in:
2026-08-26 11:11:03 +08:00
5 changed files with 128 additions and 15 deletions
@@ -0,0 +1,55 @@
package com.baiyue.file.controller;
import com.baiyue.common.core.api.ResultCode;
import com.baiyue.common.core.exception.BizException;
import com.baiyue.common.security.AuthenticatedUser;
import com.baiyue.file.service.ProductImageService;
import jakarta.annotation.Resource;
import org.springframework.http.CacheControl;
import org.springframework.http.ContentDisposition;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.nio.charset.StandardCharsets;
/** 鐢ㄤ簬骞冲彴绔鍗曡鎯呮煡鐪嬪晢鎴峰晢鍝佸浘鐗囥€? */
@RestController
@RequestMapping("/api/platform/v1/files/product-images")
public class PlatformProductImageController {
@Resource private ProductImageService images;
@GetMapping("/{tenantId}/{fileId}")
@PreAuthorize("hasAuthority('platform:order:list')")
public ResponseEntity<byte[]> download(
@AuthenticationPrincipal AuthenticatedUser user,
@PathVariable long tenantId,
@PathVariable long fileId) {
requirePlatform(user);
if (tenantId <= 0 || fileId <= 0) {
throw new BizException(ResultCode.BAD_REQUEST, "瀹㈡埛鍜屾枃浠禝D蹇呴』澶т簬0");
}
var image = images.download(tenantId, fileId);
return ResponseEntity.ok()
.cacheControl(CacheControl.noStore())
.contentType(MediaType.parseMediaType(image.contentType()))
.contentLength(image.content().length)
.header(HttpHeaders.CONTENT_DISPOSITION, ContentDisposition.inline()
.filename(image.fileName(), StandardCharsets.UTF_8).build().toString())
.body(image.content());
}
private static void requirePlatform(AuthenticatedUser user) {
if (user == null || !"PLATFORM".equalsIgnoreCase(user.appCode())
|| user.tenantId() == null || user.tenantId() != 0L
|| user.userId() == null || user.userId() <= 0) {
throw new BizException(ResultCode.FORBIDDEN, "闇€瑕佸钩鍙扮韬唤");
}
}
}
@@ -77,6 +77,26 @@ class ProductImageControllerContractTest {
assertThatThrownBy(() -> controller.upload(platform, file)).isInstanceOf(BizException.class);
}
@Test
void platformDownloadUsesTenantScopeAndOrderListPermission() throws Exception {
RequestMapping mapping = PlatformProductImageController.class.getAnnotation(RequestMapping.class);
assertThat(mapping.value()).contains("/api/platform/v1/files/product-images");
var download = PlatformProductImageController.class.getDeclaredMethod(
"download", AuthenticatedUser.class, long.class, long.class);
assertThat(download.getAnnotation(PreAuthorize.class).value())
.isEqualTo("hasAuthority('platform:order:list')");
ProductImageService service = mock(ProductImageService.class);
PlatformProductImageController controller = new PlatformProductImageController();
ReflectionTestUtils.setField(controller, "images", service);
when(service.download(10001L, 31L)).thenReturn(
new ProductImageService.Download("product.png", "image/png", new byte[] {1}));
var platform = new AuthenticatedUser(1L, 0L, 0L, 1L, "admin", "PLATFORM", "WEB", Set.of());
assertThat(controller.download(platform, 10001L, 31L).getBody()).containsExactly(1);
verify(service).download(10001L, 31L);
}
private static AuthenticatedUser merchant(long tenantId) {
return new AuthenticatedUser(2L, tenantId, tenantId, 2L, "merchant", "MERCHANT", "WEB", Set.of());
}
@@ -68,7 +68,9 @@ public class ReconciliationStatementService {
CustomerProfile customer = customer(request.customerId());
long subjectId = activeSubject();
List<Source> sources = eligibleSources(request);
if (sources.isEmpty()) throw error("FIN_409", "没有符合结算条件的CNY应收费用来源");
if (sources.isEmpty()) {
throw error("FIN_409", "当前客户在所选账期内没有可生成的CNY应收费用,请调整账期或选择其他客户后重试");
}
BigDecimal total = sources.stream().map(Source::amount).reduce(BigDecimal.ZERO, BigDecimal::add);
LocalDate dueDate = request.periodEnd().plusDays(customer.creditDays());
String statementNo = documentNumbers.next("REC");
@@ -19,6 +19,7 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.time.LocalDateTime;
import java.util.Comparator;
@@ -181,10 +182,10 @@ public class UnifiedOrderQueryService {
order.getChannelName(), order.getReceivingWarehouseId(), order.getReceivingWarehouseName(),
order.getDestinationWarehouseId(), order.getDestinationWarehouseName(), null, null,
order.getInboundCode(),
order.getExpectedShipDate(), totalPackageCount(cargo), null, null, null,
null, null, null, null, null, order.getPickupMethod(), order.getTaxMode(), order.getContainerMode(),
order.getExpectedShipDate(), totalPackageCount(cargo), null, null, grossWeightKg(cargo), volumeCbm(cargo),
null, null, null, null, order.getPickupMethod(), order.getTaxMode(), order.getContainerMode(),
order.getCustomsInspection(), order.getDestinationType(), order.getAmazonWarehouseCode(),
null, null, null, null, null, null, null, order.getExpressCompany(), order.getExpressTrackingNo(),
null, null, null, null, netWeightKg(cargo), null, null, order.getExpressCompany(), order.getExpressTrackingNo(),
string(order.getCreatedBy()), skuNos(cargo), totalQuantity(cargo), order.getShipmentId(), order.getReferenceId(),
null, null, null, null, null,
order.getCustomerRemark(), latestInternalNote,
@@ -196,11 +197,13 @@ public class UnifiedOrderQueryService {
order.getCustomerId(), order.getCustomerName(),
order.getChannelName(), order.getReceivingWarehouseId(), order.getReceivingWarehouseName(),
order.getDestinationWarehouseId(), order.getDestinationWarehouseName(), inbound.id(), inbound.version(), inbound.inboundCode(),
order.getExpectedShipDate(), inbound.totalPackageCount(), null, inbound.locationCodes(),
inbound.actualGrossWeightKg(), inbound.actualVolumeCbm(), inbound.expectedArrivalAt(), inbound.feeReviewStatus(),
order.getExpectedShipDate(), inbound.totalPackageCount() != null ? inbound.totalPackageCount() : totalPackageCount(cargo), null, inbound.locationCodes(),
inbound.actualGrossWeightKg() != null ? inbound.actualGrossWeightKg() : grossWeightKg(cargo),
inbound.actualVolumeCbm() != null ? inbound.actualVolumeCbm() : volumeCbm(cargo), inbound.expectedArrivalAt(), inbound.feeReviewStatus(),
inbound.companyName(), inbound.businessType(), order.getPickupMethod(), inbound.taxMode(), inbound.containerMode(),
inbound.customsInspection(), inbound.destinationType(), inbound.destinationCode(), inbound.palletCount(),
inbound.chargeableWeightKg(), inbound.chargeableVolumeCbm(), inbound.volumeWeightKg(), inbound.actualNetWeightKg(),
inbound.chargeableWeightKg(), inbound.chargeableVolumeCbm(), inbound.volumeWeightKg(),
inbound.actualNetWeightKg() != null ? inbound.actualNetWeightKg() : netWeightKg(cargo),
inbound.measureCompletedAt(), inbound.inboundedAt(), inbound.expressCompanyName(), inbound.expressTrackingNo(),
inbound.createdByName(), skuNos(cargo), totalQuantity(cargo), order.getShipmentId(), order.getReferenceId(), measurement(inbound),
preload(summary.preload()), loading(summary.loading()),
@@ -217,12 +220,16 @@ public class UnifiedOrderQueryService {
parseOrderStatus(inbound.orderStatusSnapshot()),
inbound.customerId(), inbound.customerName(), inbound.channelName(), inbound.warehouseId(),
inbound.warehouseName(), inbound.deliveryWarehouseId(), inbound.deliveryWarehouseName(),
inbound.id(), inbound.version(), inbound.inboundCode(), null, inbound.totalPackageCount(),
null, inbound.locationCodes(), inbound.actualGrossWeightKg(),
inbound.actualVolumeCbm(), inbound.expectedArrivalAt(), inbound.feeReviewStatus(), inbound.companyName(),
inbound.id(), inbound.version(), inbound.inboundCode(), null,
inbound.totalPackageCount() != null ? inbound.totalPackageCount() : totalPackageCount(cargo),
null, inbound.locationCodes(),
inbound.actualGrossWeightKg() != null ? inbound.actualGrossWeightKg() : grossWeightKg(cargo),
inbound.actualVolumeCbm() != null ? inbound.actualVolumeCbm() : volumeCbm(cargo),
inbound.expectedArrivalAt(), inbound.feeReviewStatus(), inbound.companyName(),
inbound.businessType(), order == null ? null : order.getPickupMethod(), inbound.taxMode(), inbound.containerMode(),
inbound.customsInspection(), inbound.destinationType(), inbound.destinationCode(), inbound.palletCount(),
inbound.chargeableWeightKg(), inbound.chargeableVolumeCbm(), inbound.volumeWeightKg(), inbound.actualNetWeightKg(),
inbound.chargeableWeightKg(), inbound.chargeableVolumeCbm(), inbound.volumeWeightKg(),
inbound.actualNetWeightKg() != null ? inbound.actualNetWeightKg() : netWeightKg(cargo),
inbound.measureCompletedAt(), inbound.inboundedAt(), inbound.expressCompanyName(), inbound.expressTrackingNo(),
inbound.createdByName(), skuNos(cargo), totalQuantity(cargo), order == null ? null : order.getShipmentId(),
order == null ? null : order.getReferenceId(), measurement(inbound), preload(summary.preload()), loading(summary.loading()),
@@ -282,7 +289,20 @@ public class UnifiedOrderQueryService {
return cargo == null ? null : cargo.totalPackageCount();
}
private record CargoSummary(List<String> skuNos, Integer totalQuantity, Integer totalPackageCount) {
private BigDecimal grossWeightKg(CargoSummary cargo) {
return cargo == null ? null : cargo.grossWeightKg();
}
private BigDecimal netWeightKg(CargoSummary cargo) {
return cargo == null ? null : cargo.netWeightKg();
}
private BigDecimal volumeCbm(CargoSummary cargo) {
return cargo == null ? null : cargo.volumeCbm();
}
private record CargoSummary(List<String> skuNos, Integer totalQuantity, Integer totalPackageCount,
BigDecimal grossWeightKg, BigDecimal netWeightKg, BigDecimal volumeCbm) {
private static CargoSummary from(List<OrderCargoEntity> cargoes) {
List<String> skuNos = cargoes.stream().map(OrderCargoEntity::getSkuNo)
.filter(Objects::nonNull).map(String::trim).filter(value -> !value.isEmpty()).distinct().toList();
@@ -292,7 +312,16 @@ public class UnifiedOrderQueryService {
.filter(Objects::nonNull).toList();
Integer totalPackageCount = packageCounts.isEmpty() ? null
: Math.toIntExact(packageCounts.stream().mapToLong(Integer::longValue).sum());
return new CargoSummary(skuNos, totalQuantity, totalPackageCount);
BigDecimal grossWeightKg = sum(cargoes, OrderCargoEntity::getGrossWeightKg);
BigDecimal netWeightKg = sum(cargoes, OrderCargoEntity::getNetWeightKg);
BigDecimal volumeCbm = sum(cargoes, OrderCargoEntity::getVolumeCbm);
return new CargoSummary(skuNos, totalQuantity, totalPackageCount, grossWeightKg, netWeightKg, volumeCbm);
}
private static BigDecimal sum(List<OrderCargoEntity> cargoes,
Function<OrderCargoEntity, BigDecimal> value) {
List<BigDecimal> values = cargoes.stream().map(value).filter(Objects::nonNull).toList();
return values.isEmpty() ? null : values.stream().reduce(BigDecimal.ZERO, BigDecimal::add);
}
}
private String inboundMainStatus(String status) {
@@ -84,13 +84,20 @@ class UnifiedOrderQueryServiceTest {
OrderCargoEntity cargo = new OrderCargoEntity();
cargo.setOrderId(77L);
cargo.setPackageCount(100);
cargo.setGrossWeightKg(new BigDecimal("12.5"));
cargo.setNetWeightKg(new BigDecimal("11.2"));
cargo.setVolumeCbm(new BigDecimal("0.375"));
when(orders.unifiedPage(eq(10001L), eq(query), any()))
.thenReturn(new PageResult<>(List.of(order), 1, 20, 1, 1));
when(firstleg.summaries(eq("internal-token"), any())).thenReturn(ApiResponse.success(List.of()));
when(orders.findCargoByOrderIds(10001L, List.of(77L))).thenReturn(List.of(cargo));
assertThat(service.page(10001L, query).records()).singleElement()
.extracting(UnifiedOrderResponse.Row::totalPackageCount).isEqualTo(100);
assertThat(service.page(10001L, query).records()).singleElement().satisfies(row -> {
assertThat(row.totalPackageCount()).isEqualTo(100);
assertThat(row.actualGrossWeightKg()).isEqualByComparingTo("12.5");
assertThat(row.totalNetWeightKg()).isEqualByComparingTo("11.2");
assertThat(row.actualVolumeCbm()).isEqualByComparingTo("0.375");
});
verify(orders).findCargoByOrderIds(10001L, List.of(77L));
}