From b705e59a4e6f09b7ff3afbd2a7318f87a5b18b91 Mon Sep 17 00:00:00 2001 From: = Date: Fri, 15 May 2026 12:05:27 +0200 Subject: [PATCH] Feat: Added sell functionality Made it possible to sell shares based on a stock. This works by getting total amount of shares owned on the stock, and splitting shares if the required amount to sell does not divide the total amount owned. --- .../idatt2003/g40/mappe/engine/Exchange.java | 51 +++++++++++++++++-- .../idi/idatt2003/g40/mappe/model/Player.java | 17 ++++++- .../idatt2003/g40/mappe/model/Portfolio.java | 37 ++++++++++++++ .../dashboard/DashBoardController.java | 20 ++++++-- .../financialsummary/SummaryController.java | 4 ++ .../widgets/financialsummary/SummaryView.java | 2 +- 6 files changed, 121 insertions(+), 10 deletions(-) diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/Exchange.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/Exchange.java index 887fc27..744bdce 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/Exchange.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/Exchange.java @@ -6,9 +6,7 @@ import edu.ntnu.idi.idatt2003.g40.mappe.model.Share; import edu.ntnu.idi.idatt2003.g40.mappe.model.Stock; import edu.ntnu.idi.idatt2003.g40.mappe.model.Transaction; -import edu.ntnu.idi.idatt2003.g40.mappe.service.PurchaseCalculator; -import edu.ntnu.idi.idatt2003.g40.mappe.service.SaleCalculator; -import edu.ntnu.idi.idatt2003.g40.mappe.service.TransactionCalculator; +import edu.ntnu.idi.idatt2003.g40.mappe.service.*; import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventData; import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventManager; import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventPublisher; @@ -199,6 +197,53 @@ public Transaction sell(final Share share, final Player player) return sale; } + /** + * Method called when a player sells share. + * + * @param amount the amount of "shares" to sell. + * @param stockSymbol the stock to sell shares in. + * @param player the player buying stock. + * + * @return Transaction representing the transaction. + * + * @throws IllegalArgumentException if any parameter is null, or if player does not have enough shares. + * */ + public List sell(final BigDecimal amount, final String stockSymbol, final Player player) + throws IllegalArgumentException { + if (amount == null || player == null || !Validator.NOT_EMPTY.isValid(stockSymbol)) { + throw new IllegalArgumentException("Invalid sell!"); + } else { + + List sharesOfStock = player.getPortfolio().getShares().stream() + .filter(s -> s.getStock().getSymbol().equals(stockSymbol)) + .toList(); + + BigDecimal totalOwned = player.getPortfolio().getTotalSharesBySymbol(stockSymbol); + + if (amount.compareTo(totalOwned) > 0) { + throw new IllegalArgumentException("Not enough shares!"); + } + ArrayList transactions = new ArrayList<>(); + BigDecimal remainingToSell = amount; + + for (Share share : sharesOfStock) { + if (remainingToSell.compareTo(BigDecimal.ZERO) <= 0) break; + + BigDecimal shareQty = share.getQuantity(); + + if (shareQty.compareTo(remainingToSell) <= 0) { + remainingToSell = remainingToSell.subtract(shareQty); + transactions.add(sell(share, player)); + } else { + Share newShare = player.getPortfolio().splitShare(share, remainingToSell); + remainingToSell = BigDecimal.ZERO; + transactions.add(sell(newShare, player)); + } + } + return transactions; + } + } + /** * Method for advancing time, increasing the amount of weeks. * */ diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Player.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Player.java index 27af458..999a316 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Player.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Player.java @@ -41,6 +41,11 @@ public final class Player { * */ private final FloatProperty networthAsFloatProp = new SimpleFloatProperty(0); + /** + * Current money of player as a listenable {@link FloatProperty} object. + * */ + private final FloatProperty moneyAsFloatProp = new SimpleFloatProperty(0); + /** * The players' portfolio, holding their shares. * */ @@ -68,6 +73,7 @@ public Player(final String name, final BigDecimal startingMoney) throws IllegalA this.startingMoney = startingMoney; this.money = this.startingMoney; this.networthAsFloatProp.setValue(this.startingMoney); + this.moneyAsFloatProp.setValue(this.startingMoney); this.portfolio = new Portfolio(); this.transactionArchive = new TransactionArchive(); } @@ -115,7 +121,6 @@ public void addMoney(final BigDecimal amount) { */ public void withdrawMoney(final BigDecimal amount) { money = money.subtract(amount); - } /** @@ -155,6 +160,15 @@ public FloatProperty getNetWorthAsFloatProperty() { return networthAsFloatProp; } + /** + * Get money as a {@link FloatProperty} object, allowing listening for changes. + * + * @return FloatProperty. + * */ + public FloatProperty getMoneyAsFloatProperty() { + return moneyAsFloatProp; + } + /** * Getter method for players' current status. * @@ -182,6 +196,7 @@ public void handleTransaction(final Transaction transaction) { portfolio.removeShare(sale.getShare()); } networthAsFloatProp.setValue(getNetWorth().floatValue()); + moneyAsFloatProp.setValue(money); transaction.commit(this); } } diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Portfolio.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Portfolio.java index f83ea00..483adb8 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Portfolio.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Portfolio.java @@ -123,4 +123,41 @@ public BigDecimal getNetWorth() { } return netWorth; } + + /** + * Helper method to get total amount of shares owned in a specific stock. + * + * @param symbol the symbol of the stock to check for shares. + * */ + public BigDecimal getTotalSharesBySymbol(final String symbol) { + return shares.stream() + .filter(s -> s.getStock().getSymbol().equals(symbol)) + .map(Share::getQuantity) + .reduce(BigDecimal.ZERO, BigDecimal::add); + } + + /** + * "Splits" a share in two pieces based on an amount. + * + * @param share the share to split. + * @param splitAmount the amount to split by. + * + * @return the split share from the original to the split amount. + * + * @throws IllegalArgumentException if share or split amount is invalid. + * */ + public Share splitShare(final Share share, final BigDecimal splitAmount) + throws IllegalArgumentException { + if (!contains(share) || splitAmount.compareTo(share.getQuantity()) > 0) { + throw new IllegalArgumentException("Cannot split share!"); + } + BigDecimal remainingAmount = share.getQuantity().subtract(splitAmount); + + Share newShare1 = new Share(share.getStock(), splitAmount, share.getPurchasePrice()); + Share newShare2 = new Share(share.getStock(), remainingAmount, share.getPurchasePrice()); + removeShare(share); + addShare(newShare1); + addShare(newShare2); + return newShare1; + } } diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/dashboard/DashBoardController.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/dashboard/DashBoardController.java index 6aa9f86..87801b1 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/dashboard/DashBoardController.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/dashboard/DashBoardController.java @@ -10,6 +10,7 @@ import java.math.BigDecimal; import java.util.List; +import java.util.Optional; public class DashBoardController extends ViewController { @@ -44,11 +45,8 @@ private void populateStockList() { getViewElement().setOnStockAction(stockBtn, s, (stock)-> { - BigDecimal amountOfSharesOwnedThisStock = player.getPortfolio().getShares().stream() - .filter(share -> share.getStock().equals(s)) - .map(Share::getQuantity) - .reduce(BigDecimal.ZERO, BigDecimal::add); - handleStockSelection(stock, amountOfSharesOwnedThisStock.floatValue()); + BigDecimal amountOfSharesOwned = player.getPortfolio().getTotalSharesBySymbol(s.getSymbol()); + handleStockSelection(stock, amountOfSharesOwned.floatValue()); }); } } @@ -72,6 +70,18 @@ protected void initInteractions() { } }); + getViewElement().setOnAction(DashBoardActions.SELL_SHARES, () -> { + if (Validator.NOT_EMPTY.isValid(getViewElement().getQuantityInputField().getText())) { + List transactions = exchange.sell(new BigDecimal(getViewElement().getQuantityInputField().getText()), getViewElement().getCurrentStock().getSymbol(), player); + + for (Transaction t : transactions) { + if(t.isCommited()) { + getViewElement().addOwnedShares(-t.getShare().getQuantity().floatValue()); + } + } + } + }); + exchange.weekProperty().addListener((observable,o,n) -> { getViewElement().updateGraph(); }); diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/financialsummary/SummaryController.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/financialsummary/SummaryController.java index cd6c58a..ee3b88e 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/financialsummary/SummaryController.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/financialsummary/SummaryController.java @@ -46,5 +46,9 @@ protected void initInteractions() { player.getNetWorthAsFloatProperty().addListener((observable, o, n) -> { getViewElement().setBalance(player.getMoney().floatValue(), player.getNetWorth().floatValue()); }); + + player.getMoneyAsFloatProperty().addListener((observable, o, n) -> { + getViewElement().setBalance(player.getMoney().floatValue(), player.getNetWorth().floatValue()); + }); } } diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/financialsummary/SummaryView.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/financialsummary/SummaryView.java index 83a30e7..092e407 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/financialsummary/SummaryView.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/financialsummary/SummaryView.java @@ -109,7 +109,7 @@ protected void initStyling() { } public void setBalance(final float money, final float netWorth) { - balanceLabel.setText(Math.round(money*100f)/100f + "$ / " + Math.round(netWorth*100f)/100f + "$"); + balanceLabel.setText(Math.round(money*100f)/100f + "NOK / " + Math.round(netWorth*100f)/100f + "NOK"); } public void setWeek(int week) {