Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cds
Submodule cds added at 727be9
3 changes: 3 additions & 0 deletions db/schema.cds
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,10 @@ entity TravelStatus : sap.common.CodeList {
Open = 'O';
Accepted = 'A';
Canceled = 'X';

Rejected = 'R';
}
}


type Price : Decimal(9,4);
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,16 @@
"@capire/common": "*",
"@capire/xflights-data": "*",
"@sap-cloud-sdk/http-client": "^4",
"@sap/cds": ">=9",
"@sap/cds": "*",
"express": "^4"
},
"devDependencies": {
"@cap-js/xsrv-federation": ".plugins/fed-xrv",
"@cap-js/cds-test": "*",
"@cap-js/sqlite": ">=2"
},
"workspaces": [
"cds"
],
"license": "Apache-2.0"
}
20 changes: 19 additions & 1 deletion srv/travel-service.cds
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,29 @@ service TravelService {
{ grant: ['*'], to: 'processor'},
{ grant: ['*'], to: 'admin'}
])
entity Travels as projection on db.Travels actions {
entity Travels as projection on db.Travels
actions {
action createTravelByTemplate() returns Travels;
action rejectTravel();
action acceptTravel();
action deductDiscount( percent: Percentage not null ) returns Travels;

action reopenTravel();
}

annotate Travels with @flow.status: Status actions {
NEW /* @from: [ null ] */ @to: /* #Draft */ #Open;
SAVE /* @from: [ #Draft ] */ @to: #Open;
// cancel @from: [ #Open ] @to: #Canceled;
rejectTravel @from: [ #Open ] @to: #Rejected;
acceptTravel @from: [ #Open ] @to: #Accepted;
// close @from: [ #Accepted ] @to: #Closed;
EDIT @from: [ #Accepted, #Rejected ] @to: /* #Draft */ #Open;

reopenTravel @from: [ #Accepted, #Rejected ] @to: $flow.previous;
PATCH /* @from: [ #Open ] */ @to: #Rejected;
};

// Also expose Flights and Currencies for travel booking UIs and Value Helps
@readonly entity Flights as projection on db.masterdata.Flights;
@readonly entity Supplements as projection on db.masterdata.Supplements;
Expand All @@ -23,6 +39,7 @@ service TravelService {
// Export functions to export download travel data
function exportJSON() returns LargeBinary @Core.MediaType:'application/json';
function exportCSV() returns LargeBinary @Core.MediaType:'text/csv';

}


Expand All @@ -41,4 +58,5 @@ entity TravelsExport @cds.persistence.skip as projection on db.Travels {
Description
}


type Percentage : Integer @assert.range: [1,100];
85 changes: 85 additions & 0 deletions test/flows.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
const cds = require('@sap/cds')

const { GET, POST, PATCH, DELETE, axios, expect } = cds.test(__dirname + '/..', '--with-mocks')
axios.defaults.auth = { username: 'alice', password: 'admin' }
axios.defaults.validateStatus = () => true

describe('Status Transition Flows', () => {
const READ = async (ID, IsActiveEntity = true) => {
const { data: travel } = await GET(`/odata/v4/travel/Travels(ID=${ID},IsActiveEntity=${IsActiveEntity})`)
if (IsActiveEntity)
travel.transitions_ = await SELECT('sap.capire.travels.Travels.transitions_').where({ up__ID: ID })
return travel
}

beforeEach(async () => {
await cds.ql.DELETE('sap.capire.travels.Travels.transitions_')
})

it('flows like a charm', async () => {
let travel

travel = await READ(1)
expect(travel.Status_code).to.eql('O')
expect(travel.transitions_).to.have.length(0)

await POST('/odata/v4/travel/Travels(ID=1,IsActiveEntity=true)/acceptTravel', {})
travel = await READ(1)
expect(travel.Status_code).to.eql('A')
expect(travel.transitions_).to.have.length(1)

await POST('/odata/v4/travel/Travels(ID=1,IsActiveEntity=true)/draftEdit', {})
travel = await READ(1, false)
expect(travel.Status_code).to.eql('O')

const res = await GET('/odata/v4/travel/Travels(ID=1,IsActiveEntity=false)?$expand=transitions_')
expect(res.status).to.eql(400)

await PATCH('/odata/v4/travel/Travels(ID=1,IsActiveEntity=false)', { Description: 'foo' })
travel = await READ(1, false)
expect(travel.Status_code).to.eql('R')

await POST('/odata/v4/travel/Travels(ID=1,IsActiveEntity=false)/draftActivate', {})
travel = await READ(1)
expect(travel.Status_code).to.eql('O')
expect(travel.transitions_).to.have.length(2)

await POST('/odata/v4/travel/Travels(ID=1,IsActiveEntity=true)/rejectTravel', {})
travel = await READ(1)
expect(travel.Status_code).to.eql('R')
expect(travel.transitions_).to.have.length(3)

await POST('/odata/v4/travel/Travels(ID=1,IsActiveEntity=true)/reopenTravel', {})
travel = await READ(1)
expect(travel.Status_code).to.eql('O')
expect(travel.transitions_).to.have.length(4)
})

// NOTE: not applicable with transitions_ being excluded from projections
it.skip('prohibits altering the flow history', async () => {
let res

await POST('/odata/v4/travel/Travels(ID=1,IsActiveEntity=true)/acceptTravel', {})
const travel = await READ(1)
expect(travel.Status_code).to.eql('A')
expect(travel.transitions_).to.have.length(1)

const transition = `up__ID=1,timestamp=${travel.transitions_[0].timestamp}`

res = await GET(`/odata/v4/travel/Travels(ID=1,IsActiveEntity=true)/transitions_(${transition})`)
expect(res.status).to.eql(200)

res = await POST('/odata/v4/travel/Travels(ID=1,IsActiveEntity=true)/transitions_', {
comment: `I shouldn't be able to do this`
})
expect(res.status).to.eql(405)

res = await PATCH(`/odata/v4/travel/Travels(ID=1,IsActiveEntity=true)/transitions_(${transition})`, {
comment: `Not this either`
})
expect(res.status).to.eql(405)

res = await DELETE(`/odata/v4/travel/Travels(ID=1,IsActiveEntity=true)/transitions_(${transition})`)
expect(res.status).to.eql(405)
})
})
Loading