diff --git a/app/_i18n/i18n.properties b/app/_i18n/i18n.properties index a2c38a4..51ad8d1 100644 --- a/app/_i18n/i18n.properties +++ b/app/_i18n/i18n.properties @@ -7,6 +7,7 @@ ArrivalTime=Arrival Time BeginDate=Starting Date Booking=Booking BookingDate=Booking Date +Status.code=Status BookingFee=Booking Fee BookingID=Booking ID BookingSupplement=Booking Supplement @@ -50,4 +51,5 @@ Travel=Travel TravelAgency=Travel Agency Travels=Travels TravelStatus=Travel Status +Status=Booking Status WebAddress=Web Address diff --git a/app/_i18n/i18n_de.properties b/app/_i18n/i18n_de.properties index 15448f4..94fb764 100644 --- a/app/_i18n/i18n_de.properties +++ b/app/_i18n/i18n_de.properties @@ -50,4 +50,5 @@ Travel=Reise TravelAgency=Veranstalter Travels=Reisen TravelStatus=Reisestatus +Status=Buchungsstatus WebAddress=Web-Adresse diff --git a/app/_i18n/i18n_en.properties b/app/_i18n/i18n_en.properties index a2c38a4..be7a0f4 100644 --- a/app/_i18n/i18n_en.properties +++ b/app/_i18n/i18n_en.properties @@ -50,4 +50,5 @@ Travel=Travel TravelAgency=Travel Agency Travels=Travels TravelStatus=Travel Status +Status=Booking Status WebAddress=Web Address diff --git a/app/_i18n/i18n_fr.properties b/app/_i18n/i18n_fr.properties index 35086d9..d036d60 100644 --- a/app/_i18n/i18n_fr.properties +++ b/app/_i18n/i18n_fr.properties @@ -50,4 +50,5 @@ Travel=Voyage TravelAgency=Agence de voyage Travels=Voyages TravelStatus=Statut du voyage +Status=Statut de la réservation WebAddress=Adresse e-mail diff --git a/app/travels/layouts.cds b/app/travels/layouts.cds index 87bd51c..dcc8b91 100644 --- a/app/travels/layouts.cds +++ b/app/travels/layouts.cds @@ -112,6 +112,13 @@ annotate TravelService.Bookings with @UI : { { Value : Flight.destination, Label : '{i18n>Destination}', @HTML5.CssDefaults: {width:'19em'}, @Common.FieldControl: #ReadOnly }, { Value : Flight.airline, Label : '{i18n>Airline}', @HTML5.CssDefaults: {width:'11em'}, @Common.FieldControl: #ReadOnly }, { Value : BookingDate, Label : '{i18n>BookingDate}' }, + { Value : Status.code, Label : '{i18n>Status}', @UI.Importance : #High, @HTML5.CssDefaults: {width:'10em'}, + Criticality : ( + Status.code == #Confirmed ? 3 : + Status.code == #Failed ? 1 : + 0 + ), + }, ], Facets : [{ @@ -140,6 +147,13 @@ annotate TravelService.Bookings with @UI : { { Value : Pos }, { Value : BookingDate }, { Value : Travel.Customer.ID }, + { Value : Status.code, Label : '{i18n>Status}', + Criticality : ( + Status.code == #Confirmed ? 3 : + Status.code == #Failed ? 1 : + 0 + ), + }, ]}, FieldGroup #Flight: {Data: [ diff --git a/package.json b/package.json index cd633a7..b7e8d0d 100644 --- a/package.json +++ b/package.json @@ -3,8 +3,9 @@ "version": "1.0.1", "description": "CAP XTravels sample application", "scripts": { + "start": "cds watch --port 4004", + "reset": "cds deploy && cds watch --port 4004", "lint": "npx eslint .", - "start": "cds-serve", "test": "cds test" }, "dependencies": { @@ -19,7 +20,16 @@ }, "cds": { "requires": { - "messaging": true + "messaging": true, + "queue": { + "maxAttempts": 6 + } + }, + "log": { + "levels": { + "odata": "error", + "cds": "debug" + } } }, "license": "Apache-2.0" diff --git a/srv/data-federation.js b/srv/data-federation.js index 949a97f..e05c696 100644 --- a/srv/data-federation.js +++ b/srv/data-federation.js @@ -1,5 +1,6 @@ const cds = require ('@sap/cds') const feed = [] +const LOG = cds.log() // Collect all entities to be federated, and prepare replica tables cds.on ('loaded', csn => { @@ -18,7 +19,10 @@ cds.on ('loaded', csn => { cds.once ('served', () => Promise.all (feed.map (async each => { const srv = await cds.connect.to (each.remote) srv._once ??=!! srv.on ('replicate', replicate) - await srv.schedule ('replicate', each) .as (`replicate ${each.entity}`) .every ('10 minutes') + await srv + .schedule ('replicate', each) + .as (`replicate ${each.entity}`) + .every ('10 minutes') }))) // Event handler for replicating single entities @@ -27,7 +31,7 @@ async function replicate (req) { let { latest } = await SELECT.one `max(modifiedAt) as latest` .from (entity) let rows = await remote.read (entity) .where `modifiedAt > ${latest||0}` if (rows.length) await UPSERT (rows) .into (entity); else return - console.log ('Replicated', rows.length, 'entries', { for: entity, via: this.kind }) // eslint-disable-line no-console + LOG.info('Replicated', rows.length, 'entries', { for: entity, via: this.kind }) // eslint-disable-line no-console } // Helpers to identify remote services, and check whether they are connected diff --git a/srv/travel-service/service.js b/srv/travel-service/service.js index a0585e8..bac0586 100644 --- a/srv/travel-service/service.js +++ b/srv/travel-service/service.js @@ -17,6 +17,7 @@ class TravelService extends cds.ApplicationService { * Integrates with the XFlights service to keep Flights data in sync on both sides. */ async service_integration() { + const LOG = cds.log() const s4 = await cds.connect.to ('sap.capire.s4.business-partner') const xflights = await cds.connect.to ('FlightsService') @@ -33,6 +34,7 @@ class TravelService extends cds.ApplicationService { return Promise.all (Bookings.map (booking => { let { Flight_ID: flight, Flight_date: date, Travel_ID, Pos } = booking // Transport Travel_ID, Pos to callback via headers + LOG.info(`[${1}] Emit BookingCreated for Flight ${flight}`) return yfligths.emit ('BookingCreated', { flight, date }, { Travel_ID, Pos }) })) }) @@ -40,18 +42,21 @@ class TravelService extends cds.ApplicationService { // Set booking status in callback of outboxed BookingCreated event xflights.after('BookingCreated/#succeeded', async function(_, req) { const { Travel_ID, Pos } = req.headers + LOG.info(`[${4}] Booking Succeeded: Update Booking Status for ${Travel_ID} to 'C'`) await UPDATE(Bookings, { Travel_ID, Pos }).set({ Status_code: 'C' }) }) xflights.after('BookingCreated/#failed', async function(err, req) { const { Travel_ID, Pos } = req.headers + LOG.info(`[${4}] Booking Failed: Update Booking Status for ${Travel_ID} to 'F'`) await UPDATE(Bookings, { Travel_ID, Pos }).set({ Status_code: 'F' }) }) // Update local Flights data whenever occupied seats change in XFlights if (Flights['@cds.persistence.table']) xflights.on ('FlightsUpdated', async function(msg) { const { flight:ID, date } = msg.data - const free_seats = await this.read(Flights, { ID, date }).columns('free_seats') - await UPDATE (Flights, { ID, date }) .with (free_seats) + const { free_seats } = await this.read(Flights, { ID, date }).columns('free_seats') + LOG.info(`[${5}] Update Flight ${ID} free seats to ${free_seats}`) + await UPDATE (Flights, { ID, date }) .with ({ free_seats }) }) } diff --git a/test/saga.http b/test/saga.http index a7cd2c2..2476ca2 100644 --- a/test/saga.http +++ b/test/saga.http @@ -1,12 +1,10 @@ @service = http://localhost:4004/odata/v4/travel ### - -GET {{service}}/Flights(ID='GA0322',date='2023-08-05') +GET {{service}}/Flights(ID='GA0322',date='2023-08-05') Authorization: Basic alice: ### - POST {{service}}/Travels Authorization: Basic alice: Content-Type: application/json @@ -28,3 +26,29 @@ Content-Type: application/json } ] } + + +### Fully Booked + +POST {{service}}/Travels +Authorization: Basic alice: +Content-Type: application/json + +{ + "ID": 4190, + "Description": "Test Travel Fully Booked", + "BeginDate": "2020-01-01", + "EndDate": "2030-12-31", + "Agency_ID": "070007", + "Customer_ID": "000001", + "Currency_code": "EUR", + "Bookings": [ + { + "Pos": 1, + "Flight_ID": "GA0322", + "Flight_date": "2023-08-05", + "BookingDate": "2022-08-05", + "Currency_code": "EUR" + } + ] +}