兼容第三种房表名单格式
This commit is contained in:
1 parent
99fb919158
commit
d56587a078
18 files changed
+427
-98
No files matched your search
+3
-3
@@ -7,11 +7,11 @@ import java.time.LocalDate;
|
||||
*
|
||||
* @param hotelId 酒店 ID;为空时按当前登录用户默认酒店解析
|
||||
* @param peoplePerRoom 每间房人数
|
||||
* @param arrival 第二种来源名单样式使用的入住酒店本地日期;第一种样式可为空并由来源旅游日期派生
|
||||
* @param departure 第二种来源名单样式使用的离店酒店本地日期;第一种样式可为空并由来源旅游日期派生
|
||||
* @param arrival 无旅游日期来源名单样式使用的入住酒店本地日期;第一种样式可为空并由来源旅游日期派生
|
||||
* @param departure 无旅游日期来源名单样式使用的离店酒店本地日期;第一种样式可为空并由来源旅游日期派生
|
||||
* @param roomType 目标 Excel 的 Room Type
|
||||
* @param paymentType 目标 Excel 的 Payment Type;默认 BTQR,当前允许 BTQR 或 CA
|
||||
* @param nationality 目标 Excel 的 Nationality;当前只允许 KR 或 CHN
|
||||
* @param nationality 目标 Excel 的 Nationality;当前只允许 KR、CN、TH、MM、RS 或 TW
|
||||
*/
|
||||
public record ReservationRoomingListGenerationRequest(
|
||||
String hotelId,
|
||||
|
||||
+4
-4
@@ -30,8 +30,8 @@ public class ReservationRoomingListGenerationServiceImpl implements ReservationR
|
||||
private static final String PAYMENT_TYPE_BTQR = "BTQR";
|
||||
private static final String PAYMENT_TYPE_CA = "CA";
|
||||
private static final String DEFAULT_PAYMENT_TYPE = PAYMENT_TYPE_BTQR;
|
||||
private static final String NATIONALITY_KR = "KR";
|
||||
private static final String NATIONALITY_CHN = "CHN";
|
||||
private static final List<String> ALLOWED_NATIONALITIES =
|
||||
List.of("KR", "CN", "TH", "MM", "RS", "TW");
|
||||
|
||||
private final HotelContextService hotelContextService;
|
||||
private final RoomingListSourceExcelParser sourceExcelParser;
|
||||
@@ -152,8 +152,8 @@ public class ReservationRoomingListGenerationServiceImpl implements ReservationR
|
||||
return "";
|
||||
}
|
||||
String code = normalized.toUpperCase(java.util.Locale.ROOT);
|
||||
if (!NATIONALITY_KR.equals(code) && !NATIONALITY_CHN.equals(code)) {
|
||||
errors.add("nationality: 当前只允许 KR 或 CHN。");
|
||||
if (!ALLOWED_NATIONALITIES.contains(code)) {
|
||||
errors.add("nationality: 当前只允许 KR、CN、TH、MM、RS 或 TW。");
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
+8
-8
@@ -8,16 +8,16 @@ import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Rooming List 护照姓名解析器。第一版只处理斜杠或空格分隔的英文护照姓名。
|
||||
* Rooming List 英文姓名解析器。第一版只处理斜杠或空格分隔的英文姓名。
|
||||
*/
|
||||
@Component
|
||||
public class RoomingListNameParser {
|
||||
|
||||
/**
|
||||
* 将来源 Excel 单元格中的护照全名拆分为目标模板 Name / First Name。
|
||||
* 将来源 Excel 单元格中的英文姓名拆分为目标模板 Name / First Name。
|
||||
*/
|
||||
public ReservationRoomingListGuestDto parse(int rowNumber, String rawPassportName) {
|
||||
String normalized = trimToNull(rawPassportName);
|
||||
public ReservationRoomingListGuestDto parse(int rowNumber, String rawEnglishName) {
|
||||
String normalized = trimToNull(rawEnglishName);
|
||||
if (normalized == null) {
|
||||
throw invalidName(rowNumber);
|
||||
}
|
||||
@@ -35,7 +35,7 @@ public class RoomingListNameParser {
|
||||
}
|
||||
|
||||
/**
|
||||
* 按 `/` 拆分护照姓名,第一段作为姓氏,剩余部分合并作为名字。
|
||||
* 按 `/` 拆分英文姓名,第一段作为姓氏,剩余部分合并作为名字。
|
||||
*/
|
||||
private ParsedName parseSlashName(String value) {
|
||||
List<String> parts = Arrays.stream(value.split("/"))
|
||||
@@ -50,7 +50,7 @@ public class RoomingListNameParser {
|
||||
}
|
||||
|
||||
/**
|
||||
* 按连续空白拆分护照姓名,第一段作为姓氏,剩余部分合并作为名字。
|
||||
* 按连续空白拆分英文姓名,第一段作为姓氏,剩余部分合并作为名字。
|
||||
*/
|
||||
private ParsedName parseWhitespaceName(String value) {
|
||||
List<String> parts = Arrays.stream(value.split("\\s+"))
|
||||
@@ -72,7 +72,7 @@ public class RoomingListNameParser {
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"ROOMING_LIST_SOURCE_FILE_INVALID",
|
||||
"来源名单文件不合法。",
|
||||
List.of("第 " + rowNumber + " 行护照全名无法拆分为 Name / First Name。"));
|
||||
List.of("第 " + rowNumber + " 行英文姓名无法拆分为 Name / First Name。"));
|
||||
}
|
||||
|
||||
private boolean isBlank(String value) {
|
||||
@@ -87,7 +87,7 @@ public class RoomingListNameParser {
|
||||
}
|
||||
|
||||
/**
|
||||
* 护照姓名拆分结果。
|
||||
* 英文姓名拆分结果。
|
||||
*
|
||||
* @param lastName 姓氏
|
||||
* @param firstName 名字
|
||||
|
||||
+48
-5
@@ -54,7 +54,7 @@ public class RoomingListSourceExcelParser {
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析来源 Excel。第二种英文姓名来源样式需要调用方提供入住 / 离店酒店本地日期。
|
||||
* 解析来源 Excel。无旅游日期来源样式需要调用方提供入住 / 离店酒店本地日期。
|
||||
*/
|
||||
public ReservationRoomingListSourceDataDto parse(
|
||||
MultipartFile sourceFile,
|
||||
@@ -86,7 +86,17 @@ public class RoomingListSourceExcelParser {
|
||||
formatter,
|
||||
requireManualStayDateRange(manualArrival, manualDeparture));
|
||||
} else {
|
||||
throw invalidFile(List.of("未找到可识别的名单表头:护照全名/旅游日期 或 英文姓/英文名。"));
|
||||
HeaderLocation singleEnglishNameHeader =
|
||||
findSingleEnglishNameHeaderIfPresent(sheet, formatter);
|
||||
if (singleEnglishNameHeader != null) {
|
||||
sourceRows = parseSingleEnglishNameRows(
|
||||
sheet,
|
||||
singleEnglishNameHeader,
|
||||
formatter,
|
||||
requireManualStayDateRange(manualArrival, manualDeparture));
|
||||
} else {
|
||||
throw invalidFile(List.of("未找到可识别的名单表头:护照全名/旅游日期、英文姓/英文名 或 英文名。"));
|
||||
}
|
||||
}
|
||||
}
|
||||
return new ReservationRoomingListSourceDataDto(
|
||||
@@ -209,6 +219,13 @@ public class RoomingListSourceExcelParser {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找第三种来源样式的单列英文名表头;该列值形如 `CAI/HAIYUN`。
|
||||
*/
|
||||
private HeaderLocation findSingleEnglishNameHeaderIfPresent(Sheet sheet, DataFormatter formatter) {
|
||||
return findHeaderIfPresent(sheet, formatter, ENGLISH_FIRST_NAME_HEADER);
|
||||
}
|
||||
|
||||
/**
|
||||
* 第二种来源样式直接使用英文姓 / 英文名,不读取证件号、中文名或生日等敏感字段。
|
||||
*/
|
||||
@@ -243,15 +260,41 @@ public class RoomingListSourceExcelParser {
|
||||
}
|
||||
|
||||
/**
|
||||
* 第二种来源样式没有可靠旅游日期,必须使用用户提交的入住 / 离店日期。
|
||||
* 第三种来源样式直接读取单列英文名并复用护照英文名拆分,不读取证件号、生日或联系方式等敏感字段。
|
||||
*/
|
||||
private SourceRows parseSingleEnglishNameRows(
|
||||
Sheet sheet,
|
||||
HeaderLocation englishNameHeader,
|
||||
DataFormatter formatter,
|
||||
TravelDateRange manualStayDateRange) {
|
||||
List<ReservationRoomingListGuestDto> guests = new ArrayList<>();
|
||||
for (int rowIndex = englishNameHeader.rowIndex() + 1; rowIndex <= sheet.getLastRowNum(); rowIndex++) {
|
||||
Row row = sheet.getRow(rowIndex);
|
||||
if (row == null) {
|
||||
continue;
|
||||
}
|
||||
String englishName = cellText(row.getCell(englishNameHeader.cellIndex()), formatter);
|
||||
if (isBlank(englishName)) {
|
||||
continue;
|
||||
}
|
||||
guests.add(nameParser.parse(rowIndex + 1, englishName));
|
||||
}
|
||||
if (guests.isEmpty()) {
|
||||
throw invalidFile(List.of("英文名列没有可用旅客姓名。"));
|
||||
}
|
||||
return new SourceRows(List.copyOf(guests), manualStayDateRange);
|
||||
}
|
||||
|
||||
/**
|
||||
* 无旅游日期来源样式没有可靠旅游日期,必须使用用户提交的入住 / 离店日期。
|
||||
*/
|
||||
private TravelDateRange requireManualStayDateRange(LocalDate manualArrival, LocalDate manualDeparture) {
|
||||
List<String> errors = new ArrayList<>();
|
||||
if (manualArrival == null) {
|
||||
errors.add("arrival: 第二种来源名单样式必须填写入住日期。");
|
||||
errors.add("arrival: 无旅游日期来源名单样式必须填写入住日期。");
|
||||
}
|
||||
if (manualDeparture == null) {
|
||||
errors.add("departure: 第二种来源名单样式必须填写离店日期。");
|
||||
errors.add("departure: 无旅游日期来源名单样式必须填写离店日期。");
|
||||
}
|
||||
if (!errors.isEmpty()) {
|
||||
throw validationError(errors);
|
||||
|
||||
Reference in new issue
Block a user