feat: add editable order collaboration remarks
This commit is contained in:
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
+5
-3
@@ -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());
|
||||
|
||||
Reference in New Issue
Block a user