feat:Add check out API
This commit is contained in:
@@ -45,6 +45,9 @@ public class EsignAuditLogService {
|
|||||||
public static final String EVT_OHIP_PUT_PROFILE = "OHIP_PUT_PROFILE";
|
public static final String EVT_OHIP_PUT_PROFILE = "OHIP_PUT_PROFILE";
|
||||||
public static final String EVT_OHIP_POST_AUTO_ROOM_ASSIGNMENT = "OHIP_POST_AUTO_ROOM_ASSIGNMENT";
|
public static final String EVT_OHIP_POST_AUTO_ROOM_ASSIGNMENT = "OHIP_POST_AUTO_ROOM_ASSIGNMENT";
|
||||||
public static final String EVT_OHIP_POST_CHECK_IN = "OHIP_POST_CHECK_IN";
|
public static final String EVT_OHIP_POST_CHECK_IN = "OHIP_POST_CHECK_IN";
|
||||||
|
public static final String EVT_OHIP_POST_CHECK_OUT_SEARCH = "OHIP_POST_CHECK_OUT_SEARCH";
|
||||||
|
public static final String EVT_OHIP_POST_CHECK_OUT_PAYMENT = "OHIP_POST_CHECK_OUT_PAYMENT";
|
||||||
|
public static final String EVT_OHIP_POST_CHECK_OUT = "OHIP_POST_CHECK_OUT";
|
||||||
public static final String EVT_OHIP_PUT_STAY_EXTENSION = "OHIP_PUT_STAY_EXTENSION";
|
public static final String EVT_OHIP_PUT_STAY_EXTENSION = "OHIP_PUT_STAY_EXTENSION";
|
||||||
|
|
||||||
@Resource(name = "esignAuditLogRepository")
|
@Resource(name = "esignAuditLogRepository")
|
||||||
|
|||||||
+14
@@ -2,6 +2,7 @@ package com.sjmresorts.opsapp.booking.reservation.controllers;
|
|||||||
|
|
||||||
import com.sjmresorts.opsapp.esign.common.EsignApiPaths;
|
import com.sjmresorts.opsapp.esign.common.EsignApiPaths;
|
||||||
import com.sjmresorts.opsapp.esign.reservation.dtos.BookingCheckInResult;
|
import com.sjmresorts.opsapp.esign.reservation.dtos.BookingCheckInResult;
|
||||||
|
import com.sjmresorts.opsapp.esign.reservation.dtos.BookingCheckOutResult;
|
||||||
import com.sjmresorts.opsapp.esign.reservation.dtos.BookingStayExtensionRequest;
|
import com.sjmresorts.opsapp.esign.reservation.dtos.BookingStayExtensionRequest;
|
||||||
import com.sjmresorts.opsapp.esign.reservation.dtos.BookingStayExtensionResult;
|
import com.sjmresorts.opsapp.esign.reservation.dtos.BookingStayExtensionResult;
|
||||||
import com.sjmresorts.opsapp.esign.reservation.services.ReservationThirdPartyClient;
|
import com.sjmresorts.opsapp.esign.reservation.services.ReservationThirdPartyClient;
|
||||||
@@ -39,6 +40,19 @@ public class BookingCheckInController {
|
|||||||
return ResponseEntity.status(resolveHttpStatus(result.statusCode())).body(result);
|
return ResponseEntity.status(resolveHttpStatus(result.statusCode())).body(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/{reservationId}/check-out")
|
||||||
|
@Operation(summary = "办理 Booking 自动退房")
|
||||||
|
public ResponseEntity<BookingCheckOutResult> checkOut(
|
||||||
|
@PathVariable String reservationId,
|
||||||
|
@RequestParam(required = false) String hotelId) {
|
||||||
|
if (StringUtils.isBlank(reservationId)) {
|
||||||
|
return ResponseEntity.badRequest().body(BookingCheckOutResult.failure(
|
||||||
|
400, reservationId, null, null, "reservationId required", null, null, null));
|
||||||
|
}
|
||||||
|
BookingCheckOutResult result = reservationThirdPartyClient.checkOut(hotelId, reservationId);
|
||||||
|
return ResponseEntity.status(resolveHttpStatus(result.statusCode())).body(result);
|
||||||
|
}
|
||||||
|
|
||||||
@PutMapping("/{reservationId}/stay")
|
@PutMapping("/{reservationId}/stay")
|
||||||
@Operation(summary = "续房并修改 Booking 离店日期")
|
@Operation(summary = "续房并修改 Booking 离店日期")
|
||||||
public ResponseEntity<BookingStayExtensionResult> extendStay(
|
public ResponseEntity<BookingStayExtensionResult> extendStay(
|
||||||
|
|||||||
+176
@@ -5,6 +5,7 @@ import com.sjmresorts.opsapp.common.utils.SimpleHttpClient;
|
|||||||
import com.sjmresorts.opsapp.esign.audit.services.EsignAuditLogService;
|
import com.sjmresorts.opsapp.esign.audit.services.EsignAuditLogService;
|
||||||
import com.sjmresorts.opsapp.esign.audit.support.ThirdPartyAuditDetailBuilder;
|
import com.sjmresorts.opsapp.esign.audit.support.ThirdPartyAuditDetailBuilder;
|
||||||
import com.sjmresorts.opsapp.esign.reservation.dtos.BookingCheckInResult;
|
import com.sjmresorts.opsapp.esign.reservation.dtos.BookingCheckInResult;
|
||||||
|
import com.sjmresorts.opsapp.esign.reservation.dtos.BookingCheckOutResult;
|
||||||
import com.sjmresorts.opsapp.esign.reservation.dtos.BookingStayExtensionResult;
|
import com.sjmresorts.opsapp.esign.reservation.dtos.BookingStayExtensionResult;
|
||||||
import com.sjmresorts.opsapp.esign.reservation.dtos.ReservationThirdPartyResult;
|
import com.sjmresorts.opsapp.esign.reservation.dtos.ReservationThirdPartyResult;
|
||||||
import com.sjmresorts.opsapp.esign.reservation.dtos.SignedRegistrationCardUploadResult;
|
import com.sjmresorts.opsapp.esign.reservation.dtos.SignedRegistrationCardUploadResult;
|
||||||
@@ -25,6 +26,7 @@ import org.springframework.stereotype.Service;
|
|||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.math.BigDecimal;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -80,6 +82,18 @@ public class ReservationThirdPartyClient {
|
|||||||
@Value("${ops-app.esign.reservation.third-party.booking-stay-extension.path-template:/rsv/v1/hotels/{hotelId}/reservations/{reservationId}}")
|
@Value("${ops-app.esign.reservation.third-party.booking-stay-extension.path-template:/rsv/v1/hotels/{hotelId}/reservations/{reservationId}}")
|
||||||
private String bookingStayExtensionPathTemplate;
|
private String bookingStayExtensionPathTemplate;
|
||||||
|
|
||||||
|
@Value("${ops-app.esign.reservation.third-party.booking-check-out.search.path-template:/rsv/v1/hotels/{hotelId}/reservations/searches}")
|
||||||
|
private String checkOutSearchPathTemplate;
|
||||||
|
|
||||||
|
@Value("${ops-app.esign.reservation.third-party.booking-check-out.payment.path-template:/csh/v1/hotels/{hotelId}/reservations/{reservationId}/payments}")
|
||||||
|
private String checkOutPaymentPathTemplate;
|
||||||
|
|
||||||
|
@Value("${ops-app.esign.reservation.third-party.booking-check-out.check-out.path-template:/csh/v1/hotels/{hotelId}/reservations/{reservationId}/checkOuts}")
|
||||||
|
private String checkOutPathTemplate;
|
||||||
|
|
||||||
|
@Value("${ops-app.esign.reservation.third-party.booking-check-out.cashier-id:104}")
|
||||||
|
private int checkOutCashierId;
|
||||||
|
|
||||||
@Value("${ops-app.esign.reservation.third-party.file-attachment.path:/med/config/v1/fileAttachments}")
|
@Value("${ops-app.esign.reservation.third-party.file-attachment.path:/med/config/v1/fileAttachments}")
|
||||||
private String fileAttachmentPath;
|
private String fileAttachmentPath;
|
||||||
|
|
||||||
@@ -311,6 +325,168 @@ public class ReservationThirdPartyClient {
|
|||||||
checkInResponse.getStatusCode(), reservationKey, roomId, assignmentSummary, checkInSummary);
|
checkInResponse.getStatusCode(), reservationKey, roomId, assignmentSummary, checkInSummary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BookingCheckOutResult checkOut(String hotelId, String reservationId) {
|
||||||
|
String reservationKey = StringUtils.trimToNull(reservationId);
|
||||||
|
String ohipHotelId = resolveOhipHotelId(hotelId);
|
||||||
|
if (StringUtils.isAnyBlank(baseUrl, ohipHotelId, reservationKey)) {
|
||||||
|
return BookingCheckOutResult.failure(400, reservationId, null, null,
|
||||||
|
"hotelId and reservationId required", null, null, null);
|
||||||
|
}
|
||||||
|
if (!oauthTokenSingleton.isConfigured()) {
|
||||||
|
return BookingCheckOutResult.failure(503, reservationKey, null, null,
|
||||||
|
"OHIP OAuth is not configured", null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONObject searchBody = new JSONObject();
|
||||||
|
JSONArray reservationIdList = new JSONArray();
|
||||||
|
try {
|
||||||
|
reservationIdList.add(Long.parseLong(reservationKey));
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return BookingCheckOutResult.failure(400, reservationKey, null, null,
|
||||||
|
"reservationId must be numeric", null, null, null);
|
||||||
|
}
|
||||||
|
searchBody.put("reservationIdList", reservationIdList);
|
||||||
|
HttpResponseResult searchResponse;
|
||||||
|
try {
|
||||||
|
searchResponse = executePost(
|
||||||
|
checkOutSearchPathTemplate.replace("{hotelId}", urlEncodePathSegment(ohipHotelId)),
|
||||||
|
searchBody,
|
||||||
|
ohipHotelId,
|
||||||
|
EsignAuditLogService.EVT_OHIP_POST_CHECK_OUT_SEARCH,
|
||||||
|
"POST check out reservation search reservationId=" + reservationKey);
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
return BookingCheckOutResult.failure(502, reservationKey, null, null, e.getMessage(), null, null, null);
|
||||||
|
}
|
||||||
|
String searchSummary = truncate(searchResponse.getResponseContent());
|
||||||
|
if (searchResponse.isTransportError()) {
|
||||||
|
return BookingCheckOutResult.failure(502, reservationKey, null, null,
|
||||||
|
StringUtils.defaultIfBlank(searchResponse.getErrorMessage(), "OHIP reservation search transport error"),
|
||||||
|
searchSummary, null, null);
|
||||||
|
}
|
||||||
|
if (!searchResponse.isSuccessful()) {
|
||||||
|
return BookingCheckOutResult.failure(searchResponse.getStatusCode(), reservationKey, null, null,
|
||||||
|
extractOhipErrorMessage(searchResponse.bodyOrEmpty(),
|
||||||
|
"OHIP reservation search returned HTTP " + searchResponse.getStatusCode()),
|
||||||
|
searchSummary, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONObject balance = extractReservationBalance(searchResponse.bodyOrEmpty());
|
||||||
|
BigDecimal balanceAmount = balance != null ? balance.getBigDecimal("amount") : null;
|
||||||
|
String currencyCode = balance != null ? balance.getString("currencyCode") : null;
|
||||||
|
if (balanceAmount == null) {
|
||||||
|
return BookingCheckOutResult.failure(502, reservationKey, null, currencyCode,
|
||||||
|
"OHIP reservation search response does not contain roomStay balance amount",
|
||||||
|
searchSummary, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
String paymentSummary = null;
|
||||||
|
if (balanceAmount.compareTo(BigDecimal.ZERO) > 0) {
|
||||||
|
JSONObject paymentBody = new JSONObject();
|
||||||
|
JSONObject criteria = new JSONObject();
|
||||||
|
JSONObject paymentMethod = new JSONObject();
|
||||||
|
paymentMethod.put("paymentMethod", "CA");
|
||||||
|
criteria.put("paymentMethod", paymentMethod);
|
||||||
|
JSONObject postingAmount = new JSONObject();
|
||||||
|
postingAmount.put("amount", balanceAmount);
|
||||||
|
postingAmount.put("currencyCode", currencyCode);
|
||||||
|
criteria.put("postingAmount", postingAmount);
|
||||||
|
criteria.put("cashierId", checkOutCashierId);
|
||||||
|
paymentBody.put("criteria", criteria);
|
||||||
|
HttpResponseResult paymentResponse;
|
||||||
|
try {
|
||||||
|
paymentResponse = executePost(
|
||||||
|
checkOutPaymentPathTemplate
|
||||||
|
.replace("{hotelId}", urlEncodePathSegment(ohipHotelId))
|
||||||
|
.replace("{reservationId}", urlEncodePathSegment(reservationKey)),
|
||||||
|
paymentBody,
|
||||||
|
ohipHotelId,
|
||||||
|
EsignAuditLogService.EVT_OHIP_POST_CHECK_OUT_PAYMENT,
|
||||||
|
"POST check out payment reservationId=" + reservationKey + " amount=" + balanceAmount);
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
return BookingCheckOutResult.failure(502, reservationKey, balanceAmount, currencyCode,
|
||||||
|
e.getMessage(), searchSummary, null, null);
|
||||||
|
}
|
||||||
|
paymentSummary = truncate(paymentResponse.getResponseContent());
|
||||||
|
if (paymentResponse.isTransportError()) {
|
||||||
|
return BookingCheckOutResult.failure(502, reservationKey, balanceAmount, currencyCode,
|
||||||
|
StringUtils.defaultIfBlank(paymentResponse.getErrorMessage(), "OHIP payment transport error"),
|
||||||
|
searchSummary, paymentSummary, null);
|
||||||
|
}
|
||||||
|
if (!paymentResponse.isSuccessful()) {
|
||||||
|
return BookingCheckOutResult.failure(paymentResponse.getStatusCode(), reservationKey, balanceAmount, currencyCode,
|
||||||
|
extractOhipErrorMessage(paymentResponse.bodyOrEmpty(),
|
||||||
|
"OHIP payment returned HTTP " + paymentResponse.getStatusCode()),
|
||||||
|
searchSummary, paymentSummary, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONObject checkOutBody = new JSONObject();
|
||||||
|
JSONObject reservation = new JSONObject();
|
||||||
|
reservation.put("cashierId", checkOutCashierId);
|
||||||
|
reservation.put("autoCheckout", true);
|
||||||
|
checkOutBody.put("reservation", reservation);
|
||||||
|
HttpResponseResult checkOutResponse;
|
||||||
|
try {
|
||||||
|
checkOutResponse = executePost(
|
||||||
|
checkOutPathTemplate
|
||||||
|
.replace("{hotelId}", urlEncodePathSegment(ohipHotelId))
|
||||||
|
.replace("{reservationId}", urlEncodePathSegment(reservationKey)),
|
||||||
|
checkOutBody,
|
||||||
|
ohipHotelId,
|
||||||
|
EsignAuditLogService.EVT_OHIP_POST_CHECK_OUT,
|
||||||
|
"POST check out reservationId=" + reservationKey);
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
return BookingCheckOutResult.failure(502, reservationKey, balanceAmount, currencyCode,
|
||||||
|
e.getMessage(), searchSummary, paymentSummary, null);
|
||||||
|
}
|
||||||
|
String checkOutSummary = truncate(checkOutResponse.getResponseContent());
|
||||||
|
if (checkOutResponse.isTransportError()) {
|
||||||
|
return BookingCheckOutResult.failure(502, reservationKey, balanceAmount, currencyCode,
|
||||||
|
StringUtils.defaultIfBlank(checkOutResponse.getErrorMessage(), "OHIP check out transport error"),
|
||||||
|
searchSummary, paymentSummary, checkOutSummary);
|
||||||
|
}
|
||||||
|
if (!checkOutResponse.isSuccessful()) {
|
||||||
|
return BookingCheckOutResult.failure(checkOutResponse.getStatusCode(), reservationKey, balanceAmount, currencyCode,
|
||||||
|
extractOhipErrorMessage(checkOutResponse.bodyOrEmpty(),
|
||||||
|
"OHIP check out returned HTTP " + checkOutResponse.getStatusCode()),
|
||||||
|
searchSummary, paymentSummary, checkOutSummary);
|
||||||
|
}
|
||||||
|
return BookingCheckOutResult.success(checkOutResponse.getStatusCode(), reservationKey, balanceAmount,
|
||||||
|
currencyCode, searchSummary, paymentSummary, checkOutSummary);
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpResponseResult executePost(
|
||||||
|
String path,
|
||||||
|
JSONObject body,
|
||||||
|
String hotelId,
|
||||||
|
String eventType,
|
||||||
|
String message) {
|
||||||
|
String url = SimpleHttpClient.joinUrl(baseUrl, path);
|
||||||
|
SimpleHttpClient.RequestBuilder request = SimpleHttpClient.post(url)
|
||||||
|
.timeoutMs(timeoutMs)
|
||||||
|
.jsonBody(body.toJSONString());
|
||||||
|
applyBusinessAuth(request, hotelId);
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
HttpResponseResult response = request.execute();
|
||||||
|
auditThirdParty(eventType, request, response, start, hotelId, message);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static JSONObject extractReservationBalance(String responseBody) {
|
||||||
|
JSONObject root = JSONObject.parseObject(responseBody);
|
||||||
|
JSONObject reservations = root.getJSONObject("reservations");
|
||||||
|
if (reservations == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
JSONArray reservationInfo = reservations.getJSONArray("reservationInfo");
|
||||||
|
if (reservationInfo == null || reservationInfo.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
JSONObject reservation = reservationInfo.getJSONObject(0);
|
||||||
|
JSONObject roomStay = reservation != null ? reservation.getJSONObject("roomStay") : null;
|
||||||
|
return roomStay != null ? roomStay.getJSONObject("balance") : null;
|
||||||
|
}
|
||||||
|
|
||||||
public BookingStayExtensionResult extendStay(String hotelId, String reservationId, LocalDate departureDate) {
|
public BookingStayExtensionResult extendStay(String hotelId, String reservationId, LocalDate departureDate) {
|
||||||
String reservationKey = StringUtils.trimToNull(reservationId);
|
String reservationKey = StringUtils.trimToNull(reservationId);
|
||||||
String ohipHotelId = resolveOhipHotelId(hotelId);
|
String ohipHotelId = resolveOhipHotelId(hotelId);
|
||||||
|
|||||||
@@ -145,6 +145,14 @@ ops-app:
|
|||||||
path-template: /fof/v1/hotels/{hotelId}/reservations/autoRoomAssignments
|
path-template: /fof/v1/hotels/{hotelId}/reservations/autoRoomAssignments
|
||||||
check-in:
|
check-in:
|
||||||
path-template: /fof/v1/hotels/{hotelId}/reservations/{reservationId}/checkIns
|
path-template: /fof/v1/hotels/{hotelId}/reservations/{reservationId}/checkIns
|
||||||
|
booking-check-out:
|
||||||
|
search:
|
||||||
|
path-template: /rsv/v1/hotels/{hotelId}/reservations/searches
|
||||||
|
payment:
|
||||||
|
path-template: /csh/v1/hotels/{hotelId}/reservations/{reservationId}/payments
|
||||||
|
check-out:
|
||||||
|
path-template: /csh/v1/hotels/{hotelId}/reservations/{reservationId}/checkOuts
|
||||||
|
cashier-id: 104
|
||||||
file-attachment:
|
file-attachment:
|
||||||
path: /med/config/v1/fileAttachments
|
path: /med/config/v1/fileAttachments
|
||||||
user-name: USER@HOTEL
|
user-name: USER@HOTEL
|
||||||
|
|||||||
@@ -142,6 +142,14 @@ ops-app:
|
|||||||
path-template: /fof/v1/hotels/{hotelId}/reservations/autoRoomAssignments
|
path-template: /fof/v1/hotels/{hotelId}/reservations/autoRoomAssignments
|
||||||
check-in:
|
check-in:
|
||||||
path-template: /fof/v1/hotels/{hotelId}/reservations/{reservationId}/checkIns
|
path-template: /fof/v1/hotels/{hotelId}/reservations/{reservationId}/checkIns
|
||||||
|
booking-check-out:
|
||||||
|
search:
|
||||||
|
path-template: /rsv/v1/hotels/{hotelId}/reservations/searches
|
||||||
|
payment:
|
||||||
|
path-template: /csh/v1/hotels/{hotelId}/reservations/{reservationId}/payments
|
||||||
|
check-out:
|
||||||
|
path-template: /csh/v1/hotels/{hotelId}/reservations/{reservationId}/checkOuts
|
||||||
|
cashier-id: 104
|
||||||
file-attachment:
|
file-attachment:
|
||||||
path: /med/config/v1/fileAttachments
|
path: /med/config/v1/fileAttachments
|
||||||
user-name: USER@HOTEL
|
user-name: USER@HOTEL
|
||||||
|
|||||||
@@ -36,6 +36,14 @@ ops-app:
|
|||||||
batch-max: 2000
|
batch-max: 2000
|
||||||
reservation:
|
reservation:
|
||||||
third-party:
|
third-party:
|
||||||
|
booking-check-out:
|
||||||
|
search:
|
||||||
|
path-template: /rsv/v1/hotels/{hotelId}/reservations/searches
|
||||||
|
payment:
|
||||||
|
path-template: /csh/v1/hotels/{hotelId}/reservations/{reservationId}/payments
|
||||||
|
check-out:
|
||||||
|
path-template: /csh/v1/hotels/{hotelId}/reservations/{reservationId}/checkOuts
|
||||||
|
cashier-id: 104
|
||||||
profile-fields:
|
profile-fields:
|
||||||
enabled: true
|
enabled: true
|
||||||
path-template: /crm/v1/profiles/{profileId}
|
path-template: /crm/v1/profiles/{profileId}
|
||||||
|
|||||||
Reference in New Issue
Block a user