/* * jPOS Project [http://jpos.org] * Copyright (C) 2000-2017 jPOS Software SRL * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.jpos.gl; import java.util.Date; import java.util.List; import java.util.ArrayList; import java.math.BigDecimal; import java.text.ParseException; import org.jdom2.Element; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.ToStringBuilder; import org.jpos.ee.Cloneable; import org.jpos.util.Tags; /** * GLTransaction. * * Represents a MiniGL Transaction. * * @see GLEntry * @see Journal * * @author <a href="mailto:apr@jpos.org">Alejandro Revilla</a> */ public class GLTransaction extends Cloneable { private long id; private Date timestamp; private Date postDate; private String detail; private Tags tags; private Journal journal; List<GLEntry> entries; public GLTransaction () { super(); } public GLTransaction (String detail) { super (); setDetail(detail); } /** * Constructs a GLTransaction out of a JDOM Element as defined in * <a href="http://jpos.org/minigl.dtd">minigl.dtd</a> * @param elem {@code '<entry>...</entry>}' element. */ public GLTransaction (Element elem) throws ParseException { super(); fromXML(elem); } /** * GLTransaction id. * * @return internal id */ public long getId() { return id; } /** * GLTransaction id. * * @param id internal id. */ public void setId(long id) { this.id = id; } /** * Timestamp. * @param d transaction timestamp */ public void setTimestamp (Date d) { this.timestamp = d; } /** * Timestamp. * @return transaction timestamp */ public Date getTimestamp () { return timestamp; } /** * Posting date * @param d transaction postdate */ public void setPostDate (Date d) { this.postDate = d; } /** * @return transaction postdate */ public Date getPostDate () { return postDate; } /** * Detail. * @param detail transaction detail */ public void setDetail (String detail) { this.detail = detail; } /** * Detail. * @return transaction detail */ public String getDetail () { return detail; } /** * Tags. * @param tags transaction tags */ public void setTags (Tags tags) { this.tags = tags; } /** * Tags. * @return transaction tags */ public Tags getTags () { return tags; } /** * @param tag to add * @return this */ public GLTransaction addTag (String tag) { getTags().add(tag); return this; } /** * Entries. * @param entries transaction entries */ public void setEntries (List<GLEntry> entries) { this.entries = entries; } /** * Entries. * @return transaction entries */ public List<GLEntry> getEntries () { if (entries == null) entries = new ArrayList<>(); return entries; } /** * Journal. * * @return journal where this transaction has been posted. */ public Journal getJournal () { return journal; } /** * Journal. * * @param journal journal where this transaction has been posted. */ public void setJournal (Journal journal) { this.journal = journal; } /** * Create GLEntry associated with this GLTransaction. * * Factory helper method. * * @param acct the account * @param amount amount * @param detail detail * @param isCredit true if credit, false if debit * @param layer the layer */ public GLEntry createGLEntry (FinalAccount acct, BigDecimal amount, String detail, boolean isCredit, short layer) { GLEntry entry = isCredit ? new GLCredit() : new GLDebit(); entry.setAccount (acct); entry.setAmount (amount); entry.setDetail (detail); entry.setCredit (isCredit); entry.setTransaction (this); entry.setLayer (layer); getEntries().add (entry); return entry; } /** * Create a debit GLEntry associated with this GLTransaction. * * Factory helper method. * * @param acct the account * @param amount amount */ public GLEntry createDebit (FinalAccount acct, BigDecimal amount) { return createGLEntry (acct, amount, null, false, (short) 0); } /** * Create a debit GLEntry associated with this GLTransaction. * * Factory helper method. * * @param acct the account * @param amount amount * @param detail detail */ public GLEntry createDebit (FinalAccount acct, BigDecimal amount, String detail) { return createGLEntry (acct, amount, detail, false, (short) 0); } /** * Create a debit GLEntry associated with this GLTransaction. * * Factory helper method. * * @param acct the account * @param amount amount * @param detail detail * @param layer layer */ public GLEntry createDebit (FinalAccount acct, BigDecimal amount, String detail, short layer) { return createGLEntry (acct, amount, detail, false, layer); } /** * Create a credit GLEntry associated with this GLTransaction. * * Factory helper method. * * @param acct the account * @param amount amount */ public GLEntry createCredit (FinalAccount acct, BigDecimal amount) { return createGLEntry (acct, amount, null, true, (short) 0); } /** * Create a credit GLEntry associated with this GLTransaction. * * Factory helper method. * * @param acct the account * @param amount amount * @param detail detail */ public GLEntry createCredit (FinalAccount acct, BigDecimal amount, String detail) { return createGLEntry (acct, amount, detail, true, (short) 0); } /** * Create a credit GLEntry associated with this GLTransaction. * * Factory helper method. * * @param acct the account * @param amount amount * @param layer the layer * @param detail detail */ public GLEntry createCredit (FinalAccount acct, BigDecimal amount, String detail, short layer) { return createGLEntry (acct, amount, detail, true, layer); } /** * * Create a reverse transaction based on this one * * @return a reversal transaction */ public GLTransaction createReverse() { GLTransaction glt = new GLTransaction ("(" + getDetail() + ")"); glt.setJournal (getJournal()); for (GLEntry e : getEntries()) { glt.createGLEntry( e.getAccount(), negate(e.getAmount()), e.getDetail(), e.isCredit(), e.getLayer() ); } return glt; } /** * * Create a reverse transaction based on this one * * @param withTags entries with any tag in <code>withTags</code> are selected (can be null) * @param withoutTags entries with any tag in <code>withoutTags</code> are discarded (can be null) * * @return a reversal transaction */ public GLTransaction createReverse(Tags withTags, Tags withoutTags) { GLTransaction glt = new GLTransaction ("(" + getDetail() + ")"); glt.setJournal (getJournal()); for (GLEntry e : getEntries()) { Tags tags = e.getTags() != null ? e.getTags() : new Tags(); if ((withTags == null || tags.containsAll(withTags)) && (withoutTags == null || !tags.containsAny(withoutTags))) { glt.createGLEntry( e.getAccount(), negate(e.getAmount()), e.getDetail(), e.isCredit(), e.getLayer() ); } } return glt; } /** * Parses a JDOM Element as defined in * <a href="http://jpos.org/minigl.dtd">minigl.dtd</a> */ public void fromXML (Element elem) throws ParseException { setDetail (elem.getChildTextTrim ("detail")); setTags (new Tags(elem.getChildTextTrim ("tags"))); setPostDate ( Util.parseDate (elem.getAttributeValue ("post-date")) ); setTimestamp ( Util.parseDateTime (elem.getAttributeValue ("date")) ); } /** * Creates a JDOM Element as defined in * <a href="http://jpos.org/minigl.dtd">minigl.dtd</a> */ public Element toXML (boolean deep) { Element elem = new Element ("transaction"); elem.setAttribute ("id", Long.toString(getId())); Util.setDateTimeAttribute (elem, "date", getTimestamp()); Util.setDateAttribute (elem, "post-date", getPostDate()); if (getDetail() != null) { Element detail = new Element ("detail").setText (getDetail()); elem.addContent (detail); } if (getTags () != null) { Element tags = new Element ("tags").setText (getTags().toString()); elem.addContent (tags); } elem.setAttribute ("journal", getJournal().getName()); for (GLEntry entry : getEntries()) { elem.addContent (entry.toXML (deep)); } return elem; } public String toString() { return new ToStringBuilder(this) .append("id", getId()) .append("detail", getDetail()) .toString(); } public boolean equals(Object other) { if ( !(other instanceof GLTransaction) ) return false; GLTransaction castOther = (GLTransaction) other; return new EqualsBuilder() .append(this.getId(), castOther.getId()) .isEquals(); } public int hashCode() { return new HashCodeBuilder() .append(getId()) .toHashCode(); } public boolean hasLayers (short[] layers) { for (GLEntry e : (List<GLEntry>) getEntries()) { if (e.hasLayers (layers)) return true; } return false; } public BigDecimal getDebits (short[] layers) { BigDecimal debits = GLSession.ZERO; for (GLEntry e : getEntries()) { if (e.isDebit() && e.hasLayers (layers)) { debits = debits.add (e.getAmount()); } } return debits; } public BigDecimal getCredits (short[] layers) { BigDecimal credits = GLSession.ZERO; for (GLEntry e : getEntries()) { if (e.isCredit() && e.hasLayers (layers)) { credits = credits.add (e.getAmount()); } } return credits; } @SuppressWarnings("unchecked") public BigDecimal getImpact(Account acct, short[] layers) { BigDecimal impact = GLSession.ZERO; for (GLEntry e : getEntries()) { if (e.getAccount().equals(acct) && e.hasLayers (layers)) { impact = impact.add (e.getImpact()); } } return impact; } private BigDecimal negate (BigDecimal bd) { return bd != null ? bd.negate() : null; } }