Skip to content

adds purchase and sale functionality with proper handling #88

Merged
merged 1 commit into from
May 12, 2026
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,31 +1,108 @@
package edu.ntnu.idi.idatt2003.gruppe42.Controller.AppControllers;

import edu.ntnu.idi.idatt2003.gruppe42.Controller.MarketController;
import edu.ntnu.idi.idatt2003.gruppe42.Model.Player;
import edu.ntnu.idi.idatt2003.gruppe42.Model.Share;
import edu.ntnu.idi.idatt2003.gruppe42.Model.Stock;
import edu.ntnu.idi.idatt2003.gruppe42.Model.Transaction.Transaction;
import edu.ntnu.idi.idatt2003.gruppe42.View.Apps.StockApp;
import java.math.BigDecimal;

public class StockAppController implements AppController {
private MarketController marketController;
private StockApp stockPopup;
private Stock currentStock;

public StockAppController(StockApp stockPopup, MarketController marketController) {
public StockAppController(StockApp stockPopup, MarketController marketController, Player player) {
this.stockPopup = stockPopup;
this.marketController = marketController;

stockPopup.getStockList().getItems().addAll(marketController.getMarket());

stockPopup.getSearchField().textProperty().addListener((obs, old, newValue) -> {
stockPopup.getStockList().getItems().addAll(marketController.getExchange().getAllStocks());

stockPopup.getSearchField().textProperty().addListener((
obs, old, newValue) -> {
if (stockPopup.getCurrentStock() != null) {
stockPopup.openSearchPage();
}
stockPopup.getStockList().getItems().setAll(marketController.getMarket(newValue));
});

stockPopup.getBuyButton().setOnMouseClicked(event -> {
currentStock = stockPopup.getCurrentStock();
if (currentStock == null) {
return;
}

String quantityText = stockPopup.getQuantityField().getText();
BigDecimal quantity;
try {
quantity = new BigDecimal(quantityText);
if (quantity.compareTo(BigDecimal.ZERO) <= 0) {
return;
}
} catch (NumberFormatException e) {
return;
}

Transaction transaction = marketController.getExchange().buy(
currentStock.getSymbol(), quantity, player
);

if (transaction == null) {
return;
}

player.getTransactionArchive().add(transaction);
player.getPortfolio().addShare(transaction.getShare());
});

stockPopup.getSellButton().setOnMouseClicked(event -> {
currentStock = stockPopup.getCurrentStock();
if (currentStock == null) {
return;
}

String quantityText = stockPopup.getQuantityField().getText();
BigDecimal quantityToSell;
try {
quantityToSell = new BigDecimal(quantityText);
if (quantityToSell.compareTo(BigDecimal.ZERO) <= 0) {
return;
}
} catch (NumberFormatException e) {
return;
}

player.getPortfolio().getShares().stream()
.filter(s -> s.getStock().getSymbol().equals(currentStock.getSymbol()))
.findFirst()
.ifPresent(existingShare -> {
if (existingShare.getQuantity().compareTo(quantityToSell) < 0) {
return;
}

Transaction transaction = marketController.getExchange().sell(existingShare, quantityToSell, player);
player.getTransactionArchive().add(transaction);
player.getPortfolio().removeShare(transaction.getShare());
});
});

}

/**
* Updates the app state on each simulation tick.
* Refreshes the market data and updates the price label for the currently viewed stock.
*/
@Override
public void nextTick(){
public void nextTick() {
System.out.println("[DEBUG] StockAppController.nextTick() - Updating Market");
marketController.updateMarket();
if (stockPopup.getCurrentStock() != null) {
System.out.println("[DEBUG] Updating Price Label for: " + stockPopup.getCurrentStock().getCompany());
stockPopup.updatePriceLabel(stockPopup.getCurrentStock());
Stock currentStockInView = stockPopup.getCurrentStock();
if (currentStockInView != null) {
System.out.println("[DEBUG] Updating Price Label for: "
+ currentStockInView.getCompany());
stockPopup.updatePriceLabel(currentStockInView);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import edu.ntnu.idi.idatt2003.gruppe42.Controller.AppControllers.AppController;
import edu.ntnu.idi.idatt2003.gruppe42.Model.Player;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package edu.ntnu.idi.idatt2003.gruppe42.Controller;

import edu.ntnu.idi.idatt2003.gruppe42.Model.Exchange;
import edu.ntnu.idi.idatt2003.gruppe42.Model.Stock;
import edu.ntnu.idi.idatt2003.gruppe42.Model.StockFileHandler;
import edu.ntnu.idi.idatt2003.gruppe42.View.Apps.StockApp;
Expand All @@ -12,7 +13,7 @@

public class MarketController {

private List<Stock> market;
private Exchange exchange;
private int stockResolution;
private StockApp stockApp;

Expand All @@ -21,7 +22,7 @@ public MarketController() {

try {
Path path = Path.of(getClass().getClassLoader().getResource("stocks.csv").toURI());
market = StockFileHandler.readFromFile(path);
exchange = new Exchange(StockFileHandler.readFromFile(path));

startMarket();
System.out.println("Market loaded");
Expand All @@ -31,19 +32,18 @@ public MarketController() {
}
}

public List<Stock> getMarket() {
return market;
public Exchange getExchange() {
return exchange;
}

public List<Stock> getMarket(String searchTerm) {
return market.stream()
.filter(stock -> stock.getCompany().toLowerCase().contains(searchTerm.toLowerCase()))
.toList();
return exchange.findStocks(searchTerm);
}

public void updateMarket() {
System.out.println("[DEBUG] MarketController.updateMarket() - Updating prices for " + market.size() + " stocks");
for (Stock stock : market) {
System.out.println("[DEBUG] MarketController.updateMarket() - Updating prices for "
+ exchange.getAllStocks().size() + " stocks");
for (Stock stock : exchange.getAllStocks()) {
stock.updatePrice();
if (stock.getStockGraph().getVisibility()) {
stock.updateStockGraph();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public DesktopViewController(Player player, GameController gameController) {

StockApp stockApp = new StockApp(400, 300, 120, 120);
MarketController marketController = new MarketController();
gameController.addAppController(new StockAppController(stockApp, marketController));
gameController.addAppController(new StockAppController(stockApp, marketController, player));

MailApp mailApp = new MailApp(400, 300, 160, 160);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,52 @@ public SaleCalculator(Share share) {
this.quantity = share.getQuantity();
}

/**
* Calculates the gross value of the sale (quantity * sales price).
*
* @return the gross sale value
*/
@Override
public BigDecimal calculateGross() {
return salesPrice.multiply(quantity);
}

/**
* Calculates the commission for the sale (1% of gross).
*
* @return the commission amount
*/
@Override
public BigDecimal calculateCommission() {
BigDecimal commissionRate = new BigDecimal("0.01");
return calculateGross().multiply(commissionRate);
}

/**
* Calculates the tax on the capital gain (22% of profit).
* Profit is defined as (sales price - purchase price) * quantity.
* If there is no profit, tax is zero (clamped by max(ZERO) in a robust implementation,
* but here we follow existing logic).
*
* @return the tax amount
*/
@Override
public BigDecimal calculateTax() {
BigDecimal taxRate = new BigDecimal("0.22");
BigDecimal differencePrice = salesPrice.subtract(purchasePrice);
BigDecimal difference = differencePrice.multiply(quantity);
return difference.multiply(taxRate);
BigDecimal priceDifference = salesPrice.subtract(purchasePrice);
BigDecimal totalProfit = priceDifference.multiply(quantity);
// Only tax if there is a profit
if (totalProfit.compareTo(BigDecimal.ZERO) <= 0) {
return BigDecimal.ZERO;
}
return totalProfit.multiply(taxRate);
}

/**
* Calculates the net total received from the sale (gross - commission - tax).
*
* @return the net sale amount
*/
@Override
public BigDecimal calculateTotal() {
return calculateGross().subtract(calculateCommission()).subtract(calculateTax());
Expand Down
66 changes: 37 additions & 29 deletions src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Exchange.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,16 @@
* </p>
*/
public class Exchange {
private String name;
private int week;
private Map<String, Stock> stockMap;
private Random random;

/**
* Constructs a new {@code Exchange} with a name and a list of stocks.
*
* @param name the name of the exchange (e.g., "NYSE")
* @param stocks the initial list of available stocks on the exchange
*/
public Exchange(String name, List<Stock> stocks) {
this.name = name;
public Exchange(List<Stock> stocks) {
this.week = 0;
this.stockMap = new HashMap<String, Stock>();
this.random = new Random();
Expand All @@ -40,10 +37,6 @@ public Exchange(String name, List<Stock> stocks) {
}
}

public String getName() {
return name;
}

public int getWeek() {
return week;
}
Expand All @@ -69,20 +62,33 @@ public Stock getStock(String symbol) {
}

/**
* Finds stocks by company name.
* Finds stocks by company name or symbol.
*
* @param searchTerm the company name to search for
* @return a list of {@code Stock} objects whose company name matches the search term
* @param searchTerm the term to search for (case-insensitive)
* @return a list of {@code Stock} objects matching the search term, sorted alphabetically
*/
public List<Stock> findStocks(String searchTerm) {
List<Stock> foundStocks = new ArrayList<>();

for (Stock stock : stockMap.values()) {
if (stock.getCompany().equals(searchTerm)) {
foundStocks.add(stock);
}
if (searchTerm == null || searchTerm.isBlank()) {
return getAllStocks();
}
return foundStocks;

String lowerSearchTerm = searchTerm.toLowerCase();
return stockMap.values().stream()
.filter(stock -> stock.getCompany().toLowerCase().contains(lowerSearchTerm)
|| stock.getSymbol().toLowerCase().contains(lowerSearchTerm))
.sorted((a, b) -> a.getCompany().compareToIgnoreCase(b.getCompany()))
.toList();
}

/**
* Returns all available stocks on the exchange, sorted alphabetically by company name.
*
* @return a sorted list of all {@code Stock} objects
*/
public List<Stock> getAllStocks() {
return stockMap.values().stream()
.sorted((a, b) -> a.getCompany().compareToIgnoreCase(b.getCompany()))
.toList();
}

/**
Expand All @@ -97,33 +103,35 @@ public List<Stock> findStocks(String searchTerm) {
* @return a {@code Purchase} transaction representing the purchase
*/
public Transaction buy(String symbol, BigDecimal quantity, Player player) {
System.out.println("[DEBUG] Exchange " + name + " - Buying " + quantity + " of " + symbol + " for player " + player.getName());
player.withdrawMoney(quantity);
System.out.println("[DEBUG] Exchange" + " Buying " + quantity + " of " + symbol + " for player " + player.getName());
Stock stock = getStock(symbol);
BigDecimal purchasePrice = stock.getSalesPrice();
player.withdrawMoney(quantity.multiply(purchasePrice));

Share share = new Share(stock, quantity, purchasePrice);
System.out.println("[DEBUG] Purchase complete: " + quantity + " shares of " + symbol + " at " + purchasePrice);
return new Purchase(share, week);
}

/**
* Allows a player to sell a share on the exchange.
* <p>
* The player receives money based on the sale calculation, and a {@code Sale} transaction is returned.
* </p>
* Allows a player to sell a portion of a share position on the exchange.
*
* @param share the share to sell
* @param share the share position from which to sell
* @param quantity the amount of stock to sell
* @param player the player performing the sale
* @return a sale transaction representing the sale
*/
public Transaction sell(Share share, Player player) {
System.out.println("[DEBUG] Exchange " + name + " - Selling shares of " + share.getStock().getSymbol() + " for player " + player.getName());
SaleCalculator saleCalculator = new SaleCalculator(share);
public Transaction sell(Share share, BigDecimal quantity, Player player) {
System.out.println("[DEBUG] Exchange - Selling " + quantity + " shares of "
+ share.getStock().getSymbol() + " for player " + player.getName());

Share shareToSell = new Share(share.getStock(), quantity, share.getPurchasePrice());
SaleCalculator saleCalculator = new SaleCalculator(shareToSell);
BigDecimal totalValue = saleCalculator.calculateTotal();
player.addMoney(totalValue);

System.out.println("[DEBUG] Sale complete: Received " + totalValue);
return new Sale(share, week);
return new Sale(shareToSell, week);
}

/**
Expand Down
Loading