-
Notifications
You must be signed in to change notification settings - Fork 138
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 2 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,31 +3,37 @@ | |||||
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 org.springframework.stereotype.Component; | ||||||
|
||||||
import com.sap.cds.Row; | ||||||
import com.google.common.base.Strings; | ||||||
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.travelservice.Booking; | ||||||
import cds.gen.travelservice.BookingSupplement; | ||||||
|
@@ -41,6 +47,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 +57,159 @@ 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 (!Strings.isNullOrEmpty(travelUUID)) { | ||||||
BigDecimal totalPrice = calculateTravelPrice(travelUUID); | ||||||
travel.totalPrice(totalPrice); | ||||||
|
||||||
persistenceService.run(Update.entity(TRAVEL).data(Map.of( | ||||||
Travel.TRAVEL_UUID, travelUUID, | ||||||
Travel.TOTAL_PRICE, totalPrice))); | ||||||
} | ||||||
} | ||||||
|
||||||
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 calculateTravelPrice(String travelUUID) { | ||||||
BigDecimal bookingFee = run(Select.from(TRAVEL) | ||||||
.columns(t -> t.BookingFee().as("sum")) | ||||||
.where(t -> t.TravelUUID().eq(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 BigDecimal run(CqnSelect query) { | ||||||
BigDecimal sum = persistenceService.run(query).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 static interface Price { | ||||||
BigDecimal 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. I don't think this interface buys much |
||||||
|
||||||
@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) { | ||||||
Map<String, Object> travelKeys = CqnAnalyzer.create(context.getModel()).analyze(update).targetKeys(); | ||||||
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. CqnAnalyzer.create(context.getModel()) -> we should offer this directly on the "event context" 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. Access travelKeys as Travel ? e.g. Travel travelKeys = CqnAnalyzer.create(context.getModel()).analyze(update).targetKeys(Travel.class);
BigDecimal oldFee = selectTravelFee(travelKeys);
BigDecimal travelPrice = selectTravelPrice(travelKeys.getTravelUuid()).add(newFee).subtract(oldFee); |
||||||
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 = selectBooking(Map.of( | ||||||
Booking.BOOKING_UUID, bookingPatch.bookingUUID(), | ||||||
Booking.IS_ACTIVE_ENTITY, 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 = selectSupplement(Map.of(BookingSupplement.BOOK_SUPPL_UUID, | ||||||
supplementPatch.bookSupplUUID(), BookingSupplement.IS_ACTIVE_ENTITY, 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) { | ||||||
draftService.patchDraft(Update.entity(TRAVEL).byId(travelUUID).data(Travel.TOTAL_PRICE, totalPrice)); | ||||||
} | ||||||
|
||||||
/* | ||||||
* 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 = selectBooking(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); | ||||||
} | ||||||
|
||||||
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)); | ||||||
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)))); | ||||||
|
||||||
return nullToZero(result.single(Price.class).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 = selectSupplement(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(); | ||||||
} | ||||||
|
||||||
private BigDecimal selectTravelPrice(Object travelUUID) { | ||||||
CqnSelect query = Select.from(TRAVEL) | ||||||
.matching(Map.of(Travel.TRAVEL_UUID, travelUUID, Travel.IS_ACTIVE_ENTITY, false)) | ||||||
.columns(t -> t.TotalPrice().as("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. maybe extract the query as a private static final and parameterize it for reuse |
||||||
BigDecimal price = draftService.run(query).single(Price.class).sum(); | ||||||
|
||||||
return nullToZero(price); | ||||||
} | ||||||
|
||||||
@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 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); | ||||||
|
||||||
return nullToZero(travel.bookingFee()); | ||||||
} | ||||||
|
||||||
private BigDecimal calculateAndPatchNewTotalPriceForDraft(final String travelUUID) { | ||||||
private Booking selectBooking(Map<String, Object> bookingKeys) { | ||||||
CqnSelect query = Select.from(BOOKING).matching(bookingKeys).columns(b -> b.BookingUUID(), | ||||||
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 |
||||||
b -> b.to_Travel_TravelUUID(), b -> b.FlightPrice()); | ||||||
return draftService.run(query).single(Booking.class); | ||||||
} | ||||||
|
||||||
private BookingSupplement selectSupplement(Map<String, Object> supplementKeys) { | ||||||
return draftService.run(Select.from(BOOKING_SUPPLEMENT).matching(supplementKeys) | ||||||
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 |
||||||
.columns(s -> s.to_Booking().to_Travel_TravelUUID(), s -> s.Price())).single(BookingSupplement.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; | ||||||
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