暂存提交

This commit is contained in:
2026-07-31 14:55:34 +08:00
parent bc3c18a6e9
commit 7a19e2f096
28 changed files with 655 additions and 46 deletions
@@ -2,10 +2,14 @@ package com.baiyue.firstleg.controller;
import com.baiyue.common.audit.BusinessAudit;
import com.baiyue.common.core.api.ApiResponse;
import com.baiyue.common.core.api.PageResult;
import com.baiyue.common.core.api.ResultCode;
import com.baiyue.common.core.exception.BizException;
import com.baiyue.common.security.AuthenticatedUser;
import com.baiyue.firstleg.dto.AssignRequest;
import com.baiyue.firstleg.dto.FulfillmentTaskQuery;
import com.baiyue.firstleg.entity.AssignmentHistoryEntity;
import com.baiyue.firstleg.entity.FulfillmentTaskEntity;
import com.baiyue.firstleg.service.FulfillmentTaskService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
@@ -13,7 +17,11 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@@ -22,11 +30,11 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* 平台端头程履约任务指派接口,在目标租户内完成任务指派或改派并记录指派历史。
* 平台端头程履约任务接口,在目标租户内提供任务查询、分配和分配历史查询。
*/
@Tag(
name = "Platform first-leg assignment / 平台头程指派",
description = "Platform APIs for assigning tenant-scoped first-leg fulfillment tasks. / 平台端按租户指派头程履约任务的接口。"
name = "平台头程履约任务",
description = "平台端按目标租户查询和分配头程履约任务。"
)
@RestController
@RequestMapping("/api/platform/v1/first-leg/tasks")
@@ -52,6 +60,7 @@ public class PlatformFulfillmentAssignmentController {
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "409", description = "Task is closed or concurrently updated / 任务已关闭或发生并发更新")
})
@PostMapping("/{taskId}/assignment")
@PreAuthorize("hasAuthority('platform:firstleg:assignment:assign')")
public ApiResponse<Void> assign(
@AuthenticationPrincipal AuthenticatedUser user,
@Parameter(description = "Target tenant ID / 目标租户 ID", example = "10001", required = true)
@@ -72,17 +81,84 @@ public class PlatformFulfillmentAssignmentController {
return ApiResponse.success(null);
}
@Operation(summary = "查询履约任务列表", description = "在指定租户内按条件分页查询头程履约任务。")
@ApiResponses({
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "查询成功"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "403", description = "无查询权限")
})
@GetMapping
@PreAuthorize("hasAuthority('platform:firstleg:task:list')")
public ApiResponse<PageResult<FulfillmentTaskEntity>> page(
@AuthenticationPrincipal AuthenticatedUser user,
@RequestParam long tenantId,
@Valid FulfillmentTaskQuery query) {
requirePlatform(user);
requireTenant(tenantId);
return ApiResponse.success(service.page(tenantId, query));
}
@Operation(summary = "查询履约任务详情", description = "查询指定租户内的头程履约任务详情。")
@ApiResponses({
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "查询成功"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "403", description = "无查看权限"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "任务不存在")
})
@GetMapping("/{taskId}")
@PreAuthorize("hasAuthority('platform:firstleg:task:view')")
public ApiResponse<FulfillmentTaskEntity> detail(
@AuthenticationPrincipal AuthenticatedUser user,
@RequestParam long tenantId,
@PathVariable long taskId) {
requirePlatform(user);
requireTenant(tenantId);
return ApiResponse.success(service.detail(tenantId, taskId));
}
@Operation(summary = "按订单查询履约任务", description = "使用订单逻辑ID查询指定租户内唯一的履约任务。")
@ApiResponses({
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "查询成功"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "403", description = "无查看权限"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "任务不存在")
})
@GetMapping("/by-order/{orderId}")
@PreAuthorize("hasAuthority('platform:firstleg:task:view')")
public ApiResponse<FulfillmentTaskEntity> byOrder(
@AuthenticationPrincipal AuthenticatedUser user,
@RequestParam long tenantId,
@PathVariable long orderId) {
requirePlatform(user);
requireTenant(tenantId);
return ApiResponse.success(service.byOrder(tenantId, orderId));
}
@Operation(summary = "查询分配历史", description = "分页查询指定履约任务不可变的分配历史。")
@ApiResponses({
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "查询成功"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "403", description = "无历史查看权限"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "任务不存在")
})
@GetMapping("/{taskId}/assignment-history")
@PreAuthorize("hasAuthority('platform:firstleg:assignment:history')")
public ApiResponse<PageResult<AssignmentHistoryEntity>> assignmentHistory(
@AuthenticationPrincipal AuthenticatedUser user,
@RequestParam long tenantId,
@PathVariable long taskId,
@RequestParam(defaultValue = "1") @Min(1) long page,
@RequestParam(defaultValue = "20") @Min(1) @Max(200) long size) {
requirePlatform(user);
requireTenant(tenantId);
return ApiResponse.success(service.assignmentHistory(tenantId, taskId, page, size));
}
private static void requirePlatform(AuthenticatedUser user) {
if (user == null || !"PLATFORM".equals(user.appCode())) {
throw new BizException(ResultCode.FORBIDDEN,
"Platform application required / 仅允许平台端访问");
throw new BizException(ResultCode.FORBIDDEN, "仅允许平台端访问");
}
}
private static void requireTenant(long tenantId) {
if (tenantId <= 0) {
throw new BizException(ResultCode.BAD_REQUEST,
"Target tenant is required / 必须指定有效目标租户");
throw new BizException(ResultCode.BAD_REQUEST, "必须指定有效目标租户");
}
}
}
@@ -1,11 +1,15 @@
package com.baiyue.firstleg.repository;
import com.baiyue.common.core.api.PageResult;
import com.baiyue.firstleg.dto.FulfillmentTaskQuery;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baiyue.firstleg.entity.AssignmentHistoryEntity;
import com.baiyue.firstleg.entity.FulfillmentTaskEntity;
import com.baiyue.firstleg.mapper.AssignmentHistoryMapper;
import com.baiyue.firstleg.mapper.FulfillmentTaskMapper;
import org.springframework.stereotype.Repository;
import org.springframework.util.StringUtils;
/**
* 头程履约任务仓储,按租户查询和更新任务,并持久化每次指派历史。
@@ -26,6 +30,31 @@ public class FulfillmentTaskRepository {
.eq(FulfillmentTaskEntity::getId, taskId));
}
public FulfillmentTaskEntity findByOrder(long tenantId, long orderId) {
return tasks.selectOne(new LambdaQueryWrapper<FulfillmentTaskEntity>()
.eq(FulfillmentTaskEntity::getTenantId, tenantId)
.eq(FulfillmentTaskEntity::getOrderId, orderId));
}
public PageResult<FulfillmentTaskEntity> page(long tenantId, FulfillmentTaskQuery query) {
LambdaQueryWrapper<FulfillmentTaskEntity> wrapper = new LambdaQueryWrapper<FulfillmentTaskEntity>()
.eq(FulfillmentTaskEntity::getTenantId, tenantId)
.eq(StringUtils.hasText(query.status()), FulfillmentTaskEntity::getStatus, query.status())
.eq(query.assigneeId() != null, FulfillmentTaskEntity::getAssigneeId, query.assigneeId())
.like(StringUtils.hasText(query.keyword()), FulfillmentTaskEntity::getOrderNo, query.keyword())
.orderByDesc(FulfillmentTaskEntity::getCreatedAt);
return PageResult.from(tasks.selectPage(new Page<>(query.page(), query.size()), wrapper));
}
public PageResult<AssignmentHistoryEntity> pageHistory(
long tenantId, long taskId, long page, long size) {
LambdaQueryWrapper<AssignmentHistoryEntity> wrapper = new LambdaQueryWrapper<AssignmentHistoryEntity>()
.eq(AssignmentHistoryEntity::getTenantId, tenantId)
.eq(AssignmentHistoryEntity::getTaskId, taskId)
.orderByDesc(AssignmentHistoryEntity::getCreatedAt);
return PageResult.from(histories.selectPage(new Page<>(page, size), wrapper));
}
public boolean update(FulfillmentTaskEntity task) {
return tasks.updateById(task) == 1;
}
@@ -2,6 +2,8 @@ package com.baiyue.firstleg.service;
import jakarta.annotation.Resource;
import com.baiyue.common.core.api.ResultCode;
import com.baiyue.common.core.api.PageResult;
import com.baiyue.firstleg.dto.FulfillmentTaskQuery;
import com.baiyue.common.core.exception.BizException;
import com.baiyue.firstleg.entity.AssignmentHistoryEntity;
import com.baiyue.firstleg.entity.FulfillmentTaskEntity;
@@ -23,11 +25,7 @@ public class FulfillmentTaskService {
@Transactional
public void assign(long tenantId, long operatorId, long taskId,
long assigneeId, String assigneeName, String reason) {
FulfillmentTaskEntity task = repository.findOwned(tenantId, taskId);
if (task == null) {
throw new BizException(ResultCode.NOT_FOUND,
"Fulfillment task not found / 头程履约任务不存在");
}
FulfillmentTaskEntity task = requireTask(tenantId, taskId);
if ("COMPLETED".equals(task.getStatus()) || "CANCELLED".equals(task.getStatus())) {
throw new BizException(ResultCode.CONFLICT,
"Closed task cannot be assigned / 已关闭任务不能再次指派");
@@ -55,4 +53,38 @@ public class FulfillmentTaskService {
history.setCreatedAt(LocalDateTime.now());
repository.insertHistory(history);
}
public PageResult<FulfillmentTaskEntity> page(long tenantId, FulfillmentTaskQuery query) {
return repository.page(tenantId, query);
}
public FulfillmentTaskEntity detail(long tenantId, long taskId) {
return requireTask(tenantId, taskId);
}
public FulfillmentTaskEntity byOrder(long tenantId, long orderId) {
FulfillmentTaskEntity task = repository.findByOrder(tenantId, orderId);
if (task == null) {
throw notFound();
}
return task;
}
public PageResult<AssignmentHistoryEntity> assignmentHistory(
long tenantId, long taskId, long page, long size) {
requireTask(tenantId, taskId);
return repository.pageHistory(tenantId, taskId, page, size);
}
private FulfillmentTaskEntity requireTask(long tenantId, long taskId) {
FulfillmentTaskEntity task = repository.findOwned(tenantId, taskId);
if (task == null) {
throw notFound();
}
return task;
}
private BizException notFound() {
return new BizException(ResultCode.NOT_FOUND, "头程履约任务不存在");
}
}
@@ -23,6 +23,7 @@ CREATE TABLE IF NOT EXISTS fl_fulfillment_task (
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
PRIMARY KEY (id),
UNIQUE KEY uk_task_tenant_order (tenant_id, order_id),
KEY idx_task_tenant_created (tenant_id, created_at),
KEY idx_task_assignee_status (tenant_id, assignee_id, status, created_at)
) ENGINE=InnoDB COMMENT='头程履约任务表:接收订单事件后形成独立履约任务,管理指派、执行和完成状态';