diff --git a/app/travels/field-control.cds b/app/travels/field-control.cds index 8c929b8..c056720 100644 --- a/app/travels/field-control.cds +++ b/app/travels/field-control.cds @@ -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'], diff --git a/db/data/sap.capire.travels-BookingStatus.csv b/db/data/sap.capire.travels-BookingStatus.csv new file mode 100644 index 0000000..75f3f88 --- /dev/null +++ b/db/data/sap.capire.travels-BookingStatus.csv @@ -0,0 +1,4 @@ +code;name +N;New +X;Rejected +B;Booked \ No newline at end of file diff --git a/db/data/sap.capire.travels-BookingStatus.texts.csv b/db/data/sap.capire.travels-BookingStatus.texts.csv new file mode 100644 index 0000000..84ac5c4 --- /dev/null +++ b/db/data/sap.capire.travels-BookingStatus.texts.csv @@ -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 diff --git a/srv/src/main/java/sap/capire/xtravels/handler/TravelValidationHandler.java b/srv/src/main/java/sap/capire/xtravels/handler/TravelValidationHandler.java new file mode 100644 index 0000000..6afcd74 --- /dev/null +++ b/srv/src/main/java/sap/capire/xtravels/handler/TravelValidationHandler.java @@ -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(); + } +} diff --git a/srv/src/test/java/sap/capire/xtravels/handler/TravelServiceTest.java b/srv/src/test/java/sap/capire/xtravels/handler/TravelServiceTest.java index 9a0a45d..d4b6786 100644 --- a/srv/src/test/java/sap/capire/xtravels/handler/TravelServiceTest.java +++ b/srv/src/test/java/sap/capire/xtravels/handler/TravelServiceTest.java @@ -63,7 +63,7 @@ public void testCreateTravel_CustomerIsNull() { .isThrownBy(() -> srv.run(insert)) .isBadRequest() .withMessageOrKey(CdsErrorStatuses.VALUE_REQUIRED.getCodeString()) - .thatTargets("Customer"); + .thatTargets("Customer_ID"); } @Test @@ -89,7 +89,7 @@ public void testCreateTravel_AgencyDoesNotExist() { .isThrownBy(() -> srv.run(insert)) .isBadRequest() .withMessageOrKey("Agency does not exist") - .thatTargets("Agency"); + .thatTargets("Agency_ID"); } @Test diff --git a/srv/travel-constraints.cds b/srv/travel-constraints.cds index a0f97c8..bdee27d 100644 --- a/srv/travel-constraints.cds +++ b/srv/travel-constraints.cds @@ -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' @@ -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'