feat: add editable order collaboration remarks

This commit is contained in:
2026-09-14 16:53:09 +08:00
parent 26c37deb55
commit e0af45db02
32 changed files with 356 additions and 175 deletions
@@ -57,14 +57,11 @@ public class InternalOrderCargoController {
}
var order = orders.findOwned(tenantId, orderId);
if (order == null) throw new BizException(ResultCode.NOT_FOUND, "订单不存在或不属于当前客户");
String latestInternalNote = orders.findNotes(tenantId, orderId).stream()
.map(com.baiyue.order.entity.OrderInternalNoteEntity::getContent)
.filter(java.util.Objects::nonNull).map(String::trim).filter(value -> !value.isEmpty())
.findFirst().orElse(null);
return ApiResponse.success(new CargoBinding(cargo.getId(), orderId, cargo.getItemNo(),
cargo.getCargoName(), cargo.getPackageCount(), cargo.getGrossWeightKg(), cargo.getVolumeCbm(),
cargo.getSkuNo(), cargo.getCargoNameEn(), cargo.getUnitsPerPackage(), cargo.getTotalQuantity(),
cargo.getNetWeightKg(), cargo.getCargoRemark(), order.getCustomerRemark(), latestInternalNote));
cargo.getNetWeightKg(), cargo.getCargoRemark(), order.getCustomerRemark(), order.getInternalRemark(),
order.getWarehouseRemark(), order.getCustomerServiceRemark()));
}
@GetMapping("/{tenantId}/{orderId}")
@@ -167,12 +164,13 @@ public class InternalOrderCargoController {
java.math.BigDecimal originalVolumeCbm, String skuNo, String cargoNameEn,
Integer unitsPerPackage, Integer totalQuantity,
java.math.BigDecimal netWeightKg, String cargoRemark,
String customerRemark, String latestInternalNote) {
String customerRemark, String latestInternalNote,
String warehouseRemark, String customerServiceRemark) {
public CargoBinding(Long cargoId, Long orderId, Integer itemNo, String cargoName,
Integer originalPackageCount, java.math.BigDecimal originalGrossWeightKg,
java.math.BigDecimal originalVolumeCbm) {
this(cargoId, orderId, itemNo, cargoName, originalPackageCount,
originalGrossWeightKg, originalVolumeCbm, null, null, null, null, null, null, null, null);
originalGrossWeightKg, originalVolumeCbm, null, null, null, null, null, null, null, null, null, null);
}
public CargoBinding(Long cargoId, Long orderId, Integer itemNo, String cargoName,
Integer originalPackageCount, java.math.BigDecimal originalGrossWeightKg,
@@ -181,7 +179,7 @@ public class InternalOrderCargoController {
String cargoRemark) {
this(cargoId, orderId, itemNo, cargoName, originalPackageCount, originalGrossWeightKg,
originalVolumeCbm, skuNo, cargoNameEn, unitsPerPackage, totalQuantity, netWeightKg,
cargoRemark, null, null);
cargoRemark, null, null, null, null);
}
}
@@ -282,23 +282,6 @@ public class PlatformFirstLegOrderController {
return transit(user, tenantId, id, OrderStatus.COMPLETED, null);
}
@BusinessAudit(event = "ORDER_INTERNAL_NOTE", resourceType = "FIRST_LEG_ORDER", resourceId = "#id", description = "Add internal note / 添加内部备注")
@Operation(summary = "Add internal note / 添加内部备注", description = "Adds a platform-only internal note to the target tenant order. / 为目标租户订单添加仅平台可见的内部备注。")
@ApiResponses({
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "Note added / 备注添加成功"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "403", description = "Platform scope required / 需要平台端权限"),
})
@PostMapping("/{id}/internal-notes")
@PreAuthorize("hasAuthority('platform:order:note')")
public ApiResponse<Long> note(@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,
@Valid @RequestBody OrderRequests.NoteRequest request) {
return ApiResponse.success(service.addNote(
OrderScopeGuard.platform(user, tenantId), user.userId(), id, request.content()
));
}
/** 仅履约权限可登记快递信息,商户端保持只读。 */
@PutMapping("/{id}/express")
@PreAuthorize("hasAuthority('platform:order:fulfillment')")
@@ -112,16 +112,16 @@ public class PlatformUnifiedOrderController {
return ApiResponse.success(splitService.containerSlices(containerBookingId, tenantId));
}
@PostMapping("/{orderId}/internal-notes")
@BusinessAudit(event = "ORDER_INTERNAL_NOTE", resourceType = "FIRST_LEG_ORDER", resourceId = "#orderId", description = "Add internal note / 添加内部备注")
@PutMapping("/{orderId}/remarks")
@BusinessAudit(event = "ORDER_REMARKS_UPDATE", resourceType = "FIRST_LEG_ORDER", resourceId = "#orderId", description = "更新订单协作备注")
@PreAuthorize("hasAuthority('platform:order:note')")
@Operation(summary = "添加订单内部备注", description = "添加仅平台内部可见的订单备注。")
public ApiResponse<Long> addInternalNote(
@Operation(summary = "更新订单协作备注", description = "客户备注只读;平台可更新内部备注、仓库备注和客服备注。")
public ApiResponse<OrderRequests.RemarksUpdateResponse> updateRemarks(
@AuthenticationPrincipal AuthenticatedUser user,
@RequestParam long tenantId,
@PathVariable long orderId,
@Valid @RequestBody OrderRequests.NoteRequest request) {
@Valid @RequestBody OrderRequests.RemarksUpdateRequest request) {
long scopedTenant = OrderScopeGuard.platform(user, tenantId);
return ApiResponse.success(orderService.addNote(scopedTenant, user.userId(), orderId, request.content()));
return ApiResponse.success(orderService.updateRemarks(scopedTenant, user.userId(), orderId, request));
}
}
@@ -316,12 +316,17 @@ public final class OrderRequests {
}
}
@Schema(name = "OrderInternalNoteRequest", description = "Platform internal order note / 平台内部订单备注")
public record NoteRequest(
@Schema(description = "Internal note content / 内部备注内容", example = "Customer confirmed the shipping schedule / 客户已确认船期", maxLength = 2000, requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank @Size(max = 2000) String content
) {
}
@Schema(name = "OrderRemarksUpdateRequest", description = "平台订单协作备注更新")
public record RemarksUpdateRequest(
@NotNull @Min(0) Integer expectedVersion,
@Size(max = 1000) String internalRemark,
@Size(max = 1000) String warehouseRemark,
@Size(max = 1000) String customerServiceRemark
) { }
public record RemarksUpdateResponse(Integer version, String customerRemark,
String internalRemark, String warehouseRemark,
String customerServiceRemark) { }
public record ExpressRequest(
@NotBlank @Size(max = 64)
@@ -35,7 +35,7 @@ public final class UnifiedOrderResponse {
MeasurementSummary measurement, PreloadSummary preload,
LoadingSummary loading, DeliverySummary delivery, TransportSummary transport,
CoLoadGroupSummary coLoadGroup,
String customerRemark, String latestInternalNote,
String customerRemark, String latestInternalNote, String warehouseRemark, String customerServiceRemark,
LocalDateTime createdAt, LocalDateTime updatedAt, LocalDateTime requestedPickupAt
) {}
@@ -35,6 +35,9 @@ public class OrderEntity extends BaseEntity {
private LocalDateTime expectedArrivalAt;
private BigDecimal estimatedVolumeCbm;
private String customerRemark;
private String internalRemark;
private String warehouseRemark;
private String customerServiceRemark;
private String pickupMethod;
private LocalDateTime requestedPickupAt;
private Long receivingWarehouseId;
@@ -117,6 +117,21 @@ public class OrderRepository {
.set(OrderCargoEntity::getUpdatedAt, cargo.getUpdatedAt())
.setSql("version = version + 1"));
}
public int updateRemarks(long tenantId, long orderId, int expectedVersion, long operatorId,
String internalRemark, String warehouseRemark, String customerServiceRemark) {
return orderMapper.update(null, new LambdaUpdateWrapper<OrderEntity>()
.eq(OrderEntity::getTenantId, tenantId)
.eq(OrderEntity::getId, orderId)
.eq(OrderEntity::getVersion, expectedVersion)
.eq(OrderEntity::getDeleted, false)
.set(OrderEntity::getInternalRemark, internalRemark)
.set(OrderEntity::getWarehouseRemark, warehouseRemark)
.set(OrderEntity::getCustomerServiceRemark, customerServiceRemark)
.set(OrderEntity::getUpdatedBy, operatorId)
.set(OrderEntity::getUpdatedAt, LocalDateTime.now())
.setSql("version = version + 1"));
}
public void insertCargo(OrderCargoEntity cargo) { cargoMapper.insert(cargo); }
public void insertParty(OrderPartyEntity party) { partyMapper.insert(party); }
public void insertNote(OrderInternalNoteEntity note) { noteMapper.insert(note); }
@@ -268,11 +268,29 @@ public class OrderApplicationService {
return new OrderDetailResponse(order, repository.findCargo(tenantId, id), repository.findParties(tenantId, id),
List.of(), repository.findHistory(tenantId, id));
}
public Long addNote(long tenantId, long userId, long id, String content) {
require(tenantId, id);
OrderInternalNoteEntity note = new OrderInternalNoteEntity();
note.setTenantId(tenantId); note.setOrderId(id); note.setContent(content);
note.setCreatedBy(userId); note.setUpdatedBy(userId); repository.insertNote(note); return note.getId();
@Transactional
public OrderRequests.RemarksUpdateResponse updateRemarks(long tenantId, long userId, long id,
OrderRequests.RemarksUpdateRequest request) {
OrderEntity order = require(tenantId, id);
String internalRemark = normalizeRemark(request.internalRemark());
String warehouseRemark = normalizeRemark(request.warehouseRemark());
String customerServiceRemark = normalizeRemark(request.customerServiceRemark());
if (repository.updateRemarks(tenantId, id, request.expectedVersion(), userId,
internalRemark, warehouseRemark, customerServiceRemark) != 1) {
throw new BizException(com.baiyue.common.core.api.ResultCode.CONFLICT, "订单备注已被其他人修改,请刷新后重试");
}
if (!java.util.Objects.equals(order.getInternalRemark(), internalRemark) && internalRemark != null) {
OrderInternalNoteEntity note = new OrderInternalNoteEntity();
note.setTenantId(tenantId); note.setOrderId(id); note.setContent(internalRemark);
note.setCreatedBy(userId); note.setUpdatedBy(userId); repository.insertNote(note);
}
return new OrderRequests.RemarksUpdateResponse(order.getVersion() + 1, order.getCustomerRemark(),
internalRemark, warehouseRemark, customerServiceRemark);
}
private static String normalizeRemark(String value) {
if (value == null || value.trim().isEmpty()) return null;
return value.trim();
}
@Transactional
@@ -11,7 +11,6 @@ import com.baiyue.order.dto.UnifiedOrderQuery;
import com.baiyue.order.dto.UnifiedOrderResponse;
import com.baiyue.order.entity.OrderEntity;
import com.baiyue.order.entity.OrderCargoEntity;
import com.baiyue.order.entity.OrderInternalNoteEntity;
import com.baiyue.order.entity.OrderStatus;
import com.baiyue.order.repository.OrderRepository;
import feign.FeignException;
@@ -66,11 +65,10 @@ public class UnifiedOrderQueryService {
List<OrderEntity> selected = orderNos.stream().map(orderByNo::get).filter(Objects::nonNull).toList();
List<Long> orderIds = selected.stream().map(OrderEntity::getId).toList();
Map<Long, FirstlegUnifiedOrderClient.Summary> inboundByOrder = summaries(null, orderIds);
Map<Long, String> latestNotes = latestNotes(null, orderIds);
Map<Long, CargoSummary> cargoSummaries = cargoSummaries(null, selected);
Map<Long, Integer> childCounts = childCounts(selected);
return selected.stream().map(order -> row(order, inboundByOrder.get(order.getId()),
latestNotes.get(order.getId()), childCounts, cargoSummaries.get(order.getId()), null)).toList();
order.getInternalRemark(), childCounts, cargoSummaries.get(order.getId()), null)).toList();
}
private PageResult<UnifiedOrderResponse.Row> page(Long tenantId, UnifiedOrderQuery query,
@@ -89,13 +87,12 @@ public class UnifiedOrderQueryService {
Map<Long, FirstlegUnifiedOrderClient.Summary> inboundByOrder = "PENDING_REVIEW".equals(query.mainStatus())
? optionalSummaries(tenantId, sourceOrderIds)
: summaries(tenantId, sourceOrderIds);
Map<Long, String> latestNotes = latestNotes(tenantId, source.records().stream().map(OrderEntity::getId).toList());
Map<Long, CargoSummary> cargoSummaries = cargoSummaries(tenantId, source.records());
Map<Long, Integer> childCounts = childCounts(source.records());
Map<Long, UnifiedOrderResponse.CoLoadGroupSummary> coLoadByOrder = coLoadGroups(
source.records().stream().map(OrderEntity::getId).toList(), includeCoLoadGroups);
List<UnifiedOrderResponse.Row> rows = source.records().stream()
.map(order -> row(order, inboundByOrder.get(order.getId()), latestNotes.get(order.getId()),
.map(order -> row(order, inboundByOrder.get(order.getId()), order.getInternalRemark(),
childCounts, cargoSummaries.get(order.getId()), coLoadByOrder.get(order.getId()))).toList();
return new PageResult<>(rows, source.page(), source.size(), source.total(), source.pages());
}
@@ -117,7 +114,7 @@ public class UnifiedOrderQueryService {
orders.findHistory(tenantId, orderId));
FirstlegUnifiedOrderClient.Detail inbound = order.getStatus() == OrderStatus.SUBMITTED
? optionalFirstlegDetail(tenantId, orderId) : firstlegDetail(tenantId, orderId);
String latestNote = orderDetail.internalNotes().isEmpty() ? null : orderDetail.internalNotes().get(0).getContent();
String latestNote = order.getInternalRemark();
String creatorName = orderDetail.statusHistory().stream()
.filter(item -> "CREATE".equals(item.getAction()))
.map(item -> item.getOperatorName())
@@ -166,14 +163,13 @@ public class UnifiedOrderQueryService {
List<Long> orderIds = summaries.stream().map(item -> item.inbound().orderId()).toList();
Map<Long, OrderEntity> orderById = orders.findByIds(tenantId, orderIds).stream()
.collect(Collectors.toMap(OrderEntity::getId, Function.identity(), (left, right) -> left));
Map<Long, String> latestNotes = latestNotes(tenantId, orderIds);
Map<Long, CargoSummary> cargoSummaries = cargoSummaries(tenantId, List.copyOf(orderById.values()));
Map<Long, Integer> childCounts = childCounts(List.copyOf(orderById.values()));
Map<Long, UnifiedOrderResponse.CoLoadGroupSummary> coLoadByOrder = coLoadGroups(
orderIds, includeCoLoadGroups);
List<UnifiedOrderResponse.Row> records = summaries.stream()
.map(summary -> row(summary, orderById.get(summary.inbound().orderId()),
latestNotes.get(summary.inbound().orderId()), childCounts,
orderById.get(summary.inbound().orderId()) == null ? null : orderById.get(summary.inbound().orderId()).getInternalRemark(), childCounts,
cargoSummaries.get(summary.inbound().orderId()),
coLoadByOrder.get(summary.inbound().orderId())))
.toList();
@@ -233,11 +229,6 @@ public class UnifiedOrderQueryService {
return summary;
}
private Map<Long, String> latestNotes(Long tenantId, List<Long> orderIds) {
return orders.findNotesByOrderIds(tenantId, orderIds).stream().collect(Collectors.toMap(
OrderInternalNoteEntity::getOrderId, OrderInternalNoteEntity::getContent, (latest, ignored) -> latest));
}
private Map<Long, CargoSummary> cargoSummaries(Long tenantId, List<OrderEntity> pageOrders) {
if (pageOrders.isEmpty()) return Map.of();
List<Long> orderIds = pageOrders.stream().map(OrderEntity::getId).toList();
@@ -338,7 +329,7 @@ public class UnifiedOrderQueryService {
null, null, null, null, null, null, null, order.getExpressCompany(), order.getExpressTrackingNo(),
StringUtils.hasText(creatorName) ? creatorName : string(order.getCreatedBy()), skuNos(cargo), totalQuantity(cargo), shipmentId(order, cargo), referenceId(order, cargo),
null, null, null, null, null, coLoadGroup,
order.getCustomerRemark(), latestInternalNote,
order.getCustomerRemark(), latestInternalNote, order.getWarehouseRemark(), order.getCustomerServiceRemark(),
order.getCreatedAt(), order.getUpdatedAt(), order.getRequestedPickupAt());
}
FirstlegUnifiedOrderClient.Inbound inbound = summary.inbound();
@@ -365,6 +356,7 @@ public class UnifiedOrderQueryService {
StringUtils.hasText(creatorName) ? creatorName : inbound.createdByName(), skuNos(cargo), totalQuantity(cargo), shipmentId(order, cargo), referenceId(order, cargo), measurement(inbound),
preload(summary.preload()), loading(summary.loading()),
delivery(summary.delivery()), transport(inbound), coLoadGroup, order.getCustomerRemark(), latestInternalNote,
order.getWarehouseRemark(), order.getCustomerServiceRemark(),
order.getCreatedAt(), order.getUpdatedAt(), inbound.requestedPickupAt());
}
@@ -394,6 +386,7 @@ public class UnifiedOrderQueryService {
measurement(inbound), preload(summary.preload()), loading(summary.loading()),
delivery(summary.delivery()), transport(inbound), coLoadGroup,
order == null ? null : order.getCustomerRemark(), latestInternalNote,
order == null ? null : order.getWarehouseRemark(), order == null ? null : order.getCustomerServiceRemark(),
inbound.createdAt(), inbound.updatedAt(), inbound.requestedPickupAt());
}
+30
View File
@@ -22,6 +22,9 @@ CREATE TABLE IF NOT EXISTS ord_order (
expected_arrival_at DATETIME(3) NULL COMMENT '客户申报预计到货时间',
estimated_volume_cbm DECIMAL(18,4) NULL COMMENT '平台运营预估体积(立方米),独立于客户下单及实测体积',
customer_remark VARCHAR(1000) NULL COMMENT '客户可见备注',
internal_remark VARCHAR(1000) NULL COMMENT '平台内部备注',
warehouse_remark VARCHAR(1000) NULL COMMENT '仓库备注',
customer_service_remark VARCHAR(1000) NULL COMMENT '客服备注',
pickup_method VARCHAR(24) NULL COMMENT '取货方式',
requested_pickup_at DATETIME(3) NULL COMMENT '客户要求平台上门提货的预约时间',
receiving_warehouse_id BIGINT UNSIGNED NULL COMMENT '国内收货仓ID',
@@ -66,6 +69,33 @@ CREATE TABLE IF NOT EXISTS ord_order (
KEY idx_order_tenant_warehouse (tenant_id, destination_warehouse_id)
) ENGINE=InnoDB COMMENT='头程订单主表:保存商户委托、目的仓、运输方式和订单当前状态';
SET @order_internal_remark_sql = IF(
EXISTS(SELECT 1 FROM information_schema.columns WHERE table_schema='baiyue_order' AND table_name='ord_order' AND column_name='internal_remark'),
'SELECT 1',
'ALTER TABLE baiyue_order.ord_order ADD COLUMN internal_remark VARCHAR(1000) NULL COMMENT ''平台内部备注'' AFTER customer_remark'
);
PREPARE order_internal_remark_stmt FROM @order_internal_remark_sql;
EXECUTE order_internal_remark_stmt;
DEALLOCATE PREPARE order_internal_remark_stmt;
SET @order_warehouse_remark_sql = IF(
EXISTS(SELECT 1 FROM information_schema.columns WHERE table_schema='baiyue_order' AND table_name='ord_order' AND column_name='warehouse_remark'),
'SELECT 1',
'ALTER TABLE baiyue_order.ord_order ADD COLUMN warehouse_remark VARCHAR(1000) NULL COMMENT ''仓库备注'' AFTER internal_remark'
);
PREPARE order_warehouse_remark_stmt FROM @order_warehouse_remark_sql;
EXECUTE order_warehouse_remark_stmt;
DEALLOCATE PREPARE order_warehouse_remark_stmt;
SET @order_customer_service_remark_sql = IF(
EXISTS(SELECT 1 FROM information_schema.columns WHERE table_schema='baiyue_order' AND table_name='ord_order' AND column_name='customer_service_remark'),
'SELECT 1',
'ALTER TABLE baiyue_order.ord_order ADD COLUMN customer_service_remark VARCHAR(1000) NULL COMMENT ''客服备注'' AFTER warehouse_remark'
);
PREPARE order_customer_service_remark_stmt FROM @order_customer_service_remark_sql;
EXECUTE order_customer_service_remark_stmt;
DEALLOCATE PREPARE order_customer_service_remark_stmt;
SET @order_fulfillment_type_sql = IF(
EXISTS(
SELECT 1 FROM information_schema.columns
+11 -6
View File
@@ -267,10 +267,10 @@ paths:
application/json:
schema: {$ref: '#/components/schemas/UnifiedOrderDetailEnvelope'}
'403': {description: 需要平台身份和 platform:order:list 权限}
/api/platform/v1/orders/{orderId}/internal-notes:
post:
summary: 添加订单内部备注
description: 添加仅平台内部可见的订单备注,需要 platform:order:note 权限。
/api/platform/v1/orders/{orderId}/remarks:
put:
summary: 更新订单协作备注
description: 客户备注只读;需要 platform:order:note 权限,可更新或清空内部备注、仓库备注和客服备注。
parameters:
- $ref: '#/components/parameters/UnifiedOrderId'
- $ref: '#/components/parameters/UnifiedTenantId'
@@ -280,9 +280,12 @@ paths:
application/json:
schema:
type: object
required: [content]
required: [expectedVersion]
properties:
content: {type: string, minLength: 1, maxLength: 2000}
expectedVersion: {type: integer, minimum: 0}
internalRemark: {type: string, nullable: true, maxLength: 1000}
warehouseRemark: {type: string, nullable: true, maxLength: 1000}
customerServiceRemark: {type: string, nullable: true, maxLength: 1000}
responses:
'200': {description: 备注添加成功或业务失败,按响应体 code 判断}
'403': {description: 需要 platform:order:note 权限}
@@ -949,6 +952,8 @@ components:
coLoadGroup: {$ref: '#/components/schemas/UnifiedCoLoadGroupSummary'}
customerRemark: {type: string, nullable: true}
latestInternalNote: {type: string, nullable: true, description: 客户详情不返回平台内部备注}
warehouseRemark: {type: string, nullable: true}
customerServiceRemark: {type: string, nullable: true}
createdAt: {type: string, format: date-time, nullable: true}
updatedAt: {type: string, format: date-time, nullable: true}
UnifiedOrderRelation:
@@ -3,7 +3,6 @@ package com.baiyue.order.controller;
import com.baiyue.common.core.exception.BizException;
import com.baiyue.order.entity.OrderCargoEntity;
import com.baiyue.order.entity.OrderEntity;
import com.baiyue.order.entity.OrderInternalNoteEntity;
import com.baiyue.order.repository.OrderRepository;
import com.baiyue.order.service.OrderApplicationService;
import com.baiyue.order.service.PrintableInboundSnapshotService;
@@ -50,9 +49,10 @@ class InternalOrderCargoControllerTest {
cargo.setCargoRemark("内部货物备注");
when(orders.findCargoInheritedByOrder(11L, 21L, 31L)).thenReturn(cargo);
OrderEntity order = new OrderEntity(); order.setId(21L); order.setCustomerRemark("客户订单备注");
OrderInternalNoteEntity note = new OrderInternalNoteEntity(); note.setContent("最新内部备注");
order.setInternalRemark("最新内部备注");
order.setWarehouseRemark("仓库备注");
order.setCustomerServiceRemark("客服备注");
when(orders.findOwned(11L, 21L)).thenReturn(order);
when(orders.findNotes(11L, 21L)).thenReturn(List.of(note));
var response = controller.detail("internal-token", 11L, 21L, 31L);
@@ -67,6 +67,8 @@ class InternalOrderCargoControllerTest {
assertThat(response.data().cargoRemark()).isEqualTo("内部货物备注");
assertThat(response.data().customerRemark()).isEqualTo("客户订单备注");
assertThat(response.data().latestInternalNote()).isEqualTo("最新内部备注");
assertThat(response.data().warehouseRemark()).isEqualTo("仓库备注");
assertThat(response.data().customerServiceRemark()).isEqualTo("客服备注");
verify(orders).findCargoInheritedByOrder(11L, 21L, 31L);
}
@@ -64,9 +64,9 @@ class OrderControllerPermissionTest {
}
@Test
void unifiedPlatformInternalNoteRequiresNotePermission() throws Exception {
assertAuthority(PlatformUnifiedOrderController.class, "addInternalNote", "hasAuthority('platform:order:note')",
AuthenticatedUser.class, long.class, long.class, OrderRequests.NoteRequest.class);
void unifiedPlatformRemarksRequireNotePermission() throws Exception {
assertAuthority(PlatformUnifiedOrderController.class, "updateRemarks", "hasAuthority('platform:order:note')",
AuthenticatedUser.class, long.class, long.class, OrderRequests.RemarksUpdateRequest.class);
}
private void assertAuthority(Class<?> controller, String methodName, String expected, Class<?>... parameterTypes)
@@ -697,6 +697,48 @@ class OrderApplicationServiceStateTest {
.isEqualTo("HDHK26081410000");
}
@Test
void updatesThreeEditableRemarksAndKeepsCustomerRemarkReadOnly() {
OrderRepository repository = mock(OrderRepository.class);
OrderEntity order = new OrderEntity();
order.setId(77L);
order.setTenantId(10001L);
order.setVersion(3);
order.setCustomerRemark("客户原始备注");
order.setInternalRemark("旧内部备注");
when(repository.findOwned(10001L, 77L)).thenReturn(order);
when(repository.updateRemarks(10001L, 77L, 3, 9L, "新内部备注", "仓库备注", "客服备注")).thenReturn(1);
var response = service(repository, mock(OrderEligibilityGuard.class), mock(OrderMasterDataGuard.class))
.updateRemarks(10001L, 9L, 77L,
new OrderRequests.RemarksUpdateRequest(3, " 新内部备注 ", "仓库备注", "客服备注"));
assertThat(response.version()).isEqualTo(4);
assertThat(response.customerRemark()).isEqualTo("客户原始备注");
assertThat(response.internalRemark()).isEqualTo("新内部备注");
assertThat(response.warehouseRemark()).isEqualTo("仓库备注");
assertThat(response.customerServiceRemark()).isEqualTo("客服备注");
verify(repository).insertNote(argThat(note -> note.getTenantId().equals(10001L)
&& note.getOrderId().equals(77L) && note.getContent().equals("新内部备注")));
}
@Test
void rejectsRemarkUpdateWhenOrderVersionChanged() {
OrderRepository repository = mock(OrderRepository.class);
OrderEntity order = new OrderEntity();
order.setId(77L);
order.setTenantId(10001L);
order.setVersion(4);
when(repository.findOwned(10001L, 77L)).thenReturn(order);
when(repository.updateRemarks(anyLong(), anyLong(), anyInt(), anyLong(), any(), any(), any())).thenReturn(0);
assertThatThrownBy(() -> service(repository, mock(OrderEligibilityGuard.class), mock(OrderMasterDataGuard.class))
.updateRemarks(10001L, 9L, 77L, new OrderRequests.RemarksUpdateRequest(3, null, null, null)))
.isInstanceOf(BizException.class)
.hasMessageContaining("请刷新后重试");
verify(repository, never()).insertNote(any());
}
private OrderApplicationService service(OrderRepository repository, OrderEligibilityGuard eligibility,
OrderMasterDataGuard masterData) {
OrderApplicationService service = springService;
@@ -10,7 +10,6 @@ import com.baiyue.order.dto.UnifiedOrderQuery;
import com.baiyue.order.dto.UnifiedOrderResponse;
import com.baiyue.order.entity.OrderCargoEntity;
import com.baiyue.order.entity.OrderEntity;
import com.baiyue.order.entity.OrderInternalNoteEntity;
import com.baiyue.order.entity.OrderStatusHistoryEntity;
import com.baiyue.order.entity.OrderStatus;
import com.baiyue.order.repository.OrderRepository;
@@ -58,14 +57,13 @@ class UnifiedOrderQueryServiceTest {
OrderEntity order = order(OrderStatus.SUBMITTED);
order.setInboundCode("RK-77");
order.setCustomerRemark("客户要求提货前联系");
OrderInternalNoteEntity note = new OrderInternalNoteEntity();
note.setOrderId(77L);
note.setContent("客服已确认提货时间");
order.setInternalRemark("客服已确认提货时间");
order.setWarehouseRemark("仓库请优先处理");
order.setCustomerServiceRemark("客服已回访");
when(orders.unifiedPage(eq(10001L), eq(query), eq(List.of(OrderStatus.SUBMITTED))))
.thenReturn(new PageResult<>(List.of(order), 1, 20, 1, 1));
when(firstleg.summaries(eq("internal-token"), any()))
.thenReturn(ApiResponse.success(List.of()));
when(orders.findNotesByOrderIds(10001L, List.of(77L))).thenReturn(List.of(note));
var result = service.page(10001L, query);
@@ -79,6 +77,8 @@ class UnifiedOrderQueryServiceTest {
assertThat(row.currentHoldReason()).isNull();
assertThat(row.customerRemark()).isEqualTo("客户要求提货前联系");
assertThat(row.latestInternalNote()).isEqualTo("客服已确认提货时间");
assertThat(row.warehouseRemark()).isEqualTo("仓库请优先处理");
assertThat(row.customerServiceRemark()).isEqualTo("客服已回访");
assertThat(row.relation()).isNull();
});
verify(firstleg).summaries(eq("internal-token"), any());