/* Finances.java * * Copyright (c) 2009 Jay Lawson <jaylawson39 at yahoo.com>. All rights reserved. * * This file is part of MekHQ. * * MekHQ is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * MekHQ is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with MekHQ. If not, see <http://www.gnu.org/licenses/>. */ package mekhq.campaign.finances; import java.io.PrintWriter; import java.io.Serializable; import java.text.DecimalFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import org.w3c.dom.DOMException; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import mekhq.MekHQ; import mekhq.MekHqXmlUtil; import mekhq.Utilities; import mekhq.campaign.Campaign; import mekhq.campaign.event.LoanDefaultedEvent; import mekhq.campaign.event.TransactionCreditEvent; import mekhq.campaign.event.TransactionDebitEvent; /** * * @author Jay Lawson <jaylawson39 at yahoo.com> */ public class Finances implements Serializable { /** * */ private static final long serialVersionUID = 8533117455496219692L; private ArrayList<Transaction> transactions; private ArrayList<Loan> loans; private ArrayList<Asset> assets; private int loanDefaults; private int failCollateral; private Date wentIntoDebt; public static final int SCHEDULE_BIWEEKLY = 0; public static final int SCHEDULE_MONTHLY = 1; public static final int SCHEDULE_QUARTERLY = 2; public static final int SCHEDULE_YEARLY = 3; public static final int SCHEDULE_NUM = 4; public static String getScheduleName(int schedule) { switch(schedule) { case Finances.SCHEDULE_BIWEEKLY: return "Bi-Weekly"; case Finances.SCHEDULE_MONTHLY: return "Monthly"; case Finances.SCHEDULE_QUARTERLY: return "Quarterly"; case Finances.SCHEDULE_YEARLY: return "Yearly"; default: return "?"; } } public Finances() { transactions = new ArrayList<Transaction>(); loans = new ArrayList<Loan>(); assets = new ArrayList<Asset>(); loanDefaults = 0; failCollateral = 0; wentIntoDebt = null; } public long getBalance() { long balance = 0; for(Transaction transaction : transactions) { balance += transaction.getAmount(); } return balance; } public long getLoanBalance() { long balance = 0; for(Loan loan : loans) { balance += loan.getRemainingValue(); } return balance; } public boolean isInDebt() { return getLoanBalance() > 0; } public int getFullYearsInDebt(GregorianCalendar cal) { if(null == wentIntoDebt) { return 0; } return Utilities.getDiffFullYears(wentIntoDebt, cal); } public int getPartialYearsInDebt(GregorianCalendar cal) { if (wentIntoDebt == null) { return 0; } return Utilities.getDiffPartialYears(wentIntoDebt, cal); } public boolean debit(long amount, int category, String reason, Date date) { if(getBalance() < amount) { return false; } Transaction t = new Transaction(-1 * amount, category, reason, date); transactions.add(t); if(null != wentIntoDebt && !isInDebt()) { wentIntoDebt = null; } MekHQ.triggerEvent(new TransactionDebitEvent(t)); return true; } public void credit(long amount, int category, String reason, Date date) { Transaction t = new Transaction(amount, category, reason, date); transactions.add(t); if(null == wentIntoDebt && isInDebt()) { wentIntoDebt = date; } MekHQ.triggerEvent(new TransactionCreditEvent(t)); } /** * This function will update the starting amount to the current balance * and clear transactions * By default, this will be called up on Jan 1 of every year in order to keep * the transaction record from becoming too large */ public void newFiscalYear(Date date) { long carryover = getBalance(); transactions = new ArrayList<Transaction>(); credit(carryover, Transaction.C_START, "Carryover from previous year", date); } public ArrayList<Transaction> getAllTransactions() { return transactions; } public ArrayList<Loan> getAllLoans() { return loans; } public ArrayList<Asset> getAllAssets() { return assets; } public void writeToXml(PrintWriter pw1, int indent) { pw1.println(MekHqXmlUtil.indentStr(indent) + "<finances>"); pw1.println(MekHqXmlUtil.indentStr(indent+1) +"<loanDefaults>" +loanDefaults +"</loanDefaults>"); for(Transaction trans : transactions) { trans.writeToXml(pw1, indent+1); } for(Loan loan : loans) { loan.writeToXml(pw1, indent+1); } for(Asset asset : assets) { asset.writeToXml(pw1, indent+1); } if(null != wentIntoDebt) { SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); MekHqXmlUtil.writeSimpleXmlTag(pw1, indent+1, "wentIntoDebt", df.format(wentIntoDebt)); } pw1.println(MekHqXmlUtil.indentStr(indent) + "</finances>"); } public static Finances generateInstanceFromXML(Node wn) { Finances retVal = new Finances(); NodeList nl = wn.getChildNodes(); for (int x=0; x<nl.getLength(); x++) { Node wn2 = nl.item(x); if (wn2.getNodeName().equalsIgnoreCase("transaction")) { retVal.transactions.add(Transaction.generateInstanceFromXML(wn2)); } else if (wn2.getNodeName().equalsIgnoreCase("loan")) { retVal.loans.add(Loan.generateInstanceFromXML(wn2)); } else if (wn2.getNodeName().equalsIgnoreCase("asset")) { retVal.assets.add(Asset.generateInstanceFromXML(wn2)); } else if (wn2.getNodeName().equalsIgnoreCase("loanDefaults")) { retVal.loanDefaults = Integer.parseInt(wn2.getTextContent().trim()); } else if (wn2.getNodeName().equalsIgnoreCase("wentIntoDebt")) { SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); try { retVal.wentIntoDebt = df.parse(wn2.getTextContent().trim()); } catch (DOMException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (ParseException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } return retVal; } public void addLoan(Loan loan) { loans.add(loan); } public void newDay(Campaign campaign) { ArrayList<Loan> newLoans = new ArrayList<Loan>(); for(Loan loan : loans) { if(loan.checkLoanPayment(campaign.getCalendar())) { if(debit(loan.getPaymentAmount(), Transaction.C_LOAN_PAYMENT, "loan payment to " + loan.getDescription(), campaign.getCalendar().getTime())) { campaign.addReport("Your account has been debited for " + DecimalFormat.getInstance().format(loan.getPaymentAmount()) + " C-bills in loan payment to " + loan.getDescription()); loan.paidLoan(); } else { campaign.addReport("<font color='red'><b>You have insufficient funds to service the debt on loan " + loan.getDescription() + "!</b></font> Funds required: " + DecimalFormat.getInstance().format(loan.getPaymentAmount())); loan.setOverdue(true); } } if(loan.getRemainingPayments() > 0) { newLoans.add(loan); } else { campaign.addReport("You have fully paid off loan " + loan.getDescription()); } } if(null != wentIntoDebt && !isInDebt()) { wentIntoDebt = null; } loans = newLoans; for(Asset asset : assets) { if(asset.getSchedule() == SCHEDULE_YEARLY && campaign.calendar.get(Calendar.DAY_OF_YEAR) == 1) { credit(asset.getIncome(), Transaction.C_MISC, "income from " + asset.getName(), campaign.getCalendar().getTime()); campaign.addReport("Your account has been credited for " + DecimalFormat.getInstance().format(asset.getIncome()) + " C-bills from " + asset.getName()); } else if(asset.getSchedule() == SCHEDULE_MONTHLY && campaign.calendar.get(Calendar.DAY_OF_MONTH) == 1) { credit(asset.getIncome(), Transaction.C_MISC, "income from " + asset.getName(), campaign.getCalendar().getTime()); campaign.addReport("Your account has been credited for " + DecimalFormat.getInstance().format(asset.getIncome()) + " C-bills from " + asset.getName()); } } } public long checkOverdueLoanPayments(Campaign campaign) { ArrayList<Loan> newLoans = new ArrayList<Loan>(); long overdueAmount = 0; for(Loan loan : loans) { if(loan.isOverdue()) { if(debit(loan.getPaymentAmount(), Transaction.C_LOAN_PAYMENT, "loan payment " + loan.getDescription(), campaign.getCalendar().getTime())) { campaign.addReport("Your account has been debited for " + DecimalFormat.getInstance().format(loan.getPaymentAmount()) + " C-bills in loan payment to " + loan.getDescription()); loan.paidLoan(); } else { overdueAmount += loan.getPaymentAmount(); } } if(loan.getRemainingPayments() > 0) { newLoans.add(loan); } else { campaign.addReport("You have fully paid off loan " + loan.getDescription()); } } loans = newLoans; if(null != wentIntoDebt && !isInDebt()) { wentIntoDebt = null; } return overdueAmount; } public void removeLoan(Loan loan) { loans.remove(loan); if(null != wentIntoDebt && !isInDebt()) { wentIntoDebt = null; } } public void defaultOnLoan(Loan loan, boolean paidCollateral) { loanDefaults++; if(!paidCollateral) { failCollateral++; } removeLoan(loan); MekHQ.triggerEvent(new LoanDefaultedEvent(loan)); } public int getLoanDefaults() { return loanDefaults; } public int getFailedCollateral() { return failCollateral; } public long getTotalLoanCollateral() { long amount = 0; for(Loan loan : loans) { amount += loan.getCollateralAmount(); } return amount; } public long getTotalAssetValue() { long amount = 0; for(Asset asset : assets) { amount += asset.getValue(); } return amount; } public void setAssets(ArrayList<Asset> newAssets) { assets = newAssets; } public long getMaxCollateral(Campaign c) { return c.getTotalEquipmentValue() + getTotalAssetValue() - getTotalLoanCollateral(); } }