优化订单任务列表排序并补充上线文档

This commit is contained in:
andy
2026-07-13 12:20:05 +08:00
parent 51ebc8e767
commit 4b98cf9491
10 changed files with 344 additions and 46 deletions

View File

@@ -38,6 +38,8 @@ public class ReservationOrderEntity {
private Long sourceMessageId;
/** 首次创建该订单的任务 IDCP1-3 允许为空。 */
private Long createdFromTaskId;
/** 订单最近业务活动 UTC 时间,用于订单列表按最新活动排序。 */
private LocalDateTime latestActivityAt;
/** 订单进入 ENDED 状态的 UTC 时间。 */
private LocalDateTime endedAt;
/** 逻辑删除 UTC 时间。 */
@@ -77,6 +79,8 @@ public class ReservationOrderEntity {
public void setSourceMessageId(Long sourceMessageId) { this.sourceMessageId = sourceMessageId; }
public Long getCreatedFromTaskId() { return createdFromTaskId; }
public void setCreatedFromTaskId(Long createdFromTaskId) { this.createdFromTaskId = createdFromTaskId; }
public LocalDateTime getLatestActivityAt() { return latestActivityAt; }
public void setLatestActivityAt(LocalDateTime latestActivityAt) { this.latestActivityAt = latestActivityAt; }
public LocalDateTime getEndedAt() { return endedAt; }
public void setEndedAt(LocalDateTime endedAt) { this.endedAt = endedAt; }
public LocalDateTime getLogicDeletedAt() { return logicDeletedAt; }

View File

@@ -2,11 +2,72 @@ package cn.nianxx.thhotel.workflows.reservation.mapper;
import cn.nianxx.thhotel.workflows.reservation.domain.ReservationOrderEntity;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
/**
* Reservation 订单 Mapper只负责本表持久化访问。
*/
@Mapper
public interface ReservationOrderMapper extends BaseMapper<ReservationOrderEntity> {
/**
* 分页查询前端订单列表。排序按订单维护的最新业务活动时间倒序,避免每次列表查询聚合全量任务。
*/
@Select("""
<script>
SELECT o.*
FROM workflow_reservation_order o
WHERE o.hotel_id = #{hotelId}
AND o.order_visibility != #{hiddenSystemVisibility}
<if test="orderStatus != null and orderStatus != ''">
AND o.order_status = #{orderStatus}
</if>
<if test="groupCode != null and groupCode != ''">
AND (
o.order_business_key LIKE CONCAT('%', #{groupCode}, '%')
OR o.active_business_key LIKE CONCAT('%', #{groupCode}, '%')
OR o.display_name LIKE CONCAT('%', #{groupCode}, '%')
)
</if>
<if test="confirmationNumber != null and confirmationNumber != ''">
AND (
o.order_business_key LIKE CONCAT('%', #{confirmationNumber}, '%')
OR o.active_business_key LIKE CONCAT('%', #{confirmationNumber}, '%')
OR o.display_name LIKE CONCAT('%', #{confirmationNumber}, '%')
)
</if>
<if test="keyword != null and keyword != ''">
AND (
o.order_business_key LIKE CONCAT('%', #{keyword}, '%')
OR o.active_business_key LIKE CONCAT('%', #{keyword}, '%')
OR o.temporary_order_code LIKE CONCAT('%', #{keyword}, '%')
OR o.order_status LIKE CONCAT('%', #{keyword}, '%')
OR o.display_name LIKE CONCAT('%', #{keyword}, '%')
<if test="sourceMessageIds != null and sourceMessageIds.size() > 0">
OR o.source_message_id IN
<foreach collection="sourceMessageIds" item="sourceMessageIdItem" open="(" separator="," close=")">
#{sourceMessageIdItem}
</foreach>
</if>
)
</if>
ORDER BY
COALESCE(o.latest_activity_at, o.updated_at) DESC,
o.updated_at DESC,
o.id DESC
</script>
""")
Page<ReservationOrderEntity> selectFrontendOrderPage(
Page<ReservationOrderEntity> page,
@Param("hotelId") String hotelId,
@Param("orderStatus") String orderStatus,
@Param("groupCode") String groupCode,
@Param("confirmationNumber") String confirmationNumber,
@Param("keyword") String keyword,
@Param("hiddenSystemVisibility") String hiddenSystemVisibility,
@Param("sourceMessageIds") List<Long> sourceMessageIds);
}

View File

@@ -24,6 +24,9 @@ public interface ReservationTaskMapper extends BaseMapper<ReservationTaskEntity>
LEFT JOIN workflow_reservation_order o
ON o.hotel_id = t.hotel_id
AND o.id = t.order_id
LEFT JOIN platform_source_message_inbox sm
ON sm.hotel_id = t.hotel_id
AND sm.id = t.source_message_id
WHERE t.hotel_id = #{hotelId}
<if test="orderId != null">
AND t.order_id = #{orderId}
@@ -61,7 +64,14 @@ public interface ReservationTaskMapper extends BaseMapper<ReservationTaskEntity>
</if>
)
</if>
ORDER BY t.order_id ASC, t.execution_order ASC
<choose>
<when test="orderId != null">
ORDER BY t.execution_order ASC, t.id ASC
</when>
<otherwise>
ORDER BY COALESCE(sm.received_at, t.created_at) DESC, t.created_at DESC, t.id DESC
</otherwise>
</choose>
</script>
""")
Page<ReservationTaskEntity> selectFrontendTaskPage(

View File

@@ -204,6 +204,7 @@ public class MybatisReservationAiWorkflowRepository implements ReservationAiWork
entity.setDisplayName(draft.displayName());
entity.setSourceMessageId(draft.sourceMessageId());
entity.setVersion(0L);
entity.setLatestActivityAt(draft.now());
entity.setCreatedAt(draft.now());
entity.setUpdatedAt(draft.now());
orderMapper.insert(entity);
@@ -249,6 +250,7 @@ public class MybatisReservationAiWorkflowRepository implements ReservationAiWork
entity.setCreatedAt(draft.now());
entity.setUpdatedAt(draft.now());
taskMapper.insert(entity);
touchOrderLatestActivity(draft.hotelId(), draft.orderId(), draft.now());
return entity.getId();
}
@@ -444,41 +446,15 @@ public class MybatisReservationAiWorkflowRepository implements ReservationAiWork
int pageNum,
int pageSize) {
List<Long> sourceMessageIds = keywordSourceMessageIds == null ? List.of() : keywordSourceMessageIds;
Page<ReservationOrderEntity> page = orderMapper.selectPage(Page.of(pageNum, pageSize),
Wrappers.<ReservationOrderEntity>lambdaQuery()
.eq(ReservationOrderEntity::getHotelId, request.hotelId())
.ne(ReservationOrderEntity::getOrderVisibility, ReservationOrderVisibility.HIDDEN_SYSTEM.name())
.eq(hasText(request.orderStatus()),
ReservationOrderEntity::getOrderStatus,
trim(request.orderStatus()))
.and(hasText(request.groupCode()), wrapper -> wrapper
.like(ReservationOrderEntity::getOrderBusinessKey, trim(request.groupCode()))
.or()
.like(ReservationOrderEntity::getActiveBusinessKey, trim(request.groupCode()))
.or()
.like(ReservationOrderEntity::getDisplayName, trim(request.groupCode())))
.and(hasText(request.confirmationNumber()), wrapper -> wrapper
.like(ReservationOrderEntity::getOrderBusinessKey, trim(request.confirmationNumber()))
.or()
.like(ReservationOrderEntity::getActiveBusinessKey, trim(request.confirmationNumber()))
.or()
.like(ReservationOrderEntity::getDisplayName, trim(request.confirmationNumber())))
.and(hasText(request.keyword()), wrapper -> wrapper
.like(ReservationOrderEntity::getOrderBusinessKey, trim(request.keyword()))
.or()
.like(ReservationOrderEntity::getActiveBusinessKey, trim(request.keyword()))
.or()
.like(ReservationOrderEntity::getTemporaryOrderCode, trim(request.keyword()))
.or()
.like(ReservationOrderEntity::getOrderStatus, trim(request.keyword()))
.or()
.like(ReservationOrderEntity::getDisplayName, trim(request.keyword()))
.or(!sourceMessageIds.isEmpty())
.in(!sourceMessageIds.isEmpty(),
ReservationOrderEntity::getSourceMessageId,
sourceMessageIds))
.orderByDesc(ReservationOrderEntity::getUpdatedAt)
.orderByDesc(ReservationOrderEntity::getId));
Page<ReservationOrderEntity> page = orderMapper.selectFrontendOrderPage(
Page.of(pageNum, pageSize),
request.hotelId(),
trim(request.orderStatus()),
trim(request.groupCode()),
trim(request.confirmationNumber()),
trim(request.keyword()),
ReservationOrderVisibility.HIDDEN_SYSTEM.name(),
sourceMessageIds);
return new ReservationPageSnapshot<>(
page.getRecords().stream().map(this::toAiQueryOrderSnapshot).toList(),
page.getTotal(),
@@ -592,6 +568,7 @@ public class MybatisReservationAiWorkflowRepository implements ReservationAiWork
.set(ReservationOrderEntity::getOrderStatus, ReservationOrderStatus.ACTIVE.name())
.set(ReservationOrderEntity::getBusinessKeySource, businessKeySource)
.set(ReservationOrderEntity::getDisplayName, orderBusinessKey)
.set(ReservationOrderEntity::getLatestActivityAt, now)
.set(ReservationOrderEntity::getUpdatedAt, now)
.eq(ReservationOrderEntity::getHotelId, hotelId)
.eq(ReservationOrderEntity::getId, orderId)
@@ -618,7 +595,7 @@ public class MybatisReservationAiWorkflowRepository implements ReservationAiWork
entity.setTaskCardType(taskCardType);
entity.setExecutionOrder(executionOrder);
entity.setUpdatedAt(now);
taskMapper.update(entity, Wrappers.<ReservationTaskEntity>lambdaUpdate()
int updatedTaskCount = taskMapper.update(entity, Wrappers.<ReservationTaskEntity>lambdaUpdate()
.eq(ReservationTaskEntity::getHotelId, hotelId)
.eq(ReservationTaskEntity::getId, taskId));
ReservationTaskCardEntity cardEntity = new ReservationTaskCardEntity();
@@ -627,6 +604,9 @@ public class MybatisReservationAiWorkflowRepository implements ReservationAiWork
taskCardMapper.update(cardEntity, Wrappers.<ReservationTaskCardEntity>lambdaUpdate()
.eq(ReservationTaskCardEntity::getHotelId, hotelId)
.eq(ReservationTaskCardEntity::getTaskId, taskId));
if (updatedTaskCount > 0) {
touchOrderLatestActivity(hotelId, orderId, now);
}
}
/**
@@ -634,11 +614,15 @@ public class MybatisReservationAiWorkflowRepository implements ReservationAiWork
*/
@Override
public boolean updateTaskDraftPayload(String hotelId, Long taskId, String draftPayloadJson, LocalDateTime now) {
return taskCardMapper.update(null, Wrappers.<ReservationTaskCardEntity>lambdaUpdate()
boolean updated = taskCardMapper.update(null, Wrappers.<ReservationTaskCardEntity>lambdaUpdate()
.set(ReservationTaskCardEntity::getDraftPayloadJson, draftPayloadJson)
.set(ReservationTaskCardEntity::getUpdatedAt, now)
.eq(ReservationTaskCardEntity::getHotelId, hotelId)
.eq(ReservationTaskCardEntity::getTaskId, taskId)) > 0;
if (updated) {
touchOrderLatestActivityByTaskId(hotelId, taskId, now);
}
return updated;
}
/**
@@ -669,6 +653,9 @@ public class MybatisReservationAiWorkflowRepository implements ReservationAiWork
.set(ReservationTaskCardEntity::getUpdatedAt, confirmedAt)
.eq(ReservationTaskCardEntity::getHotelId, hotelId)
.eq(ReservationTaskCardEntity::getTaskId, taskId));
if (updatedCardCount > 0) {
touchOrderLatestActivityByTaskId(hotelId, taskId, confirmedAt);
}
return updatedCardCount > 0;
}
@@ -704,6 +691,9 @@ public class MybatisReservationAiWorkflowRepository implements ReservationAiWork
.set(ReservationTaskCardEntity::getUpdatedAt, confirmedAt)
.eq(ReservationTaskCardEntity::getHotelId, hotelId)
.eq(ReservationTaskCardEntity::getTaskId, taskId));
if (updatedCardCount > 0) {
touchOrderLatestActivityByTaskId(hotelId, taskId, confirmedAt);
}
return updatedCardCount > 0;
}
@@ -817,7 +807,7 @@ public class MybatisReservationAiWorkflowRepository implements ReservationAiWork
*/
@Override
public boolean updateTaskStatus(String hotelId, Long taskId, String taskStatus, LocalDateTime now) {
return taskMapper.update(null, Wrappers.<ReservationTaskEntity>lambdaUpdate()
boolean updated = taskMapper.update(null, Wrappers.<ReservationTaskEntity>lambdaUpdate()
.set(ReservationTaskEntity::getTaskStatus, taskStatus)
.set(ReservationTaskEntity::getCompletedAt,
ReservationTaskStatus.COMPLETED.name().equals(taskStatus) ? now : null)
@@ -825,6 +815,10 @@ public class MybatisReservationAiWorkflowRepository implements ReservationAiWork
.setSql("version = version + 1")
.eq(ReservationTaskEntity::getHotelId, hotelId)
.eq(ReservationTaskEntity::getId, taskId)) > 0;
if (updated) {
touchOrderLatestActivityByTaskId(hotelId, taskId, now);
}
return updated;
}
/**
@@ -847,6 +841,7 @@ public class MybatisReservationAiWorkflowRepository implements ReservationAiWork
.set(ReservationOrderEntity::getActiveBusinessKey, null)
.set(ReservationOrderEntity::getLogicDeletedAt, now)
.set(ReservationOrderEntity::getLogicDeletedReason, reason)
.set(ReservationOrderEntity::getLatestActivityAt, now)
.set(ReservationOrderEntity::getUpdatedAt, now)
.eq(ReservationOrderEntity::getHotelId, hotelId)
.eq(ReservationOrderEntity::getId, orderId)
@@ -916,6 +911,40 @@ public class MybatisReservationAiWorkflowRepository implements ReservationAiWork
}));
}
/**
* 按任务定位订单并刷新订单最近业务活动时间,用于任务状态、确认和草稿等写操作。
*/
private void touchOrderLatestActivityByTaskId(String hotelId, Long taskId, LocalDateTime activityAt) {
if (taskId == null) {
return;
}
ReservationTaskEntity task = taskMapper.selectOne(Wrappers.<ReservationTaskEntity>lambdaQuery()
.eq(ReservationTaskEntity::getHotelId, hotelId)
.eq(ReservationTaskEntity::getId, taskId)
.last("LIMIT 1"));
if (task == null) {
return;
}
touchOrderLatestActivity(hotelId, task.getOrderId(), activityAt);
}
/**
* 刷新订单最近业务活动时间。只前进不回退,避免旧事件覆盖更新的用户操作。
*/
private void touchOrderLatestActivity(String hotelId, Long orderId, LocalDateTime activityAt) {
if (!hasText(hotelId) || orderId == null || activityAt == null) {
return;
}
orderMapper.update(null, Wrappers.<ReservationOrderEntity>lambdaUpdate()
.set(ReservationOrderEntity::getLatestActivityAt, activityAt)
.eq(ReservationOrderEntity::getHotelId, hotelId)
.eq(ReservationOrderEntity::getId, orderId)
.and(wrapper -> wrapper
.isNull(ReservationOrderEntity::getLatestActivityAt)
.or()
.lt(ReservationOrderEntity::getLatestActivityAt, activityAt)));
}
/**
* 将任务实体批量转换为 AI 查询任务快照,补充 transition 中的 Skill 元数据。
*/