-
-
Save abelaska/9a54f104f1f27b8d973cba0be9669cfa to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import lombok.*; | |
import java.math.BigDecimal; | |
import java.math.RoundingMode; | |
import java.time.LocalTime; | |
import java.util.*; | |
import java.util.concurrent.atomic.AtomicLong; | |
class Main { | |
static AtomicLong billIdCounter = new AtomicLong(); | |
static AtomicLong itemIdCounter = new AtomicLong(); | |
static AtomicLong tableIdCounter = new AtomicLong(); | |
static AtomicLong cashRegisterIdCounter = new AtomicLong(); | |
public enum PlateSize { | |
NORMAL, BIG | |
} | |
@EqualsAndHashCode | |
static class Item { | |
@Getter | |
final long id; | |
@Getter | |
final String name; | |
@Getter | |
final transient BigDecimal price; | |
@Getter | |
final transient ItemAction action; | |
@Getter | |
final transient ActionPrice actionPrice; | |
@Getter | |
final transient Optional<PlateSize> plateSize; | |
public BigDecimal getDiscountedPrice() { | |
return this.actionPrice.discountedPrice(this.price); | |
} | |
public Item(String name, BigDecimal price, ItemAction action, ActionPrice actionPrice) { | |
this(name, price, action, actionPrice, null); | |
} | |
public Item(String name, BigDecimal price, ItemAction action, ActionPrice actionPrice, PlateSize plateSize) { | |
this(itemIdCounter.getAndIncrement(), name, price, action, actionPrice, plateSize); | |
} | |
public Item(long id, String name, BigDecimal price, ItemAction action, ActionPrice actionPrice, PlateSize plateSize) { | |
this.id = id; | |
this.name = name; | |
this.price = price; | |
this.action = action; | |
this.actionPrice = actionPrice; | |
this.plateSize = Optional.ofNullable(plateSize); | |
} | |
} | |
@EqualsAndHashCode | |
@RequiredArgsConstructor | |
static class OrderedItem { | |
@Getter | |
final Bill bill; | |
@Getter | |
final Item item; | |
@Getter | |
final BigDecimal price; | |
@Getter | |
long quantity = 0; | |
public static OrderedItem create(Bill bill, Item item) { | |
return new OrderedItem(bill, item, item.getDiscountedPrice()); | |
} | |
public void returnBackToWaiter() { | |
this.getItem().getAction().returnBackToWaiter(this.getBill().table, item); | |
} | |
public void serve() { | |
var optionalPlateSize = this.item.getPlateSize(); | |
// put food to plate size ... | |
this.quantity++; | |
} | |
public void cancel() { | |
if (this.quantity > 0) { | |
this.quantity--; | |
} else { | |
throw new RuntimeException("There's nothing to cancel"); | |
} | |
} | |
} | |
@RequiredArgsConstructor | |
static class Bill { | |
@Getter | |
final long id; | |
@Getter | |
final Table table; | |
@Getter | |
final Set<OrderedItem> orderedItems = new HashSet<>(); | |
public static Bill create(Table table) { | |
return new Bill(billIdCounter.getAndIncrement(), table); | |
} | |
public OrderedItem serve(Item item) { | |
OrderedItem orderedItem; | |
var optionalOrderedItem = this.findOrderedItem(item); | |
if (optionalOrderedItem.isEmpty()) { | |
orderedItem = OrderedItem.create(this, item); | |
orderedItems.add(orderedItem); | |
} else { | |
orderedItem = optionalOrderedItem.get(); | |
} | |
orderedItem.serve(); | |
return orderedItem; | |
} | |
public Optional<OrderedItem> findOrderedItem(Item item) { | |
return this.orderedItems.stream().filter((it) -> item.equals(it.getItem())).findFirst(); | |
} | |
public OrderedItem returnOrderedItem(Item item) { | |
var orderedItem = this.findOrderedItem(item).orElseThrow(() -> new RuntimeException("Item " + item.getId() + " is not on the bill " + this.getId())); | |
orderedItem.cancel(); | |
if (orderedItem.getQuantity() == 0) { | |
this.orderedItems.remove(orderedItem); | |
} | |
return orderedItem; | |
} | |
} | |
@EqualsAndHashCode | |
@RequiredArgsConstructor | |
static class Table { | |
@Getter | |
final long id; | |
@Getter | |
final String name; | |
@Getter | |
final Set<Bill> bills = new HashSet<>(); | |
public static Table create(String name) { | |
return new Table(tableIdCounter.getAndIncrement(), name); | |
} | |
public Optional<Bill> findBillWithItem(Item item) { | |
return this.bills.stream().filter((bill) -> bill.findOrderedItem(item).isPresent()).findFirst(); | |
} | |
public OrderedItem serve(Item item) { | |
if (this.bills.isEmpty()) { | |
this.bills.add(Bill.create(this)); | |
} | |
var bill = this.bills.iterator().next(); | |
return this.serve(bill, item); | |
} | |
public OrderedItem serve(Bill bill, Item item) { | |
return bill.serve(item); | |
} | |
} | |
@RequiredArgsConstructor | |
static class CashRegister { | |
@Getter | |
final long id; | |
@Getter | |
final String name; | |
@Getter | |
final Set<Item> items = new HashSet<>(); | |
@Getter | |
final Set<Table> tables = new HashSet<>(); | |
public static CashRegister create(String name) { | |
return new CashRegister(cashRegisterIdCounter.getAndIncrement(), name); | |
} | |
public Table registerTable(Table table) { | |
this.tables.add(table); | |
return table; | |
} | |
public Item registerItem(Item item) { | |
this.items.add(item); | |
return item; | |
} | |
} | |
interface ActionPrice { | |
String getDescription(); | |
BigDecimal discountedPrice(BigDecimal price); | |
} | |
static abstract class TimeRangeActionPrice implements ActionPrice { | |
@Getter | |
final int discountPercent; | |
@Getter | |
final LocalTime from; | |
@Getter | |
final LocalTime until; | |
public TimeRangeActionPrice(final int discountPercent, final LocalTime from, final LocalTime until) { | |
this.discountPercent = discountPercent; | |
this.from = from; | |
this.until = until; | |
if (discountPercent < 0 || discountPercent > 100) { | |
throw new RuntimeException("Discount percentage range is 0-100, invalid value: " + discountPercent); | |
} | |
} | |
@Override | |
public String getDescription() { | |
return this.discountPercent + "% between " + this.from.toString() + " - " + this.until.toString(); | |
} | |
@Override | |
public BigDecimal discountedPrice(BigDecimal price) { | |
if (this.shouldApplyDiscount()) { | |
return price.multiply(BigDecimal.valueOf(100 - this.discountPercent)).divide(BigDecimal.valueOf(100), RoundingMode.FLOOR); | |
} | |
return price; | |
} | |
boolean shouldApplyDiscount() { | |
var now = LocalTime.now(); | |
return now.isAfter(this.from) && now.isBefore(this.until); | |
} | |
} | |
static class DrinkActionPrice extends TimeRangeActionPrice { | |
public DrinkActionPrice() { | |
super(30, LocalTime.of(14, 0), LocalTime.of(16, 0)); | |
} | |
} | |
static class MainCourseActionPrice extends TimeRangeActionPrice { | |
public MainCourseActionPrice() { | |
super(20, LocalTime.of(11, 0), LocalTime.of(13, 0)); | |
} | |
} | |
static class CrispyChicken extends Item { | |
public CrispyChicken(ItemAction action, ActionPrice actionPrice) { | |
super("Crispy Chicken", BigDecimal.valueOf(15), action, actionPrice, PlateSize.NORMAL); | |
} | |
} | |
static class CrispyChickenBig extends Item { | |
public CrispyChickenBig(ItemAction action, ActionPrice actionPrice) { | |
super("Crispy Chicken BIG", BigDecimal.valueOf(20), action, actionPrice, PlateSize.BIG); | |
} | |
} | |
static class RedBull extends Item { | |
public RedBull(ItemAction action, ActionPrice actionPrice) { | |
super("RedBull", BigDecimal.valueOf(10), action, actionPrice); | |
} | |
} | |
interface ItemAction { | |
void returnBackToWaiter(Table table, Item item); | |
void eat(); | |
void drink(); | |
} | |
@RequiredArgsConstructor | |
static abstract class AbstractItemAction implements ItemAction { | |
@Getter | |
transient boolean canReturn = true; | |
public void returnBackToWaiter(Table table, Item item) { | |
if (this.canReturn) { | |
table.findBillWithItem(item).orElseThrow(() -> new RuntimeException("Item is no longer on any of the table bills")).returnOrderedItem(item); | |
} else { | |
throw new RuntimeException("This order cannot be returned anymore, already consumed ;)"); | |
} | |
} | |
protected void consumed() { | |
this.canReturn = false; | |
} | |
} | |
static abstract class AbstractDrinkItemAction extends AbstractItemAction { | |
@Override | |
public void drink() { | |
// glo glo | |
this.consumed(); | |
} | |
public void eat() { | |
throw new RuntimeException("You cannot eat a drink :)"); | |
} | |
} | |
static abstract class AbstractMainCourseItemAction extends AbstractItemAction { | |
@Override | |
public void drink() { | |
throw new RuntimeException("You cannot drink a main course :)"); | |
} | |
public void eat() { | |
// yummy yummy | |
this.consumed(); | |
} | |
} | |
static class CrispyChickenAction extends AbstractMainCourseItemAction { | |
} | |
static class RedBullAction extends AbstractDrinkItemAction { | |
} | |
public static void main(String[] args) throws Exception { | |
var cashRegister1 = CashRegister.create("Cash Register 1"); | |
var table1 = cashRegister1.registerTable(Table.create("Table 1")); | |
var table2 = cashRegister1.registerTable(Table.create("Table 2")); | |
var drinkActionPrice = new DrinkActionPrice(); | |
var mainCourseActionPrice = new MainCourseActionPrice(); | |
var crispyChickenAction = new CrispyChickenAction(); | |
var redbullAction = new RedBullAction(); | |
var crispyChickenBig = cashRegister1.registerItem(new CrispyChickenBig(crispyChickenAction, mainCourseActionPrice)); | |
var crispyChicken = cashRegister1.registerItem(new CrispyChicken(crispyChickenAction, mainCourseActionPrice)); | |
var redBull = cashRegister1.registerItem(new RedBull(redbullAction, drinkActionPrice)); | |
var orderedRedBull = table1.serve(redBull); | |
orderedRedBull.getItem().getAction().drink(); | |
var orderedCrispyChicken = table2.serve(crispyChicken); | |
orderedCrispyChicken.returnBackToWaiter(); | |
var orderedCrispyChickenBig = table2.serve(crispyChickenBig); | |
orderedCrispyChickenBig.getItem().getAction().eat(); | |
orderedCrispyChickenBig.returnBackToWaiter(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment