feat(file): register domestic order exports
This commit is contained in:
@@ -22,6 +22,16 @@ baiyue:
|
|||||||
max-attempts: 3
|
max-attempts: 3
|
||||||
batch-size: 500
|
batch-size: 500
|
||||||
|
|
||||||
|
DOMESTIC_WAREHOUSE_ORDER_LIST:
|
||||||
|
provider-service: order-service
|
||||||
|
provider-path: /internal/exports/v1/DOMESTIC_WAREHOUSE_ORDER_LIST/batches
|
||||||
|
required-permission: platform:order:export
|
||||||
|
app-scope: PLATFORM
|
||||||
|
enabled: true
|
||||||
|
max-rows: 200000
|
||||||
|
max-attempts: 3
|
||||||
|
batch-size: 500
|
||||||
|
|
||||||
LOADING_LIST_BUY_DECLARATION_ITEMS:
|
LOADING_LIST_BUY_DECLARATION_ITEMS:
|
||||||
provider-service: firstleg-service
|
provider-service: firstleg-service
|
||||||
provider-path: /internal/exports/v1/LOADING_LIST_BUY_DECLARATION_ITEMS/batches
|
provider-path: /internal/exports/v1/LOADING_LIST_BUY_DECLARATION_ITEMS/batches
|
||||||
|
|||||||
@@ -18,6 +18,16 @@ baiyue:
|
|||||||
max-attempts: 3
|
max-attempts: 3
|
||||||
batch-size: 500
|
batch-size: 500
|
||||||
|
|
||||||
|
DOMESTIC_WAREHOUSE_ORDER_LIST:
|
||||||
|
provider-service: order-service
|
||||||
|
provider-path: /internal/exports/v1/DOMESTIC_WAREHOUSE_ORDER_LIST/batches
|
||||||
|
required-permission: platform:order:export
|
||||||
|
app-scope: PLATFORM
|
||||||
|
enabled: true
|
||||||
|
max-rows: 200000
|
||||||
|
max-attempts: 3
|
||||||
|
batch-size: 500
|
||||||
|
|
||||||
LOADING_LIST_BUY_DECLARATION_ITEMS:
|
LOADING_LIST_BUY_DECLARATION_ITEMS:
|
||||||
provider-service: firstleg-service
|
provider-service: firstleg-service
|
||||||
provider-path: /internal/exports/v1/LOADING_LIST_BUY_DECLARATION_ITEMS/batches
|
provider-path: /internal/exports/v1/LOADING_LIST_BUY_DECLARATION_ITEMS/batches
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import java.util.List;
|
|||||||
|
|
||||||
final class ExportSchemaContracts {
|
final class ExportSchemaContracts {
|
||||||
static final String BUY_DECLARATION_ITEMS = "LOADING_LIST_BUY_DECLARATION_ITEMS";
|
static final String BUY_DECLARATION_ITEMS = "LOADING_LIST_BUY_DECLARATION_ITEMS";
|
||||||
|
static final String DOMESTIC_WAREHOUSE_ORDER_LIST = "DOMESTIC_WAREHOUSE_ORDER_LIST";
|
||||||
static final List<ExpectedColumn> BUY_DECLARATION_COLUMNS = List.of(
|
static final List<ExpectedColumn> BUY_DECLARATION_COLUMNS = List.of(
|
||||||
column("customerName", "客户名称", ExportColumnType.STRING),
|
column("customerName", "客户名称", ExportColumnType.STRING),
|
||||||
column("orderNo", "订单号", ExportColumnType.STRING),
|
column("orderNo", "订单号", ExportColumnType.STRING),
|
||||||
@@ -21,20 +22,54 @@ final class ExportSchemaContracts {
|
|||||||
column("volume", "体积", ExportColumnType.DECIMAL),
|
column("volume", "体积", ExportColumnType.DECIMAL),
|
||||||
column("mark", "唛头", ExportColumnType.STRING),
|
column("mark", "唛头", ExportColumnType.STRING),
|
||||||
column("customerRemark", "客户备注", ExportColumnType.STRING));
|
column("customerRemark", "客户备注", ExportColumnType.STRING));
|
||||||
|
static final List<ExpectedColumn> DOMESTIC_WAREHOUSE_ORDER_COLUMNS = List.of(
|
||||||
|
column("inboundCode", "入仓码", ExportColumnType.STRING),
|
||||||
|
column("orderNo", "订单号", ExportColumnType.STRING),
|
||||||
|
column("mainStatus", "订单主状态", ExportColumnType.STRING),
|
||||||
|
column("customerName", "客户", ExportColumnType.STRING),
|
||||||
|
column("channelName", "渠道", ExportColumnType.STRING),
|
||||||
|
column("channelChanged", "曾变更渠道", ExportColumnType.BOOLEAN),
|
||||||
|
column("receivingWarehouseName", "定位仓库", ExportColumnType.STRING),
|
||||||
|
column("destinationWarehouseName", "交货仓", ExportColumnType.STRING),
|
||||||
|
column("expectedShipDate", "预计出运日期", ExportColumnType.DATE),
|
||||||
|
column("expectedArrivalAt", "预计到货时间", ExportColumnType.DATETIME),
|
||||||
|
column("expectedVolumeCbm", "预计体积(m³)", ExportColumnType.DECIMAL),
|
||||||
|
column("totalPackageCount", "箱数", ExportColumnType.INTEGER),
|
||||||
|
column("totalQuantity", "产品数量", ExportColumnType.INTEGER),
|
||||||
|
column("actualGrossWeightKg", "实测毛重(kg)", ExportColumnType.DECIMAL),
|
||||||
|
column("actualVolumeCbm", "实测体积(m³)", ExportColumnType.DECIMAL),
|
||||||
|
column("chargeableWeightKg", "计费重量(kg)", ExportColumnType.DECIMAL),
|
||||||
|
column("chargeableVolumeCbm", "计费体积(m³)", ExportColumnType.DECIMAL),
|
||||||
|
column("feeReviewStatus", "费用审核状态", ExportColumnType.STRING),
|
||||||
|
column("skuNos", "SKU", ExportColumnType.STRING),
|
||||||
|
column("taxMode", "报关方式", ExportColumnType.STRING),
|
||||||
|
column("containerMode", "装柜方式", ExportColumnType.STRING),
|
||||||
|
column("customsInspection", "是否商检", ExportColumnType.BOOLEAN),
|
||||||
|
column("containerNo", "集装箱号", ExportColumnType.STRING),
|
||||||
|
column("cabinetNo", "柜次号", ExportColumnType.STRING),
|
||||||
|
column("vesselName", "船名", ExportColumnType.STRING),
|
||||||
|
column("voyageNo", "航次", ExportColumnType.STRING),
|
||||||
|
column("createdAt", "创建时间", ExportColumnType.DATETIME));
|
||||||
|
|
||||||
private ExportSchemaContracts() { }
|
private ExportSchemaContracts() { }
|
||||||
|
|
||||||
static void validate(String exportType, List<ExportColumn> columns) {
|
static void validate(String exportType, List<ExportColumn> columns) {
|
||||||
if (!BUY_DECLARATION_ITEMS.equals(exportType)) return;
|
if (BUY_DECLARATION_ITEMS.equals(exportType)) {
|
||||||
if (columns.size() != BUY_DECLARATION_COLUMNS.size()) {
|
validate(columns, BUY_DECLARATION_COLUMNS, "买单报关导出列定义不匹配");
|
||||||
throw ExportFailure.permanent("买单报关导出列定义不匹配");
|
} else if (DOMESTIC_WAREHOUSE_ORDER_LIST.equals(exportType)) {
|
||||||
|
validate(columns, DOMESTIC_WAREHOUSE_ORDER_COLUMNS, "国内仓订单导出列定义不匹配");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void validate(List<ExportColumn> columns, List<ExpectedColumn> expectedColumns,
|
||||||
|
String failureMessage) {
|
||||||
|
if (columns.size() != expectedColumns.size()) throw ExportFailure.permanent(failureMessage);
|
||||||
for (int index = 0; index < columns.size(); index++) {
|
for (int index = 0; index < columns.size(); index++) {
|
||||||
ExportColumn actual = columns.get(index);
|
ExportColumn actual = columns.get(index);
|
||||||
ExpectedColumn expected = BUY_DECLARATION_COLUMNS.get(index);
|
ExpectedColumn expected = expectedColumns.get(index);
|
||||||
if (!expected.key.equals(actual.key()) || !expected.title.equals(actual.title())
|
if (!expected.key.equals(actual.key()) || !expected.title.equals(actual.title())
|
||||||
|| expected.type != actual.type()) {
|
|| expected.type != actual.type()) {
|
||||||
throw ExportFailure.permanent("买单报关导出列定义不匹配");
|
throw ExportFailure.permanent(failureMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ public class ExportTaskService {
|
|||||||
if (!user.permissions().contains(permission)) {
|
if (!user.permissions().contains(permission)) {
|
||||||
throw new BizException(ResultCode.FORBIDDEN, "缺少该导出类型的操作权限");
|
throw new BizException(ResultCode.FORBIDDEN, "缺少该导出类型的操作权限");
|
||||||
}
|
}
|
||||||
validateParameters(request.parameters());
|
validateParameters(type, request.parameters());
|
||||||
validateTypeParameters(type, request.parameters());
|
validateTypeParameters(type, request.parameters());
|
||||||
String parametersJson = parametersJson(request.parameters());
|
String parametersJson = parametersJson(request.parameters());
|
||||||
long id = IdWorker.getId();
|
long id = IdWorker.getId();
|
||||||
@@ -140,15 +140,23 @@ public class ExportTaskService {
|
|||||||
catch (JsonProcessingException exception) { throw new BizException(ResultCode.BAD_REQUEST, "导出筛选条件无法序列化"); }
|
catch (JsonProcessingException exception) { throw new BizException(ResultCode.BAD_REQUEST, "导出筛选条件无法序列化"); }
|
||||||
}
|
}
|
||||||
|
|
||||||
private void validateParameters(Map<String, Object> parameters) {
|
private void validateParameters(String type, Map<String, Object> parameters) {
|
||||||
Map<String, Object> values = parameters == null ? Map.of() : parameters;
|
Map<String, Object> values = parameters == null ? Map.of() : parameters;
|
||||||
if (values.size() > 20) throw new BizException(ResultCode.VALIDATION_FAILED, "导出筛选条件最多 20 项");
|
int maxItems = ExportSchemaContracts.DOMESTIC_WAREHOUSE_ORDER_LIST.equals(type) ? 32 : 20;
|
||||||
|
if (values.size() > maxItems) {
|
||||||
|
throw new BizException(ResultCode.VALIDATION_FAILED, "导出筛选条件最多 " + maxItems + " 项");
|
||||||
|
}
|
||||||
values.forEach((key, value) -> {
|
values.forEach((key, value) -> {
|
||||||
if (key == null || key.isBlank() || key.length() > 64) throw new BizException(ResultCode.VALIDATION_FAILED, "导出筛选键无效");
|
if (key == null || key.isBlank() || key.length() > 64) throw new BizException(ResultCode.VALIDATION_FAILED, "导出筛选键无效");
|
||||||
validateParameterValue(value, 1);
|
validateParameterValue(value, 1);
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
if (objectMapper.writeValueAsBytes(values).length > 4096) throw new BizException(ResultCode.VALIDATION_FAILED, "导出筛选条件不能超过 4KB");
|
int maxBytes = ExportSchemaContracts.DOMESTIC_WAREHOUSE_ORDER_LIST.equals(type)
|
||||||
|
? 512 * 1024 : 4096;
|
||||||
|
if (objectMapper.writeValueAsBytes(values).length > maxBytes) {
|
||||||
|
throw new BizException(ResultCode.VALIDATION_FAILED,
|
||||||
|
"导出筛选条件不能超过 " + (maxBytes / 1024) + "KB");
|
||||||
|
}
|
||||||
} catch (JsonProcessingException exception) {
|
} catch (JsonProcessingException exception) {
|
||||||
throw new BizException(ResultCode.BAD_REQUEST, "导出筛选条件无法序列化");
|
throw new BizException(ResultCode.BAD_REQUEST, "导出筛选条件无法序列化");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,6 +101,15 @@ baiyue:
|
|||||||
max-rows: 200000
|
max-rows: 200000
|
||||||
max-attempts: 3
|
max-attempts: 3
|
||||||
batch-size: 500
|
batch-size: 500
|
||||||
|
DOMESTIC_WAREHOUSE_ORDER_LIST:
|
||||||
|
provider-service: order-service
|
||||||
|
provider-path: /internal/exports/v1/DOMESTIC_WAREHOUSE_ORDER_LIST/batches
|
||||||
|
required-permission: platform:order:export
|
||||||
|
app-scope: PLATFORM
|
||||||
|
enabled: true
|
||||||
|
max-rows: 200000
|
||||||
|
max-attempts: 3
|
||||||
|
batch-size: 500
|
||||||
LOADING_LIST_BUY_DECLARATION_ITEMS:
|
LOADING_LIST_BUY_DECLARATION_ITEMS:
|
||||||
provider-service: firstleg-service
|
provider-service: firstleg-service
|
||||||
provider-path: /internal/exports/v1/LOADING_LIST_BUY_DECLARATION_ITEMS/batches
|
provider-path: /internal/exports/v1/LOADING_LIST_BUY_DECLARATION_ITEMS/batches
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
openapi: 3.0.3
|
||||||
|
info:
|
||||||
|
title: File Domestic Warehouse Order Export Contract
|
||||||
|
version: 1.0.0
|
||||||
|
description: >-
|
||||||
|
File 只持有异步任务、XLSX 文件、任务归属和下载权限。订单筛选、数据权限、分页游标及行数据由
|
||||||
|
Order 的 DOMESTIC_WAREHOUSE_ORDER_LIST Provider 持有,File 不复制订单业务查询。
|
||||||
|
paths:
|
||||||
|
/api/platform/v1/files/export-tasks:
|
||||||
|
post:
|
||||||
|
summary: 创建国内仓订单 XLSX 导出任务
|
||||||
|
description: >-
|
||||||
|
exportType 固定 DOMESTIC_WAREHOUSE_ORDER_LIST,format 固定 XLSX,调用人必须具有
|
||||||
|
platform:order:export。selectedOrderNos 非空时导出勾选业务订单号;为空或省略时由 Order
|
||||||
|
按 parameters 中当前列表筛选导出全部匹配记录,parameters 不接受 page、size 或内部数据库 ID。
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: {$ref: '#/components/schemas/CreateDomesticOrderExportTask'}
|
||||||
|
responses:
|
||||||
|
'200': {description: 返回异步任务,初始状态 PENDING}
|
||||||
|
'403': {description: 缺少 platform:order:export}
|
||||||
|
/api/platform/v1/files/export-tasks/{taskId}/download:
|
||||||
|
get:
|
||||||
|
summary: 下载本人已完成的订单导出文件
|
||||||
|
parameters:
|
||||||
|
- {name: taskId, in: path, required: true, schema: {type: string, pattern: '^[1-9][0-9]*$'}}
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: XLSX 文件;他人任务仅 platform:download:all 可下载
|
||||||
|
headers:
|
||||||
|
Cache-Control: {schema: {type: string, example: no-store}}
|
||||||
|
Content-Disposition: {schema: {type: string, example: attachment}}
|
||||||
|
content:
|
||||||
|
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet:
|
||||||
|
schema: {type: string, format: binary}
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
CreateDomesticOrderExportTask:
|
||||||
|
type: object
|
||||||
|
required: [exportType, format, taskName, parameters]
|
||||||
|
properties:
|
||||||
|
exportType: {type: string, enum: [DOMESTIC_WAREHOUSE_ORDER_LIST]}
|
||||||
|
format: {type: string, enum: [XLSX]}
|
||||||
|
taskName: {type: string, minLength: 1, maxLength: 128}
|
||||||
|
parameters: {$ref: '#/components/schemas/DomesticOrderExportParameters'}
|
||||||
|
DomesticOrderExportParameters:
|
||||||
|
type: object
|
||||||
|
description: >-
|
||||||
|
透传给 Order Provider;selectedOrderNos 非空时优先,其余字段与平台订单列表当前筛选一致。
|
||||||
|
properties:
|
||||||
|
selectedOrderNos:
|
||||||
|
type: array
|
||||||
|
maxItems: 5000
|
||||||
|
uniqueItems: true
|
||||||
|
items: {type: string, minLength: 1, maxLength: 64, description: 订单业务编号}
|
||||||
|
tenantId: {type: string, pattern: '^[1-9][0-9]*$'}
|
||||||
|
keyword: {type: string, maxLength: 64}
|
||||||
|
orderNo: {type: string, maxLength: 64}
|
||||||
|
mainStatus: {type: string}
|
||||||
|
customerId: {type: string, pattern: '^[1-9][0-9]*$'}
|
||||||
|
receivingWarehouseId: {type: string, pattern: '^[1-9][0-9]*$'}
|
||||||
|
destinationWarehouseId: {type: string, pattern: '^[1-9][0-9]*$'}
|
||||||
|
inboundCode: {type: string, maxLength: 64}
|
||||||
|
internalOrderStatus: {type: string, maxLength: 32}
|
||||||
|
expectedArrivalDate: {type: string, format: date}
|
||||||
|
feeReviewStatus: {type: string}
|
||||||
|
channelName: {type: string, maxLength: 128}
|
||||||
|
containerNo: {type: string, maxLength: 64}
|
||||||
|
cabinetNo: {type: string, maxLength: 64}
|
||||||
|
sailingDate: {type: string, format: date}
|
||||||
|
loaded: {type: boolean}
|
||||||
|
fullContainer: {type: boolean}
|
||||||
|
measurementReviewStatus: {type: string}
|
||||||
|
fbaCode: {type: string, maxLength: 64}
|
||||||
|
createdFrom: {type: string, format: date-time}
|
||||||
|
createdTo: {type: string, format: date-time}
|
||||||
|
DomesticOrderExportColumns:
|
||||||
|
type: object
|
||||||
|
description: Provider schemaVersion=1 固定列顺序;不包含任何内部数据库 ID。
|
||||||
|
properties:
|
||||||
|
inboundCode: {type: string, title: 入仓码}
|
||||||
|
orderNo: {type: string, title: 订单号}
|
||||||
|
mainStatus: {type: string, title: 订单主状态}
|
||||||
|
customerName: {type: string, title: 客户}
|
||||||
|
channelName: {type: string, title: 渠道}
|
||||||
|
channelChanged: {type: boolean, title: 曾变更渠道}
|
||||||
|
receivingWarehouseName: {type: string, title: 定位仓库}
|
||||||
|
destinationWarehouseName: {type: string, title: 交货仓}
|
||||||
|
expectedShipDate: {type: string, format: date, title: 预计出运日期}
|
||||||
|
expectedArrivalAt: {type: string, format: date-time, title: 预计到货时间}
|
||||||
|
expectedVolumeCbm: {type: number, title: 预计体积(m³)}
|
||||||
|
totalPackageCount: {type: integer, title: 箱数}
|
||||||
|
totalQuantity: {type: integer, title: 产品数量}
|
||||||
|
actualGrossWeightKg: {type: number, title: 实测毛重(kg)}
|
||||||
|
actualVolumeCbm: {type: number, title: 实测体积(m³)}
|
||||||
|
chargeableWeightKg: {type: number, title: 计费重量(kg)}
|
||||||
|
chargeableVolumeCbm: {type: number, title: 计费体积(m³)}
|
||||||
|
feeReviewStatus: {type: string, title: 费用审核状态}
|
||||||
|
skuNos: {type: string, title: SKU}
|
||||||
|
taxMode: {type: string, title: 报关方式}
|
||||||
|
containerMode: {type: string, title: 装柜方式}
|
||||||
|
customsInspection: {type: boolean, title: 是否商检}
|
||||||
|
containerNo: {type: string, title: 集装箱号}
|
||||||
|
cabinetNo: {type: string, title: 柜次号}
|
||||||
|
vesselName: {type: string, title: 船名}
|
||||||
|
voyageNo: {type: string, title: 航次}
|
||||||
|
createdAt: {type: string, format: date-time, title: 创建时间}
|
||||||
+60
@@ -0,0 +1,60 @@
|
|||||||
|
package com.baiyue.file.config;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.yaml.snakeyaml.Yaml;
|
||||||
|
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
class DomesticWarehouseOrderExportContractTest {
|
||||||
|
private static final String TYPE = "DOMESTIC_WAREHOUSE_ORDER_LIST";
|
||||||
|
|
||||||
|
@Test void applicationAndNacosRegisterTheSameOrderProvider() throws Exception {
|
||||||
|
for (String file : List.of("file/src/main/resources/application.yml",
|
||||||
|
"config/nacos/local/file-service.yml", "config/nacos/cn/file-service.yml")) {
|
||||||
|
String source = Files.readString(repositoryRoot().resolve(file));
|
||||||
|
assertThat(source).contains(TYPE + ":")
|
||||||
|
.contains("provider-service: order-service")
|
||||||
|
.contains("provider-path: /internal/exports/v1/" + TYPE + "/batches")
|
||||||
|
.contains("required-permission: platform:order:export")
|
||||||
|
.contains("app-scope: PLATFORM");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test void fileOpenApiFreezesSelectionPrecedenceFiltersAndBusinessColumns() throws Exception {
|
||||||
|
String source = Files.readString(repositoryRoot().resolve(
|
||||||
|
"file/src/main/resources/openapi/domestic-order-export-v1.yaml"));
|
||||||
|
Map<String, Object> document = map(new Yaml().load(source));
|
||||||
|
Map<String, Object> schemas = map(map(document.get("components")).get("schemas"));
|
||||||
|
Map<String, Object> parameters = map(map(schemas.get("DomesticOrderExportParameters")).get("properties"));
|
||||||
|
Map<String, Object> columns = map(map(schemas.get("DomesticOrderExportColumns")).get("properties"));
|
||||||
|
|
||||||
|
assertThat(parameters).containsKeys("selectedOrderNos", "tenantId", "keyword", "orderNo", "mainStatus",
|
||||||
|
"customerId", "receivingWarehouseId", "destinationWarehouseId", "inboundCode",
|
||||||
|
"internalOrderStatus", "expectedArrivalDate", "feeReviewStatus", "channelName", "containerNo",
|
||||||
|
"cabinetNo", "sailingDate", "loaded", "fullContainer", "measurementReviewStatus", "fbaCode",
|
||||||
|
"createdFrom", "createdTo").doesNotContainKeys("page", "size", "orderId");
|
||||||
|
assertThat(columns.keySet()).containsExactly("inboundCode", "orderNo", "mainStatus", "customerName",
|
||||||
|
"channelName", "channelChanged", "receivingWarehouseName", "destinationWarehouseName",
|
||||||
|
"expectedShipDate", "expectedArrivalAt", "expectedVolumeCbm", "totalPackageCount", "totalQuantity",
|
||||||
|
"actualGrossWeightKg", "actualVolumeCbm", "chargeableWeightKg", "chargeableVolumeCbm",
|
||||||
|
"feeReviewStatus", "skuNos", "taxMode", "containerMode", "customsInspection", "containerNo",
|
||||||
|
"cabinetNo", "vesselName", "voyageNo", "createdAt");
|
||||||
|
assertThat(columns).containsKeys("expectedVolumeCbm", "expectedArrivalAt")
|
||||||
|
.doesNotContainKeys("orderId", "tenantId", "customerId", "inboundId", "receivingWarehouseId",
|
||||||
|
"destinationWarehouseId");
|
||||||
|
assertThat(source).contains("platform:order:export", "selectedOrderNos 非空时优先", "no-store");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Path repositoryRoot() {
|
||||||
|
Path current = Path.of("").toAbsolutePath();
|
||||||
|
return Files.isDirectory(current.resolve("file")) ? current : current.getParent();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private static Map<String, Object> map(Object value) { return (Map<String, Object>) value; }
|
||||||
|
}
|
||||||
@@ -18,10 +18,13 @@ import jakarta.annotation.Resource;
|
|||||||
import com.baiyue.file.config.ExportDefinitionRegistry;
|
import com.baiyue.file.config.ExportDefinitionRegistry;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.Mockito.doAnswer;
|
import static org.mockito.Mockito.doAnswer;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
@@ -112,6 +115,62 @@ class ExportTaskServiceTest {
|
|||||||
.getResultCode());
|
.getResultCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void domesticOrderExportAcceptsSelectedBusinessNumbersAndTheCompleteFilterSnapshot() throws Exception {
|
||||||
|
var definition = definition();
|
||||||
|
definition.setProviderService("order-service");
|
||||||
|
definition.setProviderPath("/internal/exports/v1/DOMESTIC_WAREHOUSE_ORDER_LIST/batches");
|
||||||
|
definition.setRequiredPermission("platform:order:export");
|
||||||
|
when(registry.require(ExportSchemaContracts.DOMESTIC_WAREHOUSE_ORDER_LIST)).thenReturn(definition);
|
||||||
|
when(states.createConstrained(any(FileExportTask.class))).thenAnswer(invocation ->
|
||||||
|
new ExportStateService.CreationResult(invocation.getArgument(0), true));
|
||||||
|
var user = user(1L, Set.of("platform:order:export"));
|
||||||
|
List<String> selected = IntStream.range(0, 5000).mapToObj(index -> "ORD-" + index).toList();
|
||||||
|
|
||||||
|
var selectedResponse = service.create(user, new CreateExportTaskRequest(
|
||||||
|
ExportSchemaContracts.DOMESTIC_WAREHOUSE_ORDER_LIST, "XLSX", "国内仓订单", Map.of(
|
||||||
|
"selectedOrderNos", selected)));
|
||||||
|
|
||||||
|
assertEquals(ExportSchemaContracts.DOMESTIC_WAREHOUSE_ORDER_LIST, selectedResponse.exportType());
|
||||||
|
var filters = new LinkedHashMap<String, Object>();
|
||||||
|
filters.put("tenantId", "10001"); filters.put("keyword", "订单"); filters.put("orderNo", "ORD-1");
|
||||||
|
filters.put("mainStatus", "WAIT_MEASURE"); filters.put("customerId", "10001");
|
||||||
|
filters.put("receivingWarehouseId", "11"); filters.put("destinationWarehouseId", "12");
|
||||||
|
filters.put("inboundCode", "IN-1"); filters.put("internalOrderStatus", "APPROVED");
|
||||||
|
filters.put("expectedArrivalDate", "2026-09-01"); filters.put("feeReviewStatus", "PENDING");
|
||||||
|
filters.put("channelName", "海运"); filters.put("containerNo", "CONT-1");
|
||||||
|
filters.put("cabinetNo", "CAB-1"); filters.put("sailingDate", "2026-09-02");
|
||||||
|
filters.put("loaded", false); filters.put("fullContainer", true);
|
||||||
|
filters.put("measurementReviewStatus", "APPROVED"); filters.put("fbaCode", "FBA-1");
|
||||||
|
filters.put("createdFrom", "2026-08-01T00:00:00"); filters.put("createdTo", "2026-09-01T23:59:59");
|
||||||
|
|
||||||
|
service.create(user, new CreateExportTaskRequest(
|
||||||
|
ExportSchemaContracts.DOMESTIC_WAREHOUSE_ORDER_LIST, "XLSX", "国内仓订单", filters));
|
||||||
|
|
||||||
|
var tasks = org.mockito.ArgumentCaptor.forClass(FileExportTask.class);
|
||||||
|
verify(states, org.mockito.Mockito.times(2)).createConstrained(tasks.capture());
|
||||||
|
Map<?, ?> selectedSnapshot = new ObjectMapper().readValue(tasks.getAllValues().get(0).getParametersJson(), Map.class);
|
||||||
|
Map<?, ?> filterSnapshot = new ObjectMapper().readValue(tasks.getAllValues().get(1).getParametersJson(), Map.class);
|
||||||
|
assertEquals(5000, ((List<?>) selectedSnapshot.get("selectedOrderNos")).size());
|
||||||
|
assertEquals(filters, filterSnapshot);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void domesticOrderExportRequiresItsDedicatedPermission() {
|
||||||
|
var definition = definition();
|
||||||
|
definition.setProviderService("order-service");
|
||||||
|
definition.setProviderPath("/internal/exports/v1/DOMESTIC_WAREHOUSE_ORDER_LIST/batches");
|
||||||
|
definition.setRequiredPermission("platform:order:export");
|
||||||
|
when(registry.require(ExportSchemaContracts.DOMESTIC_WAREHOUSE_ORDER_LIST)).thenReturn(definition);
|
||||||
|
|
||||||
|
BizException denied = assertThrows(BizException.class, () -> service.create(user(1L, Set.of("platform:order:list")),
|
||||||
|
new CreateExportTaskRequest(ExportSchemaContracts.DOMESTIC_WAREHOUSE_ORDER_LIST,
|
||||||
|
"XLSX", "国内仓订单", Map.of())));
|
||||||
|
|
||||||
|
assertEquals(ResultCode.FORBIDDEN, denied.getResultCode());
|
||||||
|
assertTrue(denied.getMessage().contains("权限"));
|
||||||
|
}
|
||||||
|
|
||||||
private AuthenticatedUser user(long id, Set<String> permissions) {
|
private AuthenticatedUser user(long id, Set<String> permissions) {
|
||||||
return new AuthenticatedUser(id, 0L, 0L, id, "user" + id, "PLATFORM", "WEB", permissions);
|
return new AuthenticatedUser(id, 0L, 0L, id, "user" + id, "PLATFORM", "WEB", permissions);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -136,6 +136,58 @@ class GenericXlsxExportRunnerTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test void domesticOrderProviderMustReturnTheFrozenBusinessColumnsWithoutInternalIds() throws Exception {
|
||||||
|
List<ExportColumn> invalid = ExportSchemaContracts.DOMESTIC_WAREHOUSE_ORDER_COLUMNS.stream()
|
||||||
|
.filter(column -> !"expectedVolumeCbm".equals(column.key()))
|
||||||
|
.map(column -> new ExportColumn(column.key(), column.title(), column.type(), 18)).toList();
|
||||||
|
var batch = new ExportBatchResponse(1, "sha256:domestic-orders", "snapshot-orders",
|
||||||
|
invalid, List.of(), null, false, 0L);
|
||||||
|
when(provider.batch(anyString(), eq(ExportSchemaContracts.DOMESTIC_WAREHOUSE_ORDER_LIST), any(), any()))
|
||||||
|
.thenReturn(ApiResponse.success(batch));
|
||||||
|
FileExportTask task = domesticOrderTask(61L, Map.of());
|
||||||
|
var definition = new ExportDefinitionRegistry.Definition(); definition.setMaxRows(200000); definition.setBatchSize(500);
|
||||||
|
|
||||||
|
assertThrows(ExportFailure.class,
|
||||||
|
() -> runner.run(task, definition, new AtomicBoolean(), ignored -> { }));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test void domesticOrderColumnsGenerateWorkbookAndPassTheSelectionSnapshotToOrder() throws Exception {
|
||||||
|
List<ExportColumn> columns = ExportSchemaContracts.DOMESTIC_WAREHOUSE_ORDER_COLUMNS.stream()
|
||||||
|
.map(column -> new ExportColumn(column.key(), column.title(), column.type(), 18)).toList();
|
||||||
|
Map<String, Object> row = new java.util.LinkedHashMap<>();
|
||||||
|
row.put("inboundCode", "IN-1"); row.put("orderNo", "ORD-1");
|
||||||
|
row.put("expectedVolumeCbm", "1.25"); row.put("expectedArrivalAt", "2026-09-03T12:30:00");
|
||||||
|
var batch = new ExportBatchResponse(1, "sha256:domestic-orders", "snapshot-orders",
|
||||||
|
columns, List.of(row), null, false, 1L);
|
||||||
|
when(provider.batch(anyString(), eq(ExportSchemaContracts.DOMESTIC_WAREHOUSE_ORDER_LIST), any(), any()))
|
||||||
|
.thenReturn(ApiResponse.success(batch));
|
||||||
|
Map<String, Object> parameters = Map.of("selectedOrderNos", List.of("ORD-1", "ORD-2"));
|
||||||
|
FileExportTask task = domesticOrderTask(62L, parameters);
|
||||||
|
var definition = new ExportDefinitionRegistry.Definition(); definition.setMaxRows(200000); definition.setBatchSize(500);
|
||||||
|
|
||||||
|
var result = runner.run(task, definition, new AtomicBoolean(), ignored -> { });
|
||||||
|
|
||||||
|
try (var workbook = new XSSFWorkbook(storage.load(result.objectKey()).getInputStream())) {
|
||||||
|
var sheet = workbook.getSheetAt(0);
|
||||||
|
assertEquals("订单号", sheet.getRow(0).getCell(1).getStringCellValue());
|
||||||
|
assertEquals("预计到货时间", sheet.getRow(0).getCell(9).getStringCellValue());
|
||||||
|
assertEquals("预计体积(m³)", sheet.getRow(0).getCell(10).getStringCellValue());
|
||||||
|
assertEquals(1.25d, sheet.getRow(1).getCell(10).getNumericCellValue());
|
||||||
|
}
|
||||||
|
ArgumentCaptor<ExportBatchRequest> request = ArgumentCaptor.forClass(ExportBatchRequest.class);
|
||||||
|
verify(provider).batch(anyString(), eq(ExportSchemaContracts.DOMESTIC_WAREHOUSE_ORDER_LIST),
|
||||||
|
eq(definition), request.capture());
|
||||||
|
assertEquals(parameters, request.getValue().parameters());
|
||||||
|
}
|
||||||
|
|
||||||
|
private FileExportTask domesticOrderTask(long id, Map<String, Object> parameters) throws Exception {
|
||||||
|
FileExportTask task = new FileExportTask(); task.setId(id); task.setTenantId(0L);
|
||||||
|
task.setAppCode("PLATFORM"); task.setRequestedBy(7L); task.setDispatchVersion(1);
|
||||||
|
task.setExportType(ExportSchemaContracts.DOMESTIC_WAREHOUSE_ORDER_LIST); task.setTaskName("国内仓订单");
|
||||||
|
task.setParametersJson(new ObjectMapper().writeValueAsString(parameters));
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
static class TestConfig {
|
static class TestConfig {
|
||||||
@Bean ObjectMapper objectMapper() { return new ObjectMapper(); }
|
@Bean ObjectMapper objectMapper() { return new ObjectMapper(); }
|
||||||
|
|||||||
Reference in New Issue
Block a user