Skip to content

Commit d1e98bb

Browse files
authored
Simplify ts (#1308)
Apply simplifications of #1178 to ts.
2 parents 83d6c24 + afc534f commit d1e98bb

File tree

2 files changed

+67
-267
lines changed

2 files changed

+67
-267
lines changed

srv/travel-service.js

Lines changed: 0 additions & 89 deletions
This file was deleted.

srv/travel-service.ts

Lines changed: 67 additions & 178 deletions
Original file line numberDiff line numberDiff line change
@@ -1,179 +1,99 @@
1-
import { ApplicationService } from '@sap/cds'
21
import * as cds from '@sap/cds'
3-
import { Booking, BookingSupplement, Travel } from '#cds-models/TravelService'
4-
import { BookingStatusCode, TravelStatusCode } from '#cds-models/sap/fe/cap/travel'
2+
import { Booking, BookingSupplement as Supplements, Travel } from '#cds-models/TravelService'
3+
import { TravelStatusCode } from '#cds-models/sap/fe/cap/travel'
54
import { CdsDate } from '#cds-models/_'
65

6+
export class TravelService extends cds.ApplicationService { init() {
77

8-
export class TravelService extends ApplicationService {
9-
init() {
8+
// Reflected definitions from the service's CDS model
9+
const { today } = cds.builtin.types.Date as unknown as { today(): CdsDate };
1010

11-
/**
12-
* Fill in primary keys for new Travels.
13-
* Note: In contrast to Bookings and BookingSupplements that has to happen
14-
* upon SAVE, as multiple users could create new Travels concurrently.
15-
*/
16-
this.before ('CREATE', Travel, async req => {
17-
// FIXME: TS v
18-
const { maxID } = await SELECT.one `max(TravelID) as maxID` .from(Travel) as unknown as { maxID: number }
19-
req.data.TravelID = maxID + 1
20-
})
2111

12+
// Fill in alternative keys as consecutive numbers for new Travels, Bookings, and Supplements.
13+
// Note: For Travels that can't be done at NEW events, that is when drafts are created,
14+
// but on CREATE only, as multiple users could create new Travels concurrently.
2215

23-
/**
24-
* Fill in defaults for new Bookings when editing Travels.
25-
*/
26-
this.before ('NEW', Booking.drafts, async (req) => {
27-
const { to_Travel_TravelUUID } = req.data
28-
const { status } = await SELECT.one .from (Travel.drafts, to_Travel_TravelUUID, t => t.TravelStatus_code.as('status')) as { status: string }
29-
if (status === TravelStatusCode.Canceled) throw req.reject (400, 'Cannot add new bookings to rejected travels.')
30-
// FIXME: TS v
31-
const { maxID } = await SELECT.one `max(BookingID) as maxID` .from(Booking.drafts) .where({ to_Travel_TravelUUID }) as unknown as { maxID: number }
32-
req.data.BookingID = maxID + 1
33-
req.data.BookingStatus_code = BookingStatusCode.New
34-
req.data.BookingDate = (new Date).toISOString().slice(0, 10) as CdsDate // today
16+
this.before ('CREATE', Travel, async req => {
17+
let { maxID } = await SELECT.one (`max(TravelID) as maxID`) .from (Travel) as { maxID: number }
18+
req.data.TravelID = ++maxID
3519
})
3620

37-
38-
/**
39-
* Fill in defaults for new BookingSupplements when editing Travels.
40-
*/
41-
this.before ('NEW', BookingSupplement.drafts, async (req) => {
42-
const { to_Booking_BookingUUID } = req.data
43-
// FIXME: TS v
44-
const { maxID } = await SELECT.one `max(BookingSupplementID) as maxID` .from(BookingSupplement.drafts) .where({ to_Booking_BookingUUID }) as unknown as { maxID: number }
45-
req.data.BookingSupplementID = maxID + 1
21+
this.before ('NEW', Booking.drafts, async req => {
22+
let { maxID } = await SELECT.one (`max(BookingID) as maxID`) .from (Booking.drafts) .where (req.data) as { maxID: number }
23+
req.data.BookingID = ++maxID
24+
req.data.BookingDate = today() // REVISIT: could that be filled in by CAP automatically?
4625
})
4726

48-
49-
/**
50-
* Changing Booking Fees is only allowed for not yet accapted Travels.
51-
*/
52-
this.before ('UPDATE', Travel.drafts, async (req) => { if ('BookingFee' in req.data) {
53-
const { TravelStatus_code: status } = await SELECT.one .from(req.subject) as Travel
54-
if (status === TravelStatusCode.Accepted) req.reject(400, 'Booking fee can not be updated for accepted travels.', 'BookingFee')
55-
}})
56-
57-
58-
/**
59-
* Update the Travel's TotalPrice when its BookingFee is modified.
60-
*/
61-
this.after('UPDATE', Travel.drafts, (_, req) => {
62-
const { TravelUUID } = req.data
63-
if ('BookingFee' in req.data || 'GoGreen' in req.data) {
64-
return this._update_totals4(TravelUUID)
65-
}
27+
this.before ('NEW', Supplements.drafts, async req => {
28+
let { maxID } = await SELECT.one (`max(BookingSupplementID) as maxID`) .from (Supplements.drafts) .where (req.data) as { maxID: number }
29+
req.data.BookingSupplementID = ++maxID
6630
})
6731

6832

69-
/**
70-
* Update the Travel's TotalPrice when a Booking's FlightPrice is modified.
71-
*/
72-
this.after ('UPDATE', Booking.drafts, async (_, req) => { if ('FlightPrice' in req.data) {
73-
// We need to fetch the Travel's UUID for the given Booking target
74-
const { travel } = await SELECT.one `to_Travel_TravelUUID as travel` .from(req.subject)
75-
return this._update_totals4 (travel)
76-
}})
77-
78-
79-
/**
80-
* Update the Travel's TotalPrice when a Supplement's Price is modified.
81-
*/
82-
this.after ('UPDATE', BookingSupplement.drafts, async (_, req) => { if ('Price' in req.data) {
83-
const { BookSupplUUID } = req.data
84-
// We need to fetch the Travel's UUID for the given Supplement target
85-
const BookingUUID = SELECT.one(BookingSupplement.drafts, b => b.to_Booking_BookingUUID) .where({ BookSupplUUID })
86-
const { to_Travel_TravelUUID: travel } = await SELECT.one (Booking.drafts, b => b.to_Travel_TravelUUID) .where({ BookingUUID })
87-
return this._update_totals4(travel)
88-
}})
89-
90-
/**
91-
* Update the Travel's TotalPrice when a Booking Supplement is deleted.
92-
*/
93-
this.on('CANCEL', BookingSupplement.drafts, async (req, next) => {
94-
// Find out which travel is affected before the delete
95-
const { BookSupplUUID } = req.data
96-
const { to_Travel_TravelUUID } = await SELECT.one
97-
.from(BookingSupplement.drafts, b => b.to_Travel_TravelUUID)
98-
.where({ BookSupplUUID })
99-
// Delete handled by generic handlers
100-
const res = await next()
101-
// After the delete, update the totals
102-
await this._update_totals4(to_Travel_TravelUUID)
103-
return res
33+
// Ensure BeginDate is not before today and not after EndDate.
34+
this.before ('SAVE', Travel, req => {
35+
const { BeginDate, EndDate } = req.data
36+
if (BeginDate < today()) req.error (400, `Begin Date must not be before today.`, 'in/BeginDate')
37+
if (BeginDate > EndDate) req.error (400, `End Date must be after Begin Date.`, 'in/EndDate')
10438
})
10539

106-
/**
107-
* Update the Travel's TotalPrice when a Booking is deleted.
108-
*/
109-
this.on('CANCEL', Booking.drafts, async (req, next) => {
110-
// Find out which travel is affected before the delete
111-
const { BookingUUID } = req.data
112-
const { to_Travel_TravelUUID } = await SELECT.one
113-
.from(Booking.drafts, b => b.to_Travel_TravelUUID)
114-
.where({ BookingUUID })
115-
// Delete handled by generic handlers
116-
const res = await next()
117-
// After the delete, update the totals
118-
await this._update_totals4(to_Travel_TravelUUID)
119-
return res
120-
})
40+
41+
// Update a Travel's TotalPrice whenever its BookingFee is modified,
42+
// or when a nested Booking is deleted or its FlightPrice is modified,
43+
// or when a nested Supplement is deleted or its Price is modified.
44+
45+
this.on ('UPDATE', Travel.drafts, (req, next) => update_totals (req, next, ['BookingFee', 'GoGreen']))
46+
this.on ('UPDATE', Booking.drafts, (req, next) => update_totals (req, next, ['FlightPrice']))
47+
this.on ('UPDATE', Supplements.drafts, (req, next) => update_totals (req, next, ['Price']))
48+
this.on ('DELETE', Booking.drafts, (req, next) => update_totals (req, next))
49+
this.on ('DELETE', Supplements.drafts, (req, next) => update_totals (req, next))
50+
51+
// Note: using .on handlers as we need to read a Booking's or Supplement's TravelUUID before they are deleted.
52+
async function update_totals (req: cds.Request, next: Function, fields?: string[]) {
53+
if (fields && !fields.some(f => f in req.data)) return next() //> skip if no relevant data changed
54+
const travel = (req.data as Travel).TravelUUID || ( await SELECT.one `to_Travel.TravelUUID as id` .from (req.subject) ).id
55+
await next() // actually UPDATE or DELETE the subject entity
56+
await update_totalsGreen(travel);
57+
await cds.run(`UPDATE ${Travel.drafts} SET TotalPrice = coalesce (BookingFee,0)
58+
+ ( SELECT coalesce (sum(FlightPrice),0) from ${Booking.drafts} where to_Travel_TravelUUID = TravelUUID )
59+
+ ( SELECT coalesce (sum(Price),0) from ${Supplements.drafts} where to_Travel_TravelUUID = TravelUUID )
60+
WHERE TravelUUID = ?`, [travel])
61+
}
12162

12263
/**
123-
* Validate a Travel's edited data before save.
64+
* Trees-for-Tickets: helper to update totals including green flight fee
12465
*/
125-
this.before ('SAVE', Travel, req => {
126-
const { BeginDate, EndDate, BookingFee, to_Agency_AgencyID, to_Customer_CustomerID, to_Booking, TravelStatus_code } = req.data, today = (new Date).toISOString().slice(0,10)
127-
128-
// validate only not rejected travels
129-
if (TravelStatus_code !== TravelStatusCode.Canceled) {
130-
if (BookingFee == null) req.error(400, "Enter a booking fee", "in/BookingFee") // 0 is a valid BookingFee
131-
if (!BeginDate) req.error(400, "Enter a begin date", "in/BeginDate")
132-
if (!EndDate) req.error(400, "Enter an end date", "in/EndDate")
133-
if (!to_Agency_AgencyID) req.error(400, "Enter a travel agency", "in/to_Agency_AgencyID")
134-
if (!to_Customer_CustomerID) req.error(400, "Enter a customer", "in/to_Customer_CustomerID")
135-
136-
for (const booking of to_Booking) {
137-
const { BookingUUID, ConnectionID, FlightDate, FlightPrice, BookingStatus_code, to_Carrier_AirlineID, to_Customer_CustomerID } = booking
138-
if (!ConnectionID) req.error(400, "Enter a flight", `in/to_Booking(BookingUUID='${BookingUUID}',IsActiveEntity=false)/ConnectionID`)
139-
if (!FlightDate) req.error(400, "Enter a flight date", `in/to_Booking(BookingUUID='${BookingUUID}',IsActiveEntity=false)/FlightDate`)
140-
if (!FlightPrice) req.error(400, "Enter a flight price", `in/to_Booking(BookingUUID='${BookingUUID}',IsActiveEntity=false)/FlightPrice`)
141-
if (!BookingStatus_code) req.error(400, "Enter a booking status", `in/to_Booking(BookingUUID='${BookingUUID}',IsActiveEntity=false)/BookingStatus_code`)
142-
if (!to_Carrier_AirlineID) req.error(400, "Enter an airline", `in/to_Booking(BookingUUID='${BookingUUID}',IsActiveEntity=false)/to_Carrier_AirlineID`)
143-
if (!to_Customer_CustomerID) req.error(400, "Enter a customer", `in/to_Booking(BookingUUID='${BookingUUID}',IsActiveEntity=false)/to_Customer_CustomerID`)
144-
145-
for (const suppl of booking.to_BookSupplement) {
146-
const { BookSupplUUID, Price, to_Supplement_SupplementID } = suppl
147-
if (!Price) req.error(400, "Enter a price", `in/to_Booking(BookingUUID='${BookingUUID}',IsActiveEntity=false)/to_BookSupplement(BookSupplUUID='${BookSupplUUID}',IsActiveEntity=false)/Price`)
148-
if (!to_Supplement_SupplementID) req.error(400, "Enter a supplement", `in/to_Booking(BookingUUID='${BookingUUID}',IsActiveEntity=false)/to_BookSupplement(BookSupplUUID='${BookSupplUUID}',IsActiveEntity=false)/to_Supplement_SupplementID`)
149-
}
150-
}
66+
async function update_totalsGreen(TravelUUID: string) {
67+
const { GoGreen } = await SELECT.one .from(Travel.drafts) .columns('GoGreen') .where({ TravelUUID })
68+
if (GoGreen) {
69+
await UPDATE(Travel.drafts, TravelUUID)
70+
.set `GreenFee = round(BookingFee * 0.1, 0)`
71+
.set `TreesPlanted = round(BookingFee * 0.1, 0)`
72+
} else {
73+
await UPDATE(Travel.drafts, TravelUUID)
74+
.set `GreenFee = 0`
75+
.set `TreesPlanted = 0`
15176
}
152-
153-
if (BeginDate < today) req.error (400, `Begin Date ${BeginDate} must not be before today ${today}.`, 'in/BeginDate')
154-
if (BeginDate > EndDate) req.error (400, `Begin Date ${BeginDate} must be before End Date ${EndDate}.`, 'in/BeginDate')
155-
})
77+
}
15678

15779

15880
//
15981
// Action Implementations...
16082
//
161-
const { acceptTravel, rejectTravel, deductDiscount } = Travel.actions
16283

163-
this.on(acceptTravel, req => UPDATE(req.subject).with({ TravelStatus_code: TravelStatusCode.Accepted }))
164-
this.on(rejectTravel, req => UPDATE(req.subject).with({ TravelStatus_code: TravelStatusCode.Canceled }))
165-
this.on(deductDiscount, async req => {
84+
const { acceptTravel, rejectTravel, deductDiscount } = Travel.actions;
85+
this.on (acceptTravel, req => UPDATE (req.subject) .with ({ TravelStatus_code: TravelStatusCode.Accepted }))
86+
this.on (rejectTravel, req => UPDATE (req.subject) .with ({ TravelStatus_code: TravelStatusCode.Canceled }))
87+
this.on (deductDiscount, async req => {
16688
let discount = req.data.percent / 100
167-
let succeeded = await UPDATE(req.subject)
168-
.where `TravelStatus_code != 'A'`
169-
.and `BookingFee is not null`
170-
.set `TotalPrice = round (TotalPrice - BookingFee * ${discount}, 3)`
171-
.set `BookingFee = round (BookingFee - BookingFee * ${discount}, 3)`
89+
let succeeded = await UPDATE (req.subject) .where `TravelStatus.code != 'A'` .and `BookingFee != null`
90+
.with `TotalPrice = round (TotalPrice - BookingFee * ${discount}, 3)`
91+
.with `BookingFee = round (BookingFee - BookingFee * ${discount}, 3)`
17292

17393
if (!succeeded) { //> let's find out why...
174-
let travel = await SELECT.one `TravelID as ID, TravelStatus_code as status, BookingFee` .from(req.subject)
94+
let travel = await SELECT.one `TravelID as ID, TravelStatus.code as status, BookingFee` .from (req.subject)
17595
if (!travel) throw req.reject (404, `Travel "${travel.ID}" does not exist; may have been deleted meanwhile.`)
176-
if (travel.status === TravelStatusCode.Accepted) req.reject (400, `Travel "${travel.ID}" has been approved already.`)
96+
if (travel.status === TravelStatusCode.Accepted) throw req.reject (400, `Travel "${travel.ID}" has been approved already.`)
17797
if (travel.BookingFee == null) throw req.reject (404, `No discount possible, as travel "${travel.ID}" does not yet have a booking fee added.`)
17898
} else {
17999
return SELECT(req.subject)
@@ -183,35 +103,4 @@ init() {
183103
// Add base class's handlers. Handlers registered above go first.
184104
return super.init()
185105

186-
}
187-
188-
/**
189-
* Helper to re-calculate a Travel's TotalPrice from BookingFees, FlightPrices and Supplement Prices.
190-
*/
191-
async _update_totals4(travel: string) {
192-
await this._update_totalsGreen(travel)
193-
// Using plain native SQL for such complex queries
194-
await cds.run(`UPDATE ${Travel.drafts} SET
195-
TotalPrice = coalesce(BookingFee,0)
196-
+ coalesce(GreenFee,0)
197-
+ ( SELECT coalesce (sum(FlightPrice),0) from ${Booking.drafts} where to_Travel_TravelUUID = TravelUUID )
198-
+ ( SELECT coalesce (sum(Price),0) from ${BookingSupplement.drafts} where to_Travel_TravelUUID = TravelUUID )
199-
WHERE TravelUUID = ?`, [travel])
200-
}
201-
202-
/**
203-
* Trees-for-Tickets: helper to update totals including green flight fee
204-
*/
205-
async _update_totalsGreen(TravelUUID) {
206-
const { GoGreen } = await SELECT.one`GoGreen`.from(Travel.drafts).where({ TravelUUID })
207-
if (GoGreen) {
208-
await UPDATE(Travel.drafts, TravelUUID)
209-
.set`GreenFee = round(BookingFee * 0.1, 0)`
210-
.set`TreesPlanted = round(BookingFee * 0.1, 0)`
211-
} else {
212-
await UPDATE(Travel.drafts, TravelUUID)
213-
.set`GreenFee = 0`
214-
.set`TreesPlanted = 0`
215-
}
216-
}
217-
}
106+
}}

0 commit comments

Comments
 (0)