const db = require ( '../config/db' ) ;
const initModels = require ( './../models/init-models' ) ;
const { AvailableAccommodationIds , AccommodationsDetails , Availability } = require ( '../vendor/heytrip' ) ;
const { isEmpty } = require ( '../utils/commons' ) ;
const { resolveDetails , resolveRatePlans } = require ( '../helper/heytripDataHelper' ) ;
const { DEFAULT _LGC , LGC _MAPPED , HEYTRIP _API _PROD } = require ( '../config/constants' ) ;
const { sequelize : Sequelize , Op } = db ;
const models = initModels ( Sequelize ) ;
const Country = models . countryModel ;
const City = models . cityModel ;
const HeytripIds = models . heytripIdsModel ;
const Hotelinfo = models . hotelinfoModel ;
const Hotelinfo2 = models . hotelinfo2Model ;
const Rooms = models . roomsModel ;
const Facility = models . facilityModel ;
const Images = models . imagesModel ;
const Informations = models . informationsModel ;
const Reviews = models . reviewsModel ;
const ReviewsSummaries = models . reviewsSummariesModel ;
const Locations = models . locationsModel ;
// const CacheAvailability = models.cacheAvailabilityModel;
const Logs = models . requestLogsModel ;
// HeytripIds.hasMany(Hotelinfo, {
// foreignKey: 'hotel_id',
// onDelete: 'NO ACTION',
// onUpdate: 'NO ACTION',
// });
// Hotelinfo.belongsTo(HeytripIds, { as: 'aid', foreignKey: 'hotel_id', onDelete: 'NO ACTION', onUpdate: 'NO ACTION' });
// Hotelinfo.hasMany(Rooms, { sourceKey: 'hotel_id', foreignKey: 'hotel_id', onDelete: 'NO ACTION', onUpdate: 'NO ACTION' });
// Rooms.belongsTo(Hotelinfo, { targetKey: 'hotel_id', foreignKey: 'hotel_id', onDelete: 'NO ACTION', onUpdate: 'NO ACTION', });
Hotelinfo . belongsTo ( HeytripIds , { as : 'aid' , foreignKey : 'hotel_id' , onDelete : 'NO ACTION' , onUpdate : 'NO ACTION' } ) ;
Hotelinfo . hasMany ( Hotelinfo2 , { as : 'locale_info' , sourceKey : 'hotel_id' , foreignKey : 'hotel_id' , onDelete : 'NO ACTION' , onUpdate : 'NO ACTION' } ) ;
Hotelinfo . hasMany ( City , { as : 'city' , sourceKey : 'city_id' , foreignKey : 'id' , onDelete : 'NO ACTION' , onUpdate : 'NO ACTION' , } ) ; // 多语种, 所以实际是 hasMany , 用 hasOne 要指定 lgc= 1 或者2
Hotelinfo . hasMany ( Country , { as : 'country' , sourceKey : 'country_code' , foreignKey : 'id' , onDelete : 'NO ACTION' , onUpdate : 'NO ACTION' , } ) ; // 多语种, 所以实际是 hasMany , 用 hasOne 要指定 lgc= 1 或者2
// Hotelinfo2.belongsTo(Hotelinfo, { targetKey: 'hotel_id', foreignKey: 'hotel_id', onDelete: 'NO ACTION', onUpdate: 'NO ACTION', });
class Heytrip {
/ * *
* 搜索酒店
* /
hotelSearch = async ( keyword , options ) => {
const keywordSplit = keyword . split ( ' ' ) ;
const keywordWhere = ( field ) => keywordSplit . map ( ( word ) => Sequelize . where ( Sequelize . fn ( 'instr' , Sequelize . col ( field ) , word ) , { [ Op . gt ] : 0 } ) ) ;
// 'hotelinfo.address'
const keywordSearch = [ 'hotelinfo.hotel_name' ] . map ( ( field ) => ( { [ Op . and ] : keywordWhere ( field ) } ) ) ;
const keywordOrder = [ 'hotelinfo.hotel_name' ] . reduce ( ( ro , field ) => ro . concat ( keywordSplit . map ( ( word ) => Sequelize . fn ( 'instr' , Sequelize . col ( field ) , word ) ) ) , [ ] ) ;
// const keywordSearchCount = ['hi2_hotel_name', 'hi2_address'].map((field) => ({ [Op.and]: keywordWhere(field) }));
// const countRows = await Hotelinfo2.count({
// where: {
// [Op.or]: keywordSearchCount,
// },
// // distinct: true,
// group: ['hotel_id'],
// });
// const count = countRows.length;
// const findIds = countRows.map((item) => item.hotel_id);
const { count , rows } = await Hotelinfo . findAndCountAll ( {
// const [ rows ] = await Hotelinfo.findAll({
include : [
{ model : HeytripIds , as : 'aid' , attributes : [ 'hotel_id' , 'update_flag' ] , where : { update _flag : { [ Op . ne ] : 99 } } } ,
{
model : Hotelinfo2 ,
as : 'locale_info' ,
attributes : [
[ 'hi2_sn' , 'sn' ] ,
'hotel_id' ,
'lgc' ,
'locale' ,
[ 'hi2_hotel_name' , 'hotel_name' ] ,
[ 'hi2_address' , 'address' ] ,
[ 'hi2_description' , 'description' ] ,
[ 'h2_review_desc' , 'review_desc' ] ,
[ 'h2_location_highlight_desc' , 'location_highlight_desc' ] ,
] ,
required : false ,
separate : true ,
} ,
{ model : City , as : 'city' , attributes : [ 'id' , 'name' ] , where : { lgc : 2 } , required : false , separate : true } ,
{ model : Country , as : 'country' , attributes : [ 'id' , 'name' ] , where : { lgc : 2 } , required : false , separate : true } ,
] ,
// include: [{ model: Hotelinfo2, as: 'h2', required: true }],
where : {
[ Op . or ] : keywordSearch ,
// hotel_id: findIds,
} ,
order : keywordOrder ,
... options ,
// raw: true,
nest : true ,
} ) ;
return { count , rows : rows . map ( ( item ) => item . dataValues ) } ;
// return { count, rows };
} ;
getLastPageIndex = async ( where = { } ) => {
const ret = await HeytripIds . max ( 'page_index' , { where } ) ;
return ret ;
} ;
getFirstPageIndex = async ( where = { } ) => {
const ret = await HeytripIds . min ( 'page_index' , { where } ) ;
return ret ;
} ;
syncAids = async ( ) => {
const lastPageIndex = await this . getLastPageIndex ( ) ;
const pageIndex = lastPageIndex + 1 ;
const ids = await AvailableAccommodationIds ( pageIndex ) ;
if ( isEmpty ( ids ) ) {
return {
nextPage : false ,
pageIndex ,
} ;
}
const insertRows = ids . map ( ( id ) => ( { hotel _id : id , page _index : pageIndex } ) ) ;
await HeytripIds . bulkCreate ( insertRows ) ;
return {
nextPage : true ,
pageIndex ,
} ;
} ;
syncAidState = async ( ) => {
const today = new Date ( ) ;
today . setHours ( 0 , 0 , 0 , 0 ) ; // set the time to 00:00:00.000
let lastPageIndex ; let pageIndex ;
lastPageIndex = await this . getFirstPageIndex ( { last _modify _time : { [ Op . lt ] : today } } ) ;
pageIndex = lastPageIndex ;
if ( isEmpty ( lastPageIndex ) ) {
lastPageIndex = await this . getLastPageIndex ( { last _modify _time : { [ Op . gt ] : today } , page _index : { [ Op . lt ] : 9999 } } ) ;
pageIndex = lastPageIndex + 1 ;
}
console . log ( 'syncAidState' , lastPageIndex , pageIndex ) ;
const validIds = ( await AvailableAccommodationIds ( pageIndex ) ) . map ( ( id ) => String ( id ) ) ;
if ( isEmpty ( validIds ) ) {
// await HeytripIds.update({ update_flag: 99, priority: 99 }, { where: { pageIndex: [Op.gt]: pageIndex } });
// 同步结束; 本次没有的ID: 更新: 99=失效
const stateOff = await HeytripIds . findAll ( {
raw : true ,
where : {
update _flag : { [ Op . notIn ] : [ 0 , 1 ] } ,
last _modify _time : { [ Op . gt ] : today } ,
} ,
attributes : [ 'hotel_id' ] ,
} ) ;
const stateOffIds = stateOff . map ( ( item ) => item . hotel _id ) ;
if ( ! isEmpty ( stateOffIds ) ) {
await HeytripIds . update ( { update _flag : 99 , priority : 99 } , { where : { hotel _id : stateOffIds } } ) ;
console . log ( 'updated stateOff' , stateOffIds . length ) ;
}
return {
nextPage : false ,
} ;
}
const savedIds = await HeytripIds . findAll ( {
raw : true ,
where : { hotel _id : validIds } ,
attributes : [ 'hotel_id' ] ,
logging : false ,
} ) ; // savedIds <= validIds
const savedPageIds = await HeytripIds . findAll ( {
raw : true ,
where : { page _index : pageIndex , } ,
attributes : [ 'hotel_id' ] ,
// logging: false,
} ) ;
// 已存在ID: 更新: 状态, 页码, 时间
const stateNormal = savedIds . filter ( ( item ) => validIds . includes ( ( item . hotel _id ) ) ) . map ( ( item ) => item . hotel _id ) ;
if ( ! isEmpty ( stateNormal ) ) {
await HeytripIds . update ( { update _flag : 0 , page _index : pageIndex , last _modify _time : Sequelize . fn ( 'NOW' ) } , { where : { hotel _id : stateNormal } } ) ;
}
const newIds = validIds . filter ( ( id ) => ! savedIds . map ( ( item ) => item . hotel _id ) . includes ( id ) ) ;
if ( ! isEmpty ( newIds ) ) {
const insertRows = newIds . map ( ( id ) => ( { hotel _id : id , page _index : pageIndex , update _flag : 1 } ) ) ;
await HeytripIds . bulkCreate ( insertRows ) ;
}
const oldToNext = savedPageIds . filter ( ( item ) => ! validIds . includes ( ( item . hotel _id ) ) ) . map ( ( item ) => item . hotel _id ) ;
if ( ! isEmpty ( oldToNext ) ) {
await HeytripIds . update ( { page _index : Number ( pageIndex ) + 9999 , update _flag : 99 } , { where : { hotel _id : oldToNext } } ) ;
}
return {
nextPage : true ,
pageIndex ,
} ;
}
newHotels = async ( ) => {
const [ rows ] = await Sequelize . query (
'SELECT i.hotel_id, IFNULL(h.hi_sn ,0) info_exists FROM heytrip_ids as i LEFT JOIN hotelinfo AS h ON h.hotel_id = i.hotel_id WHERE h.hi_sn IS NULL AND update_flag != 0 ORDER BY info_exists LIMIT 10'
) ;
return rows ;
} ;
newHotelsLgc = async ( lgc ) => {
const [ rows ] = await Sequelize . query (
` SELECT i.hotel_id ,IFNULL(h.hi2_sn, 0) info_exists
FROM heytrip _ids AS i
LEFT JOIN hotelinfo2 AS h ON h . hotel _id = i . hotel _id
AND h . lgc = $ { lgc }
WHERE h . hi2 _sn IS NULL
AND update _flag != 99
ORDER BY info _exists LIMIT 10 `
, { logging : false }
) ;
const res = await this . syncInitHotelLgcDetailsAction ( rows , LGC _MAPPED [ lgc ] ) ;
return res ;
} ;
chinaHotels = async ( ) => {
const [ rows ] = await Sequelize . query (
'SELECT i.hotel_id, IFNULL(h.hi_sn ,0) info_exists FROM heytrip_ids as i LEFT JOIN hotelinfo AS h ON h.hotel_id = i.hotel_id WHERE h.hi_sn IS NULL AND update_flag != 0 AND i.hotel_id > 20000000 ORDER BY info_exists LIMIT 10'
) ;
const res = await this . syncInitHotelDetailsAction ( rows , LGC _MAPPED [ '2' ] ) ;
return res ;
} ;
chinaHotelsLgc2 = async ( lgc ) => {
// const [rows] = await Sequelize.query(
// `SELECT h.hotel_id
// FROM hotelinfo AS h
// LEFT JOIN hotelinfo2 AS h2 ON h.hotel_id =h2.hotel_id
// AND h2.lgc = ${lgc}
// WHERE h2.hi2_sn IS NULL LIMIT 10`
// );
const [ rows ] = await Sequelize . query (
` SELECT i.hotel_id ,IFNULL(h.hi2_sn, 0) info_exists
FROM heytrip _ids AS i
INNER JOIN hotelinfo AS h1 ON h1 . hotel _id = i . hotel _id
LEFT JOIN hotelinfo2 AS h ON h . hotel _id = i . hotel _id
AND h . lgc = $ { lgc }
WHERE h . hi2 _sn IS NULL
AND update _flag != 99
AND h1 . country _code = 'CN'
-- AND i . hotel _id > 20000000
ORDER BY info _exists LIMIT 10 `
) ;
const res = await this . syncInitHotelLgcDetailsAction ( rows , LGC _MAPPED [ lgc ] ) ;
return res ;
} ;
/ * *
* @ deprecated
* /
syncInitHotelDetailsAction = async ( rows , lgcObj = LGC _MAPPED [ DEFAULT _LGC ] ) => {
let allIds = [ ] ;
try {
allIds = rows . map ( ( item ) => item . hotel _id ) ;
const updateIds = rows . filter ( ( item ) => item . info _exists !== 0 ) . map ( ( item ) => item . hotel _id ) ;
const newIds = rows . filter ( ( item ) => item . info _exists === 0 ) . map ( ( item ) => item . hotel _id ) ;
if ( isEmpty ( rows ) ) {
return { next : ! isEmpty ( allIds ) , data : allIds } ;
}
const res = await AccommodationsDetails ( {
Language : lgcObj . locale ,
AccommodationIds : allIds ,
} ) ;
const resIds = res . map ( ( item ) => item . HotelId ) ;
// hotel info
const insertData = extractDetails ( res , lgcObj ) ;
// return insertData; // debug: 0
/** 开始Database */
const result = await Sequelize . transaction ( async ( transaction ) => {
const sequelizeOptions = { logging : false , transaction } ;
let Info ;
if ( ! isEmpty ( insertData . info ) ) Info = await Hotelinfo . bulkCreate ( insertData . info , sequelizeOptions ) ;
if ( ! isEmpty ( insertData . info2 ) ) await Hotelinfo2 . bulkCreate ( insertData . info2 , sequelizeOptions ) ;
if ( ! isEmpty ( insertData . rooms ) ) await Rooms . bulkCreate ( insertData . rooms , sequelizeOptions ) ;
if ( ! isEmpty ( insertData . images ) ) await Images . bulkCreate ( insertData . images , sequelizeOptions ) ;
if ( ! isEmpty ( insertData . facility ) ) await Facility . bulkCreate ( insertData . facility , sequelizeOptions ) ;
if ( ! isEmpty ( insertData . infos ) ) await Informations . bulkCreate ( insertData . infos , sequelizeOptions ) ;
if ( ! isEmpty ( insertData . reviews ) ) await Reviews . bulkCreate ( insertData . reviews , sequelizeOptions ) ;
if ( ! isEmpty ( insertData . reviews _summaries ) ) await ReviewsSummaries . bulkCreate ( insertData . reviews _summaries , sequelizeOptions ) ;
if ( ! isEmpty ( insertData . locations ) ) await Locations . bulkCreate ( insertData . locations , sequelizeOptions ) ;
if ( ! isEmpty ( allIds ) ) await HeytripIds . update ( { update _flag : 0 } , { where : { hotel _id : allIds } } ) ;
return Info ;
} ) ;
return { next : ! isEmpty ( allIds ) , data : allIds } ;
} catch ( error ) {
console . log ( error ) ;
return { next : false , data : allIds } ;
}
} ;
syncInitHotelLgcDetailsAction = async ( rows , lgcObj = LGC _MAPPED [ DEFAULT _LGC ] ) => {
let allIds = [ ] ;
try {
allIds = rows . map ( ( item ) => item . hotel _id ) ;
if ( isEmpty ( rows ) ) {
return { next : ! isEmpty ( allIds ) , data : allIds } ;
}
const _BaseInfoExists = await Hotelinfo . findAll ( { where : { hotel _id : allIds } , logging : false } ) ;
const updateIds = _BaseInfoExists . map ( ( item ) => ` ${ item . hotel _id } ` ) ;
// console.log('updateIds', updateIds);
const res = await AccommodationsDetails ( {
Language : lgcObj . locale ,
AccommodationIds : allIds ,
} ) ;
// hotel info
const insertData = resolveDetails ( res , lgcObj ) ;
// return insertData; // debug: 0
const resIds = insertData . info . map ( ( item ) => item . hotel _id ) ;
/** 开始Database */
const result = await Sequelize . transaction ( async ( transaction ) => {
const sequelizeOptions = { logging : false , transaction } ;
let Info ;
const offInfo = allIds . filter ( ( iitem ) => ! resIds . includes ( ` ${ iitem } ` ) ) ;
if ( ! isEmpty ( offInfo ) ) {
await HeytripIds . update ( { update _flag : 99 } , { where : { hotel _id : offInfo } , ... sequelizeOptions } ) ;
}
const newInfo = insertData . info . filter ( ( iitem ) => ! updateIds . includes ( ` ${ iitem . hotel _id } ` ) ) ;
// console.log(
// 'newInfo',
// newInfo.map((xx) => xx.hotel_id)
// );
if ( ! isEmpty ( newInfo ) ) Info = await Hotelinfo . bulkCreate ( newInfo , sequelizeOptions ) ;
if ( ! isEmpty ( insertData . info2 ) ) await Hotelinfo2 . bulkCreate ( insertData . info2 , sequelizeOptions ) ;
if ( ! isEmpty ( insertData . rooms ) ) await Rooms . bulkCreate ( insertData . rooms , sequelizeOptions ) ;
if ( ! isEmpty ( insertData . images ) ) await Images . bulkCreate ( insertData . images , sequelizeOptions ) ;
if ( ! isEmpty ( insertData . facility ) ) await Facility . bulkCreate ( insertData . facility , sequelizeOptions ) ;
if ( ! isEmpty ( insertData . infos ) ) await Informations . bulkCreate ( insertData . infos , sequelizeOptions ) ;
if ( ! isEmpty ( insertData . reviews ) ) await Reviews . bulkCreate ( insertData . reviews , sequelizeOptions ) ;
if ( ! isEmpty ( insertData . reviews _summaries ) ) await ReviewsSummaries . bulkCreate ( insertData . reviews _summaries , sequelizeOptions ) ;
if ( ! isEmpty ( insertData . locations ) ) await Locations . bulkCreate ( insertData . locations , sequelizeOptions ) ;
if ( ! isEmpty ( newInfo ) ) await HeytripIds . update ( { update _flag : 0 } , { where : { hotel _id : newInfo . map ( ( x ) => x . hotel _id ) } , ... sequelizeOptions } ) ;
return Info ;
} ) ;
return { next : ! isEmpty ( allIds ) , data : allIds } ;
} catch ( error ) {
console . log ( error ) ;
return { next : false , data : allIds } ;
}
} ;
getHotelAvailability = async ( param ) => {
const { hotel _id , checkin , checkout , adults , children _ages , rooms , nationality } = param ;
const paramBody = {
Language : 'en-US' ,
// Language: 'zh-CN',
AccommodationIds : [ Number ( hotel _id ) ] ,
CheckInDate : checkin ,
CheckOutDate : checkout ,
Nationality : nationality || 'CN' , // 默认取中国报价
NumberOfAdults : adults || 1 ,
ChildrenAges : children _ages || null , // 入住每个儿童年龄 [6,8]
// ChildrenAges: null,
NumberOfRooms : rooms || 1 ,
Currency : 'CNY' ,
} ;
const _quoteRes = await Availability ( paramBody ) ;
const quoteRes = resolveRatePlans ( _quoteRes ) ;
return quoteRes ;
} ;
writeHeytripRequestLog = async ( { action , body } ) => {
const actionMapped = {
'getHotelAvailability' : '/Accommodation/Availability' ,
'getHotelInfo' : '/Accommodation/AccommodationsDetails' ,
'getHotelIds' : '/Accommodation/AvailableAccommodationIds' ,
} ;
const data = {
action : action ,
// method: ctx.method,
path : actionMapped [ action ] ,
request _data : body ,
// ip: ctx.ip,
} ;
return await Logs . create ( data , { logging : false } ) ;
} ;
}
module . exports = new Heytrip ( ) ;