From 68f3daf02eb6e782b4ddfdd950e8d43a59f78601 Mon Sep 17 00:00:00 2001 From: Mauve Date: Mon, 26 Oct 2026 09:32:16 +0800 Subject: [PATCH] update --- pom.xml | 11 +- .../common/enums/EumnDaylightStatus.java | 3 +- .../common/listener/ConfigListener.java | 4 + .../controllers/DaylightTestController.java | 4 +- .../DaylightConfigurationScheduler.java | 51 ++++- .../DaylightFetchConfigurationService.java | 189 +++++++++++++++++- .../CfgDaylightConfigurationDao.java | 11 +- .../daylight/services/DaylightApiService.java | 2 +- ...ervationCustomFieldsNotificationEvent.java | 24 +++ .../ReservationHeaderCustomFields.java | 19 ++ ...vationCustomFieldsNotificationHandler.java | 107 ++++++++++ .../impl/ReservationNotificationHandler.java | 106 ++++++---- .../models/SysBookingProfileMapping.java | 20 ++ .../SysBookingProfileMappingDao.java | 15 ++ .../services/SysBookingCacheService.java | 35 ++++ .../reservation/models/Bookings.java | 2 + .../reservation/services/BookingService.java | 2 +- src/main/resources/application-dev.yml | 4 + 18 files changed, 546 insertions(+), 63 deletions(-) create mode 100644 src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/ReservationCustomFieldsNotificationEvent.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/ReservationHeaderCustomFields.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/eventhandlers/impl/ReservationCustomFieldsNotificationHandler.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/logging/models/SysBookingProfileMapping.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/logging/repositories/SysBookingProfileMappingDao.java create mode 100644 src/main/java/com/mgm/mcrm/apigateway/logging/services/SysBookingCacheService.java diff --git a/pom.xml b/pom.xml index f2ab9df..9743efd 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ UTF-8 21 - 11.0.25 + 11.0.26 4.2.18.Final 5.6.4 5.2.3 @@ -29,6 +29,7 @@ 2.22.3 3.2.3 2.26.1 + 9.2.0.Beta1 ${java.version} ${java.version} @@ -92,8 +93,12 @@ org.springframework.boot spring-boot-starter-mail - + + jakarta.validation + jakarta.validation-api + 4.0.0-M1 + org.springframework.boot spring-boot-starter-validation @@ -183,7 +188,7 @@ com.auth0 java-jwt - 4.6.1 + 4.6.2 com.eatthepath diff --git a/src/main/java/com/mgm/mcrm/apigateway/common/enums/EumnDaylightStatus.java b/src/main/java/com/mgm/mcrm/apigateway/common/enums/EumnDaylightStatus.java index 781bd06..c0ed085 100644 --- a/src/main/java/com/mgm/mcrm/apigateway/common/enums/EumnDaylightStatus.java +++ b/src/main/java/com/mgm/mcrm/apigateway/common/enums/EumnDaylightStatus.java @@ -24,8 +24,7 @@ public enum EumnDaylightStatus { DUEOUT("DUEOUT","DUE_OUT"), CANCELED("CANCELED","CANCELED"), CANCELLED("CANCELLED","CANCELED"), - NO_SHOW("NO SHOW","CANCELED"), - NOSHOW("NOSHOW","CANCELED"); + NO_SHOW("No-Show", "NO_SHOW"); private String key; private String value; diff --git a/src/main/java/com/mgm/mcrm/apigateway/common/listener/ConfigListener.java b/src/main/java/com/mgm/mcrm/apigateway/common/listener/ConfigListener.java index 1a9a66c..26ea03c 100644 --- a/src/main/java/com/mgm/mcrm/apigateway/common/listener/ConfigListener.java +++ b/src/main/java/com/mgm/mcrm/apigateway/common/listener/ConfigListener.java @@ -9,6 +9,7 @@ import com.mgm.mcrm.apigateway.daylight.repositories.CfgCompRuleDao; import com.mgm.mcrm.apigateway.daylight.services.DaylightApiService; import com.mgm.mcrm.apigateway.daylight.webhook.eventhandlers.impl.AvailableRoomCountUpdatedHandler; import com.mgm.mcrm.apigateway.daylight.webhook.eventhandlers.impl.CompRuleLimitsExceededChangedHandler; +import com.mgm.mcrm.apigateway.daylight.webhook.eventhandlers.impl.ReservationCustomFieldsNotificationHandler; import com.mgm.mcrm.apigateway.daylight.webhook.eventhandlers.impl.ReservationNotificationHandler; import com.mgm.mcrm.apigateway.daylight.webhook.eventhandlers.impl.RoomTypeDateAvailabilityChangedHandler; import com.mgm.mcrm.apigateway.daylight.webhook.services.DaylightWebhookFactory; @@ -33,6 +34,8 @@ public class ConfigListener implements ApplicationListener testFetchDaylightConfiguration() { BaseApiResponse propertiesResponse = daylightFetchConfigurationService.fetchDaylightProperties(); if (!propertiesResponse.isSuccess()) { diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/common/scheduler/DaylightConfigurationScheduler.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/common/scheduler/DaylightConfigurationScheduler.java index 4405028..e1f3184 100644 --- a/src/main/java/com/mgm/mcrm/apigateway/daylight/common/scheduler/DaylightConfigurationScheduler.java +++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/common/scheduler/DaylightConfigurationScheduler.java @@ -4,8 +4,8 @@ import com.mgm.mcrm.apigateway.common.dtos.BaseApiResponse; import com.mgm.mcrm.apigateway.daylight.common.services.DaylightFetchConfigurationService; import com.mgm.mcrm.apigateway.common.utils.LogUtil; import com.mgm.mcrm.apigateway.daylight.repositories.CfgDaylightConfigurationDao; -import com.mgm.mcrm.apigateway.daylight.services.DaylightApiService; import jakarta.annotation.Resource; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.event.EventListener; import org.springframework.core.annotation.Order; @@ -13,14 +13,17 @@ import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; @Component +@ConditionalOnProperty( + prefix = "daylight.scheduler", + name = "open", + havingValue = "true" +) public class DaylightConfigurationScheduler { @Resource private DaylightFetchConfigurationService daylightFetchConfigurationService; @Resource private CfgDaylightConfigurationDao cfgDaylightConfigurationDao; - @Resource - private DaylightApiService daylightApiService; @Order(1) @EventListener(ApplicationReadyEvent.class) @@ -93,6 +96,33 @@ public class DaylightConfigurationScheduler { } } + @Order(4) + @EventListener(ApplicationReadyEvent.class) + public void fetchResourceManagementRequestorsOnStartupIfMissing() { + LogUtil.info(DaylightConfigurationScheduler.class, "Checking saved Daylight resource requestors on startup"); + try { + if (!cfgDaylightConfigurationDao.existsSavedDaylightProperties()) { + LogUtil.info(DaylightConfigurationScheduler.class, "No saved Daylight properties found, skip resource requestors startup fetch"); + return; + } + + if (cfgDaylightConfigurationDao.existsSavedDaylightResourceRequestors()) { + LogUtil.info(DaylightConfigurationScheduler.class, "Saved Daylight resource requestors found, skip startup fetch"); + return; + } + + LogUtil.warn(DaylightConfigurationScheduler.class, "No saved Daylight resource requestors found, start startup fetch"); + BaseApiResponse requestorResponse = daylightFetchConfigurationService.fetchResourceManagementRequestors(); + if (requestorResponse.isSuccess()) { + LogUtil.info(DaylightConfigurationScheduler.class, "Startup resource requestors completed: " + requestorResponse.getMessage()); + } else { + LogUtil.warn(DaylightConfigurationScheduler.class, "Startup resource requestors failed: " + requestorResponse.getMessage()); + } + } catch (Exception e) { + LogUtil.error(DaylightConfigurationScheduler.class, "Startup resource requestors error: " + e.getMessage()); + } + } + /** * 按配置的时间和时区执行,拉取 Daylight 配置数据,默认北京时间每天凌晨2点 */ @@ -102,7 +132,7 @@ public class DaylightConfigurationScheduler { LogUtil.info(DaylightConfigurationScheduler.class, "Start scheduled task: fetchDaylightAllData"); try { // 1. 拉取 Properties - LogUtil.info(DaylightConfigurationScheduler.class, "Step 1/3: Fetching properties..."); + LogUtil.info(DaylightConfigurationScheduler.class, "Step 1/4: Fetching properties..."); BaseApiResponse propResponse = daylightFetchConfigurationService.fetchDaylightProperties(); if (propResponse.isSuccess()) { LogUtil.info(DaylightConfigurationScheduler.class, "Properties completed: " + propResponse.getMessage()); @@ -111,7 +141,7 @@ public class DaylightConfigurationScheduler { } // 2. 拉取 Configuration - LogUtil.info(DaylightConfigurationScheduler.class, "Step 2/3: Fetching configurations..."); + LogUtil.info(DaylightConfigurationScheduler.class, "Step 2/4: Fetching configurations..."); BaseApiResponse configResponse = daylightFetchConfigurationService.fetchDaylightConfiguration(); if (configResponse.isSuccess()) { LogUtil.info(DaylightConfigurationScheduler.class, "Configurations completed: " + configResponse.getMessage()); @@ -120,7 +150,7 @@ public class DaylightConfigurationScheduler { } // 3. 拉取 Block Codes - LogUtil.info(DaylightConfigurationScheduler.class, "Step 3/3: Fetching block codes..."); + LogUtil.info(DaylightConfigurationScheduler.class, "Step 3/4: Fetching block codes..."); BaseApiResponse blockResponse = daylightFetchConfigurationService.fetchDaylightBlockCodes(); if (blockResponse.isSuccess()) { LogUtil.info(DaylightConfigurationScheduler.class, "Block codes completed: " + blockResponse.getMessage()); @@ -128,6 +158,15 @@ public class DaylightConfigurationScheduler { LogUtil.warn(DaylightConfigurationScheduler.class, "Block codes failed: " + blockResponse.getMessage()); } + // 4. 拉取 Resource Requestors + LogUtil.info(DaylightConfigurationScheduler.class, "Step 4/4: Fetching resource requestors..."); + BaseApiResponse requestorResponse = daylightFetchConfigurationService.fetchResourceManagementRequestors(); + if (requestorResponse.isSuccess()) { + LogUtil.info(DaylightConfigurationScheduler.class, "Resource requestors completed: " + requestorResponse.getMessage()); + } else { + LogUtil.warn(DaylightConfigurationScheduler.class, "Resource requestors failed: " + requestorResponse.getMessage()); + } + LogUtil.info(DaylightConfigurationScheduler.class, "Scheduled task finished: fetchDaylightAllData"); } catch (Exception e) { LogUtil.error(DaylightConfigurationScheduler.class, "Scheduled task error: " + e.getMessage()); diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/common/services/DaylightFetchConfigurationService.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/common/services/DaylightFetchConfigurationService.java index 871dd40..893dd1e 100644 --- a/src/main/java/com/mgm/mcrm/apigateway/daylight/common/services/DaylightFetchConfigurationService.java +++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/common/services/DaylightFetchConfigurationService.java @@ -1,6 +1,7 @@ package com.mgm.mcrm.apigateway.daylight.common.services; import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; import com.mgm.mcrm.apigateway.basesetting.property.models.CfgProperties; import com.mgm.mcrm.apigateway.basesetting.property.repositories.PropertyDao; @@ -30,9 +31,12 @@ import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; @Service public class DaylightFetchConfigurationService { @@ -41,6 +45,8 @@ public class DaylightFetchConfigurationService { private static final DateTimeFormatter UTC_UPDATED_AT_FORMATTER = DateTimeFormatter .ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS") .withZone(ZoneOffset.UTC); + private static final String RESOURCE_REQUESTOR_CONFIG_NAME = "ResourceRequestor"; + private static final int RESOURCE_REQUESTOR_PAGE_SIZE = 200; private final HttpProxyErrorHandler httpErrorHandler = new HttpProxyErrorHandler(); @Resource @@ -93,7 +99,7 @@ public class DaylightFetchConfigurationService { boolean hasSavedConfig = cfgDaylightConfigurationDao.existsSavedConfigurationByDlPropertyId(dlPropertyId); if (lastSync.isPresent() && hasSavedConfig) { updatedAtParam = "&updatedAt=" - + UTC_UPDATED_AT_FORMATTER.format(lastSync.get().getLastSyncAt().toInstant()); + + UTC_UPDATED_AT_FORMATTER.format(lastSync.get().getLastSyncAt().toInstant().minusSeconds(1)); log.info("Property {} incremental sync, updatedAt: {}", dlPropertyId, updatedAtParam); } else { if (lastSync.isPresent() && !hasSavedConfig) { @@ -506,7 +512,7 @@ public class DaylightFetchConfigurationService { .findByDlPropertyIdAndConfigName(dlPropertyId, "BlockCode"); if (lastSync.isPresent() && existingConfig != null) { updatedAtParam = "&updatedAt=" - + UTC_UPDATED_AT_FORMATTER.format(lastSync.get().getLastSyncAt().toInstant()); + + UTC_UPDATED_AT_FORMATTER.format(lastSync.get().getLastSyncAt().toInstant().minusSeconds(1)); log.info("Property {} BlockCode incremental sync, updatedAt: {}", dlPropertyId, updatedAtParam); } else { if (lastSync.isPresent() && existingConfig == null) { @@ -635,4 +641,183 @@ public class DaylightFetchConfigurationService { } return response; } + + /** + * 按 property 分页拉取资源请求人;首次全量同步,之后按成功同步时间增量合并。 + */ + public BaseApiResponse fetchResourceManagementRequestors() { + BaseApiResponse response = new BaseApiResponse<>(); + Date syncStartTime = new Date(); + + try { + TokenResponse daylightApiToken = daylightTokenService.getApiAccessToken(); + if (daylightApiToken == null || StringUtils.isBlank(daylightApiToken.getAccessToken())) { + response.setSuccess(false); + response.setMessage("Invoke daylight token failed"); + return response; + } + + List allProperties = propertyDao.findByCategory("DAYLIGHT"); + if (allProperties == null || allProperties.isEmpty()) { + response.setSuccess(false); + response.setMessage("No properties found with category 'DAYLIGHT'"); + return response; + } + + int savedCount = 0; + List failedProperties = new ArrayList<>(); + for (CfgProperties property : allProperties) { + String dlPropertyId = property.getDlPropertyId(); + if (StringUtils.isBlank(dlPropertyId)) { + continue; + } + + boolean fetchSuccess = false; + int recordCount = 0; + try { + CfgDaylightConfiguration existingConfig = cfgDaylightConfigurationDao + .findByDlPropertyIdAndConfigName(dlPropertyId, RESOURCE_REQUESTOR_CONFIG_NAME); + Optional lastSync = syncLogDao + .findTopByConfigNameAndDlPropertyIdAndSyncStatusOrderByLastSyncAtDesc( + RESOURCE_REQUESTOR_CONFIG_NAME, dlPropertyId, "SUCCESS"); + + // 配置不存在时强制全量,两个日期条件均不传。 + boolean isIncremental = existingConfig != null && lastSync.isPresent() + && lastSync.get().getLastSyncAt() != null; + String dateParams = ""; + if (isIncremental) { + String syncTime = DateTimeFormatter.ISO_INSTANT.format(lastSync.get().getLastSyncAt().toInstant().minusSeconds(1)); + // 接口通过同一个 filter 接收两个日期条件,独立的日期 query 参数会被忽略。 + dateParams = "&filter=auditData.lastUpdateTime=ge=" + syncTime + + ",auditData.creationTime=ge=" + syncTime; + log.info("Property {} ResourceRequestor incremental sync, UTC time: {}", dlPropertyId, syncTime); + } else { + log.info("Property {} ResourceRequestor full sync, no audit date parameters", dlPropertyId); + } + + List allResults = fetchResourceRequestorPages( + dlPropertyId, daylightApiToken.getAccessToken(), dateParams, response); + List oldItems = existingConfig != null + && StringUtils.isNotBlank(existingConfig.getConfigValue()) + ? JSON.parseArray(existingConfig.getConfigValue(), JSONObject.class) + : new ArrayList<>(); + + // 所有页面成功后才保存,增量没有变化时保留原配置。 + if (!isIncremental || !allResults.isEmpty()) { + List savedItems = allResults; + if (isIncremental) { + Map mergedMap = new LinkedHashMap<>(); + for (JSONObject item : oldItems) { + mergedMap.put(getConfigItemKey(item), item); + } + for (JSONObject item : allResults) { + mergedMap.put(getConfigItemKey(item), item); + } + savedItems = new ArrayList<>(mergedMap.values()); + } + + CfgDaylightConfiguration config = existingConfig != null ? existingConfig : new CfgDaylightConfiguration(); + config.setConfigName(RESOURCE_REQUESTOR_CONFIG_NAME); + config.setDlPropertyId(dlPropertyId); + if (config.getCreatedAt() == null) { + config.setCreatedAt(new Date()); + } + config.setConfigValue(JSONObject.toJSONString(savedItems)); + config.setUpdatedAt(new Date()); + cfgDaylightConfigurationDao.save(config); + logConfigurationChanges("ResourceRequestor " + (isIncremental ? "incremental sync" : "full sync"), + dlPropertyId, RESOURCE_REQUESTOR_CONFIG_NAME, oldItems, savedItems); + savedCount++; + } + recordCount = allResults.size(); + fetchSuccess = true; + } catch (Exception e) { + failedProperties.add(dlPropertyId); + log.warn("Property {} ResourceRequestor sync failed", dlPropertyId, e); + } + + SysLogDaylightConfigurationSync syncLog = new SysLogDaylightConfigurationSync(); + syncLog.setConfigName(RESOURCE_REQUESTOR_CONFIG_NAME); + syncLog.setDlPropertyId(dlPropertyId); + syncLog.setLastSyncAt(syncStartTime); + syncLog.setSyncStatus(fetchSuccess ? "SUCCESS" : "FAILED"); + syncLog.setRecordCount(recordCount); + syncLog.setCreatedAt(new Date()); + syncLogDao.save(syncLog); + } + + response.setSuccess(failedProperties.isEmpty()); + response.setMessage("Fetched and saved " + savedCount + " resource requestor configuration entries." + + (failedProperties.isEmpty() ? "" : " Failed properties: " + StringUtils.join(failedProperties, ","))); + } catch (Exception e) { + log.warn("ResourceRequestor sync failed", e); + response.setSuccess(false); + response.setMessage(e.getMessage()); + } + return response; + } + + private List fetchResourceRequestorPages(String dlPropertyId, String accessToken, + String dateParams, BaseApiResponse response) throws Exception { + String apiUrl = daylightApiSetting.getServerUrl() + "/api-gateway/resource-management/v1/resource"; + HttpHeaders headers = new HttpHeaders(); + headers.set("authorization", "Bearer " + accessToken); + headers.set("Ac-Property-Id", dlPropertyId); + List allResults = new ArrayList<>(); + int pageNum = 1; + Set fetchedIds = new HashSet<>(); + long totalCount; + do { + // 此接口的分页请求参数为 pageNumber;pageNum 会被忽略并重复返回第一页。 + String queryString = "pageSize=" + RESOURCE_REQUESTOR_PAGE_SIZE + "&pageNumber=" + pageNum + dateParams; + log.info("Fetching ResourceRequestor page {} for Ac-Property-Id: {}, query: {}", pageNum, dlPropertyId, queryString); + ResponseEntity requestResponse = webProxyService.invokeRequest( + WebProxyService.DAYLIGHT_API, HttpMethod.GET, apiUrl, queryString, headers, new byte[0], + Duration.ofSeconds(daylightApiSetting.getApiTimeout()), httpErrorHandler).block(); + if (requestResponse == null) { + throw new IllegalStateException("Http request server can not visit"); + } + response.setStatusCode(requestResponse.getStatusCode().value()); + if (!requestResponse.getStatusCode().is2xxSuccessful()) { + byte[] errorBody = requestResponse.getBody(); + throw new IllegalStateException(errorBody != null + ? new String(errorBody, StandardCharsets.UTF_8) : "ResourceRequestor request failed"); + } + byte[] responseBody = requestResponse.getBody(); + if (responseBody == null || responseBody.length == 0) { + throw new IllegalStateException("ResourceRequestor response body is empty"); + } + JSONObject responseJson = JSONObject.parseObject(new String(responseBody, StandardCharsets.UTF_8)); + JSONArray results = responseJson != null ? responseJson.getJSONArray("results") : null; + JSONObject paging = responseJson != null ? responseJson.getJSONObject("paging") : null; + Long pageTotalCount = paging != null ? paging.getLong("totalCount") : null; + if (results == null || pageTotalCount == null || pageTotalCount < 0) { + throw new IllegalStateException("ResourceRequestor response is missing valid results or paging.totalCount"); + } + Integer responsePageNumber = paging.getInteger("pageNumber"); + if (responsePageNumber != null && responsePageNumber != pageNum) { + throw new IllegalStateException("ResourceRequestor requested page " + pageNum + + " but received page " + responsePageNumber + "; pagination parameters were not applied"); + } + totalCount = pageTotalCount; + log.info("ResourceRequestor response for Ac-Property-Id: {}, requestedPage={}, responsePage={}, pageCount={}, totalCount={}", + dlPropertyId, pageNum, responsePageNumber, results.size(), totalCount); + if (results.isEmpty() && allResults.size() < totalCount) { + throw new IllegalStateException("ResourceRequestor returned an empty page before all records were fetched"); + } + List pageResults = results.toJavaList(JSONObject.class); + for (JSONObject item : pageResults) { + String id = item.getString("id"); + if (StringUtils.isBlank(id) || !fetchedIds.add(id)) { + throw new IllegalStateException("ResourceRequestor returned a missing or repeated resource id on page " + pageNum); + } + } + allResults.addAll(pageResults); + } while ((long) pageNum++ * RESOURCE_REQUESTOR_PAGE_SIZE < totalCount); + + if (allResults.size() != totalCount) { + throw new IllegalStateException("ResourceRequestor returned a record count different from paging.totalCount"); + } + return allResults; + } } diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/repositories/CfgDaylightConfigurationDao.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/repositories/CfgDaylightConfigurationDao.java index 4dbf457..f3c7052 100644 --- a/src/main/java/com/mgm/mcrm/apigateway/daylight/repositories/CfgDaylightConfigurationDao.java +++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/repositories/CfgDaylightConfigurationDao.java @@ -17,7 +17,7 @@ public interface CfgDaylightConfigurationDao extends JpaRepository 0 then true else false end " + "from CfgDaylightConfiguration c " - + "where c.configName not in ('Property', 'BlockCode') " + + "where c.configName not in ('Property', 'BlockCode', 'ResourceRequestor') " + "and c.configValue is not null " + "and c.configValue <> ''") boolean existsSavedDaylightConfiguration(); @@ -25,7 +25,7 @@ public interface CfgDaylightConfigurationDao extends JpaRepository 0 then true else false end " + "from CfgDaylightConfiguration c " + "where c.dlPropertyId = ?1 " - + "and c.configName not in ('Property', 'BlockCode') " + + "and c.configName not in ('Property', 'BlockCode', 'ResourceRequestor') " + "and c.configValue is not null " + "and c.configValue <> ''") boolean existsSavedConfigurationByDlPropertyId(String dlPropertyId); @@ -36,4 +36,11 @@ public interface CfgDaylightConfigurationDao extends JpaRepository ''") boolean existsSavedDaylightBlockCodes(); + + @Query("select case when count(c) > 0 then true else false end " + + "from CfgDaylightConfiguration c " + + "where c.configName = 'ResourceRequestor' " + + "and c.configValue is not null " + + "and c.configValue <> ''") + boolean existsSavedDaylightResourceRequestors(); } diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/services/DaylightApiService.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/services/DaylightApiService.java index c92b572..3757847 100644 --- a/src/main/java/com/mgm/mcrm/apigateway/daylight/services/DaylightApiService.java +++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/services/DaylightApiService.java @@ -58,7 +58,7 @@ public class DaylightApiService { BaseApiResponse result = new BaseApiResponse<>(); try { - String url = "/api-gateway/booking/v1/reservations/" + dlReservationId + "?extend=PurchaseElements,CustomFields"; + String url = "/api-gateway/booking/v1/reservations/" + dlReservationId + "?extend=CustomFields"; // 获取对应的值 Header[] headers = HttpHeaderBuilder.custom() .contentType("application/json") diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/ReservationCustomFieldsNotificationEvent.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/ReservationCustomFieldsNotificationEvent.java new file mode 100644 index 0000000..ccd625f --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/ReservationCustomFieldsNotificationEvent.java @@ -0,0 +1,24 @@ +package com.mgm.mcrm.apigateway.daylight.webhook.dtos.reservation; + +import lombok.Data; + +import java.util.List; + +@Data +public class ReservationCustomFieldsNotificationEvent { + private String reservationId; + private String trigger; + private CustomFieldsState previous; + private CustomFieldsState current; + + @Data + public static class CustomFieldsState { + private List customFields; + } + + @Data + public static class CustomField { + private String location; + private String value; + } +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/ReservationHeaderCustomFields.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/ReservationHeaderCustomFields.java new file mode 100644 index 0000000..3c200a0 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/dtos/reservation/ReservationHeaderCustomFields.java @@ -0,0 +1,19 @@ +package com.mgm.mcrm.apigateway.daylight.webhook.dtos.reservation; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; + +@Data +public class ReservationHeaderCustomFields { + @JSONField(name = "Booker") + private String booker; + + @JSONField(name = "Approver") + private String approver; + + @JSONField(name = "App_Rea") + private String approvalReason; + + @JSONField(name = "WAIVEDEPOSIT", alternateNames = {"WAIVEDPOSIT"}) + private String waiveDeposit; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/eventhandlers/impl/ReservationCustomFieldsNotificationHandler.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/eventhandlers/impl/ReservationCustomFieldsNotificationHandler.java new file mode 100644 index 0000000..2d1c5ba --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/eventhandlers/impl/ReservationCustomFieldsNotificationHandler.java @@ -0,0 +1,107 @@ +package com.mgm.mcrm.apigateway.daylight.webhook.eventhandlers.impl; + +import com.alibaba.fastjson2.JSONObject; +import com.mgm.mcrm.apigateway.common.dtos.BaseApiResponse; +import com.mgm.mcrm.apigateway.daylight.webhook.dtos.reservation.ReservationCustomFieldsNotificationEvent; +import com.mgm.mcrm.apigateway.daylight.webhook.dtos.reservation.ReservationCustomFieldsNotificationEvent.CustomField; +import com.mgm.mcrm.apigateway.daylight.webhook.dtos.reservation.ReservationHeaderCustomFields; +import com.mgm.mcrm.apigateway.daylight.webhook.eventhandlers.DaylightWebhookHandler; +import com.mgm.mcrm.apigateway.reservation.models.Bookings; +import com.mgm.mcrm.apigateway.reservation.repositories.BookingDao; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +@Slf4j +@Component +@RequiredArgsConstructor +public class ReservationCustomFieldsNotificationHandler implements DaylightWebhookHandler { + + private final BookingDao bookingDao; + + @Override + public BaseApiResponse handle(String payload, Map headers) throws InterruptedException { + BaseApiResponse result = new BaseApiResponse<>(); + ReservationCustomFieldsNotificationEvent eventData; + try { + eventData = JSONObject.parseObject(payload, ReservationCustomFieldsNotificationEvent.class); + } catch (Exception err) { + result.setMessage("Convert Payload failed:[" + err.getMessage() + "]"); + return result; + } + + if (eventData == null || StringUtils.isBlank(eventData.getReservationId())) { + result.setMessage("Empty reservationId for this event"); + return result; + } + + try { + TimeUnit.SECONDS.sleep(8); + } catch (InterruptedException err) { + Thread.currentThread().interrupt(); + throw err; + } + + String reservationId = eventData.getReservationId(); + List bookings = bookingDao.findByDlReservationId(reservationId); + Bookings booking = null; + if (!CollectionUtils.isEmpty(bookings)) { + for (Bookings candidate : bookings) { + if ("N".equals(candidate.getApproveStatus())) { + booking = candidate; + break; + } + } + } + if (booking == null) { + log.warn("No booking with approveStatus=N for reservationId={}", reservationId); + result.setMessage("No booking with approveStatus=N for reservationId[" + reservationId + "]"); + return result; + } + + if (eventData.getCurrent() == null || CollectionUtils.isEmpty(eventData.getCurrent().getCustomFields())) { + result.setMessage("Empty current.customFields for this event"); + return result; + } + + CustomField reservationHeader = eventData.getCurrent().getCustomFields().stream() + .filter(field -> field != null && "ReservationHeader".equals(field.getLocation())) + .findFirst() + .orElse(null); + if (reservationHeader == null || StringUtils.isBlank(reservationHeader.getValue())) { + result.setMessage("Empty ReservationHeader value for this event"); + return result; + } + + ReservationHeaderCustomFields customFields; + Long approvedId; + try { + customFields = JSONObject.parseObject(reservationHeader.getValue(), ReservationHeaderCustomFields.class); + if (customFields == null) { + result.setMessage("Empty ReservationHeader value for this event"); + return result; + } + approvedId = StringUtils.isBlank(customFields.getApprover()) + ? null : Long.valueOf(customFields.getApprover().trim()); + } catch (Exception err) { + result.setMessage("Convert ReservationHeader value failed:[" + err.getMessage() + "]"); + return result; + } + + booking.setCreatedBy(customFields.getBooker()); + booking.setBooker(customFields.getBooker()); + booking.setApprovalReason(customFields.getApprovalReason()); + booking.setApprovedId(approvedId); + bookingDao.save(booking); + + log.info("Updated reservation custom fields for bookingId={}, reservationId={}", booking.getId(), reservationId); + result.setSuccess(true); + return result; + } +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/eventhandlers/impl/ReservationNotificationHandler.java b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/eventhandlers/impl/ReservationNotificationHandler.java index 2f76968..fa8ab58 100644 --- a/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/eventhandlers/impl/ReservationNotificationHandler.java +++ b/src/main/java/com/mgm/mcrm/apigateway/daylight/webhook/eventhandlers/impl/ReservationNotificationHandler.java @@ -10,11 +10,13 @@ import com.mgm.mcrm.apigateway.daylight.services.DaylightApiService; import com.mgm.mcrm.apigateway.daylight.webhook.dtos.reservation.ReservationNotificationEvent; import com.mgm.mcrm.apigateway.daylight.webhook.dtos.reservation.StatusCodeEntity; import com.mgm.mcrm.apigateway.daylight.webhook.eventhandlers.DaylightWebhookHandler; +//import com.mgm.mcrm.apigateway.logging.services.SysBookingCacheService; import com.mgm.mcrm.apigateway.reservation.models.Bookings; import com.mgm.mcrm.apigateway.reservation.services.BookingService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.Strings; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; @@ -32,6 +34,8 @@ public class ReservationNotificationHandler implements DaylightWebhookHandler { private final DaylightApiService daylightApiService; +// private final SysBookingCacheService sysBookingCacheService; + @Value("${daylight.system-id}") private String systemId; @@ -103,7 +107,17 @@ public class ReservationNotificationHandler implements DaylightWebhookHandler { StatusCodeEntity statusCode = eventData.getStatusCode(); if(statusCode!=null) { - result.setLastOperaStatus(EumnDaylightStatus.getStatusByDaylightStatus(statusCode.getDescription().toUpperCase())); + String lastOperaStatus = EumnDaylightStatus.getStatusByDaylightStatus(statusCode.getDescription().toUpperCase()); + + result.setLastOperaStatus(lastOperaStatus); + + if (Strings.CI.equalsAny(lastOperaStatus, "RESERVED", "CHECKED_IN", "DUE_IN")) { + result.setApproveStatus("N"); + } else if ("CANCELED".equalsIgnoreCase(lastOperaStatus)) { + result.setApproveStatus("C"); + } else { + result.setApproveStatus("N"); + } } result.setDlPropertyId(eventData.getPropertyId()); result.setDlRoomTypeId(eventData.getRoomTypeId()); @@ -113,49 +127,53 @@ public class ReservationNotificationHandler implements DaylightWebhookHandler { String etaTime = dateTime.format(DateTimeFormatter.ofPattern("HH:mm")); result.setEta(etaTime); } - BaseApiResponse response = - daylightApiService.getDaylightReservationByDlId(eventData.getReservationId(), eventData.getPropertyId()); - - if (response.isSuccess() && Objects.nonNull(response.getData())) { - JSONObject embedded = response.getData().getJSONObject("_embedded"); - if (embedded != null) { - JSONArray customFields = embedded.getJSONArray("customFields"); - - if (customFields != null) { - for (int j = 0; j < customFields.size(); j++) { - JSONObject jsonObject = customFields.getJSONObject(j); - String value = jsonObject.getString("value"); - JSONObject valueJson = JSONObject.parseObject(value); - String booker = valueJson.getString("Booker"); - - if (StringUtils.isNotBlank(booker)) { - result.setCreatedBy(booker); - result.setBooker(booker); - } - } - } - } - - if (StringUtils.isBlank(result.getDlExternalNo())) { - JSONArray jsonArray = response.getData().getJSONArray("externalIds"); - if (!CollectionUtils.isEmpty(jsonArray)) { - - for (int i = 0; i < jsonArray.size(); i++) { - JSONObject jsonObject = jsonArray.getJSONObject(i); - - if (jsonObject.getString("systemId").equals(systemId)) { - result.setDlExternalNo(jsonObject.getString("number")); - break; - } - } - - if (StringUtils.isBlank(result.getDlExternalNo())) { - String dlExternalNo = jsonArray.getJSONObject(0).getString("number"); - result.setDlExternalNo(dlExternalNo); - } - } - } - } +// BaseApiResponse response = +// daylightApiService.getDaylightReservationByDlId(eventData.getReservationId(), eventData.getPropertyId()); +// +// if (response.isSuccess()) { +// if (Objects.nonNull(response.getData())) { +// JSONObject embedded = response.getData().getJSONObject("_embedded"); +// if (embedded != null) { +// JSONArray customFields = embedded.getJSONArray("customFields"); +// +// if (customFields != null) { +// for (int j = 0; j < customFields.size(); j++) { +// JSONObject jsonObject = customFields.getJSONObject(j); +// String value = jsonObject.getString("value"); +// JSONObject valueJson = JSONObject.parseObject(value); +// String booker = valueJson.getString("Booker"); +// +// if (StringUtils.isNotBlank(booker)) { +// result.setCreatedBy(booker); +// result.setBooker(booker); +// } +// } +// } +// } +// +// if (StringUtils.isBlank(result.getDlExternalNo())) { +// JSONArray jsonArray = response.getData().getJSONArray("externalIds"); +// if (!CollectionUtils.isEmpty(jsonArray)) { +// +// for (int i = 0; i < jsonArray.size(); i++) { +// JSONObject jsonObject = jsonArray.getJSONObject(i); +// +// if (jsonObject.getString("systemId").equals(systemId)) { +// result.setDlExternalNo(jsonObject.getString("number")); +// break; +// } +// } +// +// if (StringUtils.isBlank(result.getDlExternalNo())) { +// String dlExternalNo = jsonArray.getJSONObject(0).getString("number"); +// result.setDlExternalNo(dlExternalNo); +// } +// } +// } +// } else { +// log.warn("reservationId={} not found _embedded", eventData.getReservationId()); +// } +// } return result; } diff --git a/src/main/java/com/mgm/mcrm/apigateway/logging/models/SysBookingProfileMapping.java b/src/main/java/com/mgm/mcrm/apigateway/logging/models/SysBookingProfileMapping.java new file mode 100644 index 0000000..f40ea3a --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/logging/models/SysBookingProfileMapping.java @@ -0,0 +1,20 @@ +package com.mgm.mcrm.apigateway.logging.models; + +import jakarta.persistence.*; +import lombok.Data; + +@Data +@Entity +@Table(name="sys_booking_profile_mapping") +public class SysBookingProfileMapping { + @Id + @Column(name = "id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + @Column(name="category") + private String category; + @Column(name="player_id") + private String playerId; + @Column(name="profile_id") + private String profileId; +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/logging/repositories/SysBookingProfileMappingDao.java b/src/main/java/com/mgm/mcrm/apigateway/logging/repositories/SysBookingProfileMappingDao.java new file mode 100644 index 0000000..edeed48 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/logging/repositories/SysBookingProfileMappingDao.java @@ -0,0 +1,15 @@ +package com.mgm.mcrm.apigateway.logging.repositories; + +import com.mgm.mcrm.apigateway.logging.models.SysBookingProfileMapping; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface SysBookingProfileMappingDao extends JpaRepository { + List findAllByCategoryAndPlayerId(String category, String playerId); + + List findByProfileId(String profileId); +} + diff --git a/src/main/java/com/mgm/mcrm/apigateway/logging/services/SysBookingCacheService.java b/src/main/java/com/mgm/mcrm/apigateway/logging/services/SysBookingCacheService.java new file mode 100644 index 0000000..22c9569 --- /dev/null +++ b/src/main/java/com/mgm/mcrm/apigateway/logging/services/SysBookingCacheService.java @@ -0,0 +1,35 @@ +package com.mgm.mcrm.apigateway.logging.services; + +import com.mgm.mcrm.apigateway.logging.repositories.SysBookingProfileMappingDao; +import jakarta.annotation.Resource; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import java.util.List; + +@Service +public class SysBookingCacheService { + + @Value("${mcrm.daylight.booking-search-cache-seconds}") + private int daylightCacheSeconds; + + @Resource + private SysBookingProfileMappingDao sysBookingProfileMappingDao; + + public String getPlayerIdFromProfileId(String profileId) { + + if (StringUtils.isEmpty(profileId)) { + return null; + } + + List result = sysBookingProfileMappingDao.findByProfileId(profileId); + + if (CollectionUtils.isEmpty(result)) { + return null; + } + + return result.getFirst(); + } +} diff --git a/src/main/java/com/mgm/mcrm/apigateway/reservation/models/Bookings.java b/src/main/java/com/mgm/mcrm/apigateway/reservation/models/Bookings.java index 23ca4d4..ad695f4 100644 --- a/src/main/java/com/mgm/mcrm/apigateway/reservation/models/Bookings.java +++ b/src/main/java/com/mgm/mcrm/apigateway/reservation/models/Bookings.java @@ -105,6 +105,8 @@ public class Bookings implements Serializable { private String mscRemark; @Column(name="approval_remark") private String approvalRemark; + @Column(name="approval_reason") + private String approvalReason; @Column(name="room") private String room; @Column(name="sharer") diff --git a/src/main/java/com/mgm/mcrm/apigateway/reservation/services/BookingService.java b/src/main/java/com/mgm/mcrm/apigateway/reservation/services/BookingService.java index 473f9fc..856b4f9 100644 --- a/src/main/java/com/mgm/mcrm/apigateway/reservation/services/BookingService.java +++ b/src/main/java/com/mgm/mcrm/apigateway/reservation/services/BookingService.java @@ -396,7 +396,7 @@ public class BookingService { // book.setGuaranteeType("CGTD"); dbBooking.setPaymentType(null); // book.setOperaProfileId(operaBooking.getOperaProfileId()); - dbBooking.setApproveStatus("N"); + dbBooking.setApproveStatus(daylightBooking.getApproveStatus()); dbBooking.setCategory("DAYLIGHT"); dbBooking.setCreatedAt(DateTimeUtil.getCurrentDateTime("yyyy-MM-dd HH:mm:ss")); diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index dab80a2..e2f4a36 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -141,6 +141,8 @@ mcrm: default-send-to: 201249946@qq.com security: authorization-data: "[MCRM_APP/securItykeyOfCrmapp87895312],[MCRM_CMS/seCuritykeyOfCrmCms8wetw432]" + daylight: + booking-search-cache-seconds: 300 server: port: 9087 referrer-policy: origin-when-cross-origin @@ -179,6 +181,8 @@ apns: daylight: scheduler: + # Enable startup fetches and scheduled tasks. Restart after changes. + open: false # Cron fields: second minute hour day-of-month month day-of-week. Restart after changes. configuration-cron: "0 0 2 * * ?" configuration-zone: "Asia/Shanghai"