优化
This commit is contained in:
@@ -9,6 +9,7 @@ import com.baiyue.order.dto.BatchOrderReviewResponse;
|
||||
import com.baiyue.order.dto.OrderDetailResponse;
|
||||
import com.baiyue.order.dto.OrderRequests;
|
||||
import com.baiyue.order.dto.TenantOrderCountResponse;
|
||||
import com.baiyue.order.dto.TenantOrderStatisticsResponse;
|
||||
import com.baiyue.order.entity.OrderEntity;
|
||||
import com.baiyue.order.entity.OrderStatus;
|
||||
import com.baiyue.order.service.OrderApplicationService;
|
||||
@@ -134,6 +135,16 @@ public class PlatformFirstLegOrderController {
|
||||
return ApiResponse.success(service.countByTenantIds(tenantIds));
|
||||
}
|
||||
|
||||
@GetMapping("/tenant-statistics")
|
||||
@PreAuthorize("hasAuthority('platform:order:list')")
|
||||
@Operation(summary = "查询客户累计及本月运单数", description = "仅统计已提交的 MASTER 根运单;草稿和 CHILD 拆分单不计入,累计不受后续取消或驳回影响。月份按 Asia/Shanghai 半开区间计算。")
|
||||
public ApiResponse<TenantOrderStatisticsResponse> tenantStatistics(
|
||||
@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@RequestParam long tenantId) {
|
||||
long scopedTenant = OrderScopeGuard.platform(user, tenantId);
|
||||
return ApiResponse.success(service.tenantSubmittedStatistics(scopedTenant));
|
||||
}
|
||||
|
||||
@Operation(summary = "Tenant order detail / 租户订单详情", description = "Returns the complete order aggregate within the explicit target tenant. / 返回明确目标租户内的完整订单聚合。")
|
||||
@ApiResponses({
|
||||
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "Query succeeded / 查询成功"),
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.baiyue.order.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
@Schema(name = "TenantOrderStatisticsResponse", description = "平台客户已提交主运单统计")
|
||||
public record TenantOrderStatisticsResponse(
|
||||
@Schema(description = "客户 ID") Long tenantId,
|
||||
@Schema(description = "历史累计已提交主运单数") Long totalSubmittedOrders,
|
||||
@Schema(description = "当前上海时区月份已提交主运单数") Long currentMonthSubmittedOrders,
|
||||
@Schema(description = "本月起始时间(含),ISO-8601") String monthStart,
|
||||
@Schema(description = "下月起始时间(不含),ISO-8601") String nextMonthStart,
|
||||
@Schema(description = "统计时区") String zoneId,
|
||||
@Schema(description = "统计快照时间,ISO-8601") String asOf
|
||||
) { }
|
||||
@@ -1,4 +1,25 @@
|
||||
package com.baiyue.order.mapper; import com.baiyue.order.entity.OrderEntity; import com.baomidou.mybatisplus.core.mapper.BaseMapper; /**
|
||||
package com.baiyue.order.mapper; import com.baiyue.order.entity.OrderEntity; import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Map;
|
||||
/**
|
||||
* 订单主表 Mapper,提供 {@code ord_order} 的 MyBatis-Plus 基础数据访问能力。
|
||||
*/
|
||||
public interface OrderMapper extends BaseMapper<OrderEntity>{}
|
||||
public interface OrderMapper extends BaseMapper<OrderEntity>{
|
||||
@Select("""
|
||||
SELECT COUNT(*) AS total_submitted_orders,
|
||||
COALESCE(SUM(CASE WHEN submitted_at >= #{monthStart}
|
||||
AND submitted_at < #{nextMonthStart}
|
||||
THEN 1 ELSE 0 END), 0) AS current_month_submitted_orders
|
||||
FROM ord_order
|
||||
WHERE tenant_id = #{tenantId}
|
||||
AND deleted = 0
|
||||
AND order_role = 'MASTER'
|
||||
AND submitted_at IS NOT NULL
|
||||
""")
|
||||
Map<String, Object> selectTenantSubmittedStatistics(
|
||||
@Param("tenantId") long tenantId,
|
||||
@Param("monthStart") LocalDateTime monthStart,
|
||||
@Param("nextMonthStart") LocalDateTime nextMonthStart);
|
||||
}
|
||||
|
||||
@@ -258,6 +258,15 @@ public class OrderRepository {
|
||||
));
|
||||
}
|
||||
|
||||
/** 一次条件聚合客户已提交主运单,取消/驳回状态不会影响累计值。 */
|
||||
public Map<String, Object> tenantSubmittedStatistics(long tenantId,
|
||||
LocalDateTime monthStart,
|
||||
LocalDateTime nextMonthStart) {
|
||||
Map<String, Object> aggregate = orderMapper.selectTenantSubmittedStatistics(
|
||||
tenantId, monthStart, nextMonthStart);
|
||||
return aggregate == null ? Map.of() : aggregate;
|
||||
}
|
||||
|
||||
public List<OrderCargoEntity> findCargo(long tenantId, long orderId) {
|
||||
return cargoMapper.selectList(new LambdaQueryWrapper<OrderCargoEntity>()
|
||||
.eq(OrderCargoEntity::getTenantId, tenantId).eq(OrderCargoEntity::getOrderId, orderId)
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.order.dto.OrderDetailResponse;
|
||||
import com.baiyue.order.dto.OrderRequests;
|
||||
import com.baiyue.order.dto.TenantOrderCountResponse;
|
||||
import com.baiyue.order.dto.TenantOrderStatisticsResponse;
|
||||
import com.baiyue.order.client.FirstlegInboundClient;
|
||||
import com.baiyue.order.entity.*;
|
||||
import com.baiyue.order.repository.OrderRepository;
|
||||
@@ -23,6 +24,7 @@ import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.List;
|
||||
import java.time.*;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -154,6 +156,30 @@ public class OrderApplicationService {
|
||||
.map(tenantId -> new TenantOrderCountResponse(tenantId, counts.getOrDefault(tenantId, 0L)))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private static final ZoneId STATISTICS_ZONE = ZoneId.of("Asia/Shanghai");
|
||||
|
||||
public TenantOrderStatisticsResponse tenantSubmittedStatistics(long tenantId) {
|
||||
return tenantSubmittedStatistics(tenantId, Clock.system(STATISTICS_ZONE));
|
||||
}
|
||||
|
||||
TenantOrderStatisticsResponse tenantSubmittedStatistics(long tenantId, Clock clock) {
|
||||
ZonedDateTime asOf = ZonedDateTime.now(clock).withZoneSameInstant(STATISTICS_ZONE);
|
||||
YearMonth month = YearMonth.from(asOf);
|
||||
ZonedDateTime monthStart = month.atDay(1).atStartOfDay(STATISTICS_ZONE);
|
||||
ZonedDateTime nextMonthStart = month.plusMonths(1).atDay(1).atStartOfDay(STATISTICS_ZONE);
|
||||
var aggregate = repository.tenantSubmittedStatistics(tenantId,
|
||||
monthStart.toLocalDateTime(), nextMonthStart.toLocalDateTime());
|
||||
long total = number(aggregate.get("total_submitted_orders"));
|
||||
long current = number(aggregate.get("current_month_submitted_orders"));
|
||||
return new TenantOrderStatisticsResponse(tenantId, total, current,
|
||||
monthStart.toOffsetDateTime().toString(), nextMonthStart.toOffsetDateTime().toString(),
|
||||
STATISTICS_ZONE.getId(), asOf.toOffsetDateTime().toString());
|
||||
}
|
||||
|
||||
private static long number(Object value) {
|
||||
return value instanceof Number n ? n.longValue() : 0L;
|
||||
}
|
||||
public OrderDetailResponse detail(long tenantId, long id) {
|
||||
OrderEntity order = require(tenantId, id);
|
||||
return new OrderDetailResponse(order, repository.findCargo(tenantId, id), repository.findParties(tenantId, id),
|
||||
|
||||
@@ -55,6 +55,7 @@ CREATE TABLE IF NOT EXISTS ord_order (
|
||||
UNIQUE KEY uk_order_tenant_no (tenant_id, order_no),
|
||||
UNIQUE KEY uk_order_tenant_inbound_code (tenant_id, inbound_code),
|
||||
KEY idx_order_tenant_status_time (tenant_id, status, created_at),
|
||||
KEY idx_order_tenant_role_submitted (tenant_id, order_role, submitted_at),
|
||||
KEY idx_order_tenant_customer (tenant_id, customer_id),
|
||||
KEY idx_order_tenant_warehouse (tenant_id, destination_warehouse_id)
|
||||
) ENGINE=InnoDB COMMENT='头程订单主表:保存商户委托、目的仓、运输方式和订单当前状态';
|
||||
|
||||
@@ -231,6 +231,25 @@ paths:
|
||||
responses:
|
||||
'200': {description: 成功或业务失败,按响应体 code 判断}
|
||||
'403': {description: 需要 platform:order:create 权限}
|
||||
/api/platform/v1/orders/first-leg/tenant-statistics:
|
||||
get:
|
||||
tags: [Platform first-leg orders]
|
||||
summary: 查询客户累计及本月运单数
|
||||
description: 仅统计 submitted_at 非空的 MASTER 根运单,草稿和 CHILD 拆分单不计入;累计不受取消或驳回影响;月份按 Asia/Shanghai 半开区间计算。
|
||||
parameters:
|
||||
- {$ref: '#/components/parameters/TenantId'}
|
||||
responses:
|
||||
'200':
|
||||
description: 成功;需要 platform:order:list 权限
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
code: {type: string, example: '200'}
|
||||
data: {$ref: '#/components/schemas/TenantOrderStatisticsResponse'}
|
||||
'403': {description: 需要平台身份和 platform:order:list 权限}
|
||||
|
||||
/api/platform/v1/orders/first-leg/tenant-counts:
|
||||
get:
|
||||
summary: 批量查询客户运单数
|
||||
@@ -327,6 +346,18 @@ paths:
|
||||
'403': {description: 需要 merchant:order:view 权限}
|
||||
|
||||
components:
|
||||
schemas:
|
||||
TenantOrderStatisticsResponse:
|
||||
type: object
|
||||
required: [tenantId, totalSubmittedOrders, currentMonthSubmittedOrders, monthStart, nextMonthStart, zoneId, asOf]
|
||||
properties:
|
||||
tenantId: {type: string, pattern: '^[1-9][0-9]*$', description: Long 按字符串序列化以避免 JavaScript 精度丢失}
|
||||
totalSubmittedOrders: {type: string, pattern: '^[0-9]+$', description: Long 按字符串序列化}
|
||||
currentMonthSubmittedOrders: {type: string, pattern: '^[0-9]+$', description: Long 按字符串序列化}
|
||||
monthStart: {type: string, format: date-time}
|
||||
nextMonthStart: {type: string, format: date-time}
|
||||
zoneId: {type: string, example: Asia/Shanghai}
|
||||
asOf: {type: string, format: date-time}
|
||||
securitySchemes:
|
||||
bearerAuth:
|
||||
type: http
|
||||
|
||||
Reference in New Issue
Block a user