From 5ada5390d6b9c1038afdb03167714f2aa165f017 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Thu, 9 Jan 2025 20:50:02 -0600 Subject: [PATCH 1/3] Remove `TotalEarnings` tracking and associated functionality. Removed the `TotalEarnings` field, related methods, and all references in the codebase. This includes updates to personnel salary payments, campaign options, financial transactions, and UI components. The feature is no longer needed and simplifies code management. --- MekHQ/src/mekhq/campaign/Campaign.java | 24 ++------ MekHQ/src/mekhq/campaign/CampaignOptions.java | 19 ------ .../src/mekhq/campaign/finances/Finances.java | 59 ++++++------------- .../src/mekhq/campaign/personnel/Person.java | 51 ---------------- .../adapter/PersonnelTableMouseAdapter.java | 6 -- .../mekhq/gui/panes/CampaignOptionsPane.java | 2 - MekHQ/src/mekhq/gui/view/PersonViewPanel.java | 34 +---------- 7 files changed, 27 insertions(+), 168 deletions(-) diff --git a/MekHQ/src/mekhq/campaign/Campaign.java b/MekHQ/src/mekhq/campaign/Campaign.java index dfa601762f..3ce1f65072 100644 --- a/MekHQ/src/mekhq/campaign/Campaign.java +++ b/MekHQ/src/mekhq/campaign/Campaign.java @@ -7830,25 +7830,13 @@ public void completeMission(@Nullable Mission mission, MissionStatus status) { ResourceBundle financeResources = ResourceBundle.getBundle("mekhq.resources.Finances", MekHQ.getMHQOptions().getLocale()); - Money shares = remainingMoney.multipliedBy(contract.getSharesPercent()).dividedBy(100); - remainingMoney = remainingMoney.minus(shares); + if (remainingMoney.isGreaterThan(Money.zero())) { + Money shares = remainingMoney.multipliedBy(contract.getSharesPercent()).dividedBy(100); + remainingMoney = remainingMoney.minus(shares); - if (getFinances().debit(TransactionType.SALARIES, getLocalDate(), shares, - String.format(financeResources.getString("ContractSharePayment.text"), contract.getName()))) { - addReport(financeResources.getString("DistributedShares.text"), shares.toAmountAndSymbolString()); - - if (getCampaignOptions().isTrackTotalEarnings()) { - boolean sharesForAll = getCampaignOptions().isSharesForAll(); - - int numberOfShares = getActivePersonnel().stream() - .mapToInt(person -> person.getNumShares(this, sharesForAll)) - .sum(); - - Money singleShare = shares.dividedBy(numberOfShares); - - for (Person person : getActivePersonnel()) { - person.payPersonShares(this, singleShare, sharesForAll); - } + if (getFinances().debit(TransactionType.SALARIES, getLocalDate(), shares, + String.format(financeResources.getString("ContractSharePayment.text"), contract.getName()))) { + addReport(financeResources.getString("DistributedShares.text"), shares.toAmountAndSymbolString()); } } } diff --git a/MekHQ/src/mekhq/campaign/CampaignOptions.java b/MekHQ/src/mekhq/campaign/CampaignOptions.java index 7c434f1326..879ec23c76 100644 --- a/MekHQ/src/mekhq/campaign/CampaignOptions.java +++ b/MekHQ/src/mekhq/campaign/CampaignOptions.java @@ -211,7 +211,6 @@ public static String getTransitUnitName(final int unit) { private TimeInDisplayFormat timeInServiceDisplayFormat; private boolean useTimeInRank; private TimeInDisplayFormat timeInRankDisplayFormat; - private boolean trackTotalEarnings; private boolean trackTotalXPEarnings; private boolean showOriginFaction; @@ -745,7 +744,6 @@ public CampaignOptions() { setTimeInServiceDisplayFormat(TimeInDisplayFormat.YEARS); setUseTimeInRank(false); setTimeInRankDisplayFormat(TimeInDisplayFormat.MONTHS_YEARS); - setTrackTotalEarnings(false); setTrackTotalXPEarnings(false); setShowOriginFaction(true); @@ -1712,20 +1710,6 @@ public void setTimeInRankDisplayFormat(final TimeInDisplayFormat timeInRankDispl this.timeInRankDisplayFormat = timeInRankDisplayFormat; } - /** - * @return whether to track the total earnings of personnel - */ - public boolean isTrackTotalEarnings() { - return trackTotalEarnings; - } - - /** - * @param trackTotalEarnings the new value for whether to track total earnings for personnel - */ - public void setTrackTotalEarnings(final boolean trackTotalEarnings) { - this.trackTotalEarnings = trackTotalEarnings; - } - /** * @return whether to track the total experience earnings of personnel */ @@ -4818,7 +4802,6 @@ public void writeToXml(final PrintWriter pw, int indent) { getTimeInServiceDisplayFormat().name()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useTimeInRank", isUseTimeInRank()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "timeInRankDisplayFormat", getTimeInRankDisplayFormat().name()); - MHQXMLUtility.writeSimpleXMLTag(pw, indent, "trackTotalEarnings", isTrackTotalEarnings()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "trackTotalXPEarnings", isTrackTotalXPEarnings()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "showOriginFaction", isShowOriginFaction()); // endregion Expanded Personnel Information @@ -5506,8 +5489,6 @@ public static CampaignOptions generateCampaignOptionsFromXml(Node wn, Version ve retVal.setUseTimeInRank(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("timeInRankDisplayFormat")) { retVal.setTimeInRankDisplayFormat(TimeInDisplayFormat.valueOf(wn2.getTextContent().trim())); - } else if (wn2.getNodeName().equalsIgnoreCase("trackTotalEarnings")) { - retVal.setTrackTotalEarnings(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("trackTotalXPEarnings")) { retVal.setTrackTotalXPEarnings(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("showOriginFaction")) { diff --git a/MekHQ/src/mekhq/campaign/finances/Finances.java b/MekHQ/src/mekhq/campaign/finances/Finances.java index 715aca7f18..031364c43b 100644 --- a/MekHQ/src/mekhq/campaign/finances/Finances.java +++ b/MekHQ/src/mekhq/campaign/finances/Finances.java @@ -19,25 +19,6 @@ */ package mekhq.campaign.finances; -import java.io.BufferedWriter; -import java.io.File; -import java.io.PrintWriter; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.time.LocalDate; -import java.time.Period; -import java.time.temporal.ChronoUnit; -import java.util.ArrayList; -import java.util.List; -import java.util.ResourceBundle; -import java.util.stream.Collectors; -import java.util.stream.IntStream; - -import org.apache.commons.csv.CSVFormat; -import org.apache.commons.csv.CSVPrinter; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; - import megamek.common.annotations.Nullable; import megamek.logging.MMLogger; import mekhq.MekHQ; @@ -52,6 +33,24 @@ import mekhq.io.FileType; import mekhq.utilities.MHQXMLUtility; import mekhq.utilities.ReportingUtilities; +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVPrinter; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.PrintWriter; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.time.LocalDate; +import java.time.Period; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.List; +import java.util.ResourceBundle; +import java.util.stream.Collectors; +import java.util.stream.IntStream; /** * @author Jay Lawson (jaylawson39 at yahoo.com) @@ -319,12 +318,6 @@ public void newDay(final Campaign campaign, final LocalDate yesterday, final Loc campaign.addReport( String.format(resourceMap.getString("Salaries.text"), payRollCost.toAmountAndSymbolString())); - - if (campaign.getCampaignOptions().isTrackTotalEarnings()) { - for (Person person : campaign.getActivePersonnel()) { - person.payPersonSalary(campaign); - } - } } else { campaign.addReport( String.format("" @@ -449,24 +442,10 @@ private void payoutShares(Campaign campaign, Contract contract, LocalDate date) if (debit(TransactionType.SALARIES, date, shares, String.format(resourceMap.getString("ContractSharePayment.text"), contract.getName()))) { campaign.addReport(resourceMap.getString("DistributedShares.text"), shares.toAmountAndSymbolString()); - - if (campaign.getCampaignOptions().isTrackTotalEarnings()) { - int numberOfShares = 0; - boolean sharesForAll = campaign.getCampaignOptions().isSharesForAll(); - for (Person person : campaign.getActivePersonnel()) { - numberOfShares += person.getNumShares(campaign, sharesForAll); - } - - Money singleShare = shares.dividedBy(numberOfShares); - for (Person person : campaign.getActivePersonnel()) { - person.payPersonShares(campaign, singleShare, sharesForAll); - } - } } else { /* * This should not happen, as the shares payment should be less than the - * contract - * payment that has just been made. + * contract payment that has just been made. */ campaign.addReport("" + resourceMap.getString("InsufficientFunds.text"), resourceMap.getString("Shares.text"), diff --git a/MekHQ/src/mekhq/campaign/personnel/Person.java b/MekHQ/src/mekhq/campaign/personnel/Person.java index 0de2c9e071..fdc6f12bf4 100644 --- a/MekHQ/src/mekhq/campaign/personnel/Person.java +++ b/MekHQ/src/mekhq/campaign/personnel/Person.java @@ -146,7 +146,6 @@ public class Person { private int totalXPEarnings; private int acquisitions; private Money salary; - private Money totalEarnings; private int hits; private int hitsPrior; private PrisonerStatus prisonerStatus; @@ -351,7 +350,6 @@ public Person(final String preNominal, final String givenName, final String surn nTasks = 0; doctorId = null; salary = Money.of(-1); - totalEarnings = Money.of(0); status = PersonnelStatus.ACTIVE; prisonerStatus = PrisonerStatus.FREE; hits = 0; @@ -2033,10 +2031,6 @@ public void writeToXML(final PrintWriter pw, int indent, final Campaign campaign if (!salary.equals(Money.of(-1))) { MHQXMLUtility.writeSimpleXMLTag(pw, indent, "salary", salary); } - - if (!totalEarnings.equals(Money.of(0))) { - MHQXMLUtility.writeSimpleXMLTag(pw, indent, "totalEarnings", totalEarnings); - } // Always save a person's status, to make it easy to parse the personnel saved // data MHQXMLUtility.writeSimpleXMLTag(pw, indent, "status", status.name()); @@ -2396,8 +2390,6 @@ public static Person generateInstanceFromXML(Node wn, Campaign c, Version versio retVal.prisonerStatus = PrisonerStatus.parseFromString(wn2.getTextContent().trim()); } else if (wn2.getNodeName().equalsIgnoreCase("salary")) { retVal.salary = Money.fromXmlString(wn2.getTextContent().trim()); - } else if (wn2.getNodeName().equalsIgnoreCase("totalEarnings")) { - retVal.totalEarnings = Money.fromXmlString(wn2.getTextContent().trim()); } else if (wn2.getNodeName().equalsIgnoreCase("minutesLeft")) { retVal.minutesLeft = Integer.parseInt(wn2.getTextContent()); } else if (wn2.getNodeName().equalsIgnoreCase("overtimeLeft")) { @@ -2863,49 +2855,6 @@ private static void removeUnusedEdgeTriggers(Person retVal, List edgeOpt } } - /** - * @return the person's total earnings - */ - public Money getTotalEarnings() { - return totalEarnings; - } - - /** - * This is used to pay a person - * - * @param money the amount of money to add to their total earnings - */ - public void payPerson(final Money money) { - totalEarnings = getTotalEarnings().plus(money); - } - - /** - * This is used to pay a person their salary - * - * @param campaign the campaign the person is a part of - */ - public void payPersonSalary(final Campaign campaign) { - if (getStatus().isActive()) { - payPerson(getSalary(campaign)); - } - } - - /** - * This is used to pay a person their share value based on the value of a single - * share - * - * @param campaign the campaign the person is a part of - * @param money the value of a single share - * @param sharesForAll whether or not all personnel have shares - */ - public void payPersonShares(final Campaign campaign, final Money money, - final boolean sharesForAll) { - final int shares = getNumShares(campaign, sharesForAll); - if (shares > 0) { - payPerson(money.multipliedBy(shares)); - } - } - // region Ranks public RankSystem getRankSystem() { return rankSystem; diff --git a/MekHQ/src/mekhq/gui/adapter/PersonnelTableMouseAdapter.java b/MekHQ/src/mekhq/gui/adapter/PersonnelTableMouseAdapter.java index aaa398abb4..680dd09f4b 100644 --- a/MekHQ/src/mekhq/gui/adapter/PersonnelTableMouseAdapter.java +++ b/MekHQ/src/mekhq/gui/adapter/PersonnelTableMouseAdapter.java @@ -1103,12 +1103,6 @@ public void actionPerformed(ActionEvent action) { return; } - // pay person - for (Person person : people) { - person.payPerson(Money.of(payment)); - MekHQ.triggerEvent(new PersonChangedEvent(person)); - } - // add expense gui.getCampaign().removeFunds(TransactionType.MISCELLANEOUS, Money.of(payment), String.format(resources.getString("givePayment.format"), diff --git a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java index 93d689fbfc..f017495822 100644 --- a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java +++ b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java @@ -8488,7 +8488,6 @@ public void setOptions(@Nullable CampaignOptions options, chkUseTimeInRank.doClick(); } comboTimeInRankDisplayFormat.setSelectedItem(options.getTimeInRankDisplayFormat()); - chkTrackTotalEarnings.setSelected(options.isTrackTotalEarnings()); chkTrackTotalXPEarnings.setSelected(options.isTrackTotalXPEarnings()); chkShowOriginFaction.setSelected(options.isShowOriginFaction()); @@ -9229,7 +9228,6 @@ public void updateOptions() { options.setTimeInServiceDisplayFormat(comboTimeInServiceDisplayFormat.getSelectedItem()); options.setUseTimeInRank(chkUseTimeInRank.isSelected()); options.setTimeInRankDisplayFormat(comboTimeInRankDisplayFormat.getSelectedItem()); - options.setTrackTotalEarnings(chkTrackTotalEarnings.isSelected()); options.setTrackTotalXPEarnings(chkTrackTotalXPEarnings.isSelected()); options.setShowOriginFaction(chkShowOriginFaction.isSelected()); diff --git a/MekHQ/src/mekhq/gui/view/PersonViewPanel.java b/MekHQ/src/mekhq/gui/view/PersonViewPanel.java index 304a78def1..b1fe13715d 100644 --- a/MekHQ/src/mekhq/gui/view/PersonViewPanel.java +++ b/MekHQ/src/mekhq/gui/view/PersonViewPanel.java @@ -27,7 +27,6 @@ import mekhq.campaign.Campaign; import mekhq.campaign.Kill; import mekhq.campaign.event.PersonChangedEvent; -import mekhq.campaign.finances.Money; import mekhq.campaign.log.LogEntry; import mekhq.campaign.personnel.*; import mekhq.campaign.personnel.education.Academy; @@ -825,37 +824,8 @@ public void mouseClicked(MouseEvent e) { y++; } - // We show the following if track total earnings is on for a free person or if - // the - // person has previously tracked total earnings - if (campaign.getCampaignOptions().isTrackTotalEarnings() - && (person.getPrisonerStatus().isFree() || person.getTotalEarnings().isGreaterThan(Money.zero()))) { - JLabel lblTotalEarnings1 = new JLabel(resourceMap.getString("lblTotalEarnings1.text")); - lblTotalEarnings1.setName("lblTotalEarnings1"); - gridBagConstraints = new GridBagConstraints(); - gridBagConstraints.gridx = 0; - gridBagConstraints.gridy = y; - gridBagConstraints.fill = GridBagConstraints.NONE; - gridBagConstraints.anchor = GridBagConstraints.NORTHWEST; - pnlInfo.add(lblTotalEarnings1, gridBagConstraints); - - JLabel lblTotalEarnings2 = new JLabel(person.getTotalEarnings().toAmountAndSymbolString()); - lblTotalEarnings2.setName("lblTotalEarnings2"); - lblTotalEarnings1.setLabelFor(lblTotalEarnings2); - gridBagConstraints = new GridBagConstraints(); - gridBagConstraints.gridx = 1; - gridBagConstraints.gridy = y; - gridBagConstraints.weightx = 1.0; - gridBagConstraints.insets = new Insets(0, 10, 0, 0); - gridBagConstraints.fill = GridBagConstraints.NONE; - gridBagConstraints.anchor = GridBagConstraints.NORTHWEST; - pnlInfo.add(lblTotalEarnings2, gridBagConstraints); - y++; - } - - // We show the following if track total xp earnings is on for a free person or - // if the - // person has previously tracked total xp earnings + // We show the following if track total xp earnings are on for a free person or + // if the person has previously tracked total xp earnings if (campaign.getCampaignOptions().isTrackTotalXPEarnings() && (person.getPrisonerStatus().isFree() || (person.getTotalXPEarnings() != 0))) { JLabel lblTotalXPEarnings1 = new JLabel(resourceMap.getString("lblTotalXPEarnings1.text")); From 94706466edc58b6344f21a8c0cfa53154a4aac30 Mon Sep 17 00:00:00 2001 From: psikomonkie <189469115+psikomonkie@users.noreply.github.com> Date: Fri, 10 Jan 2025 17:01:13 -0500 Subject: [PATCH 2/3] Issue 5736: Restored Total Earnings, Added check to prevent negative payment. --- MekHQ/src/mekhq/campaign/Campaign.java | 24 +++- MekHQ/src/mekhq/campaign/CampaignOptions.java | 19 ++++ .../mekhq/campaign/finances/Accountant.java | 27 +++++ .../src/mekhq/campaign/finances/Finances.java | 107 +++++++++++++++--- .../src/mekhq/campaign/personnel/Person.java | 46 ++++++++ .../adapter/PersonnelTableMouseAdapter.java | 13 ++- .../mekhq/gui/panes/CampaignOptionsPane.java | 2 + MekHQ/src/mekhq/gui/view/PersonViewPanel.java | 34 +++++- 8 files changed, 250 insertions(+), 22 deletions(-) diff --git a/MekHQ/src/mekhq/campaign/Campaign.java b/MekHQ/src/mekhq/campaign/Campaign.java index 3ce1f65072..4a2b24fb96 100644 --- a/MekHQ/src/mekhq/campaign/Campaign.java +++ b/MekHQ/src/mekhq/campaign/Campaign.java @@ -5605,6 +5605,24 @@ public void removeFunds(final TransactionType type, final Money quantity, addReport("Funds removed : " + quantityString + " (" + description + ')'); } + + /** + * Generic method for paying Personnel (Person) in the company. + * Debits money from the campaign and if the campaign tracks + * total earnings it will account for that. + * @param type TransactionType being debited + * @param quantity total money - it's usually displayed outside of this method + * @param description String displayed in the ledger & report + * @param individualPayouts Map of Person to the Money they're owed + */ + public void payPersonnel(TransactionType type, Money quantity, String description, Map individualPayouts) { + getFinances().debit(type, getLocalDate(), quantity, description, + individualPayouts, getCampaignOptions().isTrackTotalEarnings()); + String quantityString = quantity.toAmountAndSymbolString(); + addReport("Funds removed : " + quantityString + " (" + description + ')'); + + } + public CampaignOptions getCampaignOptions() { return campaignOptions; } @@ -7828,15 +7846,17 @@ public void completeMission(@Nullable Mission mission, MissionStatus status) { if (getCampaignOptions().isUseShareSystem()) { ResourceBundle financeResources = ResourceBundle.getBundle("mekhq.resources.Finances", - MekHQ.getMHQOptions().getLocale()); + MekHQ.getMHQOptions().getLocale()); if (remainingMoney.isGreaterThan(Money.zero())) { Money shares = remainingMoney.multipliedBy(contract.getSharesPercent()).dividedBy(100); remainingMoney = remainingMoney.minus(shares); if (getFinances().debit(TransactionType.SALARIES, getLocalDate(), shares, - String.format(financeResources.getString("ContractSharePayment.text"), contract.getName()))) { + String.format(financeResources.getString("ContractSharePayment.text"), contract.getName()))) { addReport(financeResources.getString("DistributedShares.text"), shares.toAmountAndSymbolString()); + + getFinances().payOutSharesToPersonnel(this, shares); } } } diff --git a/MekHQ/src/mekhq/campaign/CampaignOptions.java b/MekHQ/src/mekhq/campaign/CampaignOptions.java index 879ec23c76..7c434f1326 100644 --- a/MekHQ/src/mekhq/campaign/CampaignOptions.java +++ b/MekHQ/src/mekhq/campaign/CampaignOptions.java @@ -211,6 +211,7 @@ public static String getTransitUnitName(final int unit) { private TimeInDisplayFormat timeInServiceDisplayFormat; private boolean useTimeInRank; private TimeInDisplayFormat timeInRankDisplayFormat; + private boolean trackTotalEarnings; private boolean trackTotalXPEarnings; private boolean showOriginFaction; @@ -744,6 +745,7 @@ public CampaignOptions() { setTimeInServiceDisplayFormat(TimeInDisplayFormat.YEARS); setUseTimeInRank(false); setTimeInRankDisplayFormat(TimeInDisplayFormat.MONTHS_YEARS); + setTrackTotalEarnings(false); setTrackTotalXPEarnings(false); setShowOriginFaction(true); @@ -1710,6 +1712,20 @@ public void setTimeInRankDisplayFormat(final TimeInDisplayFormat timeInRankDispl this.timeInRankDisplayFormat = timeInRankDisplayFormat; } + /** + * @return whether to track the total earnings of personnel + */ + public boolean isTrackTotalEarnings() { + return trackTotalEarnings; + } + + /** + * @param trackTotalEarnings the new value for whether to track total earnings for personnel + */ + public void setTrackTotalEarnings(final boolean trackTotalEarnings) { + this.trackTotalEarnings = trackTotalEarnings; + } + /** * @return whether to track the total experience earnings of personnel */ @@ -4802,6 +4818,7 @@ public void writeToXml(final PrintWriter pw, int indent) { getTimeInServiceDisplayFormat().name()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useTimeInRank", isUseTimeInRank()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "timeInRankDisplayFormat", getTimeInRankDisplayFormat().name()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "trackTotalEarnings", isTrackTotalEarnings()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "trackTotalXPEarnings", isTrackTotalXPEarnings()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "showOriginFaction", isShowOriginFaction()); // endregion Expanded Personnel Information @@ -5489,6 +5506,8 @@ public static CampaignOptions generateCampaignOptionsFromXml(Node wn, Version ve retVal.setUseTimeInRank(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("timeInRankDisplayFormat")) { retVal.setTimeInRankDisplayFormat(TimeInDisplayFormat.valueOf(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("trackTotalEarnings")) { + retVal.setTrackTotalEarnings(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("trackTotalXPEarnings")) { retVal.setTrackTotalXPEarnings(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("showOriginFaction")) { diff --git a/MekHQ/src/mekhq/campaign/finances/Accountant.java b/MekHQ/src/mekhq/campaign/finances/Accountant.java index f436fe7d50..4a2d8ac582 100644 --- a/MekHQ/src/mekhq/campaign/finances/Accountant.java +++ b/MekHQ/src/mekhq/campaign/finances/Accountant.java @@ -20,12 +20,16 @@ */ package mekhq.campaign.finances; +import java.time.LocalDate; +import java.util.HashMap; +import java.util.Map; import java.util.UUID; import megamek.common.Entity; import mekhq.campaign.Campaign; import mekhq.campaign.CampaignOptions; import mekhq.campaign.Hangar; +import mekhq.campaign.finances.enums.TransactionType; import mekhq.campaign.parts.Part; import mekhq.campaign.personnel.Person; import mekhq.campaign.personnel.enums.PersonnelRole; @@ -228,4 +232,27 @@ public Money getContractBase() { return getTheoreticalPayroll(getCampaignOptions().isInfantryDontCount()); } } + + /** + * Returns a map of every Person and their salary. + * + * @see Finances#debit(TransactionType, LocalDate, Money, String, Map, boolean) + * @return map of personnel to their pay, including pool as a null key + */ + public Map getPayRollSummary() { + Map payRollSummary = new HashMap<>(); + for (Person p : getCampaign().getActivePersonnel()) { + payRollSummary.put(p, p.getSalary(getCampaign())); + } + // And pay our pool + payRollSummary.put(null, Money.of( + (getCampaign().getCampaignOptions().getRoleBaseSalaries() + [PersonnelRole.ASTECH.ordinal()].getAmount().doubleValue() + * getCampaign().getAstechPool()) + + (getCampaign().getCampaignOptions().getRoleBaseSalaries() + [PersonnelRole.MEDIC.ordinal()].getAmount().doubleValue() + * getCampaign().getMedicPool()))); + + return payRollSummary; + } } diff --git a/MekHQ/src/mekhq/campaign/finances/Finances.java b/MekHQ/src/mekhq/campaign/finances/Finances.java index 031364c43b..cc28f4f598 100644 --- a/MekHQ/src/mekhq/campaign/finances/Finances.java +++ b/MekHQ/src/mekhq/campaign/finances/Finances.java @@ -48,6 +48,7 @@ import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.ResourceBundle; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -158,6 +159,17 @@ public int getPartialYearsInDebt(LocalDate date) { return 0; } + /** + * Debits (removes) money from the campaign's balance. + * Consider the debit method that takes a Map of Person to Money + * if this debit is for paying your crew. + * @param type TransactionType being debited + * @param date when the transaction occurred + * @param amount Money to remove from the campaign's balanace + * @param reason String displayed in the ledger + * @return true if the transaction succeeds, false if it doesn't, + * such as from insufficient balance + */ public boolean debit(final TransactionType type, final LocalDate date, final Money amount, final String reason) { if (getBalance().isLessThan(amount)) { @@ -172,6 +184,41 @@ public boolean debit(final TransactionType type, final LocalDate date, final Mon return true; } + + /** + * Debits (removes) money from the campaign's balance. + * When debiting money to people in the Campaign, + * if TrackTotalEarnings is true we'll want to pay each + * Person what they're owed. Use this method to debit (remove) + * money from your Campaign's balance while paying it to + * the provided people (Person) in the individualPayoutsMap. + * @param type TransactionType being debited + * @param date when the transaction occurred + * @param amount total money - it's usually displayed outside of this method + * @param reason String displayed in the ledger + * @param individualPayouts Map of Person to the Money they're owed + * @param isTrackTotalEarnings true if we want to apply earnings to individual people (Person) + * @return true if the transaction succeeds, false if it doesn't, + * such as from insufficient balance + */ + public boolean debit(final TransactionType type, final LocalDate date, Money amount, String reason, Map individualPayouts, boolean isTrackTotalEarnings) { + if (debit(type, date, amount, reason)) { + if (isTrackTotalEarnings && !individualPayouts.isEmpty()) { + for (Person person : individualPayouts.keySet()) { + Money payout = individualPayouts.get(person); + if (person != null) { // Null person will be used for temp personnel + person.payPerson(payout); + } + } + } else { + logger.error(String.format("Individual Payouts is Empty! Transaction Type: %s Date: %s Reason: %s",type, date, reason)); + } + return true; + } + + return false; + } + public void credit(final TransactionType type, final LocalDate date, final Money amount, final String reason) { Transaction t = new Transaction(type, date, amount, reason); @@ -311,13 +358,17 @@ public void newDay(final Campaign campaign, final LocalDate yesterday, final Loc } if (campaign.getCampaignOptions().isPayForSalaries()) { + Money payRollCost = campaign.getAccountant().getPayRoll(); if (debit(TransactionType.SALARIES, today, payRollCost, - resourceMap.getString("Salaries.title"))) { + resourceMap.getString("Salaries.title"), + campaign.getAccountant().getPayRollSummary(), + campaign.getCampaignOptions().isTrackTotalEarnings())) { campaign.addReport( - String.format(resourceMap.getString("Salaries.text"), - payRollCost.toAmountAndSymbolString())); + String.format(resourceMap.getString("Salaries.text"), + payRollCost.toAmountAndSymbolString())); + } else { campaign.addReport( String.format("" @@ -438,23 +489,51 @@ private void payoutShares(Campaign campaign, Contract contract, LocalDate date) if (campaign.getCampaignOptions().isUseAtB() && campaign.getCampaignOptions().isUseShareSystem() && (contract instanceof AtBContract)) { Money shares = contract.getMonthlyPayOut().multipliedBy(contract.getSharesPercent()) - .dividedBy(100); - if (debit(TransactionType.SALARIES, date, shares, + .dividedBy(100); + if (shares.isGreaterThan(Money.zero())) { + if (debit(TransactionType.SALARIES, date, shares, String.format(resourceMap.getString("ContractSharePayment.text"), contract.getName()))) { - campaign.addReport(resourceMap.getString("DistributedShares.text"), shares.toAmountAndSymbolString()); - } else { - /* - * This should not happen, as the shares payment should be less than the - * contract payment that has just been made. - */ - campaign.addReport("" - + resourceMap.getString("InsufficientFunds.text"), resourceMap.getString("Shares.text"), + campaign.addReport(resourceMap.getString("DistributedShares.text"), shares.toAmountAndSymbolString()); + + payOutSharesToPersonnel(campaign, shares); + } else { + /* + * This should not happen, as the shares payment should be less than the + * contract payment that has just been made. + */ + campaign.addReport("" + + resourceMap.getString("InsufficientFunds.text"), resourceMap.getString("Shares.text"), ""); - logger.error("Attempted to payout share amount larger than the payment of the contract"); + logger.error("Attempted to payout share amount larger than the payment of the contract"); + } } } } +/** + * Shares calculate the amount debited without iterating + * through all the personnel, so it's not more efficient + * to provide that information to debit. Pay out shares + * manually for now. + * @param campaign where to pull personnel from + * @param shares total value of the shares to pay out + */ +public void payOutSharesToPersonnel(Campaign campaign, Money shares) { + if (campaign.getCampaignOptions().isTrackTotalEarnings()) { + boolean sharesForAll = campaign.getCampaignOptions().isSharesForAll(); + + int numberOfShares = campaign.getActivePersonnel().stream() + .mapToInt(person -> person.getNumShares(campaign, sharesForAll)) + .sum(); + + Money singleShare = shares.dividedBy(numberOfShares); + + for (Person person : campaign.getActivePersonnel()) { + person.payPersonShares(campaign, singleShare, sharesForAll); + } + } +} + public Money checkOverdueLoanPayments(Campaign campaign) { List newLoans = new ArrayList<>(); Money overdueAmount = Money.zero(); diff --git a/MekHQ/src/mekhq/campaign/personnel/Person.java b/MekHQ/src/mekhq/campaign/personnel/Person.java index fdc6f12bf4..e03cbc46e8 100644 --- a/MekHQ/src/mekhq/campaign/personnel/Person.java +++ b/MekHQ/src/mekhq/campaign/personnel/Person.java @@ -146,6 +146,7 @@ public class Person { private int totalXPEarnings; private int acquisitions; private Money salary; + private Money totalEarnings; private int hits; private int hitsPrior; private PrisonerStatus prisonerStatus; @@ -350,6 +351,7 @@ public Person(final String preNominal, final String givenName, final String surn nTasks = 0; doctorId = null; salary = Money.of(-1); + totalEarnings = Money.of(0); status = PersonnelStatus.ACTIVE; prisonerStatus = PrisonerStatus.FREE; hits = 0; @@ -2031,6 +2033,10 @@ public void writeToXML(final PrintWriter pw, int indent, final Campaign campaign if (!salary.equals(Money.of(-1))) { MHQXMLUtility.writeSimpleXMLTag(pw, indent, "salary", salary); } + + if (!totalEarnings.equals(Money.of(0))) { + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "totalEarnings", totalEarnings); + } // Always save a person's status, to make it easy to parse the personnel saved // data MHQXMLUtility.writeSimpleXMLTag(pw, indent, "status", status.name()); @@ -2390,6 +2396,8 @@ public static Person generateInstanceFromXML(Node wn, Campaign c, Version versio retVal.prisonerStatus = PrisonerStatus.parseFromString(wn2.getTextContent().trim()); } else if (wn2.getNodeName().equalsIgnoreCase("salary")) { retVal.salary = Money.fromXmlString(wn2.getTextContent().trim()); + } else if (wn2.getNodeName().equalsIgnoreCase("totalEarnings")) { + retVal.totalEarnings = Money.fromXmlString(wn2.getTextContent().trim()); } else if (wn2.getNodeName().equalsIgnoreCase("minutesLeft")) { retVal.minutesLeft = Integer.parseInt(wn2.getTextContent()); } else if (wn2.getNodeName().equalsIgnoreCase("overtimeLeft")) { @@ -2855,6 +2863,44 @@ private static void removeUnusedEdgeTriggers(Person retVal, List edgeOpt } } + /** + * @return the person's total earnings + */ + public Money getTotalEarnings() { + return totalEarnings; + } + + /** + * This is used to pay a person. Preventing negative payments + * is intentional to ensure we don't accidentally + * change someone when trying to give them money. + * To charge a person, implement a new method. + * (And then add a @see here) + * + * @param money the amount of money to add to their total earnings + */ + public void payPerson(final Money money) { + if (money.isPositiveOrZero()) { + totalEarnings = getTotalEarnings().plus((money)); + } + } + + /** + * This is used to pay a person their share value based on the value of a single + * share + * + * @param campaign the campaign the person is a part of + * @param money the value of a single share + * @param sharesForAll whether or not all personnel have shares + */ + public void payPersonShares(final Campaign campaign, final Money money, + final boolean sharesForAll) { + final int shares = getNumShares(campaign, sharesForAll); + if (shares > 0) { + payPerson(money.multipliedBy(shares)); + } + } + // region Ranks public RankSystem getRankSystem() { return rankSystem; diff --git a/MekHQ/src/mekhq/gui/adapter/PersonnelTableMouseAdapter.java b/MekHQ/src/mekhq/gui/adapter/PersonnelTableMouseAdapter.java index 680dd09f4b..43fffd719b 100644 --- a/MekHQ/src/mekhq/gui/adapter/PersonnelTableMouseAdapter.java +++ b/MekHQ/src/mekhq/gui/adapter/PersonnelTableMouseAdapter.java @@ -1103,10 +1103,15 @@ public void actionPerformed(ActionEvent action) { return; } - // add expense - gui.getCampaign().removeFunds(TransactionType.MISCELLANEOUS, Money.of(payment), - String.format(resources.getString("givePayment.format"), - selectedPerson.getFullName())); + // pay person & add expense + Map personMoneyMap = new HashMap<>(); + personMoneyMap.put(selectedPerson, Money.of(payment)); + gui.getCampaign().payPersonnel(TransactionType.MISCELLANEOUS, + Money.of(payment), + String.format(resources.getString("givePayment.format"),selectedPerson.getFullName()), + personMoneyMap); + MekHQ.triggerEvent(new PersonChangedEvent(selectedPerson)); + break; } diff --git a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java index f017495822..93d689fbfc 100644 --- a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java +++ b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java @@ -8488,6 +8488,7 @@ public void setOptions(@Nullable CampaignOptions options, chkUseTimeInRank.doClick(); } comboTimeInRankDisplayFormat.setSelectedItem(options.getTimeInRankDisplayFormat()); + chkTrackTotalEarnings.setSelected(options.isTrackTotalEarnings()); chkTrackTotalXPEarnings.setSelected(options.isTrackTotalXPEarnings()); chkShowOriginFaction.setSelected(options.isShowOriginFaction()); @@ -9228,6 +9229,7 @@ public void updateOptions() { options.setTimeInServiceDisplayFormat(comboTimeInServiceDisplayFormat.getSelectedItem()); options.setUseTimeInRank(chkUseTimeInRank.isSelected()); options.setTimeInRankDisplayFormat(comboTimeInRankDisplayFormat.getSelectedItem()); + options.setTrackTotalEarnings(chkTrackTotalEarnings.isSelected()); options.setTrackTotalXPEarnings(chkTrackTotalXPEarnings.isSelected()); options.setShowOriginFaction(chkShowOriginFaction.isSelected()); diff --git a/MekHQ/src/mekhq/gui/view/PersonViewPanel.java b/MekHQ/src/mekhq/gui/view/PersonViewPanel.java index b1fe13715d..304a78def1 100644 --- a/MekHQ/src/mekhq/gui/view/PersonViewPanel.java +++ b/MekHQ/src/mekhq/gui/view/PersonViewPanel.java @@ -27,6 +27,7 @@ import mekhq.campaign.Campaign; import mekhq.campaign.Kill; import mekhq.campaign.event.PersonChangedEvent; +import mekhq.campaign.finances.Money; import mekhq.campaign.log.LogEntry; import mekhq.campaign.personnel.*; import mekhq.campaign.personnel.education.Academy; @@ -824,8 +825,37 @@ public void mouseClicked(MouseEvent e) { y++; } - // We show the following if track total xp earnings are on for a free person or - // if the person has previously tracked total xp earnings + // We show the following if track total earnings is on for a free person or if + // the + // person has previously tracked total earnings + if (campaign.getCampaignOptions().isTrackTotalEarnings() + && (person.getPrisonerStatus().isFree() || person.getTotalEarnings().isGreaterThan(Money.zero()))) { + JLabel lblTotalEarnings1 = new JLabel(resourceMap.getString("lblTotalEarnings1.text")); + lblTotalEarnings1.setName("lblTotalEarnings1"); + gridBagConstraints = new GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = y; + gridBagConstraints.fill = GridBagConstraints.NONE; + gridBagConstraints.anchor = GridBagConstraints.NORTHWEST; + pnlInfo.add(lblTotalEarnings1, gridBagConstraints); + + JLabel lblTotalEarnings2 = new JLabel(person.getTotalEarnings().toAmountAndSymbolString()); + lblTotalEarnings2.setName("lblTotalEarnings2"); + lblTotalEarnings1.setLabelFor(lblTotalEarnings2); + gridBagConstraints = new GridBagConstraints(); + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = y; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.insets = new Insets(0, 10, 0, 0); + gridBagConstraints.fill = GridBagConstraints.NONE; + gridBagConstraints.anchor = GridBagConstraints.NORTHWEST; + pnlInfo.add(lblTotalEarnings2, gridBagConstraints); + y++; + } + + // We show the following if track total xp earnings is on for a free person or + // if the + // person has previously tracked total xp earnings if (campaign.getCampaignOptions().isTrackTotalXPEarnings() && (person.getPrisonerStatus().isFree() || (person.getTotalXPEarnings() != 0))) { JLabel lblTotalXPEarnings1 = new JLabel(resourceMap.getString("lblTotalXPEarnings1.text")); From 5e9e2e77dc33945b5419c022b4ca48000dc65c0a Mon Sep 17 00:00:00 2001 From: psikomonkie <189469115+psikomonkie@users.noreply.github.com> Date: Fri, 10 Jan 2025 17:08:57 -0500 Subject: [PATCH 3/3] Issue 5736: Fixed JavaDoc error --- MekHQ/src/mekhq/campaign/Campaign.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MekHQ/src/mekhq/campaign/Campaign.java b/MekHQ/src/mekhq/campaign/Campaign.java index 4a2b24fb96..2b8f5b8b81 100644 --- a/MekHQ/src/mekhq/campaign/Campaign.java +++ b/MekHQ/src/mekhq/campaign/Campaign.java @@ -5612,7 +5612,7 @@ public void removeFunds(final TransactionType type, final Money quantity, * total earnings it will account for that. * @param type TransactionType being debited * @param quantity total money - it's usually displayed outside of this method - * @param description String displayed in the ledger & report + * @param description String displayed in the ledger and report * @param individualPayouts Map of Person to the Money they're owed */ public void payPersonnel(TransactionType type, Money quantity, String description, Map individualPayouts) {