实现V4目录管理后台CP1后端
This commit is contained in:
@@ -15,6 +15,7 @@ public enum PlatformPermissionCode {
|
||||
RESERVATION_AUDIT_READ,
|
||||
RESERVATION_INVOICE_GENERATE,
|
||||
RESERVATION_ROOMING_LIST_GENERATE,
|
||||
RESERVATION_CATALOG_MANAGE,
|
||||
HOTEL_SWITCH,
|
||||
SYSTEM_AUTH_READ,
|
||||
SYSTEM_USER_MANAGE,
|
||||
|
||||
@@ -324,6 +324,7 @@ public class PlatformIdentityBootstrapRunner implements ApplicationRunner {
|
||||
case RESERVATION_AUDIT_READ -> "读取任务审计";
|
||||
case RESERVATION_INVOICE_GENERATE -> "生成预订发票";
|
||||
case RESERVATION_ROOMING_LIST_GENERATE -> "生成 Rooming List";
|
||||
case RESERVATION_CATALOG_MANAGE -> "管理预订目录";
|
||||
case HOTEL_SWITCH -> "切换酒店";
|
||||
case SYSTEM_AUTH_READ -> "读取当前登录上下文";
|
||||
case SYSTEM_USER_MANAGE -> "管理用户";
|
||||
@@ -345,7 +346,7 @@ public class PlatformIdentityBootstrapRunner implements ApplicationRunner {
|
||||
RESERVATION_TASK_CONFIRM, RESERVATION_MANUAL_REVIEW_RESOLVE,
|
||||
RESERVATION_OPERA_SIM_EXECUTE,
|
||||
RESERVATION_AUDIT_READ, RESERVATION_INVOICE_GENERATE,
|
||||
RESERVATION_ROOMING_LIST_GENERATE -> "RESERVATION";
|
||||
RESERVATION_ROOMING_LIST_GENERATE, RESERVATION_CATALOG_MANAGE -> "RESERVATION";
|
||||
case HOTEL_SWITCH, HOTEL_MANAGE -> "HOTEL";
|
||||
default -> "SYSTEM";
|
||||
};
|
||||
|
||||
@@ -13,9 +13,13 @@ import java.time.LocalDateTime;
|
||||
* @param sourceCode 关联 Source 代码
|
||||
* @param status 目录状态
|
||||
* @param sourceSystem 目录来源
|
||||
* @param externalAccountId 外部系统 Account ID
|
||||
* @param catalogVersion 目录版本
|
||||
* @param lastSyncedAt 最近同步 UTC 时间
|
||||
* @param metadataJson 扩展属性 JSON
|
||||
* @param version 乐观锁版本
|
||||
* @param createdAt 创建 UTC 时间
|
||||
* @param updatedAt 更新 UTC 时间
|
||||
*/
|
||||
public record ReservationV4CatalogAccountSnapshot(
|
||||
Long id,
|
||||
@@ -26,8 +30,12 @@ public record ReservationV4CatalogAccountSnapshot(
|
||||
String sourceCode,
|
||||
String status,
|
||||
String sourceSystem,
|
||||
String externalAccountId,
|
||||
String catalogVersion,
|
||||
LocalDateTime lastSyncedAt,
|
||||
String metadataJson
|
||||
String metadataJson,
|
||||
Long version,
|
||||
LocalDateTime createdAt,
|
||||
LocalDateTime updatedAt
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -12,10 +12,14 @@ import java.time.LocalDateTime;
|
||||
* @param displayName 显示名称
|
||||
* @param status 目录状态
|
||||
* @param sourceSystem 目录来源
|
||||
* @param externalId 外部系统目录 ID
|
||||
* @param sortOrder 展示排序
|
||||
* @param catalogVersion 目录版本
|
||||
* @param lastSyncedAt 最近同步 UTC 时间
|
||||
* @param metadataJson 扩展属性 JSON
|
||||
* @param version 乐观锁版本
|
||||
* @param createdAt 创建 UTC 时间
|
||||
* @param updatedAt 更新 UTC 时间
|
||||
*/
|
||||
public record ReservationV4CatalogCodeSnapshot(
|
||||
Long id,
|
||||
@@ -25,9 +29,13 @@ public record ReservationV4CatalogCodeSnapshot(
|
||||
String displayName,
|
||||
String status,
|
||||
String sourceSystem,
|
||||
String externalId,
|
||||
Integer sortOrder,
|
||||
String catalogVersion,
|
||||
LocalDateTime lastSyncedAt,
|
||||
String metadataJson
|
||||
String metadataJson,
|
||||
Long version,
|
||||
LocalDateTime createdAt,
|
||||
LocalDateTime updatedAt
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.common.request;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* Reservation V4 Account 目录新增请求。只允许后台管理入口创建系统维护目录。
|
||||
*
|
||||
* @param hotelId 酒店 ID
|
||||
* @param accountCode Account 稳定代码
|
||||
* @param accountName Account 显示名称
|
||||
* @param marketCode 关联 Market 代码
|
||||
* @param sourceCode 关联 Source 代码
|
||||
* @param externalAccountId 外部系统 Account ID,未接 PMS 时可为空
|
||||
* @param catalogVersion 目录版本,为空时后端生成系统维护版本
|
||||
* @param metadataJson 扩展属性 JSON,第一版只保存不参与业务计算
|
||||
*/
|
||||
public record ReservationV4CatalogAdminCreateAccountRequest(
|
||||
@JsonProperty("hotel_id")
|
||||
String hotelId,
|
||||
@JsonProperty("account_code")
|
||||
String accountCode,
|
||||
@JsonProperty("account_name")
|
||||
String accountName,
|
||||
@JsonProperty("market_code")
|
||||
String marketCode,
|
||||
@JsonProperty("source_code")
|
||||
String sourceCode,
|
||||
@JsonProperty("external_account_id")
|
||||
String externalAccountId,
|
||||
@JsonProperty("catalog_version")
|
||||
String catalogVersion,
|
||||
@JsonProperty("metadata_json")
|
||||
String metadataJson
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.common.request;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* Reservation V4 通用代码目录新增请求。Controller 固定 Room Type 或 Rate Code 类型。
|
||||
*
|
||||
* @param hotelId 酒店 ID
|
||||
* @param code 目录稳定代码
|
||||
* @param displayName 目录显示名称
|
||||
* @param externalId 外部系统目录 ID,未接 PMS 时可为空
|
||||
* @param sortOrder 展示排序
|
||||
* @param catalogVersion 目录版本,为空时后端生成系统维护版本
|
||||
* @param metadataJson 扩展属性 JSON,例如房型人数或 Rate Code 价格能力
|
||||
*/
|
||||
public record ReservationV4CatalogAdminCreateCodeRequest(
|
||||
@JsonProperty("hotel_id")
|
||||
String hotelId,
|
||||
String code,
|
||||
@JsonProperty("display_name")
|
||||
String displayName,
|
||||
@JsonProperty("external_id")
|
||||
String externalId,
|
||||
@JsonProperty("sort_order")
|
||||
Integer sortOrder,
|
||||
@JsonProperty("catalog_version")
|
||||
String catalogVersion,
|
||||
@JsonProperty("metadata_json")
|
||||
String metadataJson
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.common.request;
|
||||
|
||||
/**
|
||||
* Reservation V4 目录状态更新请求。
|
||||
*
|
||||
* @param status 目标目录状态:ACTIVE 或 DISABLED
|
||||
*/
|
||||
public record ReservationV4CatalogAdminStatusUpdateRequest(
|
||||
String status
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.common.result;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
/**
|
||||
* Reservation V4 Account 目录管理响应。
|
||||
*
|
||||
* @param id 目录记录 ID 字符串
|
||||
* @param hotelId 酒店 ID
|
||||
* @param accountCode Account 稳定代码
|
||||
* @param accountName Account 显示名称
|
||||
* @param marketCode 关联 Market 代码
|
||||
* @param sourceCode 关联 Source 代码
|
||||
* @param status 目录状态
|
||||
* @param catalogSource 目录来源
|
||||
* @param externalAccountId 外部系统 Account ID
|
||||
* @param catalogVersion 目录版本
|
||||
* @param metadataJson 扩展属性 JSON
|
||||
* @param version 乐观锁版本
|
||||
* @param createdAt 创建 UTC 时间
|
||||
* @param updatedAt 更新 UTC 时间
|
||||
*/
|
||||
public record ReservationV4CatalogAdminAccountResult(
|
||||
String id,
|
||||
@JsonProperty("hotel_id")
|
||||
String hotelId,
|
||||
@JsonProperty("account_code")
|
||||
String accountCode,
|
||||
@JsonProperty("account_name")
|
||||
String accountName,
|
||||
@JsonProperty("market_code")
|
||||
String marketCode,
|
||||
@JsonProperty("source_code")
|
||||
String sourceCode,
|
||||
String status,
|
||||
@JsonProperty("catalog_source")
|
||||
String catalogSource,
|
||||
@JsonProperty("external_account_id")
|
||||
String externalAccountId,
|
||||
@JsonProperty("catalog_version")
|
||||
String catalogVersion,
|
||||
@JsonProperty("metadata_json")
|
||||
String metadataJson,
|
||||
Long version,
|
||||
@JsonProperty("created_at")
|
||||
OffsetDateTime createdAt,
|
||||
@JsonProperty("updated_at")
|
||||
OffsetDateTime updatedAt
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.common.result;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
/**
|
||||
* Reservation V4 Room Type / Rate Code 目录管理响应。
|
||||
*
|
||||
* @param id 目录记录 ID 字符串
|
||||
* @param hotelId 酒店 ID
|
||||
* @param catalogType 目录类型
|
||||
* @param code 稳定代码
|
||||
* @param displayName 显示名称
|
||||
* @param status 目录状态
|
||||
* @param catalogSource 目录来源
|
||||
* @param externalId 外部系统目录 ID
|
||||
* @param sortOrder 展示排序
|
||||
* @param catalogVersion 目录版本
|
||||
* @param metadataJson 扩展属性 JSON
|
||||
* @param version 乐观锁版本
|
||||
* @param createdAt 创建 UTC 时间
|
||||
* @param updatedAt 更新 UTC 时间
|
||||
*/
|
||||
public record ReservationV4CatalogAdminCodeResult(
|
||||
String id,
|
||||
@JsonProperty("hotel_id")
|
||||
String hotelId,
|
||||
@JsonProperty("catalog_type")
|
||||
String catalogType,
|
||||
String code,
|
||||
@JsonProperty("display_name")
|
||||
String displayName,
|
||||
String status,
|
||||
@JsonProperty("catalog_source")
|
||||
String catalogSource,
|
||||
@JsonProperty("external_id")
|
||||
String externalId,
|
||||
@JsonProperty("sort_order")
|
||||
Integer sortOrder,
|
||||
@JsonProperty("catalog_version")
|
||||
String catalogVersion,
|
||||
@JsonProperty("metadata_json")
|
||||
String metadataJson,
|
||||
Long version,
|
||||
@JsonProperty("created_at")
|
||||
OffsetDateTime createdAt,
|
||||
@JsonProperty("updated_at")
|
||||
OffsetDateTime updatedAt
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.control;
|
||||
|
||||
import cn.nianxx.thhotel.platform.access.common.enums.PlatformPermissionCode;
|
||||
import cn.nianxx.thhotel.platform.common.result.PlatformPageResult;
|
||||
import cn.nianxx.thhotel.platform.security.common.dto.AuthenticatedUserContext;
|
||||
import cn.nianxx.thhotel.platform.security.service.AdminAuthorizationService;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.request.ReservationV4CatalogAdminCreateAccountRequest;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.request.ReservationV4CatalogAdminCreateCodeRequest;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.request.ReservationV4CatalogAdminStatusUpdateRequest;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.result.ReservationV4CatalogAdminAccountResult;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.result.ReservationV4CatalogAdminCodeResult;
|
||||
import cn.nianxx.thhotel.workflows.reservation.service.ReservationV4CatalogAdminService;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* Reservation V4 目录管理 Controller。提供系统后台维护 Account、Room Type、Rate Code 的第一版接口。
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/admin/reservation/catalogs")
|
||||
public class ReservationV4CatalogAdminController {
|
||||
|
||||
private final AdminAuthorizationService authorizationService;
|
||||
private final ReservationV4CatalogAdminService catalogAdminService;
|
||||
|
||||
/**
|
||||
* 注入管理鉴权和目录管理服务,Controller 只负责 HTTP 参数边界和权限入口。
|
||||
*/
|
||||
public ReservationV4CatalogAdminController(
|
||||
AdminAuthorizationService authorizationService,
|
||||
ReservationV4CatalogAdminService catalogAdminService) {
|
||||
this.authorizationService = authorizationService;
|
||||
this.catalogAdminService = catalogAdminService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询 Account 目录管理列表,需要目录管理权限。
|
||||
*/
|
||||
@GetMapping(value = "/accounts", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public PlatformPageResult<ReservationV4CatalogAdminAccountResult> listAccounts(
|
||||
@RequestParam(name = "hotel_id", required = false) String hotelId,
|
||||
@RequestParam(required = false) String keyword,
|
||||
@RequestParam(required = false) String status,
|
||||
@RequestParam(name = "page_num", required = false) Integer pageNum,
|
||||
@RequestParam(name = "page_size", required = false) Integer pageSize) {
|
||||
authorizationService.requirePermission(PlatformPermissionCode.RESERVATION_CATALOG_MANAGE.name());
|
||||
return catalogAdminService.listAccounts(hotelId, keyword, status, pageNum, pageSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增 Account 目录项,需要目录管理权限并写入管理审计。
|
||||
*/
|
||||
@PostMapping(
|
||||
value = "/accounts",
|
||||
consumes = MediaType.APPLICATION_JSON_VALUE,
|
||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ReservationV4CatalogAdminAccountResult createAccount(
|
||||
@RequestBody(required = false) ReservationV4CatalogAdminCreateAccountRequest request) {
|
||||
AuthenticatedUserContext actor = authorizationService.requirePermission(
|
||||
PlatformPermissionCode.RESERVATION_CATALOG_MANAGE.name());
|
||||
return catalogAdminService.createAccount(request, actor);
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用或停用 Account 目录项;状态实际变化时写入管理审计。
|
||||
*/
|
||||
@PutMapping(
|
||||
value = "/accounts/{accountId}/status",
|
||||
consumes = MediaType.APPLICATION_JSON_VALUE,
|
||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ReservationV4CatalogAdminAccountResult updateAccountStatus(
|
||||
@PathVariable String accountId,
|
||||
@RequestBody(required = false) ReservationV4CatalogAdminStatusUpdateRequest request) {
|
||||
AuthenticatedUserContext actor = authorizationService.requirePermission(
|
||||
PlatformPermissionCode.RESERVATION_CATALOG_MANAGE.name());
|
||||
return catalogAdminService.updateAccountStatus(accountId, request, actor);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询 Room Type 目录管理列表,需要目录管理权限。
|
||||
*/
|
||||
@GetMapping(value = "/room-types", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public PlatformPageResult<ReservationV4CatalogAdminCodeResult> listRoomTypes(
|
||||
@RequestParam(name = "hotel_id", required = false) String hotelId,
|
||||
@RequestParam(required = false) String keyword,
|
||||
@RequestParam(required = false) String status,
|
||||
@RequestParam(name = "page_num", required = false) Integer pageNum,
|
||||
@RequestParam(name = "page_size", required = false) Integer pageSize) {
|
||||
authorizationService.requirePermission(PlatformPermissionCode.RESERVATION_CATALOG_MANAGE.name());
|
||||
return catalogAdminService.listRoomTypes(hotelId, keyword, status, pageNum, pageSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增 Room Type 目录项,需要目录管理权限并写入管理审计。
|
||||
*/
|
||||
@PostMapping(
|
||||
value = "/room-types",
|
||||
consumes = MediaType.APPLICATION_JSON_VALUE,
|
||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ReservationV4CatalogAdminCodeResult createRoomType(
|
||||
@RequestBody(required = false) ReservationV4CatalogAdminCreateCodeRequest request) {
|
||||
AuthenticatedUserContext actor = authorizationService.requirePermission(
|
||||
PlatformPermissionCode.RESERVATION_CATALOG_MANAGE.name());
|
||||
return catalogAdminService.createRoomType(request, actor);
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用或停用 Room Type 目录项;状态实际变化时写入管理审计。
|
||||
*/
|
||||
@PutMapping(
|
||||
value = "/room-types/{catalogId}/status",
|
||||
consumes = MediaType.APPLICATION_JSON_VALUE,
|
||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ReservationV4CatalogAdminCodeResult updateRoomTypeStatus(
|
||||
@PathVariable String catalogId,
|
||||
@RequestBody(required = false) ReservationV4CatalogAdminStatusUpdateRequest request) {
|
||||
AuthenticatedUserContext actor = authorizationService.requirePermission(
|
||||
PlatformPermissionCode.RESERVATION_CATALOG_MANAGE.name());
|
||||
return catalogAdminService.updateRoomTypeStatus(catalogId, request, actor);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询 Rate Code 目录管理列表,需要目录管理权限。
|
||||
*/
|
||||
@GetMapping(value = "/rate-codes", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public PlatformPageResult<ReservationV4CatalogAdminCodeResult> listRateCodes(
|
||||
@RequestParam(name = "hotel_id", required = false) String hotelId,
|
||||
@RequestParam(required = false) String keyword,
|
||||
@RequestParam(required = false) String status,
|
||||
@RequestParam(name = "page_num", required = false) Integer pageNum,
|
||||
@RequestParam(name = "page_size", required = false) Integer pageSize) {
|
||||
authorizationService.requirePermission(PlatformPermissionCode.RESERVATION_CATALOG_MANAGE.name());
|
||||
return catalogAdminService.listRateCodes(hotelId, keyword, status, pageNum, pageSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增 Rate Code 目录项,需要目录管理权限并写入管理审计。
|
||||
*/
|
||||
@PostMapping(
|
||||
value = "/rate-codes",
|
||||
consumes = MediaType.APPLICATION_JSON_VALUE,
|
||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ReservationV4CatalogAdminCodeResult createRateCode(
|
||||
@RequestBody(required = false) ReservationV4CatalogAdminCreateCodeRequest request) {
|
||||
AuthenticatedUserContext actor = authorizationService.requirePermission(
|
||||
PlatformPermissionCode.RESERVATION_CATALOG_MANAGE.name());
|
||||
return catalogAdminService.createRateCode(request, actor);
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用或停用 Rate Code 目录项;状态实际变化时写入管理审计。
|
||||
*/
|
||||
@PutMapping(
|
||||
value = "/rate-codes/{catalogId}/status",
|
||||
consumes = MediaType.APPLICATION_JSON_VALUE,
|
||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ReservationV4CatalogAdminCodeResult updateRateCodeStatus(
|
||||
@PathVariable String catalogId,
|
||||
@RequestBody(required = false) ReservationV4CatalogAdminStatusUpdateRequest request) {
|
||||
AuthenticatedUserContext actor = authorizationService.requirePermission(
|
||||
PlatformPermissionCode.RESERVATION_CATALOG_MANAGE.name());
|
||||
return catalogAdminService.updateRateCodeStatus(catalogId, request, actor);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.control;
|
||||
|
||||
import cn.nianxx.thhotel.platform.common.exception.AdminOperationException;
|
||||
import cn.nianxx.thhotel.platform.common.result.PlatformErrorResponse;
|
||||
import cn.nianxx.thhotel.platform.security.service.impl.AdminAuthorizationException;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
/**
|
||||
* Reservation V4 目录管理后台异常处理。保持平台错误响应结构,同时避免 platform 模块反向依赖 workflow。
|
||||
*/
|
||||
@RestControllerAdvice(assignableTypes = ReservationV4CatalogAdminController.class)
|
||||
public class ReservationV4CatalogAdminControllerAdvice {
|
||||
|
||||
/**
|
||||
* 处理目录管理后台登录和权限异常。
|
||||
*/
|
||||
@ExceptionHandler(AdminAuthorizationException.class)
|
||||
public ResponseEntity<PlatformErrorResponse> handleAuthorization(AdminAuthorizationException exception) {
|
||||
return ResponseEntity.status(exception.getStatus())
|
||||
.body(new PlatformErrorResponse(exception.getErrorCode(), exception.getMessage()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理目录管理后台受控业务异常。
|
||||
*/
|
||||
@ExceptionHandler(AdminOperationException.class)
|
||||
public ResponseEntity<PlatformErrorResponse> handleOperation(AdminOperationException exception) {
|
||||
return ResponseEntity.status(exception.getStatus())
|
||||
.body(new PlatformErrorResponse(exception.getErrorCode(), exception.getMessage()));
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import cn.nianxx.thhotel.workflows.reservation.mapper.ReservationCatalogCodeMapp
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.springframework.stereotype.Repository;
|
||||
@@ -132,6 +133,140 @@ public class MybatisReservationV4CatalogRepository implements ReservationV4Catal
|
||||
return new ReservationPageSnapshot<>(items, page.getTotal(), pageNum, pageSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* 管理后台分页查询 Account 目录,可包含 DISABLED 记录。
|
||||
*/
|
||||
@Override
|
||||
public ReservationPageSnapshot<ReservationV4CatalogAccountSnapshot> queryAdminAccounts(
|
||||
String hotelId,
|
||||
String keyword,
|
||||
String status,
|
||||
int pageNum,
|
||||
int pageSize) {
|
||||
String normalizedKeyword = trimToNull(keyword);
|
||||
String normalizedStatus = trimToNull(status);
|
||||
LambdaQueryWrapper<ReservationCatalogAccountEntity> query = Wrappers.<ReservationCatalogAccountEntity>lambdaQuery()
|
||||
.eq(ReservationCatalogAccountEntity::getHotelId, hotelId)
|
||||
.eq(normalizedStatus != null, ReservationCatalogAccountEntity::getStatus, normalizedStatus)
|
||||
.isNull(ReservationCatalogAccountEntity::getLogicDeletedAt)
|
||||
.and(normalizedKeyword != null, wrapper -> wrapper
|
||||
.like(ReservationCatalogAccountEntity::getAccountCode, normalizedKeyword)
|
||||
.or()
|
||||
.like(ReservationCatalogAccountEntity::getAccountName, normalizedKeyword))
|
||||
.orderByAsc(ReservationCatalogAccountEntity::getAccountCode)
|
||||
.orderByAsc(ReservationCatalogAccountEntity::getId);
|
||||
Page<ReservationCatalogAccountEntity> page = accountMapper.selectPage(Page.of(pageNum, pageSize), query);
|
||||
List<ReservationV4CatalogAccountSnapshot> items = page.getRecords().stream()
|
||||
.map(this::toAccountSnapshot)
|
||||
.toList();
|
||||
return new ReservationPageSnapshot<>(items, page.getTotal(), pageNum, pageSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* 管理后台分页查询 Room Type / Rate Code 目录,可包含 DISABLED 记录。
|
||||
*/
|
||||
@Override
|
||||
public ReservationPageSnapshot<ReservationV4CatalogCodeSnapshot> queryAdminCatalogCodes(
|
||||
String hotelId,
|
||||
String catalogType,
|
||||
String keyword,
|
||||
String status,
|
||||
int pageNum,
|
||||
int pageSize) {
|
||||
String normalizedKeyword = trimToNull(keyword);
|
||||
String normalizedStatus = trimToNull(status);
|
||||
LambdaQueryWrapper<ReservationCatalogCodeEntity> query = Wrappers.<ReservationCatalogCodeEntity>lambdaQuery()
|
||||
.eq(ReservationCatalogCodeEntity::getHotelId, hotelId)
|
||||
.eq(ReservationCatalogCodeEntity::getCatalogType, catalogType)
|
||||
.eq(normalizedStatus != null, ReservationCatalogCodeEntity::getStatus, normalizedStatus)
|
||||
.isNull(ReservationCatalogCodeEntity::getLogicDeletedAt)
|
||||
.and(normalizedKeyword != null, wrapper -> wrapper
|
||||
.like(ReservationCatalogCodeEntity::getCode, normalizedKeyword)
|
||||
.or()
|
||||
.like(ReservationCatalogCodeEntity::getDisplayName, normalizedKeyword))
|
||||
.orderByAsc(ReservationCatalogCodeEntity::getSortOrder)
|
||||
.orderByAsc(ReservationCatalogCodeEntity::getCode)
|
||||
.orderByAsc(ReservationCatalogCodeEntity::getId);
|
||||
Page<ReservationCatalogCodeEntity> page = codeMapper.selectPage(Page.of(pageNum, pageSize), query);
|
||||
List<ReservationV4CatalogCodeSnapshot> items = page.getRecords().stream()
|
||||
.map(this::toCodeSnapshot)
|
||||
.toList();
|
||||
return new ReservationPageSnapshot<>(items, page.getTotal(), pageNum, pageSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按内部 ID 查询 Account 目录。
|
||||
*/
|
||||
@Override
|
||||
public Optional<ReservationV4CatalogAccountSnapshot> findAccountById(Long id) {
|
||||
if (id == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
ReservationCatalogAccountEntity entity = accountMapper.selectOne(
|
||||
Wrappers.<ReservationCatalogAccountEntity>lambdaQuery()
|
||||
.eq(ReservationCatalogAccountEntity::getId, id)
|
||||
.isNull(ReservationCatalogAccountEntity::getLogicDeletedAt)
|
||||
.last("LIMIT 1"));
|
||||
return Optional.ofNullable(entity).map(this::toAccountSnapshot);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按内部 ID 查询通用代码目录。
|
||||
*/
|
||||
@Override
|
||||
public Optional<ReservationV4CatalogCodeSnapshot> findCatalogCodeById(Long id) {
|
||||
if (id == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
ReservationCatalogCodeEntity entity = codeMapper.selectOne(
|
||||
Wrappers.<ReservationCatalogCodeEntity>lambdaQuery()
|
||||
.eq(ReservationCatalogCodeEntity::getId, id)
|
||||
.isNull(ReservationCatalogCodeEntity::getLogicDeletedAt)
|
||||
.last("LIMIT 1"));
|
||||
return Optional.ofNullable(entity).map(this::toCodeSnapshot);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按酒店和 Account Code 查询未逻辑删除 Account 目录。
|
||||
*/
|
||||
@Override
|
||||
public Optional<ReservationV4CatalogAccountSnapshot> findAccountByCode(String hotelId, String accountCode) {
|
||||
String normalizedCode = trimToNull(accountCode);
|
||||
if (trimToNull(hotelId) == null || normalizedCode == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
ReservationCatalogAccountEntity entity = accountMapper.selectOne(
|
||||
Wrappers.<ReservationCatalogAccountEntity>lambdaQuery()
|
||||
.eq(ReservationCatalogAccountEntity::getHotelId, hotelId)
|
||||
.eq(ReservationCatalogAccountEntity::getAccountCode, normalizedCode)
|
||||
.isNull(ReservationCatalogAccountEntity::getLogicDeletedAt)
|
||||
.last("LIMIT 1"));
|
||||
return Optional.ofNullable(entity).map(this::toAccountSnapshot);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按酒店、目录类型和代码查询未逻辑删除通用目录。
|
||||
*/
|
||||
@Override
|
||||
public Optional<ReservationV4CatalogCodeSnapshot> findCatalogCodeByCode(
|
||||
String hotelId,
|
||||
String catalogType,
|
||||
String code) {
|
||||
String normalizedType = trimToNull(catalogType);
|
||||
String normalizedCode = trimToNull(code);
|
||||
if (trimToNull(hotelId) == null || normalizedType == null || normalizedCode == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
ReservationCatalogCodeEntity entity = codeMapper.selectOne(
|
||||
Wrappers.<ReservationCatalogCodeEntity>lambdaQuery()
|
||||
.eq(ReservationCatalogCodeEntity::getHotelId, hotelId)
|
||||
.eq(ReservationCatalogCodeEntity::getCatalogType, normalizedType)
|
||||
.eq(ReservationCatalogCodeEntity::getCode, normalizedCode)
|
||||
.isNull(ReservationCatalogCodeEntity::getLogicDeletedAt)
|
||||
.last("LIMIT 1"));
|
||||
return Optional.ofNullable(entity).map(this::toCodeSnapshot);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前酒店是否已有 Account 目录记录;包含非 ACTIVE 记录,避免启动种子覆盖人工维护数据。
|
||||
*/
|
||||
@@ -196,6 +331,64 @@ public class MybatisReservationV4CatalogRepository implements ReservationV4Catal
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增 Account 目录记录。
|
||||
*/
|
||||
@Override
|
||||
public void insertAccount(ReservationCatalogAccountEntity entity) {
|
||||
accountMapper.insert(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增通用代码目录记录。
|
||||
*/
|
||||
@Override
|
||||
public void insertCatalogCode(ReservationCatalogCodeEntity entity) {
|
||||
codeMapper.insert(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按版本更新 Account 状态,避免后台并发覆盖。
|
||||
*/
|
||||
@Override
|
||||
public boolean updateAccountStatus(
|
||||
Long id,
|
||||
String status,
|
||||
long expectedVersion,
|
||||
long nextVersion,
|
||||
LocalDateTime updatedAt) {
|
||||
ReservationCatalogAccountEntity entity = new ReservationCatalogAccountEntity();
|
||||
entity.setStatus(status);
|
||||
entity.setVersion(nextVersion);
|
||||
entity.setUpdatedAt(updatedAt);
|
||||
int updated = accountMapper.update(entity, Wrappers.<ReservationCatalogAccountEntity>lambdaUpdate()
|
||||
.eq(ReservationCatalogAccountEntity::getId, id)
|
||||
.eq(ReservationCatalogAccountEntity::getVersion, expectedVersion)
|
||||
.isNull(ReservationCatalogAccountEntity::getLogicDeletedAt));
|
||||
return updated > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按版本更新通用代码目录状态,避免后台并发覆盖。
|
||||
*/
|
||||
@Override
|
||||
public boolean updateCatalogCodeStatus(
|
||||
Long id,
|
||||
String status,
|
||||
long expectedVersion,
|
||||
long nextVersion,
|
||||
LocalDateTime updatedAt) {
|
||||
ReservationCatalogCodeEntity entity = new ReservationCatalogCodeEntity();
|
||||
entity.setStatus(status);
|
||||
entity.setVersion(nextVersion);
|
||||
entity.setUpdatedAt(updatedAt);
|
||||
int updated = codeMapper.update(entity, Wrappers.<ReservationCatalogCodeEntity>lambdaUpdate()
|
||||
.eq(ReservationCatalogCodeEntity::getId, id)
|
||||
.eq(ReservationCatalogCodeEntity::getVersion, expectedVersion)
|
||||
.isNull(ReservationCatalogCodeEntity::getLogicDeletedAt));
|
||||
return updated > 0;
|
||||
}
|
||||
|
||||
private ReservationV4CatalogAccountSnapshot toAccountSnapshot(ReservationCatalogAccountEntity entity) {
|
||||
return new ReservationV4CatalogAccountSnapshot(
|
||||
entity.getId(),
|
||||
@@ -206,9 +399,13 @@ public class MybatisReservationV4CatalogRepository implements ReservationV4Catal
|
||||
entity.getSourceCode(),
|
||||
entity.getStatus(),
|
||||
entity.getSourceSystem(),
|
||||
entity.getExternalAccountId(),
|
||||
entity.getCatalogVersion(),
|
||||
entity.getLastSyncedAt(),
|
||||
entity.getMetadataJson());
|
||||
entity.getMetadataJson(),
|
||||
entity.getVersion(),
|
||||
entity.getCreatedAt(),
|
||||
entity.getUpdatedAt());
|
||||
}
|
||||
|
||||
private ReservationV4CatalogCodeSnapshot toCodeSnapshot(ReservationCatalogCodeEntity entity) {
|
||||
@@ -220,10 +417,14 @@ public class MybatisReservationV4CatalogRepository implements ReservationV4Catal
|
||||
entity.getDisplayName(),
|
||||
entity.getStatus(),
|
||||
entity.getSourceSystem(),
|
||||
entity.getExternalId(),
|
||||
entity.getSortOrder(),
|
||||
entity.getCatalogVersion(),
|
||||
entity.getLastSyncedAt(),
|
||||
entity.getMetadataJson());
|
||||
entity.getMetadataJson(),
|
||||
entity.getVersion(),
|
||||
entity.getCreatedAt(),
|
||||
entity.getUpdatedAt());
|
||||
}
|
||||
|
||||
private String trimToNull(String value) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationV4CatalogAc
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationV4CatalogCodeSnapshot;
|
||||
import cn.nianxx.thhotel.workflows.reservation.domain.ReservationCatalogAccountEntity;
|
||||
import cn.nianxx.thhotel.workflows.reservation.domain.ReservationCatalogCodeEntity;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
@@ -41,6 +42,47 @@ public interface ReservationV4CatalogRepository {
|
||||
int pageNum,
|
||||
int pageSize);
|
||||
|
||||
/**
|
||||
* 管理后台分页查询 Account 目录,可按 ACTIVE / DISABLED 过滤。
|
||||
*/
|
||||
ReservationPageSnapshot<ReservationV4CatalogAccountSnapshot> queryAdminAccounts(
|
||||
String hotelId,
|
||||
String keyword,
|
||||
String status,
|
||||
int pageNum,
|
||||
int pageSize);
|
||||
|
||||
/**
|
||||
* 管理后台分页查询 Room Type / Rate Code 目录,可按 ACTIVE / DISABLED 过滤。
|
||||
*/
|
||||
ReservationPageSnapshot<ReservationV4CatalogCodeSnapshot> queryAdminCatalogCodes(
|
||||
String hotelId,
|
||||
String catalogType,
|
||||
String keyword,
|
||||
String status,
|
||||
int pageNum,
|
||||
int pageSize);
|
||||
|
||||
/**
|
||||
* 按内部 ID 查询 Account 目录,管理后台写操作需要包含 DISABLED 记录。
|
||||
*/
|
||||
Optional<ReservationV4CatalogAccountSnapshot> findAccountById(Long id);
|
||||
|
||||
/**
|
||||
* 按内部 ID 查询通用代码目录,管理后台写操作需要包含 DISABLED 记录。
|
||||
*/
|
||||
Optional<ReservationV4CatalogCodeSnapshot> findCatalogCodeById(Long id);
|
||||
|
||||
/**
|
||||
* 按酒店和 Account Code 查询未逻辑删除目录,管理后台新增时用于重复校验。
|
||||
*/
|
||||
Optional<ReservationV4CatalogAccountSnapshot> findAccountByCode(String hotelId, String accountCode);
|
||||
|
||||
/**
|
||||
* 按酒店、目录类型和代码查询未逻辑删除目录,管理后台新增时用于重复校验。
|
||||
*/
|
||||
Optional<ReservationV4CatalogCodeSnapshot> findCatalogCodeByCode(String hotelId, String catalogType, String code);
|
||||
|
||||
/**
|
||||
* 判断当前酒店是否已有任意 Account 目录记录,用于启动初始化避免覆盖人工目录。
|
||||
*/
|
||||
@@ -60,4 +102,24 @@ public interface ReservationV4CatalogRepository {
|
||||
* 通用目录 Code 不存在时插入固定种子目录,存在时保持原数据不变。
|
||||
*/
|
||||
void insertCatalogCodeIfAbsent(ReservationCatalogCodeEntity entity);
|
||||
|
||||
/**
|
||||
* 新增 Account 目录记录,调用方负责业务校验和审计。
|
||||
*/
|
||||
void insertAccount(ReservationCatalogAccountEntity entity);
|
||||
|
||||
/**
|
||||
* 新增通用代码目录记录,调用方负责业务校验和审计。
|
||||
*/
|
||||
void insertCatalogCode(ReservationCatalogCodeEntity entity);
|
||||
|
||||
/**
|
||||
* 更新 Account 目录状态并推进乐观锁版本。
|
||||
*/
|
||||
boolean updateAccountStatus(Long id, String status, long expectedVersion, long nextVersion, LocalDateTime updatedAt);
|
||||
|
||||
/**
|
||||
* 更新通用代码目录状态并推进乐观锁版本。
|
||||
*/
|
||||
boolean updateCatalogCodeStatus(Long id, String status, long expectedVersion, long nextVersion, LocalDateTime updatedAt);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.service;
|
||||
|
||||
import cn.nianxx.thhotel.platform.common.result.PlatformPageResult;
|
||||
import cn.nianxx.thhotel.platform.security.common.dto.AuthenticatedUserContext;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.request.ReservationV4CatalogAdminCreateAccountRequest;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.request.ReservationV4CatalogAdminCreateCodeRequest;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.request.ReservationV4CatalogAdminStatusUpdateRequest;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.result.ReservationV4CatalogAdminAccountResult;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.result.ReservationV4CatalogAdminCodeResult;
|
||||
|
||||
/**
|
||||
* Reservation V4 目录管理服务。第一版只管理 Account、Room Type 和 Rate Code。
|
||||
*/
|
||||
public interface ReservationV4CatalogAdminService {
|
||||
|
||||
/**
|
||||
* 分页查询 Account 目录管理列表。
|
||||
*/
|
||||
PlatformPageResult<ReservationV4CatalogAdminAccountResult> listAccounts(
|
||||
String hotelId,
|
||||
String keyword,
|
||||
String status,
|
||||
Integer pageNum,
|
||||
Integer pageSize);
|
||||
|
||||
/**
|
||||
* 新增 Account 目录项。
|
||||
*/
|
||||
ReservationV4CatalogAdminAccountResult createAccount(
|
||||
ReservationV4CatalogAdminCreateAccountRequest request,
|
||||
AuthenticatedUserContext actor);
|
||||
|
||||
/**
|
||||
* 更新 Account 目录状态。
|
||||
*/
|
||||
ReservationV4CatalogAdminAccountResult updateAccountStatus(
|
||||
String accountId,
|
||||
ReservationV4CatalogAdminStatusUpdateRequest request,
|
||||
AuthenticatedUserContext actor);
|
||||
|
||||
/**
|
||||
* 分页查询 Room Type 目录管理列表。
|
||||
*/
|
||||
PlatformPageResult<ReservationV4CatalogAdminCodeResult> listRoomTypes(
|
||||
String hotelId,
|
||||
String keyword,
|
||||
String status,
|
||||
Integer pageNum,
|
||||
Integer pageSize);
|
||||
|
||||
/**
|
||||
* 新增 Room Type 目录项。
|
||||
*/
|
||||
ReservationV4CatalogAdminCodeResult createRoomType(
|
||||
ReservationV4CatalogAdminCreateCodeRequest request,
|
||||
AuthenticatedUserContext actor);
|
||||
|
||||
/**
|
||||
* 更新 Room Type 目录状态。
|
||||
*/
|
||||
ReservationV4CatalogAdminCodeResult updateRoomTypeStatus(
|
||||
String catalogId,
|
||||
ReservationV4CatalogAdminStatusUpdateRequest request,
|
||||
AuthenticatedUserContext actor);
|
||||
|
||||
/**
|
||||
* 分页查询 Rate Code 目录管理列表。
|
||||
*/
|
||||
PlatformPageResult<ReservationV4CatalogAdminCodeResult> listRateCodes(
|
||||
String hotelId,
|
||||
String keyword,
|
||||
String status,
|
||||
Integer pageNum,
|
||||
Integer pageSize);
|
||||
|
||||
/**
|
||||
* 新增 Rate Code 目录项。
|
||||
*/
|
||||
ReservationV4CatalogAdminCodeResult createRateCode(
|
||||
ReservationV4CatalogAdminCreateCodeRequest request,
|
||||
AuthenticatedUserContext actor);
|
||||
|
||||
/**
|
||||
* 更新 Rate Code 目录状态。
|
||||
*/
|
||||
ReservationV4CatalogAdminCodeResult updateRateCodeStatus(
|
||||
String catalogId,
|
||||
ReservationV4CatalogAdminStatusUpdateRequest request,
|
||||
AuthenticatedUserContext actor);
|
||||
}
|
||||
@@ -0,0 +1,583 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.service.impl;
|
||||
|
||||
import cn.nianxx.thhotel.platform.audit.common.dto.PlatformAdminAuditLogDraft;
|
||||
import cn.nianxx.thhotel.platform.audit.service.PlatformAdminAuditLogService;
|
||||
import cn.nianxx.thhotel.platform.common.exception.AdminOperationException;
|
||||
import cn.nianxx.thhotel.platform.common.result.PlatformPageResult;
|
||||
import cn.nianxx.thhotel.platform.common.result.PlatformPaginationResult;
|
||||
import cn.nianxx.thhotel.platform.common.time.UtcTimeFormatter;
|
||||
import cn.nianxx.thhotel.platform.hotel.service.HotelContextException;
|
||||
import cn.nianxx.thhotel.platform.hotel.service.HotelContextService;
|
||||
import cn.nianxx.thhotel.platform.security.common.dto.AuthenticatedUserContext;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationPageSnapshot;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationV4CatalogAccountSnapshot;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationV4CatalogCodeSnapshot;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.enums.ReservationV4CatalogSourceSystem;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.enums.ReservationV4CatalogStatus;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.enums.ReservationV4CatalogType;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.request.ReservationV4CatalogAdminCreateAccountRequest;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.request.ReservationV4CatalogAdminCreateCodeRequest;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.request.ReservationV4CatalogAdminStatusUpdateRequest;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.result.ReservationV4CatalogAdminAccountResult;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.result.ReservationV4CatalogAdminCodeResult;
|
||||
import cn.nianxx.thhotel.workflows.reservation.domain.ReservationCatalogAccountEntity;
|
||||
import cn.nianxx.thhotel.workflows.reservation.domain.ReservationCatalogCodeEntity;
|
||||
import cn.nianxx.thhotel.workflows.reservation.repository.ReservationV4CatalogRepository;
|
||||
import cn.nianxx.thhotel.workflows.reservation.service.ReservationV4CatalogAdminService;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Locale;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* Reservation V4 目录管理服务实现。写操作只修改目录表,不触碰业务任务历史 payload。
|
||||
*/
|
||||
@Service
|
||||
public class ReservationV4CatalogAdminServiceImpl implements ReservationV4CatalogAdminService {
|
||||
|
||||
private static final int DEFAULT_PAGE_NUM = 1;
|
||||
private static final int DEFAULT_PAGE_SIZE = 20;
|
||||
private static final int MAX_PAGE_NUM = 1000;
|
||||
private static final int MAX_PAGE_SIZE = 100;
|
||||
private static final int MAX_CODE_LENGTH = 64;
|
||||
private static final int MAX_NAME_LENGTH = 255;
|
||||
private static final int MAX_EXTERNAL_ID_LENGTH = 128;
|
||||
private static final int MAX_METADATA_LENGTH = 65535;
|
||||
private static final String TARGET_TYPE_ACCOUNT = "RESERVATION_V4_CATALOG_ACCOUNT";
|
||||
private static final String TARGET_TYPE_CODE = "RESERVATION_V4_CATALOG_CODE";
|
||||
private static final DateTimeFormatter VERSION_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
|
||||
|
||||
private final ReservationV4CatalogRepository catalogRepository;
|
||||
private final HotelContextService hotelContextService;
|
||||
private final PlatformAdminAuditLogService auditLogService;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
/**
|
||||
* 注入目录仓储、酒店上下文、管理审计和 JSON 工具。
|
||||
*/
|
||||
public ReservationV4CatalogAdminServiceImpl(
|
||||
ReservationV4CatalogRepository catalogRepository,
|
||||
HotelContextService hotelContextService,
|
||||
PlatformAdminAuditLogService auditLogService,
|
||||
ObjectMapper objectMapper) {
|
||||
this.catalogRepository = catalogRepository;
|
||||
this.hotelContextService = hotelContextService;
|
||||
this.auditLogService = auditLogService;
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询 Account 目录管理列表,支持 ACTIVE / DISABLED 过滤。
|
||||
*/
|
||||
@Override
|
||||
public PlatformPageResult<ReservationV4CatalogAdminAccountResult> listAccounts(
|
||||
String hotelId,
|
||||
String keyword,
|
||||
String status,
|
||||
Integer pageNum,
|
||||
Integer pageSize) {
|
||||
String accessibleHotelId = resolveHotelId(hotelId);
|
||||
ReservationPageSnapshot<ReservationV4CatalogAccountSnapshot> page = catalogRepository.queryAdminAccounts(
|
||||
accessibleHotelId,
|
||||
trimToNull(keyword),
|
||||
normalizeNullableStatus(status),
|
||||
normalizePageNum(pageNum),
|
||||
normalizePageSize(pageSize));
|
||||
return new PlatformPageResult<>(
|
||||
page.items().stream().map(this::toAccountResult).toList(),
|
||||
new PlatformPaginationResult(page.pageNum(), page.pageSize(), page.total()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增 Account 目录项,并记录管理审计。
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public ReservationV4CatalogAdminAccountResult createAccount(
|
||||
ReservationV4CatalogAdminCreateAccountRequest request,
|
||||
AuthenticatedUserContext actor) {
|
||||
if (request == null) {
|
||||
throw invalidRequest("Account 目录新增请求不能为空。");
|
||||
}
|
||||
String hotelId = resolveHotelId(request.hotelId());
|
||||
String accountCode = requireCode(request.accountCode(), "Account Code 不能为空。");
|
||||
if (catalogRepository.findAccountByCode(hotelId, accountCode).isPresent()) {
|
||||
throw conflict("Account Code 已存在。");
|
||||
}
|
||||
String accountName = requireText(request.accountName(), "Account 名称不能为空。", MAX_NAME_LENGTH);
|
||||
String marketCode = requireCode(request.marketCode(), "Market Code 不能为空。");
|
||||
String sourceCode = requireCode(request.sourceCode(), "Source Code 不能为空。");
|
||||
ensureActiveCatalogCode(hotelId, ReservationV4CatalogType.MARKET, marketCode, "Market Code 不存在或未启用。");
|
||||
ensureActiveCatalogCode(hotelId, ReservationV4CatalogType.SOURCE, sourceCode, "Source Code 不存在或未启用。");
|
||||
String metadataJson = normalizeMetadataJson(request.metadataJson());
|
||||
LocalDateTime now = nowUtc();
|
||||
ReservationCatalogAccountEntity entity = new ReservationCatalogAccountEntity();
|
||||
entity.setHotelId(hotelId);
|
||||
entity.setAccountCode(accountCode);
|
||||
entity.setAccountName(accountName);
|
||||
entity.setMarketCode(marketCode);
|
||||
entity.setSourceCode(sourceCode);
|
||||
entity.setStatus(ReservationV4CatalogStatus.ACTIVE.name());
|
||||
entity.setSourceSystem(ReservationV4CatalogSourceSystem.SYSTEM_MANAGED.name());
|
||||
entity.setExternalAccountId(optionalText(request.externalAccountId(), MAX_EXTERNAL_ID_LENGTH, "外部 Account ID 过长。"));
|
||||
entity.setCatalogVersion(resolveCatalogVersion(request.catalogVersion(), now));
|
||||
entity.setLastSyncedAt(null);
|
||||
entity.setMetadataJson(metadataJson);
|
||||
entity.setVersion(0L);
|
||||
entity.setCreatedAt(now);
|
||||
entity.setUpdatedAt(now);
|
||||
try {
|
||||
catalogRepository.insertAccount(entity);
|
||||
} catch (DuplicateKeyException exception) {
|
||||
throw conflict("Account Code 已存在。");
|
||||
}
|
||||
ReservationV4CatalogAdminAccountResult after = getAccountResult(entity.getId());
|
||||
audit(actor, TARGET_TYPE_ACCOUNT, stringId(entity.getId()), "CREATE_ACCOUNT_CATALOG", null, after);
|
||||
return after;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新 Account 目录状态,DISABLED 后普通 lookup 不再返回该 Account。
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public ReservationV4CatalogAdminAccountResult updateAccountStatus(
|
||||
String accountId,
|
||||
ReservationV4CatalogAdminStatusUpdateRequest request,
|
||||
AuthenticatedUserContext actor) {
|
||||
Long id = parseId(accountId, "Account 目录 ID 不合法。");
|
||||
String targetStatus = normalizeRequiredStatus(request);
|
||||
ReservationV4CatalogAccountSnapshot beforeSnapshot = catalogRepository.findAccountById(id)
|
||||
.orElseThrow(() -> notFound("Account 目录不存在。"));
|
||||
resolveHotelId(beforeSnapshot.hotelId());
|
||||
ReservationV4CatalogAdminAccountResult before = toAccountResult(beforeSnapshot);
|
||||
if (targetStatus.equals(beforeSnapshot.status())) {
|
||||
return before;
|
||||
}
|
||||
long expectedVersion = beforeSnapshot.version() == null ? 0L : beforeSnapshot.version();
|
||||
boolean updated = catalogRepository.updateAccountStatus(
|
||||
id,
|
||||
targetStatus,
|
||||
expectedVersion,
|
||||
expectedVersion + 1,
|
||||
nowUtc());
|
||||
if (!updated) {
|
||||
throw conflict("Account 目录已被其他操作更新,请刷新后重试。");
|
||||
}
|
||||
ReservationV4CatalogAdminAccountResult after = getAccountResult(id);
|
||||
audit(actor, TARGET_TYPE_ACCOUNT, stringId(id), "UPDATE_ACCOUNT_CATALOG_STATUS", before, after);
|
||||
return after;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询 Room Type 目录管理列表。
|
||||
*/
|
||||
@Override
|
||||
public PlatformPageResult<ReservationV4CatalogAdminCodeResult> listRoomTypes(
|
||||
String hotelId,
|
||||
String keyword,
|
||||
String status,
|
||||
Integer pageNum,
|
||||
Integer pageSize) {
|
||||
return listCatalogCodes(hotelId, ReservationV4CatalogType.ROOM_TYPE, keyword, status, pageNum, pageSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增 Room Type 目录项。
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public ReservationV4CatalogAdminCodeResult createRoomType(
|
||||
ReservationV4CatalogAdminCreateCodeRequest request,
|
||||
AuthenticatedUserContext actor) {
|
||||
return createCatalogCode(request, ReservationV4CatalogType.ROOM_TYPE, actor);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新 Room Type 目录状态。
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public ReservationV4CatalogAdminCodeResult updateRoomTypeStatus(
|
||||
String catalogId,
|
||||
ReservationV4CatalogAdminStatusUpdateRequest request,
|
||||
AuthenticatedUserContext actor) {
|
||||
return updateCatalogCodeStatus(catalogId, ReservationV4CatalogType.ROOM_TYPE, request, actor);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询 Rate Code 目录管理列表。
|
||||
*/
|
||||
@Override
|
||||
public PlatformPageResult<ReservationV4CatalogAdminCodeResult> listRateCodes(
|
||||
String hotelId,
|
||||
String keyword,
|
||||
String status,
|
||||
Integer pageNum,
|
||||
Integer pageSize) {
|
||||
return listCatalogCodes(hotelId, ReservationV4CatalogType.RATE_CODE, keyword, status, pageNum, pageSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增 Rate Code 目录项。
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public ReservationV4CatalogAdminCodeResult createRateCode(
|
||||
ReservationV4CatalogAdminCreateCodeRequest request,
|
||||
AuthenticatedUserContext actor) {
|
||||
return createCatalogCode(request, ReservationV4CatalogType.RATE_CODE, actor);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新 Rate Code 目录状态。
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public ReservationV4CatalogAdminCodeResult updateRateCodeStatus(
|
||||
String catalogId,
|
||||
ReservationV4CatalogAdminStatusUpdateRequest request,
|
||||
AuthenticatedUserContext actor) {
|
||||
return updateCatalogCodeStatus(catalogId, ReservationV4CatalogType.RATE_CODE, request, actor);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询通用代码目录管理列表。
|
||||
*/
|
||||
private PlatformPageResult<ReservationV4CatalogAdminCodeResult> listCatalogCodes(
|
||||
String hotelId,
|
||||
ReservationV4CatalogType catalogType,
|
||||
String keyword,
|
||||
String status,
|
||||
Integer pageNum,
|
||||
Integer pageSize) {
|
||||
String accessibleHotelId = resolveHotelId(hotelId);
|
||||
ReservationPageSnapshot<ReservationV4CatalogCodeSnapshot> page = catalogRepository.queryAdminCatalogCodes(
|
||||
accessibleHotelId,
|
||||
catalogType.name(),
|
||||
trimToNull(keyword),
|
||||
normalizeNullableStatus(status),
|
||||
normalizePageNum(pageNum),
|
||||
normalizePageSize(pageSize));
|
||||
return new PlatformPageResult<>(
|
||||
page.items().stream().map(this::toCodeResult).toList(),
|
||||
new PlatformPaginationResult(page.pageNum(), page.pageSize(), page.total()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增 Room Type / Rate Code 目录项。
|
||||
*/
|
||||
private ReservationV4CatalogAdminCodeResult createCatalogCode(
|
||||
ReservationV4CatalogAdminCreateCodeRequest request,
|
||||
ReservationV4CatalogType catalogType,
|
||||
AuthenticatedUserContext actor) {
|
||||
if (request == null) {
|
||||
throw invalidRequest("目录新增请求不能为空。");
|
||||
}
|
||||
String hotelId = resolveHotelId(request.hotelId());
|
||||
String code = requireCode(request.code(), "目录代码不能为空。");
|
||||
if (catalogRepository.findCatalogCodeByCode(hotelId, catalogType.name(), code).isPresent()) {
|
||||
throw conflict("目录代码已存在。");
|
||||
}
|
||||
String displayName = requireText(request.displayName(), "目录显示名称不能为空。", MAX_NAME_LENGTH);
|
||||
String metadataJson = normalizeMetadataJson(request.metadataJson());
|
||||
LocalDateTime now = nowUtc();
|
||||
ReservationCatalogCodeEntity entity = new ReservationCatalogCodeEntity();
|
||||
entity.setHotelId(hotelId);
|
||||
entity.setCatalogType(catalogType.name());
|
||||
entity.setCode(code);
|
||||
entity.setDisplayName(displayName);
|
||||
entity.setStatus(ReservationV4CatalogStatus.ACTIVE.name());
|
||||
entity.setSourceSystem(ReservationV4CatalogSourceSystem.SYSTEM_MANAGED.name());
|
||||
entity.setExternalId(optionalText(request.externalId(), MAX_EXTERNAL_ID_LENGTH, "外部目录 ID 过长。"));
|
||||
entity.setSortOrder(request.sortOrder() == null ? 0 : request.sortOrder());
|
||||
entity.setCatalogVersion(resolveCatalogVersion(request.catalogVersion(), now));
|
||||
entity.setLastSyncedAt(null);
|
||||
entity.setMetadataJson(metadataJson);
|
||||
entity.setVersion(0L);
|
||||
entity.setCreatedAt(now);
|
||||
entity.setUpdatedAt(now);
|
||||
try {
|
||||
catalogRepository.insertCatalogCode(entity);
|
||||
} catch (DuplicateKeyException exception) {
|
||||
throw conflict("目录代码已存在。");
|
||||
}
|
||||
ReservationV4CatalogAdminCodeResult after = getCodeResult(entity.getId());
|
||||
audit(actor, TARGET_TYPE_CODE, stringId(entity.getId()), "CREATE_" + catalogType.name() + "_CATALOG", null, after);
|
||||
return after;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新 Room Type / Rate Code 目录状态,并校验 ID 所属目录类型。
|
||||
*/
|
||||
private ReservationV4CatalogAdminCodeResult updateCatalogCodeStatus(
|
||||
String catalogId,
|
||||
ReservationV4CatalogType catalogType,
|
||||
ReservationV4CatalogAdminStatusUpdateRequest request,
|
||||
AuthenticatedUserContext actor) {
|
||||
Long id = parseId(catalogId, "目录 ID 不合法。");
|
||||
String targetStatus = normalizeRequiredStatus(request);
|
||||
ReservationV4CatalogCodeSnapshot beforeSnapshot = catalogRepository.findCatalogCodeById(id)
|
||||
.orElseThrow(() -> notFound("目录不存在。"));
|
||||
if (!catalogType.name().equals(beforeSnapshot.catalogType())) {
|
||||
throw notFound("目录不存在。");
|
||||
}
|
||||
resolveHotelId(beforeSnapshot.hotelId());
|
||||
ReservationV4CatalogAdminCodeResult before = toCodeResult(beforeSnapshot);
|
||||
if (targetStatus.equals(beforeSnapshot.status())) {
|
||||
return before;
|
||||
}
|
||||
long expectedVersion = beforeSnapshot.version() == null ? 0L : beforeSnapshot.version();
|
||||
boolean updated = catalogRepository.updateCatalogCodeStatus(
|
||||
id,
|
||||
targetStatus,
|
||||
expectedVersion,
|
||||
expectedVersion + 1,
|
||||
nowUtc());
|
||||
if (!updated) {
|
||||
throw conflict("目录已被其他操作更新,请刷新后重试。");
|
||||
}
|
||||
ReservationV4CatalogAdminCodeResult after = getCodeResult(id);
|
||||
audit(actor, TARGET_TYPE_CODE, stringId(id), "UPDATE_" + catalogType.name() + "_CATALOG_STATUS", before, after);
|
||||
return after;
|
||||
}
|
||||
|
||||
private ReservationV4CatalogAdminAccountResult getAccountResult(Long id) {
|
||||
return catalogRepository.findAccountById(id)
|
||||
.map(this::toAccountResult)
|
||||
.orElseThrow(() -> notFound("Account 目录不存在。"));
|
||||
}
|
||||
|
||||
private ReservationV4CatalogAdminCodeResult getCodeResult(Long id) {
|
||||
return catalogRepository.findCatalogCodeById(id)
|
||||
.map(this::toCodeResult)
|
||||
.orElseThrow(() -> notFound("目录不存在。"));
|
||||
}
|
||||
|
||||
private ReservationV4CatalogAdminAccountResult toAccountResult(ReservationV4CatalogAccountSnapshot snapshot) {
|
||||
return new ReservationV4CatalogAdminAccountResult(
|
||||
stringId(snapshot.id()),
|
||||
snapshot.hotelId(),
|
||||
snapshot.accountCode(),
|
||||
snapshot.accountName(),
|
||||
snapshot.marketCode(),
|
||||
snapshot.sourceCode(),
|
||||
snapshot.status(),
|
||||
snapshot.sourceSystem(),
|
||||
snapshot.externalAccountId(),
|
||||
snapshot.catalogVersion(),
|
||||
snapshot.metadataJson(),
|
||||
snapshot.version(),
|
||||
UtcTimeFormatter.toUtcOffsetDateTime(snapshot.createdAt()),
|
||||
UtcTimeFormatter.toUtcOffsetDateTime(snapshot.updatedAt()));
|
||||
}
|
||||
|
||||
private ReservationV4CatalogAdminCodeResult toCodeResult(ReservationV4CatalogCodeSnapshot snapshot) {
|
||||
return new ReservationV4CatalogAdminCodeResult(
|
||||
stringId(snapshot.id()),
|
||||
snapshot.hotelId(),
|
||||
snapshot.catalogType(),
|
||||
snapshot.code(),
|
||||
snapshot.displayName(),
|
||||
snapshot.status(),
|
||||
snapshot.sourceSystem(),
|
||||
snapshot.externalId(),
|
||||
snapshot.sortOrder(),
|
||||
snapshot.catalogVersion(),
|
||||
snapshot.metadataJson(),
|
||||
snapshot.version(),
|
||||
UtcTimeFormatter.toUtcOffsetDateTime(snapshot.createdAt()),
|
||||
UtcTimeFormatter.toUtcOffsetDateTime(snapshot.updatedAt()));
|
||||
}
|
||||
|
||||
private void ensureActiveCatalogCode(
|
||||
String hotelId,
|
||||
ReservationV4CatalogType catalogType,
|
||||
String code,
|
||||
String message) {
|
||||
if (catalogRepository.findActiveCatalogCode(hotelId, catalogType.name(), code).isEmpty()) {
|
||||
throw invalidRequest(message);
|
||||
}
|
||||
}
|
||||
|
||||
private String resolveHotelId(String hotelId) {
|
||||
try {
|
||||
return hotelContextService.resolveCurrentHotelId(hotelId);
|
||||
} catch (HotelContextException exception) {
|
||||
throw new AdminOperationException(exception.getStatus(), exception.getErrorCode(), exception.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private String normalizeRequiredStatus(ReservationV4CatalogAdminStatusUpdateRequest request) {
|
||||
if (request == null) {
|
||||
throw invalidRequest("目录状态更新请求不能为空。");
|
||||
}
|
||||
String status = normalizeNullableStatus(request.status());
|
||||
if (status == null) {
|
||||
throw invalidRequest("目录状态不能为空。");
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
private String normalizeNullableStatus(String status) {
|
||||
String normalized = trimToNull(status);
|
||||
if (normalized == null) {
|
||||
return null;
|
||||
}
|
||||
String upper = normalized.toUpperCase(Locale.ROOT);
|
||||
if (!ReservationV4CatalogStatus.ACTIVE.name().equals(upper)
|
||||
&& !ReservationV4CatalogStatus.DISABLED.name().equals(upper)) {
|
||||
throw invalidRequest("目录状态不合法。");
|
||||
}
|
||||
return upper;
|
||||
}
|
||||
|
||||
private String requireCode(String value, String message) {
|
||||
String normalized = trimToNull(value);
|
||||
if (normalized == null) {
|
||||
throw invalidRequest(message);
|
||||
}
|
||||
if (normalized.length() > MAX_CODE_LENGTH) {
|
||||
throw invalidRequest("目录代码过长。");
|
||||
}
|
||||
return normalized.toUpperCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
private String requireText(String value, String message, int maxLength) {
|
||||
String normalized = trimToNull(value);
|
||||
if (normalized == null) {
|
||||
throw invalidRequest(message);
|
||||
}
|
||||
if (normalized.length() > maxLength) {
|
||||
throw invalidRequest("文本长度超过限制。");
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private String optionalText(String value, int maxLength, String message) {
|
||||
String normalized = trimToNull(value);
|
||||
if (normalized == null) {
|
||||
return null;
|
||||
}
|
||||
if (normalized.length() > maxLength) {
|
||||
throw invalidRequest(message);
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private String normalizeMetadataJson(String value) {
|
||||
String normalized = trimToNull(value);
|
||||
if (normalized == null) {
|
||||
return null;
|
||||
}
|
||||
if (normalized.length() > MAX_METADATA_LENGTH) {
|
||||
throw invalidRequest("目录扩展属性 JSON 过长。");
|
||||
}
|
||||
try {
|
||||
objectMapper.readTree(normalized);
|
||||
} catch (Exception exception) {
|
||||
throw invalidRequest("目录扩展属性 JSON 不合法。");
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private String resolveCatalogVersion(String requestedVersion, LocalDateTime now) {
|
||||
String normalized = trimToNull(requestedVersion);
|
||||
if (normalized != null) {
|
||||
if (normalized.length() > MAX_CODE_LENGTH) {
|
||||
throw invalidRequest("目录版本过长。");
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
return "system-managed-" + VERSION_TIME_FORMATTER.format(now);
|
||||
}
|
||||
|
||||
private Long parseId(String value, String message) {
|
||||
String normalized = trimToNull(value);
|
||||
if (normalized == null) {
|
||||
throw invalidRequest(message);
|
||||
}
|
||||
try {
|
||||
return Long.parseLong(normalized);
|
||||
} catch (NumberFormatException exception) {
|
||||
throw invalidRequest(message);
|
||||
}
|
||||
}
|
||||
|
||||
private int normalizePageNum(Integer pageNum) {
|
||||
if (pageNum == null || pageNum < 1) {
|
||||
return DEFAULT_PAGE_NUM;
|
||||
}
|
||||
return Math.min(pageNum, MAX_PAGE_NUM);
|
||||
}
|
||||
|
||||
private int normalizePageSize(Integer pageSize) {
|
||||
if (pageSize == null || pageSize < 1) {
|
||||
return DEFAULT_PAGE_SIZE;
|
||||
}
|
||||
return Math.min(pageSize, MAX_PAGE_SIZE);
|
||||
}
|
||||
|
||||
private LocalDateTime nowUtc() {
|
||||
return LocalDateTime.now(ZoneOffset.UTC);
|
||||
}
|
||||
|
||||
private String stringId(Long id) {
|
||||
return id == null ? null : id.toString();
|
||||
}
|
||||
|
||||
private AdminOperationException invalidRequest(String message) {
|
||||
return new AdminOperationException(HttpStatus.BAD_REQUEST, "RESERVATION_CATALOG_INVALID_REQUEST", message);
|
||||
}
|
||||
|
||||
private AdminOperationException notFound(String message) {
|
||||
return new AdminOperationException(HttpStatus.NOT_FOUND, "RESERVATION_CATALOG_NOT_FOUND", message);
|
||||
}
|
||||
|
||||
private AdminOperationException conflict(String message) {
|
||||
return new AdminOperationException(HttpStatus.CONFLICT, "RESERVATION_CATALOG_CONFLICT", message);
|
||||
}
|
||||
|
||||
private void audit(
|
||||
AuthenticatedUserContext actor,
|
||||
String targetType,
|
||||
String targetId,
|
||||
String action,
|
||||
Object before,
|
||||
Object after) {
|
||||
auditLogService.record(new PlatformAdminAuditLogDraft(
|
||||
actor,
|
||||
targetType,
|
||||
targetId,
|
||||
action,
|
||||
toJson(before),
|
||||
toJson(after)));
|
||||
}
|
||||
|
||||
private String toJson(Object value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return objectMapper.writeValueAsString(value);
|
||||
} catch (JsonProcessingException exception) {
|
||||
throw new AdminOperationException(
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
"ADMIN_AUDIT_SERIALIZE_FAILED",
|
||||
"系统管理审计序列化失败。");
|
||||
}
|
||||
}
|
||||
|
||||
private String trimToNull(String value) {
|
||||
if (value == null || value.trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return value.trim();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user