fix(order): remove manual completion bypass of fulfillment gate
This commit is contained in:
@@ -6067,7 +6067,6 @@ INSERT INTO iam_permission (id, tenant_id, app_code, permission_type, parent_id,
|
|||||||
INSERT INTO iam_permission (id, tenant_id, app_code, permission_type, parent_id, permission_code, permission_name, route_path, http_method, sort_no, status, built_in, created_at, updated_at, deleted) VALUES (5102012,0,'PLATFORM','BUTTON',5102010,'platform:order:reject','订单驳回',NULL,NULL,20,'ACTIVE',1,CURRENT_TIMESTAMP(3),CURRENT_TIMESTAMP(3),0);
|
INSERT INTO iam_permission (id, tenant_id, app_code, permission_type, parent_id, permission_code, permission_name, route_path, http_method, sort_no, status, built_in, created_at, updated_at, deleted) VALUES (5102012,0,'PLATFORM','BUTTON',5102010,'platform:order:reject','订单驳回',NULL,NULL,20,'ACTIVE',1,CURRENT_TIMESTAMP(3),CURRENT_TIMESTAMP(3),0);
|
||||||
INSERT INTO iam_permission (id, tenant_id, app_code, permission_type, parent_id, permission_code, permission_name, route_path, http_method, sort_no, status, built_in, created_at, updated_at, deleted) VALUES (5102013,0,'PLATFORM','BUTTON',5102010,'platform:order:accept','订单受理',NULL,NULL,30,'ACTIVE',1,CURRENT_TIMESTAMP(3),CURRENT_TIMESTAMP(3),0);
|
INSERT INTO iam_permission (id, tenant_id, app_code, permission_type, parent_id, permission_code, permission_name, route_path, http_method, sort_no, status, built_in, created_at, updated_at, deleted) VALUES (5102013,0,'PLATFORM','BUTTON',5102010,'platform:order:accept','订单受理',NULL,NULL,30,'ACTIVE',1,CURRENT_TIMESTAMP(3),CURRENT_TIMESTAMP(3),0);
|
||||||
INSERT INTO iam_permission (id, tenant_id, app_code, permission_type, parent_id, permission_code, permission_name, route_path, http_method, sort_no, status, built_in, created_at, updated_at, deleted) VALUES (5102014,0,'PLATFORM','BUTTON',5102010,'platform:order:process','开始处理',NULL,NULL,40,'ACTIVE',1,CURRENT_TIMESTAMP(3),CURRENT_TIMESTAMP(3),0);
|
INSERT INTO iam_permission (id, tenant_id, app_code, permission_type, parent_id, permission_code, permission_name, route_path, http_method, sort_no, status, built_in, created_at, updated_at, deleted) VALUES (5102014,0,'PLATFORM','BUTTON',5102010,'platform:order:process','开始处理',NULL,NULL,40,'ACTIVE',1,CURRENT_TIMESTAMP(3),CURRENT_TIMESTAMP(3),0);
|
||||||
INSERT INTO iam_permission (id, tenant_id, app_code, permission_type, parent_id, permission_code, permission_name, route_path, http_method, sort_no, status, built_in, created_at, updated_at, deleted) VALUES (5102015,0,'PLATFORM','BUTTON',5102010,'platform:order:complete','完成订单',NULL,NULL,50,'ACTIVE',1,CURRENT_TIMESTAMP(3),CURRENT_TIMESTAMP(3),0);
|
|
||||||
INSERT INTO iam_permission (id, tenant_id, app_code, permission_type, parent_id, permission_code, permission_name, route_path, http_method, sort_no, status, built_in, created_at, updated_at, deleted) VALUES (5102016,0,'PLATFORM','BUTTON',5102010,'platform:order:cancel','取消订单',NULL,NULL,60,'ACTIVE',1,CURRENT_TIMESTAMP(3),CURRENT_TIMESTAMP(3),0);
|
INSERT INTO iam_permission (id, tenant_id, app_code, permission_type, parent_id, permission_code, permission_name, route_path, http_method, sort_no, status, built_in, created_at, updated_at, deleted) VALUES (5102016,0,'PLATFORM','BUTTON',5102010,'platform:order:cancel','取消订单',NULL,NULL,60,'ACTIVE',1,CURRENT_TIMESTAMP(3),CURRENT_TIMESTAMP(3),0);
|
||||||
INSERT INTO iam_permission (id, tenant_id, app_code, permission_type, parent_id, permission_code, permission_name, route_path, http_method, sort_no, status, built_in, created_at, updated_at, deleted) VALUES (5102017,0,'PLATFORM','BUTTON',5102010,'platform:order:note','内部备注',NULL,NULL,70,'ACTIVE',1,CURRENT_TIMESTAMP(3),CURRENT_TIMESTAMP(3),0);
|
INSERT INTO iam_permission (id, tenant_id, app_code, permission_type, parent_id, permission_code, permission_name, route_path, http_method, sort_no, status, built_in, created_at, updated_at, deleted) VALUES (5102017,0,'PLATFORM','BUTTON',5102010,'platform:order:note','内部备注',NULL,NULL,70,'ACTIVE',1,CURRENT_TIMESTAMP(3),CURRENT_TIMESTAMP(3),0);
|
||||||
INSERT INTO iam_permission (id, tenant_id, app_code, permission_type, parent_id, permission_code, permission_name, route_path, http_method, sort_no, status, built_in, created_at, updated_at, deleted) VALUES (5102018,0,'PLATFORM','BUTTON',5102010,'platform:order:fulfillment','登记履约快递',NULL,NULL,80,'ACTIVE',1,CURRENT_TIMESTAMP(3),CURRENT_TIMESTAMP(3),0);
|
INSERT INTO iam_permission (id, tenant_id, app_code, permission_type, parent_id, permission_code, permission_name, route_path, http_method, sort_no, status, built_in, created_at, updated_at, deleted) VALUES (5102018,0,'PLATFORM','BUTTON',5102010,'platform:order:fulfillment','登记履约快递',NULL,NULL,80,'ACTIVE',1,CURRENT_TIMESTAMP(3),CURRENT_TIMESTAMP(3),0);
|
||||||
@@ -6449,7 +6448,6 @@ INSERT INTO iam_role_permission (role_id, permission_id, tenant_id, created_at)
|
|||||||
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id, created_at) VALUES (4000001,5102012,0,CURRENT_TIMESTAMP(3));
|
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id, created_at) VALUES (4000001,5102012,0,CURRENT_TIMESTAMP(3));
|
||||||
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id, created_at) VALUES (4000001,5102013,0,CURRENT_TIMESTAMP(3));
|
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id, created_at) VALUES (4000001,5102013,0,CURRENT_TIMESTAMP(3));
|
||||||
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id, created_at) VALUES (4000001,5102014,0,CURRENT_TIMESTAMP(3));
|
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id, created_at) VALUES (4000001,5102014,0,CURRENT_TIMESTAMP(3));
|
||||||
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id, created_at) VALUES (4000001,5102015,0,CURRENT_TIMESTAMP(3));
|
|
||||||
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id, created_at) VALUES (4000001,5102016,0,CURRENT_TIMESTAMP(3));
|
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id, created_at) VALUES (4000001,5102016,0,CURRENT_TIMESTAMP(3));
|
||||||
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id, created_at) VALUES (4000001,5102017,0,CURRENT_TIMESTAMP(3));
|
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id, created_at) VALUES (4000001,5102017,0,CURRENT_TIMESTAMP(3));
|
||||||
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id, created_at) VALUES (4000001,5102018,0,CURRENT_TIMESTAMP(3));
|
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id, created_at) VALUES (4000001,5102018,0,CURRENT_TIMESTAMP(3));
|
||||||
@@ -6730,7 +6728,6 @@ INSERT INTO iam_role_permission (role_id, permission_id, tenant_id, created_at)
|
|||||||
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id, created_at) VALUES (4000003,5102012,0,CURRENT_TIMESTAMP(3));
|
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id, created_at) VALUES (4000003,5102012,0,CURRENT_TIMESTAMP(3));
|
||||||
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id, created_at) VALUES (4000003,5102013,0,CURRENT_TIMESTAMP(3));
|
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id, created_at) VALUES (4000003,5102013,0,CURRENT_TIMESTAMP(3));
|
||||||
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id, created_at) VALUES (4000003,5102014,0,CURRENT_TIMESTAMP(3));
|
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id, created_at) VALUES (4000003,5102014,0,CURRENT_TIMESTAMP(3));
|
||||||
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id, created_at) VALUES (4000003,5102015,0,CURRENT_TIMESTAMP(3));
|
|
||||||
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id, created_at) VALUES (4000003,5102016,0,CURRENT_TIMESTAMP(3));
|
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id, created_at) VALUES (4000003,5102016,0,CURRENT_TIMESTAMP(3));
|
||||||
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id, created_at) VALUES (4000003,5102017,0,CURRENT_TIMESTAMP(3));
|
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id, created_at) VALUES (4000003,5102017,0,CURRENT_TIMESTAMP(3));
|
||||||
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id, created_at) VALUES (4000003,5102018,0,CURRENT_TIMESTAMP(3));
|
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id, created_at) VALUES (4000003,5102018,0,CURRENT_TIMESTAMP(3));
|
||||||
|
|||||||
@@ -101,7 +101,6 @@ VALUES
|
|||||||
(5102012, 0, 'PLATFORM', 'BUTTON', 5102010, 'platform:order:reject', '订单驳回', NULL, NULL, 20, 'ACTIVE', 0),
|
(5102012, 0, 'PLATFORM', 'BUTTON', 5102010, 'platform:order:reject', '订单驳回', NULL, NULL, 20, 'ACTIVE', 0),
|
||||||
(5102013, 0, 'PLATFORM', 'BUTTON', 5102010, 'platform:order:accept', '订单受理', NULL, NULL, 30, 'ACTIVE', 0),
|
(5102013, 0, 'PLATFORM', 'BUTTON', 5102010, 'platform:order:accept', '订单受理', NULL, NULL, 30, 'ACTIVE', 0),
|
||||||
(5102014, 0, 'PLATFORM', 'BUTTON', 5102010, 'platform:order:process', '开始处理', NULL, NULL, 40, 'ACTIVE', 0),
|
(5102014, 0, 'PLATFORM', 'BUTTON', 5102010, 'platform:order:process', '开始处理', NULL, NULL, 40, 'ACTIVE', 0),
|
||||||
(5102015, 0, 'PLATFORM', 'BUTTON', 5102010, 'platform:order:complete', '完成订单', NULL, NULL, 50, 'ACTIVE', 0),
|
|
||||||
(5102016, 0, 'PLATFORM', 'BUTTON', 5102010, 'platform:order:cancel', '取消订单', NULL, NULL, 60, 'ACTIVE', 0),
|
(5102016, 0, 'PLATFORM', 'BUTTON', 5102010, 'platform:order:cancel', '取消订单', NULL, NULL, 60, 'ACTIVE', 0),
|
||||||
(5102017, 0, 'PLATFORM', 'BUTTON', 5102010, 'platform:order:note', '内部备注', NULL, NULL, 70, 'ACTIVE', 0),
|
(5102017, 0, 'PLATFORM', 'BUTTON', 5102010, 'platform:order:note', '内部备注', NULL, NULL, 70, 'ACTIVE', 0),
|
||||||
(5102018, 0, 'PLATFORM', 'BUTTON', 5102010, 'platform:order:fulfillment', '登记履约快递', NULL, NULL, 80, 'ACTIVE', 0),
|
(5102018, 0, 'PLATFORM', 'BUTTON', 5102010, 'platform:order:fulfillment', '登记履约快递', NULL, NULL, 80, 'ACTIVE', 0),
|
||||||
|
|||||||
@@ -291,16 +291,6 @@ public class PlatformFirstLegOrderController {
|
|||||||
return transit(user, tenantId, id, OrderStatus.PROCESSING, null);
|
return transit(user, tenantId, id, OrderStatus.PROCESSING, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@BusinessAudit(event = "ORDER_COMPLETE", resourceType = "FIRST_LEG_ORDER", resourceId = "#id", description = "Complete order / 完成订单")
|
|
||||||
@Operation(summary = "Complete / 完成订单", description = "Marks a processing order as completed and publishes the integration event. / 将处理中订单标记为完成,并发布集成事件。")
|
|
||||||
@PostMapping("/{id}/complete")
|
|
||||||
@PreAuthorize("hasAuthority('platform:order:complete')")
|
|
||||||
public ApiResponse<Void> complete(@AuthenticationPrincipal AuthenticatedUser user,
|
|
||||||
@Parameter(description = "Target tenant ID / 目标租户 ID", example = "10001", required = true) @RequestParam long tenantId,
|
|
||||||
@Parameter(description = "Order ID / 订单 ID", example = "190000000000000001", required = true) @PathVariable long id) {
|
|
||||||
return transit(user, tenantId, id, OrderStatus.COMPLETED, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 仅履约权限可登记快递信息,商户端保持只读。 */
|
/** 仅履约权限可登记快递信息,商户端保持只读。 */
|
||||||
@PutMapping("/{id}/express")
|
@PutMapping("/{id}/express")
|
||||||
@PreAuthorize("hasAuthority('platform:order:fulfillment')")
|
@PreAuthorize("hasAuthority('platform:order:fulfillment')")
|
||||||
|
|||||||
@@ -174,6 +174,9 @@ public class OrderApplicationService {
|
|||||||
@Transactional
|
@Transactional
|
||||||
public void transit(long tenantId, long userId, String username, long id, OrderStatus target, String reason,
|
public void transit(long tenantId, long userId, String username, long id, OrderStatus target, String reason,
|
||||||
Integer expectedVersion) {
|
Integer expectedVersion) {
|
||||||
|
if (target == OrderStatus.COMPLETED) {
|
||||||
|
throw new BizException(ResultCode.CONFLICT, "订单完成须由真实签收和全部有效履约分片完成后自动确认");
|
||||||
|
}
|
||||||
if (target == OrderStatus.CANCELLED) fulfillmentCompletion.lockCancellationRoot(tenantId, id);
|
if (target == OrderStatus.CANCELLED) fulfillmentCompletion.lockCancellationRoot(tenantId, id);
|
||||||
OrderEntity order = require(tenantId, id);
|
OrderEntity order = require(tenantId, id);
|
||||||
if (expectedVersion != null && !expectedVersion.equals(order.getVersion())) {
|
if (expectedVersion != null && !expectedVersion.equals(order.getVersion())) {
|
||||||
@@ -205,7 +208,6 @@ public class OrderApplicationService {
|
|||||||
requirePrintableInboundCode(order);
|
requirePrintableInboundCode(order);
|
||||||
order.setSubmittedAt(LocalDateTime.now());
|
order.setSubmittedAt(LocalDateTime.now());
|
||||||
}
|
}
|
||||||
if (target == OrderStatus.COMPLETED) order.setCompletedAt(LocalDateTime.now());
|
|
||||||
if (repository.saveOrder(tenantId, order) != 1) throw new BizException("Concurrent order update");
|
if (repository.saveOrder(tenantId, order) != 1) throw new BizException("Concurrent order update");
|
||||||
record(tenantId, id, from, target, target.name(), reason, userId, username);
|
record(tenantId, id, from, target, target.name(), reason, userId, username);
|
||||||
if (target == OrderStatus.SUBMITTED) prepareSubmittedInbound(order);
|
if (target == OrderStatus.SUBMITTED) prepareSubmittedInbound(order);
|
||||||
@@ -585,7 +587,6 @@ public class OrderApplicationService {
|
|||||||
case SUBMITTED -> target == OrderStatus.APPROVED || target == OrderStatus.REJECTED || target == OrderStatus.CANCELLED;
|
case SUBMITTED -> target == OrderStatus.APPROVED || target == OrderStatus.REJECTED || target == OrderStatus.CANCELLED;
|
||||||
case APPROVED -> target == OrderStatus.ACCEPTED || target == OrderStatus.CANCELLED;
|
case APPROVED -> target == OrderStatus.ACCEPTED || target == OrderStatus.CANCELLED;
|
||||||
case ACCEPTED -> target == OrderStatus.PROCESSING || target == OrderStatus.CANCELLED;
|
case ACCEPTED -> target == OrderStatus.PROCESSING || target == OrderStatus.CANCELLED;
|
||||||
case PROCESSING -> target == OrderStatus.COMPLETED;
|
|
||||||
case REJECTED -> target == OrderStatus.DRAFT || target == OrderStatus.CANCELLED;
|
case REJECTED -> target == OrderStatus.DRAFT || target == OrderStatus.CANCELLED;
|
||||||
default -> false;
|
default -> false;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ paths:
|
|||||||
已完成才完成根订单;来源子单独立完成并在根锁下重新评估聚合。明确 CANCELLED 子单
|
已完成才完成根订单;来源子单独立完成并在根锁下重新评估聚合。明确 CANCELLED 子单
|
||||||
不参与完成门禁,其他未完成状态均阻断。重复事实不得重复写历史。响应明确返回来源状态
|
不参与完成门禁,其他未完成状态均阻断。重复事实不得重复写历史。响应明确返回来源状态
|
||||||
status 和履约根 rootOrderId/rootStatus;根尚未聚合完成时允许保留其原状态。
|
status 和履约根 rootOrderId/rootStatus;根尚未聚合完成时允许保留其原状态。
|
||||||
|
订单完成仅由此可信履约回写和取消后聚合门禁产生,普通订单状态流转禁止目标 COMPLETED,
|
||||||
|
平台不提供手工完成订单接口。
|
||||||
parameters:
|
parameters:
|
||||||
- name: X-Internal-Service-Token
|
- name: X-Internal-Service-Token
|
||||||
in: header
|
in: header
|
||||||
|
|||||||
@@ -49,6 +49,32 @@ class OrderApplicationServiceStateTest {
|
|||||||
static class TestConfig {
|
static class TestConfig {
|
||||||
@Bean ObjectMapper objectMapper() { return new ObjectMapper().findAndRegisterModules(); }
|
@Bean ObjectMapper objectMapper() { return new ObjectMapper().findAndRegisterModules(); }
|
||||||
}
|
}
|
||||||
|
@Test
|
||||||
|
void manualCompletionRejectsEveryStateAndRoleWithoutWritingFulfillmentFacts() {
|
||||||
|
for (String role : List.of("MASTER", "CHILD")) {
|
||||||
|
for (OrderStatus status : OrderStatus.values()) {
|
||||||
|
OrderRepository repository = mock(OrderRepository.class);
|
||||||
|
OrderEntity order = persistedOrder(status);
|
||||||
|
order.setOrderRole(role);
|
||||||
|
when(repository.findOwned(10001L, 1L)).thenReturn(order);
|
||||||
|
clearInvocations(repository);
|
||||||
|
OrderFulfillmentCompletionService completion = mock(OrderFulfillmentCompletionService.class);
|
||||||
|
var app = service(repository, mock(OrderEligibilityGuard.class), mock(OrderMasterDataGuard.class));
|
||||||
|
ReflectionTestUtils.setField(app, "fulfillmentCompletion", completion);
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> app.transit(10001, 2, "u", 1, OrderStatus.COMPLETED, null))
|
||||||
|
.isInstanceOf(BizException.class)
|
||||||
|
.hasMessageContaining("真实签收");
|
||||||
|
assertThatThrownBy(() -> app.transit(10001, 2, "u", 1, OrderStatus.COMPLETED, null, 0))
|
||||||
|
.isInstanceOf(BizException.class)
|
||||||
|
.hasMessageContaining("真实签收");
|
||||||
|
verifyNoInteractions(repository, completion);
|
||||||
|
assertThat(order.getStatus()).isEqualTo(status);
|
||||||
|
assertThat(order.getCompletedAt()).isNull();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void cancellationLocksRootBeforeSourceWriteAndThenAggregates() {
|
void cancellationLocksRootBeforeSourceWriteAndThenAggregates() {
|
||||||
OrderRepository repository = mock(OrderRepository.class);
|
OrderRepository repository = mock(OrderRepository.class);
|
||||||
|
|||||||
Reference in New Issue
Block a user