diff --git a/package-lock.json b/package-lock.json index d389822e..ba0f31b0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "uapi-json", - "version": "1.17.3", + "version": "1.17.4", "lockfileVersion": 3, "requires": true, "packages": { diff --git a/package.json b/package.json index bf2f56f5..eb0eec96 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "uapi-json", - "version": "1.17.3", + "version": "1.17.4", "description": "Travelport Universal API", "main": "src/", "files": [ diff --git a/src/Services/Air/AirParser.js b/src/Services/Air/AirParser.js index 4baafd43..983d3381 100644 --- a/src/Services/Air/AirParser.js +++ b/src/Services/Air/AirParser.js @@ -585,9 +585,8 @@ function getTicketFromEtr(etr, obj, allowNoProviderLocatorCodeRetrieval = false) const exchangedTickets = []; const allCoupons = ticketsList.map((ticket) => { - return Object.entries(ticket['air:Coupon']).map(([couponKey, coupon]) => { + return Object.values(ticket['air:Coupon']).map((coupon) => { return { - key: couponKey, ticketNumber: ticket.TicketNumber, couponNumber: coupon.CouponNumber, from: coupon.Origin, @@ -607,59 +606,54 @@ function getTicketFromEtr(etr, obj, allowNoProviderLocatorCodeRetrieval = false) return all.concat(nextChunk); }, []); - const tickets = ticketsList.map( - (ticket) => { - if (ticket['air:ExchangedTicketInfo']) { - ticket['air:ExchangedTicketInfo'].forEach( - (t) => exchangedTickets.push(t.Number) - ); - } + // Filling exchanged tickets array + ticketsList.forEach((ticket) => { + if (ticket['air:ExchangedTicketInfo']) { + ticket['air:ExchangedTicketInfo'].forEach( + (t) => exchangedTickets.push(t.Number) + ); + } + }); - const coupons = Object.keys(ticket['air:Coupon']).map( - (couponKey) => { - const allCouponsIndex = allCoupons.findIndex((ac) => ac.key === couponKey); - const coupon = allCoupons[allCouponsIndex]; - const nextCoupon = allCoupons[allCouponsIndex + 1]; - - let bookingInfo = null; - // looking for fareInfo by it's fareBasis - // and for bookingInfo by correct FareInfoRef - if (airPricingInfo && airPricingInfo['air:FareInfo']) { - Object.keys(airPricingInfo['air:FareInfo']).forEach( - (fareKey) => { - const fare = airPricingInfo['air:FareInfo'][fareKey]; - if (fare.FareBasis === coupon.FareBasis - && airPricingInfo['air:BookingInfo']) { - const bInfo = airPricingInfo['air:BookingInfo'].find( - (info) => info.FareInfoRef === fareKey - ); - - if (bInfo) { - bookingInfo = bInfo; - } - } - } + // Getting ticket coupons + const ticketsObject = allCoupons.reduce((acc, coupon, index) => { + const { ticketNumber } = coupon; + const ticket = acc[ticketNumber] || { ticketNumber, coupons: [] }; + const { coupons } = ticket; + const nextCoupon = allCoupons[index + 1]; + + let bookingInfo = null; + // looking for fareInfo by it's fareBasis + // and for bookingInfo by correct FareInfoRef + if (airPricingInfo && airPricingInfo['air:FareInfo']) { + Object.keys(airPricingInfo['air:FareInfo']).forEach( + (fareKey) => { + const fare = airPricingInfo['air:FareInfo'][fareKey]; + if (fare.FareBasis === coupon.FareBasis && airPricingInfo['air:BookingInfo']) { + const bInfo = airPricingInfo['air:BookingInfo'].find( + (info) => info.FareInfoRef === fareKey ); - } - return { - ...coupon, - stopover: ( - nextCoupon - ? nextCoupon.stopover - : true - ), - ...(bookingInfo !== null ? { serviceClass: bookingInfo.CabinClass } : null) - }; + if (bInfo) { + bookingInfo = bInfo; + } + } } ); - - return { - ticketNumber: ticket.TicketNumber, - coupons, - }; } - ); + const couponData = { + ...coupon, + stopover: ( + nextCoupon + ? nextCoupon.stopover + : true + ), + ...(bookingInfo !== null ? { serviceClass: bookingInfo.CabinClass } : null) + }; + return { ...acc, [ticketNumber]: { ...ticket, coupons: [...coupons, couponData] } }; + }, {}); + + const tickets = Object.values(ticketsObject); const taxes = (airPricingInfo && airPricingInfo['air:TaxInfo']) ? Object.keys(airPricingInfo['air:TaxInfo']).map( diff --git a/test/Air/AirParser.test.js b/test/Air/AirParser.test.js index 50483be5..e655db2c 100644 --- a/test/Air/AirParser.test.js +++ b/test/Air/AirParser.test.js @@ -311,7 +311,8 @@ describe('#AirParser', () => { }); describe('getTicket', () => { - function testTicket(result) { + function testTicket(result, options = {}) { + const { allowNoProviderLocatorCodeRetrieval = false } = options; expect(result).to.be.an('object'); expect(result).to.include.all.keys([ 'uapi_ur_locator', 'uapi_reservation_locator', 'pnr', 'ticketNumber', @@ -330,9 +331,11 @@ describe('#AirParser', () => { } ); } - expect(result.uapi_ur_locator).to.match(pnrRegExp); - expect(result.uapi_reservation_locator).to.match(pnrRegExp); - expect(result.pnr).to.match(pnrRegExp); + if (!allowNoProviderLocatorCodeRetrieval) { + expect(result.uapi_ur_locator).to.match(pnrRegExp); + expect(result.uapi_reservation_locator).to.match(pnrRegExp); + expect(result.pnr).to.match(pnrRegExp); + } expect(result.ticketNumber).to.match(ticketRegExp); expect(result.platingCarrier).to.match(/^[A-Z0-9]{2}$/i); expect(result.ticketingPcc).to.match(/^[A-Z0-9]{3,4}$/i); @@ -494,6 +497,23 @@ describe('#AirParser', () => { }); }); + it('should parse conjunction ticket with 3 parts and missing data', async () => { + const uParser = new Parser('air:AirRetrieveDocumentRsp', 'v52_0', {}); + const parseFunction = airParser.AIR_GET_TICKET; + const xml = fs.readFileSync(`${xmlFolder}/get-ticket-conjunction.xml`).toString(); + const json = await uParser.parse(xml); + const parsingOptions = { allowNoProviderLocatorCodeRetrieval: true }; + const result = parseFunction.call(uParser, json, parsingOptions); + + testTicket(result, parsingOptions); + result.tickets.forEach((ticket) => { + const { ticketNumber, coupons } = ticket; + coupons.forEach((coupon) => { + expect(coupon.ticketNumber).to.equal(ticketNumber); + }); + }); + }); + it('should parse exchanged conjunction ticket', () => { const uParser = new Parser('air:AirRetrieveDocumentRsp', 'v52_0', {}); const parseFunction = airParser.AIR_GET_TICKET; @@ -503,15 +523,14 @@ describe('#AirParser', () => { .then((json) => parseFunction.call(uParser, json)) .then((result) => { testTicket(result); - const couponsStopover = [false, true, false, true]; + const expectedStopovers = [false, true, false, true]; const coupons = result.tickets.reduce( (acc, ticket) => acc.concat(ticket.coupons), [] ); console.log(coupons.map((c) => c.stopover)); - coupons.forEach((coupon, index) => { - expect(coupon.stopover).to.be.equal(couponsStopover[index]); - }); + const parsedStopovers = coupons.flatMap((coupon) => coupon.stopover); + expect(parsedStopovers).to.deep.equal(expectedStopovers); expect(result.priceInfoDetailsAvailable).to.equal(true); expect(result.exchangedTickets).to.have.length.above(0); }); @@ -720,7 +739,7 @@ describe('#AirParser', () => { expect(coupon).to.have.all.keys([ 'couponNumber', 'from', 'to', 'departure', 'airline', 'flightNumber', 'fareBasisCode', 'status', 'notValidBefore', 'notValidAfter', 'bookingClass', 'stopover', - 'key', 'ticketNumber', + 'ticketNumber', ]); expect(coupon.couponNumber).to.match(/\d+/i); expect(coupon.from).to.match(/[A-Z]{3}/i); @@ -789,7 +808,6 @@ describe('#AirParser', () => { ticketNumber: '0809903654876', coupons: [ { - key: 'SGNQhOBAAA/Bn2KYVCAAAA==', ticketNumber: '0809903654876', couponNumber: '1', from: 'KBP', diff --git a/test/FakeResponses/Air/get-ticket-conjunction.xml b/test/FakeResponses/Air/get-ticket-conjunction.xml new file mode 100644 index 00000000..7d56cb54 --- /dev/null +++ b/test/FakeResponses/Air/get-ticket-conjunction.xml @@ -0,0 +1,48 @@ + + + + + + + + + + WAW LO X/FRA VN SGN M/IT //HAN VN LPQ VN REP//PNH VN X/HAN VN X/LON LO WAW M/IT END ROE3.866426 XT 42.77DE109.71RA7.66C495.79JC57.47LA72.80O6114.95KX19.16L538.49T870.31UB1488.44YQ + + + + + + + + + + + + + + + + + + + + + + + + + + + WAW LO X/FRA VN SGN M/IT //HAN VN LPQ VN REP//PNH VN X/HAN VN X/LON LO WAW M/IT END ROE3.866426 XT 42.77DE109.71RA7.66C495.79JC57.47LA72.80O6114.95KX19.16L538.49T870.31UB1488.44YQ + + + + + + + + \ No newline at end of file