Skip to content

Commit

Permalink
Merge pull request #88 from einaskoi/per/transactions
Browse files Browse the repository at this point in the history
adds purchase and sale functionality with proper handling
  • Loading branch information
einaskoi authored May 12, 2026
2 parents 5799ef4 + 2766af7 commit 36ed70c
Show file tree
Hide file tree
Showing 12 changed files with 281 additions and 90 deletions.
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

0 comments on commit 36ed70c

Please sign in to comment.