/****************************************************************************** * Product: Adempiere ERP & CRM Smart Business Solution * * Copyright (C) 1999-2006 ComPiere, Inc. All Rights Reserved. * * This program is free software; you can redistribute it and/or modify it * * under the terms version 2 of the GNU General Public License as published * * by the Free Software Foundation. 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 General Public License for more details. * * You should have received a copy of the GNU General Public License along * * with this program; if not, write to the Free Software Foundation, Inc., * * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * * For the text or an alternative of this public license, you may reach us * * ComPiere, Inc., 2620 Augustine Dr. #245, Santa Clara, CA 95054, USA * * or via info@compiere.org or http://www.compiere.org/license.html * *****************************************************************************/ package org.compiere.model; import java.io.File; import java.math.BigDecimal; import java.sql.ResultSet; import java.sql.Timestamp; import java.util.List; import java.util.Properties; import java.util.logging.Level; import org.compiere.process.DocAction; import org.compiere.process.DocumentEngine; import org.compiere.util.CLogger; import org.compiere.util.DB; import org.compiere.util.DisplayType; import org.compiere.util.Env; import org.compiere.util.Msg; import org.compiere.util.TimeUtil; import org.compiere.util.Util; /** * Cash Journal Model * * @author Jorg Janke * @version $Id: MCash.java,v 1.3 2006/07/30 00:51:03 jjanke Exp $ * @author victor.perez@e-evolution.com, e-Evolution http://www.e-evolution.com *
  • FR [ 1866214 ] * @see https://sourceforge.net/p/adempiere/feature-requests/298/ *
  • FR [ 2520591 ] Support multiples calendar for Org * @see https://sourceforge.net/p/adempiere/feature-requests/631/ * @author Teo Sarca, SC ARHIPAC SERVICE SRL *
  • BF [ 1831997 ] Cash journal allocation reversed *
  • BF [ 1894524 ] Pay an reversed invoice *
  • BF [ 1899477 ] MCash.getLines should return only active lines *
  • BF [ 2588326 ] Cash Lines are not correctly updated on voiding */ public class MCash extends X_C_Cash implements DocAction { /** * generated serial id */ private static final long serialVersionUID = -7632011237765946083L; /** * Get or create new Cash Journal for currency, org and date * @param ctx context * @param C_Currency_ID currency * @param AD_Org_ID org * @param dateAcct date * @param trxName transaction * @return MCash */ public static MCash get (Properties ctx, int AD_Org_ID, Timestamp dateAcct, int C_Currency_ID, String trxName) { // Existing Journal final String whereClause = "C_Cash.AD_Org_ID=?" // #1 + " AND TRUNC(C_Cash.StatementDate)=?" // #2 + " AND C_Cash.Processed='N'" + " AND EXISTS (SELECT * FROM C_CashBook cb " + "WHERE C_Cash.C_CashBook_ID=cb.C_CashBook_ID AND cb.AD_Org_ID=C_Cash.AD_Org_ID" + " AND cb.C_Currency_ID=?)"; // #3 MCash retValue = new Query(ctx, I_C_Cash.Table_Name, whereClause, trxName) .setParameters(AD_Org_ID,TimeUtil.getDay(dateAcct),C_Currency_ID) .first() ; if (retValue != null) return retValue; // Get CashBook MCashBook cb = MCashBook.get (ctx, AD_Org_ID, C_Currency_ID); if (cb == null) { s_log.warning("No CashBook for AD_Org_ID=" + AD_Org_ID + ", C_Currency_ID=" + C_Currency_ID); return null; } // Create New Journal retValue = new MCash (cb, dateAcct); retValue.saveEx(trxName); return retValue; } // get /** * Get or create new Cash Journal for CashBook and date * @param ctx context * @param C_CashBook_ID cashbook * @param dateAcct date * @param trxName transaction * @return cash */ public static MCash get (Properties ctx, int C_CashBook_ID, Timestamp dateAcct, String trxName) { final String whereClause ="C_CashBook_ID=?" // #1 + " AND TRUNC(StatementDate)=?" // #2 + " AND Processed='N'"; MCash retValue = new Query(ctx, MCash.Table_Name, whereClause, trxName) .setParameters(C_CashBook_ID, TimeUtil.getDay(dateAcct)) .first() ; if (retValue != null) return retValue; // Get CashBook MCashBook cb = new MCashBook (ctx, C_CashBook_ID, trxName); if (cb.get_ID() ==0) { s_log.warning("Not found C_CashBook_ID=" + C_CashBook_ID); return null; } // Create New Journal retValue = new MCash (cb, dateAcct); retValue.saveEx(trxName); return retValue; } // get /** Static Logger */ private static CLogger s_log = CLogger.getCLogger (MCash.class); /** * UUID based Constructor * @param ctx Context * @param C_Cash_UU UUID key * @param trxName Transaction */ public MCash(Properties ctx, String C_Cash_UU, String trxName) { super(ctx, C_Cash_UU, trxName); if (Util.isEmpty(C_Cash_UU)) setInitialDefaults(ctx); } /** * Standard Constructor * @param ctx context * @param C_Cash_ID id * @param trxName transaction */ public MCash (Properties ctx, int C_Cash_ID, String trxName) { super (ctx, C_Cash_ID, trxName); if (C_Cash_ID == 0) setInitialDefaults(ctx); } // MCash /** * Set the initial defaults for a new record */ private void setInitialDefaults(Properties ctx) { setBeginningBalance (Env.ZERO); setEndingBalance (Env.ZERO); setStatementDifference(Env.ZERO); setDocAction(DOCACTION_Complete); setDocStatus(DOCSTATUS_Drafted); // Timestamp today = TimeUtil.getDay(System.currentTimeMillis()); setStatementDate (today); // @#Date@ setDateAcct (today); // @#Date@ StringBuilder name = new StringBuilder(DisplayType.getDateFormat(DisplayType.Date).format(today)) .append(" ").append(MOrg.get(ctx, getAD_Org_ID()).getValue()); setName (name.toString()); setIsApproved(false); setPosted (false); // N setProcessed (false); } /** * Load Constructor * @param ctx context * @param rs result set * @param trxName transaction */ public MCash (Properties ctx, ResultSet rs, String trxName) { super(ctx, rs, trxName); } // MCash /** * Parent Constructor * @param cb cash book * @param today date - if null today */ public MCash (MCashBook cb, Timestamp today) { this (cb.getCtx(), 0, cb.get_TrxName()); setClientOrg(cb); setC_CashBook_ID(cb.getC_CashBook_ID()); if (today != null) { setStatementDate (today); setDateAcct (today); StringBuilder name = new StringBuilder(DisplayType.getDateFormat(DisplayType.Date).format(today)) .append(" ").append(cb.getName()); setName (name.toString()); } m_book = cb; } // MCash /** * @param ctx * @param C_Cash_ID * @param trxName * @param virtualColumns */ public MCash(Properties ctx, int C_Cash_ID, String trxName, String... virtualColumns) { super(ctx, C_Cash_ID, trxName, virtualColumns); } /** Lines */ protected MCashLine[] m_lines = null; /** CashBook */ protected MCashBook m_book = null; /** * Get Lines * @param requery true to reload from DB * @return lines */ public MCashLine[] getLines (boolean requery) { if (m_lines != null && !requery) { set_TrxName(m_lines, get_TrxName()); return m_lines; } final String whereClause =MCashLine.COLUMNNAME_C_Cash_ID+"=?"; List list = new Query(getCtx(),I_C_CashLine.Table_Name, whereClause, get_TrxName()) .setParameters(getC_Cash_ID()) .setOrderBy(I_C_CashLine.COLUMNNAME_Line+","+I_C_CashLine.COLUMNNAME_C_CashLine_ID) .setOnlyActiveRecords(true) .list(); m_lines = list.toArray(new MCashLine[list.size()]); return m_lines; } // getLines /** * Get Cash Book * @return cash book */ public MCashBook getCashBook() { if (m_book == null) m_book = MCashBook.getCopy(getCtx(), getC_CashBook_ID(), get_TrxName()); return m_book; } // getCashBook /** * Get Document No * @return name */ @Override public String getDocumentNo() { return getName(); } // getDocumentNo /** * Get Document Info * @return document info (untranslated) */ @Override public String getDocumentInfo() { StringBuilder msgreturn = new StringBuilder().append(Msg.getElement(getCtx(), "C_Cash_ID")).append(" ").append(getDocumentNo()); return msgreturn.toString(); } // getDocumentInfo /** * Create PDF * @return PDF File or null */ @Override public File createPDF () { try { StringBuilder msgfile = new StringBuilder().append(get_TableName()).append(get_ID()).append("_"); File temp = File.createTempFile(msgfile.toString(), ".pdf"); return createPDF (temp); } catch (Exception e) { log.severe("Could not create PDF - " + e.getMessage()); } return null; } // getPDF /** * Create PDF file * @param file output file * @return not implemented, always return null */ public File createPDF (File file) { return null; } // createPDF @Override protected boolean beforeSave (boolean newRecord) { setAD_Org_ID(getCashBook().getAD_Org_ID()); if (getAD_Org_ID() == 0) { log.saveError("Error", Msg.parseTranslation(getCtx(), "@AD_Org_ID@")); return false; } // Calculate End Balance setEndingBalance(getBeginningBalance().add(getStatementDifference())); return true; } // beforeSave /** * Process document * @param processAction document action * @return true if performed */ @Override public boolean processIt (String processAction) { m_processMsg = null; DocumentEngine engine = new DocumentEngine (this, getDocStatus()); return engine.processIt (processAction, getDocAction()); } // process /** Process Message */ protected String m_processMsg = null; /** Just Prepared Flag */ protected boolean m_justPrepared = false; /** * Unlock Document. * @return true if success */ @Override public boolean unlockIt() { if (log.isLoggable(Level.INFO)) log.info(toString()); setProcessing(false); return true; } // unlockIt /** * Invalidate Document * @return true if success */ @Override public boolean invalidateIt() { if (log.isLoggable(Level.INFO)) log.info(toString()); setDocAction(DOCACTION_Prepare); return true; } // invalidateIt /** * Prepare Document * @return new status (In Progress or Invalid) */@Override public String prepareIt() { if (log.isLoggable(Level.INFO)) log.info(toString()); m_processMsg = ModelValidationEngine.get().fireDocValidate(this, ModelValidator.TIMING_BEFORE_PREPARE); if (m_processMsg != null) return DocAction.STATUS_Invalid; // Std Period open? if (!MPeriod.isOpen(getCtx(), getDateAcct(), MDocType.DOCBASETYPE_CashJournal, getAD_Org_ID())) { m_processMsg = "@PeriodClosed@"; return DocAction.STATUS_Invalid; } MCashLine[] lines = getLines(false); if (lines.length == 0) { m_processMsg = "@NoLines@"; return DocAction.STATUS_Invalid; } // Add up Amounts BigDecimal difference = Env.ZERO; int C_Currency_ID = getC_Currency_ID(); for (int i = 0; i < lines.length; i++) { MCashLine line = lines[i]; if (!line.isActive()) continue; if (C_Currency_ID == line.getC_Currency_ID()) difference = difference.add(line.getAmount()); else { BigDecimal amt = MConversionRate.convert(getCtx(), line.getAmount(), line.getC_Currency_ID(), C_Currency_ID, getDateAcct(), 0, getAD_Client_ID(), getAD_Org_ID()); if (amt == null) { m_processMsg = "No Conversion Rate found - @C_CashLine_ID@= " + line.getLine(); return DocAction.STATUS_Invalid; } difference = difference.add(amt); } } setStatementDifference(difference); m_processMsg = ModelValidationEngine.get().fireDocValidate(this, ModelValidator.TIMING_AFTER_PREPARE); if (m_processMsg != null) return DocAction.STATUS_Invalid; m_justPrepared = true; if (!DOCACTION_Complete.equals(getDocAction())) setDocAction(DOCACTION_Complete); return DocAction.STATUS_InProgress; } // prepareIt /** * Approve Document * @return true if success */ @Override public boolean approveIt() { if (log.isLoggable(Level.INFO)) log.info(toString()); setIsApproved(true); return true; } // approveIt /** * Reject Approval * @return true if success */ @Override public boolean rejectIt() { if (log.isLoggable(Level.INFO)) log.info(toString()); setIsApproved(false); return true; } // rejectIt /** * Complete Document * @return new status (Complete, In Progress, Invalid, Waiting ..) */ @Override public String completeIt() { // Re-Check if (!m_justPrepared) { String status = prepareIt(); m_justPrepared = false; if (!DocAction.STATUS_InProgress.equals(status)) return status; } m_processMsg = ModelValidationEngine.get().fireDocValidate(this, ModelValidator.TIMING_BEFORE_COMPLETE); if (m_processMsg != null) return DocAction.STATUS_Invalid; // Implicit Approval if (!isApproved()) approveIt(); // if (log.isLoggable(Level.INFO)) log.info(toString()); MCashLine[] lines = getLines(false); for (int i = 0; i < lines.length; i++) { MCashLine line = lines[i]; if (MCashLine.CASHTYPE_Invoice.equals(line.getCashType())) { // Check if the invoice is completed - teo_sarca BF [ 1894524 ] MInvoice invoice = line.getInvoice(); if ( !MInvoice.DOCSTATUS_Completed.equals(invoice.getDocStatus()) && !MInvoice.DOCSTATUS_Closed.equals(invoice.getDocStatus()) && !MInvoice.DOCSTATUS_Reversed.equals(invoice.getDocStatus()) && !MInvoice.DOCSTATUS_Voided.equals(invoice.getDocStatus()) ) { m_processMsg = "@Line@ "+line.getLine()+": @InvoiceCreateDocNotCompleted@"; return DocAction.STATUS_Invalid; } // StringBuilder name = new StringBuilder().append(Msg.translate(getCtx(), "C_Cash_ID")).append(": ").append(getName()) .append(" - ").append(Msg.translate(getCtx(), "Line")).append(" ").append(line.getLine()); MAllocationHdr hdr = new MAllocationHdr(getCtx(), false, getDateAcct(), line.getC_Currency_ID(), name.toString(), get_TrxName()); hdr.setAD_Org_ID(getAD_Org_ID()); if (!hdr.save()) { m_processMsg = CLogger.retrieveErrorString("Could not create Allocation Hdr"); return DocAction.STATUS_Invalid; } // Allocation Line MAllocationLine aLine = new MAllocationLine (hdr, line.getAmount(), line.getDiscountAmt(), line.getWriteOffAmt(), Env.ZERO); aLine.setC_Invoice_ID(line.getC_Invoice_ID()); aLine.setC_CashLine_ID(line.getC_CashLine_ID()); if (!aLine.save()) { m_processMsg = CLogger.retrieveErrorString("Could not create Allocation Line"); return DocAction.STATUS_Invalid; } // Should start WF if(!hdr.processIt(DocAction.ACTION_Complete)) { m_processMsg = CLogger.retrieveErrorString("Could not process Allocation"); return DocAction.STATUS_Invalid; } if (!hdr.save()) { m_processMsg = CLogger.retrieveErrorString("Could not save Allocation"); return DocAction.STATUS_Invalid; } } else if (MCashLine.CASHTYPE_BankAccountTransfer.equals(line.getCashType())) { // Payment just as intermediate info MPayment pay = new MPayment (getCtx(), 0, get_TrxName()); pay.setAD_Org_ID(getAD_Org_ID()); String documentNo = getName(); pay.setDocumentNo(documentNo); pay.setR_PnRef(documentNo); pay.set_ValueNoCheck("TrxType", "X"); // Transfer pay.set_ValueNoCheck("TenderType", "X"); // //Modification for cash payment - Posterita pay.setC_CashBook_ID(getC_CashBook_ID()); //End of modification - Posterita pay.setC_BankAccount_ID(line.getC_BankAccount_ID()); pay.setC_DocType_ID(true); // Receipt pay.setDateTrx(getStatementDate()); pay.setDateAcct(getDateAcct()); pay.setAmount(line.getC_Currency_ID(), line.getAmount().negate()); // Transfer pay.setDescription(line.getDescription()); pay.setDocStatus(MPayment.DOCSTATUS_Closed); pay.setDocAction(MPayment.DOCACTION_None); pay.setPosted(true); pay.setIsAllocated(true); // Has No Allocation! pay.setProcessed(true); if (!pay.save()) { m_processMsg = CLogger.retrieveErrorString("Could not create Payment"); return DocAction.STATUS_Invalid; } line.setC_Payment_ID(pay.getC_Payment_ID()); if (!line.save()) { m_processMsg = "Could not update Cash Line"; return DocAction.STATUS_Invalid; } } } // User Validation String valid = ModelValidationEngine.get().fireDocValidate(this, ModelValidator.TIMING_AFTER_COMPLETE); if (valid != null) { m_processMsg = valid; return DocAction.STATUS_Invalid; } // setProcessed(true); setDocAction(DOCACTION_Close); return DocAction.STATUS_Completed; } // completeIt /** * Void Document. * Same as Close. * @return true if successfully voided */ @Override public boolean voidIt() { if (log.isLoggable(Level.INFO)) log.info(toString()); // Before Void m_processMsg = ModelValidationEngine.get().fireDocValidate(this,ModelValidator.TIMING_BEFORE_VOID); if (m_processMsg != null) return false; //FR [ 1866214 ] boolean retValue = reverseIt(); if (retValue) { // After Void m_processMsg = ModelValidationEngine.get().fireDocValidate(this,ModelValidator.TIMING_AFTER_VOID); if (m_processMsg != null) return false; setDocAction(DOCACTION_None); } return retValue; } // voidIt /** * Reverse Cash * Period needs to be open * @return true if reversed */ protected boolean reverseIt() { if (DOCSTATUS_Closed.equals(getDocStatus()) || DOCSTATUS_Reversed.equals(getDocStatus()) || DOCSTATUS_Voided.equals(getDocStatus())) { m_processMsg = "Document Closed: " + getDocStatus(); setDocAction(DOCACTION_None); return false; } // Can we delete posting if (!MPeriod.isOpen(getCtx(), this.getDateAcct(), MPeriodControl.DOCBASETYPE_CashJournal, getAD_Org_ID())) throw new IllegalStateException("@PeriodClosed@"); // Reverse Allocations MAllocationHdr[] allocations = MAllocationHdr.getOfCash(getCtx(), getC_Cash_ID(), get_TrxName()); for(MAllocationHdr allocation : allocations) { allocation.reverseCorrectIt(); if(!allocation.save()) throw new IllegalStateException("Cannot reverse allocations"); } MCashLine[] cashlines = getLines(true); for (MCashLine cashline : cashlines ) { BigDecimal oldAmount = cashline.getAmount(); BigDecimal oldDiscount = cashline.getDiscountAmt(); BigDecimal oldWriteOff = cashline.getWriteOffAmt(); cashline.setAmount(Env.ZERO); cashline.setDiscountAmt(Env.ZERO); cashline.setWriteOffAmt(Env.ZERO); StringBuilder msgadd = new StringBuilder().append(Msg.getMsg(getCtx(), "Voided")) .append(" (Amount=").append(oldAmount).append(", Discount=").append(oldDiscount) .append(", WriteOff=").append(oldWriteOff).append(", )"); cashline.addDescription(msgadd.toString()); if (MCashLine.CASHTYPE_BankAccountTransfer.equals(cashline.getCashType())) { if (cashline.getC_Payment_ID() == 0) throw new IllegalStateException("Cannot reverse payment"); MPayment payment = new MPayment(getCtx(), cashline.getC_Payment_ID(),get_TrxName()); payment.reverseCorrectIt(); payment.saveEx(); } cashline.saveEx(); } setName(getName()+"^"); addDescription(Msg.getMsg(getCtx(), "Voided")); setDocStatus(DOCSTATUS_Reversed); // for direct calls setProcessed(true); setPosted(true); setDocAction(DOCACTION_None); saveEx(); // Delete Posting MFactAcct.deleteEx(Table_ID, getC_Cash_ID(), get_TrxName()); return true; } // reverse /** * Add to Description * @param description text */ public void addDescription (String description) { String desc = getDescription(); if (desc == null) setDescription(description); else{ StringBuilder msgd = new StringBuilder(desc).append(" | ").append(description); setDescription(msgd.toString()); } } // addDescription /** * Close Document. * Cancel not delivered Quantities * @return true if success */ @Override public boolean closeIt() { if (log.isLoggable(Level.INFO)) log.info(toString()); // Before Close m_processMsg = ModelValidationEngine.get().fireDocValidate(this,ModelValidator.TIMING_BEFORE_CLOSE); if (m_processMsg != null) return false; setDocAction(DOCACTION_None); // After Close m_processMsg = ModelValidationEngine.get().fireDocValidate(this,ModelValidator.TIMING_AFTER_CLOSE); if (m_processMsg != null) return false; return true; } // closeIt /** * Reverse Correction * @return true if success */ @Override public boolean reverseCorrectIt() { if (log.isLoggable(Level.INFO)) log.info(toString()); // Before reverseCorrect m_processMsg = ModelValidationEngine.get().fireDocValidate(this,ModelValidator.TIMING_BEFORE_REVERSECORRECT); if (m_processMsg != null) return false; //FR [ 1866214 ] boolean retValue = reverseIt(); if (retValue) { // After reverseCorrect m_processMsg = ModelValidationEngine.get().fireDocValidate(this,ModelValidator.TIMING_AFTER_REVERSECORRECT); if (m_processMsg != null) return false; } return retValue; } // reverseCorrectionIt /** * Reverse Accrual - none * @return true if success */ @Override public boolean reverseAccrualIt() { if (log.isLoggable(Level.INFO)) log.info(toString()); // Before reverseAccrual m_processMsg = ModelValidationEngine.get().fireDocValidate(this,ModelValidator.TIMING_BEFORE_REVERSEACCRUAL); if (m_processMsg != null) return false; // After reverseAccrual m_processMsg = ModelValidationEngine.get().fireDocValidate(this,ModelValidator.TIMING_AFTER_REVERSEACCRUAL); if (m_processMsg != null) return false; return false; } // reverseAccrualIt /** * Re-activate * @return true if success */ @Override public boolean reActivateIt() { if (log.isLoggable(Level.INFO)) log.info(toString()); // Before reActivate m_processMsg = ModelValidationEngine.get().fireDocValidate(this,ModelValidator.TIMING_BEFORE_REACTIVATE); if (m_processMsg != null) return false; setProcessed(false); if (reverseCorrectIt()) return true; // After reActivate m_processMsg = ModelValidationEngine.get().fireDocValidate(this,ModelValidator.TIMING_AFTER_REACTIVATE); if (m_processMsg != null) return false; return false; } // reActivateIt /** * Set Processed * @param processed processed */ @Override public void setProcessed (boolean processed) { super.setProcessed (processed); StringBuilder sql = new StringBuilder("UPDATE C_CashLine SET Processed='") .append((processed ? "Y" : "N")) .append("' WHERE C_Cash_ID=").append(getC_Cash_ID()); int noLine = DB.executeUpdate (sql.toString(), get_TrxName()); m_lines = null; if (log.isLoggable(Level.FINE)) log.fine(processed + " - Lines=" + noLine); } // setProcessed /** * String Representation * @return info */ @Override public String toString () { StringBuilder sb = new StringBuilder ("MCash["); sb.append (get_ID ()) .append ("-").append (getName()) .append(", Balance=").append(getBeginningBalance()) .append("->").append(getEndingBalance()) .append ("]"); return sb.toString (); } // toString /** * Get Summary * @return Summary of Document */ @Override public String getSummary() { StringBuilder sb = new StringBuilder(); sb.append(getName()); // : Total Lines = 123.00 (#1) sb.append(": ") .append(Msg.translate(getCtx(),"BeginningBalance")).append("=").append(getBeginningBalance()) .append(",") .append(Msg.translate(getCtx(),"EndingBalance")).append("=").append(getEndingBalance()) .append(" (#").append(getLines(false).length).append(")"); // - Description if (getDescription() != null && getDescription().length() > 0) sb.append(" - ").append(getDescription()); return sb.toString(); } // getSummary /** * Get Process Message * @return clear text error message */ @Override public String getProcessMsg() { return m_processMsg; } // getProcessMsg /** * Get Document Owner (Responsible) * @return AD_User_ID */ @Override public int getDoc_User_ID() { return getCreatedBy(); } // getDoc_User_ID /** * Get Document Approval Amount * @return amount difference */ @Override public BigDecimal getApprovalAmt() { return getStatementDifference(); } // getApprovalAmt /** * Get Currency * @return Currency */ @Override public int getC_Currency_ID () { return getCashBook().getC_Currency_ID(); } // getC_Currency_ID /** * Document Status is Complete, Closed or Reversed * @return true if CO, CL or RE */ public boolean isComplete() { String ds = getDocStatus(); return DOCSTATUS_Completed.equals(ds) || DOCSTATUS_Closed.equals(ds) || DOCSTATUS_Reversed.equals(ds); } // isComplete } // MCash