暂存提交

This commit is contained in:
2026-08-19 16:24:02 +08:00
parent f5c4bf130a
commit 6acb6178da
354 changed files with 25000 additions and 198 deletions
@@ -0,0 +1,9 @@
package com.baiyue.lastleg.relabel;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@MapperScan("com.baiyue.lastleg.relabel.mapper")
public class RelabelMapperConfiguration {
}
@@ -0,0 +1,53 @@
package com.baiyue.lastleg.relabel.controller;
import com.baiyue.common.core.api.ApiResponse;
import com.baiyue.common.core.api.PageResult;
import com.baiyue.common.security.AuthenticatedUser;
import com.baiyue.lastleg.relabel.dto.RelabelRequests;
import com.baiyue.lastleg.relabel.dto.RelabelResponses;
import com.baiyue.lastleg.relabel.service.RelabelApplicationService;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.server.ResponseStatusException;
@RestController
@RequestMapping("/api/platform/v1/last-leg/relabel-applications")
public class RelabelApplicationController {
@Resource private RelabelApplicationService service;
@PostMapping
public ApiResponse<RelabelResponses.Detail> create(@AuthenticationPrincipal AuthenticatedUser user,
@RequestParam(required = false) String customerId, @Valid @RequestBody RelabelRequests.Create request) {
return ApiResponse.success(service.create(scope(user, customerId), operator(user), request));
}
@GetMapping
public ApiResponse<PageResult<RelabelResponses.Summary>> page(@AuthenticationPrincipal AuthenticatedUser user,
@RequestParam(required = false) String customerId, @Valid RelabelRequests.Query query) {
return ApiResponse.success(service.page(scope(user, customerId), query));
}
@GetMapping("/{relabelNo}")
public ApiResponse<RelabelResponses.Detail> detail(@AuthenticationPrincipal AuthenticatedUser user,
@RequestParam(required = false) String customerId, @PathVariable String relabelNo) {
return ApiResponse.success(service.detail(scope(user, customerId), relabelNo));
}
@PutMapping("/{relabelNo}/status")
public ApiResponse<RelabelResponses.Detail> status(@AuthenticationPrincipal AuthenticatedUser user,
@RequestParam(required = false) String customerId, @PathVariable String relabelNo,
@Valid @RequestBody RelabelRequests.ChangeStatus request) {
return ApiResponse.success(service.changeStatus(scope(user, customerId), relabelNo, operator(user), request));
}
private String scope(AuthenticatedUser user, String requested) {
if (user == null || user.tenantId() == null) throw new ResponseStatusException(HttpStatus.UNAUTHORIZED);
if ("PLATFORM".equals(user.appCode()) && user.tenantId() == 0L) {
if (requested == null || requested.isBlank() || "0".equals(requested)) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Platform request must specify target tenant");
}
return requested.trim();
}
return String.valueOf(user.tenantId());
}
private String operator(AuthenticatedUser user) { return user.userId() == null ? user.username() : String.valueOf(user.userId()); }
}
@@ -0,0 +1,54 @@
package com.baiyue.lastleg.relabel.dto;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import jakarta.validation.constraints.Size;
import java.time.LocalDateTime;
import java.util.List;
public final class RelabelRequests {
private RelabelRequests() {}
public record Create(
@NotBlank @Size(max = 128) String customerOrderNo,
@NotBlank @Size(max = 128) String overseasWarehouseId,
@NotNull LocalDateTime labelDeadline,
@NotBlank @Size(max = 128) String contactName,
@NotBlank @Size(max = 64) String contactPhone,
@Size(max = 1000) String remark,
@NotBlank @Size(max = 255) String idempotencyKey,
@NotEmpty @Size(max = 500) List<@Valid Item> items
) {}
public record Item(
@NotBlank @Size(max = 128) String skuCode,
@Size(max = 128) String productImageFileId,
@Size(max = 255) String productImageFileName,
@Size(max = 1000) String productImageUrl,
@NotBlank @Size(max = 255) String productNameZh,
@Positive int totalPackageCount,
@Positive int notifiedPackageCount,
@NotBlank @Size(max = 128) String shipmentNo,
@Size(max = 255) String trackingCode,
@NotBlank @Size(max = 128) String newSkuCode,
@Size(max = 1000) String labelRequirement,
@Size(max = 128) String relabelFileId,
@Size(max = 255) String relabelFileName,
@Size(max = 1000) String relabelFileUrl
) {}
public record Query(@Min(1) long page, @Min(1) @Max(200) long size, String relabelNo,
String customerOrderNo, String skuCode, String status,
String overseasWarehouseId, LocalDateTime createdFrom, LocalDateTime createdTo) {
public Query { if (page == 0) page = 1; if (size == 0) size = 20; }
}
public record ChangeStatus(@NotBlank @Size(max = 32) String status,
@Size(max = 1000) String remark) {}
}
@@ -0,0 +1,22 @@
package com.baiyue.lastleg.relabel.dto;
import java.time.LocalDateTime;
import java.util.List;
public final class RelabelResponses {
private RelabelResponses() {}
public record Summary(Long id, String relabelNo, Long lastlegOrderId, String lastlegOrderNo,
String customerOrderNo, String overseasWarehouseId, LocalDateTime labelDeadline,
String contactName, String contactPhone, String status, int itemCount,
int totalPackageCount, int notifiedPackageCount, String skuCodes,
LocalDateTime createdAt) {}
public record Item(Long id, String skuCode, String productImageFileId, String productImageFileName,
String productImageUrl, String productNameZh, int totalPackageCount,
int notifiedPackageCount, String shipmentNo, String trackingCode,
String newSkuCode, String labelRequirement, String relabelFileId,
String relabelFileName, String relabelFileUrl) {}
public record History(String fromStatus, String toStatus, String operatorId,
String remark, LocalDateTime createdAt) {}
public record Detail(Summary application, String remark, List<Item> items, List<History> history) {}
}
@@ -0,0 +1,32 @@
package com.baiyue.lastleg.relabel.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("ll_relabel_application")
public class RelabelApplication {
@TableId(type = IdType.INPUT) private Long id;
private String customerId;
private String relabelNo;
private Long lastlegOrderId;
private String customerOrderNo;
private String overseasWarehouseId;
private LocalDateTime labelDeadline;
private String contactName;
private String contactPhone;
private String remark;
private String status;
private Integer itemCount;
private Integer totalPackageCount;
private Integer notifiedPackageCount;
private String idempotencyKey;
private String requestHash;
private String createdBy;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
@@ -0,0 +1,31 @@
package com.baiyue.lastleg.relabel.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("ll_relabel_item")
public class RelabelItem {
@TableId(type = IdType.INPUT) private Long id;
private Long applicationId;
private String customerId;
private String skuCode;
private String productImageFileId;
private String productImageFileName;
private String productImageUrl;
private String productNameZh;
private Integer totalPackageCount;
private Integer notifiedPackageCount;
private String shipmentNo;
private String trackingCode;
private String newSkuCode;
private String labelRequirement;
private String relabelFileId;
private String relabelFileName;
private String relabelFileUrl;
private LocalDateTime createdAt;
}
@@ -0,0 +1,21 @@
package com.baiyue.lastleg.relabel.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("ll_relabel_status_history")
public class RelabelStatusHistory {
@TableId(type = IdType.INPUT) private Long id;
private String customerId;
private Long applicationId;
private String fromStatus;
private String toStatus;
private String operatorId;
private String remark;
private LocalDateTime createdAt;
}
@@ -0,0 +1,19 @@
package com.baiyue.lastleg.relabel.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baiyue.lastleg.relabel.entity.RelabelApplication;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface RelabelApplicationMapper extends BaseMapper<RelabelApplication> {
@Select("SELECT * FROM ll_relabel_application WHERE customer_id=#{customerId} AND relabel_no=#{relabelNo} LIMIT 1")
RelabelApplication findNo(@Param("customerId") String customerId, @Param("relabelNo") String relabelNo);
@Select("SELECT * FROM ll_relabel_application WHERE customer_id=#{customerId} AND idempotency_key=#{key} LIMIT 1")
RelabelApplication findKey(@Param("customerId") String customerId, @Param("key") String key);
@Select("SELECT * FROM ll_relabel_application WHERE customer_id=#{customerId} AND relabel_no=#{relabelNo} LIMIT 1 FOR UPDATE")
RelabelApplication lockNo(@Param("customerId") String customerId, @Param("relabelNo") String relabelNo);
}
@@ -0,0 +1,9 @@
package com.baiyue.lastleg.relabel.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baiyue.lastleg.relabel.entity.RelabelItem;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface RelabelItemMapper extends BaseMapper<RelabelItem> {
}
@@ -0,0 +1,9 @@
package com.baiyue.lastleg.relabel.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baiyue.lastleg.relabel.entity.RelabelStatusHistory;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface RelabelStatusHistoryMapper extends BaseMapper<RelabelStatusHistory> {
}
@@ -0,0 +1,264 @@
package com.baiyue.lastleg.relabel.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baiyue.common.core.api.PageResult;
import com.baiyue.lastleg.entity.LastlegOrder;
import com.baiyue.lastleg.entity.LastlegOrderStatusHistory;
import com.baiyue.lastleg.mapper.LastlegOrderMapper;
import com.baiyue.lastleg.mapper.LastlegOrderStatusHistoryMapper;
import com.baiyue.lastleg.relabel.dto.RelabelRequests;
import com.baiyue.lastleg.relabel.dto.RelabelResponses;
import com.baiyue.lastleg.relabel.entity.RelabelApplication;
import com.baiyue.lastleg.relabel.entity.RelabelItem;
import com.baiyue.lastleg.relabel.entity.RelabelStatusHistory;
import com.baiyue.lastleg.relabel.mapper.RelabelApplicationMapper;
import com.baiyue.lastleg.relabel.mapper.RelabelItemMapper;
import com.baiyue.lastleg.relabel.mapper.RelabelStatusHistoryMapper;
import com.baiyue.lastleg.service.LastlegOrderNoGenerator;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.Resource;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ResponseStatusException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HexFormat;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
@Service
public class RelabelApplicationService {
public static final String PENDING_PROCESSING = "PENDING_PROCESSING";
private static final Set<String> STATUSES = Set.of(PENDING_PROCESSING, "PROCESSING", "COMPLETED", "REJECTED", "CANCELLED");
@Resource private RelabelApplicationMapper applications;
@Resource private RelabelItemMapper items;
@Resource private RelabelStatusHistoryMapper histories;
@Resource private LastlegOrderMapper orders;
@Resource private LastlegOrderStatusHistoryMapper orderHistories;
@Resource private LastlegOrderNoGenerator orderNoGenerator;
@Resource private ObjectMapper objectMapper;
@Transactional
public RelabelResponses.Detail create(String customerId, String operatorId, RelabelRequests.Create request) {
int total = 0;
int notified = 0;
for (RelabelRequests.Item item : request.items()) {
if (item.notifiedPackageCount() > item.totalPackageCount()) {
throw badRequest("Notified relocation packages cannot exceed total packages for SKU " + item.skuCode());
}
total = Math.addExact(total, item.totalPackageCount());
notified = Math.addExact(notified, item.notifiedPackageCount());
}
String requestHash = hash(request);
RelabelApplication replay = applications.findKey(customerId, request.idempotencyKey().trim());
if (replay != null) {
if (!requestHash.equals(replay.getRequestHash())) throw conflict("Idempotency key was reused with different relabel data");
return detail(customerId, replay.getRelabelNo());
}
LocalDateTime now = LocalDateTime.now();
long applicationId = IdWorker.getId();
String relabelNo = "FBR" + now.format(DateTimeFormatter.BASIC_ISO_DATE) + applicationId;
LastlegOrder order = new LastlegOrder();
order.setId(IdWorker.getId());
order.setOrderNo(orderNoGenerator.next());
order.setCustomerId(customerId);
order.setOrderType("RELABEL_APPLICATION");
order.setInboundCode(relabelNo);
order.setSourceRegion("CUSTOMER");
order.setSourceInboundId(relabelNo);
order.setSourceOrderNo(request.customerOrderNo().trim());
order.setOverseasWarehouseId(request.overseasWarehouseId().trim());
order.setPalletCount(0);
order.setPackageCount(total);
order.setPickedPackageCount(0);
order.setShippedPackageCount(0);
order.setCurrentStatus("PENDING_ASSIGNMENT");
order.setSubOrderStatus("RELABEL:" + PENDING_PROCESSING);
order.setRelabelOrderNo(relabelNo);
order.setRelabelStatus(PENDING_PROCESSING);
order.setCustomerRemark(trim(request.remark()));
order.setSourceEventId("CUSTOMER_RELABEL:" + relabelNo);
order.setSourceLoadingPlanId("RELABEL_APPLICATION");
order.setSourcePayloadHash("sha256:" + requestHash);
order.setCreatedAt(now);
order.setUpdatedAt(now);
orders.insert(order);
RelabelApplication application = new RelabelApplication();
application.setId(applicationId);
application.setCustomerId(customerId);
application.setRelabelNo(relabelNo);
application.setLastlegOrderId(order.getId());
application.setCustomerOrderNo(request.customerOrderNo().trim());
application.setOverseasWarehouseId(request.overseasWarehouseId().trim());
application.setLabelDeadline(request.labelDeadline());
application.setContactName(request.contactName().trim());
application.setContactPhone(request.contactPhone().trim());
application.setRemark(trim(request.remark()));
application.setStatus(PENDING_PROCESSING);
application.setItemCount(request.items().size());
application.setTotalPackageCount(total);
application.setNotifiedPackageCount(notified);
application.setIdempotencyKey(request.idempotencyKey().trim());
application.setRequestHash(requestHash);
application.setCreatedBy(trim(operatorId));
application.setCreatedAt(now);
application.setUpdatedAt(now);
applications.insert(application);
for (RelabelRequests.Item source : request.items()) insertItem(customerId, applicationId, source, now);
insertHistory(customerId, applicationId, null, PENDING_PROCESSING, operatorId, "Relabel application submitted", now);
insertOrderHistory(order.getId(), null, order.getCurrentStatus(), "Relabel application submitted", now);
return detail(customerId, relabelNo);
}
@Transactional(readOnly = true)
public PageResult<RelabelResponses.Summary> page(String customerId, RelabelRequests.Query query) {
if (query.createdFrom() != null && query.createdTo() != null && query.createdFrom().isAfter(query.createdTo())) {
throw badRequest("createdFrom must not be after createdTo");
}
LambdaQueryWrapper<RelabelApplication> wrapper = new LambdaQueryWrapper<RelabelApplication>()
.eq(RelabelApplication::getCustomerId, customerId)
.like(StringUtils.hasText(query.relabelNo()), RelabelApplication::getRelabelNo, query.relabelNo())
.like(StringUtils.hasText(query.customerOrderNo()), RelabelApplication::getCustomerOrderNo, query.customerOrderNo())
.eq(StringUtils.hasText(query.status()), RelabelApplication::getStatus, query.status())
.eq(StringUtils.hasText(query.overseasWarehouseId()), RelabelApplication::getOverseasWarehouseId, query.overseasWarehouseId())
.ge(query.createdFrom() != null, RelabelApplication::getCreatedAt, query.createdFrom())
.le(query.createdTo() != null, RelabelApplication::getCreatedAt, query.createdTo())
.apply(StringUtils.hasText(query.skuCode()),
"EXISTS (SELECT 1 FROM ll_relabel_item ri WHERE ri.application_id=ll_relabel_application.id AND ri.customer_id=ll_relabel_application.customer_id AND ri.sku_code LIKE CONCAT('%',{0},'%'))",
query.skuCode())
.orderByDesc(RelabelApplication::getCreatedAt);
Page<RelabelApplication> page = applications.selectPage(new Page<>(query.page(), query.size()), wrapper);
List<RelabelResponses.Summary> records = page.getRecords().stream().map(value -> summary(value, itemList(value.getId()))).toList();
return new PageResult<>(records, page.getCurrent(), page.getSize(), page.getTotal(), page.getPages());
}
@Transactional(readOnly = true)
public RelabelResponses.Detail detail(String customerId, String relabelNo) {
RelabelApplication application = require(customerId, relabelNo);
List<RelabelItem> detailItems = itemList(application.getId());
List<RelabelStatusHistory> statusHistory = histories.selectList(new LambdaQueryWrapper<RelabelStatusHistory>()
.eq(RelabelStatusHistory::getCustomerId, customerId)
.eq(RelabelStatusHistory::getApplicationId, application.getId())
.orderByAsc(RelabelStatusHistory::getCreatedAt));
return new RelabelResponses.Detail(summary(application, detailItems), application.getRemark(),
detailItems.stream().map(this::item).toList(), statusHistory.stream().map(value ->
new RelabelResponses.History(value.getFromStatus(), value.getToStatus(), value.getOperatorId(),
value.getRemark(), value.getCreatedAt())).toList());
}
@Transactional
public RelabelResponses.Detail changeStatus(String customerId, String relabelNo, String operatorId,
RelabelRequests.ChangeStatus request) {
RelabelApplication application = applications.lockNo(customerId, relabelNo);
if (application == null) throw notFound("Relabel application not found");
String next = request.status().trim().toUpperCase();
if (!STATUSES.contains(next)) throw badRequest("Unsupported relabel status");
if (next.equals(application.getStatus())) return detail(customerId, relabelNo);
validateTransition(application.getStatus(), next);
String before = application.getStatus();
LocalDateTime now = LocalDateTime.now();
application.setStatus(next);
application.setUpdatedAt(now);
applications.updateById(application);
LastlegOrder order = orders.selectById(application.getLastlegOrderId());
if (order == null || !customerId.equals(order.getCustomerId())) throw conflict("Linked tail-leg order is missing");
String orderBefore = order.getCurrentStatus();
String mainStatus = STATUSES.contains(orderBefore) ? "PENDING_ASSIGNMENT" : orderBefore;
// A return-relabel tail order joins the normal inbound queue after labels are finished.
String orderStatus = switch (next) {
case "COMPLETED" -> "RETURN_RELABEL".equals(order.getOrderType())
? "PENDING_ASSIGNMENT" : "COMPLETED";
case "REJECTED", "CANCELLED" -> "CANCELLED";
default -> mainStatus;
};
order.setCurrentStatus(orderStatus);
order.setSubOrderStatus("RELABEL:" + next);
order.setRelabelStatus(next);
order.setUpdatedAt(now);
orders.updateById(order);
insertHistory(customerId, application.getId(), before, next, operatorId, request.remark(), now);
if (!Objects.equals(orderBefore, orderStatus)) {
insertOrderHistory(order.getId(), orderBefore, orderStatus, request.remark(), now);
}
return detail(customerId, relabelNo);
}
private void insertItem(String customerId, long applicationId, RelabelRequests.Item source, LocalDateTime now) {
RelabelItem item = new RelabelItem();
item.setId(IdWorker.getId()); item.setApplicationId(applicationId); item.setCustomerId(customerId);
item.setSkuCode(source.skuCode().trim()); item.setProductImageFileId(trim(source.productImageFileId()));
item.setProductImageFileName(trim(source.productImageFileName())); item.setProductImageUrl(trim(source.productImageUrl()));
item.setProductNameZh(source.productNameZh().trim()); item.setTotalPackageCount(source.totalPackageCount());
item.setNotifiedPackageCount(source.notifiedPackageCount()); item.setShipmentNo(source.shipmentNo().trim());
item.setTrackingCode(trim(source.trackingCode())); item.setNewSkuCode(source.newSkuCode().trim());
item.setLabelRequirement(trim(source.labelRequirement())); item.setRelabelFileId(trim(source.relabelFileId()));
item.setRelabelFileName(trim(source.relabelFileName())); item.setRelabelFileUrl(trim(source.relabelFileUrl()));
item.setCreatedAt(now); items.insert(item);
}
private RelabelResponses.Summary summary(RelabelApplication value, List<RelabelItem> detailItems) {
LastlegOrder order = orders.selectById(value.getLastlegOrderId());
String skuCodes = detailItems.stream().map(RelabelItem::getSkuCode).filter(Objects::nonNull).distinct().collect(Collectors.joining(","));
return new RelabelResponses.Summary(value.getId(), value.getRelabelNo(), value.getLastlegOrderId(),
order == null ? null : order.getOrderNo(), value.getCustomerOrderNo(), value.getOverseasWarehouseId(),
value.getLabelDeadline(), value.getContactName(), value.getContactPhone(), value.getStatus(),
Objects.requireNonNullElse(value.getItemCount(), detailItems.size()),
Objects.requireNonNullElse(value.getTotalPackageCount(), 0),
Objects.requireNonNullElse(value.getNotifiedPackageCount(), 0), skuCodes, value.getCreatedAt());
}
private RelabelResponses.Item item(RelabelItem value) {
return new RelabelResponses.Item(value.getId(), value.getSkuCode(), value.getProductImageFileId(),
value.getProductImageFileName(), value.getProductImageUrl(), value.getProductNameZh(),
value.getTotalPackageCount(), value.getNotifiedPackageCount(), value.getShipmentNo(),
value.getTrackingCode(), value.getNewSkuCode(), value.getLabelRequirement(), value.getRelabelFileId(),
value.getRelabelFileName(), value.getRelabelFileUrl());
}
private List<RelabelItem> itemList(Long applicationId) {
return items.selectList(new LambdaQueryWrapper<RelabelItem>().eq(RelabelItem::getApplicationId, applicationId)
.orderByAsc(RelabelItem::getId));
}
private RelabelApplication require(String customerId, String relabelNo) {
RelabelApplication value = applications.findNo(customerId, relabelNo);
if (value == null) throw notFound("Relabel application not found");
return value;
}
private void insertHistory(String customerId, Long id, String from, String to, String operator,
String remark, LocalDateTime now) {
RelabelStatusHistory value = new RelabelStatusHistory(); value.setId(IdWorker.getId()); value.setCustomerId(customerId);
value.setApplicationId(id); value.setFromStatus(from); value.setToStatus(to); value.setOperatorId(trim(operator));
value.setRemark(trim(remark)); value.setCreatedAt(now); histories.insert(value);
}
private void insertOrderHistory(Long id, String from, String to, String remark, LocalDateTime now) {
LastlegOrderStatusHistory value = new LastlegOrderStatusHistory(); value.setOrderId(id); value.setFromStatus(from);
value.setToStatus(to); value.setEventId("RELABEL_APPLICATION"); value.setRemark(trim(remark)); value.setCreatedAt(now);
orderHistories.insert(value);
}
private static void validateTransition(String from, String to) {
boolean allowed = PENDING_PROCESSING.equals(from) && Set.of("PROCESSING", "REJECTED", "CANCELLED").contains(to)
|| "PROCESSING".equals(from) && Set.of("COMPLETED", "REJECTED", "CANCELLED").contains(to);
if (!allowed) throw conflict("Relabel status cannot change from " + from + " to " + to);
}
private String hash(Object value) {
try { return HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256")
.digest(objectMapper.copy().findAndRegisterModules().writeValueAsString(value).getBytes(StandardCharsets.UTF_8))); }
catch (Exception exception) { throw new IllegalStateException(exception); }
}
private static String trim(String value) { return StringUtils.hasText(value) ? value.trim() : null; }
private static ResponseStatusException badRequest(String message) { return new ResponseStatusException(HttpStatus.BAD_REQUEST, message); }
private static ResponseStatusException conflict(String message) { return new ResponseStatusException(HttpStatus.CONFLICT, message); }
private static ResponseStatusException notFound(String message) { return new ResponseStatusException(HttpStatus.NOT_FOUND, message); }
}