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); + } + } +}