条件
This commit is contained in:
parent
bcbdc79b6c
commit
2ba76809b3
@ -14,8 +14,13 @@
|
||||
|
||||
- `startDate`、`endDate` 必填,包含首尾日期,最多 31 天。
|
||||
- `storeName` 选填;不传或传空字符串时查询全部网点。多个网点可用逗号分隔。
|
||||
- 后端调用 NGP 时固定传入 `postState=800`(财务核算成功)和
|
||||
`refundStatus=0`(无需退款),调用方不需要传这两个参数,也不能覆盖。
|
||||
- 后端通过 `ngp.api.bill.getSaleOrderList` 查询业绩,并固定传入
|
||||
`postStateList=[800]`(财务核算成功)和 `refundType=0`(无需退款)。
|
||||
调用方不需要传这两个参数,也不能覆盖。
|
||||
- 该 NGP 方法需要在开放平台为当前应用开通“往来单位数据授权”。若未授权,
|
||||
接口会直接返回 NGP 的授权错误,不会降级为未筛选的全量数据。
|
||||
- 业绩缓存记录固定筛选规则的版本。首次部署本规则后,旧的“全部状态”缓存会自动失效并
|
||||
重新拉取,无需手工清空缓存表;月度每周统计也会使用同一份已筛选缓存。
|
||||
- NGP 每页最多查询 200 条;服务会读取全部页面后返回合计。
|
||||
|
||||
## 响应
|
||||
|
||||
@ -27,8 +27,9 @@ import java.util.concurrent.atomic.AtomicLong;
|
||||
@Component
|
||||
public class NgpApiClient {
|
||||
static final String SALES_POST_BILL_LIST = "ngp.bill.salepostbill.list";
|
||||
static final String PERFORMANCE_SALE_ORDER_LIST = "ngp.api.bill.getSaleOrderList";
|
||||
static final int FINANCIAL_ACCOUNTING_SUCCESS = 800;
|
||||
static final int NO_REFUND = 0;
|
||||
static final int NO_REFUND_TYPE = 0;
|
||||
|
||||
private final FoundationProperties.Ngp config;
|
||||
private final NgpTokenProvider tokenProvider;
|
||||
@ -62,6 +63,11 @@ public class NgpApiClient {
|
||||
return call(SALES_POST_BILL_LIST, salesPostBillRequestBody(beginTime, endTime, pageIndex));
|
||||
}
|
||||
|
||||
public JsonNode performanceBillPage(String beginTime, String endTime, int pageIndex) {
|
||||
return call(PERFORMANCE_SALE_ORDER_LIST,
|
||||
performanceBillRequestBody(beginTime, endTime, pageIndex));
|
||||
}
|
||||
|
||||
Map<String, Object> salesPostBillRequestBody(String beginTime, String endTime, int pageIndex) {
|
||||
Map<String, Object> body = new LinkedHashMap<>();
|
||||
body.put("pageSize", Math.min(Math.max(config.getPageSize(), 1), 200));
|
||||
@ -69,8 +75,18 @@ public class NgpApiClient {
|
||||
body.put("vchtypes", List.of(2000));
|
||||
body.put("beginTime", beginTime);
|
||||
body.put("endTime", endTime);
|
||||
body.put("postState", FINANCIAL_ACCOUNTING_SUCCESS);
|
||||
body.put("refundStatus", NO_REFUND);
|
||||
return body;
|
||||
}
|
||||
|
||||
Map<String, Object> performanceBillRequestBody(String beginTime, String endTime, int pageIndex) {
|
||||
Map<String, Object> body = new LinkedHashMap<>();
|
||||
body.put("pageSize", Math.min(Math.max(config.getPageSize(), 1), 200));
|
||||
body.put("pageIndex", pageIndex);
|
||||
body.put("startTime", beginTime);
|
||||
body.put("endTime", endTime);
|
||||
body.put("vchtypes", List.of(2000));
|
||||
body.put("postStateList", List.of(FINANCIAL_ACCOUNTING_SUCCESS));
|
||||
body.put("refundType", NO_REFUND_TYPE);
|
||||
return body;
|
||||
}
|
||||
|
||||
|
||||
@ -17,6 +17,8 @@ import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
public class NgpPerformanceCacheRepository {
|
||||
static final String FILTER_SIGNATURE = "sale-order-post-800-refund-type-0-v1";
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
public NgpPerformanceCacheRepository(JdbcTemplate jdbcTemplate) {
|
||||
@ -39,16 +41,23 @@ public class NgpPerformanceCacheRepository {
|
||||
jdbcTemplate.execute("""
|
||||
CREATE TABLE IF NOT EXISTS ngp_performance_daily_status (
|
||||
business_date DATE NOT NULL PRIMARY KEY,
|
||||
refreshed_at DATETIME(3) NOT NULL
|
||||
refreshed_at DATETIME(3) NOT NULL,
|
||||
filter_signature VARCHAR(64) NOT NULL DEFAULT ''
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
|
||||
""");
|
||||
ensureFilterSignatureColumn();
|
||||
}
|
||||
|
||||
public Optional<LocalDateTime> findRefreshTime(LocalDate date) {
|
||||
List<LocalDateTime> rows = jdbcTemplate.query(
|
||||
"SELECT refreshed_at FROM ngp_performance_daily_status WHERE business_date = ?",
|
||||
"""
|
||||
SELECT refreshed_at
|
||||
FROM ngp_performance_daily_status
|
||||
WHERE business_date = ? AND filter_signature = ?
|
||||
""",
|
||||
(resultSet, rowNum) -> resultSet.getTimestamp("refreshed_at").toLocalDateTime(),
|
||||
date
|
||||
date,
|
||||
FILTER_SIGNATURE
|
||||
);
|
||||
return rows.stream().findFirst();
|
||||
}
|
||||
@ -111,9 +120,27 @@ public class NgpPerformanceCacheRepository {
|
||||
});
|
||||
}
|
||||
jdbcTemplate.update("""
|
||||
INSERT INTO ngp_performance_daily_status (business_date, refreshed_at)
|
||||
VALUES (?, ?)
|
||||
ON DUPLICATE KEY UPDATE refreshed_at = VALUES(refreshed_at)
|
||||
""", date, Timestamp.valueOf(refreshedAt));
|
||||
INSERT INTO ngp_performance_daily_status (business_date, refreshed_at, filter_signature)
|
||||
VALUES (?, ?, ?)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
refreshed_at = VALUES(refreshed_at),
|
||||
filter_signature = VALUES(filter_signature)
|
||||
""", date, Timestamp.valueOf(refreshedAt), FILTER_SIGNATURE);
|
||||
}
|
||||
|
||||
private void ensureFilterSignatureColumn() {
|
||||
Integer columnCount = jdbcTemplate.queryForObject("""
|
||||
SELECT COUNT(*)
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'ngp_performance_daily_status'
|
||||
AND COLUMN_NAME = 'filter_signature'
|
||||
""", Integer.class);
|
||||
if (columnCount == null || columnCount == 0) {
|
||||
jdbcTemplate.execute("""
|
||||
ALTER TABLE ngp_performance_daily_status
|
||||
ADD COLUMN filter_signature VARCHAR(64) NOT NULL DEFAULT ''
|
||||
""");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -50,7 +50,7 @@ public class NgpPerformanceDayLoader {
|
||||
int concurrency = Math.min(Math.max(config.getConcurrency(), 1), 100);
|
||||
Aggregate aggregate = new Aggregate();
|
||||
|
||||
JsonNode firstPage = apiClient.salesPostBillPage(beginTime, endTime, 1);
|
||||
JsonNode firstPage = apiClient.performanceBillPage(beginTime, endTime, 1);
|
||||
collect(firstPage, 1, aggregate);
|
||||
long observedTotal = extractTotal(firstPage);
|
||||
int totalPages = Math.max(1, pages(observedTotal, pageSize));
|
||||
@ -61,7 +61,7 @@ public class NgpPerformanceDayLoader {
|
||||
for (int page = batchStart; page <= batchEnd; page++) {
|
||||
int pageIndex = page;
|
||||
futures.add(new PageFuture(pageIndex, CompletableFuture.supplyAsync(
|
||||
() -> apiClient.salesPostBillPage(beginTime, endTime, pageIndex), queryExecutor)));
|
||||
() -> apiClient.performanceBillPage(beginTime, endTime, pageIndex), queryExecutor)));
|
||||
}
|
||||
for (PageFuture future : futures) {
|
||||
try {
|
||||
|
||||
@ -6,6 +6,7 @@ import org.junit.jupiter.api.Test;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@ -38,12 +39,16 @@ class NgpApiClientTest {
|
||||
WebClient.builder()
|
||||
);
|
||||
|
||||
Map<String, Object> body = client.salesPostBillRequestBody(
|
||||
Map<String, Object> body = client.performanceBillRequestBody(
|
||||
"2026-09-01 00:00:00", "2026-09-01 23:59:59", 3);
|
||||
|
||||
assertThat(body)
|
||||
.containsEntry("postState", 800)
|
||||
.containsEntry("refundStatus", 0)
|
||||
.containsEntry("pageIndex", 3);
|
||||
.containsEntry("postStateList", List.of(800))
|
||||
.containsEntry("refundType", 0)
|
||||
.containsEntry("vchtypes", List.of(2000))
|
||||
.containsEntry("startTime", "2026-09-01 00:00:00")
|
||||
.containsEntry("endTime", "2026-09-01 23:59:59")
|
||||
.containsEntry("pageIndex", 3)
|
||||
.doesNotContainKeys("postState", "refundStatus");
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,40 @@
|
||||
package com.company.agentfoundation.ngp.performance;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.contains;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
class NgpPerformanceCacheRepositoryTest {
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void ignoresCacheCreatedWithAnOlderFilterPolicy() {
|
||||
JdbcTemplate jdbcTemplate = mock(JdbcTemplate.class);
|
||||
when(jdbcTemplate.query(
|
||||
contains("filter_signature = ?"),
|
||||
any(RowMapper.class),
|
||||
any(),
|
||||
any()
|
||||
)).thenReturn(List.of());
|
||||
NgpPerformanceCacheRepository repository = new NgpPerformanceCacheRepository(jdbcTemplate);
|
||||
LocalDate date = LocalDate.of(2026, 9, 1);
|
||||
|
||||
repository.findRefreshTime(date);
|
||||
|
||||
verify(jdbcTemplate).query(
|
||||
contains("filter_signature = ?"),
|
||||
any(RowMapper.class),
|
||||
eq(date),
|
||||
eq(NgpPerformanceCacheRepository.FILTER_SIGNATURE)
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -33,29 +33,28 @@ class NgpPerformanceDayLoaderTest {
|
||||
|
||||
@Test
|
||||
void fetchesEveryPageDeduplicatesBillsAndTotalsByStore() throws Exception {
|
||||
when(apiClient.salesPostBillPage(anyString(), anyString(), org.mockito.ArgumentMatchers.anyInt()))
|
||||
when(apiClient.performanceBillPage(anyString(), anyString(), org.mockito.ArgumentMatchers.anyInt()))
|
||||
.thenAnswer(invocation -> pages().get(invocation.getArgument(2, Integer.class)));
|
||||
|
||||
Map<String, NgpStoreTotal> result = loader.load(LocalDate.of(2026, 9, 1));
|
||||
|
||||
assertThat(result.get(NgpPerformanceDayLoader.normalize("兰教练(抖店)")).totalCents()).isEqualTo(2195);
|
||||
assertThat(result.get(NgpPerformanceDayLoader.normalize("另一个网点")).totalCents()).isEqualTo(200);
|
||||
verify(apiClient, times(3)).salesPostBillPage(anyString(), anyString(), org.mockito.ArgumentMatchers.anyInt());
|
||||
verify(apiClient, times(3)).performanceBillPage(anyString(), anyString(), org.mockito.ArgumentMatchers.anyInt());
|
||||
}
|
||||
|
||||
private Map<Integer, JsonNode> pages() throws Exception {
|
||||
return Map.of(
|
||||
1, objectMapper.readTree("""
|
||||
{"code":0,"data":{"total":5,"list":[
|
||||
{"vchcode":"A","ofullname":"兰教练(抖店)","detailList":[
|
||||
{"currencyDisedTaxedTotal":"6.15"},{"currencyDisedTotal":"5.80"}]},
|
||||
{"vchcode":"B","ofullname":"兰教练(抖店)","currencyBillTotal":"10.00","detailList":[]}
|
||||
{"vchcode":"A","ofullname":"兰教练(抖店)","currencyBillTotal":"11.95"},
|
||||
{"vchcode":"B","ofullname":"兰教练(抖店)","currencyBillTotal":"10.00"}
|
||||
]}}
|
||||
"""),
|
||||
2, objectMapper.readTree("""
|
||||
{"code":0,"data":{"total":5,"list":[
|
||||
{"vchcode":"B","ofullname":"兰教练(抖店)","currencyBillTotal":"10.00","detailList":[]},
|
||||
{"vchcode":"C","ofullname":"另一个网点","detailList":[{"currencyTotal":"-2.00"}]}
|
||||
{"vchcode":"B","ofullname":"兰教练(抖店)","currencyBillTotal":"10.00"},
|
||||
{"vchcode":"C","ofullname":"另一个网点","currencyBillTotal":"-2.00"}
|
||||
]}}
|
||||
"""),
|
||||
3, objectMapper.readTree("""
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user