Skip to content
Draft

Talk #39

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
2 changes: 1 addition & 1 deletion app/travels/field-control.cds
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ annotate TravelService.Travels with @(Common : {
BeginDate @readonly: (Status.code = #Accepted) @mandatory: (Status.code != #Accepted);
EndDate @readonly: (Status.code = #Accepted) @mandatory: (Status.code != #Accepted);
Agency @readonly: (Status.code = #Accepted) @mandatory: (Status.code != #Accepted);
Customer @readonly: (Status.code = #Accepted) @mandatory: (Status.code != #Accepted);
Customer @readonly: (Status.code = #Accepted);// @mandatory: (Status.code != #Accepted);
} actions {
deductDiscount @(
Common.SideEffects.TargetProperties : ['in/TotalPrice', 'in/BookingFee'],
Expand Down
4 changes: 4 additions & 0 deletions db/data/sap.capire.travels-BookingStatus.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
code;name
N;New
X;Rejected
B;Booked
13 changes: 13 additions & 0 deletions db/data/sap.capire.travels-BookingStatus.texts.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
code;locale;name
N;de;Neu
B;de;Bestätigt
X;de;Storniert
N;fr;Nouveau
B;fr;Réservé
X;fr;Annulé
N;it;Nuova
B;it;Prenotato
X;it;Annullato
N;en;New
B;en;Accepted
X;en;Rejected
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
package sap.capire.xtravels.handler;

import static cds.gen.travelservice.TravelService_.BOOKINGS;
import static cds.gen.travelservice.TravelService_.TRAVELS;
import static com.sap.cds.services.cds.CqnService.EVENT_CREATE;
import static com.sap.cds.services.cds.CqnService.EVENT_UPDATE;
import static com.sap.cds.services.draft.DraftService.EVENT_DRAFT_PATCH;

import cds.gen.travelservice.Bookings;
import cds.gen.travelservice.Bookings_;
import cds.gen.travelservice.Passengers_;
import cds.gen.travelservice.TravelAgencies_;
import cds.gen.travelservice.TravelService;
import cds.gen.travelservice.TravelService_;
import cds.gen.travelservice.Travels;
import cds.gen.travelservice.Travels_;
import com.sap.cds.Row;
import com.sap.cds.ql.Select;
import com.sap.cds.ql.cqn.CqnSelect;
import com.sap.cds.services.EventContext;
import com.sap.cds.services.handler.EventHandler;
import com.sap.cds.services.handler.annotations.After;
import com.sap.cds.services.handler.annotations.Before;
import com.sap.cds.services.handler.annotations.HandlerOrder;
import com.sap.cds.services.handler.annotations.ServiceName;
import com.sap.cds.services.messages.MessageTarget;
import java.time.LocalDate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
@ServiceName(TravelService_.CDS_NAME)
public class TravelValidationHandler implements EventHandler {

@Autowired TravelService ts;

@Before(event = {EVENT_CREATE, EVENT_UPDATE, EVENT_DRAFT_PATCH})
public void validateTravelBeforeWrite(Travels_ ref, Travels travel, EventContext ctx) {
ref = travelRef(ref, travel);

if (travel.getDescription() != null && travel.getDescription().length() < 3) {
ctx.getMessages().error("Description too short").target(TRAVELS, b -> b.Description());
}

if (travel.getCustomerId() == null) {
if (travel.containsKey(Travels_.CUSTOMER_ID)) {
ctx.getMessages().error("409003").target(TRAVELS, b -> b.Customer_ID());
}
} else {
var result =
ts.run(Select.from(Passengers_.class).byId(travel.getCustomerId()).columns(p -> p.ID()));
if (result.rowCount() == 0) {
ctx.getMessages().error("Customer does not exist").target(TRAVELS, b -> b.Customer_ID());
}
}

if (travel.getAgencyId() != null) {
var result =
ts.run(
Select.from(TravelAgencies_.class).byId(travel.getAgencyId()).columns(a -> a.ID()));
if (result.rowCount() == 0) {
ctx.getMessages().error("Agency does not exist").target(TRAVELS, b -> b.Agency_ID());
}
}

if (travel.getBookings() != null && !travel.getBookings().isEmpty()) {
for (Bookings booking : travel.getBookings()) {
booking.setTravel(travel);
Bookings_ bRef = bookingRef(ref.Bookings(), booking);
validateBookings(bRef, booking, ctx);
booking.setTravel(null);
}
}

if (!travel.containsKey(Travels.BEGIN_DATE) && !travel.containsKey(Travels.END_DATE)) {
return;
}

LocalDate beginDate = travel.getBeginDate();
LocalDate endDate = travel.getEndDate();

if (beginDate != null && endDate == null) {
Travels beforeImage = ts.run(Select.from(ref).columns(Travels_::EndDate)).single();
endDate = beforeImage.getEndDate();
}

if (endDate != null && beginDate == null) {
Travels beforeImage = ts.run(Select.from(ref).columns(Travels_::BeginDate)).single();
endDate = beforeImage.getBeginDate();
}

if (beginDate != null && endDate != null && beginDate.isAfter(endDate)) {
ctx.getMessages()
.error("ASSERT_ENDDATE_AFTER_BEGINDATE")
.target(TRAVELS, t -> t.BeginDate())
.additionalTargets(MessageTarget.create(TRAVELS, t -> t.EndDate()));
}
}

// @After(event = { EVENT_DRAFT_PATCH, EVENT_CREATE, EVENT_UPDATE })
public void validateTravelAfterWriteOnDB(Travels_ ref, Travels travel, EventContext ctx) {
if (!travel.containsKey(Travels.BEGIN_DATE) && !travel.containsKey(Travels.END_DATE)) {
return;
}

ref = travelRef(ref, travel);

CqnSelect check =
Select.from(ref).columns(t -> t.BeginDate().gt(t.EndDate()).as("beginAfterEnd"));
Row result = ts.run(check).single();
if (result.get("beginAfterEnd").equals(Boolean.TRUE)) {
ctx.getMessages()
.error("ASSERT_ENDDATE_AFTER_BEGINDATE")
.target(TRAVELS, t -> t.BeginDate())
.additionalTargets(MessageTarget.create(TRAVELS, t -> t.EndDate()));
}
}

private static Travels_ travelRef(Travels_ ref, Travels travel) {
Integer id = travel.getId();
if (id != null) {
boolean isActiveEntity = travel.getIsActiveEntity() == Boolean.FALSE ? false : true;
ref.filter(t -> t.ID().eq(id).and(t.IsActiveEntity().eq(isActiveEntity)));
}

return ref;
}

@Before(event = {EVENT_CREATE, EVENT_UPDATE, EVENT_DRAFT_PATCH})
public void validateBookings(Bookings_ ref, Bookings bookings, EventContext ctx) {
ref = bookingRef(ref, bookings);

if (bookings.getFlightDate() != null) {
Travels travel = bookings.getTravel();
if (travel == null) {
var result =
ts.run(Select.from(ref.Travel()).columns(t -> t.BeginDate(), t -> t.EndDate()));
travel = result.single();
}

LocalDate flightDate = bookings.getFlightDate();
LocalDate beginDate = travel.getBeginDate();
LocalDate endDate = travel.getEndDate();

if (beginDate != null && endDate != null) {
if (flightDate.isBefore(beginDate) || flightDate.isAfter(endDate)) {
MessageTarget target =
travel != null
? MessageTarget.create(TRAVELS, t -> t.Bookings().Flight_date())
: MessageTarget.create(BOOKINGS, t -> t.Flight_date());
ctx.getMessages().error("ASSERT_BOOKINGS_IN_TRAVEL_PERIOD").target(target);
}
}
}
}

private static Bookings_ bookingRef(Bookings_ ref, Bookings booking) {
String flightId = booking.getFlightId();
LocalDate flightDate = booking.getFlightDate();
if (flightId != null && flightDate != null) {
boolean isActiveEntity = booking.getIsActiveEntity() == Boolean.FALSE ? false : true;
ref.filter(
t ->
t.Flight_ID()
.eq(flightId)
.and(t.Flight_date().eq(flightDate))
.and(t.IsActiveEntity().eq(isActiveEntity)));
}

return ref;
}

@After(
event = {EVENT_CREATE, EVENT_UPDATE},
entity = {Travels_.CDS_NAME, Bookings_.CDS_NAME})
@HandlerOrder(HandlerOrder.LATE)
public void throwIfError(EventContext ctx) {
ctx.getMessages().throwIfError();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public void testCreateTravel_CustomerIsNull() {
.isThrownBy(() -> srv.run(insert))
.isBadRequest()
.withMessageOrKey(CdsErrorStatuses.VALUE_REQUIRED.getCodeString())
.thatTargets("Customer");
.thatTargets("Customer_ID");
}

@Test
Expand All @@ -89,7 +89,7 @@ public void testCreateTravel_AgencyDoesNotExist() {
.isThrownBy(() -> srv.run(insert))
.isBadRequest()
.withMessageOrKey("Agency does not exist")
.thatTargets("Agency");
.thatTargets("Agency_ID");
}

@Test
Expand Down
41 changes: 23 additions & 18 deletions srv/travel-constraints.cds
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,27 @@ using { TravelService } from './travel-service';

annotate TravelService.Travels with {

Description @assert: (case
when length(Description) < 3 then 'Description too short'
end);
// Description @assert: (case
// when length(Description) < 3 then 'Description too short'
// end);

Agency @assert: (case
when not exists Agency then 'Agency does not exist'
end);
// Agency @assert: (case
// when not exists Agency then 'Agency does not exist'
// end);

Customer @assert: (case
when Customer is null then 'Customer must be specified'
when not exists Customer then 'Customer does not exist'
end);
// Customer @assert: (case
// when Customer is null then 'Customer must be specified'
// when not exists Customer then 'Customer does not exist'
// end);

EndDate @assert: (case
when EndDate < BeginDate then error('ASSERT_ENDDATE_AFTER_BEGINDATE', null, (BeginDate, EndDate))
end);
// EndDate @assert: (case
// when EndDate < BeginDate then error('ASSERT_ENDDATE_AFTER_BEGINDATE', null, (BeginDate, EndDate))
// end);

// EndDate @assert: (case
// when EndDate < BeginDate then 'ASSERT_ENDDATE_AFTER_BEGINDATE'
// when exists Bookings [Flight.date > Travel.EndDate] then 'ASSERT_BOOKINGS_IN_TRAVEL_PERIOD'
// end);

BookingFee @assert: (case
when BookingFee < 0 then 'ASSERT_BOOKING_FEE_NON_NEGATIVE'
Expand All @@ -29,11 +34,11 @@ annotate TravelService.Travels with {

annotate TravelService.Bookings with {

Flight {
date @assert: (case
when date not between $self.Travel.BeginDate and $self.Travel.EndDate then 'ASSERT_BOOKINGS_IN_TRAVEL_PERIOD'
end);
}
// Flight {
// date @assert: (case
// when date not between $self.Travel.BeginDate and $self.Travel.EndDate then 'ASSERT_BOOKINGS_IN_TRAVEL_PERIOD'
// end);
// }

FlightPrice @assert: (case
when FlightPrice < 0 then 'ASSERT_FLIGHT_PRICE_POSITIVE'
Expand Down