Skip to content

152 fix portfolio #153

Merged
merged 6 commits into from
May 26, 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
Expand Up @@ -295,15 +295,19 @@ public Transaction sell(BigDecimal amount,
throw new IllegalArgumentException("Player does not own any shares of this stock!");
}

Share ownedPosition = matchingShares.getFirst();
BigDecimal totalOwned = ownedPosition.getQuantity();
BigDecimal totalOwned = matchingShares.stream()
.map(Share::getQuantity)
.reduce(BigDecimal.ZERO, BigDecimal::add);

if (amount.compareTo(totalOwned) > 0) {
amount = totalOwned;
}

Stock stock = ownedPosition.getStock();
Share shareToSell = new Share(stock, amount, stock.getSalesPrice());
Stock stock = matchingShares.getFirst().getStock();

BigDecimal oldestPurchasePrice = matchingShares.getFirst().getPurchasePrice();

Share shareToSell = new Share(stock, amount, oldestPurchasePrice);

Transaction sale = TransactionFactory.createTransaction(
TransactionType.SALE, shareToSell, getWeek()
Expand Down
107 changes: 52 additions & 55 deletions src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Portfolio.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import edu.ntnu.idi.idatt2003.g40.mappe.utils.Validator;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand All @@ -17,9 +18,9 @@
public final class Portfolio {

/**
* Map used to handle internal shares.
* List of shares in portfolio.
* */
private final Map<String, Share> shares = new HashMap<>();
private final List<Share> shares = new ArrayList<>();

/**
* Creates an empty portfolio.
Expand All @@ -39,30 +40,13 @@ public void addShare(final Share share) throws IllegalArgumentException {
if (share == null) {
throw new IllegalArgumentException("Invalid share!");
}
String symbol = share.getStock().getSymbol().toUpperCase();

if (shares.containsKey(symbol)) {
Share existingShare = shares.get(symbol);
BigDecimal totalQuantity =
existingShare.getQuantity().add(share.getQuantity());

shares.put(symbol,
new Share(
share.getStock(),
totalQuantity, existingShare.getPurchasePrice()
)
);
} else {
shares.put(symbol, share);
}
shares.add(share);
}

/**
* Removes a share from the portfolio.
*
* <p>Uses the quantity value to deduct share amount from the map.
* If quantity to remove is equal to amount held, removes share entirely.
* If not, splits the share.</p>
* <p>Removes based on FIFO (First In First Out)</p>
*
* @param share the share to remove
*
Expand All @@ -72,35 +56,42 @@ public void addShare(final Share share) throws IllegalArgumentException {
* @throws IllegalArgumentException if share is null.
*
*/
public boolean removeShare(final Share share)
throws IllegalArgumentException {
public boolean removeShare(final Share share) throws IllegalArgumentException {
if (share == null) {
throw new IllegalArgumentException("Invalid share!");
}
String symbol = share.getStock().getSymbol().toUpperCase();
if (!shares.containsKey(symbol)) {
return false;
}

Share ownedShare = shares.get(symbol);
int comparison = ownedShare.getQuantity().compareTo(share.getQuantity());
String symbol = share.getStock().getSymbol();

if (comparison < 0) {
throw new IllegalArgumentException(
"Cannot remove more shares than are currently owned!");
} else if (comparison == 0) {

shares.remove(symbol);
} else {
BigDecimal remainingQuantity =
ownedShare.getQuantity().subtract(share.getQuantity());
shares.put(symbol,
new Share(
share.getStock(),
remainingQuantity,
ownedShare.getPurchasePrice()
)
);
List<Share> matchingShares = shares.stream()
.filter(s -> s.getStock().getSymbol().equalsIgnoreCase(symbol))
.toList();

BigDecimal quantityToRemove = share.getQuantity();

BigDecimal totalOwned = matchingShares.stream()
.map(Share::getQuantity)
.reduce(BigDecimal.ZERO, BigDecimal::add);

if (quantityToRemove.compareTo(totalOwned) > 0) {
throw new IllegalArgumentException("Cannot remove more shares than are currently owned!");
}

for (Share s : matchingShares) {
if (quantityToRemove.signum() <= 0) {
break;
}

int comparison = s.getQuantity().compareTo(quantityToRemove);
shares.remove(s);

if (comparison > 0) {
BigDecimal remainingQuantity = s.getQuantity().subtract(quantityToRemove);
shares.add(new Share(s.getStock(), remainingQuantity, s.getPurchasePrice()));
quantityToRemove = BigDecimal.ZERO;
} else {
quantityToRemove = quantityToRemove.subtract(s.getQuantity());
}
}
return true;
}
Expand All @@ -111,7 +102,7 @@ public boolean removeShare(final Share share)
* @return a list of shares
*/
public List<Share> getShares() {
return List.copyOf(shares.values());
return shares;
}

/**
Expand Down Expand Up @@ -141,8 +132,9 @@ public List<Share> getShares(final String symbol)
throw new IllegalArgumentException(
Validator.VALID_STOCK_SYMBOL.getErrorMessage());
}
Share share = shares.get(symbol.toUpperCase());
return share != null ? List.of(share) : List.of();
return shares.stream()
.filter(s -> s.getStock().getSymbol().equalsIgnoreCase(symbol))
.toList();
}

/**
Expand All @@ -160,10 +152,13 @@ public boolean contains(final Share share)
if (share == null) {
throw new IllegalArgumentException("Invalid share!");
}
String symbol = share.getStock().getSymbol().toUpperCase();
Share owned = shares.get(symbol);
return owned != null
&& owned.getQuantity().compareTo(share.getQuantity()) >= 0;

BigDecimal totalOwned = shares.stream()
.filter(s -> s.getStock().getSymbol().equalsIgnoreCase(share.getStock().getSymbol()))
.map(Share::getQuantity)
.reduce(BigDecimal.ZERO, BigDecimal::add);

return totalOwned.compareTo(share.getQuantity()) >= 0;
}

/**
Expand All @@ -174,7 +169,7 @@ public boolean contains(final Share share)
* */
public BigDecimal getNetWorth() {
BigDecimal netWorth = BigDecimal.ZERO;
for (Share s : shares.values()) {
for (Share s : shares) {
netWorth = netWorth.add(
s.getQuantity().multiply(s.getStock().getSalesPrice())
);
Expand All @@ -194,7 +189,9 @@ public BigDecimal getTotalShareQuantityBySymbol(final String symbol) {
if (symbol == null) {
return BigDecimal.ZERO;
}
Share share = shares.get(symbol.toUpperCase());
return share != null ? share.getQuantity() : BigDecimal.ZERO;
return shares.stream()
.filter(s -> s.getStock().getSymbol().equalsIgnoreCase(symbol))
.map(Share::getQuantity)
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,8 @@
import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventType;
import edu.ntnu.idi.idatt2003.g40.mappe.view.ViewController;
import edu.ntnu.idi.idatt2003.g40.mappe.view.ViewEnum;

import java.io.File;
import java.util.List;

import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
Expand All @@ -22,12 +20,10 @@
*
* <p>Extends {@link ViewController}</p>
*
* <p>
* Handles four user actions: navigating to the create-game screen,
* <p>Handles four user actions: navigating to the create-game screen,
* going back to the main menu, uploading a custom save file from
* disk, and opening a save when one of the displayed rows is
* clicked.
* </p>
* clicked.</p>
*/
public class PlayGameController extends ViewController<PlayGameView> {

Expand All @@ -38,19 +34,6 @@ public class PlayGameController extends ViewController<PlayGameView> {
* */
private final SaveGameService saveGameService;

/**
* Constructor with a default {@link SaveGameService} pointing at
* the bundled saves directory.
*
* @param view the {@link PlayGameView} this controller is
* attached to.
* @param eventManager the active {@link EventManager}.
*/
public PlayGameController(final PlayGameView view,
final EventManager eventManager) {
this(view, eventManager, new SaveGameService());
}

/**
* Constructor accepting an explicit {@link SaveGameService} for
* tests or custom save locations.
Expand Down Expand Up @@ -122,12 +105,10 @@ protected void initInteractions() {
* Re-reads every save from disk and returns the one whose name
* matches, or {@code null} if no matching save is found.
*
* <p>
* Used to defeat the staleness of the {@link SaveGame} instance
* <p>Used to defeat the staleness of the {@link SaveGame} instance
* bound to the clicked row in {@link PlayGameView}: between when
* {@link #refresh()} last ran and the click, the auto-save flow may
* have written newer content to disk.
* </p>
* have written newer content to disk.</p>
*
* @param name the save display name to look up.
* @return the freshly parsed {@link SaveGame}, or {@code null}.
Expand All @@ -149,10 +130,8 @@ private SaveGame findFreshSaveByName(final String name) {
* disk. Parses it using {@link SaveGameService#loadSaveFromFile} and
* appends the resulting save to the view's current list.
*
* <p>
* Shows an alert if the user picks a file that contains no valid
* save entry.
* </p>
* <p>Shows an alert if the user picks a file that contains no valid
* save entry.</p>
*/
private void handleUploadSave() {
FileChooser fileChooser = new FileChooser();
Expand All @@ -175,13 +154,27 @@ private void handleUploadSave() {
if (uploadedSave == null) {
showAlert(AlertType.WARNING,
"No saves found",
"Den valgte filen inneholdt ingen gyldig save.\n\n"
+ "Forventet JSON-format:\n"
"The chosen file includes an unsupported format!\n\n"
+ "Expected JSON format:\n"
+ " {\n"
+ " \"name\": \"MySave\",\n"
+ " \"balance\": 10000.00,\n"
+ " \"startingCapital\": 10000.00,\n"
+ " \"stockDataPath\": null\n"
+ " \"week\": 1\n"
+ " \"ownedShares\": [\n"
+ " { \"symbol\": \"AAPL\", \"quantity\": 5, \"purchasePrice\": 150.00 }\n"
+ " ]\n"
+ " \"transactions\": [\n"
+ " { \"type\": \"PURCHASE\", \"symbol\": \"AAPL\", \"quantity\": 5, \"price\": 150.00, \"week\": 1}\n"
+ " ]\n"
+ " \"stocks\": [\n"
+ " { \"symbol\": \"AAPL\", \"companyName\": \"Apple inc.\", \"salesPrice\": 155.25}\n"
+ " ]\n"
+ " \"netWorthHistory\": [\n"
+ " 1001.10,\n"
+ " 1025.24,\n"
+ " ]\n"
+ " }");
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,6 @@ public void handleContextUpdate(final Exchange criticalExchange, final Player ac
this.playerNetWorthHistory.addAll(this.player.getNetWorthHistory());
} else {
this.playerNetWorthHistory.add(this.player.getStartingMoney());
this.playerNetWorthHistory.add(this.player.getNetWorth());
}

getViewElement().setWeek(this.exchange.getWeek());
Expand Down
13 changes: 0 additions & 13 deletions src/main/resources/saves/Halleluja.json

This file was deleted.

Loading