-
Notifications
You must be signed in to change notification settings - Fork 133
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Rewrite RecalculatePriceHandler #609
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -3,32 +3,39 @@ | |||||
import static cds.gen.travelservice.TravelService_.BOOKING; | ||||||
import static cds.gen.travelservice.TravelService_.BOOKING_SUPPLEMENT; | ||||||
import static cds.gen.travelservice.TravelService_.TRAVEL; | ||||||
import static com.sap.cds.ql.CQL.sum; | ||||||
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_CANCEL; | ||||||
import static com.sap.cds.services.draft.DraftService.EVENT_DRAFT_PATCH; | ||||||
import static java.lang.Boolean.FALSE; | ||||||
import static java.util.Objects.requireNonNullElse; | ||||||
|
||||||
import java.math.BigDecimal; | ||||||
import java.util.HashMap; | ||||||
import java.util.Map; | ||||||
import java.util.Objects; | ||||||
import java.util.Optional; | ||||||
import java.util.function.UnaryOperator; | ||||||
|
||||||
import org.springframework.stereotype.Component; | ||||||
|
||||||
import com.sap.cds.Row; | ||||||
import com.sap.cds.Result; | ||||||
import com.sap.cds.Struct; | ||||||
import com.sap.cds.ql.Select; | ||||||
import com.sap.cds.ql.Update; | ||||||
import com.sap.cds.ql.cqn.CqnAnalyzer; | ||||||
import com.sap.cds.ql.cqn.CqnSelect; | ||||||
import com.sap.cds.ql.cqn.CqnUpdate; | ||||||
import com.sap.cds.services.ErrorStatuses; | ||||||
import com.sap.cds.services.ServiceException; | ||||||
import com.sap.cds.services.cds.CqnService; | ||||||
import com.sap.cds.services.draft.DraftCancelEventContext; | ||||||
import com.sap.cds.services.draft.DraftPatchEventContext; | ||||||
import com.sap.cds.services.draft.DraftService; | ||||||
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.On; | ||||||
import com.sap.cds.services.handler.annotations.ServiceName; | ||||||
import com.sap.cds.services.persistence.PersistenceService; | ||||||
import com.sap.cds.services.utils.StringUtils; | ||||||
|
||||||
import cds.gen.sap.fe.cap.travel.TravelModel_; | ||||||
import cds.gen.travelservice.Booking; | ||||||
import cds.gen.travelservice.BookingSupplement; | ||||||
import cds.gen.travelservice.BookingSupplement_; | ||||||
|
@@ -41,6 +48,8 @@ | |||||
@ServiceName(TravelService_.CDS_NAME) | ||||||
public class RecalculatePriceHandler implements EventHandler { | ||||||
|
||||||
private static final BigDecimal ZERO = new BigDecimal(0); | ||||||
|
||||||
private final DraftService draftService; | ||||||
private final PersistenceService persistenceService; | ||||||
|
||||||
|
@@ -49,112 +58,168 @@ public RecalculatePriceHandler(DraftService draftService, PersistenceService per | |||||
this.persistenceService = persistenceService; | ||||||
} | ||||||
|
||||||
@Before(event = {CqnService.EVENT_CREATE, CqnService.EVENT_UPDATE}, entity = {Booking_.CDS_NAME, BookingSupplement_.CDS_NAME}) | ||||||
public void disableUpdateAndCreateForBookingAndBookingSupplement() { | ||||||
throw new ServiceException(ErrorStatuses.BAD_REQUEST, "error.booking.only_patch"); | ||||||
@After(event = { EVENT_CREATE, EVENT_UPDATE }, entity = Travel_.CDS_NAME) | ||||||
public void calculateTotalPriceOfTravel(Travel travel) { | ||||||
String travelUUID = travel.travelUUID(); | ||||||
if (travelUUID != null) { | ||||||
BigDecimal totalPrice = calculateTravelPrice(travelUUID); | ||||||
persistenceService.run(Update.entity(TRAVEL).data(Travel.create() | ||||||
.travelUUID(travelUUID) | ||||||
.totalPrice(totalPrice))); | ||||||
|
||||||
travel.totalPrice(totalPrice); | ||||||
} | ||||||
} | ||||||
|
||||||
private BigDecimal calculateTravelPrice(String travelUUID) { | ||||||
BigDecimal bookingFee = run(Select.from(TravelModel_.TRAVEL) | ||||||
.columns(t -> t.BookingFee().as("sum")) | ||||||
.byId(travelUUID)); | ||||||
|
||||||
BigDecimal flights = run(Select.from(BOOKING) | ||||||
.columns(b -> b.FlightPrice().sum().as("sum")) | ||||||
.where(b -> b.to_Travel_TravelUUID().eq(travelUUID))); | ||||||
|
||||||
BigDecimal supplements = run(Select.from(BOOKING_SUPPLEMENT) | ||||||
.columns(s -> s.Price().sum().as("sum")) | ||||||
.where(s -> s.to_Booking().to_Travel().TravelUUID().eq(travelUUID))); | ||||||
|
||||||
return bookingFee.add(flights).add(supplements); | ||||||
} | ||||||
|
||||||
private static BigDecimal calculateTotalPriceForTravel(CqnService db, String travelUUID, | ||||||
boolean isActiveEntity) { | ||||||
// get booking fee | ||||||
BigDecimal bookingFee = BigDecimal.valueOf(0); | ||||||
Optional<Row> bookingFeeRow = db | ||||||
.run(Select.from(TRAVEL).columns(Travel_::BookingFee) | ||||||
.where(t -> t.TravelUUID().eq(travelUUID) | ||||||
.and(t.IsActiveEntity().eq(isActiveEntity)) | ||||||
.and(t.BookingFee().isNotNull())) | ||||||
.limit(1)) | ||||||
.first(); | ||||||
if (bookingFeeRow.isPresent()) { | ||||||
bookingFee = (BigDecimal) bookingFeeRow.get().get("BookingFee"); | ||||||
private BigDecimal run(CqnSelect query) { | ||||||
Result result = persistenceService.run(query); | ||||||
BigDecimal sum = result.first(Price.class).map(Price::sum).orElse(ZERO); | ||||||
|
||||||
return nullToZero(sum); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. needed? |
||||||
} | ||||||
|
||||||
private interface Price { | ||||||
BigDecimal sum(); | ||||||
} | ||||||
|
||||||
@On(event = { EVENT_DRAFT_PATCH }, entity = Travel_.CDS_NAME) | ||||||
public void updateTravelPriceOnBookingFeeUpdate(DraftPatchEventContext context) { | ||||||
CqnUpdate update = context.getCqn(); | ||||||
Travel travel = Struct.access(update.data()).as(Travel.class); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
BigDecimal newFee = travel.bookingFee(); | ||||||
if (newFee != null) { | ||||||
var travelKeys = CqnAnalyzer.create(context.getModel()).analyze(update).targetKeys(); | ||||||
BigDecimal oldFee = selectTravelFee(travelKeys); | ||||||
BigDecimal travelPrice = selectTravelPrice(travelKeys.get(Travel.TRAVEL_UUID)).add(newFee).subtract(oldFee); | ||||||
|
||||||
travel.totalPrice(travelPrice); | ||||||
} | ||||||
} | ||||||
|
||||||
// get sum of flight prices from all bookings | ||||||
BigDecimal flightPriceSum = new BigDecimal(0); | ||||||
Optional<Row> flightPriceRow = db | ||||||
.run(Select.from(BOOKING).columns(b -> sum(b.FlightPrice()).as("FlightPriceSum")) | ||||||
.where(b -> b.to_Travel_TravelUUID().eq(travelUUID).and(b.IsActiveEntity().eq(isActiveEntity)))) | ||||||
.first(); | ||||||
@On(event = { EVENT_DRAFT_PATCH }, entity = Booking_.CDS_NAME) | ||||||
public void updateTravelPriceOnBookingUpdate(Booking bookingPatch) { | ||||||
BigDecimal newPrice = bookingPatch.flightPrice(); | ||||||
if (newPrice != null) { | ||||||
Booking booking = selectBookingPrice(Booking.create() | ||||||
.bookingUUID(bookingPatch.bookingUUID()) | ||||||
.isActiveEntity(false)); | ||||||
|
||||||
if (flightPriceRow.isPresent()) { | ||||||
flightPriceSum = (BigDecimal) Objects.requireNonNullElse(flightPriceRow.get().get("FlightPriceSum"), new BigDecimal(0)); | ||||||
String travelUUID = booking.toTravelTravelUUID(); | ||||||
updateTravelPrice(travelUUID, newPrice, booking.flightPrice()); | ||||||
} | ||||||
} | ||||||
|
||||||
@On(event = { EVENT_DRAFT_PATCH }, entity = BookingSupplement_.CDS_NAME) | ||||||
public void updateTravelPriceOnSupplementUpdate(BookingSupplement supplementPatch) { | ||||||
BigDecimal newPrice = supplementPatch.price(); | ||||||
if (newPrice != null) { | ||||||
BookingSupplement supplement = selectSupplementPrice(s -> s | ||||||
.bookSupplUUID(supplementPatch.bookSupplUUID()) | ||||||
.isActiveEntity(false)); | ||||||
|
||||||
// get sum of the prices of all booking supplements for the travel | ||||||
BigDecimal supplementPriceSum = new BigDecimal(0); | ||||||
Optional<Row> supplementPriceSumRow = db | ||||||
.run(Select.from(BOOKING_SUPPLEMENT).columns(c -> sum(c.Price()).as("PriceSum")) | ||||||
.where(b -> b.to_Travel_TravelUUID().eq(travelUUID).and(b.IsActiveEntity().eq(isActiveEntity)))) | ||||||
.first(); | ||||||
if (supplementPriceSumRow.isPresent()) { | ||||||
supplementPriceSum = (BigDecimal) Objects.requireNonNullElse(flightPriceRow.get().get("PriceSum"), new BigDecimal(0)); | ||||||
String travelUUID = supplement.toTravelTravelUUID(); | ||||||
updateTravelPrice(travelUUID, newPrice, supplement.price()); | ||||||
} | ||||||
} | ||||||
|
||||||
// update travel's total price | ||||||
return bookingFee.add(flightPriceSum).add(supplementPriceSum); | ||||||
private void updateTravelPrice(String travelUUID, BigDecimal newPrice, BigDecimal oldPrice) { | ||||||
BigDecimal travelPrice = selectTravelPrice(travelUUID).add(newPrice).subtract(nullToZero(oldPrice)); | ||||||
updateTravelPrice(travelUUID, travelPrice); | ||||||
} | ||||||
|
||||||
@After(event = {CqnService.EVENT_UPDATE, CqnService.EVENT_CREATE}, entity = Travel_.CDS_NAME) | ||||||
public void calculateNewTotalPriceForActiveTravel(Travel travel) { | ||||||
private void updateTravelPrice(String travelUUID, BigDecimal totalPrice) { | ||||||
CqnUpdate update = Update.entity(TRAVEL).byId(travelUUID).data(Travel.TOTAL_PRICE, totalPrice); | ||||||
draftService.patchDraft(update); | ||||||
} | ||||||
|
||||||
/* | ||||||
* Elements annotated with @Core.computed are not transferred during | ||||||
* DRAFT_SAVE. Normally, we'd re-compute the @Core.computed values after | ||||||
* DRAFT_SAVE and store them to the active version. For the TravelStatus_code | ||||||
* this is not possible as they originate as the result of a custom action | ||||||
* and thus cannot be re-computed. We have to take them from the draft version and | ||||||
* store them to the active version *before* the DRAFT_SAVE event. | ||||||
*/ | ||||||
@On(event = { EVENT_DRAFT_CANCEL }, entity = Booking_.CDS_NAME) | ||||||
public void updateTravelPriceOnCancelBooking(DraftCancelEventContext context) { | ||||||
Booking booking = selectBookingPrice(entityKeys(context)); | ||||||
String travelUUID = booking.toTravelTravelUUID(); | ||||||
BigDecimal supplementPrice = calculateSupplementPrice(booking.bookingUUID()); | ||||||
BigDecimal totalPrice = selectTravelPrice(travelUUID).subtract(supplementPrice) | ||||||
.subtract(nullToZero(booking.flightPrice())); | ||||||
|
||||||
String travelUUID = travel.travelUUID(); | ||||||
if (StringUtils.isEmpty(travelUUID)) { | ||||||
return; | ||||||
} | ||||||
travel.totalPrice(calculateTotalPriceForTravel(persistenceService, travelUUID, true)); | ||||||
updateTravelPrice(travelUUID, totalPrice); | ||||||
} | ||||||
|
||||||
private BigDecimal calculateSupplementPrice(String bookingUUID) { | ||||||
Result result = draftService.run(Select.from(BOOKING_SUPPLEMENT).columns(s -> s.Price().sum().as("sum")) | ||||||
.where(s -> s.to_Booking_BookingUUID().eq(bookingUUID).and(s.IsActiveEntity().eq(FALSE)))); | ||||||
Price price = result.single(Price.class); | ||||||
|
||||||
Map<String, Object> data = new HashMap<>(); | ||||||
data.put(Travel.TOTAL_PRICE, travel.totalPrice()); | ||||||
data.put(Travel.TRAVEL_UUID, travelUUID); | ||||||
persistenceService.run(Update.entity(TRAVEL).data(data)); | ||||||
return nullToZero(price.sum()); | ||||||
} | ||||||
|
||||||
@After(event = { DraftService.EVENT_DRAFT_PATCH }, entity = Travel_.CDS_NAME) | ||||||
public void recalculateTravelPriceIfTravelWasUpdated(final Travel travel) { | ||||||
if (travel.travelUUID() != null && travel.bookingFee() != null) { // only for patched booking fee | ||||||
String travelUUID = travel.travelUUID(); | ||||||
travel.totalPrice(calculateAndPatchNewTotalPriceForDraft(travelUUID)); | ||||||
@On(event = { EVENT_DRAFT_CANCEL }, entity = BookingSupplement_.CDS_NAME) | ||||||
public void updateTravelPriceAfterDeleteBookingSupplement(DraftCancelEventContext context) { | ||||||
BookingSupplement supplement = selectSupplementPrice(entityKeys(context)); | ||||||
|
||||||
if (supplement.price() != null) { | ||||||
String travelUUID = supplement.toTravelTravelUUID(); | ||||||
updateTravelPrice(travelUUID, ZERO, supplement.price()); | ||||||
} | ||||||
} | ||||||
|
||||||
@After(event = { DraftService.EVENT_DRAFT_PATCH, DraftService.EVENT_DRAFT_NEW }, entity = Booking_.CDS_NAME) | ||||||
public void recalculateTravelPriceIfFlightPriceWasUpdated(final Booking booking) { | ||||||
draftService.run(Select.from(BOOKING).columns(bs -> bs.to_Travel().TravelUUID().as(Travel.TRAVEL_UUID)) | ||||||
.where(bs -> bs.BookingUUID().eq(booking.bookingUUID()) | ||||||
.and(bs.IsActiveEntity().eq(FALSE)))) | ||||||
.first() | ||||||
.ifPresent(row -> calculateAndPatchNewTotalPriceForDraft((String) row.get(Travel.TRAVEL_UUID))); | ||||||
private static Map<String, Object> entityKeys(DraftCancelEventContext context) { | ||||||
return CqnAnalyzer.create(context.getModel()).analyze(context.getCqn()).targetKeys(); | ||||||
} | ||||||
|
||||||
@After(event = { DraftService.EVENT_DRAFT_NEW, DraftService.EVENT_DRAFT_PATCH, | ||||||
DraftService.EVENT_DRAFT_SAVE }, entity = BookingSupplement_.CDS_NAME) | ||||||
public void recalculateTravelPriceIfPriceWasUpdated(final BookingSupplement bookingSupplement) { | ||||||
draftService.run(Select.from(BOOKING_SUPPLEMENT) | ||||||
.columns(bs -> bs.to_Booking().to_Travel().TravelUUID().as(Travel.TRAVEL_UUID)) | ||||||
.where(bs -> bs.BookSupplUUID().eq(bookingSupplement.bookSupplUUID()) | ||||||
.and(bs.IsActiveEntity().eq(FALSE)))) | ||||||
.first() | ||||||
.ifPresent(row -> calculateAndPatchNewTotalPriceForDraft((String) row.get(Travel.TRAVEL_UUID))); | ||||||
private BigDecimal selectTravelPrice(Object travelUUID) { | ||||||
CqnSelect query = Select.from(TRAVEL).columns(t -> t.TotalPrice().as("sum")).matching(Map.of( | ||||||
Travel.TRAVEL_UUID, travelUUID, | ||||||
Travel.IS_ACTIVE_ENTITY, false)); | ||||||
BigDecimal price = draftService.run(query).single(Price.class).sum(); | ||||||
|
||||||
return nullToZero(price); | ||||||
} | ||||||
|
||||||
private BigDecimal calculateAndPatchNewTotalPriceForDraft(final String travelUUID) { | ||||||
private BigDecimal selectTravelFee(Map<String, Object> travelKeys) { | ||||||
CqnSelect query = Select.from(TRAVEL).matching(travelKeys).columns(b -> b.BookingFee()); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe extract the query as a private static final and parameterize it for reuse |
||||||
Travel travel = draftService.run(query).single(Travel.class); | ||||||
|
||||||
BigDecimal totalPrice = calculateTotalPriceForTravel(draftService, travelUUID, false); | ||||||
Map<String, Object> map = new HashMap<String, Object>(); | ||||||
map.put(Travel.TRAVEL_UUID, travelUUID); | ||||||
map.put(Travel.TOTAL_PRICE, totalPrice); | ||||||
CqnUpdate update = Update.entity(TRAVEL).data(map); | ||||||
draftService.patchDraft(update); | ||||||
return totalPrice; | ||||||
return nullToZero(travel.bookingFee()); | ||||||
} | ||||||
|
||||||
private Booking selectBookingPrice(Map<String, Object> bookingKeys) { | ||||||
CqnSelect query = Select.from(BOOKING).matching(bookingKeys).columns( | ||||||
b -> b.BookingUUID(), b -> b.to_Travel_TravelUUID(), b -> b.FlightPrice()); | ||||||
return draftService.run(query).single(Booking.class); | ||||||
} | ||||||
|
||||||
private BookingSupplement selectSupplementPrice(UnaryOperator<BookingSupplement> supplement) { | ||||||
return selectSupplementPrice(supplement.apply(BookingSupplement.create())); | ||||||
} | ||||||
|
||||||
private BookingSupplement selectSupplementPrice(Map<String, Object> supplementKeys) { | ||||||
CqnSelect query = Select.from(BOOKING_SUPPLEMENT).matching(supplementKeys) | ||||||
.columns(s -> s.to_Booking().to_Travel_TravelUUID(), s -> s.Price()); | ||||||
return draftService.run(query).single(BookingSupplement.class); | ||||||
} | ||||||
|
||||||
private static BigDecimal nullToZero(BigDecimal d) { | ||||||
return requireNonNullElse(d, ZERO); | ||||||
} | ||||||
|
||||||
@Before(event = { EVENT_CREATE, EVENT_UPDATE }, entity = { Booking_.CDS_NAME, BookingSupplement_.CDS_NAME }) | ||||||
public void disableUpdateAndCreateForBookingAndBookingSupplement() { | ||||||
throw new ServiceException(ErrorStatuses.BAD_REQUEST, "error.booking.only_patch"); | ||||||
} | ||||||
|
||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
booking fee is not a sum