diff --git a/CRMMobileApi.iml b/CRMMobileApi.iml
deleted file mode 100644
index a10b566..0000000
--- a/CRMMobileApi.iml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/main/java/com/mgm/mcrm/appsetting/controllers/CfgSettingController.java b/src/main/java/com/mgm/mcrm/appsetting/controllers/CfgSettingController.java
index 9d92faf..4483c9c 100644
--- a/src/main/java/com/mgm/mcrm/appsetting/controllers/CfgSettingController.java
+++ b/src/main/java/com/mgm/mcrm/appsetting/controllers/CfgSettingController.java
@@ -16,6 +16,7 @@ import org.springframework.web.bind.annotation.*;
import com.mgm.mcrm.appsetting.services.SettingService;
import java.util.List;
+import java.util.ArrayList;
import java.util.Map;
@@ -23,12 +24,18 @@ import java.util.Map;
@CrossOrigin(origins = "*")
@RequestMapping("/api")
public class CfgSettingController {
+ private static final List PUBLIC_LOGIN_SETTING_KEYS = List.of("APP_MOCK_SSO", "APP_MOCK_TPD");
@Resource
private SettingService settingService;
@Resource
private SysSettingService sysSettingService;
+ @PostMapping(value = "/app/setting/get_calendar_setting", produces = "application/json;charset=UTF-8")
+ public ResponseEntity>> getCalendarSetting() {
+ return ResponseEntity.ok(BaseApiResponse.ok("Get Setting Successed", settingService.getCalendarSetting()));
+ }
+
@RequestMapping(value = "/web/setting/get_setting_by_key", method = RequestMethod.POST, produces = "application/json;charset=UTF-8")
public ResponseEntity> getSettingByKey(HttpServletRequest request, @RequestBody CfgSettingQueryRequest params) throws Exception {
boolean isSuccess = false;
@@ -51,6 +58,29 @@ public class CfgSettingController {
return ResponseEntity.status(HttpStatus.OK).body(result);
}
+ @PostMapping(value = "/web/setting/get_public_setting_by_key", produces = "application/json;charset=UTF-8")
+ public ResponseEntity> getPublicSettingByKey(HttpServletRequest request,
+ @RequestBody(required=false) CfgSettingQueryRequest params) throws Exception {
+ if (params == null || !"MACAU_STRING".equals(params.getKeyName())) {
+ return ResponseEntity.ok(BaseApiResponse.error("SETTING_KEY_NOT_ALLOWED", "Setting key is not allowed"));
+ }
+ return getSettingByKey(request, params);
+ }
+
+ @PostMapping(value = "/system/sys_setting/get_public_login_settings", produces = "application/json;charset=UTF-8")
+ public ResponseEntity>> getPublicLoginSettings(HttpServletRequest request) {
+ boolean internalNetwork = NetworkUtil.isMatchInternal(NetworkUtil.getClientIpAddr(request));
+ List settings = new ArrayList<>();
+ for (String key : PUBLIC_LOGIN_SETTING_KEYS) {
+ SysSetting setting = sysSettingService.getSysSettingByKey(key);
+ if (setting != null) {
+ setting.setInternalNetwork(internalNetwork);
+ settings.add(setting);
+ }
+ }
+ return ResponseEntity.ok(BaseApiResponse.ok("", settings));
+ }
+
@RequestMapping(value = "/system/sys_setting/get", method = RequestMethod.POST, produces = "application/json;charset=UTF-8")
public ResponseEntity> getSysSettingByKey(HttpServletRequest request, @RequestBody SysSettingQueryRequest queryFilter) {
BaseApiResponse result = new BaseApiResponse<>();
diff --git a/src/main/java/com/mgm/mcrm/appsetting/services/SettingService.java b/src/main/java/com/mgm/mcrm/appsetting/services/SettingService.java
index 43e9c81..918bc5e 100644
--- a/src/main/java/com/mgm/mcrm/appsetting/services/SettingService.java
+++ b/src/main/java/com/mgm/mcrm/appsetting/services/SettingService.java
@@ -1,6 +1,8 @@
package com.mgm.mcrm.appsetting.services;
import java.util.List;
+import java.util.ArrayList;
+import org.apache.commons.lang3.Strings;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
@@ -9,9 +11,27 @@ import com.mgm.mcrm.appsetting.repositories.SettingDao;
@Service
public class SettingService {
+ @org.springframework.beans.factory.annotation.Value("${calendar.data-mock-enabled:false}")
+ private boolean calendarDataMockEnabled;
@Resource
private SettingDao settingDao;
+
+ public List getCalendarSetting() {
+ if (calendarDataMockEnabled)
+ return com.mgm.mcrm.roomcalendar.services.CalendarMockData.settings();
+ List result = new ArrayList<>();
+ for (CfgSettings setting : settingDao.findAll()) {
+ if (setting != null && setting.getKeys() != null
+ && Strings.CS.equalsAny(setting.getKeys(),
+ "CALENDAR_VIEW_PREVIOUS_DAYS", "CALENDAR_VIEW_VISIBLE_DAYS",
+ "CALENDAR_VIEW_YESTERDAY_START", "CALENDAR_VIEW_YESTERDAY_END",
+ "MAX_SELECTED_ROOM_TYPES")) {
+ result.add(setting);
+ }
+ }
+ return result;
+ }
public CfgSettings getSettingByKey(String key) {
CfgSettings setting = null;
diff --git a/src/main/java/com/mgm/mcrm/authorization/controllers/MobileLoginController.java b/src/main/java/com/mgm/mcrm/authorization/controllers/MobileLoginController.java
index 333f7a8..c059c7f 100644
--- a/src/main/java/com/mgm/mcrm/authorization/controllers/MobileLoginController.java
+++ b/src/main/java/com/mgm/mcrm/authorization/controllers/MobileLoginController.java
@@ -153,6 +153,7 @@ public class MobileLoginController {
if(SysConstants.PERMISSION_APP_TODO_LIST.equals(per.getId())) userInfo.setCanViewTodoList(true);
if(SysConstants.PERMISSION_APP_DASHBOARD.equals(per.getId())) userInfo.setCanViewDashboard(true);
if(SysConstants.PERMISSION_APP_APPROVAL.equals(per.getId())) userInfo.setCanViewApproval(true);
+ if(SysConstants.PERMISSION_APP_CALENDAR.equals(per.getId())) userInfo.setCanViewCalendar(true);
}
}
Token accessToken = JwtTokenUtil.generateTokenByLoginUser(request.getRequestURI(),loginUser, SysConstants.MOBILE_TOKEN_TYPE,false);
@@ -251,28 +252,9 @@ public class MobileLoginController {
String isReNew = request.getParameter("isRenewRefreshToken");
boolean isRenewRefreshToken = (!"false".equalsIgnoreCase(isReNew));
- RefreshTokenResponse result = new RefreshTokenResponse();
- LoginUserInfo loginUser = JwtTokenUtil.parseUserFromRequest(request);
- if(loginUser!=null){
- Token accessToken = JwtTokenUtil.generateTokenByLoginUser(request.getRequestURI(),loginUser,SysConstants.MOBILE_TOKEN_TYPE,false);
- userTokenService.updateTokenByUniqueId(loginUser.getTokenUuid(),SysConstants.MOBILE_TOKEN_TYPE,accessToken);
- result.setAccessToken(accessToken.getToken());
- if(isRenewRefreshToken){
- Token refreshToken = JwtTokenUtil.generateTokenByLoginUser(request.getRequestURI(),loginUser,SysConstants.MOBILE_TOKEN_TYPE,true);
- userTokenService.saveToken(refreshToken);
- result.setRefreshToken(refreshToken.getToken());
- if(StringUtils.isNotBlank(loginUser.getTokenUuid())){
- userTokenService.removeTokenByUniqueId(loginUser.getTokenUuid(),SysConstants.MOBILE_TOKEN_TYPE);
- }
- }
- result.setSuccess(true);
- return ResponseEntity.status(HttpStatus.OK).body(result);
- }else{
- result.setSuccess(false);
- result.setCode("401");
- result.setMessage("Unauthorized operation");
- return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(result);
- }
+ RefreshTokenResponse result = userTokenService.refreshMobileToken(
+ request.getHeader("RefreshToken"), request.getRequestURI(), isRenewRefreshToken);
+ return ResponseEntity.status(result.isSuccess() ? HttpStatus.OK : HttpStatus.UNAUTHORIZED).body(result);
}
@RequestMapping(value="/app/logout",method = RequestMethod.POST)
diff --git a/src/main/java/com/mgm/mcrm/authorization/filters/JwtAuthFilter.java b/src/main/java/com/mgm/mcrm/authorization/filters/JwtAuthFilter.java
index 1ef8e41..fe7df16 100644
--- a/src/main/java/com/mgm/mcrm/authorization/filters/JwtAuthFilter.java
+++ b/src/main/java/com/mgm/mcrm/authorization/filters/JwtAuthFilter.java
@@ -60,7 +60,13 @@ public class JwtAuthFilter extends OncePerRequestFilter {
) throws ServletException, IOException{
boolean validatePass = true;
try {
- String token = request.getHeader("Authorization");
+ boolean refreshRequest = "/api/app/refresh_token".equals(request.getRequestURI());
+ String token = request.getHeader(refreshRequest ? "RefreshToken" : "Authorization");
+ if (refreshRequest && !"OPTIONS".equalsIgnoreCase(request.getMethod())
+ && userTokenService.validateMobileRefreshToken(token) == null) {
+ sendErrorResponse(response, 401, "Invalid Refresh Token");
+ return;
+ }
if(StringUtils.isNotBlank(token) && token.startsWith("Bearer ")){
token = token.substring(7);
}
@@ -68,7 +74,11 @@ public class JwtAuthFilter extends OncePerRequestFilter {
if(StringUtils.isNotBlank(token) && dbToken!=null) {
JWTVerifier jwtVerifier = JwtTokenUtil.getJwtVerifier();
try {
- jwtVerifier.verify(token);
+ var verified = jwtVerifier.verify(token);
+ if (!refreshRequest && "refresh".equals(verified.getClaim("token_use").asString())) {
+ sendErrorResponse(response, 401, "Invalid Access Token");
+ return;
+ }
}
catch (SignatureVerificationException e) {
LogUtil.error(JwtAuthFilter.class, "[preHandle] SignatureVerificationException \nException Message:" + e.getMessage());
@@ -76,11 +86,9 @@ public class JwtAuthFilter extends OncePerRequestFilter {
validatePass=false;
}
catch (TokenExpiredException e) {
- if(!request.getRequestURI().contains("refresh_token")) {
- LogUtil.error(JwtAuthFilter.class, "[preHandle] TokenExpiredException \nException Message:" + e.getMessage());
- sendErrorResponse(response,401, e.getMessage());
- validatePass=false;
- }
+ LogUtil.error(JwtAuthFilter.class, "[preHandle] TokenExpiredException \nException Message:" + e.getMessage());
+ sendErrorResponse(response,401, e.getMessage());
+ validatePass=false;
}
catch (JWTVerificationException e) {
LogUtil.error(JwtAuthFilter.class, "[preHandle] JWTVerificationException \nException Message:" + e.getMessage());
diff --git a/src/main/java/com/mgm/mcrm/authorization/services/MgmUserService.java b/src/main/java/com/mgm/mcrm/authorization/services/MgmUserService.java
index 6550b93..a0d0693 100644
--- a/src/main/java/com/mgm/mcrm/authorization/services/MgmUserService.java
+++ b/src/main/java/com/mgm/mcrm/authorization/services/MgmUserService.java
@@ -38,6 +38,10 @@ import com.mgm.mcrm.common.dtos.http_util.HttpResponseResult;
@Service
public class MgmUserService {
+ // Temporary Calendar integration testing only; leave disabled outside test configuration.
+ @org.springframework.beans.factory.annotation.Value("${calendar.permission-mock-enabled:false}")
+ private boolean calendarPermissionMockEnabled;
+
@Resource
private MGMApiService mgmApiService;
@Resource
@@ -200,6 +204,7 @@ public class MgmUserService {
}
private UserInfoApiRecord supplementInfoId(String userId,UserInfoApiRecord info) {
+ info.setCanViewCalendar(calendarPermissionMockEnabled);
// 待完善,有可能需要一层一层获得property,division,department,team
CfgProperties property = propertyDao.getByName(info.getProperty());
if(property!=null) {
@@ -237,6 +242,7 @@ public class MgmUserService {
if(SysConstants.PERMISSION_APP_TODO_LIST.equals(per.getPermissionId())) info.setCanViewTodoList(true);
if(SysConstants.PERMISSION_APP_DASHBOARD.equals(per.getPermissionId())) info.setCanViewDashboard(true);
if(SysConstants.PERMISSION_APP_APPROVAL.equals(per.getPermissionId())) info.setCanViewApproval(true);
+ if(SysConstants.PERMISSION_APP_CALENDAR.equals(per.getPermissionId())) info.setCanViewCalendar(true);
}
}
}
diff --git a/src/main/java/com/mgm/mcrm/authorization/services/UserTokenService.java b/src/main/java/com/mgm/mcrm/authorization/services/UserTokenService.java
index aadc406..482c0b7 100644
--- a/src/main/java/com/mgm/mcrm/authorization/services/UserTokenService.java
+++ b/src/main/java/com/mgm/mcrm/authorization/services/UserTokenService.java
@@ -1,6 +1,10 @@
package com.mgm.mcrm.authorization.services;
import com.auth0.jwt.JWT;
+import com.mgm.mcrm.authorization.utils.JwtTokenUtil;
+import com.mgm.mcrm.authorization.dtos.LoginUserInfo;
+import com.mgm.mcrm.authorization.dtos.RefreshTokenResponse;
+import com.mgm.mcrm.common.constants.SysConstants;
import com.mgm.mcrm.authorization.models.Token;
import com.mgm.mcrm.authorization.repositories.TokenDao;
import jakarta.transaction.Transactional;
@@ -15,6 +19,46 @@ import java.time.LocalDateTime;
public class UserTokenService {
private final TokenDao tokenDao;
+ public LoginUserInfo validateMobileRefreshToken(String value) {
+ if (StringUtils.isBlank(value)) return null;
+ if (value.startsWith("Bearer ")) value = value.substring(7);
+ try {
+ var jwt = JwtTokenUtil.getJwtVerifier().verify(value);
+ if (!"refresh".equals(jwt.getClaim("token_use").asString())
+ || !Integer.valueOf(SysConstants.MOBILE_TOKEN_TYPE).equals(jwt.getClaim("token_type").asInt())
+ || jwt.getExpiresAt() == null || StringUtils.isBlank(jwt.getId())) return null;
+ Token stored = tokenDao.findByUuidAndTokenType(jwt.getId(), SysConstants.MOBILE_TOKEN_TYPE);
+ if (stored == null || !value.equals(stored.getToken()) || stored.getExpireTime() == null
+ || !stored.getExpireTime().isAfter(LocalDateTime.now())) return null;
+ LoginUserInfo user = JwtTokenUtil.parseUserFromToken(value);
+ return user != null && StringUtils.isNotBlank(user.getLoginId())
+ && user.getLoginId().equals(stored.getCreatedBy()) ? user : null;
+ } catch (Exception invalidToken) {
+ return null;
+ }
+ }
+
+ @Transactional
+ public RefreshTokenResponse refreshMobileToken(String value, String issuer, boolean renew) {
+ RefreshTokenResponse result = new RefreshTokenResponse();
+ LoginUserInfo user = validateMobileRefreshToken(value);
+ if (user == null || (renew && tokenDao.deleteByUuidAndTokenType(user.getTokenUuid(), SysConstants.MOBILE_TOKEN_TYPE) != 1)) {
+ result.setCode("401");
+ result.setMessage("Unauthorized operation");
+ return result;
+ }
+ Token access = JwtTokenUtil.generateTokenByLoginUser(issuer, user, SysConstants.MOBILE_TOKEN_TYPE, false);
+ saveToken(access);
+ result.setAccessToken(access.getToken());
+ if (renew) {
+ Token refresh = JwtTokenUtil.generateTokenByLoginUser(issuer, user, SysConstants.MOBILE_TOKEN_TYPE, true);
+ saveToken(refresh);
+ result.setRefreshToken(refresh.getToken());
+ }
+ result.setSuccess(true);
+ return result;
+ }
+
@Transactional
public Token saveToken(Token token){
if(token!=null){
@@ -57,6 +101,7 @@ public class UserTokenService {
newDbToken.setTokenType(newToken.getTokenType());
newDbToken.setExpireTime(newToken.getExpireTime());
newDbToken.setCreatedAt(LocalDateTime.now());
+ newDbToken.setCreatedBy(newToken.getCreatedBy());
newDbToken.setUpdatedBy(newToken.getCreatedBy());
newDbToken.setUpdatedBy(newToken.getCreatedBy());
newDbToken.setUpdatedAt(LocalDateTime.now());
diff --git a/src/main/java/com/mgm/mcrm/authorization/utils/JwtTokenUtil.java b/src/main/java/com/mgm/mcrm/authorization/utils/JwtTokenUtil.java
index 3b567f1..d89942c 100644
--- a/src/main/java/com/mgm/mcrm/authorization/utils/JwtTokenUtil.java
+++ b/src/main/java/com/mgm/mcrm/authorization/utils/JwtTokenUtil.java
@@ -62,6 +62,8 @@ public class JwtTokenUtil {
.withIssuedAt(now).withNotBefore(now).withExpiresAt(expDate).withJWTId(uuid)
.withClaim("login_id", user.getLoginId())
.withClaim("group_name", user.getGroupName())
+ .withClaim("token_use", isRefreshToken ? "refresh" : "access")
+ .withClaim("token_type", tokenType)
.withClaim("last_post_time", now)
.withSubject(String.valueOf(user.getLoginId()))
.sign(Algorithm.HMAC256(secretKey));
@@ -106,7 +108,8 @@ public class JwtTokenUtil {
loginUser.setId(-1L);
}
loginUser.setLoginId(decodeData.getClaim("login_id").asString());
- loginUser.setGroupName(decodeData.getClaim("user_group").asString());
+ String groupName = decodeData.getClaim("group_name").asString();
+ loginUser.setGroupName(groupName != null ? groupName : decodeData.getClaim("user_group").asString());
loginUser.setTokenUuid(decodeData.getId());
}catch(Exception err) {
LogUtil.error(SysCommon.class, "Parse Token failed:"+err.getMessage());
diff --git a/src/main/java/com/mgm/mcrm/basesetting/daylight/services/DaylightService.java b/src/main/java/com/mgm/mcrm/basesetting/daylight/services/DaylightService.java
index 54917ec..1fad7a5 100644
--- a/src/main/java/com/mgm/mcrm/basesetting/daylight/services/DaylightService.java
+++ b/src/main/java/com/mgm/mcrm/basesetting/daylight/services/DaylightService.java
@@ -1019,10 +1019,10 @@ public class DaylightService {
// String currentDate = DateTimeUtil.getCurrentDate("yyyy-MM-dd");
- String url = "/api-gateway/booking/v1/reservations?pageNumber=1&pageSize=50&filter=profileId==" + profileId
+ String url = "/api-gateway/aggregator/v1/reservations?pageNumber=1&pageSize=50&filter=profileId==" + profileId
+ ";(statusCode.code==DI,statusCode.code==RS,statusCode.code==DO,statusCode.code==IH,statusCode.code==CX)"
// + ";departureDate=ge=" + currentDate
- + "&extend=PurchaseElements&extend=CustomFields&extend=Breakdown&extend=DoorKeys&sort=-createdAt";
+ + "&extend=PurchaseElements,CustomFields,Breakdown,DoorKeys&sort=-createdAt";
Map map = new HashMap<>();
try {
for (CfgProperties property : properties) {
diff --git a/src/main/java/com/mgm/mcrm/basesetting/property/models/CfgProperties.java b/src/main/java/com/mgm/mcrm/basesetting/property/models/CfgProperties.java
index cc72821..ab46dd7 100644
--- a/src/main/java/com/mgm/mcrm/basesetting/property/models/CfgProperties.java
+++ b/src/main/java/com/mgm/mcrm/basesetting/property/models/CfgProperties.java
@@ -36,6 +36,12 @@ public class CfgProperties implements Serializable {
private String dlPropertyId;
@Column(name="sort_order")
private Integer sortOrder;
+ @Column(name="show_calendar", nullable = false)
+ private Integer showCalendar;
+ @Column(name="calendar_name", length = 256)
+ private String calendarName;
+ @Column(name="calendar_order")
+ private Integer calendarOrder;
@Column(name="created_at")
private Date createdAt;
diff --git a/src/main/java/com/mgm/mcrm/basesetting/property/repositories/PropertyDao.java b/src/main/java/com/mgm/mcrm/basesetting/property/repositories/PropertyDao.java
index 6ae3df5..e36c7bd 100644
--- a/src/main/java/com/mgm/mcrm/basesetting/property/repositories/PropertyDao.java
+++ b/src/main/java/com/mgm/mcrm/basesetting/property/repositories/PropertyDao.java
@@ -1,5 +1,6 @@
package com.mgm.mcrm.basesetting.property.repositories;
+import java.util.Collection;
import java.util.List;
import org.springframework.data.domain.Page;
@@ -51,4 +52,6 @@ public interface PropertyDao extends JpaRepository {
CfgProperties queryByName(String propertyName);
List findByCategory(String category);
+
+ List findByCalendarNameIn(Collection calendarNames);
}
diff --git a/src/main/java/com/mgm/mcrm/basesetting/room/models/CfgRooms.java b/src/main/java/com/mgm/mcrm/basesetting/room/models/CfgRooms.java
index e336663..cb5f9f1 100644
--- a/src/main/java/com/mgm/mcrm/basesetting/room/models/CfgRooms.java
+++ b/src/main/java/com/mgm/mcrm/basesetting/room/models/CfgRooms.java
@@ -51,6 +51,14 @@ public class CfgRooms implements Serializable {
@Column(name = "category")
private String category;
+ @Column(name="show_calendar", nullable = false)
+ private Integer showCalendar;
+ @Column(name="calendar_name", length = 256)
+ private String calendarName;
+ @Column(name="calendar_order")
+ private Integer calendarOrder;
+ @Column(name="default_show_calendar")
+ private Integer defaultShowCalendar;
@Transient
private List roomTypeIds;
diff --git a/src/main/java/com/mgm/mcrm/basesetting/room/repositories/RoomDao.java b/src/main/java/com/mgm/mcrm/basesetting/room/repositories/RoomDao.java
index 1bc9eab..e3fa447 100644
--- a/src/main/java/com/mgm/mcrm/basesetting/room/repositories/RoomDao.java
+++ b/src/main/java/com/mgm/mcrm/basesetting/room/repositories/RoomDao.java
@@ -10,9 +10,11 @@ import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Transactional;
import com.mgm.mcrm.basesetting.room.models.CfgRooms;
+import com.mgm.mcrm.roomcalendar.repositories.RoomCalendarRoomProjection;
public interface RoomDao extends JpaRepository {
@@ -34,4 +36,13 @@ public interface RoomDao extends JpaRepository {
@Query(value="select * from cfg_rooms where id =?1",nativeQuery=true)
CfgRooms getById(Long id);
+
+ @Query("select r.propertyId as propertyId, r.name as name, r.calendarName as calendarName, "
+ + "r.calendarOrder as calendarOrder, r.defaultShowCalendar as defaultShowCalendar "
+ + "from CfgRooms r "
+ + "where r.showCalendar = 1 and r.propertyId in :propertyIds "
+ + "order by r.propertyId asc, r.calendarOrder asc, r.name asc")
+ List queryCalendarRoomsByPropertyIds(@Param("propertyIds") List propertyIds);
+
+ List findByPropertyIdInAndCalendarNameIn(List propertyIds, List calendarNames);
}
diff --git a/src/main/java/com/mgm/mcrm/booking/services/BookingService.java b/src/main/java/com/mgm/mcrm/booking/services/BookingService.java
index cd52bf3..53681d5 100644
--- a/src/main/java/com/mgm/mcrm/booking/services/BookingService.java
+++ b/src/main/java/com/mgm/mcrm/booking/services/BookingService.java
@@ -1430,13 +1430,13 @@ public class BookingService {
DaylightReservationResponse daylightReservationByDlId =
dlService.getDaylightReservationByDlId(reservationId, property.getDlPropertyId());
- bookings.setDlAccountId(daylightReservationByDlId.getAccountId());
- bookings.setDlReservationId(reservationId);
- bookings.setConfirmationNo(confirmationNumber);
- bookings.setLastOperaStatus("RESERVED");
- bookings.setApproveStatus("N");
-
- bookingDao.save(bookings);
+// bookings.setDlAccountId(daylightReservationByDlId.getAccountId());
+// bookings.setDlReservationId(reservationId);
+// bookings.setConfirmationNo(confirmationNumber);
+// bookings.setLastOperaStatus("RESERVED");
+// bookings.setApproveStatus("N");
+//
+// bookingDao.save(bookings);
dlService.createDaylightCompRule(
daylightReservationByDlId.getAccountId(),
@@ -1452,7 +1452,7 @@ public class BookingService {
//Create Reservation 后处理WaitListBooking
if (bookings.getWaitlistId() != null) {
Bookings waitlistBooking = bookingDao.findById(bookings.getWaitlistId()).orElse(null);
- waitlistBooking.setConfirmationNo(bookings.getConfirmationNo());
+// waitlistBooking.setConfirmationNo(bookings.getConfirmationNo());
waitlistBooking.setApproveStatus("WF");
waitlistBooking.setUpdatedAt(DateTimeUtil.getCurrentDateTime("yyyy-MM-dd HH:mm:ss"));
bookingDao.save(waitlistBooking);
diff --git a/src/main/java/com/mgm/mcrm/booking/services/WaitlistBookingService.java b/src/main/java/com/mgm/mcrm/booking/services/WaitlistBookingService.java
index 7b4275f..e17c533 100644
--- a/src/main/java/com/mgm/mcrm/booking/services/WaitlistBookingService.java
+++ b/src/main/java/com/mgm/mcrm/booking/services/WaitlistBookingService.java
@@ -326,7 +326,7 @@ public class WaitlistBookingService {
Date creatdDate = DateTimeUtil.getCurrentDateTime("yyyy-MM-dd HH:mm:ss");
if(request!=null) {
- LoginUserInfo userInfo =SysCommon.parseUserFromToken(request);
+ LoginUserInfo userInfo = SysCommon.parseUserFromToken(request);
if (userInfo != null) {
userId = userInfo.getLoginId();
if(!StringUtils.isEmpty(userId)) {
diff --git a/src/main/java/com/mgm/mcrm/common/config/WebFilterConfig.java b/src/main/java/com/mgm/mcrm/common/config/WebFilterConfig.java
index 6458ad7..b8f8099 100644
--- a/src/main/java/com/mgm/mcrm/common/config/WebFilterConfig.java
+++ b/src/main/java/com/mgm/mcrm/common/config/WebFilterConfig.java
@@ -59,11 +59,10 @@ public class WebFilterConfig {
JwtTokenUtil.setAppSessionExpiration(apiTokenExpiration);
JwtTokenUtil.setAppSessionExpiration(mobileSessionExpiration);
List excludePatterns = Arrays.asList(
- "/api/system/write_log",
- "/api/system/sys_setting/get_list",
+ "/api/system/write_public_log",
+ "/api/system/sys_setting/get_public_login_settings",
"/api/app/sso/*",
- "/api/app/booking/mock_cancel",
- "/api/web/setting/get_setting_by_key",
+ "/api/web/setting/get_public_setting_by_key",
"/swagger*/*",
"/v3/**",
"/api-docs",
diff --git a/src/main/java/com/mgm/mcrm/common/constants/SysConstants.java b/src/main/java/com/mgm/mcrm/common/constants/SysConstants.java
index f3b28a3..ec9079c 100644
--- a/src/main/java/com/mgm/mcrm/common/constants/SysConstants.java
+++ b/src/main/java/com/mgm/mcrm/common/constants/SysConstants.java
@@ -26,5 +26,6 @@ public class SysConstants {
public static final String PERMISSION_APP_TODO_LIST="10001_03";
public static final String PERMISSION_APP_DASHBOARD="10001_04";
public static final String PERMISSION_APP_APPROVAL="10001_05";
+ public static final String PERMISSION_APP_CALENDAR="10001_07";
}
diff --git a/src/main/java/com/mgm/mcrm/common/dtos/mgm_api/TodoDetailResult.java b/src/main/java/com/mgm/mcrm/common/dtos/mgm_api/TodoDetailResult.java
index bb936bf..a75e3ea 100644
--- a/src/main/java/com/mgm/mcrm/common/dtos/mgm_api/TodoDetailResult.java
+++ b/src/main/java/com/mgm/mcrm/common/dtos/mgm_api/TodoDetailResult.java
@@ -21,14 +21,19 @@ public class TodoDetailResult {
private String Salutation = null;
private String Location=null;
private String ActivePrgId=null;
- private Integer PrgTO=null;
- private Integer PrgWin=null;
+ @com.alibaba.fastjson.annotation.JSONField(name = "PrgTO")
+ private String PrgTO = null;
+ @com.alibaba.fastjson.annotation.JSONField(name = "PrgWin")
+ private String PrgWin = null;
private Double TORIPercentage=null;
- private Integer FrontMoney=null;
- private Integer TTO=null;
+ @com.alibaba.fastjson.annotation.JSONField(name = "FrontMoney")
+ private String FrontMoney = null;
+ @com.alibaba.fastjson.annotation.JSONField(name = "TTO")
+ private String TTO = null;
@JsonProperty("01CompBalance")
- private Integer ComBalance=null;
+ @com.alibaba.fastjson.annotation.JSONField(name = "01CompBalance")
+ private String ComBalance = null;
private String R3MTO=null;
private String R3MTOD=null;
@@ -42,21 +47,42 @@ public class TodoDetailResult {
private String R6MTPD=null;
private String R6MTheo = null;
private String R6MRIPercentage=null;
- private Integer ExpComm=null;
- private Integer NetCreditOutstanding=null;
- private Integer UnsettledExpenses=null;
- private Integer TodayTo=null;
- private Integer TodayTPD=null;
- private Integer TodayWin=null;
- private Integer TodayTheo = null;
- private Integer TripWin=null;
- private Integer TripTPD=null;
- private Integer TripTheo = null;
- private Double TripRIPercentage=null;
- private Integer YesterdayTO=null;
- private Integer YesterdayWin=null;
- private Integer YesterdayPlayHour=null;
- private Integer AveragePlayHour = null;
+ @com.alibaba.fastjson.annotation.JSONField(name = "ExpComm")
+ private String ExpComm = null;
+ @com.alibaba.fastjson.annotation.JSONField(name = "IRCProgramFundBalance")
+ @JsonProperty("iRCProgramFundBalance")
+ private String ircProgramFundBalance = null;
+ @com.alibaba.fastjson.annotation.JSONField(name = "NetCreditOutstanding")
+ private String NetCreditOutstanding = null;
+ @com.alibaba.fastjson.annotation.JSONField(name = "UnsettledExpenses")
+ private String UnsettledExpenses = null;
+ @com.alibaba.fastjson.annotation.JSONField(name = "TodayTo")
+ private String TodayTo = null;
+ @com.alibaba.fastjson.annotation.JSONField(name = "TodayTPD")
+ private String TodayTPD = null;
+ @com.alibaba.fastjson.annotation.JSONField(name = "TodayWin")
+ private String TodayWin = null;
+ @com.alibaba.fastjson.annotation.JSONField(name = "TodayTheo")
+ private String TodayTheo = null;
+ @com.alibaba.fastjson.annotation.JSONField(name = "TripWin")
+ private String TripWin = null;
+ @com.alibaba.fastjson.annotation.JSONField(name = "TripTPD")
+ private String TripTPD = null;
+ @com.alibaba.fastjson.annotation.JSONField(name = "TripTheo")
+ private String TripTheo = null;
+ @com.alibaba.fastjson.annotation.JSONField(name = "TripRIPercentage")
+ private String TripRIPercentage = null;
+ @com.alibaba.fastjson.annotation.JSONField(name = "YesterdayTO")
+ private String YesterdayTO = null;
+ @com.alibaba.fastjson.annotation.JSONField(name = "YesterdayWin")
+ private String YesterdayWin = null;
+ @com.alibaba.fastjson.annotation.JSONField(name = "YesterdayPlayHour")
+ private String YesterdayPlayHour = null;
+ @com.alibaba.fastjson.annotation.JSONField(name = "AveragePlayHour")
+ private String AveragePlayHour = null;
+ @com.alibaba.fastjson.annotation.JSONField(name = "HouseAdvantagePercentage")
+ @JsonProperty("HouseAdvantagePercentage")
+ private String houseAdvantage = null;
private List Descriptions = new ArrayList();
public TodoDetailResult() {
}
diff --git a/src/main/java/com/mgm/mcrm/common/dtos/mgm_api/TodoListRecord.java b/src/main/java/com/mgm/mcrm/common/dtos/mgm_api/TodoListRecord.java
index 18b9bcf..2991a8b 100644
--- a/src/main/java/com/mgm/mcrm/common/dtos/mgm_api/TodoListRecord.java
+++ b/src/main/java/com/mgm/mcrm/common/dtos/mgm_api/TodoListRecord.java
@@ -211,6 +211,11 @@ public class TodoListRecord {
return MAId;
}
+ @com.fasterxml.jackson.annotation.JsonProperty("maId")
+ public Integer getCalendarMaId() {
+ return MAId;
+ }
+
public void setMAId(Integer mAId) {
MAId = mAId;
}
diff --git a/src/main/java/com/mgm/mcrm/common/dtos/mgm_api/UserInfoApiRecord.java b/src/main/java/com/mgm/mcrm/common/dtos/mgm_api/UserInfoApiRecord.java
index 309eab4..f505ffb 100644
--- a/src/main/java/com/mgm/mcrm/common/dtos/mgm_api/UserInfoApiRecord.java
+++ b/src/main/java/com/mgm/mcrm/common/dtos/mgm_api/UserInfoApiRecord.java
@@ -26,6 +26,7 @@ public class UserInfoApiRecord {
private boolean canViewTodoList=false;
private boolean canViewDashboard=false;
private boolean canViewApproval=false;
+ private boolean canViewCalendar=false;
private String groupName;
private String phone;
private boolean loginTag;
diff --git a/src/main/java/com/mgm/mcrm/common/services/MGMApiService.java b/src/main/java/com/mgm/mcrm/common/services/MGMApiService.java
index c5a4e16..5e02526 100644
--- a/src/main/java/com/mgm/mcrm/common/services/MGMApiService.java
+++ b/src/main/java/com/mgm/mcrm/common/services/MGMApiService.java
@@ -44,8 +44,38 @@ public class MGMApiService {
private static final String API_GET_APPROVER_DATA="/RequestWaitlistData";
private static final String API_GET_APPROVER="/RequestRoomBookingApprovalRouting";
private static final String API_GET_TODO_LIST = "/GetTodolist";
+ private static final String API_REQUEST_ROOM_CALENDAR = "/RequestRoomCalendar";
+ public HttpResponseResult invokeRequestRoomCalendar(
+ String playerId,
+ String property,
+ String roomType,
+ String startDate,
+ String endDate) throws Exception {
+ return invokeRequestRoomCalendar(playerId, property, roomType, startDate, endDate, 0);
+ }
+
+ public HttpResponseResult invokeRequestRoomCalendar(
+ String playerId,
+ String property,
+ String roomType,
+ String startDate,
+ String endDate,
+ int type) throws Exception {
+ JSONObject requestObj = new JSONObject();
+ Map requestHeader = new HashMap<>();
+ requestHeader.put("Authorization", getAccessToken());
+ if (StringUtils.isNotBlank(playerId)) {
+ requestObj.put("PlayerId", playerId);
+ }
+ requestObj.put("Property", property);
+ requestObj.put("RoomType", roomType);
+ requestObj.put("StartDate", startDate);
+ requestObj.put("EndDate", endDate);
+ return invokeESBAPI(ESB_Server, API_REQUEST_ROOM_CALENDAR, requestObj, "POST", requestHeader, type);
+ }
+
public HttpResponseResult invokeGetPlayerProfile(
String playerId,
String userId) throws Exception {
@@ -416,4 +446,4 @@ public class MGMApiService {
LogUtil.info(MGMApiService.class, "[invokeESBAPI]SpendTime:" + spanTime + "ms");
return httpResult;
}
-}
\ No newline at end of file
+}
diff --git a/src/main/java/com/mgm/mcrm/logging/controllers/SysLogAppController.java b/src/main/java/com/mgm/mcrm/logging/controllers/SysLogAppController.java
index 3b46aff..92f7b15 100644
--- a/src/main/java/com/mgm/mcrm/logging/controllers/SysLogAppController.java
+++ b/src/main/java/com/mgm/mcrm/logging/controllers/SysLogAppController.java
@@ -1,7 +1,10 @@
package com.mgm.mcrm.logging.controllers;
import java.util.Date;
-import java.util.Map;
+import com.mgm.mcrm.authorization.dtos.LoginUserInfo;
+import com.mgm.mcrm.authorization.utils.JwtTokenUtil;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.web.util.HtmlUtils;
import com.mgm.mcrm.common.dtos.BaseApiResponse;
import com.mgm.mcrm.logging.dtos.AppLogDto;
@@ -28,18 +31,35 @@ public class SysLogAppController {
@RequestMapping(value = "/system/write_log", method = RequestMethod.POST, produces = "application/json;charset=UTF-8")
public ResponseEntity> writeAppLog(HttpServletRequest request, @RequestBody(required=true) AppLogDto appLogDto) {
+ // The JWT filter authenticates this route before the controller runs.
+ LoginUserInfo user = JwtTokenUtil.parseUserFromRequest(request);
+ if (user == null || StringUtils.isBlank(user.getLoginId())) {
+ return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(BaseApiResponse.unauthorizedError("Invalid Token"));
+ }
+ return writeLog(request, appLogDto, limit(user.getLoginId(), 64));
+ }
+
+ @PostMapping(value = "/system/write_public_log", produces = "application/json;charset=UTF-8")
+ public ResponseEntity> writePublicAppLog(HttpServletRequest request,
+ @RequestBody(required=true) AppLogDto appLogDto) {
+ return writeLog(request, appLogDto, "ANONYMOUS");
+ }
+
+ private ResponseEntity> writeLog(HttpServletRequest request, AppLogDto appLogDto, String loginId) {
+ if (appLogDto == null) {
+ return ResponseEntity.badRequest().body(BaseApiResponse.badRequestError("Missing request data"));
+ }
String processMessage="";
String errorCode="";
BaseApiResponse result = new BaseApiResponse<>();
SysLogApp sysLogApp = new SysLogApp();
- String loginId = appLogDto.getLoginId();
- String eventCode = appLogDto.getEventCode();
- String transId = appLogDto.getTransId();
- String logDesc = appLogDto.getLogDesc();
- Integer tagSuccess = appLogDto.getTagSuccess();
- String requestBody = appLogDto.getRequestBody();
- String responseBody = appLogDto.getResponseBody();
+ String eventCode = limit(appLogDto.getEventCode(), 100);
+ String transId = limit(appLogDto.getTransId(), 100);
+ String logDesc = escape(appLogDto.getLogDesc(), 1000);
+ Integer tagSuccess = appLogDto.getTagSuccess() == null ? 0 : appLogDto.getTagSuccess();
+ String requestBody = escape(appLogDto.getRequestBody(), 4000);
+ String responseBody = escape(appLogDto.getResponseBody(), 4000);
Date now = DateTimeUtil.getCurrentDateTime("yyyy-MM-dd HH:mm:ss.SSSSS");
sysLogApp.setLoginId(loginId);
@@ -61,5 +81,13 @@ public class SysLogAppController {
result.setData(appLog);
return ResponseEntity.status(HttpStatus.OK).body(result);
}
+
+ private static String limit(String value, int maxLength) {
+ return value == null ? null : value.substring(0, Math.min(value.length(), maxLength));
+ }
+
+ private static String escape(String value, int maxLength) {
+ return value == null ? null : HtmlUtils.htmlEscape(limit(value, maxLength));
+ }
}
diff --git a/src/main/java/com/mgm/mcrm/player/services/TodoService.java b/src/main/java/com/mgm/mcrm/player/services/TodoService.java
index 3a01f6c..62d55a5 100644
--- a/src/main/java/com/mgm/mcrm/player/services/TodoService.java
+++ b/src/main/java/com/mgm/mcrm/player/services/TodoService.java
@@ -414,12 +414,14 @@ public class TodoService {
Date newDate = DateTimeUtil.getCurrentDateTime("yyyy-MM-dd HH:mm:ss");
Bookings booking = bookingDao.findById(bookId).orElse(null);
errors = cancelResponse;
+ String errorMessage = errors != null && StringUtils.isNotBlank(errors.getString("message"))
+ ? errors.getString("message") : "Cancel Booking Fail";
booking.setUpdatedAt(newDate);
booking.setCreatedAt(newDate);
booking.setApproveStatus("F");
- booking.setErrorMessage(errors.getString("message"));
+ booking.setErrorMessage(errorMessage);
processResult.setSuccess(false);
- processResult.setMessage(errors.getString("message")!=null?errors.getString("message"):"System Error");
+ processResult.setMessage(errorMessage);
processResult.setCode("SYSTEM_ERROR");
processResult.setData(bookingView);
}
diff --git a/src/main/java/com/mgm/mcrm/roomcalendar/controllers/RoomCalendarController.java b/src/main/java/com/mgm/mcrm/roomcalendar/controllers/RoomCalendarController.java
new file mode 100644
index 0000000..33df39c
--- /dev/null
+++ b/src/main/java/com/mgm/mcrm/roomcalendar/controllers/RoomCalendarController.java
@@ -0,0 +1,94 @@
+package com.mgm.mcrm.roomcalendar.controllers;
+
+import com.mgm.mcrm.authorization.dtos.LoginUserInfo;
+import com.mgm.mcrm.authorization.services.SysGroupPermissionService;
+import com.mgm.mcrm.authorization.utils.JwtTokenUtil;
+import com.mgm.mcrm.common.constants.SysConstants;
+import com.mgm.mcrm.roomcalendar.dtos.RoomCalendarQualification;
+import com.mgm.mcrm.roomcalendar.dtos.response.GetPropertyRoomTypeListResponse;
+import com.mgm.mcrm.roomcalendar.dtos.response.RoomCalendarApiResponse;
+import com.mgm.mcrm.roomcalendar.services.RoomCalendarService;
+import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletRequest;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+import java.util.Map;
+
+@RestController
+@RequestMapping("/api")
+public class RoomCalendarController {
+
+ // Same switch as the user-info flag; authentication is still required.
+ @org.springframework.beans.factory.annotation.Value("${calendar.permission-mock-enabled:false}")
+ private boolean calendarPermissionMockEnabled;
+
+ @Resource
+ private RoomCalendarService roomCalendarService;
+ @Resource
+ private SysGroupPermissionService sysGroupPermissionService;
+
+ @PostMapping(
+ value = "/app/room_calendar/request_room_calendar",
+ produces = MediaType.APPLICATION_JSON_VALUE,
+ consumes = MediaType.APPLICATION_JSON_VALUE)
+ public RoomCalendarApiResponse> requestRoomCalendar(
+ HttpServletRequest request,
+ @RequestBody(required = false) Map params) throws Exception {
+ return processCalendarRequest(request, params);
+ }
+
+ @PostMapping(
+ value = "/app/room_calendar/player_qualification",
+ produces = MediaType.APPLICATION_JSON_VALUE,
+ consumes = MediaType.APPLICATION_JSON_VALUE)
+ public RoomCalendarApiResponse> getPlayerRoomQualification(
+ HttpServletRequest request,
+ @RequestBody(required = false) Map params) throws Exception {
+ return processCalendarRequest(request, params);
+ }
+
+ @GetMapping(
+ value = "/app/room_calendar/get_properties_room_types",
+ produces = MediaType.APPLICATION_JSON_VALUE)
+ public GetPropertyRoomTypeListResponse requestPropertiesRoomTypes(HttpServletRequest request) {
+ if (!canViewCalendar(request)) {
+ GetPropertyRoomTypeListResponse result = new GetPropertyRoomTypeListResponse();
+ result.setSuccess(false);
+ result.setMessage("NO PERMISSION");
+ result.setCalendarErrorCode("NO_PERMISSION");
+ return result;
+ }
+ return roomCalendarService.getPropertyRoomType();
+ }
+
+ private RoomCalendarApiResponse> processCalendarRequest(
+ HttpServletRequest request,
+ Map params) throws Exception {
+ RoomCalendarApiResponse> result = new RoomCalendarApiResponse<>();
+ if (params == null) {
+ result.setSuccess(false);
+ result.setMessage("Missing request data");
+ return result;
+ }
+
+ if (!canViewCalendar(request)) {
+ result.setSuccess(false);
+ result.setMessage("NO PERMISSION");
+ result.setCalendarErrorCode("NO_PERMISSION");
+ return result;
+ }
+ return roomCalendarService.getRoomCalendar(params);
+ }
+
+ private boolean canViewCalendar(HttpServletRequest request) {
+ LoginUserInfo user = JwtTokenUtil.parseUserFromRequest(request);
+ return user != null && (calendarPermissionMockEnabled || sysGroupPermissionService.checkGroupPermission(
+ user.getGroupName(), SysConstants.PERMISSION_APP_CALENDAR));
+ }
+}
diff --git a/src/main/java/com/mgm/mcrm/roomcalendar/dtos/RoomCalendarQualification.java b/src/main/java/com/mgm/mcrm/roomcalendar/dtos/RoomCalendarQualification.java
new file mode 100644
index 0000000..e6d16cc
--- /dev/null
+++ b/src/main/java/com/mgm/mcrm/roomcalendar/dtos/RoomCalendarQualification.java
@@ -0,0 +1,14 @@
+package com.mgm.mcrm.roomcalendar.dtos;
+
+import lombok.Data;
+
+@Data
+public class RoomCalendarQualification {
+ private String property;
+ private String roomType;
+ private String date;
+ private String roomCriteria;
+ private Integer roomCount;
+ private String propertyLeader;
+ private Integer bookingDateQualificationFlag;
+}
diff --git a/src/main/java/com/mgm/mcrm/roomcalendar/dtos/response/GetPropertyRoomTypeListResponse.java b/src/main/java/com/mgm/mcrm/roomcalendar/dtos/response/GetPropertyRoomTypeListResponse.java
new file mode 100644
index 0000000..74d6e18
--- /dev/null
+++ b/src/main/java/com/mgm/mcrm/roomcalendar/dtos/response/GetPropertyRoomTypeListResponse.java
@@ -0,0 +1,12 @@
+package com.mgm.mcrm.roomcalendar.dtos.response;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.List;
+
+@Getter
+@Setter
+public class GetPropertyRoomTypeListResponse extends RoomCalendarApiResponse {
+ private List propertyList;
+}
diff --git a/src/main/java/com/mgm/mcrm/roomcalendar/dtos/response/GetPropertyRoomTypeResponse.java b/src/main/java/com/mgm/mcrm/roomcalendar/dtos/response/GetPropertyRoomTypeResponse.java
new file mode 100644
index 0000000..6e026f9
--- /dev/null
+++ b/src/main/java/com/mgm/mcrm/roomcalendar/dtos/response/GetPropertyRoomTypeResponse.java
@@ -0,0 +1,17 @@
+package com.mgm.mcrm.roomcalendar.dtos.response;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class GetPropertyRoomTypeResponse {
+ private String code;
+ private String name;
+ private String displayName;
+ private List defaultRoomTypes;
+ private List roomTypes;
+ private List otherProperties;
+}
diff --git a/src/main/java/com/mgm/mcrm/roomcalendar/dtos/response/RoomCalendarApiResponse.java b/src/main/java/com/mgm/mcrm/roomcalendar/dtos/response/RoomCalendarApiResponse.java
new file mode 100644
index 0000000..dee1ccd
--- /dev/null
+++ b/src/main/java/com/mgm/mcrm/roomcalendar/dtos/response/RoomCalendarApiResponse.java
@@ -0,0 +1,18 @@
+package com.mgm.mcrm.roomcalendar.dtos.response;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.mgm.mcrm.common.dtos.BaseApiResponse;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class RoomCalendarApiResponse extends BaseApiResponse {
+ @JsonInclude(JsonInclude.Include.NON_NULL)
+ private String errorCode;
+
+ public void setCalendarErrorCode(String errorCode) {
+ setCode(errorCode);
+ this.errorCode = errorCode;
+ }
+}
diff --git a/src/main/java/com/mgm/mcrm/roomcalendar/dtos/response/RoomType.java b/src/main/java/com/mgm/mcrm/roomcalendar/dtos/response/RoomType.java
new file mode 100644
index 0000000..e5159c3
--- /dev/null
+++ b/src/main/java/com/mgm/mcrm/roomcalendar/dtos/response/RoomType.java
@@ -0,0 +1,13 @@
+package com.mgm.mcrm.roomcalendar.dtos.response;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import lombok.Data;
+
+@Data
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class RoomType {
+ private String code;
+ private String label;
+ private String displayName;
+ private Integer defaultShowCalendar;
+}
diff --git a/src/main/java/com/mgm/mcrm/roomcalendar/repositories/RoomCalendarRoomProjection.java b/src/main/java/com/mgm/mcrm/roomcalendar/repositories/RoomCalendarRoomProjection.java
new file mode 100644
index 0000000..e439984
--- /dev/null
+++ b/src/main/java/com/mgm/mcrm/roomcalendar/repositories/RoomCalendarRoomProjection.java
@@ -0,0 +1,9 @@
+package com.mgm.mcrm.roomcalendar.repositories;
+
+public interface RoomCalendarRoomProjection {
+ String getPropertyId();
+ String getName();
+ String getCalendarName();
+ Integer getCalendarOrder();
+ Integer getDefaultShowCalendar();
+}
diff --git a/src/main/java/com/mgm/mcrm/roomcalendar/services/CalendarMockData.java b/src/main/java/com/mgm/mcrm/roomcalendar/services/CalendarMockData.java
new file mode 100644
index 0000000..df704e5
--- /dev/null
+++ b/src/main/java/com/mgm/mcrm/roomcalendar/services/CalendarMockData.java
@@ -0,0 +1,93 @@
+package com.mgm.mcrm.roomcalendar.services;
+
+import com.mgm.mcrm.appsetting.models.CfgSettings;
+import com.mgm.mcrm.roomcalendar.dtos.RoomCalendarQualification;
+import com.mgm.mcrm.roomcalendar.dtos.response.*;
+import java.time.LocalDate;
+import java.time.temporal.ChronoUnit;
+import java.util.*;
+
+/** Calendar display fixtures only. Never writes bookings or configuration. */
+public final class CalendarMockData {
+ private CalendarMockData() {}
+
+ private static GetPropertyRoomTypeResponse hotel(String code, String name, String... rooms) {
+ var hotel = new GetPropertyRoomTypeResponse();
+ hotel.setCode(code); hotel.setName(name + " [MOCK]"); hotel.setDisplayName(name + " [MOCK]");
+ var types = new ArrayList();
+ for (int i=0; i settings() {
+ var result = new ArrayList();
+ String[][] values = {{"CALENDAR_VIEW_PREVIOUS_DAYS","7"},{"CALENDAR_VIEW_VISIBLE_DAYS","60"},
+ {"CALENDAR_VIEW_YESTERDAY_START","00:00"},{"CALENDAR_VIEW_YESTERDAY_END","23:59"},
+ {"MAX_SELECTED_ROOM_TYPES","3"}};
+ for (var value:values) {
+ var setting = new CfgSettings(); setting.setKeys(value[0]); setting.setKeyValues(value[1]);
+ result.add(setting);
+ }
+ return result;
+ }
+
+ public static List records(String playerId, String properties,
+ String rooms, String startDate, String endDate) {
+ LocalDate start = LocalDate.parse(startDate), end = LocalDate.parse(endDate);
+ if (end.isBefore(start) || ChronoUnit.DAYS.between(start,end)>120)
+ throw new IllegalArgumentException("Mock date range must be 1 to 121 days");
+ var catalog = new LinkedHashMap();
+ for (var property:metadata().getPropertyList()) {
+ if (property.getOtherProperties()!=null)
+ property.getOtherProperties().forEach(child->catalog.put(child.getCode(),child));
+ else catalog.put(property.getCode(),property);
+ }
+ Set requestedRooms = new LinkedHashSet<>(Arrays.asList(rooms.split(",")));
+ var result = new ArrayList();
+ Set matchedRooms = new HashSet<>();
+ for (String code:new LinkedHashSet<>(Arrays.asList(properties.split(",")))) {
+ var property = catalog.get(code.trim());
+ if (property==null) throw new IllegalArgumentException("Unknown mock property: "+code);
+ for (var type:property.getRoomTypes()) {
+ if (!requestedRooms.contains(type.getCode())) continue;
+ matchedRooms.add(type.getCode());
+ for (var date=start; !date.isAfter(end); date=date.plusDays(1)) {
+ int seed = Math.floorMod(Objects.hash(code,type.getCode(),date.toString(),playerId),1000);
+ var record = new RoomCalendarQualification();
+ record.setProperty(code); record.setRoomType(type.getCode()); record.setDate(date.toString());
+ record.setRoomCount(seed%5==0 ? 0 : 1+seed%24);
+ record.setRoomCriteria(String.valueOf(1000+(seed%8)*500));
+ record.setPropertyLeader("Demo Host "+(1+seed%3));
+ record.setBookingDateQualificationFlag(seed%3==0 ? 0 : 1);
+ result.add(record);
+ }
+ }
+ }
+ if (!matchedRooms.containsAll(requestedRooms))
+ throw new IllegalArgumentException("Unknown mock room or room/property mismatch");
+ return result;
+ }
+}
diff --git a/src/main/java/com/mgm/mcrm/roomcalendar/services/RoomCalendarService.java b/src/main/java/com/mgm/mcrm/roomcalendar/services/RoomCalendarService.java
new file mode 100644
index 0000000..d8d9fbc
--- /dev/null
+++ b/src/main/java/com/mgm/mcrm/roomcalendar/services/RoomCalendarService.java
@@ -0,0 +1,465 @@
+package com.mgm.mcrm.roomcalendar.services;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.mgm.mcrm.basesetting.property.models.CfgProperties;
+import com.mgm.mcrm.basesetting.property.repositories.PropertyDao;
+import com.mgm.mcrm.basesetting.room.models.CfgRooms;
+import com.mgm.mcrm.basesetting.room.repositories.RoomDao;
+import com.mgm.mcrm.common.dtos.http_util.HttpResponseResult;
+import com.mgm.mcrm.common.services.MGMApiService;
+import com.mgm.mcrm.roomcalendar.dtos.RoomCalendarQualification;
+import com.mgm.mcrm.roomcalendar.dtos.response.GetPropertyRoomTypeListResponse;
+import com.mgm.mcrm.roomcalendar.dtos.response.GetPropertyRoomTypeResponse;
+import com.mgm.mcrm.roomcalendar.dtos.response.RoomCalendarApiResponse;
+import com.mgm.mcrm.roomcalendar.dtos.response.RoomType;
+import com.mgm.mcrm.roomcalendar.repositories.RoomCalendarRoomProjection;
+import jakarta.annotation.Resource;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+@Service
+public class RoomCalendarService {
+ @org.springframework.beans.factory.annotation.Value("${calendar.data-mock-enabled:false}")
+ private boolean calendarDataMockEnabled;
+
+ @Resource
+ private MGMApiService mgmApiService;
+ @Resource
+ private PropertyDao propertyDao;
+ @Resource
+ private RoomDao roomDao;
+
+ public RoomCalendarApiResponse> getRoomCalendar(Map params) throws Exception {
+ RoomCalendarApiResponse> result = new RoomCalendarApiResponse<>();
+ String playerId;
+ String property;
+ String roomType;
+ String startDate;
+ String endDate;
+
+ try {
+ playerId = getOptionalString(params, "playerId", "PlayerId");
+ property = getRequiredString(params, "property", "Property");
+ roomType = getRequiredString(params, "roomType", "RoomType");
+ startDate = getRequiredString(params, "startDate", "StartDate");
+ endDate = getRequiredString(params, "endDate", "EndDate");
+ } catch (IllegalArgumentException ex) {
+ result.setSuccess(false);
+ result.setMessage(ex.getMessage());
+ return result;
+ }
+
+ if (calendarDataMockEnabled) {
+ try {
+ result.setData(CalendarMockData.records(playerId, property, roomType, startDate, endDate));
+ result.setSuccess(true);
+ result.setCode("200");
+ result.setMessage("CALENDAR_MOCK_DATA");
+ } catch (java.time.DateTimeException | IllegalArgumentException ex) {
+ result.setSuccess(false);
+ result.setMessage(ex.getMessage());
+ }
+ return result;
+ }
+
+ CalendarVisibilityCheck visibility = validateCalendarVisibility(property, roomType);
+ if (visibility.errorCode != null) {
+ result.setSuccess(false);
+ result.setMessage(visibility.errorMessage);
+ result.setCalendarErrorCode(visibility.errorCode);
+ result.setData(new ArrayList<>());
+ return result;
+ }
+
+ List allRecords = new ArrayList<>();
+ boolean anySuccess = false;
+
+ if (StringUtils.isNotBlank(visibility.operaProperty) && StringUtils.isNotBlank(visibility.operaRoomType)) {
+ try {
+ HttpResponseResult response = mgmApiService.invokeRequestRoomCalendar(
+ playerId, visibility.operaProperty, visibility.operaRoomType, startDate, endDate, 0);
+ if (response.getStatusCode() == 200) {
+ allRecords.addAll(parseRoomCalendarResponse(response.getResponseContent()));
+ anySuccess = true;
+ }
+ } catch (Exception ignored) {
+ }
+ }
+
+ if (StringUtils.isNotBlank(visibility.daylightProperty) && StringUtils.isNotBlank(visibility.daylightRoomType)) {
+ try {
+ HttpResponseResult response = mgmApiService.invokeRequestRoomCalendar(
+ playerId, visibility.daylightProperty, visibility.daylightRoomType, startDate, endDate, 1);
+ if (response.getStatusCode() == 200) {
+ allRecords.addAll(parseRoomCalendarResponse(response.getResponseContent()));
+ anySuccess = true;
+ }
+ } catch (Exception ignored) {
+ }
+ }
+
+ if (!anySuccess) {
+ result.setSuccess(false);
+ result.setMessage("RequestRoomCalendar failed");
+ result.setData(new ArrayList<>());
+ return result;
+ }
+
+ result.setData(allRecords);
+ result.setSuccess(true);
+ result.setCode("200");
+ return result;
+ }
+
+ private CalendarVisibilityCheck validateCalendarVisibility(String property, String roomType) {
+ List propertyNames = parseDelimitedValues(property);
+ List roomTypeNames = parseDelimitedValues(roomType);
+ List properties = propertyDao.findByCalendarNameIn(propertyNames);
+ List visibleProperties = new ArrayList<>();
+
+ for (String propertyName : propertyNames) {
+ CfgProperties cfgProperty = findByCalendarName(properties, propertyName);
+ if (isShowCalendar(cfgProperty)) {
+ visibleProperties.add(cfgProperty);
+ }
+ }
+ if (visibleProperties.isEmpty()) {
+ return CalendarVisibilityCheck.failure("PROPERTYNOSHOW", "Property no show in calendar");
+ }
+
+ List propertyIds = new ArrayList<>();
+ for (CfgProperties cfgProperty : visibleProperties) {
+ propertyIds.add(String.valueOf(cfgProperty.getId()));
+ }
+
+ List rooms = roomDao.findByPropertyIdInAndCalendarNameIn(propertyIds, roomTypeNames);
+
+ String operaProperty = "";
+ String operaRoomType = "";
+ String daylightProperty = "";
+ String daylightRoomType = "";
+
+ List operaIds = new ArrayList<>();
+ List operaCodes = new ArrayList<>();
+ List daylightIds = new ArrayList<>();
+ List daylightCodes = new ArrayList<>();
+ for (CfgProperties cfgProperty : visibleProperties) {
+ String category = cfgProperty.getCategory();
+ if (StringUtils.equalsIgnoreCase(category, "OPERA")) {
+ operaIds.add(String.valueOf(cfgProperty.getId()));
+ operaCodes.add(cfgProperty.getCode());
+ } else if (StringUtils.equalsIgnoreCase(category, "DAYLIGHT")) {
+ daylightIds.add(String.valueOf(cfgProperty.getId()));
+ daylightCodes.add(cfgProperty.getCode());
+ }
+ }
+
+ if (!operaIds.isEmpty()) {
+ List visibleOperaRoomTypes = filterVisibleRoomTypes(roomTypeNames, rooms, operaIds);
+ if (!visibleOperaRoomTypes.isEmpty()) {
+ operaProperty = StringUtils.join(operaCodes, ",");
+ operaRoomType = StringUtils.join(visibleOperaRoomTypes, ",");
+ }
+ }
+
+ if (!daylightIds.isEmpty()) {
+ List visibleDaylightRoomTypes = filterVisibleRoomTypes(roomTypeNames, rooms, daylightIds);
+ if (!visibleDaylightRoomTypes.isEmpty()) {
+ daylightProperty = StringUtils.join(daylightCodes, ",");
+ daylightRoomType = StringUtils.join(visibleDaylightRoomTypes, ",");
+ }
+ }
+
+ if (StringUtils.isBlank(operaRoomType) && StringUtils.isBlank(daylightRoomType)) {
+ return CalendarVisibilityCheck.failure("ROOMTYPENOSHOW", "Room type no show in calendar");
+ }
+
+ return CalendarVisibilityCheck.success(operaProperty, operaRoomType, daylightProperty, daylightRoomType);
+ }
+
+ public GetPropertyRoomTypeListResponse getPropertyRoomType() {
+ if (calendarDataMockEnabled) return CalendarMockData.metadata();
+ GetPropertyRoomTypeListResponse response = new GetPropertyRoomTypeListResponse();
+ List properties = propertyDao.findAll();
+ Map> roomsByPropertyId = buildCalendarRoomsByPropertyId(properties);
+ List propertyList = new ArrayList<>();
+ List otherProperties = new ArrayList<>();
+
+ properties.sort(Comparator.comparing(RoomCalendarService::getPropertyCalendarOrder));
+ for (CfgProperties property : properties) {
+ if (!isShowCalendar(property)) {
+ continue;
+ }
+
+ List roomTypes = buildRoomTypes(property, roomsByPropertyId);
+ String calendarName = StringUtils.defaultIfBlank(property.getCalendarName(), property.getName());
+ if (StringUtils.equalsIgnoreCase(property.getCategory(), "OPERA")
+ || StringUtils.equalsIgnoreCase(property.getCategory(), "DAYLIGHT")) {
+ propertyList.add(buildPropertyRoomTypeResponse(calendarName, calendarName, null, roomTypes));
+ } else if (StringUtils.equalsIgnoreCase(property.getCategory(), "OTHER")) {
+ otherProperties.add(buildPropertyRoomTypeResponse(
+ calendarName,
+ calendarName,
+ StringUtils.defaultIfBlank(property.getName(), property.getCalendarName()),
+ roomTypes));
+ }
+ }
+
+ if (!otherProperties.isEmpty()) {
+ GetPropertyRoomTypeResponse otherGroup = buildPropertyRoomTypeResponse(
+ "OTHER", "Other Property", null, new ArrayList<>());
+ otherGroup.setDefaultRoomTypes(buildDefaultProperties(otherProperties));
+ otherGroup.setOtherProperties(otherProperties);
+ propertyList.add(otherGroup);
+ }
+
+ response.setPropertyList(propertyList);
+ response.setSuccess(true);
+ response.setCode("200");
+ return response;
+ }
+
+ private Map> buildCalendarRoomsByPropertyId(List properties) {
+ List propertyIds = new ArrayList<>();
+ for (CfgProperties property : properties) {
+ if (isShowCalendar(property) && property.getId() != null) {
+ propertyIds.add(String.valueOf(property.getId()));
+ }
+ }
+
+ Map> roomsByPropertyId = new HashMap<>();
+ if (propertyIds.isEmpty()) {
+ return roomsByPropertyId;
+ }
+ for (RoomCalendarRoomProjection room : roomDao.queryCalendarRoomsByPropertyIds(propertyIds)) {
+ if (room == null || StringUtils.isBlank(room.getPropertyId())) {
+ continue;
+ }
+ roomsByPropertyId.computeIfAbsent(room.getPropertyId(), key -> new ArrayList<>()).add(room);
+ }
+ return roomsByPropertyId;
+ }
+
+ private static List buildRoomTypes(
+ CfgProperties property,
+ Map> roomsByPropertyId) {
+ List rooms = new ArrayList<>(
+ roomsByPropertyId.getOrDefault(String.valueOf(property.getId()), new ArrayList<>()));
+ rooms.sort(Comparator.comparing(RoomCalendarService::getRoomCalendarOrder));
+
+ List roomTypes = new ArrayList<>();
+ for (RoomCalendarRoomProjection room : rooms) {
+ String calendarName = StringUtils.defaultIfBlank(room.getCalendarName(), room.getName());
+ if (StringUtils.isBlank(calendarName)) {
+ continue;
+ }
+ RoomType existing = findRoomType(roomTypes, calendarName);
+ if (existing != null) {
+ if (isDefaultShowCalendar(room.getDefaultShowCalendar())) {
+ existing.setDefaultShowCalendar(1);
+ }
+ continue;
+ }
+ RoomType item = new RoomType();
+ item.setCode(calendarName);
+ item.setLabel(calendarName);
+ item.setDefaultShowCalendar(isDefaultShowCalendar(room.getDefaultShowCalendar()) ? 1 : 0);
+ roomTypes.add(item);
+ }
+ return roomTypes;
+ }
+
+ private static GetPropertyRoomTypeResponse buildPropertyRoomTypeResponse(
+ String code, String name, String displayName, List roomTypes) {
+ GetPropertyRoomTypeResponse item = new GetPropertyRoomTypeResponse();
+ item.setCode(StringUtils.defaultString(code));
+ item.setName(StringUtils.defaultString(name));
+ item.setDisplayName(displayName);
+ item.setRoomTypes(roomTypes);
+ item.setDefaultRoomTypes(buildDefaultRoomTypes(roomTypes));
+ return item;
+ }
+
+ private static List buildDefaultRoomTypes(List roomTypes) {
+ List defaults = new ArrayList<>();
+ for (RoomType roomType : roomTypes) {
+ if (defaults.size() >= 3) {
+ break;
+ }
+ if (isDefaultShowCalendar(roomType.getDefaultShowCalendar()) && StringUtils.isNotBlank(roomType.getCode())) {
+ defaults.add(roomType.getCode());
+ }
+ }
+ return defaults;
+ }
+
+ private static List buildDefaultProperties(List properties) {
+ List defaults = new ArrayList<>();
+ for (GetPropertyRoomTypeResponse property : properties) {
+ if (defaults.size() >= 3) {
+ break;
+ }
+ if (property.getDefaultRoomTypes() != null
+ && !property.getDefaultRoomTypes().isEmpty()
+ && StringUtils.isNotBlank(property.getCode())) {
+ defaults.add(property.getCode());
+ }
+ }
+ return defaults;
+ }
+
+ static List parseRoomCalendarResponse(String responseContent) {
+ JSONObject resultObj = JSONObject.parseObject(responseContent);
+ if (!StringUtils.equals("OK", resultObj.getString("StatusCode"))) {
+ throw new IllegalArgumentException(StringUtils.defaultIfBlank(
+ resultObj.getString("StatusDescription"), "RequestRoomCalendar failed"));
+ }
+
+ JSONArray records = resultObj.getJSONArray("Records");
+ List result = new ArrayList<>();
+ if (records == null) {
+ return result;
+ }
+ for (int i = 0; i < records.size(); i++) {
+ JSONObject item = records.getJSONObject(i);
+ RoomCalendarQualification record = new RoomCalendarQualification();
+ record.setProperty(item.getString("Property"));
+ record.setRoomType(item.getString("RoomType"));
+ record.setDate(normalizeDate(item.getString("Date")));
+ record.setRoomCriteria(item.getString("RoomCriteria"));
+ record.setRoomCount(item.getInteger("RoomCount"));
+ record.setPropertyLeader(item.getString("PropertyLeader"));
+ record.setBookingDateQualificationFlag(item.getInteger("BookingDateQualificationFlag"));
+ result.add(record);
+ }
+ return result;
+ }
+
+ private static List filterVisibleRoomTypes(List requestedNames, List rooms, List propertyIds) {
+ Set propertyIdSet = new LinkedHashSet<>(propertyIds);
+ Set visible = new LinkedHashSet<>();
+ for (String requestedName : requestedNames) {
+ for (CfgRooms room : rooms) {
+ if (StringUtils.equals(requestedName, room.getCalendarName())
+ && isShowCalendar(room)
+ && propertyIdSet.contains(room.getPropertyId())) {
+ visible.add(requestedName);
+ break;
+ }
+ }
+ }
+ return new ArrayList<>(visible);
+ }
+
+ private static CfgProperties findByCalendarName(List properties, String name) {
+ for (CfgProperties property : properties) {
+ if (StringUtils.equals(property.getCalendarName(), name)) {
+ return property;
+ }
+ }
+ return null;
+ }
+
+ private static RoomType findRoomType(List roomTypes, String code) {
+ for (RoomType roomType : roomTypes) {
+ if (StringUtils.equals(roomType.getCode(), code)) {
+ return roomType;
+ }
+ }
+ return null;
+ }
+
+ private static List parseDelimitedValues(String value) {
+ List values = new ArrayList<>();
+ for (String item : StringUtils.split(StringUtils.defaultString(value), ",")) {
+ if (StringUtils.isNotBlank(item)) {
+ values.add(StringUtils.trim(item));
+ }
+ }
+ return values;
+ }
+
+ private static String getRequiredString(Map params, String lowerKey, String upperKey) {
+ Object value = params.get(lowerKey);
+ if (value == null) {
+ value = params.get(upperKey);
+ }
+ if (value == null || StringUtils.isBlank(String.valueOf(value))) {
+ throw new IllegalArgumentException("Missing parameter: " + lowerKey);
+ }
+ return String.valueOf(value);
+ }
+
+ private static String getOptionalString(Map params, String lowerKey, String upperKey) {
+ Object value = params.get(lowerKey);
+ if (value == null) {
+ value = params.get(upperKey);
+ }
+ return value == null || StringUtils.isBlank(String.valueOf(value)) ? "" : String.valueOf(value);
+ }
+
+ private static String normalizeDate(String value) {
+ if (StringUtils.isBlank(value)) {
+ return "";
+ }
+ return value.length() >= 10 ? value.substring(0, 10) : value;
+ }
+
+ private static boolean isShowCalendar(CfgProperties property) {
+ return property != null && Integer.valueOf(1).equals(property.getShowCalendar());
+ }
+
+ private static boolean isShowCalendar(CfgRooms room) {
+ return room != null && Integer.valueOf(1).equals(room.getShowCalendar());
+ }
+
+ private static boolean isDefaultShowCalendar(Integer value) {
+ return Integer.valueOf(1).equals(value);
+ }
+
+ private static Integer getPropertyCalendarOrder(CfgProperties property) {
+ return property == null || property.getCalendarOrder() == null ? Integer.MAX_VALUE : property.getCalendarOrder();
+ }
+
+ private static Integer getRoomCalendarOrder(RoomCalendarRoomProjection room) {
+ return room == null || room.getCalendarOrder() == null ? Integer.MAX_VALUE : room.getCalendarOrder();
+ }
+
+ private static final class CalendarVisibilityCheck {
+ private final String errorCode;
+ private final String errorMessage;
+ private final String operaProperty;
+ private final String operaRoomType;
+ private final String daylightProperty;
+ private final String daylightRoomType;
+
+ private CalendarVisibilityCheck(String errorCode, String errorMessage,
+ String operaProperty, String operaRoomType,
+ String daylightProperty, String daylightRoomType) {
+ this.errorCode = errorCode;
+ this.errorMessage = errorMessage;
+ this.operaProperty = operaProperty;
+ this.operaRoomType = operaRoomType;
+ this.daylightProperty = daylightProperty;
+ this.daylightRoomType = daylightRoomType;
+ }
+
+ private static CalendarVisibilityCheck success(String operaProperty, String operaRoomType,
+ String daylightProperty, String daylightRoomType) {
+ return new CalendarVisibilityCheck(null, null, operaProperty, operaRoomType, daylightProperty, daylightRoomType);
+ }
+
+ private static CalendarVisibilityCheck failure(String errorCode, String errorMessage) {
+ return new CalendarVisibilityCheck(errorCode, errorMessage, null, null, null, null);
+ }
+ }
+}
diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml
index 05f7130..d33ae34 100644
--- a/src/main/resources/application-dev.yml
+++ b/src/main/resources/application-dev.yml
@@ -160,3 +160,8 @@ apns:
host: api.sandbox.push.apple.com
# Production Host
# host: api.push.apple.com
+
+calendar:
+ # Temporary team testing defaults. Disable both before formal delivery.
+ permission-mock-enabled: true
+ data-mock-enabled: true
diff --git a/src/test/java/com/mgm/mcrm/PublicEndpointsMigrationTest.java b/src/test/java/com/mgm/mcrm/PublicEndpointsMigrationTest.java
new file mode 100644
index 0000000..b658217
--- /dev/null
+++ b/src/test/java/com/mgm/mcrm/PublicEndpointsMigrationTest.java
@@ -0,0 +1,127 @@
+package com.mgm.mcrm;
+
+import com.auth0.jwt.JWT;
+import com.auth0.jwt.algorithms.Algorithm;
+import com.mgm.mcrm.appsetting.controllers.CfgSettingController;
+import com.mgm.mcrm.appsetting.dtos.CfgSettingQueryRequest;
+import com.mgm.mcrm.appsetting.models.CfgSettings;
+import com.mgm.mcrm.appsetting.models.SysSetting;
+import com.mgm.mcrm.appsetting.services.SettingService;
+import com.mgm.mcrm.appsetting.services.SysSettingService;
+import com.mgm.mcrm.authorization.services.UserTokenService;
+import com.mgm.mcrm.common.config.WebFilterConfig;
+import com.mgm.mcrm.logging.controllers.SysLogAppController;
+import com.mgm.mcrm.logging.dtos.AppLogDto;
+import com.mgm.mcrm.logging.models.SysLogApp;
+import com.mgm.mcrm.logging.services.SysLogAppService;
+import org.junit.jupiter.api.Test;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.test.util.ReflectionTestUtils;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import java.util.concurrent.atomic.AtomicBoolean;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+class PublicEndpointsMigrationTest {
+ @Test void publicConfigurationRejectsOtherKeysBeforeQuery() throws Exception {
+ var service = mock(SettingService.class);
+ var controller = new CfgSettingController();
+ ReflectionTestUtils.setField(controller, "settingService", service);
+ for (String key : new String[]{null, "", "MACAU_IP", "macau_string", "MACAU_STRING "}) {
+ var query = new CfgSettingQueryRequest(); query.setKeyName(key);
+ var result = controller.getPublicSettingByKey(new MockHttpServletRequest(), query).getBody();
+ assertFalse(result.isSuccess()); assertEquals("SETTING_KEY_NOT_ALLOWED", result.getCode());
+ }
+ assertFalse(controller.getPublicSettingByKey(new MockHttpServletRequest(), null).getBody().isSuccess());
+ verifyNoInteractions(service);
+ var setting = new CfgSettings();
+ when(service.getSettingByKey("MACAU_STRING")).thenReturn(setting);
+ var query = new CfgSettingQueryRequest(); query.setKeyName("MACAU_STRING");
+ assertSame(setting, controller.getPublicSettingByKey(new MockHttpServletRequest(), query).getBody().getData());
+ verify(service).getSettingByKey("MACAU_STRING");
+ }
+
+ @Test void publicLoginSettingsOnlyFetchTwoAllowedKeysAndAllowMissingSettings() {
+ var service = mock(SysSettingService.class);
+ var controller = new CfgSettingController();
+ ReflectionTestUtils.setField(controller, "sysSettingService", service);
+ var sso = new SysSetting(); var tpd = new SysSetting();
+ when(service.getSysSettingByKey("APP_MOCK_SSO")).thenReturn(sso);
+ when(service.getSysSettingByKey("APP_MOCK_TPD")).thenReturn(tpd);
+ assertEquals(java.util.List.of(sso, tpd), controller.getPublicLoginSettings(new MockHttpServletRequest()).getBody().getData());
+ verify(service).getSysSettingByKey("APP_MOCK_SSO");
+ verify(service).getSysSettingByKey("APP_MOCK_TPD");
+ verifyNoMoreInteractions(service);
+ when(service.getSysSettingByKey("APP_MOCK_TPD")).thenReturn(null);
+ assertEquals(1, controller.getPublicLoginSettings(new MockHttpServletRequest()).getBody().getData().size());
+ }
+
+ @Test void filterAllowsOnlyNewPublicRoutesAndKeepsRefreshProtected() throws Exception {
+ var config = new WebFilterConfig();
+ var tokens = mock(UserTokenService.class);
+ ReflectionTestUtils.setField(config, "secretKey", "test-only-key");
+ ReflectionTestUtils.setField(config, "userTokenService", tokens);
+ var filter = config.jwtAuthFilter().getFilter();
+ for (String route : new String[]{"/system/write_public_log", "/system/sys_setting/get_public_login_settings", "/web/setting/get_public_setting_by_key"}) {
+ var reached = new AtomicBoolean();
+ filter.doFilter(new MockHttpServletRequest("POST", "/api" + route), new MockHttpServletResponse(),
+ (req, res) -> reached.set(true));
+ assertTrue(reached.get(), route);
+ }
+ for (String route : new String[]{"/system/write_log", "/system/sys_setting/get_list", "/web/setting/get_setting_by_key", "/app/booking/mock_cancel", "/app/refresh_token"}) {
+ var reached = new AtomicBoolean(); var response = new MockHttpServletResponse();
+ filter.doFilter(new MockHttpServletRequest("POST", "/api" + route), response, (req, res) -> reached.set(true));
+ assertFalse(reached.get(), route); assertEquals(401, response.getStatus(), route);
+ }
+ }
+
+ private SysLogAppController logs(SysLogAppService service) {
+ var controller = new SysLogAppController();
+ ReflectionTestUtils.setField(controller, "logService", service);
+ when(service.save(any(SysLogApp.class))).thenAnswer(call -> call.getArgument(0));
+ return controller;
+ }
+
+ @Test void anonymousIdentityCannotBeSpoofedAndNullBodiesAreSafe() {
+ var service = mock(SysLogAppService.class); var controller = logs(service);
+ var dto = new AppLogDto(); dto.setLoginId("admin");
+ var result = controller.writePublicAppLog(new MockHttpServletRequest(), dto).getBody().getData();
+ assertEquals("ANONYMOUS", result.getLoginId()); assertNull(result.getLogDesc());
+ assertNull(result.getRequestBody()); assertNull(result.getResponseBody()); assertEquals(0, result.getTagSuccess());
+ assertEquals(400, controller.writePublicAppLog(new MockHttpServletRequest(), null).getStatusCode().value());
+ verify(service, times(1)).save(any(SysLogApp.class));
+ }
+
+ @Test void authenticatedLogUsesTokenIdentityAndRequiresIdentity() {
+ var service = mock(SysLogAppService.class); var controller = logs(service);
+ var dto = new AppLogDto(); dto.setLoginId("forged-user");
+ var request = new MockHttpServletRequest();
+ assertEquals(401, controller.writeAppLog(request, dto).getStatusCode().value());
+ verify(service, never()).save(any());
+ // Controller identity test; JWT admission is covered separately by the filter test.
+ request.addHeader("Authorization", "Bearer " + JWT.create().withSubject("80001")
+ .withClaim("login_id", "actual-user").sign(Algorithm.HMAC256("test-only-key")));
+ assertEquals("actual-user", controller.writeAppLog(request, dto).getBody().getData().getLoginId());
+ }
+
+ @Test void logsLimitInputAndEscapeText() {
+ var controller = logs(mock(SysLogAppService.class));
+ var dto = new AppLogDto(); dto.setEventCode("e".repeat(101)); dto.setTransId("t".repeat(101));
+ dto.setLogDesc("x".repeat(999) + "&");
+ var log = controller.writePublicAppLog(new MockHttpServletRequest(), dto).getBody().getData();
+ assertEquals(100, log.getEventCode().length()); assertEquals(100, log.getTransId().length());
+ assertEquals("x".repeat(999) + "<", log.getLogDesc());
+ assertEquals(4000, log.getRequestBody().length()); assertEquals("<script>&", log.getResponseBody());
+ }
+
+ @Test void httpRejectsMissingAndMalformedLogBodiesWithoutSaving() throws Exception {
+ var service = mock(SysLogAppService.class); var mvc = MockMvcBuilders.standaloneSetup(logs(service)).build();
+ mvc.perform(post("/api/system/write_public_log")).andExpect(status().isBadRequest());
+ mvc.perform(post("/api/system/write_public_log").contentType("application/json")
+ .content("{\"tagSuccess\":\"not-a-number\"}")).andExpect(status().isBadRequest());
+ verify(service, never()).save(any());
+ }
+}
diff --git a/src/test/java/com/mgm/mcrm/RefreshTokenMigrationTest.java b/src/test/java/com/mgm/mcrm/RefreshTokenMigrationTest.java
new file mode 100644
index 0000000..96a8e24
--- /dev/null
+++ b/src/test/java/com/mgm/mcrm/RefreshTokenMigrationTest.java
@@ -0,0 +1,103 @@
+package com.mgm.mcrm;
+
+import com.auth0.jwt.JWT;
+import com.auth0.jwt.algorithms.Algorithm;
+import com.mgm.mcrm.authorization.dtos.LoginUserInfo;
+import com.mgm.mcrm.authorization.models.Token;
+import com.mgm.mcrm.authorization.repositories.TokenDao;
+import com.mgm.mcrm.authorization.services.UserTokenService;
+import com.mgm.mcrm.authorization.utils.JwtTokenUtil;
+import com.mgm.mcrm.authorization.filters.JwtAuthFilter;
+import com.mgm.mcrm.common.utils.EncryptUtil;
+import org.apache.logging.log4j.util.Base64Util;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.mock.web.*;
+import org.springframework.test.util.ReflectionTestUtils;
+import java.time.*;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicBoolean;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+class RefreshTokenMigrationTest {
+ final Map records = new HashMap<>();
+ TokenDao dao; UserTokenService service; LoginUserInfo user;
+ @BeforeEach void setup() {
+ JwtTokenUtil.setSecretKey("refresh-test-key");
+ ReflectionTestUtils.setField(JwtTokenUtil.class, "verifier", null);
+ JwtTokenUtil.setAccessTokenExpiration(5); JwtTokenUtil.setAppSessionExpiration(30);
+ dao = mock(TokenDao.class); service = new UserTokenService(dao);
+ when(dao.save(any(Token.class))).thenAnswer(c -> {Token t=c.getArgument(0); records.put(t.getUuid(),t); return t;});
+ when(dao.findByUuid(anyString())).thenAnswer(c -> records.get(c.getArgument(0)));
+ when(dao.findByUuidAndTokenType(anyString(),eq(1))).thenAnswer(c -> records.get(c.getArgument(0)));
+ when(dao.deleteByUuidAndTokenType(anyString(),eq(1))).thenAnswer(c -> records.remove(c.getArgument(0)) == null ? 0 : 1);
+ when(dao.deleteByUserIdAndTokenType(anyString(),eq(1))).thenAnswer(c -> {
+ int size=records.size(); records.values().removeIf(t -> c.getArgument(0).equals(t.getCreatedBy())); return size-records.size();
+ });
+ user = new LoginUserInfo(); user.setLoginId("80001"); user.setGroupName("IM");
+ }
+ Token issue(boolean refresh) {return service.saveToken(JwtTokenUtil.generateTokenByLoginUser("/api/app/sso/login",user,1,refresh));}
+
+ @Test void validRefreshPreservesProtocolOwnerGroupAndOriginalRefreshWhenNotRenewing() {
+ Token old=issue(true);
+ var result=service.refreshMobileToken(old.getToken(),"/api/app/refresh_token",false);
+ assertTrue(result.isSuccess()); assertNotNull(result.getAccessToken()); assertNull(result.getRefreshToken());
+ assertNotNull(service.validateMobileRefreshToken(old.getToken()));
+ Token access=service.getToken(result.getAccessToken()); assertEquals("80001",access.getCreatedBy());
+ assertEquals("access",JWT.decode(access.getToken()).getClaim("token_use").asString());
+ assertEquals("IM",JwtTokenUtil.parseUserFromToken(access.getToken()).getGroupName());
+ }
+ @Test void rotationRevokesOldRefreshAndLogoutRevokesAllOwnedCredentials() {
+ Token old=issue(true);
+ var result=service.refreshMobileToken(old.getToken(),"/api/app/refresh_token",true);
+ assertTrue(result.isSuccess()); assertNull(service.validateMobileRefreshToken(old.getToken()));
+ assertNotNull(service.validateMobileRefreshToken(result.getRefreshToken()));
+ assertEquals(2,service.clearTokenByUser("80001",1));
+ assertFalse(service.refreshMobileToken(result.getRefreshToken(),"/api/app/refresh_token",false).isSuccess());
+ }
+ @Test void invalidRevokedExpiredAndAccessCredentialsNeverIssueTokens() {
+ Token refresh=issue(true); Token access=issue(false); int count=records.size();
+ for(String value:new String[]{"", "bad", access.getToken(), refresh.getToken()+"x"})
+ assertFalse(service.refreshMobileToken(value,"/api/app/refresh_token",false).isSuccess());
+ assertEquals(count,records.size());
+ refresh.setExpireTime(LocalDateTime.now().minusSeconds(1));
+ assertNull(service.validateMobileRefreshToken(refresh.getToken()));
+ records.clear(); assertNull(service.validateMobileRefreshToken(refresh.getToken()));
+ }
+ @Test void legacyWrongPurposeWrongTypeAndExpiredSignedTokensRejected() {
+ for(String purpose:new String[]{null,"access","refresh"}) {
+ for(int type:new int[]{0,1}) {
+ String id=UUID.randomUUID().toString();
+ String value=JWT.create().withSubject("80001").withJWTId(id).withClaim("login_id","80001")
+ .withClaim("token_use",purpose).withClaim("token_type",type)
+ .withExpiresAt("refresh".equals(purpose) && type == 1 ? Instant.now().minusSeconds(10) : Instant.now().plusSeconds(300))
+ .sign(Algorithm.HMAC256("refresh-test-key"));
+ Token row=new Token(); row.setUuid(id); row.setToken(value); row.setCreatedBy("80001");
+ row.setExpireTime(LocalDateTime.now().plusMinutes(30)); records.put(id,row);
+ assertNull(service.validateMobileRefreshToken(value));
+ }
+ }
+ Token refresh=issue(true); refresh.setCreatedBy("other-user");
+ assertNull(service.validateMobileRefreshToken(refresh.getToken()));
+ }
+ @Test void filterUsesRefreshHeaderEvenWithExpiredAccessAndRetainsClientCheck() throws Exception {
+ Token refresh=issue(true); var filter=new JwtAuthFilter(); filter.setUserTokenService(service);
+ var request=new MockHttpServletRequest("POST","/api/app/refresh_token");
+ request.addHeader("Authorization",JWT.create().withSubject("80001").withExpiresAt(Instant.now().minusSeconds(60))
+ .sign(Algorithm.HMAC256("refresh-test-key"))); request.addHeader("RefreshToken",refresh.getToken());
+ request.addHeader("clientId",EncryptUtil.sha256(Base64Util.encode("mcrm_80001")));
+ var reached=new AtomicBoolean(); var response=new MockHttpServletResponse();
+ filter.doFilter(request,response,(req,res)->reached.set(true)); assertTrue(reached.get());
+ request.removeHeader("clientId"); reached.set(false); response=new MockHttpServletResponse();
+ filter.doFilter(request,response,(req,res)->reached.set(true)); assertFalse(reached.get());
+ }
+ @Test void refreshCannotAuthorizeBusinessRequests() throws Exception {
+ var filter=new JwtAuthFilter(); filter.setUserTokenService(service);
+ var request=new MockHttpServletRequest("POST","/api/app/me");
+ request.addHeader("Authorization",issue(true).getToken());
+ var response=new MockHttpServletResponse(); var reached=new AtomicBoolean();
+ filter.doFilter(request,response,(req,res)->reached.set(true));
+ assertFalse(reached.get()); assertEquals(401,response.getStatus());
+ }
+}
diff --git a/src/test/java/com/mgm/mcrm/TodoIrcMigrationTest.java b/src/test/java/com/mgm/mcrm/TodoIrcMigrationTest.java
new file mode 100644
index 0000000..59c53b5
--- /dev/null
+++ b/src/test/java/com/mgm/mcrm/TodoIrcMigrationTest.java
@@ -0,0 +1,21 @@
+package com.mgm.mcrm;
+
+import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson.parser.Feature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.mgm.mcrm.common.dtos.mgm_api.TodoDetailResult;
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.*;
+
+class TodoIrcMigrationTest {
+ @Test
+ void upstreamBalanceSurvivesParsingAndAppSerialization() throws Exception {
+ for (String amount : new String[]{"0", "1234.50"}) {
+ TodoDetailResult detail = JSONObject.parseObject(
+ "{\"IRCProgramFundBalance\":\"" + amount + "\"}",
+ TodoDetailResult.class, Feature.SupportNonPublicField, Feature.IgnoreNotMatch);
+ assertEquals(amount, detail.getIrcProgramFundBalance());
+ assertEquals(amount, new ObjectMapper().valueToTree(detail).get("iRCProgramFundBalance").asText());
+ }
+ }
+}
diff --git a/src/test/java/com/mgm/mcrm/TodoNumericMigrationTest.java b/src/test/java/com/mgm/mcrm/TodoNumericMigrationTest.java
new file mode 100644
index 0000000..5a8f9aa
--- /dev/null
+++ b/src/test/java/com/mgm/mcrm/TodoNumericMigrationTest.java
@@ -0,0 +1,55 @@
+package com.mgm.mcrm;
+
+import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson.parser.Feature;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.mgm.mcrm.common.dtos.mgm_api.TodoDetailResult;
+import com.mgm.mcrm.common.dtos.mgm_api.TodoListRecord;
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.*;
+
+class TodoNumericMigrationTest {
+ private final ObjectMapper mapper = new ObjectMapper();
+ private JsonNode detail(String json) {
+ return mapper.valueToTree(JSONObject.parseObject(json, TodoDetailResult.class,
+ Feature.SupportNonPublicField, Feature.IgnoreNotMatch));
+ }
+ @Test void numericFieldsPreserveDecimalsLargeNumbersZeroAndNull() {
+ String[][] fields = {{"PrgTO","prgTO"},{"PrgWin","prgWin"},{"FrontMoney","frontMoney"},
+ {"TTO","tto"},{"01CompBalance","comBalance"},{"ExpComm","expComm"},
+ {"NetCreditOutstanding","netCreditOutstanding"},{"UnsettledExpenses","unsettledExpenses"},
+ {"TodayTo","todayTo"},{"TodayTPD","todayTPD"},{"TodayWin","todayWin"},{"TodayTheo","todayTheo"},
+ {"TripWin","tripWin"},{"TripTPD","tripTPD"},{"TripTheo","tripTheo"},
+ {"TripRIPercentage","tripRIPercentage"},{"YesterdayTO","yesterdayTO"},{"YesterdayWin","yesterdayWin"},
+ {"YesterdayPlayHour","yesterdayPlayHour"},{"AveragePlayHour","averagePlayHour"}};
+ for (String[] field : fields) {
+ for (String value : new String[]{"12.75","3000000000","0","-12.75"}) {
+ for (String input : new String[]{value, "\"" + value + "\""}) {
+ JsonNode result = detail("{\"" + field[0] + "\":" + input + "}").get(field[1]);
+ assertNotNull(result, field[1]); assertTrue(result.isTextual(), field[1]);
+ assertEquals(value, result.asText(), field[0]);
+ }
+ }
+ assertTrue(detail("{\"" + field[0] + "\":null}").get(field[1]).isNull());
+ }
+ }
+ @Test void aliasesAndDaylightOnlyDetailFieldsRemain() {
+ var result = detail("{\"01CompBalance\":\"37.50\",\"TORIPercentage\":0.25,\"HouseAdvantagePercentage\":\"0.1\",\"IRCProgramFundBalance\":\"99\"}");
+ assertEquals("37.50", result.get("comBalance").asText());
+ assertEquals("37.50", result.get("01CompBalance").asText());
+ assertEquals(0.25, result.get("toripercentage").asDouble());
+ assertEquals("0.1", result.get("HouseAdvantagePercentage").asText());
+ assertEquals("99", result.get("iRCProgramFundBalance").asText());
+ }
+ @Test void listPreservesMaidEtaAndRoomFlagsAlongsideMaId() {
+ var record = JSONObject.parseObject("{\"MAId\":123,\"ETA\":\"13:45\"}", TodoListRecord.class,
+ Feature.SupportNonPublicField, Feature.IgnoreNotMatch);
+ record.setRoomReady(true); record.setIsQueue(true);
+ JsonNode result = mapper.valueToTree(record);
+ assertEquals(123,result.get("maid").asInt());assertEquals(123,result.get("maId").asInt());
+ assertEquals("13:45",result.get("eta").asText());
+ assertTrue(result.get("roomReady").asBoolean());assertTrue(result.get("isQueue").asBoolean());
+ assertFalse(result.has("calendarMaId"));
+ }
+}
diff --git a/src/test/java/com/mgm/mcrm/TodoRemainingContractTest.java b/src/test/java/com/mgm/mcrm/TodoRemainingContractTest.java
new file mode 100644
index 0000000..1d07938
--- /dev/null
+++ b/src/test/java/com/mgm/mcrm/TodoRemainingContractTest.java
@@ -0,0 +1,61 @@
+package com.mgm.mcrm;
+
+import com.alibaba.fastjson.JSONObject;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.mgm.mcrm.common.dtos.mgm_api.*;
+import com.mgm.mcrm.player.services.TodoService;
+import com.mgm.mcrm.player.dtos.request.TodoCancelBookingRequest;
+import com.mgm.mcrm.appsetting.services.SysSettingService;
+import com.mgm.mcrm.booking.models.*;
+import com.mgm.mcrm.booking.repositories.*;
+import com.mgm.mcrm.booking.services.OperaService;
+import com.mgm.mcrm.basesetting.property.repositories.PropertyDao;
+import org.junit.jupiter.api.Test;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.test.util.ReflectionTestUtils;
+import java.util.Optional;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+class TodoRemainingContractTest {
+ @Test void remainingDtosPreservePascalInputAndCamelOutput() {
+ var mapper = new ObjectMapper();
+ var list=JSONObject.parseObject("{\"StatusCode\":\"0\",\"StatusDescription\":\"OK\",\"Records\":[{\"MAId\":80001}]}",TodoListResult.class);
+ var json=mapper.valueToTree(list);
+ assertEquals("0",json.get("statusCode").asText());
+ assertEquals("OK",json.get("statusDescription").asText());
+ assertEquals(80001,json.get("records").get(0).get("maId").asInt());
+ var dates=JSONObject.parseObject("{\"StatusCode\":\"0\",\"Records\":[{\"Date\":\"2026-09-10\",\"DateRecords\":[{\"MAId\":80001}]}]}",TodoDateListResult.class);
+ var date=mapper.valueToTree(dates).get("records").get(0);
+ assertEquals("2026-09-10",date.get("date").asText());
+ assertEquals(80001,date.get("dateRecords").get(0).get("maId").asInt());
+ var description=JSONObject.parseObject("{\"ItemRecordType\":\"Offer\",\"ItemRecordId\":42,\"ItemRecordDescription\":\"Room\"}",TodoDetailDescription.class);
+ var detail=mapper.valueToTree(description);
+ assertEquals("Offer",detail.get("itemRecordType").asText());
+ assertEquals(42,detail.get("itemRecordId").asInt());
+ assertEquals("Room",detail.get("itemRecordDescription").asText());
+ }
+
+ @Test void nullOperaCancellationReturnsFailureInsteadOfThrowing() throws Exception {
+ var service=new TodoService(); var views=mock(BookingViewDao.class); var bookings=mock(BookingDao.class);
+ var opera=mock(OperaService.class); var settings=mock(SysSettingService.class);
+ ReflectionTestUtils.setField(service,"bookingViewDao",views);
+ ReflectionTestUtils.setField(service,"bookingDao",bookings);
+ ReflectionTestUtils.setField(service,"operaService",opera);
+ ReflectionTestUtils.setField(service,"sysSettingService",settings);
+ ReflectionTestUtils.setField(service,"propertyDao",mock(PropertyDao.class));
+ var view=new BookingsView(); view.setPropertyCategory("OPERA"); view.setConfirmationNo("test-confirmation");
+ var booking=new Bookings();
+ when(views.findById(Long.valueOf(42))).thenReturn(Optional.of(view));
+ when(bookings.findById(42L)).thenReturn(Optional.of(booking));
+ when(opera.fetchBooking(any())).thenReturn(new JSONObject());
+ when(opera.cancelBooking(any())).thenReturn(null);
+ var request=new TodoCancelBookingRequest(); request.setId(42L);
+ var result=service.cancelBooking(request,new MockHttpServletRequest());
+ assertFalse(result.isSuccess()); assertEquals("SYSTEM_ERROR",result.getCode());
+ assertEquals("Cancel Booking Fail",result.getMessage());
+ assertEquals("Cancel Booking Fail",booking.getErrorMessage());
+ assertEquals("F",booking.getApproveStatus());
+ verify(opera,times(1)).cancelBooking(any());
+ }
+}
diff --git a/src/test/java/com/mgm/mcrm/roomcalendar/CalendarBootstrapMigrationTest.java b/src/test/java/com/mgm/mcrm/roomcalendar/CalendarBootstrapMigrationTest.java
new file mode 100644
index 0000000..50425ee
--- /dev/null
+++ b/src/test/java/com/mgm/mcrm/roomcalendar/CalendarBootstrapMigrationTest.java
@@ -0,0 +1,135 @@
+package com.mgm.mcrm.roomcalendar;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.mgm.mcrm.appsetting.controllers.CfgSettingController;
+import com.mgm.mcrm.appsetting.models.CfgSettings;
+import com.mgm.mcrm.appsetting.repositories.SettingDao;
+import com.mgm.mcrm.appsetting.services.SettingService;
+import com.mgm.mcrm.authorization.models.SysGroupPermission;
+import com.mgm.mcrm.authorization.models.SysUserView;
+import com.mgm.mcrm.authorization.repositories.*;
+import com.mgm.mcrm.authorization.services.MgmUserService;
+import com.mgm.mcrm.authorization.services.UserTokenService;
+import com.mgm.mcrm.basesetting.property.repositories.*;
+import com.mgm.mcrm.basesetting.team.repositories.*;
+import com.mgm.mcrm.common.config.WebFilterConfig;
+import com.mgm.mcrm.common.dtos.http_util.HttpResponseResult;
+import com.mgm.mcrm.common.services.MGMApiService;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.test.util.ReflectionTestUtils;
+import jakarta.servlet.FilterChain;
+import java.util.*;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+class CalendarBootstrapMigrationTest {
+ @Mock MGMApiService esb;
+ @Mock PropertyDao propertyDao;
+ @Mock BlockDao blockDao;
+ @Mock DivisionDao divisionDao;
+ @Mock DepartmentDao departmentDao;
+ @Mock SectionDao sectionDao;
+ @Mock TeamStructureDao teamStructureDao;
+ @Mock SysUserViewDao users;
+ @Mock SysGroupPermissionDao permissions;
+ @InjectMocks MgmUserService userService;
+
+ @ParameterizedTest
+ @ValueSource(strings={"allowed","denied","no-local-user"})
+ void calendarPermissionComesFromLocalGroupAndKeepsBookingPermission(String mode) throws Exception {
+ HttpResponseResult upstream = new HttpResponseResult();
+ upstream.setStatusCode(200);
+ upstream.setResponseContent("{\"StatusCode\":\"OK\",\"Records\":[{\"userId\":\"123\",\"userName\":\"Tester\",\"canViewCalendar\":true}]}");
+ when(esb.invokeGetUserInfo("123","")).thenReturn(upstream);
+ if (!mode.equals("no-local-user")) {
+ SysUserView user = new SysUserView();
+ user.setGroupId(7L); user.setGroupName("test-group");
+ when(users.getByLoginId("123")).thenReturn(user);
+ SysGroupPermission booking = new SysGroupPermission();
+ booking.setPermissionId("10001_01");
+ SysGroupPermission calendar = new SysGroupPermission();
+ calendar.setPermissionId("10001_07");
+ when(permissions.getAllByGroupId(7L)).thenReturn(
+ mode.equals("allowed") ? List.of(booking,calendar) : List.of(booking));
+ }
+ var result = userService.findUserInfoByUserId("123");
+ assertTrue(result.isSuccess(),result.getMessage());
+ var json = new ObjectMapper().valueToTree(result.getData());
+ assertEquals(mode.equals("allowed"),json.get("canViewCalendar").asBoolean());
+ assertEquals(!mode.equals("no-local-user"),result.getData().isCanViewBooking());
+ }
+
+ @ParameterizedTest
+ @ValueSource(booleans={true,false})
+ void mockSwitchOnlyGrantsCalendarWithoutGroupConfiguration(boolean enabled) throws Exception {
+ ReflectionTestUtils.setField(userService,"calendarPermissionMockEnabled",enabled);
+ HttpResponseResult upstream = new HttpResponseResult();
+ upstream.setStatusCode(200);
+ upstream.setResponseContent("{\"StatusCode\":\"OK\",\"Records\":[{\"userId\":\"123\"}]}");
+ when(esb.invokeGetUserInfo("123","")).thenReturn(upstream);
+ var result = userService.findUserInfoByUserId("123");
+ assertTrue(result.isSuccess(),result.getMessage());
+ assertEquals(enabled,result.getData().isCanViewCalendar());
+ assertFalse(result.getData().isCanViewBooking());
+ verifyNoInteractions(permissions);
+ }
+
+ CfgSettings setting(String key,String value) {
+ CfgSettings setting = new CfgSettings();
+ setting.setKeys(key); setting.setKeyValues(value);
+ return setting;
+ }
+
+ @Test void calendarSettingsExposeOnlyFiveExactKeysAndPreserveValues() throws Exception {
+ List keys = List.of("CALENDAR_VIEW_PREVIOUS_DAYS","CALENDAR_VIEW_VISIBLE_DAYS",
+ "CALENDAR_VIEW_YESTERDAY_START","CALENDAR_VIEW_YESTERDAY_END","MAX_SELECTED_ROOM_TYPES");
+ List rows = new ArrayList<>();
+ for (String key : keys) rows.add(setting(key,"3"));
+ rows.add(setting("OTHER_SETTING","private"));
+ rows.add(setting("calendar_view_previous_days","wrong-case"));
+ rows.add(setting(null,"missing-key"));
+ rows.add(null);
+ SettingDao dao = mock(SettingDao.class);
+ when(dao.findAll()).thenReturn(rows);
+ SettingService service = new SettingService();
+ ReflectionTestUtils.setField(service,"settingDao",dao);
+ CfgSettingController controller = new CfgSettingController();
+ ReflectionTestUtils.setField(controller,"settingService",service);
+ var result = controller.getCalendarSetting();
+ assertEquals(200,result.getStatusCode().value());
+ assertTrue(result.getBody().isSuccess());
+ assertEquals(keys,result.getBody().getData().stream().map(CfgSettings::getKeys).toList());
+ var json = new ObjectMapper().valueToTree(result.getBody());
+ assertEquals("3",json.get("data").get(0).get("keyValues").asText());
+ }
+
+ @Test void missingSettingsReturnEmptyListWithoutInventedDefaults() {
+ SettingDao dao = mock(SettingDao.class);
+ when(dao.findAll()).thenReturn(List.of());
+ SettingService service = new SettingService();
+ ReflectionTestUtils.setField(service,"settingDao",dao);
+ assertTrue(service.getCalendarSetting().isEmpty());
+ }
+
+ @Test void calendarSettingsRouteRemainsBehindExistingJwtFilter() throws Exception {
+ WebFilterConfig config = new WebFilterConfig();
+ ReflectionTestUtils.setField(config,"secretKey","calendar-test-only-secret");
+ ReflectionTestUtils.setField(config,"userTokenService",mock(UserTokenService.class));
+ var filter = config.jwtAuthFilter().getFilter();
+ var request = new MockHttpServletRequest("POST","/api/app/setting/get_calendar_setting");
+ var response = new MockHttpServletResponse();
+ FilterChain chain = mock(FilterChain.class);
+ filter.doFilter(request,response,chain);
+ assertEquals(401,response.getStatus());
+ verifyNoInteractions(chain);
+ }
+}
diff --git a/src/test/java/com/mgm/mcrm/roomcalendar/CalendarMockDataTest.java b/src/test/java/com/mgm/mcrm/roomcalendar/CalendarMockDataTest.java
new file mode 100644
index 0000000..8d2f6f8
--- /dev/null
+++ b/src/test/java/com/mgm/mcrm/roomcalendar/CalendarMockDataTest.java
@@ -0,0 +1,73 @@
+package com.mgm.mcrm.roomcalendar;
+
+import com.mgm.mcrm.roomcalendar.services.*;
+import com.mgm.mcrm.appsetting.services.SettingService;
+import com.mgm.mcrm.appsetting.repositories.SettingDao;
+import com.mgm.mcrm.basesetting.property.repositories.PropertyDao;
+import com.mgm.mcrm.basesetting.room.repositories.RoomDao;
+import com.mgm.mcrm.common.services.MGMApiService;
+import org.junit.jupiter.api.Test;
+import org.springframework.test.util.ReflectionTestUtils;
+import java.util.*;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+class CalendarMockDataTest {
+ @Test void catalogAndQueriesAgreeForEveryHotelAcrossMonthBoundary() {
+ var catalog = CalendarMockData.metadata().getPropertyList();
+ var hotels = new ArrayList<>(catalog.subList(0,2));
+ hotels.addAll(catalog.get(2).getOtherProperties());
+ assertEquals(6,hotels.size());
+ int count=0;
+ for (var hotel:hotels) {
+ assertTrue(hotel.getName().contains("[MOCK]"));
+ String rooms=String.join(",",hotel.getRoomTypes().stream().map(t->t.getCode()).toList());
+ var rows=CalendarMockData.records("",hotel.getCode(),rooms,"2026-09-28","2026-10-03");
+ assertEquals(hotel.getRoomTypes().size()*6,rows.size());
+ assertTrue(rows.stream().anyMatch(row->row.getDate().equals("2026-10-01")));
+ assertEquals(rows,CalendarMockData.records("",hotel.getCode(),rooms,"2026-09-28","2026-10-03"));
+ count+=rows.size();
+ }
+ assertEquals(96,count);
+ }
+
+ @Test void variedAvailabilityAndPlayerQualificationAreDeterministic() {
+ var rows=CalendarMockData.records("80001","SIM_COTAI","CR,ES","2026-09-01","2026-10-31");
+ assertTrue(rows.stream().anyMatch(r->r.getRoomCount()==0));
+ assertTrue(rows.stream().anyMatch(r->r.getRoomCount()>0));
+ assertEquals(Set.of(0,1),new HashSet<>(rows.stream().map(r->r.getBookingDateQualificationFlag()).toList()));
+ assertNotEquals(rows,CalendarMockData.records("80002","SIM_COTAI","CR,ES","2026-09-01","2026-10-31"));
+ assertEquals(4,CalendarMockData.records("","SIM_MO,SIM_LB","MO_STD,LB_GRAND","2026-09-01","2026-09-02").size());
+ }
+
+ @Test void invalidRequestsFailInsteadOfProducingPlausibleData() {
+ assertThrows(IllegalArgumentException.class,()->CalendarMockData.records("","MC","CR","2026-09-01","2026-09-02"));
+ assertThrows(IllegalArgumentException.class,()->CalendarMockData.records("","SIM_COTAI","GR","2026-09-01","2026-09-02"));
+ assertThrows(IllegalArgumentException.class,()->CalendarMockData.records("","SIM_COTAI","CR","2026-09-02","2026-09-01"));
+ assertThrows(IllegalArgumentException.class,()->CalendarMockData.records("","SIM_COTAI","CR","2026-01-01","2026-12-31"));
+ }
+
+ @Test void enabledMocksDoNotReadDatabaseOrCallEsbAndDisabledSettingsUseDatabase() throws Exception {
+ var service=new RoomCalendarService();
+ var properties=mock(PropertyDao.class); var rooms=mock(RoomDao.class); var esb=mock(MGMApiService.class);
+ ReflectionTestUtils.setField(service,"propertyDao",properties);
+ ReflectionTestUtils.setField(service,"roomDao",rooms);
+ ReflectionTestUtils.setField(service,"mgmApiService",esb);
+ ReflectionTestUtils.setField(service,"calendarDataMockEnabled",true);
+ assertEquals(3,service.getPropertyRoomType().getPropertyList().size());
+ var result=service.getRoomCalendar(Map.of("Property","SIM_COTAI","RoomType","CR",
+ "StartDate","2026-09-01","EndDate","2026-09-03"));
+ assertTrue(result.isSuccess()); assertEquals(3,result.getData().size());
+ assertEquals("CALENDAR_MOCK_DATA",result.getMessage());
+ verifyNoInteractions(properties,rooms,esb);
+ var settings=new SettingService(); var dao=mock(SettingDao.class);
+ ReflectionTestUtils.setField(settings,"settingDao",dao);
+ ReflectionTestUtils.setField(settings,"calendarDataMockEnabled",true);
+ assertEquals("60",settings.getCalendarSetting().stream()
+ .filter(s->s.getKeys().equals("CALENDAR_VIEW_VISIBLE_DAYS")).findFirst().orElseThrow().getKeyValues());
+ verifyNoInteractions(dao);
+ ReflectionTestUtils.setField(settings,"calendarDataMockEnabled",false);
+ when(dao.findAll()).thenReturn(List.of());
+ assertTrue(settings.getCalendarSetting().isEmpty()); verify(dao).findAll();
+ }
+}
diff --git a/src/test/java/com/mgm/mcrm/roomcalendar/CalendarQueryMigrationTest.java b/src/test/java/com/mgm/mcrm/roomcalendar/CalendarQueryMigrationTest.java
new file mode 100644
index 0000000..9efc80b
--- /dev/null
+++ b/src/test/java/com/mgm/mcrm/roomcalendar/CalendarQueryMigrationTest.java
@@ -0,0 +1,249 @@
+package com.mgm.mcrm.roomcalendar;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.mgm.mcrm.authorization.dtos.LoginUserInfo;
+import com.mgm.mcrm.authorization.services.SysGroupPermissionService;
+import com.mgm.mcrm.authorization.utils.JwtTokenUtil;
+import com.mgm.mcrm.basesetting.property.models.CfgProperties;
+import com.mgm.mcrm.basesetting.property.repositories.PropertyDao;
+import com.mgm.mcrm.basesetting.room.models.CfgRooms;
+import com.mgm.mcrm.basesetting.room.repositories.RoomDao;
+import com.mgm.mcrm.common.dtos.http_util.HttpResponseResult;
+import com.mgm.mcrm.common.services.MGMApiService;
+import com.mgm.mcrm.roomcalendar.controllers.RoomCalendarController;
+import com.mgm.mcrm.roomcalendar.repositories.RoomCalendarRoomProjection;
+import com.mgm.mcrm.roomcalendar.services.RoomCalendarService;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.test.util.ReflectionTestUtils;
+import java.util.*;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+class CalendarQueryMigrationTest {
+ PropertyDao properties;
+ RoomDao rooms;
+ MGMApiService esb;
+ RoomCalendarService service;
+
+ @BeforeEach void setup() {
+ properties = mock(PropertyDao.class);
+ rooms = mock(RoomDao.class);
+ esb = mock(MGMApiService.class);
+ service = new RoomCalendarService();
+ ReflectionTestUtils.setField(service, "propertyDao", properties);
+ ReflectionTestUtils.setField(service, "roomDao", rooms);
+ ReflectionTestUtils.setField(service, "mgmApiService", esb);
+ }
+
+ Map request(boolean pascal) {
+ Map result = new HashMap<>();
+ result.put(pascal ? "Property" : "property", "Macau");
+ result.put(pascal ? "RoomType" : "roomType", "SUITE");
+ result.put(pascal ? "StartDate" : "startDate", "2026-09-10");
+ result.put(pascal ? "EndDate" : "endDate", "2026-09-12");
+ return result;
+ }
+
+ CfgProperties property(long id, String name, String category, int order) {
+ CfgProperties p = new CfgProperties();
+ p.setId(id); p.setCalendarName(name); p.setName(name);
+ p.setCode("MO"); p.setCategory(category);
+ p.setCalendarOrder(order); p.setShowCalendar(1);
+ return p;
+ }
+
+ void visible() {
+ when(properties.findByCalendarNameIn(any())).thenReturn(List.of(property(1,"Macau","OPERA",1)));
+ CfgRooms room = new CfgRooms();
+ room.setPropertyId("1"); room.setCalendarName("SUITE"); room.setShowCalendar(1);
+ when(rooms.findByPropertyIdInAndCalendarNameIn(List.of("1"),List.of("SUITE")))
+ .thenReturn(List.of(room));
+ }
+
+ HttpResponseResult response(int status, String body) {
+ HttpResponseResult response = new HttpResponseResult();
+ response.setStatusCode(status); response.setResponseContent(body);
+ return response;
+ }
+
+ @ParameterizedTest @ValueSource(booleans={true,false})
+ void aliasesResolveCalendarNameToEsbCodeAndNormalizeResponse(boolean pascal) throws Exception {
+ visible();
+ when(esb.invokeRequestRoomCalendar("","MO","SUITE","2026-09-10","2026-09-12",0))
+ .thenReturn(response(200,"{\"StatusCode\":\"OK\",\"Records\":[{\"Property\":\"MO\",\"RoomType\":\"SUITE\",\"Date\":\"2026-09-10T00:00:00\",\"RoomCriteria\":\"A\",\"RoomCount\":3,\"PropertyLeader\":\"L\",\"BookingDateQualificationFlag\":1}]}"));
+ var result = service.getRoomCalendar(request(pascal));
+ assertTrue(result.isSuccess());
+ assertEquals("2026-09-10",result.getData().getFirst().getDate());
+ assertEquals(3,result.getData().getFirst().getRoomCount());
+ verify(esb).invokeRequestRoomCalendar("","MO","SUITE","2026-09-10","2026-09-12",0);
+ }
+
+ @Test void missingInputDoesNotCallDatabaseOrEsb() throws Exception {
+ assertFalse(service.getRoomCalendar(Map.of()).isSuccess());
+ verifyNoInteractions(properties,rooms,esb);
+ }
+
+ @Test void hiddenPropertyDoesNotReachRoomLookupOrEsb() throws Exception {
+ CfgProperties p = property(1,"Macau","OPERA",1); p.setShowCalendar(0);
+ when(properties.findByCalendarNameIn(any())).thenReturn(List.of(p));
+ var result = service.getRoomCalendar(request(true));
+ assertEquals("PROPERTYNOSHOW",result.getErrorCode());
+ verifyNoInteractions(rooms,esb);
+ }
+
+ @Test void hiddenRoomDoesNotReachEsb() throws Exception {
+ visible();
+ CfgRooms hidden = new CfgRooms(); hidden.setCalendarName("SUITE"); hidden.setShowCalendar(0);
+ when(rooms.findByPropertyIdInAndCalendarNameIn(anyList(),anyList())).thenReturn(List.of(hidden));
+ assertEquals("ROOMTYPENOSHOW",service.getRoomCalendar(request(true)).getErrorCode());
+ verifyNoInteractions(esb);
+ }
+
+ @ParameterizedTest @ValueSource(ints={200,503})
+ void upstreamFailureIsNotEmptySuccess(int status) throws Exception {
+ visible();
+ when(esb.invokeRequestRoomCalendar(anyString(),anyString(),anyString(),anyString(),anyString(),anyInt()))
+ .thenReturn(response(status,"{\"StatusCode\":\"FAIL\",\"StatusDescription\":\"Rejected\"}"));
+ assertFalse(service.getRoomCalendar(request(true)).isSuccess());
+ }
+
+ RoomCalendarRoomProjection projected(String propertyId,String name,int order) {
+ var row = mock(RoomCalendarRoomProjection.class);
+ when(row.getPropertyId()).thenReturn(propertyId);
+ when(row.getCalendarName()).thenReturn(name);
+ when(row.getCalendarOrder()).thenReturn(order);
+ when(row.getDefaultShowCalendar()).thenReturn(1);
+ return row;
+ }
+
+ @ParameterizedTest @ValueSource(strings={"OPERA","DAYLIGHT"})
+ void metadataOrdersRoomsCapsDefaultsAndGroupsOtherProperties(String category) {
+ CfgProperties hidden = property(3,"Hidden",category,0); hidden.setShowCalendar(0);
+ when(properties.findAll()).thenReturn(new ArrayList<>(List.of(
+ property(2,"Other Hotel","OTHER",2),hidden,property(1,"Macau",category,1))));
+ List projectedRooms = List.of(
+ projected("1","D",4), projected("1","B",2), projected("1","A",1),
+ projected("1","C",3), projected("2","X",1));
+ when(rooms.queryCalendarRoomsByPropertyIds(anyList())).thenReturn(projectedRooms);
+ var result = service.getPropertyRoomType();
+ assertTrue(result.isSuccess());
+ assertEquals(2,result.getPropertyList().size());
+ var macau = result.getPropertyList().getFirst();
+ assertEquals("Macau",macau.getCode());
+ assertEquals(List.of("A","B","C"),macau.getDefaultRoomTypes());
+ var other = result.getPropertyList().get(1);
+ assertEquals("OTHER",other.getCode());
+ assertEquals(List.of("Other Hotel"),other.getDefaultRoomTypes());
+ assertEquals("X",other.getOtherProperties().getFirst().getRoomTypes().getFirst().getCode());
+ verifyNoInteractions(esb);
+ }
+
+ @Test void daylightCalendarUsesConfiguredPropertyCodeAndRoomCalendarName() throws Exception {
+ CfgProperties daylight = property(1090,"Cotai","DAYLIGHT",1);
+ daylight.setCode("configured-daylight-code");
+ when(properties.findByCalendarNameIn(List.of("Cotai"))).thenReturn(List.of(daylight));
+ CfgRooms room = new CfgRooms();
+ room.setPropertyId("1090"); room.setCalendarName("configured-room-name"); room.setShowCalendar(1);
+ when(rooms.findByPropertyIdInAndCalendarNameIn(List.of("1090"),List.of("configured-room-name")))
+ .thenReturn(List.of(room));
+ when(esb.invokeRequestRoomCalendar("","configured-daylight-code","configured-room-name","2026-09-10","2026-09-12",1))
+ .thenReturn(response(200,"{\"StatusCode\":\"OK\",\"Records\":[]}"));
+ var payload = request(true);
+ payload.put("Property","Cotai"); payload.put("RoomType","configured-room-name");
+ assertTrue(service.getRoomCalendar(payload).isSuccess());
+ verify(esb).invokeRequestRoomCalendar("","configured-daylight-code","configured-room-name","2026-09-10","2026-09-12",1);
+ }
+
+ @ParameterizedTest @ValueSource(strings={"","12345"})
+ void esbAdapterUsesAuthenticatedPostAndOptionalPlayer(String player) throws Exception {
+ MGMApiService adapter = spy(new MGMApiService());
+ doReturn("test-token").when(adapter).getAccessToken();
+ HttpResponseResult expected = response(200,"{}");
+ doAnswer(call -> {
+ assertEquals("/RequestRoomCalendar",call.getArgument(1));
+ com.alibaba.fastjson.JSONObject body = call.getArgument(2);
+ assertEquals("MO",body.getString("Property"));
+ assertEquals("SUITE",body.getString("RoomType"));
+ assertEquals("2026-09-10",body.getString("StartDate"));
+ assertEquals("2026-09-12",body.getString("EndDate"));
+ assertEquals(player.isEmpty() ? null : player,body.getString("PlayerId"));
+ assertEquals("POST",call.getArgument(3));
+ assertEquals(Map.of("Authorization","test-token"),call.getArgument(4));
+ return expected;
+ }).when(adapter).invokeESBAPI(anyString(),anyString(),any(),anyString(),anyMap(),anyInt());
+ assertSame(expected,adapter.invokeRequestRoomCalendar(player,"MO","SUITE","2026-09-10","2026-09-12"));
+ }
+
+ @Test void permittedPlayerRequestReachesServiceWithoutLosingPayload() throws Exception {
+ RoomCalendarController controller = new RoomCalendarController();
+ RoomCalendarService queries = mock(RoomCalendarService.class);
+ SysGroupPermissionService permissions = mock(SysGroupPermissionService.class);
+ ReflectionTestUtils.setField(controller,"roomCalendarService",queries);
+ ReflectionTestUtils.setField(controller,"sysGroupPermissionService",permissions);
+ MockHttpServletRequest req = new MockHttpServletRequest();
+ LoginUserInfo user = new LoginUserInfo(); user.setGroupName("calendar-users");
+ when(permissions.checkGroupPermission("calendar-users","10001_07")).thenReturn(true);
+ Map payload = request(true); payload.put("PlayerId","12345");
+ var expected = new com.mgm.mcrm.roomcalendar.dtos.response.RoomCalendarApiResponse>();
+ expected.setSuccess(true);
+ when(queries.getRoomCalendar(payload)).thenReturn(expected);
+ try (var jwt = mockStatic(JwtTokenUtil.class)) {
+ jwt.when(() -> JwtTokenUtil.parseUserFromRequest(req)).thenReturn(user);
+ assertSame(expected,controller.getPlayerRoomQualification(req,payload));
+ verify(queries).getRoomCalendar(payload);
+ }
+ }
+
+ @ParameterizedTest @ValueSource(booleans={true,false})
+ void mockPermissionAllowsAllCalendarRoutesButStillRequiresUser(boolean loggedIn) throws Exception {
+ RoomCalendarController controller = new RoomCalendarController();
+ RoomCalendarService queries = mock(RoomCalendarService.class);
+ SysGroupPermissionService permissions = mock(SysGroupPermissionService.class);
+ ReflectionTestUtils.setField(controller,"roomCalendarService",queries);
+ ReflectionTestUtils.setField(controller,"sysGroupPermissionService",permissions);
+ ReflectionTestUtils.setField(controller,"calendarPermissionMockEnabled",true);
+ MockHttpServletRequest req = new MockHttpServletRequest();
+ try (var jwt = mockStatic(JwtTokenUtil.class)) {
+ jwt.when(() -> JwtTokenUtil.parseUserFromRequest(req))
+ .thenReturn(loggedIn ? new LoginUserInfo() : null);
+ var general = controller.requestRoomCalendar(req,request(true));
+ var player = controller.getPlayerRoomQualification(req,request(true));
+ var metadata = controller.requestPropertiesRoomTypes(req);
+ if (loggedIn) {
+ verify(queries,times(2)).getRoomCalendar(request(true));
+ verify(queries).getPropertyRoomType();
+ } else {
+ assertEquals("NO_PERMISSION",general.getErrorCode());
+ assertEquals("NO_PERMISSION",player.getErrorCode());
+ assertEquals("NO_PERMISSION",metadata.getErrorCode());
+ verifyNoInteractions(queries);
+ }
+ verifyNoInteractions(permissions);
+ }
+ }
+
+ @Test void allControllerRoutesRejectDeniedGroupAndSerializeCompatibleErrors() throws Exception {
+ RoomCalendarController controller = new RoomCalendarController();
+ RoomCalendarService queries = mock(RoomCalendarService.class);
+ SysGroupPermissionService permissions = mock(SysGroupPermissionService.class);
+ ReflectionTestUtils.setField(controller,"roomCalendarService",queries);
+ ReflectionTestUtils.setField(controller,"sysGroupPermissionService",permissions);
+ MockHttpServletRequest req = new MockHttpServletRequest();
+ LoginUserInfo user = new LoginUserInfo(); user.setGroupName("restricted");
+ try (var jwt = mockStatic(JwtTokenUtil.class)) {
+ jwt.when(() -> JwtTokenUtil.parseUserFromRequest(req)).thenReturn(user);
+ var result = controller.requestRoomCalendar(req,request(true));
+ var json = new ObjectMapper().valueToTree(result);
+ assertEquals("NO_PERMISSION",json.get("code").asText());
+ assertEquals("NO_PERMISSION",json.get("errorCode").asText());
+ assertEquals("NO_PERMISSION",controller.getPlayerRoomQualification(req,request(true)).getErrorCode());
+ assertEquals("NO_PERMISSION",controller.requestPropertiesRoomTypes(req).getErrorCode());
+ verify(permissions,times(3)).checkGroupPermission("restricted","10001_07");
+ verifyNoInteractions(queries);
+ }
+ }
+}