feat: add editable order collaboration remarks
This commit is contained in:
@@ -579,6 +579,9 @@ CREATE TABLE IF NOT EXISTS ord_order (
|
|||||||
expected_arrival_at DATETIME(3) NULL COMMENT '客户申报预计到货时间',
|
expected_arrival_at DATETIME(3) NULL COMMENT '客户申报预计到货时间',
|
||||||
estimated_volume_cbm DECIMAL(18,4) NULL COMMENT '平台运营预估体积(立方米),独立于客户下单及实测体积',
|
estimated_volume_cbm DECIMAL(18,4) NULL COMMENT '平台运营预估体积(立方米),独立于客户下单及实测体积',
|
||||||
customer_remark VARCHAR(1000) 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 '取货方式',
|
pickup_method VARCHAR(24) NULL COMMENT '取货方式',
|
||||||
requested_pickup_at DATETIME(3) NULL COMMENT '客户要求平台上门提货的预约时间',
|
requested_pickup_at DATETIME(3) NULL COMMENT '客户要求平台上门提货的预约时间',
|
||||||
receiving_warehouse_id BIGINT UNSIGNED NULL COMMENT '国内收货仓逻辑ID',
|
receiving_warehouse_id BIGINT UNSIGNED NULL COMMENT '国内收货仓逻辑ID',
|
||||||
@@ -618,6 +621,33 @@ CREATE TABLE IF NOT EXISTS ord_order (
|
|||||||
KEY idx_order_tenant_warehouse (tenant_id, destination_warehouse_id)
|
KEY idx_order_tenant_warehouse (tenant_id, destination_warehouse_id)
|
||||||
) ENGINE=InnoDB COMMENT='头程订单主表:保存商户委托、目的仓、运输方式和订单当前状态';
|
) 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(
|
SET @order_fulfillment_type_sql = IF(
|
||||||
EXISTS(
|
EXISTS(
|
||||||
SELECT 1 FROM information_schema.columns
|
SELECT 1 FROM information_schema.columns
|
||||||
@@ -5413,6 +5443,7 @@ CREATE TABLE IF NOT EXISTS fl_loading_plan_item (
|
|||||||
internal_remark VARCHAR(1000) NULL COMMENT '平台内部备注',
|
internal_remark VARCHAR(1000) NULL COMMENT '平台内部备注',
|
||||||
customer_remark VARCHAR(1000) NULL COMMENT '客户备注快照',
|
customer_remark VARCHAR(1000) NULL COMMENT '客户备注快照',
|
||||||
warehouse_remark VARCHAR(1000) NULL COMMENT '仓库备注快照',
|
warehouse_remark VARCHAR(1000) NULL COMMENT '仓库备注快照',
|
||||||
|
customer_service_remark VARCHAR(1000) NULL COMMENT '客服备注快照',
|
||||||
frozen_at DATETIME(3) NULL COMMENT '装柜完成后的永久冻结时间',
|
frozen_at DATETIME(3) NULL COMMENT '装柜完成后的永久冻结时间',
|
||||||
frozen_by BIGINT UNSIGNED NULL COMMENT '冻结操作人ID',
|
frozen_by BIGINT UNSIGNED NULL COMMENT '冻结操作人ID',
|
||||||
removed_by BIGINT UNSIGNED NULL COMMENT '推送前逻辑删除操作人ID',
|
removed_by BIGINT UNSIGNED NULL COMMENT '推送前逻辑删除操作人ID',
|
||||||
@@ -5452,6 +5483,15 @@ CREATE TABLE IF NOT EXISTS fl_loading_plan_item (
|
|||||||
CONSTRAINT chk_loading_item_deleted CHECK (deleted IN (0, 1))
|
CONSTRAINT chk_loading_item_deleted CHECK (deleted IN (0, 1))
|
||||||
) ENGINE=InnoDB COMMENT='智能装柜客户货物明细;客户归属仅在明细层';
|
) ENGINE=InnoDB COMMENT='智能装柜客户货物明细;客户归属仅在明细层';
|
||||||
|
|
||||||
|
SET @loading_item_customer_service_remark_sql = IF(
|
||||||
|
EXISTS(SELECT 1 FROM information_schema.columns WHERE table_schema='baiyue_firstleg' AND table_name='fl_loading_plan_item' AND column_name='customer_service_remark'),
|
||||||
|
'SELECT 1',
|
||||||
|
'ALTER TABLE baiyue_firstleg.fl_loading_plan_item ADD COLUMN customer_service_remark VARCHAR(1000) NULL COMMENT ''客服备注快照'' AFTER warehouse_remark'
|
||||||
|
);
|
||||||
|
PREPARE loading_item_customer_service_remark_stmt FROM @loading_item_customer_service_remark_sql;
|
||||||
|
EXECUTE loading_item_customer_service_remark_stmt;
|
||||||
|
DEALLOCATE PREPARE loading_item_customer_service_remark_stmt;
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS fl_loading_pallet_label (
|
CREATE TABLE IF NOT EXISTS fl_loading_pallet_label (
|
||||||
id BIGINT UNSIGNED NOT NULL COMMENT '散货托盘标签ID(雪花ID)',
|
id BIGINT UNSIGNED NOT NULL COMMENT '散货托盘标签ID(雪花ID)',
|
||||||
loading_plan_id BIGINT UNSIGNED NOT NULL COMMENT '装柜计划ID',
|
loading_plan_id BIGINT UNSIGNED NOT NULL COMMENT '装柜计划ID',
|
||||||
@@ -6154,7 +6194,9 @@ BEGIN
|
|||||||
IF current_plan_status = 'LOADING' AND EXISTS (
|
IF current_plan_status = 'LOADING' AND EXISTS (
|
||||||
SELECT 1 FROM fl_loading_pick_item
|
SELECT 1 FROM fl_loading_pick_item
|
||||||
WHERE loading_plan_item_id = OLD.id AND picked_package_count > 0) THEN
|
WHERE loading_plan_item_id = OLD.id AND picked_package_count > 0) THEN
|
||||||
IF NOT (NEW.warehouse_remark <=> OLD.warehouse_remark) THEN
|
IF NOT (NEW.internal_remark <=> OLD.internal_remark)
|
||||||
|
OR NOT (NEW.warehouse_remark <=> OLD.warehouse_remark)
|
||||||
|
OR NOT (NEW.customer_service_remark <=> OLD.customer_service_remark) THEN
|
||||||
IF NEW.package_count <> OLD.package_count OR NEW.deleted <> OLD.deleted
|
IF NEW.package_count <> OLD.package_count OR NEW.deleted <> OLD.deleted
|
||||||
OR NOT (NEW.customer_name <=> OLD.customer_name)
|
OR NOT (NEW.customer_name <=> OLD.customer_name)
|
||||||
OR NOT (NEW.inbound_id <=> OLD.inbound_id)
|
OR NOT (NEW.inbound_id <=> OLD.inbound_id)
|
||||||
@@ -6182,14 +6224,13 @@ BEGIN
|
|||||||
OR NOT (NEW.shipping_mark <=> OLD.shipping_mark)
|
OR NOT (NEW.shipping_mark <=> OLD.shipping_mark)
|
||||||
OR NOT (NEW.load_sequence <=> OLD.load_sequence)
|
OR NOT (NEW.load_sequence <=> OLD.load_sequence)
|
||||||
OR NOT (NEW.position_snapshot <=> OLD.position_snapshot)
|
OR NOT (NEW.position_snapshot <=> OLD.position_snapshot)
|
||||||
OR NOT (NEW.internal_remark <=> OLD.internal_remark)
|
|
||||||
OR NOT (NEW.customer_remark <=> OLD.customer_remark)
|
OR NOT (NEW.customer_remark <=> OLD.customer_remark)
|
||||||
OR NOT (NEW.frozen_at <=> OLD.frozen_at)
|
OR NOT (NEW.frozen_at <=> OLD.frozen_at)
|
||||||
OR NOT (NEW.frozen_by <=> OLD.frozen_by)
|
OR NOT (NEW.frozen_by <=> OLD.frozen_by)
|
||||||
OR NOT (NEW.removed_by <=> OLD.removed_by)
|
OR NOT (NEW.removed_by <=> OLD.removed_by)
|
||||||
OR NOT (NEW.removed_by_name <=> OLD.removed_by_name)
|
OR NOT (NEW.removed_by_name <=> OLD.removed_by_name)
|
||||||
OR NOT (NEW.removed_at <=> OLD.removed_at) THEN
|
OR NOT (NEW.removed_at <=> OLD.removed_at) THEN
|
||||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '已有PDA拣货记录的装柜明细修改仓库备注时不得变更其他业务字段';
|
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '已有PDA拣货记录的装柜明细修改备注时不得变更其他业务字段';
|
||||||
END IF;
|
END IF;
|
||||||
ELSEIF NOT EXISTS (
|
ELSEIF NOT EXISTS (
|
||||||
SELECT 1 FROM fl_loading_pick_item
|
SELECT 1 FROM fl_loading_pick_item
|
||||||
@@ -6224,7 +6265,6 @@ BEGIN
|
|||||||
OR NOT (NEW.shipping_mark <=> OLD.shipping_mark)
|
OR NOT (NEW.shipping_mark <=> OLD.shipping_mark)
|
||||||
OR NOT (NEW.load_sequence <=> OLD.load_sequence)
|
OR NOT (NEW.load_sequence <=> OLD.load_sequence)
|
||||||
OR NOT (NEW.position_snapshot <=> OLD.position_snapshot)
|
OR NOT (NEW.position_snapshot <=> OLD.position_snapshot)
|
||||||
OR NOT (NEW.internal_remark <=> OLD.internal_remark)
|
|
||||||
OR NOT (NEW.customer_remark <=> OLD.customer_remark)
|
OR NOT (NEW.customer_remark <=> OLD.customer_remark)
|
||||||
OR NOT (NEW.frozen_at <=> OLD.frozen_at)
|
OR NOT (NEW.frozen_at <=> OLD.frozen_at)
|
||||||
OR NOT (NEW.frozen_by <=> OLD.frozen_by)
|
OR NOT (NEW.frozen_by <=> OLD.frozen_by)
|
||||||
@@ -14791,6 +14831,7 @@ CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_item', 'updated_by
|
|||||||
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_item', 'version', '乐观锁版本号');
|
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_item', 'version', '乐观锁版本号');
|
||||||
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_item', 'volume_cbm', '本次装柜体积m3');
|
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_item', 'volume_cbm', '本次装柜体积m3');
|
||||||
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_item', 'warehouse_remark', '仓库备注快照');
|
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_item', 'warehouse_remark', '仓库备注快照');
|
||||||
|
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_item', 'customer_service_remark', '客服备注快照');
|
||||||
CALL queue_column_comment('baiyue_firstleg', 'fl_measure_review_record', 'allow_rejected_config', '操作时Nacos异常放行开关值');
|
CALL queue_column_comment('baiyue_firstleg', 'fl_measure_review_record', 'allow_rejected_config', '操作时Nacos异常放行开关值');
|
||||||
CALL queue_column_comment('baiyue_firstleg', 'fl_measure_review_record', 'exception_release', '是否异常放行');
|
CALL queue_column_comment('baiyue_firstleg', 'fl_measure_review_record', 'exception_release', '是否异常放行');
|
||||||
CALL queue_column_comment('baiyue_firstleg', 'fl_measure_review_record', 'id', '测量审核记录ID');
|
CALL queue_column_comment('baiyue_firstleg', 'fl_measure_review_record', 'id', '测量审核记录ID');
|
||||||
@@ -15359,6 +15400,9 @@ CALL queue_column_comment('baiyue_order', 'ord_order', 'created_by', '创建人I
|
|||||||
CALL queue_column_comment('baiyue_order', 'ord_order', 'customer_id', '客户逻辑ID');
|
CALL queue_column_comment('baiyue_order', 'ord_order', 'customer_id', '客户逻辑ID');
|
||||||
CALL queue_column_comment('baiyue_order', 'ord_order', 'customer_name', '下单时客户名称快照');
|
CALL queue_column_comment('baiyue_order', 'ord_order', 'customer_name', '下单时客户名称快照');
|
||||||
CALL queue_column_comment('baiyue_order', 'ord_order', 'customer_remark', '客户可见备注');
|
CALL queue_column_comment('baiyue_order', 'ord_order', 'customer_remark', '客户可见备注');
|
||||||
|
CALL queue_column_comment('baiyue_order', 'ord_order', 'internal_remark', '平台内部备注');
|
||||||
|
CALL queue_column_comment('baiyue_order', 'ord_order', 'warehouse_remark', '仓库备注');
|
||||||
|
CALL queue_column_comment('baiyue_order', 'ord_order', 'customer_service_remark', '客服备注');
|
||||||
CALL queue_column_comment('baiyue_order', 'ord_order', 'customs_inspection', '是否需要商检:0否、1是');
|
CALL queue_column_comment('baiyue_order', 'ord_order', 'customs_inspection', '是否需要商检:0否、1是');
|
||||||
CALL queue_column_comment('baiyue_order', 'ord_order', 'deleted', '逻辑删除标记');
|
CALL queue_column_comment('baiyue_order', 'ord_order', 'deleted', '逻辑删除标记');
|
||||||
CALL queue_column_comment('baiyue_order', 'ord_order', 'destination_country_code', '目的国家二字码');
|
CALL queue_column_comment('baiyue_order', 'ord_order', 'destination_country_code', '目的国家二字码');
|
||||||
|
|||||||
@@ -44,12 +44,13 @@ public interface OrderCargoClient {
|
|||||||
Integer originalPackageCount, BigDecimal originalGrossWeightKg,
|
Integer originalPackageCount, BigDecimal originalGrossWeightKg,
|
||||||
BigDecimal originalVolumeCbm, String skuNo, String cargoNameEn,
|
BigDecimal originalVolumeCbm, String skuNo, String cargoNameEn,
|
||||||
Integer unitsPerPackage, Integer totalQuantity, BigDecimal netWeightKg,
|
Integer unitsPerPackage, Integer totalQuantity, BigDecimal netWeightKg,
|
||||||
String cargoRemark, String customerRemark, String latestInternalNote) {
|
String cargoRemark, String customerRemark, String latestInternalNote,
|
||||||
|
String warehouseRemark, String customerServiceRemark) {
|
||||||
public CargoBinding(Long cargoId, Long orderId, Integer itemNo, String cargoName,
|
public CargoBinding(Long cargoId, Long orderId, Integer itemNo, String cargoName,
|
||||||
Integer originalPackageCount, BigDecimal originalGrossWeightKg,
|
Integer originalPackageCount, BigDecimal originalGrossWeightKg,
|
||||||
BigDecimal originalVolumeCbm) {
|
BigDecimal originalVolumeCbm) {
|
||||||
this(cargoId, orderId, itemNo, cargoName, originalPackageCount,
|
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,
|
public CargoBinding(Long cargoId, Long orderId, Integer itemNo, String cargoName,
|
||||||
Integer originalPackageCount, BigDecimal originalGrossWeightKg,
|
Integer originalPackageCount, BigDecimal originalGrossWeightKg,
|
||||||
@@ -58,7 +59,7 @@ public interface OrderCargoClient {
|
|||||||
String cargoRemark) {
|
String cargoRemark) {
|
||||||
this(cargoId, orderId, itemNo, cargoName, originalPackageCount, originalGrossWeightKg,
|
this(cargoId, orderId, itemNo, cargoName, originalPackageCount, originalGrossWeightKg,
|
||||||
originalVolumeCbm, skuNo, cargoNameEn, unitsPerPackage, totalQuantity, netWeightKg,
|
originalVolumeCbm, skuNo, cargoNameEn, unitsPerPackage, totalQuantity, netWeightKg,
|
||||||
cargoRemark, null, null);
|
cargoRemark, null, null, null, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -38,8 +38,8 @@ public class PlatformSmartLoadingController {
|
|||||||
public ApiResponse<Plan> recommend(@AuthenticationPrincipal AuthenticatedUser u,@PathVariable @Positive long planId,@Valid @RequestBody RecommendRequest r){FirstlegScopeGuard.platform(u);return ApiResponse.success(service.recommend(planId,r,u.userId(),u.username()));}
|
public ApiResponse<Plan> recommend(@AuthenticationPrincipal AuthenticatedUser u,@PathVariable @Positive long planId,@Valid @RequestBody RecommendRequest r){FirstlegScopeGuard.platform(u);return ApiResponse.success(service.recommend(planId,r,u.userId(),u.username()));}
|
||||||
@PatchMapping("/plans/{planId}/items/{itemId}") @PreAuthorize("hasAuthority('platform:firstleg:smart-loading:assign')")
|
@PatchMapping("/plans/{planId}/items/{itemId}") @PreAuthorize("hasAuthority('platform:firstleg:smart-loading:assign')")
|
||||||
public ApiResponse<Plan> adjust(@AuthenticationPrincipal AuthenticatedUser u,@PathVariable @Positive long planId,@PathVariable @Positive long itemId,@Valid @RequestBody AdjustRequest r){FirstlegScopeGuard.platform(u);return ApiResponse.success(service.adjust(planId,itemId,r,u.userId(),u.username()));}
|
public ApiResponse<Plan> adjust(@AuthenticationPrincipal AuthenticatedUser u,@PathVariable @Positive long planId,@PathVariable @Positive long itemId,@Valid @RequestBody AdjustRequest r){FirstlegScopeGuard.platform(u);return ApiResponse.success(service.adjust(planId,itemId,r,u.userId(),u.username()));}
|
||||||
@PatchMapping("/plans/{planId}/items/{itemId}/warehouse-remark") @PreAuthorize("hasAuthority('platform:firstleg:preload:edit')")
|
@PatchMapping("/plans/{planId}/items/{itemId}/remarks") @PreAuthorize("hasAuthority('platform:firstleg:preload:edit')")
|
||||||
public ApiResponse<Plan> warehouseRemark(@AuthenticationPrincipal AuthenticatedUser u,@PathVariable @Positive long planId,@PathVariable @Positive long itemId,@Valid @RequestBody WarehouseRemarkRequest r){FirstlegScopeGuard.platform(u);return ApiResponse.success(service.updateWarehouseRemark(planId,itemId,r,u.userId(),u.username()));}
|
public ApiResponse<Plan> remarks(@AuthenticationPrincipal AuthenticatedUser u,@PathVariable @Positive long planId,@PathVariable @Positive long itemId,@Valid @RequestBody RemarksRequest r){FirstlegScopeGuard.platform(u);return ApiResponse.success(service.updateRemarks(planId,itemId,r,u.userId(),u.username()));}
|
||||||
@DeleteMapping("/plans/{planId}/items/{itemId}") @PreAuthorize("hasAuthority('platform:firstleg:smart-loading:delete')")
|
@DeleteMapping("/plans/{planId}/items/{itemId}") @PreAuthorize("hasAuthority('platform:firstleg:smart-loading:delete')")
|
||||||
public ApiResponse<Plan> delete(@AuthenticationPrincipal AuthenticatedUser u,@PathVariable @Positive long planId,@PathVariable @Positive long itemId,@Valid @RequestBody DeleteRequest r){FirstlegScopeGuard.platform(u);return ApiResponse.success(service.delete(planId,itemId,r,u.userId(),u.username()));}
|
public ApiResponse<Plan> delete(@AuthenticationPrincipal AuthenticatedUser u,@PathVariable @Positive long planId,@PathVariable @Positive long itemId,@Valid @RequestBody DeleteRequest r){FirstlegScopeGuard.platform(u);return ApiResponse.success(service.delete(planId,itemId,r,u.userId(),u.username()));}
|
||||||
@DeleteMapping("/plans/{planId}/unpicked-items") @PreAuthorize("hasAuthority('platform:firstleg:smart-loading:delete')")
|
@DeleteMapping("/plans/{planId}/unpicked-items") @PreAuthorize("hasAuthority('platform:firstleg:smart-loading:delete')")
|
||||||
|
|||||||
@@ -240,6 +240,7 @@ public final class LoadingListDtos {
|
|||||||
String internalRemark,
|
String internalRemark,
|
||||||
String customerRemark,
|
String customerRemark,
|
||||||
String warehouseRemark,
|
String warehouseRemark,
|
||||||
|
String customerServiceRemark,
|
||||||
int loadSequence,
|
int loadSequence,
|
||||||
LocalDateTime frozenAt,
|
LocalDateTime frozenAt,
|
||||||
Integer itemVersion
|
Integer itemVersion
|
||||||
|
|||||||
@@ -83,7 +83,8 @@ public final class SmartLoadingDtos { private SmartLoadingDtos(){}
|
|||||||
@NotNull @Min(0) Integer expectedItemVersion,@NotBlank @Size(max=128) String operationId) {}
|
@NotNull @Min(0) Integer expectedItemVersion,@NotBlank @Size(max=128) String operationId) {}
|
||||||
public record RemoveUnpickedRequest(@NotNull @Min(0) Integer expectedPlanVersion,
|
public record RemoveUnpickedRequest(@NotNull @Min(0) Integer expectedPlanVersion,
|
||||||
@NotBlank @Size(max=128) String operationId) {}
|
@NotBlank @Size(max=128) String operationId) {}
|
||||||
public record WarehouseRemarkRequest(@Size(max=1000) String warehouseRemark,
|
public record RemarksRequest(@Size(max=1000) String internalRemark,
|
||||||
|
@Size(max=1000) String warehouseRemark, @Size(max=1000) String customerServiceRemark,
|
||||||
@NotNull @Min(0) Integer expectedPlanVersion, @NotNull @Min(0) Integer expectedItemVersion,
|
@NotNull @Min(0) Integer expectedPlanVersion, @NotNull @Min(0) Integer expectedItemVersion,
|
||||||
@NotBlank @Size(max=128) String operationId) {}
|
@NotBlank @Size(max=128) String operationId) {}
|
||||||
public record PlanCoLoadGroup(Long groupId,String groupNo,Long lockedPlanId,Integer memberOrderCount,
|
public record PlanCoLoadGroup(Long groupId,String groupNo,Long lockedPlanId,Integer memberOrderCount,
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ public class LoadingPlanItemEntity {
|
|||||||
private String unitType; private Long palletId; private String palletNo; private Boolean indivisible;
|
private String unitType; private Long palletId; private String palletNo; private Boolean indivisible;
|
||||||
private Integer palletCount; private BigDecimal grossWeightKg; private BigDecimal netWeightKg; private BigDecimal dimensionalWeightKg; private BigDecimal volumeCbm;
|
private Integer palletCount; private BigDecimal grossWeightKg; private BigDecimal netWeightKg; private BigDecimal dimensionalWeightKg; private BigDecimal volumeCbm;
|
||||||
private String fbaTrackingNo; private String shipmentNo; private String shippingMark; private Integer loadSequence; private String positionSnapshot;
|
private String fbaTrackingNo; private String shipmentNo; private String shippingMark; private Integer loadSequence; private String positionSnapshot;
|
||||||
private String internalRemark; private String customerRemark; private String warehouseRemark; private LocalDateTime frozenAt; private Long frozenBy;
|
private String internalRemark; private String customerRemark; private String warehouseRemark; private String customerServiceRemark; private LocalDateTime frozenAt; private Long frozenBy;
|
||||||
private Long removedBy; private String removedByName; private LocalDateTime removedAt; @TableLogic private Boolean deleted;
|
private Long removedBy; private String removedByName; private LocalDateTime removedAt; @TableLogic private Boolean deleted;
|
||||||
@TableField(exist=false) private Long activeInboundDetailId;
|
@TableField(exist=false) private Long activeInboundDetailId;
|
||||||
@TableField(exist=false) private Integer pickedPackageCount;
|
@TableField(exist=false) private Integer pickedPackageCount;
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ public interface LoadingListQueryMapper {
|
|||||||
* i.package_count / NULLIF(inbound.total_package_count,0)),0) pallet_count,
|
* i.package_count / NULLIF(inbound.total_package_count,0)),0) pallet_count,
|
||||||
gross_weight_kg, net_weight_kg, volume_cbm, dimensional_weight_kg,
|
gross_weight_kg, net_weight_kg, volume_cbm, dimensional_weight_kg,
|
||||||
product_quantity, units_per_carton, fba_tracking_no, shipment_no,
|
product_quantity, units_per_carton, fba_tracking_no, shipment_no,
|
||||||
shipping_mark, internal_remark, customer_remark, warehouse_remark,
|
shipping_mark, internal_remark, customer_remark, warehouse_remark, customer_service_remark,
|
||||||
load_sequence, frozen_at, i.version AS item_version
|
load_sequence, frozen_at, i.version AS item_version
|
||||||
FROM fl_loading_plan_item i
|
FROM fl_loading_plan_item i
|
||||||
JOIN fl_loading_plan p ON p.id=i.loading_plan_id
|
JOIN fl_loading_plan p ON p.id=i.loading_plan_id
|
||||||
@@ -457,6 +457,7 @@ public interface LoadingListQueryMapper {
|
|||||||
private String internalRemark;
|
private String internalRemark;
|
||||||
private String customerRemark;
|
private String customerRemark;
|
||||||
private String warehouseRemark;
|
private String warehouseRemark;
|
||||||
|
private String customerServiceRemark;
|
||||||
private Integer loadSequence;
|
private Integer loadSequence;
|
||||||
private LocalDateTime frozenAt;
|
private LocalDateTime frozenAt;
|
||||||
private Integer itemVersion;
|
private Integer itemVersion;
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baiyue.firstl
|
|||||||
int removeEditing(@Param("planId") long planId,@Param("itemId") long itemId,@Param("expectedVersion") int expectedVersion,@Param("operatorId") long operatorId,@Param("operatorName") String operatorName);
|
int removeEditing(@Param("planId") long planId,@Param("itemId") long itemId,@Param("expectedVersion") int expectedVersion,@Param("operatorId") long operatorId,@Param("operatorName") String operatorName);
|
||||||
@Update("""
|
@Update("""
|
||||||
UPDATE fl_loading_plan_item i
|
UPDATE fl_loading_plan_item i
|
||||||
SET i.warehouse_remark=#{remark}, i.updated_by=#{operatorId}, i.updated_by_name=#{operatorName},
|
SET i.internal_remark=#{internalRemark}, i.warehouse_remark=#{warehouseRemark},
|
||||||
|
i.customer_service_remark=#{customerServiceRemark}, i.updated_by=#{operatorId}, i.updated_by_name=#{operatorName},
|
||||||
i.updated_at=CURRENT_TIMESTAMP(3), i.version=i.version+1
|
i.updated_at=CURRENT_TIMESTAMP(3), i.version=i.version+1
|
||||||
WHERE i.id=#{itemId} AND i.loading_plan_id=#{planId} AND i.deleted=0 AND i.frozen_at IS NULL
|
WHERE i.id=#{itemId} AND i.loading_plan_id=#{planId} AND i.deleted=0 AND i.frozen_at IS NULL
|
||||||
AND i.version=#{expectedItemVersion}
|
AND i.version=#{expectedItemVersion}
|
||||||
@@ -21,9 +22,12 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baiyue.firstl
|
|||||||
AND p.plan_status IN ('EDITING','LOADING')
|
AND p.plan_status IN ('EDITING','LOADING')
|
||||||
)
|
)
|
||||||
""")
|
""")
|
||||||
int updateWarehouseRemark(@Param("planId") long planId, @Param("itemId") long itemId,
|
int updateRemarks(@Param("planId") long planId, @Param("itemId") long itemId,
|
||||||
@Param("touchedPlanVersion") int touchedPlanVersion,
|
@Param("touchedPlanVersion") int touchedPlanVersion,
|
||||||
@Param("expectedItemVersion") int expectedItemVersion,
|
@Param("expectedItemVersion") int expectedItemVersion,
|
||||||
@Param("remark") String remark, @Param("operatorId") long operatorId,
|
@Param("internalRemark") String internalRemark,
|
||||||
|
@Param("warehouseRemark") String warehouseRemark,
|
||||||
|
@Param("customerServiceRemark") String customerServiceRemark,
|
||||||
|
@Param("operatorId") long operatorId,
|
||||||
@Param("operatorName") String operatorName);
|
@Param("operatorName") String operatorName);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ public class LoadingListRepository {
|
|||||||
decimal(row.getGrossWeightKg()), decimal(row.getNetWeightKg()), decimal(row.getVolumeCbm()),
|
decimal(row.getGrossWeightKg()), decimal(row.getNetWeightKg()), decimal(row.getVolumeCbm()),
|
||||||
decimal(row.getDimensionalWeightKg()), value(row.getProductQuantity()),
|
decimal(row.getDimensionalWeightKg()), value(row.getProductQuantity()),
|
||||||
integer(row.getUnitsPerCarton()), row.getFbaTrackingNo(), row.getShipmentNo(), row.getShippingMark(),
|
integer(row.getUnitsPerCarton()), row.getFbaTrackingNo(), row.getShipmentNo(), row.getShippingMark(),
|
||||||
row.getInternalRemark(), row.getCustomerRemark(), row.getWarehouseRemark(),
|
row.getInternalRemark(), row.getCustomerRemark(), row.getWarehouseRemark(), row.getCustomerServiceRemark(),
|
||||||
integer(row.getLoadSequence()), row.getFrozenAt(), row.getItemVersion())).toList();
|
integer(row.getLoadSequence()), row.getFrozenAt(), row.getItemVersion())).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -147,10 +147,11 @@ public class SmartLoadingRepository {
|
|||||||
public int remove(long planId, long itemId, int version, long operatorId, String operatorName) {
|
public int remove(long planId, long itemId, int version, long operatorId, String operatorName) {
|
||||||
return items.removeEditing(planId, itemId, version, operatorId, operatorName);
|
return items.removeEditing(planId, itemId, version, operatorId, operatorName);
|
||||||
}
|
}
|
||||||
public int updateWarehouseRemark(long planId, long itemId, int touchedPlanVersion, int expectedItemVersion,
|
public int updateRemarks(long planId, long itemId, int touchedPlanVersion, int expectedItemVersion,
|
||||||
String remark, long operatorId, String operatorName) {
|
String internalRemark, String warehouseRemark, String customerServiceRemark,
|
||||||
return items.updateWarehouseRemark(planId, itemId, touchedPlanVersion, expectedItemVersion,
|
long operatorId, String operatorName) {
|
||||||
remark, operatorId, operatorName);
|
return items.updateRemarks(planId, itemId, touchedPlanVersion, expectedItemVersion,
|
||||||
|
internalRemark, warehouseRemark, customerServiceRemark, operatorId, operatorName);
|
||||||
}
|
}
|
||||||
public int startPicking(long planId, long operatorId, String operatorName) {
|
public int startPicking(long planId, long operatorId, String operatorName) {
|
||||||
return plans.startPicking(planId, operatorId, operatorName);
|
return plans.startPicking(planId, operatorId, operatorName);
|
||||||
|
|||||||
@@ -1043,42 +1043,55 @@ public class SmartLoadingService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@BusinessAudit(event = "SMART_LOADING_WAREHOUSE_REMARK", resourceType = "LOADING_PLAN",
|
@BusinessAudit(event = "SMART_LOADING_REMARKS", resourceType = "LOADING_PLAN",
|
||||||
resourceId = "#planId", description = "修改装柜仓库备注")
|
resourceId = "#planId", description = "修改装柜协作备注")
|
||||||
public Plan updateWarehouseRemark(long planId, long itemId, WarehouseRemarkRequest request,
|
public Plan updateRemarks(long planId, long itemId, RemarksRequest request,
|
||||||
long operatorId, String operatorName) {
|
long operatorId, String operatorName) {
|
||||||
String operation = operation(request.operationId());
|
String operation = operation(request.operationId());
|
||||||
String remark = trim(request.warehouseRemark());
|
String internalRemark = nullableRemark(request.internalRemark());
|
||||||
if (!StringUtils.hasText(remark)) remark = null;
|
String warehouseRemark = nullableRemark(request.warehouseRemark());
|
||||||
String requestHash = sha256("WAREHOUSE_REMARK|" + planId + '|' + itemId + '|'
|
String customerServiceRemark = nullableRemark(request.customerServiceRemark());
|
||||||
+ Objects.toString(remark, "") + '|' + request.expectedPlanVersion() + '|'
|
String requestHash = sha256("REMARKS|" + planId + '|' + itemId + '|'
|
||||||
|
+ Objects.toString(internalRemark, "") + '|' + Objects.toString(warehouseRemark, "") + '|'
|
||||||
|
+ Objects.toString(customerServiceRemark, "") + '|' + request.expectedPlanVersion() + '|'
|
||||||
+ request.expectedItemVersion());
|
+ request.expectedItemVersion());
|
||||||
Plan replay = replay(operation, planId, "WAREHOUSE_REMARK", itemId, requestHash);
|
Plan replay = replay(operation, planId, "REMARKS", itemId, requestHash);
|
||||||
if (replay != null) return replay;
|
if (replay != null) return replay;
|
||||||
LoadingPlanEntity plan = editing(planId, request.expectedPlanVersion(), operatorId, operatorName);
|
LoadingPlanEntity plan = editing(planId, request.expectedPlanVersion(), operatorId, operatorName);
|
||||||
LoadingPlanItemEntity item = repository.item(planId, itemId);
|
LoadingPlanItemEntity item = repository.item(planId, itemId);
|
||||||
if (item == null) throw new BizException(ResultCode.NOT_FOUND, "装柜明细不存在");
|
if (item == null) throw new BizException(ResultCode.NOT_FOUND, "装柜明细不存在");
|
||||||
if (Objects.equals(item.getWarehouseRemark(), remark)) {
|
if (Objects.equals(item.getInternalRemark(), internalRemark)
|
||||||
|
&& Objects.equals(item.getWarehouseRemark(), warehouseRemark)
|
||||||
|
&& Objects.equals(item.getCustomerServiceRemark(), customerServiceRemark)) {
|
||||||
return view(plan, requireBooking(plan.getContainerBookingId()));
|
return view(plan, requireBooking(plan.getContainerBookingId()));
|
||||||
}
|
}
|
||||||
String before = json(values("warehouseRemark", item.getWarehouseRemark(), "version", item.getVersion()));
|
String before = json(values("internalRemark", item.getInternalRemark(),
|
||||||
|
"warehouseRemark", item.getWarehouseRemark(), "customerServiceRemark", item.getCustomerServiceRemark(),
|
||||||
|
"version", item.getVersion()));
|
||||||
try {
|
try {
|
||||||
touch(plan, operatorId, operatorName);
|
touch(plan, operatorId, operatorName);
|
||||||
if (repository.updateWarehouseRemark(planId, itemId, request.expectedPlanVersion() + 1,
|
if (repository.updateRemarks(planId, itemId, request.expectedPlanVersion() + 1,
|
||||||
request.expectedItemVersion(), remark, operatorId, operatorName) != 1) {
|
request.expectedItemVersion(), internalRemark, warehouseRemark, customerServiceRemark,
|
||||||
|
operatorId, operatorName) != 1) {
|
||||||
throw conflict("装柜计划或明细已被其他人修改,请刷新后重试");
|
throw conflict("装柜计划或明细已被其他人修改,请刷新后重试");
|
||||||
}
|
}
|
||||||
} catch (DataAccessException exception) {
|
} catch (DataAccessException exception) {
|
||||||
if (hasSqlState(exception, "45000")) {
|
if (hasSqlState(exception, "45000")) {
|
||||||
throw conflict("装柜计划或明细状态已变化,仓库备注未保存,请刷新后重试");
|
throw conflict("装柜计划或明细状态已变化,备注未保存,请刷新后重试");
|
||||||
}
|
}
|
||||||
throw exception;
|
throw exception;
|
||||||
}
|
}
|
||||||
history(planId, itemId, "WAREHOUSE_REMARK", plan.getPlanStatus(), plan.getPlanStatus(), operation,
|
history(planId, itemId, "REMARKS", plan.getPlanStatus(), plan.getPlanStatus(), operation,
|
||||||
before, json(values("warehouseRemark", remark, "requestHash", requestHash)), operatorId, operatorName);
|
before, json(values("internalRemark", internalRemark, "warehouseRemark", warehouseRemark,
|
||||||
|
"customerServiceRemark", customerServiceRemark, "requestHash", requestHash)), operatorId, operatorName);
|
||||||
return view(repository.plan(planId), requireBooking(plan.getContainerBookingId()));
|
return view(repository.plan(planId), requireBooking(plan.getContainerBookingId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String nullableRemark(String value) {
|
||||||
|
String remark = trim(value);
|
||||||
|
return StringUtils.hasText(remark) ? remark : null;
|
||||||
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@BusinessAudit(event = "SMART_LOADING_ITEM_DELETE", resourceType = "LOADING_PLAN_ITEM",
|
@BusinessAudit(event = "SMART_LOADING_ITEM_DELETE", resourceType = "LOADING_PLAN_ITEM",
|
||||||
resourceId = "#itemId", description = "删除智能装柜明细")
|
resourceId = "#itemId", description = "删除智能装柜明细")
|
||||||
@@ -1384,7 +1397,12 @@ public class SmartLoadingService {
|
|||||||
i.setNetWeightKg(unit(c.unitNetWeightKg()).multiply(BigDecimal.valueOf(count)));
|
i.setNetWeightKg(unit(c.unitNetWeightKg()).multiply(BigDecimal.valueOf(count)));
|
||||||
i.setVolumeCbm(unit(c.unitVolumeCbm()).multiply(BigDecimal.valueOf(count)));
|
i.setVolumeCbm(unit(c.unitVolumeCbm()).multiply(BigDecimal.valueOf(count)));
|
||||||
i.setPalletCount(estimatedPalletCount(c, count));
|
i.setPalletCount(estimatedPalletCount(c, count));
|
||||||
applyCargo(i, cargo, count); i.setLoadSequence(seq); i.setDeleted(false); i.setCreatedBy(op); i.setCreatedByName(name); i.setUpdatedBy(op); i.setUpdatedByName(name); i.setVersion(0); return i;
|
applyCargo(i, cargo, count);
|
||||||
|
i.setCustomerRemark(cargo.customerRemark());
|
||||||
|
i.setInternalRemark(cargo.latestInternalNote());
|
||||||
|
i.setWarehouseRemark(cargo.warehouseRemark());
|
||||||
|
i.setCustomerServiceRemark(cargo.customerServiceRemark());
|
||||||
|
i.setLoadSequence(seq); i.setDeleted(false); i.setCreatedBy(op); i.setCreatedByName(name); i.setUpdatedBy(op); i.setUpdatedByName(name); i.setVersion(0); return i;
|
||||||
}
|
}
|
||||||
private int estimatedPalletCount(Candidate candidate, int packageCount) {
|
private int estimatedPalletCount(Candidate candidate, int packageCount) {
|
||||||
InboundOrderEntity inbound = repository.inbound(candidate.accountId(), candidate.inboundId());
|
InboundOrderEntity inbound = repository.inbound(candidate.accountId(), candidate.inboundId());
|
||||||
@@ -1397,8 +1415,6 @@ public class SmartLoadingService {
|
|||||||
item.setSkuCode(cargo.skuNo());
|
item.setSkuCode(cargo.skuNo());
|
||||||
item.setProductNameEn(cargo.cargoNameEn());
|
item.setProductNameEn(cargo.cargoNameEn());
|
||||||
item.setUnitsPerCarton(cargo.unitsPerPackage());
|
item.setUnitsPerCarton(cargo.unitsPerPackage());
|
||||||
item.setCustomerRemark(cargo.customerRemark());
|
|
||||||
item.setInternalRemark(cargo.latestInternalNote());
|
|
||||||
int original = cargo.originalPackageCount() == null || cargo.originalPackageCount() <= 0
|
int original = cargo.originalPackageCount() == null || cargo.originalPackageCount() <= 0
|
||||||
? boxes : cargo.originalPackageCount();
|
? boxes : cargo.originalPackageCount();
|
||||||
if (cargo.unitsPerPackage() != null) {
|
if (cargo.unitsPerPackage() != null) {
|
||||||
@@ -1419,9 +1435,7 @@ public class SmartLoadingService {
|
|||||||
&& Objects.equals(current.getSkuCode(), desired.getSkuCode())
|
&& Objects.equals(current.getSkuCode(), desired.getSkuCode())
|
||||||
&& Objects.equals(current.getProductNameEn(), desired.getProductNameEn())
|
&& Objects.equals(current.getProductNameEn(), desired.getProductNameEn())
|
||||||
&& Objects.equals(current.getUnitsPerCarton(), desired.getUnitsPerCarton())
|
&& Objects.equals(current.getUnitsPerCarton(), desired.getUnitsPerCarton())
|
||||||
&& Objects.equals(current.getProductQuantity(), desired.getProductQuantity())
|
&& Objects.equals(current.getProductQuantity(), desired.getProductQuantity());
|
||||||
&& Objects.equals(current.getCustomerRemark(), desired.getCustomerRemark())
|
|
||||||
&& Objects.equals(current.getInternalRemark(), desired.getInternalRemark());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean decimalEquals(BigDecimal left, BigDecimal right) {
|
private static boolean decimalEquals(BigDecimal left, BigDecimal right) {
|
||||||
@@ -1434,7 +1448,8 @@ public class SmartLoadingService {
|
|||||||
"palletCount", item.getPalletCount(), "skuCode", item.getSkuCode(),
|
"palletCount", item.getPalletCount(), "skuCode", item.getSkuCode(),
|
||||||
"productNameEn", item.getProductNameEn(), "unitsPerCarton", item.getUnitsPerCarton(),
|
"productNameEn", item.getProductNameEn(), "unitsPerCarton", item.getUnitsPerCarton(),
|
||||||
"productQuantity", item.getProductQuantity(), "customerRemark", item.getCustomerRemark(),
|
"productQuantity", item.getProductQuantity(), "customerRemark", item.getCustomerRemark(),
|
||||||
"internalRemark", item.getInternalRemark(), "version", item.getVersion());
|
"internalRemark", item.getInternalRemark(), "warehouseRemark", item.getWarehouseRemark(),
|
||||||
|
"customerServiceRemark", item.getCustomerServiceRemark(), "version", item.getVersion());
|
||||||
}
|
}
|
||||||
|
|
||||||
private OrderCargoClient.CargoBinding loadCargo(Candidate candidate) {
|
private OrderCargoClient.CargoBinding loadCargo(Candidate candidate) {
|
||||||
|
|||||||
@@ -319,6 +319,7 @@ CREATE TABLE IF NOT EXISTS fl_loading_plan_item (
|
|||||||
internal_remark VARCHAR(1000) NULL COMMENT '平台内部备注',
|
internal_remark VARCHAR(1000) NULL COMMENT '平台内部备注',
|
||||||
customer_remark VARCHAR(1000) NULL COMMENT '客户备注快照',
|
customer_remark VARCHAR(1000) NULL COMMENT '客户备注快照',
|
||||||
warehouse_remark VARCHAR(1000) NULL COMMENT '仓库备注快照',
|
warehouse_remark VARCHAR(1000) NULL COMMENT '仓库备注快照',
|
||||||
|
customer_service_remark VARCHAR(1000) NULL COMMENT '客服备注快照',
|
||||||
frozen_at DATETIME(3) NULL COMMENT '装柜完成后的永久冻结时间',
|
frozen_at DATETIME(3) NULL COMMENT '装柜完成后的永久冻结时间',
|
||||||
frozen_by BIGINT UNSIGNED NULL COMMENT '冻结操作人ID',
|
frozen_by BIGINT UNSIGNED NULL COMMENT '冻结操作人ID',
|
||||||
removed_by BIGINT UNSIGNED NULL COMMENT '推送前逻辑删除操作人ID',
|
removed_by BIGINT UNSIGNED NULL COMMENT '推送前逻辑删除操作人ID',
|
||||||
@@ -358,6 +359,15 @@ CREATE TABLE IF NOT EXISTS fl_loading_plan_item (
|
|||||||
CONSTRAINT chk_loading_item_deleted CHECK (deleted IN (0, 1))
|
CONSTRAINT chk_loading_item_deleted CHECK (deleted IN (0, 1))
|
||||||
) ENGINE=InnoDB COMMENT='智能装柜客户货物明细;客户归属仅在明细层';
|
) ENGINE=InnoDB COMMENT='智能装柜客户货物明细;客户归属仅在明细层';
|
||||||
|
|
||||||
|
SET @loading_item_customer_service_remark_sql = IF(
|
||||||
|
EXISTS(SELECT 1 FROM information_schema.columns WHERE table_schema='baiyue_firstleg' AND table_name='fl_loading_plan_item' AND column_name='customer_service_remark'),
|
||||||
|
'SELECT 1',
|
||||||
|
'ALTER TABLE baiyue_firstleg.fl_loading_plan_item ADD COLUMN customer_service_remark VARCHAR(1000) NULL COMMENT ''客服备注快照'' AFTER warehouse_remark'
|
||||||
|
);
|
||||||
|
PREPARE loading_item_customer_service_remark_stmt FROM @loading_item_customer_service_remark_sql;
|
||||||
|
EXECUTE loading_item_customer_service_remark_stmt;
|
||||||
|
DEALLOCATE PREPARE loading_item_customer_service_remark_stmt;
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS fl_loading_pallet_label (
|
CREATE TABLE IF NOT EXISTS fl_loading_pallet_label (
|
||||||
id BIGINT UNSIGNED NOT NULL COMMENT '散货托盘标签ID(雪花ID)',
|
id BIGINT UNSIGNED NOT NULL COMMENT '散货托盘标签ID(雪花ID)',
|
||||||
loading_plan_id BIGINT UNSIGNED NOT NULL COMMENT '装柜计划ID',
|
loading_plan_id BIGINT UNSIGNED NOT NULL COMMENT '装柜计划ID',
|
||||||
@@ -1036,7 +1046,9 @@ BEGIN
|
|||||||
IF current_plan_status = 'LOADING' AND EXISTS (
|
IF current_plan_status = 'LOADING' AND EXISTS (
|
||||||
SELECT 1 FROM fl_loading_pick_item
|
SELECT 1 FROM fl_loading_pick_item
|
||||||
WHERE loading_plan_item_id = OLD.id AND picked_package_count > 0) THEN
|
WHERE loading_plan_item_id = OLD.id AND picked_package_count > 0) THEN
|
||||||
IF NOT (NEW.warehouse_remark <=> OLD.warehouse_remark) THEN
|
IF NOT (NEW.internal_remark <=> OLD.internal_remark)
|
||||||
|
OR NOT (NEW.warehouse_remark <=> OLD.warehouse_remark)
|
||||||
|
OR NOT (NEW.customer_service_remark <=> OLD.customer_service_remark) THEN
|
||||||
IF NEW.package_count <> OLD.package_count OR NEW.deleted <> OLD.deleted
|
IF NEW.package_count <> OLD.package_count OR NEW.deleted <> OLD.deleted
|
||||||
OR NOT (NEW.customer_name <=> OLD.customer_name)
|
OR NOT (NEW.customer_name <=> OLD.customer_name)
|
||||||
OR NOT (NEW.inbound_id <=> OLD.inbound_id)
|
OR NOT (NEW.inbound_id <=> OLD.inbound_id)
|
||||||
@@ -1064,14 +1076,13 @@ BEGIN
|
|||||||
OR NOT (NEW.shipping_mark <=> OLD.shipping_mark)
|
OR NOT (NEW.shipping_mark <=> OLD.shipping_mark)
|
||||||
OR NOT (NEW.load_sequence <=> OLD.load_sequence)
|
OR NOT (NEW.load_sequence <=> OLD.load_sequence)
|
||||||
OR NOT (NEW.position_snapshot <=> OLD.position_snapshot)
|
OR NOT (NEW.position_snapshot <=> OLD.position_snapshot)
|
||||||
OR NOT (NEW.internal_remark <=> OLD.internal_remark)
|
|
||||||
OR NOT (NEW.customer_remark <=> OLD.customer_remark)
|
OR NOT (NEW.customer_remark <=> OLD.customer_remark)
|
||||||
OR NOT (NEW.frozen_at <=> OLD.frozen_at)
|
OR NOT (NEW.frozen_at <=> OLD.frozen_at)
|
||||||
OR NOT (NEW.frozen_by <=> OLD.frozen_by)
|
OR NOT (NEW.frozen_by <=> OLD.frozen_by)
|
||||||
OR NOT (NEW.removed_by <=> OLD.removed_by)
|
OR NOT (NEW.removed_by <=> OLD.removed_by)
|
||||||
OR NOT (NEW.removed_by_name <=> OLD.removed_by_name)
|
OR NOT (NEW.removed_by_name <=> OLD.removed_by_name)
|
||||||
OR NOT (NEW.removed_at <=> OLD.removed_at) THEN
|
OR NOT (NEW.removed_at <=> OLD.removed_at) THEN
|
||||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '已有PDA拣货记录的装柜明细修改仓库备注时不得变更其他业务字段';
|
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '已有PDA拣货记录的装柜明细修改备注时不得变更其他业务字段';
|
||||||
END IF;
|
END IF;
|
||||||
ELSEIF NOT EXISTS (
|
ELSEIF NOT EXISTS (
|
||||||
SELECT 1 FROM fl_loading_pick_item
|
SELECT 1 FROM fl_loading_pick_item
|
||||||
@@ -1106,7 +1117,6 @@ BEGIN
|
|||||||
OR NOT (NEW.shipping_mark <=> OLD.shipping_mark)
|
OR NOT (NEW.shipping_mark <=> OLD.shipping_mark)
|
||||||
OR NOT (NEW.load_sequence <=> OLD.load_sequence)
|
OR NOT (NEW.load_sequence <=> OLD.load_sequence)
|
||||||
OR NOT (NEW.position_snapshot <=> OLD.position_snapshot)
|
OR NOT (NEW.position_snapshot <=> OLD.position_snapshot)
|
||||||
OR NOT (NEW.internal_remark <=> OLD.internal_remark)
|
|
||||||
OR NOT (NEW.customer_remark <=> OLD.customer_remark)
|
OR NOT (NEW.customer_remark <=> OLD.customer_remark)
|
||||||
OR NOT (NEW.frozen_at <=> OLD.frozen_at)
|
OR NOT (NEW.frozen_at <=> OLD.frozen_at)
|
||||||
OR NOT (NEW.frozen_by <=> OLD.frozen_by)
|
OR NOT (NEW.frozen_by <=> OLD.frozen_by)
|
||||||
|
|||||||
@@ -774,20 +774,21 @@ paths:
|
|||||||
content: {application/json: {schema: {$ref: '#/components/schemas/SmartLoadingRemoveUnpickedRequest'}}}
|
content: {application/json: {schema: {$ref: '#/components/schemas/SmartLoadingRemoveUnpickedRequest'}}}
|
||||||
responses:
|
responses:
|
||||||
'200': {description: 成功或业务失败,data 为最新 SmartLoadingPlan}
|
'200': {description: 成功或业务失败,data 为最新 SmartLoadingPlan}
|
||||||
/api/platform/v1/first-leg/smart-loading/plans/{planId}/items/{itemId}/warehouse-remark:
|
/api/platform/v1/first-leg/smart-loading/plans/{planId}/items/{itemId}/remarks:
|
||||||
patch:
|
patch:
|
||||||
summary: 修改装柜明细仓库备注
|
summary: 修改装柜明细协作备注
|
||||||
description: >-
|
description: >-
|
||||||
需要 platform:firstleg:preload:edit 权限。EDITING/LOADING 可修改或清空 warehouseRemark;
|
需要 platform:firstleg:preload:edit 权限。客户备注只读;EDITING/LOADING 可修改或清空
|
||||||
|
internalRemark、warehouseRemark 和 customerServiceRemark;
|
||||||
COMPLETED、计划/明细版本冲突返回 A0409 且事务零写入,写入历史且与 PDA final complete 使用同一并发门禁。
|
COMPLETED、计划/明细版本冲突返回 A0409 且事务零写入,写入历史且与 PDA final complete 使用同一并发门禁。
|
||||||
成功响应中的 version 是递增后的 planVersion,items[].version 是递增后的 itemVersion;调用方同时使用
|
成功响应中的 version 是递增后的 planVersion,items[].version 是递增后的 itemVersion;调用方同时使用
|
||||||
canEdit、items[].canAdjust/canRemove 与自身权限决定操作可用性。数量调整或动态增量不会覆盖已有 warehouseRemark。
|
canEdit、items[].canAdjust/canRemove 与自身权限决定操作可用性。数量调整或动态增量不会覆盖已有协作备注。
|
||||||
parameters:
|
parameters:
|
||||||
- {name: planId, in: path, required: true, schema: {type: string, pattern: '^[1-9][0-9]*$'}}
|
- {name: planId, in: path, required: true, schema: {type: string, pattern: '^[1-9][0-9]*$'}}
|
||||||
- {name: itemId, in: path, required: true, schema: {type: string, pattern: '^[1-9][0-9]*$'}}
|
- {name: itemId, in: path, required: true, schema: {type: string, pattern: '^[1-9][0-9]*$'}}
|
||||||
requestBody:
|
requestBody:
|
||||||
required: true
|
required: true
|
||||||
content: {application/json: {schema: {$ref: '#/components/schemas/SmartLoadingWarehouseRemarkRequest'}}}
|
content: {application/json: {schema: {$ref: '#/components/schemas/SmartLoadingRemarksRequest'}}}
|
||||||
responses:
|
responses:
|
||||||
'200': {description: 成功或业务失败,data 为最新 SmartLoadingPlan}
|
'200': {description: 成功或业务失败,data 为最新 SmartLoadingPlan}
|
||||||
/openapi/v1/inbound-measurements:
|
/openapi/v1/inbound-measurements:
|
||||||
@@ -2323,11 +2324,13 @@ components:
|
|||||||
properties:
|
properties:
|
||||||
expectedPlanVersion: {type: integer, minimum: 0}
|
expectedPlanVersion: {type: integer, minimum: 0}
|
||||||
operationId: {type: string, minLength: 1, maxLength: 128}
|
operationId: {type: string, minLength: 1, maxLength: 128}
|
||||||
SmartLoadingWarehouseRemarkRequest:
|
SmartLoadingRemarksRequest:
|
||||||
type: object
|
type: object
|
||||||
required: [expectedPlanVersion, expectedItemVersion, operationId]
|
required: [expectedPlanVersion, expectedItemVersion, operationId]
|
||||||
properties:
|
properties:
|
||||||
|
internalRemark: {type: string, nullable: true, maxLength: 1000, description: 内部备注;null/空字符串均表示清空}
|
||||||
warehouseRemark: {type: string, nullable: true, maxLength: 1000, description: 仓库备注;null/空字符串均表示清空}
|
warehouseRemark: {type: string, nullable: true, maxLength: 1000, description: 仓库备注;null/空字符串均表示清空}
|
||||||
|
customerServiceRemark: {type: string, nullable: true, maxLength: 1000, description: 客服备注;null/空字符串均表示清空}
|
||||||
expectedPlanVersion: {type: integer, minimum: 0}
|
expectedPlanVersion: {type: integer, minimum: 0}
|
||||||
expectedItemVersion: {type: integer, minimum: 0}
|
expectedItemVersion: {type: integer, minimum: 0}
|
||||||
operationId: {type: string, minLength: 1, maxLength: 128}
|
operationId: {type: string, minLength: 1, maxLength: 128}
|
||||||
@@ -2518,8 +2521,9 @@ components:
|
|||||||
inboundCode: {type: string, nullable: true}
|
inboundCode: {type: string, nullable: true}
|
||||||
customerName: {type: string, nullable: true}
|
customerName: {type: string, nullable: true}
|
||||||
customerRemark: {type: string, nullable: true, readOnly: true, description: 来源为订单 customerRemark}
|
customerRemark: {type: string, nullable: true, readOnly: true, description: 来源为订单 customerRemark}
|
||||||
internalRemark: {type: string, nullable: true, readOnly: true, description: 来源为订单最新内部备注,不使用 cargoRemark}
|
internalRemark: {type: string, nullable: true, description: 内部备注;预装阶段可修改}
|
||||||
warehouseRemark: {type: string, nullable: true, description: 仓库人员逐条维护,可在 EDITING/LOADING 修改}
|
warehouseRemark: {type: string, nullable: true, description: 仓库人员逐条维护,可在 EDITING/LOADING 修改}
|
||||||
|
customerServiceRemark: {type: string, nullable: true, description: 客服备注;预装阶段可修改}
|
||||||
packageCount: {type: integer, minimum: 1}
|
packageCount: {type: integer, minimum: 1}
|
||||||
totalPackageCount: {type: integer, minimum: 1, description: 当前入仓明细可在计划内分配的总箱数,包含已用与未用箱数}
|
totalPackageCount: {type: integer, minimum: 1, description: 当前入仓明细可在计划内分配的总箱数,包含已用与未用箱数}
|
||||||
remainingPackageCount: {type: integer, minimum: 0, description: 当前入仓明细尚未分配到本计划的箱数}
|
remainingPackageCount: {type: integer, minimum: 0, description: 当前入仓明细尚未分配到本计划的箱数}
|
||||||
|
|||||||
@@ -405,6 +405,7 @@ components:
|
|||||||
internalRemark: {type: string, nullable: true, description: 内部备注}
|
internalRemark: {type: string, nullable: true, description: 内部备注}
|
||||||
customerRemark: {type: string, nullable: true, description: 客户备注}
|
customerRemark: {type: string, nullable: true, description: 客户备注}
|
||||||
warehouseRemark: {type: string, nullable: true, description: 仓库备注}
|
warehouseRemark: {type: string, nullable: true, description: 仓库备注}
|
||||||
|
customerServiceRemark: {type: string, nullable: true, description: 客服备注}
|
||||||
loadSequence: {type: integer, format: int32, minimum: 0, description: 装柜顺序}
|
loadSequence: {type: integer, format: int32, minimum: 0, description: 装柜顺序}
|
||||||
frozenAt: {type: string, format: date-time, nullable: true, description: 明细冻结时间}
|
frozenAt: {type: string, format: date-time, nullable: true, description: 明细冻结时间}
|
||||||
itemVersion: {type: integer, format: int32, minimum: 0, description: 明细乐观锁版本;更新仓库备注时作为 expectedItemVersion}
|
itemVersion: {type: integer, format: int32, minimum: 0, description: 明细乐观锁版本;更新仓库备注时作为 expectedItemVersion}
|
||||||
|
|||||||
+4
-4
@@ -54,7 +54,7 @@ class PlatformSmartLoadingControllerContractTest {
|
|||||||
authority("removeGroup","platform:firstleg:smart-loading:delete",AuthenticatedUser.class,long.class,long.class,RemoveGroupRequest.class);
|
authority("removeGroup","platform:firstleg:smart-loading:delete",AuthenticatedUser.class,long.class,long.class,RemoveGroupRequest.class);
|
||||||
authority("recommend","platform:firstleg:smart-loading:assign",AuthenticatedUser.class,long.class,RecommendRequest.class);
|
authority("recommend","platform:firstleg:smart-loading:assign",AuthenticatedUser.class,long.class,RecommendRequest.class);
|
||||||
authority("adjust","platform:firstleg:smart-loading:assign",AuthenticatedUser.class,long.class,long.class,AdjustRequest.class);
|
authority("adjust","platform:firstleg:smart-loading:assign",AuthenticatedUser.class,long.class,long.class,AdjustRequest.class);
|
||||||
authority("warehouseRemark","platform:firstleg:preload:edit",AuthenticatedUser.class,long.class,long.class,WarehouseRemarkRequest.class);
|
authority("remarks","platform:firstleg:preload:edit",AuthenticatedUser.class,long.class,long.class,RemarksRequest.class);
|
||||||
authority("delete","platform:firstleg:smart-loading:delete",AuthenticatedUser.class,long.class,long.class,DeleteRequest.class);
|
authority("delete","platform:firstleg:smart-loading:delete",AuthenticatedUser.class,long.class,long.class,DeleteRequest.class);
|
||||||
authority("removeUnpicked","platform:firstleg:smart-loading:delete",AuthenticatedUser.class,long.class,RemoveUnpickedRequest.class);
|
authority("removeUnpicked","platform:firstleg:smart-loading:delete",AuthenticatedUser.class,long.class,RemoveUnpickedRequest.class);
|
||||||
}
|
}
|
||||||
@@ -77,13 +77,13 @@ class PlatformSmartLoadingControllerContractTest {
|
|||||||
.contains("SmartLoadingAdjustRequest:")
|
.contains("SmartLoadingAdjustRequest:")
|
||||||
.contains("SmartLoadingDeleteRequest:")
|
.contains("SmartLoadingDeleteRequest:")
|
||||||
.contains("SmartLoadingRemoveUnpickedRequest:")
|
.contains("SmartLoadingRemoveUnpickedRequest:")
|
||||||
.contains("SmartLoadingWarehouseRemarkRequest:")
|
.contains("SmartLoadingRemarksRequest:")
|
||||||
.contains("/items/{itemId}/warehouse-remark:")
|
.contains("/items/{itemId}/remarks:")
|
||||||
.contains("需要 platform:firstleg:preload:edit 权限")
|
.contains("需要 platform:firstleg:preload:edit 权限")
|
||||||
.contains("warehouseRemark: {type: string, nullable: true, maxLength: 1000")
|
.contains("warehouseRemark: {type: string, nullable: true, maxLength: 1000")
|
||||||
.contains("成功响应中的 version 是递增后的 planVersion")
|
.contains("成功响应中的 version 是递增后的 planVersion")
|
||||||
.contains("items[].version 是递增后的 itemVersion")
|
.contains("items[].version 是递增后的 itemVersion")
|
||||||
.contains("数量调整或动态增量不会覆盖已有 warehouseRemark")
|
.contains("数量调整或动态增量不会覆盖已有协作备注")
|
||||||
.contains("柜内总容积和总重量分别允许达到柜型额定值的150%")
|
.contains("柜内总容积和总重量分别允许达到柜型额定值的150%")
|
||||||
.contains("人工分配不限制订单国内仓及渠道与柜次一致")
|
.contains("人工分配不限制订单国内仓及渠道与柜次一致")
|
||||||
.contains("智能推荐允许跨国内仓,但仍只选择柜次渠道")
|
.contains("智能推荐允许跨国内仓,但仍只选择柜次渠道")
|
||||||
|
|||||||
+9
-7
@@ -122,8 +122,8 @@ class SmartLoadingSqlContractTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("仓库备注使用计划先行与明细单表CAS并允许已拣明细仅改备注")
|
@DisplayName("可编辑备注使用计划先行与明细单表CAS并允许已拣明细仅改备注")
|
||||||
void shouldUpdateWarehouseRemarkWithoutMultiTableTriggerConflict() throws Exception {
|
void shouldUpdateEditableRemarksWithoutMultiTableTriggerConflict() throws Exception {
|
||||||
String mapper = Files.readString(Path.of(
|
String mapper = Files.readString(Path.of(
|
||||||
"src/main/java/com/baiyue/firstleg/mapper/LoadingPlanItemMapper.java"), StandardCharsets.UTF_8);
|
"src/main/java/com/baiyue/firstleg/mapper/LoadingPlanItemMapper.java"), StandardCharsets.UTF_8);
|
||||||
assertThat(mapper)
|
assertThat(mapper)
|
||||||
@@ -133,8 +133,8 @@ class SmartLoadingSqlContractTest {
|
|||||||
.contains("i.frozen_at IS NULL")
|
.contains("i.frozen_at IS NULL")
|
||||||
.doesNotContain("UPDATE fl_loading_plan_item i JOIN fl_loading_plan p")
|
.doesNotContain("UPDATE fl_loading_plan_item i JOIN fl_loading_plan p")
|
||||||
.doesNotContain("p.version=p.version+1");
|
.doesNotContain("p.version=p.version+1");
|
||||||
assertWarehouseRemarkTriggerContract(migration());
|
assertEditableRemarksTriggerContract(migration());
|
||||||
assertWarehouseRemarkTriggerContract(unifiedMigration());
|
assertEditableRemarksTriggerContract(unifiedMigration());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void assertContainerLifecycleSchema(String sql) {
|
private static void assertContainerLifecycleSchema(String sql) {
|
||||||
@@ -151,11 +151,13 @@ class SmartLoadingSqlContractTest {
|
|||||||
.contains("column_name='merged_into_container_booking_id'");
|
.contains("column_name='merged_into_container_booking_id'");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void assertWarehouseRemarkTriggerContract(String sql) {
|
private static void assertEditableRemarksTriggerContract(String sql) {
|
||||||
assertThat(sql)
|
assertThat(sql)
|
||||||
.contains("IF NOT (NEW.warehouse_remark <=> OLD.warehouse_remark) THEN")
|
.contains("IF NOT (NEW.internal_remark <=> OLD.internal_remark)")
|
||||||
|
.contains("OR NOT (NEW.warehouse_remark <=> OLD.warehouse_remark)")
|
||||||
|
.contains("OR NOT (NEW.customer_service_remark <=> OLD.customer_service_remark) THEN")
|
||||||
.contains("IF NEW.package_count <> OLD.package_count OR NEW.deleted <> OLD.deleted")
|
.contains("IF NEW.package_count <> OLD.package_count OR NEW.deleted <> OLD.deleted")
|
||||||
.contains("已有PDA拣货记录的装柜明细修改仓库备注时不得变更其他业务字段")
|
.contains("已有PDA拣货记录的装柜明细修改备注时不得变更其他业务字段")
|
||||||
.contains("ELSEIF NOT EXISTS (")
|
.contains("ELSEIF NOT EXISTS (")
|
||||||
.contains("IF OLD.frozen_at IS NOT NULL THEN")
|
.contains("IF OLD.frozen_at IS NOT NULL THEN")
|
||||||
.contains("装柜完成后的明细永久冻结");
|
.contains("装柜完成后的明细永久冻结");
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import com.baiyue.firstleg.dto.SmartLoadingDtos.WorkbenchContainer;
|
|||||||
import com.baiyue.firstleg.dto.SmartLoadingDtos.WorkbenchContainerQuery;
|
import com.baiyue.firstleg.dto.SmartLoadingDtos.WorkbenchContainerQuery;
|
||||||
import com.baiyue.firstleg.dto.SmartLoadingDtos.Plan;
|
import com.baiyue.firstleg.dto.SmartLoadingDtos.Plan;
|
||||||
import com.baiyue.firstleg.dto.SmartLoadingDtos.RecommendRequest;
|
import com.baiyue.firstleg.dto.SmartLoadingDtos.RecommendRequest;
|
||||||
import com.baiyue.firstleg.dto.SmartLoadingDtos.WarehouseRemarkRequest;
|
import com.baiyue.firstleg.dto.SmartLoadingDtos.RemarksRequest;
|
||||||
import com.baiyue.firstleg.entity.ContainerBookingEntity;
|
import com.baiyue.firstleg.entity.ContainerBookingEntity;
|
||||||
import com.baiyue.firstleg.entity.LoadingPlanEntity;
|
import com.baiyue.firstleg.entity.LoadingPlanEntity;
|
||||||
import com.baiyue.firstleg.entity.LoadingPlanHistoryEntity;
|
import com.baiyue.firstleg.entity.LoadingPlanHistoryEntity;
|
||||||
@@ -1267,7 +1267,7 @@ class SmartLoadingServiceTest {
|
|||||||
when(cargoClient.detail(anyString(), eq(9L), eq(30L), eq(20L))).thenReturn(ApiResponse.success(
|
when(cargoClient.detail(anyString(), eq(9L), eq(30L), eq(20L))).thenReturn(ApiResponse.success(
|
||||||
new OrderCargoClient.CargoBinding(20L, 30L, 1, "中文品名", 10,
|
new OrderCargoClient.CargoBinding(20L, 30L, 1, "中文品名", 10,
|
||||||
new BigDecimal("20"), new BigDecimal("2"), "SKU-1", "English name",
|
new BigDecimal("20"), new BigDecimal("2"), "SKU-1", "English name",
|
||||||
4, 100, new BigDecimal("10"), "货物备注", "客户备注", "最新内部备注")));
|
4, 100, new BigDecimal("10"), "货物备注", "客户备注", "最新内部备注", "仓库备注", "客服备注")));
|
||||||
doAnswer(invocation -> {
|
doAnswer(invocation -> {
|
||||||
Object value = invocation.getArgument(0);
|
Object value = invocation.getArgument(0);
|
||||||
if (value instanceof LoadingPlanItemEntity item) item.setId(88L);
|
if (value instanceof LoadingPlanItemEntity item) item.setId(88L);
|
||||||
@@ -1288,6 +1288,8 @@ class SmartLoadingServiceTest {
|
|||||||
assertThat(saved.getNetWeightKg()).isEqualByComparingTo("7.5");
|
assertThat(saved.getNetWeightKg()).isEqualByComparingTo("7.5");
|
||||||
assertThat(saved.getCustomerRemark()).isEqualTo("客户备注");
|
assertThat(saved.getCustomerRemark()).isEqualTo("客户备注");
|
||||||
assertThat(saved.getInternalRemark()).isEqualTo("最新内部备注");
|
assertThat(saved.getInternalRemark()).isEqualTo("最新内部备注");
|
||||||
|
assertThat(saved.getWarehouseRemark()).isEqualTo("仓库备注");
|
||||||
|
assertThat(saved.getCustomerServiceRemark()).isEqualTo("客服备注");
|
||||||
assertThat(saved.getInternalRemark()).isNotEqualTo("货物备注");
|
assertThat(saved.getInternalRemark()).isNotEqualTo("货物备注");
|
||||||
|
|
||||||
ArgumentCaptor<LoadingPlanHistoryEntity> historyCaptor = ArgumentCaptor.forClass(LoadingPlanHistoryEntity.class);
|
ArgumentCaptor<LoadingPlanHistoryEntity> historyCaptor = ArgumentCaptor.forClass(LoadingPlanHistoryEntity.class);
|
||||||
@@ -1451,20 +1453,20 @@ class SmartLoadingServiceTest {
|
|||||||
when(repository.booking(99L)).thenReturn(booking());
|
when(repository.booking(99L)).thenReturn(booking());
|
||||||
when(repository.items(1L)).thenReturn(List.of(line));
|
when(repository.items(1L)).thenReturn(List.of(line));
|
||||||
when(repository.touch(1L, 2, 7L, "u")).thenReturn(1);
|
when(repository.touch(1L, 2, 7L, "u")).thenReturn(1);
|
||||||
when(repository.updateWarehouseRemark(1L, 8L, 3, 0, null, 7L, "u")).thenReturn(1);
|
when(repository.updateRemarks(1L, 8L, 3, 0, null, null, null, 7L, "u")).thenReturn(1);
|
||||||
|
|
||||||
Plan result = service.updateWarehouseRemark(1L, 8L,
|
Plan result = service.updateRemarks(1L, 8L,
|
||||||
new WarehouseRemarkRequest(" ", 2, 0, "warehouse-clear"), 7L, "u");
|
new RemarksRequest(null, " ", null, 2, 0, "warehouse-clear"), 7L, "u");
|
||||||
|
|
||||||
assertThat(result.status()).isEqualTo("LOADING");
|
assertThat(result.status()).isEqualTo("LOADING");
|
||||||
verify(loadingPickService).lockMutable(1L);
|
verify(loadingPickService).lockMutable(1L);
|
||||||
var order = inOrder(loadingPickService, repository);
|
var order = inOrder(loadingPickService, repository);
|
||||||
order.verify(loadingPickService).lockMutable(1L);
|
order.verify(loadingPickService).lockMutable(1L);
|
||||||
order.verify(repository).touch(1L, 2, 7L, "u");
|
order.verify(repository).touch(1L, 2, 7L, "u");
|
||||||
order.verify(repository).updateWarehouseRemark(1L, 8L, 3, 0, null, 7L, "u");
|
order.verify(repository).updateRemarks(1L, 8L, 3, 0, null, null, null, 7L, "u");
|
||||||
ArgumentCaptor<LoadingPlanHistoryEntity> history = ArgumentCaptor.forClass(LoadingPlanHistoryEntity.class);
|
ArgumentCaptor<LoadingPlanHistoryEntity> history = ArgumentCaptor.forClass(LoadingPlanHistoryEntity.class);
|
||||||
verify(repository).insert(history.capture());
|
verify(repository).insert(history.capture());
|
||||||
assertThat(history.getValue().getActionType()).isEqualTo("WAREHOUSE_REMARK");
|
assertThat(history.getValue().getActionType()).isEqualTo("REMARKS");
|
||||||
assertThat(history.getValue().getBeforeSnapshot()).contains("旧仓库备注");
|
assertThat(history.getValue().getBeforeSnapshot()).contains("旧仓库备注");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1477,14 +1479,14 @@ class SmartLoadingServiceTest {
|
|||||||
when(repository.booking(99L)).thenReturn(booking());
|
when(repository.booking(99L)).thenReturn(booking());
|
||||||
when(repository.items(1L)).thenReturn(List.of(line));
|
when(repository.items(1L)).thenReturn(List.of(line));
|
||||||
when(repository.touch(1L, 1, 7L, "u")).thenReturn(1);
|
when(repository.touch(1L, 1, 7L, "u")).thenReturn(1);
|
||||||
when(repository.updateWarehouseRemark(1L, 8L, 2, 0, "待核对外箱", 7L, "u")).thenReturn(1);
|
when(repository.updateRemarks(1L, 8L, 2, 0, null, "待核对外箱", null, 7L, "u")).thenReturn(1);
|
||||||
|
|
||||||
Plan result = service.updateWarehouseRemark(1L, 8L,
|
Plan result = service.updateRemarks(1L, 8L,
|
||||||
new WarehouseRemarkRequest("待核对外箱", 1, 0, "warehouse-editing"), 7L, "u");
|
new RemarksRequest(null, "待核对外箱", null, 1, 0, "warehouse-editing"), 7L, "u");
|
||||||
|
|
||||||
assertThat(result.status()).isEqualTo("EDITING");
|
assertThat(result.status()).isEqualTo("EDITING");
|
||||||
assertThat(result.version()).isEqualTo(2);
|
assertThat(result.version()).isEqualTo(2);
|
||||||
verify(repository).updateWarehouseRemark(1L, 8L, 2, 0, "待核对外箱", 7L, "u");
|
verify(repository).updateRemarks(1L, 8L, 2, 0, null, "待核对外箱", null, 7L, "u");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -1497,13 +1499,13 @@ class SmartLoadingServiceTest {
|
|||||||
when(repository.booking(99L)).thenReturn(booking());
|
when(repository.booking(99L)).thenReturn(booking());
|
||||||
when(repository.items(1L)).thenReturn(List.of(line));
|
when(repository.items(1L)).thenReturn(List.of(line));
|
||||||
|
|
||||||
Plan result = service.updateWarehouseRemark(1L, 8L,
|
Plan result = service.updateRemarks(1L, 8L,
|
||||||
new WarehouseRemarkRequest(" 待核对外箱 ", 2, 0, "warehouse-noop"), 7L, "u");
|
new RemarksRequest(null, " 待核对外箱 ", null, 2, 0, "warehouse-noop"), 7L, "u");
|
||||||
|
|
||||||
assertThat(result.version()).isEqualTo(2);
|
assertThat(result.version()).isEqualTo(2);
|
||||||
verify(repository, never()).touch(anyLong(), anyInt(), anyLong(), anyString());
|
verify(repository, never()).touch(anyLong(), anyInt(), anyLong(), anyString());
|
||||||
verify(repository, never()).updateWarehouseRemark(anyLong(), anyLong(), anyInt(), anyInt(),
|
verify(repository, never()).updateRemarks(anyLong(), anyLong(), anyInt(), anyInt(),
|
||||||
any(), anyLong(), anyString());
|
any(), any(), any(), anyLong(), anyString());
|
||||||
verify(repository, never()).insert(any(LoadingPlanHistoryEntity.class));
|
verify(repository, never()).insert(any(LoadingPlanHistoryEntity.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1514,10 +1516,10 @@ class SmartLoadingServiceTest {
|
|||||||
when(repository.plan(1L)).thenReturn(plan);
|
when(repository.plan(1L)).thenReturn(plan);
|
||||||
when(repository.item(1L, 8L)).thenReturn(line);
|
when(repository.item(1L, 8L)).thenReturn(line);
|
||||||
when(repository.touch(1L, 2, 7L, "u")).thenReturn(1);
|
when(repository.touch(1L, 2, 7L, "u")).thenReturn(1);
|
||||||
when(repository.updateWarehouseRemark(1L, 8L, 3, 0, "新备注", 7L, "u")).thenReturn(0);
|
when(repository.updateRemarks(1L, 8L, 3, 0, null, "新备注", null, 7L, "u")).thenReturn(0);
|
||||||
|
|
||||||
BizException error = catchThrowableOfType(() -> service.updateWarehouseRemark(1L, 8L,
|
BizException error = catchThrowableOfType(() -> service.updateRemarks(1L, 8L,
|
||||||
new WarehouseRemarkRequest("新备注", 2, 0, "warehouse-conflict"), 7L, "u"), BizException.class);
|
new RemarksRequest(null, "新备注", null, 2, 0, "warehouse-conflict"), 7L, "u"), BizException.class);
|
||||||
|
|
||||||
assertThat(error.getResultCode()).isEqualTo(ResultCode.CONFLICT);
|
assertThat(error.getResultCode()).isEqualTo(ResultCode.CONFLICT);
|
||||||
verify(repository).touch(1L, 2, 7L, "u");
|
verify(repository).touch(1L, 2, 7L, "u");
|
||||||
@@ -1531,15 +1533,15 @@ class SmartLoadingServiceTest {
|
|||||||
when(repository.plan(1L)).thenReturn(plan);
|
when(repository.plan(1L)).thenReturn(plan);
|
||||||
when(repository.item(1L, 8L)).thenReturn(line);
|
when(repository.item(1L, 8L)).thenReturn(line);
|
||||||
when(repository.touch(1L, 2, 7L, "u")).thenReturn(1);
|
when(repository.touch(1L, 2, 7L, "u")).thenReturn(1);
|
||||||
when(repository.updateWarehouseRemark(1L, 8L, 3, 0, "新备注", 7L, "u"))
|
when(repository.updateRemarks(1L, 8L, 3, 0, null, "新备注", null, 7L, "u"))
|
||||||
.thenThrow(new DataIntegrityViolationException("trigger",
|
.thenThrow(new DataIntegrityViolationException("trigger",
|
||||||
new SQLException("状态冲突", "45000")));
|
new SQLException("状态冲突", "45000")));
|
||||||
|
|
||||||
BizException error = catchThrowableOfType(() -> service.updateWarehouseRemark(1L, 8L,
|
BizException error = catchThrowableOfType(() -> service.updateRemarks(1L, 8L,
|
||||||
new WarehouseRemarkRequest("新备注", 2, 0, "warehouse-trigger"), 7L, "u"), BizException.class);
|
new RemarksRequest(null, "新备注", null, 2, 0, "warehouse-trigger"), 7L, "u"), BizException.class);
|
||||||
|
|
||||||
assertThat(error.getResultCode()).isEqualTo(ResultCode.CONFLICT);
|
assertThat(error.getResultCode()).isEqualTo(ResultCode.CONFLICT);
|
||||||
assertThat(error.getMessage()).contains("仓库备注未保存").contains("刷新后重试");
|
assertThat(error.getMessage()).contains("备注未保存").contains("刷新后重试");
|
||||||
verify(repository, never()).insert(any(LoadingPlanHistoryEntity.class));
|
verify(repository, never()).insert(any(LoadingPlanHistoryEntity.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1547,46 +1549,46 @@ class SmartLoadingServiceTest {
|
|||||||
void stalePlanVersionRejectsWarehouseRemarkBeforeItemWrite() {
|
void stalePlanVersionRejectsWarehouseRemarkBeforeItemWrite() {
|
||||||
when(repository.plan(1L)).thenReturn(plan("LOADING", 3));
|
when(repository.plan(1L)).thenReturn(plan("LOADING", 3));
|
||||||
|
|
||||||
BizException error = catchThrowableOfType(() -> service.updateWarehouseRemark(1L, 8L,
|
BizException error = catchThrowableOfType(() -> service.updateRemarks(1L, 8L,
|
||||||
new WarehouseRemarkRequest("新备注", 2, 0, "warehouse-stale-plan"), 7L, "u"), BizException.class);
|
new RemarksRequest(null, "新备注", null, 2, 0, "warehouse-stale-plan"), 7L, "u"), BizException.class);
|
||||||
|
|
||||||
assertThat(error.getResultCode()).isEqualTo(ResultCode.CONFLICT);
|
assertThat(error.getResultCode()).isEqualTo(ResultCode.CONFLICT);
|
||||||
verify(repository, never()).touch(anyLong(), anyInt(), anyLong(), anyString());
|
verify(repository, never()).touch(anyLong(), anyInt(), anyLong(), anyString());
|
||||||
verify(repository, never()).updateWarehouseRemark(anyLong(), anyLong(), anyInt(), anyInt(),
|
verify(repository, never()).updateRemarks(anyLong(), anyLong(), anyInt(), anyInt(),
|
||||||
any(), anyLong(), anyString());
|
any(), any(), any(), anyLong(), anyString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void completedPlanRejectsWarehouseRemarkBeforeAnyWrite() {
|
void completedPlanRejectsWarehouseRemarkBeforeAnyWrite() {
|
||||||
when(repository.plan(1L)).thenReturn(plan("COMPLETED", 4));
|
when(repository.plan(1L)).thenReturn(plan("COMPLETED", 4));
|
||||||
|
|
||||||
BizException error = catchThrowableOfType(() -> service.updateWarehouseRemark(1L, 8L,
|
BizException error = catchThrowableOfType(() -> service.updateRemarks(1L, 8L,
|
||||||
new WarehouseRemarkRequest("新备注", 4, 0, "warehouse-completed"), 7L, "u"), BizException.class);
|
new RemarksRequest(null, "新备注", null, 4, 0, "warehouse-completed"), 7L, "u"), BizException.class);
|
||||||
|
|
||||||
assertThat(error.getResultCode()).isEqualTo(ResultCode.CONFLICT);
|
assertThat(error.getResultCode()).isEqualTo(ResultCode.CONFLICT);
|
||||||
verify(loadingPickService, never()).lockMutable(anyLong());
|
verify(loadingPickService, never()).lockMutable(anyLong());
|
||||||
verify(repository, never()).item(anyLong(), anyLong());
|
verify(repository, never()).item(anyLong(), anyLong());
|
||||||
verify(repository, never()).updateWarehouseRemark(anyLong(), anyLong(), anyInt(), anyInt(),
|
verify(repository, never()).updateRemarks(anyLong(), anyLong(), anyInt(), anyInt(),
|
||||||
any(), anyLong(), anyString());
|
any(), any(), any(), anyLong(), anyString());
|
||||||
verify(repository, never()).insert(any(LoadingPlanHistoryEntity.class));
|
verify(repository, never()).insert(any(LoadingPlanHistoryEntity.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void exactWarehouseRemarkReplayIsReadOnly() {
|
void exactWarehouseRemarkReplayIsReadOnly() {
|
||||||
String requestHash = sha256("WAREHOUSE_REMARK|1|8|备注|2|0");
|
String requestHash = sha256("REMARKS|1|8||备注||2|0");
|
||||||
when(repository.operation("warehouse-replay"))
|
when(repository.operation("warehouse-replay"))
|
||||||
.thenReturn(history(1L, 8L, "WAREHOUSE_REMARK", requestHash));
|
.thenReturn(history(1L, 8L, "REMARKS", requestHash));
|
||||||
when(repository.plan(1L)).thenReturn(plan("LOADING", 3));
|
when(repository.plan(1L)).thenReturn(plan("LOADING", 3));
|
||||||
when(repository.booking(99L)).thenReturn(booking());
|
when(repository.booking(99L)).thenReturn(booking());
|
||||||
when(repository.items(1L)).thenReturn(List.of(item(8, 9, 20, 1)));
|
when(repository.items(1L)).thenReturn(List.of(item(8, 9, 20, 1)));
|
||||||
|
|
||||||
Plan result = service.updateWarehouseRemark(1L, 8L,
|
Plan result = service.updateRemarks(1L, 8L,
|
||||||
new WarehouseRemarkRequest("备注", 2, 0, "warehouse-replay"), 7L, "u");
|
new RemarksRequest(null, "备注", null, 2, 0, "warehouse-replay"), 7L, "u");
|
||||||
|
|
||||||
assertThat(result.version()).isEqualTo(3);
|
assertThat(result.version()).isEqualTo(3);
|
||||||
verify(loadingPickService, never()).lockMutable(anyLong());
|
verify(loadingPickService, never()).lockMutable(anyLong());
|
||||||
verify(repository, never()).updateWarehouseRemark(anyLong(), anyLong(), anyInt(), anyInt(),
|
verify(repository, never()).updateRemarks(anyLong(), anyLong(), anyInt(), anyInt(),
|
||||||
any(), anyLong(), anyString());
|
any(), any(), any(), anyLong(), anyString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -57,14 +57,11 @@ public class InternalOrderCargoController {
|
|||||||
}
|
}
|
||||||
var order = orders.findOwned(tenantId, orderId);
|
var order = orders.findOwned(tenantId, orderId);
|
||||||
if (order == null) throw new BizException(ResultCode.NOT_FOUND, "订单不存在或不属于当前客户");
|
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(),
|
return ApiResponse.success(new CargoBinding(cargo.getId(), orderId, cargo.getItemNo(),
|
||||||
cargo.getCargoName(), cargo.getPackageCount(), cargo.getGrossWeightKg(), cargo.getVolumeCbm(),
|
cargo.getCargoName(), cargo.getPackageCount(), cargo.getGrossWeightKg(), cargo.getVolumeCbm(),
|
||||||
cargo.getSkuNo(), cargo.getCargoNameEn(), cargo.getUnitsPerPackage(), cargo.getTotalQuantity(),
|
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}")
|
@GetMapping("/{tenantId}/{orderId}")
|
||||||
@@ -167,12 +164,13 @@ public class InternalOrderCargoController {
|
|||||||
java.math.BigDecimal originalVolumeCbm, String skuNo, String cargoNameEn,
|
java.math.BigDecimal originalVolumeCbm, String skuNo, String cargoNameEn,
|
||||||
Integer unitsPerPackage, Integer totalQuantity,
|
Integer unitsPerPackage, Integer totalQuantity,
|
||||||
java.math.BigDecimal netWeightKg, String cargoRemark,
|
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,
|
public CargoBinding(Long cargoId, Long orderId, Integer itemNo, String cargoName,
|
||||||
Integer originalPackageCount, java.math.BigDecimal originalGrossWeightKg,
|
Integer originalPackageCount, java.math.BigDecimal originalGrossWeightKg,
|
||||||
java.math.BigDecimal originalVolumeCbm) {
|
java.math.BigDecimal originalVolumeCbm) {
|
||||||
this(cargoId, orderId, itemNo, cargoName, originalPackageCount,
|
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,
|
public CargoBinding(Long cargoId, Long orderId, Integer itemNo, String cargoName,
|
||||||
Integer originalPackageCount, java.math.BigDecimal originalGrossWeightKg,
|
Integer originalPackageCount, java.math.BigDecimal originalGrossWeightKg,
|
||||||
@@ -181,7 +179,7 @@ public class InternalOrderCargoController {
|
|||||||
String cargoRemark) {
|
String cargoRemark) {
|
||||||
this(cargoId, orderId, itemNo, cargoName, originalPackageCount, originalGrossWeightKg,
|
this(cargoId, orderId, itemNo, cargoName, originalPackageCount, originalGrossWeightKg,
|
||||||
originalVolumeCbm, skuNo, cargoNameEn, unitsPerPackage, totalQuantity, netWeightKg,
|
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);
|
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")
|
@PutMapping("/{id}/express")
|
||||||
@PreAuthorize("hasAuthority('platform:order:fulfillment')")
|
@PreAuthorize("hasAuthority('platform:order:fulfillment')")
|
||||||
|
|||||||
@@ -112,16 +112,16 @@ public class PlatformUnifiedOrderController {
|
|||||||
return ApiResponse.success(splitService.containerSlices(containerBookingId, tenantId));
|
return ApiResponse.success(splitService.containerSlices(containerBookingId, tenantId));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/{orderId}/internal-notes")
|
@PutMapping("/{orderId}/remarks")
|
||||||
@BusinessAudit(event = "ORDER_INTERNAL_NOTE", resourceType = "FIRST_LEG_ORDER", resourceId = "#orderId", description = "Add internal note / 添加内部备注")
|
@BusinessAudit(event = "ORDER_REMARKS_UPDATE", resourceType = "FIRST_LEG_ORDER", resourceId = "#orderId", description = "更新订单协作备注")
|
||||||
@PreAuthorize("hasAuthority('platform:order:note')")
|
@PreAuthorize("hasAuthority('platform:order:note')")
|
||||||
@Operation(summary = "添加订单内部备注", description = "添加仅平台内部可见的订单备注。")
|
@Operation(summary = "更新订单协作备注", description = "客户备注只读;平台可更新内部备注、仓库备注和客服备注。")
|
||||||
public ApiResponse<Long> addInternalNote(
|
public ApiResponse<OrderRequests.RemarksUpdateResponse> updateRemarks(
|
||||||
@AuthenticationPrincipal AuthenticatedUser user,
|
@AuthenticationPrincipal AuthenticatedUser user,
|
||||||
@RequestParam long tenantId,
|
@RequestParam long tenantId,
|
||||||
@PathVariable long orderId,
|
@PathVariable long orderId,
|
||||||
@Valid @RequestBody OrderRequests.NoteRequest request) {
|
@Valid @RequestBody OrderRequests.RemarksUpdateRequest request) {
|
||||||
long scopedTenant = OrderScopeGuard.platform(user, tenantId);
|
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 / 平台内部订单备注")
|
@Schema(name = "OrderRemarksUpdateRequest", description = "平台订单协作备注更新")
|
||||||
public record NoteRequest(
|
public record RemarksUpdateRequest(
|
||||||
@Schema(description = "Internal note content / 内部备注内容", example = "Customer confirmed the shipping schedule / 客户已确认船期", maxLength = 2000, requiredMode = Schema.RequiredMode.REQUIRED)
|
@NotNull @Min(0) Integer expectedVersion,
|
||||||
@NotBlank @Size(max = 2000) String content
|
@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(
|
public record ExpressRequest(
|
||||||
@NotBlank @Size(max = 64)
|
@NotBlank @Size(max = 64)
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ public final class UnifiedOrderResponse {
|
|||||||
MeasurementSummary measurement, PreloadSummary preload,
|
MeasurementSummary measurement, PreloadSummary preload,
|
||||||
LoadingSummary loading, DeliverySummary delivery, TransportSummary transport,
|
LoadingSummary loading, DeliverySummary delivery, TransportSummary transport,
|
||||||
CoLoadGroupSummary coLoadGroup,
|
CoLoadGroupSummary coLoadGroup,
|
||||||
String customerRemark, String latestInternalNote,
|
String customerRemark, String latestInternalNote, String warehouseRemark, String customerServiceRemark,
|
||||||
LocalDateTime createdAt, LocalDateTime updatedAt, LocalDateTime requestedPickupAt
|
LocalDateTime createdAt, LocalDateTime updatedAt, LocalDateTime requestedPickupAt
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,9 @@ public class OrderEntity extends BaseEntity {
|
|||||||
private LocalDateTime expectedArrivalAt;
|
private LocalDateTime expectedArrivalAt;
|
||||||
private BigDecimal estimatedVolumeCbm;
|
private BigDecimal estimatedVolumeCbm;
|
||||||
private String customerRemark;
|
private String customerRemark;
|
||||||
|
private String internalRemark;
|
||||||
|
private String warehouseRemark;
|
||||||
|
private String customerServiceRemark;
|
||||||
private String pickupMethod;
|
private String pickupMethod;
|
||||||
private LocalDateTime requestedPickupAt;
|
private LocalDateTime requestedPickupAt;
|
||||||
private Long receivingWarehouseId;
|
private Long receivingWarehouseId;
|
||||||
|
|||||||
@@ -117,6 +117,21 @@ public class OrderRepository {
|
|||||||
.set(OrderCargoEntity::getUpdatedAt, cargo.getUpdatedAt())
|
.set(OrderCargoEntity::getUpdatedAt, cargo.getUpdatedAt())
|
||||||
.setSql("version = version + 1"));
|
.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 insertCargo(OrderCargoEntity cargo) { cargoMapper.insert(cargo); }
|
||||||
public void insertParty(OrderPartyEntity party) { partyMapper.insert(party); }
|
public void insertParty(OrderPartyEntity party) { partyMapper.insert(party); }
|
||||||
public void insertNote(OrderInternalNoteEntity note) { noteMapper.insert(note); }
|
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),
|
return new OrderDetailResponse(order, repository.findCargo(tenantId, id), repository.findParties(tenantId, id),
|
||||||
List.of(), repository.findHistory(tenantId, id));
|
List.of(), repository.findHistory(tenantId, id));
|
||||||
}
|
}
|
||||||
public Long addNote(long tenantId, long userId, long id, String content) {
|
@Transactional
|
||||||
require(tenantId, id);
|
public OrderRequests.RemarksUpdateResponse updateRemarks(long tenantId, long userId, long id,
|
||||||
OrderInternalNoteEntity note = new OrderInternalNoteEntity();
|
OrderRequests.RemarksUpdateRequest request) {
|
||||||
note.setTenantId(tenantId); note.setOrderId(id); note.setContent(content);
|
OrderEntity order = require(tenantId, id);
|
||||||
note.setCreatedBy(userId); note.setUpdatedBy(userId); repository.insertNote(note); return note.getId();
|
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
|
@Transactional
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import com.baiyue.order.dto.UnifiedOrderQuery;
|
|||||||
import com.baiyue.order.dto.UnifiedOrderResponse;
|
import com.baiyue.order.dto.UnifiedOrderResponse;
|
||||||
import com.baiyue.order.entity.OrderEntity;
|
import com.baiyue.order.entity.OrderEntity;
|
||||||
import com.baiyue.order.entity.OrderCargoEntity;
|
import com.baiyue.order.entity.OrderCargoEntity;
|
||||||
import com.baiyue.order.entity.OrderInternalNoteEntity;
|
|
||||||
import com.baiyue.order.entity.OrderStatus;
|
import com.baiyue.order.entity.OrderStatus;
|
||||||
import com.baiyue.order.repository.OrderRepository;
|
import com.baiyue.order.repository.OrderRepository;
|
||||||
import feign.FeignException;
|
import feign.FeignException;
|
||||||
@@ -66,11 +65,10 @@ public class UnifiedOrderQueryService {
|
|||||||
List<OrderEntity> selected = orderNos.stream().map(orderByNo::get).filter(Objects::nonNull).toList();
|
List<OrderEntity> selected = orderNos.stream().map(orderByNo::get).filter(Objects::nonNull).toList();
|
||||||
List<Long> orderIds = selected.stream().map(OrderEntity::getId).toList();
|
List<Long> orderIds = selected.stream().map(OrderEntity::getId).toList();
|
||||||
Map<Long, FirstlegUnifiedOrderClient.Summary> inboundByOrder = summaries(null, orderIds);
|
Map<Long, FirstlegUnifiedOrderClient.Summary> inboundByOrder = summaries(null, orderIds);
|
||||||
Map<Long, String> latestNotes = latestNotes(null, orderIds);
|
|
||||||
Map<Long, CargoSummary> cargoSummaries = cargoSummaries(null, selected);
|
Map<Long, CargoSummary> cargoSummaries = cargoSummaries(null, selected);
|
||||||
Map<Long, Integer> childCounts = childCounts(selected);
|
Map<Long, Integer> childCounts = childCounts(selected);
|
||||||
return selected.stream().map(order -> row(order, inboundByOrder.get(order.getId()),
|
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,
|
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())
|
Map<Long, FirstlegUnifiedOrderClient.Summary> inboundByOrder = "PENDING_REVIEW".equals(query.mainStatus())
|
||||||
? optionalSummaries(tenantId, sourceOrderIds)
|
? optionalSummaries(tenantId, sourceOrderIds)
|
||||||
: summaries(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, CargoSummary> cargoSummaries = cargoSummaries(tenantId, source.records());
|
||||||
Map<Long, Integer> childCounts = childCounts(source.records());
|
Map<Long, Integer> childCounts = childCounts(source.records());
|
||||||
Map<Long, UnifiedOrderResponse.CoLoadGroupSummary> coLoadByOrder = coLoadGroups(
|
Map<Long, UnifiedOrderResponse.CoLoadGroupSummary> coLoadByOrder = coLoadGroups(
|
||||||
source.records().stream().map(OrderEntity::getId).toList(), includeCoLoadGroups);
|
source.records().stream().map(OrderEntity::getId).toList(), includeCoLoadGroups);
|
||||||
List<UnifiedOrderResponse.Row> rows = source.records().stream()
|
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();
|
childCounts, cargoSummaries.get(order.getId()), coLoadByOrder.get(order.getId()))).toList();
|
||||||
return new PageResult<>(rows, source.page(), source.size(), source.total(), source.pages());
|
return new PageResult<>(rows, source.page(), source.size(), source.total(), source.pages());
|
||||||
}
|
}
|
||||||
@@ -117,7 +114,7 @@ public class UnifiedOrderQueryService {
|
|||||||
orders.findHistory(tenantId, orderId));
|
orders.findHistory(tenantId, orderId));
|
||||||
FirstlegUnifiedOrderClient.Detail inbound = order.getStatus() == OrderStatus.SUBMITTED
|
FirstlegUnifiedOrderClient.Detail inbound = order.getStatus() == OrderStatus.SUBMITTED
|
||||||
? optionalFirstlegDetail(tenantId, orderId) : firstlegDetail(tenantId, orderId);
|
? 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()
|
String creatorName = orderDetail.statusHistory().stream()
|
||||||
.filter(item -> "CREATE".equals(item.getAction()))
|
.filter(item -> "CREATE".equals(item.getAction()))
|
||||||
.map(item -> item.getOperatorName())
|
.map(item -> item.getOperatorName())
|
||||||
@@ -166,14 +163,13 @@ public class UnifiedOrderQueryService {
|
|||||||
List<Long> orderIds = summaries.stream().map(item -> item.inbound().orderId()).toList();
|
List<Long> orderIds = summaries.stream().map(item -> item.inbound().orderId()).toList();
|
||||||
Map<Long, OrderEntity> orderById = orders.findByIds(tenantId, orderIds).stream()
|
Map<Long, OrderEntity> orderById = orders.findByIds(tenantId, orderIds).stream()
|
||||||
.collect(Collectors.toMap(OrderEntity::getId, Function.identity(), (left, right) -> left));
|
.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, CargoSummary> cargoSummaries = cargoSummaries(tenantId, List.copyOf(orderById.values()));
|
||||||
Map<Long, Integer> childCounts = childCounts(List.copyOf(orderById.values()));
|
Map<Long, Integer> childCounts = childCounts(List.copyOf(orderById.values()));
|
||||||
Map<Long, UnifiedOrderResponse.CoLoadGroupSummary> coLoadByOrder = coLoadGroups(
|
Map<Long, UnifiedOrderResponse.CoLoadGroupSummary> coLoadByOrder = coLoadGroups(
|
||||||
orderIds, includeCoLoadGroups);
|
orderIds, includeCoLoadGroups);
|
||||||
List<UnifiedOrderResponse.Row> records = summaries.stream()
|
List<UnifiedOrderResponse.Row> records = summaries.stream()
|
||||||
.map(summary -> row(summary, orderById.get(summary.inbound().orderId()),
|
.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()),
|
cargoSummaries.get(summary.inbound().orderId()),
|
||||||
coLoadByOrder.get(summary.inbound().orderId())))
|
coLoadByOrder.get(summary.inbound().orderId())))
|
||||||
.toList();
|
.toList();
|
||||||
@@ -233,11 +229,6 @@ public class UnifiedOrderQueryService {
|
|||||||
return summary;
|
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) {
|
private Map<Long, CargoSummary> cargoSummaries(Long tenantId, List<OrderEntity> pageOrders) {
|
||||||
if (pageOrders.isEmpty()) return Map.of();
|
if (pageOrders.isEmpty()) return Map.of();
|
||||||
List<Long> orderIds = pageOrders.stream().map(OrderEntity::getId).toList();
|
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(),
|
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),
|
StringUtils.hasText(creatorName) ? creatorName : string(order.getCreatedBy()), skuNos(cargo), totalQuantity(cargo), shipmentId(order, cargo), referenceId(order, cargo),
|
||||||
null, null, null, null, null, coLoadGroup,
|
null, null, null, null, null, coLoadGroup,
|
||||||
order.getCustomerRemark(), latestInternalNote,
|
order.getCustomerRemark(), latestInternalNote, order.getWarehouseRemark(), order.getCustomerServiceRemark(),
|
||||||
order.getCreatedAt(), order.getUpdatedAt(), order.getRequestedPickupAt());
|
order.getCreatedAt(), order.getUpdatedAt(), order.getRequestedPickupAt());
|
||||||
}
|
}
|
||||||
FirstlegUnifiedOrderClient.Inbound inbound = summary.inbound();
|
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),
|
StringUtils.hasText(creatorName) ? creatorName : inbound.createdByName(), skuNos(cargo), totalQuantity(cargo), shipmentId(order, cargo), referenceId(order, cargo), measurement(inbound),
|
||||||
preload(summary.preload()), loading(summary.loading()),
|
preload(summary.preload()), loading(summary.loading()),
|
||||||
delivery(summary.delivery()), transport(inbound), coLoadGroup, order.getCustomerRemark(), latestInternalNote,
|
delivery(summary.delivery()), transport(inbound), coLoadGroup, order.getCustomerRemark(), latestInternalNote,
|
||||||
|
order.getWarehouseRemark(), order.getCustomerServiceRemark(),
|
||||||
order.getCreatedAt(), order.getUpdatedAt(), inbound.requestedPickupAt());
|
order.getCreatedAt(), order.getUpdatedAt(), inbound.requestedPickupAt());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -394,6 +386,7 @@ public class UnifiedOrderQueryService {
|
|||||||
measurement(inbound), preload(summary.preload()), loading(summary.loading()),
|
measurement(inbound), preload(summary.preload()), loading(summary.loading()),
|
||||||
delivery(summary.delivery()), transport(inbound), coLoadGroup,
|
delivery(summary.delivery()), transport(inbound), coLoadGroup,
|
||||||
order == null ? null : order.getCustomerRemark(), latestInternalNote,
|
order == null ? null : order.getCustomerRemark(), latestInternalNote,
|
||||||
|
order == null ? null : order.getWarehouseRemark(), order == null ? null : order.getCustomerServiceRemark(),
|
||||||
inbound.createdAt(), inbound.updatedAt(), inbound.requestedPickupAt());
|
inbound.createdAt(), inbound.updatedAt(), inbound.requestedPickupAt());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ CREATE TABLE IF NOT EXISTS ord_order (
|
|||||||
expected_arrival_at DATETIME(3) NULL COMMENT '客户申报预计到货时间',
|
expected_arrival_at DATETIME(3) NULL COMMENT '客户申报预计到货时间',
|
||||||
estimated_volume_cbm DECIMAL(18,4) NULL COMMENT '平台运营预估体积(立方米),独立于客户下单及实测体积',
|
estimated_volume_cbm DECIMAL(18,4) NULL COMMENT '平台运营预估体积(立方米),独立于客户下单及实测体积',
|
||||||
customer_remark VARCHAR(1000) 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 '取货方式',
|
pickup_method VARCHAR(24) NULL COMMENT '取货方式',
|
||||||
requested_pickup_at DATETIME(3) NULL COMMENT '客户要求平台上门提货的预约时间',
|
requested_pickup_at DATETIME(3) NULL COMMENT '客户要求平台上门提货的预约时间',
|
||||||
receiving_warehouse_id BIGINT UNSIGNED NULL COMMENT '国内收货仓ID',
|
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)
|
KEY idx_order_tenant_warehouse (tenant_id, destination_warehouse_id)
|
||||||
) ENGINE=InnoDB COMMENT='头程订单主表:保存商户委托、目的仓、运输方式和订单当前状态';
|
) 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(
|
SET @order_fulfillment_type_sql = IF(
|
||||||
EXISTS(
|
EXISTS(
|
||||||
SELECT 1 FROM information_schema.columns
|
SELECT 1 FROM information_schema.columns
|
||||||
|
|||||||
@@ -267,10 +267,10 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema: {$ref: '#/components/schemas/UnifiedOrderDetailEnvelope'}
|
schema: {$ref: '#/components/schemas/UnifiedOrderDetailEnvelope'}
|
||||||
'403': {description: 需要平台身份和 platform:order:list 权限}
|
'403': {description: 需要平台身份和 platform:order:list 权限}
|
||||||
/api/platform/v1/orders/{orderId}/internal-notes:
|
/api/platform/v1/orders/{orderId}/remarks:
|
||||||
post:
|
put:
|
||||||
summary: 添加订单内部备注
|
summary: 更新订单协作备注
|
||||||
description: 添加仅平台内部可见的订单备注,需要 platform:order:note 权限。
|
description: 客户备注只读;需要 platform:order:note 权限,可更新或清空内部备注、仓库备注和客服备注。
|
||||||
parameters:
|
parameters:
|
||||||
- $ref: '#/components/parameters/UnifiedOrderId'
|
- $ref: '#/components/parameters/UnifiedOrderId'
|
||||||
- $ref: '#/components/parameters/UnifiedTenantId'
|
- $ref: '#/components/parameters/UnifiedTenantId'
|
||||||
@@ -280,9 +280,12 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
type: object
|
type: object
|
||||||
required: [content]
|
required: [expectedVersion]
|
||||||
properties:
|
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:
|
responses:
|
||||||
'200': {description: 备注添加成功或业务失败,按响应体 code 判断}
|
'200': {description: 备注添加成功或业务失败,按响应体 code 判断}
|
||||||
'403': {description: 需要 platform:order:note 权限}
|
'403': {description: 需要 platform:order:note 权限}
|
||||||
@@ -949,6 +952,8 @@ components:
|
|||||||
coLoadGroup: {$ref: '#/components/schemas/UnifiedCoLoadGroupSummary'}
|
coLoadGroup: {$ref: '#/components/schemas/UnifiedCoLoadGroupSummary'}
|
||||||
customerRemark: {type: string, nullable: true}
|
customerRemark: {type: string, nullable: true}
|
||||||
latestInternalNote: {type: string, nullable: true, description: 客户详情不返回平台内部备注}
|
latestInternalNote: {type: string, nullable: true, description: 客户详情不返回平台内部备注}
|
||||||
|
warehouseRemark: {type: string, nullable: true}
|
||||||
|
customerServiceRemark: {type: string, nullable: true}
|
||||||
createdAt: {type: string, format: date-time, nullable: true}
|
createdAt: {type: string, format: date-time, nullable: true}
|
||||||
updatedAt: {type: string, format: date-time, nullable: true}
|
updatedAt: {type: string, format: date-time, nullable: true}
|
||||||
UnifiedOrderRelation:
|
UnifiedOrderRelation:
|
||||||
|
|||||||
+5
-3
@@ -3,7 +3,6 @@ package com.baiyue.order.controller;
|
|||||||
import com.baiyue.common.core.exception.BizException;
|
import com.baiyue.common.core.exception.BizException;
|
||||||
import com.baiyue.order.entity.OrderCargoEntity;
|
import com.baiyue.order.entity.OrderCargoEntity;
|
||||||
import com.baiyue.order.entity.OrderEntity;
|
import com.baiyue.order.entity.OrderEntity;
|
||||||
import com.baiyue.order.entity.OrderInternalNoteEntity;
|
|
||||||
import com.baiyue.order.repository.OrderRepository;
|
import com.baiyue.order.repository.OrderRepository;
|
||||||
import com.baiyue.order.service.OrderApplicationService;
|
import com.baiyue.order.service.OrderApplicationService;
|
||||||
import com.baiyue.order.service.PrintableInboundSnapshotService;
|
import com.baiyue.order.service.PrintableInboundSnapshotService;
|
||||||
@@ -50,9 +49,10 @@ class InternalOrderCargoControllerTest {
|
|||||||
cargo.setCargoRemark("内部货物备注");
|
cargo.setCargoRemark("内部货物备注");
|
||||||
when(orders.findCargoInheritedByOrder(11L, 21L, 31L)).thenReturn(cargo);
|
when(orders.findCargoInheritedByOrder(11L, 21L, 31L)).thenReturn(cargo);
|
||||||
OrderEntity order = new OrderEntity(); order.setId(21L); order.setCustomerRemark("客户订单备注");
|
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.findOwned(11L, 21L)).thenReturn(order);
|
||||||
when(orders.findNotes(11L, 21L)).thenReturn(List.of(note));
|
|
||||||
|
|
||||||
var response = controller.detail("internal-token", 11L, 21L, 31L);
|
var response = controller.detail("internal-token", 11L, 21L, 31L);
|
||||||
|
|
||||||
@@ -67,6 +67,8 @@ class InternalOrderCargoControllerTest {
|
|||||||
assertThat(response.data().cargoRemark()).isEqualTo("内部货物备注");
|
assertThat(response.data().cargoRemark()).isEqualTo("内部货物备注");
|
||||||
assertThat(response.data().customerRemark()).isEqualTo("客户订单备注");
|
assertThat(response.data().customerRemark()).isEqualTo("客户订单备注");
|
||||||
assertThat(response.data().latestInternalNote()).isEqualTo("最新内部备注");
|
assertThat(response.data().latestInternalNote()).isEqualTo("最新内部备注");
|
||||||
|
assertThat(response.data().warehouseRemark()).isEqualTo("仓库备注");
|
||||||
|
assertThat(response.data().customerServiceRemark()).isEqualTo("客服备注");
|
||||||
verify(orders).findCargoInheritedByOrder(11L, 21L, 31L);
|
verify(orders).findCargoInheritedByOrder(11L, 21L, 31L);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -64,9 +64,9 @@ class OrderControllerPermissionTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void unifiedPlatformInternalNoteRequiresNotePermission() throws Exception {
|
void unifiedPlatformRemarksRequireNotePermission() throws Exception {
|
||||||
assertAuthority(PlatformUnifiedOrderController.class, "addInternalNote", "hasAuthority('platform:order:note')",
|
assertAuthority(PlatformUnifiedOrderController.class, "updateRemarks", "hasAuthority('platform:order:note')",
|
||||||
AuthenticatedUser.class, long.class, long.class, OrderRequests.NoteRequest.class);
|
AuthenticatedUser.class, long.class, long.class, OrderRequests.RemarksUpdateRequest.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertAuthority(Class<?> controller, String methodName, String expected, Class<?>... parameterTypes)
|
private void assertAuthority(Class<?> controller, String methodName, String expected, Class<?>... parameterTypes)
|
||||||
|
|||||||
@@ -697,6 +697,48 @@ class OrderApplicationServiceStateTest {
|
|||||||
.isEqualTo("HDHK26081410000");
|
.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,
|
private OrderApplicationService service(OrderRepository repository, OrderEligibilityGuard eligibility,
|
||||||
OrderMasterDataGuard masterData) {
|
OrderMasterDataGuard masterData) {
|
||||||
OrderApplicationService service = springService;
|
OrderApplicationService service = springService;
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import com.baiyue.order.dto.UnifiedOrderQuery;
|
|||||||
import com.baiyue.order.dto.UnifiedOrderResponse;
|
import com.baiyue.order.dto.UnifiedOrderResponse;
|
||||||
import com.baiyue.order.entity.OrderCargoEntity;
|
import com.baiyue.order.entity.OrderCargoEntity;
|
||||||
import com.baiyue.order.entity.OrderEntity;
|
import com.baiyue.order.entity.OrderEntity;
|
||||||
import com.baiyue.order.entity.OrderInternalNoteEntity;
|
|
||||||
import com.baiyue.order.entity.OrderStatusHistoryEntity;
|
import com.baiyue.order.entity.OrderStatusHistoryEntity;
|
||||||
import com.baiyue.order.entity.OrderStatus;
|
import com.baiyue.order.entity.OrderStatus;
|
||||||
import com.baiyue.order.repository.OrderRepository;
|
import com.baiyue.order.repository.OrderRepository;
|
||||||
@@ -58,14 +57,13 @@ class UnifiedOrderQueryServiceTest {
|
|||||||
OrderEntity order = order(OrderStatus.SUBMITTED);
|
OrderEntity order = order(OrderStatus.SUBMITTED);
|
||||||
order.setInboundCode("RK-77");
|
order.setInboundCode("RK-77");
|
||||||
order.setCustomerRemark("客户要求提货前联系");
|
order.setCustomerRemark("客户要求提货前联系");
|
||||||
OrderInternalNoteEntity note = new OrderInternalNoteEntity();
|
order.setInternalRemark("客服已确认提货时间");
|
||||||
note.setOrderId(77L);
|
order.setWarehouseRemark("仓库请优先处理");
|
||||||
note.setContent("客服已确认提货时间");
|
order.setCustomerServiceRemark("客服已回访");
|
||||||
when(orders.unifiedPage(eq(10001L), eq(query), eq(List.of(OrderStatus.SUBMITTED))))
|
when(orders.unifiedPage(eq(10001L), eq(query), eq(List.of(OrderStatus.SUBMITTED))))
|
||||||
.thenReturn(new PageResult<>(List.of(order), 1, 20, 1, 1));
|
.thenReturn(new PageResult<>(List.of(order), 1, 20, 1, 1));
|
||||||
when(firstleg.summaries(eq("internal-token"), any()))
|
when(firstleg.summaries(eq("internal-token"), any()))
|
||||||
.thenReturn(ApiResponse.success(List.of()));
|
.thenReturn(ApiResponse.success(List.of()));
|
||||||
when(orders.findNotesByOrderIds(10001L, List.of(77L))).thenReturn(List.of(note));
|
|
||||||
|
|
||||||
var result = service.page(10001L, query);
|
var result = service.page(10001L, query);
|
||||||
|
|
||||||
@@ -79,6 +77,8 @@ class UnifiedOrderQueryServiceTest {
|
|||||||
assertThat(row.currentHoldReason()).isNull();
|
assertThat(row.currentHoldReason()).isNull();
|
||||||
assertThat(row.customerRemark()).isEqualTo("客户要求提货前联系");
|
assertThat(row.customerRemark()).isEqualTo("客户要求提货前联系");
|
||||||
assertThat(row.latestInternalNote()).isEqualTo("客服已确认提货时间");
|
assertThat(row.latestInternalNote()).isEqualTo("客服已确认提货时间");
|
||||||
|
assertThat(row.warehouseRemark()).isEqualTo("仓库请优先处理");
|
||||||
|
assertThat(row.customerServiceRemark()).isEqualTo("客服已回访");
|
||||||
assertThat(row.relation()).isNull();
|
assertThat(row.relation()).isNull();
|
||||||
});
|
});
|
||||||
verify(firstleg).summaries(eq("internal-token"), any());
|
verify(firstleg).summaries(eq("internal-token"), any());
|
||||||
|
|||||||
Reference in New Issue
Block a user