/* * Copyright 2011-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package pl.com.bottega.ecommerce.sales.domain.reservation; import java.util.ArrayList; import java.util.Date; import java.util.List; import javax.persistence.CascadeType; import javax.persistence.Embedded; import javax.persistence.Entity; import javax.persistence.EnumType; import javax.persistence.Enumerated; import javax.persistence.FetchType; import javax.persistence.JoinColumn; import javax.persistence.OneToMany; import org.hibernate.annotations.Fetch; import org.hibernate.annotations.FetchMode; import pl.com.bottega.ddd.annotations.domain.AggregateRoot; import pl.com.bottega.ddd.annotations.domain.Function; import pl.com.bottega.ddd.annotations.domain.Invariant; import pl.com.bottega.ddd.annotations.domain.InvariantsList; import pl.com.bottega.ddd.support.domain.BaseAggregateRoot; import pl.com.bottega.ecommerce.canonicalmodel.publishedlanguage.AggregateId; import pl.com.bottega.ecommerce.canonicalmodel.publishedlanguage.ClientData; import pl.com.bottega.ecommerce.sales.domain.offer.Discount; import pl.com.bottega.ecommerce.sales.domain.offer.DiscountPolicy; import pl.com.bottega.ecommerce.sales.domain.offer.Offer; import pl.com.bottega.ecommerce.sales.domain.offer.OfferItem; import pl.com.bottega.ecommerce.sales.domain.productscatalog.Product; import pl.com.bottega.ecommerce.sharedkernel.Money; /** * Reservation is just a "wish list". System can not guarantee that user can buy desired products.</br> * Reservation generates Offer VO, that is calculated based on current prices and current avability. * * @author Slawek * */ @InvariantsList({ "closed: closed reservation cano not be modified", "duplicates: can not add already added product, increase quantity instead", }) @Entity @AggregateRoot public class Reservation extends BaseAggregateRoot{ public enum ReservationStatus{ OPENED, CLOSED } @Enumerated(EnumType.STRING) private ReservationStatus status; @OneToMany(cascade = CascadeType.ALL, fetch=FetchType.EAGER, orphanRemoval = true) @JoinColumn(name = "reservation") @Fetch(FetchMode.JOIN) private List<ReservationItem> items; @Embedded private ClientData clientData; private Date createDate; @SuppressWarnings("unused") private Reservation() {} Reservation(AggregateId aggregateId, ReservationStatus status, ClientData clientData, Date createDate){ this.aggregateId = aggregateId; this.status = status; this.clientData = clientData; this.createDate = createDate; this.items = new ArrayList<ReservationItem>(); } @Invariant({"closed", "duplicates"}) public void add(Product product, int quantity){ if (isClosed()) domainError("Reservation already closed"); if (!product.isAvailabe()) domainError("Product is no longer available"); if (contains(product)){ increase(product, quantity); } else{ addNew(product, quantity); } } /** * Sample function closured by policy </br> * Higher order function closured by policy function</br> * </br> * Function loads current prices, and prepares offer according to the current availability and given discount * @param discountPolicy * @return */ @Function public Offer calculateOffer(DiscountPolicy discountPolicy) { List<OfferItem> availabeItems = new ArrayList<OfferItem>(); List<OfferItem> unavailableItems = new ArrayList<OfferItem>(); for (ReservationItem item : items) { if (item.getProduct().isAvailabe()){ Discount discount = discountPolicy.applyDiscount(item.getProduct(), item.getQuantity(), item.getProduct().getPrice()); OfferItem offerItem = new OfferItem(item.getProduct().generateSnapshot(), item.getQuantity(), discount); availabeItems.add(offerItem); } else { OfferItem offerItem = new OfferItem(item.getProduct().generateSnapshot(), item.getQuantity()); unavailableItems.add(offerItem); } } return new Offer(availabeItems, unavailableItems); } private void addNew(Product product, int quantity) { ReservationItem item = new ReservationItem(product, quantity); items.add(item); } private void increase(Product product, int quantity) { for (ReservationItem item : items) { if (item.getProduct().equals(product)){ item.changeQuantityBy(quantity); break; } } } public boolean contains(Product product) { for (ReservationItem item : items) { if (item.getProduct().equals(product)) return true; } return false; } public boolean isClosed() { return status.equals(ReservationStatus.CLOSED); } @Invariant({"closed"}) public void close(){ if (isClosed()) domainError("Reservation is already closed"); status = ReservationStatus.CLOSED; } public List<ReservedProduct> getReservedProducts() { ArrayList<ReservedProduct> result = new ArrayList<ReservedProduct>(items.size()); for (ReservationItem item : items) { result.add(new ReservedProduct(item.getProduct().getAggregateId(), item.getProduct().getName(), item.getQuantity(), calculateItemCost(item))); } return result; } private Money calculateItemCost(ReservationItem item){ return item.getProduct().getPrice().multiplyBy(item.getQuantity()); } public ClientData getClientData() { return clientData; } public Date getCreateDate() { return createDate; } public ReservationStatus getStatus() { return status; } }