Skip to content

Instantly share code, notes, and snippets.

@abelaska
Forked from aukrocz/ukol.java
Last active January 3, 2023 15:41
Show Gist options
  • Save abelaska/9a54f104f1f27b8d973cba0be9669cfa to your computer and use it in GitHub Desktop.
Save abelaska/9a54f104f1f27b8d973cba0be9669cfa to your computer and use it in GitHub Desktop.
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