diff --git a/file/src/main/java/com/baiyue/file/controller/PlatformProductImageController.java b/file/src/main/java/com/baiyue/file/controller/PlatformProductImageController.java new file mode 100644 index 000000000..4ffcf47d0 --- /dev/null +++ b/file/src/main/java/com/baiyue/file/controller/PlatformProductImageController.java @@ -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 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, "闇€瑕佸钩鍙扮韬唤"); + } + } +} diff --git a/file/src/test/java/com/baiyue/file/controller/ProductImageControllerContractTest.java b/file/src/test/java/com/baiyue/file/controller/ProductImageControllerContractTest.java index d9828b371..eaff89224 100644 --- a/file/src/test/java/com/baiyue/file/controller/ProductImageControllerContractTest.java +++ b/file/src/test/java/com/baiyue/file/controller/ProductImageControllerContractTest.java @@ -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()); } diff --git a/finance/src/main/java/com/baiyue/finance/service/ReconciliationStatementService.java b/finance/src/main/java/com/baiyue/finance/service/ReconciliationStatementService.java index cb2a5669e..7311d5375 100644 --- a/finance/src/main/java/com/baiyue/finance/service/ReconciliationStatementService.java +++ b/finance/src/main/java/com/baiyue/finance/service/ReconciliationStatementService.java @@ -68,7 +68,9 @@ public class ReconciliationStatementService { CustomerProfile customer = customer(request.customerId()); long subjectId = activeSubject(); List 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"); diff --git a/order/src/main/java/com/baiyue/order/service/UnifiedOrderQueryService.java b/order/src/main/java/com/baiyue/order/service/UnifiedOrderQueryService.java index 5e3401d97..b50c0d987 100644 --- a/order/src/main/java/com/baiyue/order/service/UnifiedOrderQueryService.java +++ b/order/src/main/java/com/baiyue/order/service/UnifiedOrderQueryService.java @@ -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 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 skuNos, Integer totalQuantity, Integer totalPackageCount, + BigDecimal grossWeightKg, BigDecimal netWeightKg, BigDecimal volumeCbm) { private static CargoSummary from(List cargoes) { List 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 cargoes, + Function value) { + List 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) { diff --git a/order/src/test/java/com/baiyue/order/service/UnifiedOrderQueryServiceTest.java b/order/src/test/java/com/baiyue/order/service/UnifiedOrderQueryServiceTest.java index aa5c9a3d6..b0e6e56e6 100644 --- a/order/src/test/java/com/baiyue/order/service/UnifiedOrderQueryServiceTest.java +++ b/order/src/test/java/com/baiyue/order/service/UnifiedOrderQueryServiceTest.java @@ -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)); }