From 9802eea0be208360ca093ee700673f997bb9ebbe Mon Sep 17 00:00:00 2001 From: vpj-cd Date: Fri, 17 Nov 2006 03:13:45 +0000 Subject: [PATCH] move branch adempire311 to trunk --- serverRoot/.classpath | 14 + serverRoot/.packaging | 2 + serverRoot/.project | 22 + serverRoot/.xdoclet | 2 + serverRoot/RUN_build.bat | 19 + serverRoot/RUN_build.sh | 18 + serverRoot/ServerRoot.html | 33 + serverRoot/build.xml | 235 ++ serverRoot/documentation.bat | 5 + serverRoot/packages.txt | 4 + serverRoot/packaging-build.xml | 4 + serverRoot/src/ear/application.xml | 24 + .../ejb/org/compiere/session/ServerBean.java | 669 +++++ .../ejb/org/compiere/session/StatusBean.java | 288 +++ .../main/server/org/compiere/acct/Doc.java | 2162 +++++++++++++++++ .../server/org/compiere/acct/DocLine.java | 1016 ++++++++ .../org/compiere/acct/DocLine_Allocation.java | 142 ++ .../org/compiere/acct/DocLine_Bank.java | 122 + .../org/compiere/acct/DocLine_Cash.java | 138 ++ .../main/server/org/compiere/acct/DocTax.java | 240 ++ .../org/compiere/acct/Doc_Allocation.java | 892 +++++++ .../server/org/compiere/acct/Doc_Bank.java | 223 ++ .../server/org/compiere/acct/Doc_Cash.java | 254 ++ .../org/compiere/acct/Doc_GLJournal.java | 168 ++ .../server/org/compiere/acct/Doc_InOut.java | 283 +++ .../org/compiere/acct/Doc_Inventory.java | 179 ++ .../server/org/compiere/acct/Doc_Invoice.java | 953 ++++++++ .../org/compiere/acct/Doc_MatchInv.java | 327 +++ .../server/org/compiere/acct/Doc_MatchPO.java | 243 ++ .../org/compiere/acct/Doc_Movement.java | 179 ++ .../server/org/compiere/acct/Doc_Order.java | 624 +++++ .../server/org/compiere/acct/Doc_Payment.java | 183 ++ .../org/compiere/acct/Doc_Production.java | 216 ++ .../org/compiere/acct/Doc_ProjectIssue.java | 221 ++ .../org/compiere/acct/Doc_Requisition.java | 153 ++ .../main/server/org/compiere/acct/Fact.java | 860 +++++++ .../server/org/compiere/acct/FactLine.java | 949 ++++++++ .../server/org/compiere/acct/Matcher.java | 165 ++ .../server/org/compiere/acct/ProductInfo.java | 390 +++ .../compiere/ldap/LdapConnectionHandler.java | 118 + .../server/org/compiere/ldap/LdapMessage.java | 172 ++ .../org/compiere/ldap/LdapProcessor.java | 141 ++ .../org/compiere/ldap/LdapProcessorModel.java | 158 ++ .../server/org/compiere/ldap/LdapResult.java | 147 ++ .../org/compiere/server/AcctProcessor.java | 172 ++ .../org/compiere/server/AdempiereServer.java | 388 +++ .../compiere/server/AdempiereServerGroup.java | 90 + .../compiere/server/AdempiereServerMgr.java | 532 ++++ .../org/compiere/server/AlertProcessor.java | 323 +++ .../org/compiere/server/EMailProcessor.java | 700 ++++++ .../org/compiere/server/RequestProcessor.java | 655 +++++ .../server/org/compiere/server/Scheduler.java | 290 +++ .../compiere/server/WorkflowProcessor.java | 485 ++++ .../org/compiere/web/AdempiereMonitor.java | 869 +++++++ .../compiere/web/AdempiereMonitorFilter.java | 166 ++ .../org/compiere/web/ServerStatus.java | 96 + .../servlet/org/compiere/web/StatusInfo.java | 168 ++ serverRoot/src/web/AdemPiere150x50.gif | Bin 0 -> 2603 bytes serverRoot/src/web/Adempiere120x60.gif | Bin 0 -> 2025 bytes serverRoot/src/web/Background.gif | Bin 0 -> 2301 bytes serverRoot/src/web/C32.gif | Bin 0 -> 2213 bytes serverRoot/src/web/Logo.gif | Bin 0 -> 3372 bytes serverRoot/src/web/WEB-INF/web.xml | 75 + serverRoot/src/web/adempiere.html | 242 ++ serverRoot/src/web/adempiere.jnlp | 30 + serverRoot/src/web/adempiere_logo.gif | Bin 0 -> 7475 bytes serverRoot/src/web/adempiere_logo.png | Bin 0 -> 2176 bytes serverRoot/src/web/adempiere_logo2.gif | Bin 0 -> 7475 bytes serverRoot/src/web/adempiere_logo3.png | Bin 0 -> 5712 bytes serverRoot/src/web/applet.html | 81 + serverRoot/src/web/error.html | 13 + serverRoot/src/web/favicon.ico | Bin 0 -> 4286 bytes serverRoot/src/web/footer_bar.gif | Bin 0 -> 64062 bytes serverRoot/src/web/header_banner.jpg | Bin 0 -> 16220 bytes serverRoot/src/web/header_banner.png | Bin 0 -> 3472 bytes serverRoot/src/web/header_bar.jpg | Bin 0 -> 6757 bytes serverRoot/src/web/index.html | 13 + serverRoot/src/web/robots.txt | 4 + serverRoot/src/web/standard.css | 151 ++ serverRoot/src/web/webstart.jpg | Bin 0 -> 4577 bytes serverRoot/src/web/webstart_s.jpg | Bin 0 -> 1750 bytes serverRoot/src/web/zip.gif | Bin 0 -> 1305 bytes serverRoot/xdoclet-build.xml | 37 + 83 files changed, 18937 insertions(+) create mode 100644 serverRoot/.classpath create mode 100644 serverRoot/.packaging create mode 100644 serverRoot/.project create mode 100644 serverRoot/.xdoclet create mode 100644 serverRoot/RUN_build.bat create mode 100644 serverRoot/RUN_build.sh create mode 100644 serverRoot/ServerRoot.html create mode 100644 serverRoot/build.xml create mode 100644 serverRoot/documentation.bat create mode 100644 serverRoot/packages.txt create mode 100644 serverRoot/packaging-build.xml create mode 100644 serverRoot/src/ear/application.xml create mode 100644 serverRoot/src/main/ejb/org/compiere/session/ServerBean.java create mode 100644 serverRoot/src/main/ejb/org/compiere/session/StatusBean.java create mode 100644 serverRoot/src/main/server/org/compiere/acct/Doc.java create mode 100644 serverRoot/src/main/server/org/compiere/acct/DocLine.java create mode 100644 serverRoot/src/main/server/org/compiere/acct/DocLine_Allocation.java create mode 100644 serverRoot/src/main/server/org/compiere/acct/DocLine_Bank.java create mode 100644 serverRoot/src/main/server/org/compiere/acct/DocLine_Cash.java create mode 100644 serverRoot/src/main/server/org/compiere/acct/DocTax.java create mode 100644 serverRoot/src/main/server/org/compiere/acct/Doc_Allocation.java create mode 100644 serverRoot/src/main/server/org/compiere/acct/Doc_Bank.java create mode 100644 serverRoot/src/main/server/org/compiere/acct/Doc_Cash.java create mode 100644 serverRoot/src/main/server/org/compiere/acct/Doc_GLJournal.java create mode 100644 serverRoot/src/main/server/org/compiere/acct/Doc_InOut.java create mode 100644 serverRoot/src/main/server/org/compiere/acct/Doc_Inventory.java create mode 100644 serverRoot/src/main/server/org/compiere/acct/Doc_Invoice.java create mode 100644 serverRoot/src/main/server/org/compiere/acct/Doc_MatchInv.java create mode 100644 serverRoot/src/main/server/org/compiere/acct/Doc_MatchPO.java create mode 100644 serverRoot/src/main/server/org/compiere/acct/Doc_Movement.java create mode 100644 serverRoot/src/main/server/org/compiere/acct/Doc_Order.java create mode 100644 serverRoot/src/main/server/org/compiere/acct/Doc_Payment.java create mode 100644 serverRoot/src/main/server/org/compiere/acct/Doc_Production.java create mode 100644 serverRoot/src/main/server/org/compiere/acct/Doc_ProjectIssue.java create mode 100644 serverRoot/src/main/server/org/compiere/acct/Doc_Requisition.java create mode 100644 serverRoot/src/main/server/org/compiere/acct/Fact.java create mode 100644 serverRoot/src/main/server/org/compiere/acct/FactLine.java create mode 100644 serverRoot/src/main/server/org/compiere/acct/Matcher.java create mode 100644 serverRoot/src/main/server/org/compiere/acct/ProductInfo.java create mode 100644 serverRoot/src/main/server/org/compiere/ldap/LdapConnectionHandler.java create mode 100644 serverRoot/src/main/server/org/compiere/ldap/LdapMessage.java create mode 100644 serverRoot/src/main/server/org/compiere/ldap/LdapProcessor.java create mode 100644 serverRoot/src/main/server/org/compiere/ldap/LdapProcessorModel.java create mode 100644 serverRoot/src/main/server/org/compiere/ldap/LdapResult.java create mode 100644 serverRoot/src/main/server/org/compiere/server/AcctProcessor.java create mode 100644 serverRoot/src/main/server/org/compiere/server/AdempiereServer.java create mode 100644 serverRoot/src/main/server/org/compiere/server/AdempiereServerGroup.java create mode 100644 serverRoot/src/main/server/org/compiere/server/AdempiereServerMgr.java create mode 100644 serverRoot/src/main/server/org/compiere/server/AlertProcessor.java create mode 100644 serverRoot/src/main/server/org/compiere/server/EMailProcessor.java create mode 100644 serverRoot/src/main/server/org/compiere/server/RequestProcessor.java create mode 100644 serverRoot/src/main/server/org/compiere/server/Scheduler.java create mode 100644 serverRoot/src/main/server/org/compiere/server/WorkflowProcessor.java create mode 100644 serverRoot/src/main/servlet/org/compiere/web/AdempiereMonitor.java create mode 100644 serverRoot/src/main/servlet/org/compiere/web/AdempiereMonitorFilter.java create mode 100644 serverRoot/src/main/servlet/org/compiere/web/ServerStatus.java create mode 100644 serverRoot/src/main/servlet/org/compiere/web/StatusInfo.java create mode 100644 serverRoot/src/web/AdemPiere150x50.gif create mode 100644 serverRoot/src/web/Adempiere120x60.gif create mode 100644 serverRoot/src/web/Background.gif create mode 100644 serverRoot/src/web/C32.gif create mode 100644 serverRoot/src/web/Logo.gif create mode 100644 serverRoot/src/web/WEB-INF/web.xml create mode 100644 serverRoot/src/web/adempiere.html create mode 100644 serverRoot/src/web/adempiere.jnlp create mode 100644 serverRoot/src/web/adempiere_logo.gif create mode 100644 serverRoot/src/web/adempiere_logo.png create mode 100644 serverRoot/src/web/adempiere_logo2.gif create mode 100644 serverRoot/src/web/adempiere_logo3.png create mode 100644 serverRoot/src/web/applet.html create mode 100644 serverRoot/src/web/error.html create mode 100644 serverRoot/src/web/favicon.ico create mode 100644 serverRoot/src/web/footer_bar.gif create mode 100644 serverRoot/src/web/header_banner.jpg create mode 100644 serverRoot/src/web/header_banner.png create mode 100644 serverRoot/src/web/header_bar.jpg create mode 100644 serverRoot/src/web/index.html create mode 100644 serverRoot/src/web/robots.txt create mode 100644 serverRoot/src/web/standard.css create mode 100644 serverRoot/src/web/webstart.jpg create mode 100644 serverRoot/src/web/webstart_s.jpg create mode 100644 serverRoot/src/web/zip.gif create mode 100644 serverRoot/xdoclet-build.xml diff --git a/serverRoot/.classpath b/serverRoot/.classpath new file mode 100644 index 0000000000..93a8c7c246 --- /dev/null +++ b/serverRoot/.classpath @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/serverRoot/.packaging b/serverRoot/.packaging new file mode 100644 index 0000000000..a336b0bb42 --- /dev/null +++ b/serverRoot/.packaging @@ -0,0 +1,2 @@ + + diff --git a/serverRoot/.project b/serverRoot/.project new file mode 100644 index 0000000000..292a281227 --- /dev/null +++ b/serverRoot/.project @@ -0,0 +1,22 @@ + + + serverRoot + + + base + dbPort + looks + server + tools + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/serverRoot/.xdoclet b/serverRoot/.xdoclet new file mode 100644 index 0000000000..a336b0bb42 --- /dev/null +++ b/serverRoot/.xdoclet @@ -0,0 +1,2 @@ + + diff --git a/serverRoot/RUN_build.bat b/serverRoot/RUN_build.bat new file mode 100644 index 0000000000..444db2175d --- /dev/null +++ b/serverRoot/RUN_build.bat @@ -0,0 +1,19 @@ +@Title Build Adempiere Root +@Rem $Header: /cvsroot/adempiere/serverRoot/RUN_build.bat,v 1.11 2005/09/16 00:50:14 jjanke Exp $ + +@CALL ..\utils_dev\myDevEnv.bat +@IF NOT %ADEMPIERE_ENV%==Y GOTO NOBUILD + +@echo Cleanup ... +@"%JAVA_HOME%\bin\java" -Dant.home="." %ANT_PROPERTIES% org.apache.tools.ant.Main clean + +@echo Building ... +@"%JAVA_HOME%\bin\java" -Dant.home="." %ANT_PROPERTIES% org.apache.tools.ant.Main main + +@Echo Done ... +@sleep 60 +@exit + +:NOBUILD +@Echo Check myDevEnv.bat (copy from myDevEnvTemplate.bat) +@Pause diff --git a/serverRoot/RUN_build.sh b/serverRoot/RUN_build.sh new file mode 100644 index 0000000000..5f80633c60 --- /dev/null +++ b/serverRoot/RUN_build.sh @@ -0,0 +1,18 @@ +# Module compiling script +# Ported from Windows script Marek Mosiewicz + + +SAVED_DIR=`pwd` #save current dir +cd `dirname $0`/../utils_dev #change dir to place where script resides - doesn not work with sym links +UTILS_DEV=`pwd` #this is adempiere source +cd $SAVED_DIR #back to the saved directory + +. $UTILS_DEV/myDevEnv.sh #call environment +echo done +if [ ! $ADEMPIERE_ENV==Y ] ; then + echo "Can't set developemeent environemnt - check myDevEnv.sh" + exit 1 +fi + +echo running Ant +$JAVA_HOME/bin/java -Dant.home="." $ANT_PROPERTIES org.apache.tools.ant.Main diff --git a/serverRoot/ServerRoot.html b/serverRoot/ServerRoot.html new file mode 100644 index 0000000000..7cc45612f2 --- /dev/null +++ b/serverRoot/ServerRoot.html @@ -0,0 +1,33 @@ + + + +JBuilder Project ServerRoot.jpx + + +

Project ServerRoot Notes

+
+ + + + +
Title: + + +
Author: + + +
Company: + + +
Description: + + +

+

Things to do...

+
    + +
  1. First +
  2. Second +
+ + diff --git a/serverRoot/build.xml b/serverRoot/build.xml new file mode 100644 index 0000000000..bfc474cd19 --- /dev/null +++ b/serverRoot/build.xml @@ -0,0 +1,235 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/serverRoot/documentation.bat b/serverRoot/documentation.bat new file mode 100644 index 0000000000..182c3fd299 --- /dev/null +++ b/serverRoot/documentation.bat @@ -0,0 +1,5 @@ +@Rem API Documentation for Server + +call ..\doc\documentation.bat "src\main\client;src\main\ejb;src\main\server;src\main\servlet" doc -private + +@pause diff --git a/serverRoot/packages.txt b/serverRoot/packages.txt new file mode 100644 index 0000000000..88cb732d7d --- /dev/null +++ b/serverRoot/packages.txt @@ -0,0 +1,4 @@ +org.compiere.client +org.compiere.session +org.compiere.server +org.compiere.web diff --git a/serverRoot/packaging-build.xml b/serverRoot/packaging-build.xml new file mode 100644 index 0000000000..971b488314 --- /dev/null +++ b/serverRoot/packaging-build.xml @@ -0,0 +1,4 @@ + + + + diff --git a/serverRoot/src/ear/application.xml b/serverRoot/src/ear/application.xml new file mode 100644 index 0000000000..21074ce959 --- /dev/null +++ b/serverRoot/src/ear/application.xml @@ -0,0 +1,24 @@ + + + + adempiereRoot + Adempiere Root + + AdempiereSLib.jar + + + Adempiere.jar + + + adempiereRoot.jar + + + + adempiereRoot.war + /admin + + + diff --git a/serverRoot/src/main/ejb/org/compiere/session/ServerBean.java b/serverRoot/src/main/ejb/org/compiere/session/ServerBean.java new file mode 100644 index 0000000000..d97c6ed171 --- /dev/null +++ b/serverRoot/src/main/ejb/org/compiere/session/ServerBean.java @@ -0,0 +1,669 @@ +/****************************************************************************** + * 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.session; + +import java.io.*; +import java.sql.*; +import java.util.*; +import java.util.logging.*; +import javax.ejb.*; +import javax.sql.*; + +import org.compiere.*; +import org.compiere.acct.*; +import org.compiere.model.*; +import org.compiere.process.*; +import org.compiere.util.*; +import org.compiere.wf.*; + +/** + * Adempiere Server Bean. + * + * @ejb.bean name="adempiere/Server" + * display-name="Adempiere Server Session Bean" + * type="Stateless" + * view-type="both" + * transaction-type="Bean" + * jndi-name="adempiere/Server" + * local-jndi-name="adempiere/ServerLocal" + * + * @ejb.ejb-ref ejb-name="adempiere/Server" + * view-type="both" + * ref-name="adempiere/Server" + * @ejb.ejb-ref ejb-name="adempiere/Server" + * view-type="local" + * ref-name="adempiere/ServerLocal" + * + * @author Jorg Janke + * @version $Id: ServerBean.java,v 1.3 2006/07/30 00:53:33 jjanke Exp $ + */ +public class ServerBean implements SessionBean +{ + /** Context */ + private SessionContext m_Context; + /** Logger */ + private static CLogger log = CLogger.getCLogger(ServerBean.class); + // + private static int s_no = 0; + private int m_no = 0; + // + private int m_windowCount = 0; + private int m_postCount = 0; + private int m_processCount = 0; + private int m_workflowCount = 0; + private int m_paymentCount = 0; + private int m_nextSeqCount = 0; + private int m_stmt_rowSetCount = 0; + private int m_stmt_updateCount = 0; + private int m_cacheResetCount = 0; + private int m_updateLOBCount = 0; + + /** + * Get and create Window Model Value Object + * @ejb.interface-method view-type="both" + * + * @param ctx Environment Properties + * @param WindowNo number of this window + * @param AD_Window_ID the internal number of the window, if not 0, AD_Menu_ID is ignored + * @param AD_Menu_ID ine internal menu number, used when AD_Window_ID is 0 + * @return initialized Window Model + */ + public GridWindowVO getWindowVO (Properties ctx, int WindowNo, int AD_Window_ID, int AD_Menu_ID) + { + log.info ("getWindowVO[" + m_no + "] Window=" + AD_Window_ID); + // log.fine(ctx); + GridWindowVO vo = GridWindowVO.create(ctx, WindowNo, AD_Window_ID, AD_Menu_ID); + m_windowCount++; + return vo; + } // getWindowVO + + + /** + * Post Immediate + * @ejb.interface-method view-type="both" + * + * @param ctx Client Context + * @param AD_Client_ID Client ID of Document + * @param AD_Table_ID Table ID of Document + * @param Record_ID Record ID of this document + * @param force force posting + * @param trxName transaction + * @return null, if success or error message + */ + public String postImmediate (Properties ctx, + int AD_Client_ID, int AD_Table_ID, int Record_ID, boolean force, String trxName) + { + log.info ("[" + m_no + "] Table=" + AD_Table_ID + ", Record=" + Record_ID); + m_postCount++; + MAcctSchema[] ass = MAcctSchema.getClientAcctSchema(ctx, AD_Client_ID); + return Doc.postImmediate(ass, AD_Table_ID, Record_ID, force, trxName); + } // postImmediate + + /************************************************************************* + * Get Prepared Statement ResultSet + * @ejb.interface-method view-type="both" + * + * @param info Result info + * @return RowSet + * @throws NotSerializableException + */ + public RowSet pstmt_getRowSet (CStatementVO info) + throws NotSerializableException + { + log.finer("[" + m_no + "]"); + m_stmt_rowSetCount++; + CPreparedStatement pstmt = new CPreparedStatement(info); + return pstmt.remote_getRowSet(); + } // pstmt_getRowSet + + /** + * Get Statement ResultSet + * @ejb.interface-method view-type="both" + * + * @param info Result info + * @return RowSet + */ + public RowSet stmt_getRowSet (CStatementVO info) + { + log.finer("[" + m_no + "]"); + m_stmt_rowSetCount++; + CStatement stmt = new CStatement(info); + return stmt.remote_getRowSet(); + } // stmt_getRowSet + + /** + * Execute Update + * @ejb.interface-method view-type="both" + * + * @param info Result info + * @return row count + */ + public int stmt_executeUpdate (CStatementVO info) + { + log.finer("[" + m_no + "]"); + m_stmt_updateCount++; + if (info.getParameterCount() == 0) + { + CStatement stmt = new CStatement(info); + return stmt.remote_executeUpdate(); + } + CPreparedStatement pstmt = new CPreparedStatement(info); + return pstmt.remote_executeUpdate(); + } // stmt_executeUpdate + + /************************************************************************* + * Get next number for Key column = 0 is Error. + * @ejb.interface-method view-type="both" + * + * @param AD_Client_ID client + * @param TableName table name + * @param trxName optional Transaction Name + * @return next no + */ + public int getNextID (int AD_Client_ID, String TableName, String trxName) + { + int retValue = MSequence.getNextID (AD_Client_ID, TableName, trxName); + log.finer("[" + m_no + "] " + TableName + " = " + retValue); + m_nextSeqCount++; + return retValue; + } // getNextID + + /** + * Get Document No from table + * @ejb.interface-method view-type="both" + * + * @param AD_Client_ID client + * @param TableName table name + * @param trxName optional Transaction Name + * @return document no or null + */ + public String getDocumentNo (int AD_Client_ID, String TableName, String trxName) + { + m_nextSeqCount++; + String dn = MSequence.getDocumentNo (AD_Client_ID, TableName, trxName); + if (dn == null) // try again + dn = MSequence.getDocumentNo (AD_Client_ID, TableName, trxName); + return dn; + } // GetDocumentNo + + /** + * Get Document No based on Document Type + * @ejb.interface-method view-type="both" + * + * @param C_DocType_ID document type + * @param trxName optional Transaction Name + * @return document no or null + */ + public String getDocumentNo (int C_DocType_ID, String trxName) + { + m_nextSeqCount++; + String dn = MSequence.getDocumentNo (C_DocType_ID, trxName); + if (dn == null) // try again + dn = MSequence.getDocumentNo (C_DocType_ID, trxName); + return dn; + } // getDocumentNo + + + /************************************************************************* + * Process Remote + * @ejb.interface-method view-type="both" + * + * @param ctx Context + * @param pi Process Info + * @return resulting Process Info + */ + public ProcessInfo process (Properties ctx, ProcessInfo pi) + { + String className = pi.getClassName(); + log.info(className + " - " + pi); + m_processCount++; + // Get Class + Class clazz = null; + try + { + clazz = Class.forName (className); + } + catch (ClassNotFoundException ex) + { + log.log(Level.WARNING, className, ex); + pi.setSummary ("ClassNotFound", true); + return pi; + } + // Get Process + SvrProcess process = null; + try + { + process = (SvrProcess)clazz.newInstance (); + } + catch (Exception ex) + { + log.log(Level.WARNING, "Instance for " + className, ex); + pi.setSummary ("InstanceError", true); + return pi; + } + // Start Process + Trx trx = Trx.get(Trx.createTrxName("ServerPrc"), true); + try + { + boolean ok = process.startProcess (ctx, pi, trx); + pi = process.getProcessInfo(); + trx.commit(); + trx.close(); + } + catch (Exception ex1) + { + trx.rollback(); + trx.close(); + pi.setSummary ("ProcessError", true); + return pi; + } + return pi; + } // process + + + /************************************************************************* + * Run Workflow (and wait) on Server + * @ejb.interface-method view-type="both" + * + * @param ctx Context + * @param pi Process Info + * @param AD_Workflow_ID id + * @return process info + */ + public ProcessInfo workflow (Properties ctx, ProcessInfo pi, int AD_Workflow_ID) + { + log.info ("[" + m_no + "] " + AD_Workflow_ID); + m_workflowCount++; + MWorkflow wf = MWorkflow.get (ctx, AD_Workflow_ID); + MWFProcess wfProcess = null; + if (pi.isBatch()) + wfProcess = wf.start(pi); // may return null + else + wfProcess = wf.startWait(pi); // may return null + log.fine(pi.toString()); + return pi; + } // workflow + + /** + * Online Payment from Server + * @ejb.interface-method view-type="both" + * Called from MPayment processOnline + * @param ctx Context + * @param C_Payment_ID payment + * @param C_PaymentProcessor_ID processor + * @param trxName transaction + * @return true if approved + */ + public boolean paymentOnline (Properties ctx, + int C_Payment_ID, int C_PaymentProcessor_ID, String trxName) + { + MPayment payment = new MPayment (ctx, C_Payment_ID, trxName); + MPaymentProcessor mpp = new MPaymentProcessor (ctx, C_PaymentProcessor_ID, null); + log.info ("[" + m_no + "] " + payment + " - " + mpp); + m_paymentCount++; + boolean approved = false; + try + { + PaymentProcessor pp = PaymentProcessor.create(mpp, payment); + if (pp == null) + payment.setErrorMessage("No Payment Processor"); + else + { + approved = pp.processCC (); + if (approved) + payment.setErrorMessage(null); + else + payment.setErrorMessage("From " + payment.getCreditCardName() + + ": " + payment.getR_RespMsg()); + } + } + catch (Exception e) + { + log.log(Level.SEVERE, "", e); + payment.setErrorMessage("Payment Processor Error"); + } + payment.save(); + return approved; + } // paymentOnline + + /** + * Create EMail from Server (Request User) + * @ejb.interface-method view-type="both" + * @param ctx Context + * @param AD_Client_ID client + * @param to recipient email address + * @param subject subject + * @param message message + * @return EMail + */ + public EMail createEMail (Properties ctx, int AD_Client_ID, + String to, String subject, String message) + { + MClient client = MClient.get(ctx, AD_Client_ID); + return client.createEMail(to, subject, message); + } // createEMail + + /** + * Create EMail from Server (Request User) + * @ejb.interface-method view-type="both" + * @param ctx Context + * @param AD_Client_ID client + * @param AD_User_ID user to send email from + * @param to recipient email address + * @param subject subject + * @param message message + * @return EMail + */ + public EMail createEMail (Properties ctx, int AD_Client_ID, + int AD_User_ID, + String to, String subject, String message) + { + MClient client = MClient.get(ctx, AD_Client_ID); + MUser from = new MUser (ctx, AD_User_ID, null); + return client.createEMail(from, to, subject, message); + } // createEMail + + + /** + * Create EMail from Server (Request User) + * @ejb.interface-method view-type="both" + * @param AD_Task_ID task + * @return execution trace + */ + public String executeTask (int AD_Task_ID) + { + MTask task = new MTask (Env.getCtx(), AD_Task_ID, null); // Server Context + return task.execute(); + } // executeTask + + + /** + * Cash Reset + * @ejb.interface-method view-type="both" + * + * @param tableName table name + * @param Record_ID record or 0 for all + * @return number of records reset + */ + public int cacheReset (String tableName, int Record_ID) + { + log.config(tableName + " - " + Record_ID); + m_cacheResetCount++; + return CacheMgt.get().reset(tableName, Record_ID); + } // cacheReset + + /** + * LOB update + * @ejb.interface-method view-type="both" + * + * @param sql table name + * @param displayType display type (i.e. BLOB/CLOB) + * @param value the data + * @return true if updated + */ + public boolean updateLOB (String sql, int displayType, Object value) + { + if (sql == null || value == null) + { + log.fine("No sql or data"); + return false; + } + log.fine(sql); + m_updateLOBCount++; + boolean success = true; + Connection con = DB.createConnection(false, Connection.TRANSACTION_READ_COMMITTED); + PreparedStatement pstmt = null; + try + { + pstmt = con.prepareStatement(sql); + if (displayType == DisplayType.TextLong) + pstmt.setString(1, (String)value); + else + pstmt.setBytes(1, (byte[])value); + int no = pstmt.executeUpdate(); + // + pstmt.close(); + pstmt = null; + } + catch (Exception e) + { + log.log(Level.FINE, sql, e); + success = false; + } + // Close Statement + try + { + if (pstmt != null) + pstmt.close(); + pstmt = null; + } + catch (Exception e) + { + pstmt = null; + } + + // Success - commit local trx + if (success) + { + try + { + con.commit(); + con.close(); + con = null; + } + catch (Exception e) + { + log.log(Level.SEVERE, "commit" , e); + success = false; + } + } + // Error - roll back + if (!success) + { + log.severe ("rollback"); + try + { + con.rollback(); + con.close(); + con = null; + } + catch (Exception ee) + { + log.log(Level.SEVERE, "rollback" , ee); + } + } + + // Clean Connection + try + { + if (con != null) + con.close(); + con = null; + } + catch (Exception e) + { + con = null; + } + return success; + } // updateLOB + + + /************************************************************************** + * Describes the instance and its content for debugging purpose + * @ejb.interface-method view-type="both" + * @return Debugging information about the instance and its content + */ + public String getStatus() + { + StringBuffer sb = new StringBuffer("ServerBean["); + sb.append(m_no) + .append("-Window=").append(m_windowCount) + .append(",Post=").append(m_postCount) + .append(",Process=").append(m_processCount) + .append(",NextSeq=").append(m_nextSeqCount) + .append(",Workflow=").append(m_workflowCount) + .append(",Payment=").append(m_paymentCount) + .append(",RowSet=").append(m_stmt_rowSetCount) + .append(",Update=").append(m_stmt_updateCount) + .append(",CacheReset=").append(m_cacheResetCount) + .append(",UpdateLob=").append(m_updateLOBCount) + .append("]"); + return sb.toString(); + } // getStatus + + + /** + * String Representation + * @return info + */ + public String toString() + { + return getStatus(); + } // toString + + + /************************************************************************** + * Create the Session Bean + * @throws EJBException + * @throws CreateException + * @ejb.create-method view-type="both" + */ + public void ejbCreate() throws EJBException, CreateException + { + m_no = ++s_no; + try + { + Adempiere.startup(false); + } + catch (Exception ex) + { + log.log(Level.SEVERE, "ejbCreate", ex); + // throw new CreateException (); + } + log.info ("#" + getStatus()); + } // ejbCreate + + + // ------------------------------------------------------------------------- + // Framework Callbacks + // ------------------------------------------------------------------------- + + /** + * Method setSessionContext + * @param aContext SessionContext + * @throws EJBException + * @see javax.ejb.SessionBean#setSessionContext(SessionContext) + */ + public void setSessionContext (SessionContext aContext) throws EJBException + { + m_Context = aContext; + } // setSessionContext + + /** + * Method ejbActivate + * @throws EJBException + * @see javax.ejb.SessionBean#ejbActivate() + */ + public void ejbActivate() throws EJBException + { + if (log == null) + log = CLogger.getCLogger(getClass()); + log.info ("ejbActivate " + getStatus()); + } // ejbActivate + + /** + * Method ejbPassivate + * @throws EJBException + * @see javax.ejb.SessionBean#ejbPassivate() + */ + public void ejbPassivate() throws EJBException + { + log.info ("ejbPassivate " + getStatus()); + } // ejbPassivate + + /** + * Method ejbRemove + * @throws EJBException + * @see javax.ejb.SessionBean#ejbRemove() + */ + public void ejbRemove() throws EJBException + { + log.info ("ejbRemove " + getStatus()); + } // ejbRemove + + + /************************************************************************** + * Dump SerialVersionUID of class + * @param clazz class + */ + protected static void dumpSVUID (Class clazz) + { + String s = clazz.getName() + + " ==\nstatic final long serialVersionUID = " + + java.io.ObjectStreamClass.lookup(clazz).getSerialVersionUID() + + "L;\n"; + System.out.println (s); + } // dumpSVUID + + /** + * Print UID of used classes. + * R2.5.1h + +org.compiere.process.ProcessInfo == +static final long serialVersionUID = -1993220053515488725L; + +org.compiere.util.CStatementVO == +static final long serialVersionUID = -3393389471515956399L; + +org.compiere.model.MQuery == +static final long serialVersionUID = 1511402030597166113L; + +org.compiere.model.POInfo == +static final long serialVersionUID = -5976719579744948419L; + +org.compiere.model.POInfoColumn == +static final long serialVersionUID = -3983585608504631958L; + +org.compiere.model.MWindowVO == +static final long serialVersionUID = 3802628212531678981L; + +org.compiere.model.MTabVO == +static final long serialVersionUID = 9160212869277319305L; + +org.compiere.model.MFieldVO == +static final long serialVersionUID = 4385061125114436797L; + +org.compiere.model.MLookupInfo == +static final long serialVersionUID = -7958664359250070233L; + + * + * * @param args ignored + */ + public static void main (String[] args) + { + dumpSVUID(ProcessInfo.class); + dumpSVUID(CStatementVO.class); + dumpSVUID(MQuery.class); + dumpSVUID(POInfo.class); + dumpSVUID(POInfoColumn.class); + dumpSVUID(GridWindowVO.class); + dumpSVUID(GridTabVO.class); + dumpSVUID(GridFieldVO.class); + dumpSVUID(MLookupInfo.class); + } // main + +} // ServerBean diff --git a/serverRoot/src/main/ejb/org/compiere/session/StatusBean.java b/serverRoot/src/main/ejb/org/compiere/session/StatusBean.java new file mode 100644 index 0000000000..8e22e10b51 --- /dev/null +++ b/serverRoot/src/main/ejb/org/compiere/session/StatusBean.java @@ -0,0 +1,288 @@ +/****************************************************************************** + * 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.session; + +import java.util.logging.*; +import javax.ejb.*; + +import org.compiere.*; +import org.compiere.db.*; +import org.compiere.util.*; + + +/** + * Adempiere Status Bean + * + * @ejb.bean name="adempiere/Status" + * display-name="Adempiere Status Session Bean" + * type="Stateless" + * view-type="both" + * transaction-type="Bean" + * jndi-name="adempiere/Status" + * local-jndi-name="adempiere/StatusLocal" + * + * @ejb.ejb-ref ejb-name="adempiere/Status" + * view-type="both" + * ref-name="adempiere/Status" + * @ejb.ejb-ref ejb-name="adempiere/Status" + * view-type="local" + * ref-name="adempiere/StatusLocal" + * + * @author Jorg Janke + * @version $Id: StatusBean.java,v 1.3 2006/07/30 00:53:33 jjanke Exp $ + */ +public class StatusBean implements SessionBean +{ + /** Context */ + private SessionContext m_Context; + /** Logging */ + private static CLogger log = CLogger.getCLogger(StatusBean.class); + + private static int s_no = 0; + private int m_no = 0; + // + private int m_versionCount = 0; + private int m_databaseCount = 0; + + + /** + * Get Version (Date) + * @ejb.interface-method view-type="both" + * @return version e.g. 2002-09-02 + */ + public String getDateVersion() + { + m_versionCount++; + log.info ("getDateVersion " + m_versionCount); + return Adempiere.DATE_VERSION; + } // getDateVersion + + /** + * Get Main Version + * @ejb.interface-method view-type="both" + * @return main version - e.g. Version 2.4.3b + */ + public String getMainVersion() + { + return Adempiere.MAIN_VERSION; + } // getMainVersion + + /** + * Get Database Type + * @ejb.interface-method view-type="both" + * @return Database Type + */ + public String getDbType() + { + return CConnection.get().getType(); + } // getDbType + + /** + * Get Database Host + * @ejb.interface-method view-type="both" + * @return Database Host Name + */ + public String getDbHost() + { + m_databaseCount++; + log.info ("getDbHost " + m_databaseCount); + return CConnection.get().getDbHost(); + } // getDbHost + + /** + * Get Database Port + * @ejb.interface-method view-type="both" + * @return Database Port + */ + public int getDbPort() + { + return CConnection.get().getDbPort(); + } // getDbPort + + /** + * Get Database SID + * @ejb.interface-method view-type="both" + * @return Database SID + */ + public String getDbName() + { + return CConnection.get().getDbName(); + } // getDbSID + + /** + * Get Database URL + * @ejb.interface-method view-type="both" + * @return Database URL + */ + public String getConnectionURL() + { + return CConnection.get().getConnectionURL(); + } // getConnectionURL + + /** + * Get Database UID + * @ejb.interface-method view-type="both" + * @return Database User Name + */ + public String getDbUid() + { + return CConnection.get().getDbUid(); + } // getDbUID + + /** + * Get Database PWD + * @ejb.interface-method view-type="both" + * @return Database User Password + */ + public String getDbPwd() + { + return CConnection.get().getDbPwd(); + } // getDbPWD + + /** + * Get Connection Manager Host + * @ejb.interface-method view-type="both" + * @return Connection Manager Host + */ + public String getFwHost() + { + return CConnection.get().getFwHost(); + } // getCMHost + + /** + * Get Connection Manager Port + * @ejb.interface-method view-type="both" + * @return Connection Manager Port + */ + public int getFwPort() + { + return CConnection.get().getFwPort(); + } // getCMPort + + + /************************************************************************** + * Get Version Count + * @ejb.interface-method view-type="both" + * @return number of version inquiries + */ + public int getVersionCount() + { + return m_versionCount; + } // getVersionCount + + /** + * Get Database Count + * @ejb.interface-method view-type="both" + * @return number of database inquiries + */ + public int getDatabaseCount() + { + return m_databaseCount; + } // getVersionCount + + /** + * Describes the instance and its content for debugging purpose + * @ejb.interface-method view-type="both" + * @return Debugging information about the instance and its content + */ + public String getStatus() + { + StringBuffer sb = new StringBuffer("StatusBean[No="); + sb.append(m_no) + .append(",VersionCount=").append(m_versionCount) + .append(",DatabaseCount=").append(m_versionCount) + .append("]"); + return sb.toString(); + } // getStatus + + + /** + * String Representation + * @return info + */ + public String toString() + { + return getStatus(); + } // toString + + + /************************************************************************** + * Create the Session Bean + * @throws EJBException + * @throws CreateException + * @ejb.create-method view-type="both" + */ + public void ejbCreate() throws EJBException, CreateException + { + m_no = ++s_no; + try + { + org.compiere.Adempiere.startup(false); + } + catch (Exception ex) + { + log.log(Level.SEVERE, "", ex); + // throw new CreateException (); + } + log.info("#" + m_no + " - " + getStatus()); + } // ejbCreate + + + // ------------------------------------------------------------------------- + // Framework Callbacks + // ------------------------------------------------------------------------- + + /** + * Set Session Context + * @param aContext context + * @throws EJBException + */ + public void setSessionContext (SessionContext aContext) throws EJBException + { + m_Context = aContext; + } + + /** + * Ejb Activate + * @throws EJBException + */ + public void ejbActivate() throws EJBException + { + if (log == null) + log = CLogger.getCLogger(getClass()); + log.fine("ejbActivate"); + } + + /** + * Ejb Passivate + * @throws EJBException + */ + public void ejbPassivate() throws EJBException + { + log.fine("ejbPassivate"); + } + + /** + * Ejb Remove + * @throws EJBException + */ + public void ejbRemove() throws EJBException + { + log.fine("ejbRemove"); + } + +} // StatusBean diff --git a/serverRoot/src/main/server/org/compiere/acct/Doc.java b/serverRoot/src/main/server/org/compiere/acct/Doc.java new file mode 100644 index 0000000000..569b5317f1 --- /dev/null +++ b/serverRoot/src/main/server/org/compiere/acct/Doc.java @@ -0,0 +1,2162 @@ +/****************************************************************************** + * 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.acct; + +import java.lang.reflect.*; +import java.math.*; +import java.sql.*; +import java.util.*; +import java.util.logging.*; + +import org.compiere.model.*; +import org.compiere.process.*; +import org.compiere.report.*; +import org.compiere.util.*; + +/** + * Posting Document Root. + * + *
+ *  Table               Base Document Types (C_DocType.DocBaseType & AD_Reference_ID=183)
+ *      Class           AD_Table_ID
+ *  ------------------  ------------------------------
+ *  C_Invoice:          ARI, ARC, ARF, API, APC
+ *      Doc_Invoice     318 - has C_DocType_ID
+ *
+ *  C_Payment:          ARP, APP
+ *      Doc_Payment     335 - has C_DocType_ID
+ *
+ *  C_Order:            SOO, POO,  POR (Requisition)
+ *      Doc_Order       259 - has C_DocType_ID
+ *
+ *  M_InOut:            MMS, MMR
+ *      Doc_InOut       319 - DocType derived
+ *
+ *  M_Inventory:        MMI
+ *      Doc_Inventory   321 - DocType fixed
+ *
+ *  M_Movement:         MMM
+ *      Doc_Movement    323 - DocType fixed
+ *
+ *  M_Production:       MMP
+ *      Doc_Production  325 - DocType fixed
+ *
+ *  C_BankStatement:    CMB
+ *      Doc_Bank        392 - DocType fixed
+ *
+ *  C_Cash:             CMC
+ *      Doc_Cash        407 - DocType fixed
+ *
+ *  C_Allocation:       CMA
+ *      Doc_Allocation  390 - DocType fixed
+ *
+ *  GL_Journal:         GLJ
+ *      Doc_GLJournal   224 = has C_DocType_ID
+ *
+ *  Matching Invoice    MXI
+ *      M_MatchInv      472 - DocType fixed
+ *
+ *  Matching PO         MXP
+ *      M_MatchPO       473 - DocType fixed
+ *
+ *	Project Issue		PJI
+ *		C_ProjectIssue	623 - DocType fixed
+ *	
+ *  
+ * @author Jorg Janke + * @version $Id: Doc.java,v 1.6 2006/07/30 00:53:33 jjanke Exp $ + */ +public abstract class Doc +{ + /** AD_Table_ID's of documents */ + public static int[] documentsTableID = new int[] { + MInvoice.Table_ID, // C_Invoice + MAllocationHdr.Table_ID, // C_Allocation + MCash.Table_ID, // C_Cash + MBankStatement.Table_ID, // C_BankStatement + MOrder.Table_ID, // C_Order + MPayment.Table_ID, // C_Payment + MInOut.Table_ID, // M_InOut + MInventory.Table_ID, // M_Inventory + MMovement.Table_ID, // M_Movement + X_M_Production.Table_ID, // M_Production + MJournal.Table_ID, // GL_Journal + MMatchInv.Table_ID, // M_MatchInv + MMatchPO.Table_ID, // M_MatchPO + MProjectIssue.Table_ID, // C_ProjectIssue + MRequisition.Table_ID // M_Requisition + }; + + /** Table Names of documents */ + public static String[] documentsTableName = new String[] { + MInvoice.Table_Name, // C_Invoice + MAllocationHdr.Table_Name, // C_Allocation + MCash.Table_Name, // C_Cash + MBankStatement.Table_Name, // C_BankStatement + MOrder.Table_Name, // C_Order + MPayment.Table_Name, // C_Payment + MInOut.Table_Name, // M_InOut + MInventory.Table_Name, // M_Inventory + MMovement.Table_Name, // M_Movement + X_M_Production.Table_Name, // M_Production + MJournal.Table_Name, // GL_Journal + MMatchInv.Table_Name, // M_MatchInv + MMatchPO.Table_Name, // M_MatchPO + MProjectIssue.Table_Name, // C_ProjectIssue + MRequisition.Table_Name // M_Requisition + }; + + /************************************************************************** + * Document Types + * -------------- + * C_DocType.DocBaseType & AD_Reference_ID=183 + * C_Invoice: ARI, ARC, ARF, API, APC + * C_Payment: ARP, APP + * C_Order: SOO, POO + * M_Transaction: MMI, MMM, MMS, MMR + * C_BankStatement: CMB + * C_Cash: CMC + * C_Allocation: CMA + * GL_Journal: GLJ + * C_ProjectIssue PJI + * M_Requisition POR + **************************************************************************/ + + /** AR Invoices - ARI */ + public static final String DOCTYPE_ARInvoice = MDocType.DOCBASETYPE_ARInvoice; + /** AR Credit Memo */ + public static final String DOCTYPE_ARCredit = "ARC"; + /** AR Receipt */ + public static final String DOCTYPE_ARReceipt = "ARR"; + /** AR ProForma */ + public static final String DOCTYPE_ARProForma = "ARF"; + /** AP Invoices */ + public static final String DOCTYPE_APInvoice = "API"; + /** AP Credit Memo */ + public static final String DOCTYPE_APCredit = "APC"; + /** AP Payment */ + public static final String DOCTYPE_APPayment = "APP"; + /** CashManagement Bank Statement */ + public static final String DOCTYPE_BankStatement = "CMB"; + /** CashManagement Cash Journals */ + public static final String DOCTYPE_CashJournal = "CMC"; + /** CashManagement Allocations */ + public static final String DOCTYPE_Allocation = "CMA"; + /** Material Shipment */ + public static final String DOCTYPE_MatShipment = "MMS"; + /** Material Receipt */ + public static final String DOCTYPE_MatReceipt = "MMR"; + /** Material Inventory */ + public static final String DOCTYPE_MatInventory = "MMI"; + /** Material Movement */ + public static final String DOCTYPE_MatMovement = "MMM"; + /** Material Production */ + public static final String DOCTYPE_MatProduction = "MMP"; + /** Match Invoice */ + public static final String DOCTYPE_MatMatchInv = "MXI"; + /** Match PO */ + public static final String DOCTYPE_MatMatchPO = "MXP"; + /** GL Journal */ + public static final String DOCTYPE_GLJournal = "GLJ"; + /** Purchase Order */ + public static final String DOCTYPE_POrder = "POO"; + /** Sales Order */ + public static final String DOCTYPE_SOrder = "SOO"; + /** Project Issue */ + public static final String DOCTYPE_ProjectIssue = "PJI"; + /** Purchase Requisition */ + public static final String DOCTYPE_PurchaseRequisition = "POR"; + + + + // Posting Status - AD_Reference_ID=234 // + /** Document Status */ + public static final String STATUS_NotPosted = "N"; + /** Document Status */ + public static final String STATUS_NotBalanced = "b"; + /** Document Status */ + public static final String STATUS_NotConvertible = "c"; + /** Document Status */ + public static final String STATUS_PeriodClosed = "p"; + /** Document Status */ + public static final String STATUS_InvalidAccount = "i"; + /** Document Status */ + public static final String STATUS_PostPrepared = "y"; + /** Document Status */ + public static final String STATUS_Posted = "Y"; + /** Document Status */ + public static final String STATUS_Error = "E"; + + + /** + * Create Posting document + * @param ass accounting schema + * @param AD_Table_ID Table ID of Documents + * @param Record_ID record ID to load + * @param trxName transaction name + * @return Document or null + */ + public static Doc get (MAcctSchema[] ass, int AD_Table_ID, int Record_ID, String trxName) + { + String TableName = null; + for (int i = 0; i < documentsTableID.length; i++) + { + if (documentsTableID[i] == AD_Table_ID) + { + TableName = documentsTableName[i]; + break; + } + } + if (TableName == null) + { + s_log.severe("Not found AD_Table_ID=" + AD_Table_ID); + return null; + } + // + Doc doc = null; + StringBuffer sql = new StringBuffer("SELECT * FROM ") + .append(TableName) + .append(" WHERE ").append(TableName).append("_ID=? AND Processed='Y'"); + PreparedStatement pstmt = null; + try + { + pstmt = DB.prepareStatement (sql.toString(), trxName); + pstmt.setInt (1, Record_ID); + ResultSet rs = pstmt.executeQuery (); + if (rs.next ()) + { + doc = get (ass, AD_Table_ID, rs, trxName); + } + else + s_log.severe("Not Found: " + TableName + "_ID=" + Record_ID); + rs.close (); + pstmt.close (); + pstmt = null; + } + catch (Exception e) + { + s_log.log (Level.SEVERE, sql.toString(), e); + } + try + { + if (pstmt != null) + pstmt.close (); + pstmt = null; + } + catch (Exception e) + { + pstmt = null; + } + return doc; + } // get + + /** + * Create Posting document + * @param ass accounting schema + * @param AD_Table_ID Table ID of Documents + * @param rs ResultSet + * @param trxName transaction name + * @return Document + */ + public static Doc get (MAcctSchema[] ass, int AD_Table_ID, ResultSet rs, String trxName) + { + Doc doc = null; + switch (AD_Table_ID) + { + case MInvoice.Table_ID: + doc = new Doc_Invoice (ass, rs, trxName); + break; + case MAllocationHdr.Table_ID: + doc = new Doc_Allocation (ass, rs, trxName); + break; + case MCash.Table_ID: + doc = new Doc_Cash (ass, rs, trxName); + break; + case MBankStatement.Table_ID: + doc = new Doc_Bank (ass, rs, trxName); + break; + case MOrder.Table_ID: + doc = new Doc_Order (ass, rs, trxName); + break; + case MPayment.Table_ID: + doc = new Doc_Payment (ass, rs, trxName); + break; + case MInOut.Table_ID: + doc = new Doc_InOut (ass, rs, trxName); + break; + case MInventory.Table_ID: + doc = new Doc_Inventory (ass, rs, trxName); + break; + case MMovement.Table_ID: + doc = new Doc_Movement (ass, rs, trxName); + break; + case X_M_Production.Table_ID: + doc = new Doc_Production (ass, rs, trxName); + break; + case MJournal.Table_ID: + doc = new Doc_GLJournal (ass, rs, trxName); + break; + case MMatchInv.Table_ID: + doc = new Doc_MatchInv (ass, rs, trxName); + break; + case MMatchPO.Table_ID: + doc = new Doc_MatchPO (ass, rs, trxName); + break; + case MProjectIssue.Table_ID: + doc = new Doc_ProjectIssue (ass, rs, trxName); + break; + case MRequisition.Table_ID: + doc = new Doc_Requisition (ass, rs, trxName); + break; + } + if (doc == null) + s_log.log(Level.SEVERE, "Unknown AD_Table_ID=" + AD_Table_ID); + return doc; + } // get + + /** + * Post Document + * @param ass accounting schemata + * @param AD_Table_ID Transaction table + * @param Record_ID Record ID of this document + * @param force force posting + * @param trxName transaction + * @return null if the document was posted or error message + */ + public static String postImmediate (MAcctSchema[] ass, + int AD_Table_ID, int Record_ID, boolean force, String trxName) + { + Doc doc = get (ass, AD_Table_ID, Record_ID, trxName); + if (doc != null) + return doc.post (force, true); // repost + return "NoDoc"; + } // post + + /** Static Log */ + protected static CLogger s_log = CLogger.getCLogger(Doc.class); + /** Log per Document */ + protected CLogger log = CLogger.getCLogger(getClass()); + + + /************************************************************************** + * Constructor + * @param ass accounting schemata + * @param clazz Document Class + * @param rs result set + * @param defaultDocumentType default document type or null + * @param trxName trx + */ + Doc (MAcctSchema[] ass, Class clazz, ResultSet rs, String defaultDocumentType, String trxName) + { + p_Status = STATUS_Error; + m_ass = ass; + m_ctx = new Properties(m_ass[0].getCtx()); + m_ctx.setProperty("#AD_Client_ID", String.valueOf(m_ass[0].getAD_Client_ID())); + + String className = clazz.getName(); + className = className.substring(className.lastIndexOf('.')+1); + try + { + Constructor constructor = clazz.getConstructor(new Class[]{Properties.class, ResultSet.class, String.class}); + p_po = (PO)constructor.newInstance(new Object[]{m_ctx, rs, trxName}); + } + catch (Exception e) + { + String msg = className + ": " + e.getLocalizedMessage(); + log.severe(msg); + throw new IllegalArgumentException(msg); + } + + // DocStatus + int index = p_po.get_ColumnIndex("DocStatus"); + if (index != -1) + m_DocStatus = (String)p_po.get_Value(index); + + // Document Type + setDocumentType (defaultDocumentType); + m_trxName = trxName; + if (m_trxName == null) + m_trxName = "Post" + m_DocumentType + p_po.get_ID(); + p_po.set_TrxName(m_trxName); + + // Amounts + m_Amounts[0] = Env.ZERO; + m_Amounts[1] = Env.ZERO; + m_Amounts[2] = Env.ZERO; + m_Amounts[3] = Env.ZERO; + } // Doc + + /** Accounting Schema Array */ + private MAcctSchema[] m_ass = null; + /** Properties */ + private Properties m_ctx = null; + /** Transaction Name */ + private String m_trxName = null; + /** The Document */ + protected PO p_po = null; + /** Document Type */ + private String m_DocumentType = null; + /** Document Status */ + private String m_DocStatus = null; + /** Document No */ + private String m_DocumentNo = null; + /** Description */ + private String m_Description = null; + /** GL Category */ + private int m_GL_Category_ID = 0; + /** GL Period */ + private MPeriod m_period = null; + /** Period ID */ + private int m_C_Period_ID = 0; + /** Location From */ + private int m_C_LocFrom_ID = 0; + /** Location To */ + private int m_C_LocTo_ID = 0; + /** Accounting Date */ + private Timestamp m_DateAcct = null; + /** Document Date */ + private Timestamp m_DateDoc = null; + /** Tax Included */ + private boolean m_TaxIncluded = false; + /** Is (Source) Multi-Currency Document - i.e. the document has different currencies + * (if true, the document will not be source balanced) */ + private boolean m_MultiCurrency = false; + /** BP Sales Region */ + private int m_BP_C_SalesRegion_ID = -1; + /** B Partner */ + private int m_C_BPartner_ID = -1; + + /** Bank Account */ + private int m_C_BankAccount_ID = -1; + /** Cach Book */ + private int m_C_CashBook_ID = -1; + /** Currency */ + private int m_C_Currency_ID = -1; + + /** Contained Doc Lines */ + protected DocLine[] p_lines; + + /** Facts */ + private ArrayList m_fact = null; + + /** No Currency in Document Indicator (-1) */ + protected static final int NO_CURRENCY = -2; + + /** Actual Document Status */ + protected String p_Status = null; + /** Error Message */ + protected String p_Error = null; + + + /** + * Get Context + * @return context + */ + protected Properties getCtx() + { + return m_ctx; + } // getCtx + + /** + * Get Table Name + * @return table name + */ + public String get_TableName() + { + return p_po.get_TableName(); + } // get_TableName + + /** + * Get Table ID + * @return table id + */ + public int get_Table_ID() + { + return p_po.get_Table_ID(); + } // get_Table_ID + + /** + * Get Record_ID + * @return record id + */ + public int get_ID() + { + return p_po.get_ID(); + } // get_ID + + /** + * Get Persistent Object + * @return po + */ + protected PO getPO() + { + return p_po; + } // getPO + + /** + * Post Document. + *
+	 *  - try to lock document (Processed='Y' (AND Processing='N' AND Posted='N'))
+	 * 		- if not ok - return false
+	 *          - postlogic (for all Accounting Schema)
+	 *              - create Fact lines
+	 *          - postCommit
+	 *              - commits Fact lines and Document & sets Processing = 'N'
+	 *              - if error - create Note
+	 *  
+ * @param force if true ignore that locked + * @param repost if true ignore that already posted + * @return null if posted error otherwise + */ + public final String post (boolean force, boolean repost) + { + if (m_DocStatus == null) + ; // return "No DocStatus for DocumentNo=" + getDocumentNo(); + else if (m_DocStatus.equals(DocumentEngine.STATUS_Completed) + || m_DocStatus.equals(DocumentEngine.STATUS_Closed) + || m_DocStatus.equals(DocumentEngine.STATUS_Voided) + || m_DocStatus.equals(DocumentEngine.STATUS_Reversed)) + ; + else + return "Invalid DocStatus='" + m_DocStatus + "' for DocumentNo=" + getDocumentNo(); + // + if (p_po.getAD_Client_ID() != m_ass[0].getAD_Client_ID()) + { + String error = "AD_Client_ID Conflict - Document=" + p_po.getAD_Client_ID() + + ", AcctSchema=" + m_ass[0].getAD_Client_ID(); + log.severe(error); + return error; + } + + // Lock Record ---- + StringBuffer sql = new StringBuffer ("UPDATE "); + sql.append(get_TableName()).append( " SET Processing='Y' WHERE ") + .append(get_TableName()).append("_ID=").append(get_ID()) + .append(" AND Processed='Y' AND IsActive='Y'"); + if (!force) + sql.append(" AND (Processing='N' OR Processing IS NULL)"); + if (!repost) + sql.append(" AND Posted='N'"); + if (DB.executeUpdate(sql.toString(), null) == 1) // outside trx + log.info("Locked: " + get_TableName() + "_ID=" + get_ID()); + else + { + log.log(Level.SEVERE, "Resubmit - Cannot lock " + get_TableName() + "_ID=" + + get_ID() + ", Force=" + force + ",RePost=" + repost); + if (force) + return "Cannot Lock - ReSubmit"; + return "Cannot Lock - ReSubmit or RePost with Force"; + } + + p_Error = loadDocumentDetails(); + if (p_Error != null) + return p_Error; + + // Delete existing Accounting + if (repost) + { + if (isPosted() && !isPeriodOpen()) // already posted - don't delete if period closed + { + log.log(Level.SEVERE, toString() + " - Period Closed for already posed document"); + unlock(); + return "PeriodClosed"; + } + // delete it + deleteAcct(); + } + else if (isPosted()) + { + log.log(Level.SEVERE, toString() + " - Document already posted"); + unlock(); + return "AlreadyPosted"; + } + + p_Status = STATUS_NotPosted; + + // Create Fact per AcctSchema + m_fact = new ArrayList(); + + // for all Accounting Schema + boolean OK = true; + try + { + for (int i = 0; OK && i < m_ass.length; i++) + { + // if acct schema has "only" org, skip + boolean skip = false; + if (m_ass[i].getAD_OrgOnly_ID() != 0) + { + if (m_ass[i].getOnlyOrgs() == null) + m_ass[i].setOnlyOrgs(MReportTree.getChildIDs(getCtx(), + 0, MAcctSchemaElement.ELEMENTTYPE_Organization, + m_ass[i].getAD_OrgOnly_ID())); + + // Header Level Org + skip = m_ass[i].isSkipOrg(getAD_Org_ID()); + // Line Level Org + if (p_lines != null) + { + for (int line = 0; skip && line < p_lines.length; line++) + { + skip = m_ass[i].isSkipOrg(p_lines[line].getAD_Org_ID()); + if (!skip) + break; + } + } + } + if (skip) + continue; + // post + log.info("(" + i + ") " + p_po); + p_Status = postLogic (i); + if (!p_Status.equals(STATUS_Posted)) + OK = false; + } + } + catch (Exception e) + { + log.log(Level.SEVERE, "", e); + p_Status = STATUS_Error; + p_Error = e.toString(); + OK = false; + } + + // commitFact + p_Status = postCommit (p_Status); + + // Create Note + if (!p_Status.equals(STATUS_Posted)) + { + // Insert Note + String AD_MessageValue = "PostingError-" + p_Status; + int AD_User_ID = p_po.getUpdatedBy(); + MNote note = new MNote (getCtx(), AD_MessageValue, AD_User_ID, + getAD_Client_ID(), getAD_Org_ID(), null); + note.setRecord(p_po.get_Table_ID(), p_po.get_ID()); + // Reference + note.setReference(toString()); // Document + // Text + StringBuffer Text = new StringBuffer (Msg.getMsg(Env.getCtx(), AD_MessageValue)); + if (p_Error != null) + Text.append(" (").append(p_Error).append(")"); + String cn = getClass().getName(); + Text.append(" - ").append(cn.substring(cn.lastIndexOf('.'))) + .append(" (").append(getDocumentType()) + .append(" - DocumentNo=").append(getDocumentNo()) + .append(", DateAcct=").append(getDateAcct().toString().substring(0,10)) + .append(", Amount=").append(getAmount()) + .append(", Sta=").append(p_Status) + .append(" - PeriodOpen=").append(isPeriodOpen()) + .append(", Balanced=").append(isBalanced()); + note.setTextMsg(Text.toString()); + note.save(); + } + + // dispose facts + for (int i = 0; i < m_fact.size(); i++) + { + Fact fact = m_fact.get(i); + if (fact != null) + fact.dispose(); + } + p_lines = null; + + if (p_Status.equals(STATUS_Posted)) + return null; + return p_Error; + } // post + + /** + * Delete Accounting + * @return number of records + */ + private int deleteAcct() + { + StringBuffer sql = new StringBuffer ("DELETE Fact_Acct WHERE AD_Table_ID=") + .append(get_Table_ID()) + .append(" AND Record_ID=").append(p_po.get_ID()); + int no = DB.executeUpdate(sql.toString(), getTrxName()); + if (no != 0) + log.info("deleted=" + no); + return no; + } // deleteAcct + + /** + * Posting logic for Accounting Schema index + * @param index Accounting Schema index + * @return posting status/error code + */ + private final String postLogic (int index) + { + log.info("(" + index + ") " + p_po); + + // rejectUnbalanced + if (!m_ass[index].isSuspenseBalancing() && !isBalanced()) + return STATUS_NotBalanced; + + // rejectUnconvertible + if (!isConvertible(m_ass[index])) + return STATUS_NotConvertible; + + // rejectPeriodClosed + if (!isPeriodOpen()) + return STATUS_PeriodClosed; + + // createFacts + ArrayList facts = createFacts (m_ass[index]); + if (facts == null) + return STATUS_Error; + for (int f = 0; f < facts.size(); f++) + { + Fact fact = facts.get(f); + if (fact == null) + return STATUS_Error; + m_fact.add(fact); + // + p_Status = STATUS_PostPrepared; + + // check accounts + if (!fact.checkAccounts()) + return STATUS_InvalidAccount; + + // distribute + if (!fact.distribute()) + return STATUS_Error; + + // balanceSource + if (!fact.isSourceBalanced()) + { + fact.balanceSource(); + if (!fact.isSourceBalanced()) + return STATUS_NotBalanced; + } + + // balanceSegments + if (!fact.isSegmentBalanced()) + { + fact.balanceSegments(); + if (!fact.isSegmentBalanced()) + return STATUS_NotBalanced; + } + + // balanceAccounting + if (!fact.isAcctBalanced()) + { + fact.balanceAccounting(); + if (!fact.isAcctBalanced()) + return STATUS_NotBalanced; + } + + } // for all facts + + return STATUS_Posted; + } // postLogic + + /** + * Post Commit. + * Save Facts & Document + * @param status status + * @return Posting Status + */ + private final String postCommit (String status) + { + log.info("Sta=" + status + " DT=" + getDocumentType() + + " ID=" + p_po.get_ID()); + p_Status = status; + + Trx trx = Trx.get(getTrxName(), true); + try + { + // *** Transaction Start *** + // Commit Facts + if (status.equals(STATUS_Posted)) + { + for (int i = 0; i < m_fact.size(); i++) + { + Fact fact = m_fact.get(i); + if (fact == null) + ; + else if (fact.save(getTrxName())) + ; + else + { + log.log(Level.SEVERE, "(fact not saved) ... rolling back"); + trx.rollback(); + trx.close(); + unlock(); + return STATUS_Error; + } + } + } + // Commit Doc + if (!save(getTrxName())) // contains unlock & document status update + { + log.log(Level.SEVERE, "(doc not saved) ... rolling back"); + trx.rollback(); + trx.close(); + unlock(); + return STATUS_Error; + } + // Success + trx.commit(); + trx.close(); + trx = null; + // *** Transaction End *** + } + catch (Exception e) + { + log.log(Level.SEVERE, "... rolling back", e); + status = STATUS_Error; + try { + if (trx != null) + trx.rollback(); + } catch (Exception e2) {} + try { + if (trx != null) + trx.close(); + trx = null; + } catch (Exception e3) {} + unlock(); + } + p_Status = status; + return status; + } // postCommit + + /** + * Get Trx Name and create Transaction + * @return Trx Name + */ + protected String getTrxName() + { + return m_trxName; + } // getTrxName + + /** + * Unlock Document + */ + private void unlock() + { + StringBuffer sql = new StringBuffer ("UPDATE "); + sql.append(get_TableName()).append( " SET Processing='N' WHERE ") + .append(get_TableName()).append("_ID=").append(p_po.get_ID()); + DB.executeUpdate(sql.toString(), null); // outside trx + } // unlock + + + /************************************************************************** + * Load Document Type and GL Info. + * Set p_DocumentType and p_GL_Category_ID + * @return document type + */ + protected String getDocumentType() + { + if (m_DocumentType == null) + setDocumentType(null); + return m_DocumentType; + } // getDocumentType + + /** + * Load Document Type and GL Info. + * Set p_DocumentType and p_GL_Category_ID + * @param DocumentType + */ + protected void setDocumentType (String DocumentType) + { + if (DocumentType != null) + m_DocumentType = DocumentType; + // No Document Type defined + if (m_DocumentType == null && getC_DocType_ID() != 0) + { + String sql = "SELECT DocBaseType, GL_Category_ID FROM C_DocType WHERE C_DocType_ID=?"; + try + { + PreparedStatement pstmt = DB.prepareStatement(sql, null); + pstmt.setInt(1, getC_DocType_ID()); + ResultSet rsDT = pstmt.executeQuery(); + if (rsDT.next()) + { + m_DocumentType = rsDT.getString(1); + m_GL_Category_ID = rsDT.getInt(2); + } + rsDT.close(); + pstmt.close(); + } + catch (SQLException e) + { + log.log(Level.SEVERE, sql, e); + } + } + if (m_DocumentType == null) + { + log.log(Level.SEVERE, "No DocBaseType for C_DocType_ID=" + + getC_DocType_ID() + ", DocumentNo=" + getDocumentNo()); + } + + // We have a document Type, but no GL info - search for DocType + if (m_GL_Category_ID == 0) + { + String sql = "SELECT GL_Category_ID FROM C_DocType " + + "WHERE AD_Client_ID=? AND DocBaseType=?"; + try + { + PreparedStatement pstmt = DB.prepareStatement(sql, null); + pstmt.setInt(1, getAD_Client_ID()); + pstmt.setString(2, m_DocumentType); + ResultSet rsDT = pstmt.executeQuery(); + if (rsDT.next()) + m_GL_Category_ID = rsDT.getInt(1); + rsDT.close(); + pstmt.close(); + } + catch (SQLException e) + { + log.log(Level.SEVERE, sql, e); + } + } + + // Still no GL_Category - get Default GL Category + if (m_GL_Category_ID == 0) + { + String sql = "SELECT GL_Category_ID FROM GL_Category " + + "WHERE AD_Client_ID=? " + + "ORDER BY IsDefault DESC"; + try + { + PreparedStatement pstmt = DB.prepareStatement(sql, null); + pstmt.setInt(1, getAD_Client_ID()); + ResultSet rsDT = pstmt.executeQuery(); + if (rsDT.next()) + m_GL_Category_ID = rsDT.getInt(1); + rsDT.close(); + pstmt.close(); + } + catch (SQLException e) + { + log.log(Level.SEVERE, sql, e); + } + } + // + if (m_GL_Category_ID == 0) + log.log(Level.SEVERE, "No default GL_Category - " + toString()); + + if (m_DocumentType == null) + throw new IllegalStateException("Document Type not found"); + } // setDocumentType + + + /************************************************************************** + * Is the Source Document Balanced + * @return true if (source) baanced + */ + public boolean isBalanced() + { + // Multi-Currency documents are source balanced by definition + if (isMultiCurrency()) + return true; + // + boolean retValue = getBalance().signum() == 0; + if (retValue) + log.fine("Yes " + toString()); + else + log.warning("NO - " + toString()); + return retValue; + } // isBalanced + + /** + * Is Document convertible to currency and Conversion Type + * @param acctSchema accounting schema + * @return true, if vonvertable to accounting currency + */ + public boolean isConvertible (MAcctSchema acctSchema) + { + // No Currency in document + if (getC_Currency_ID() == NO_CURRENCY) + { + log.fine("(none) - " + toString()); + return true; + } + // Get All Currencies + HashSet set = new HashSet(); + set.add(new Integer(getC_Currency_ID())); + for (int i = 0; p_lines != null && i < p_lines.length; i++) + { + int C_Currency_ID = p_lines[i].getC_Currency_ID(); + if (C_Currency_ID != NO_CURRENCY) + set.add(new Integer(C_Currency_ID)); + } + + // just one and the same + if (set.size() == 1 && acctSchema.getC_Currency_ID() == getC_Currency_ID()) + { + log.fine("(same) Cur=" + getC_Currency_ID() + " - " + toString()); + return true; + } + + boolean convertible = true; + Iterator it = set.iterator(); + while (it.hasNext() && convertible) + { + int C_Currency_ID = ((Integer)it.next()).intValue(); + if (C_Currency_ID != acctSchema.getC_Currency_ID()) + { + BigDecimal amt = MConversionRate.getRate (C_Currency_ID, acctSchema.getC_Currency_ID(), + getDateAcct(), getC_ConversionType_ID(), getAD_Client_ID(), getAD_Org_ID()); + if (amt == null) + { + convertible = false; + log.warning ("NOT from C_Currency_ID=" + C_Currency_ID + + " to " + acctSchema.getC_Currency_ID() + + " - " + toString()); + } + else + log.fine("From C_Currency_ID=" + C_Currency_ID); + } + } + + log.fine("Convertible=" + convertible + ", AcctSchema C_Currency_ID=" + acctSchema.getC_Currency_ID() + " - " + toString()); + return convertible; + } // isConvertible + + /** + * Calculate Period from DateAcct. + * m_C_Period_ID is set to -1 of not open to 0 if not found + */ + public void setPeriod() + { + if (m_period != null) + return; + + // Period defined in GL Journal (e.g. adjustment period) + int index = p_po.get_ColumnIndex("C_Period_ID"); + if (index != -1) + { + Integer ii = (Integer)p_po.get_Value(index); + if (ii != null) + m_period = MPeriod.get(getCtx(), ii.intValue()); + } + if (m_period == null) + m_period = MPeriod.get(getCtx(), getDateAcct()); + // Is Period Open? + if (m_period != null + && m_period.isOpen(getDocumentType())) + m_C_Period_ID = m_period.getC_Period_ID(); + else + m_C_Period_ID = -1; + // + log.fine( // + AD_Client_ID + " - " + getDateAcct() + " - " + getDocumentType() + " => " + m_C_Period_ID); + } // setC_Period_ID + + /** + * Get C_Period_ID + * @return period + */ + public int getC_Period_ID() + { + if (m_period == null) + setPeriod(); + return m_C_Period_ID; + } // getC_Period_ID + + /** + * Is Period Open + * @return true if period is open + */ + public boolean isPeriodOpen() + { + setPeriod(); + boolean open = m_C_Period_ID > 0; + if (open) + log.fine("Yes - " + toString()); + else + log.warning("NO - " + toString()); + return open; + } // isPeriodOpen + + /*************************************************************************/ + + /** Amount Type - Invoice - Gross */ + public static final int AMTTYPE_Gross = 0; + /** Amount Type - Invoice - Net */ + public static final int AMTTYPE_Net = 1; + /** Amount Type - Invoice - Charge */ + public static final int AMTTYPE_Charge = 2; + + /** Source Amounts (may not all be used) */ + private BigDecimal[] m_Amounts = new BigDecimal[4]; + /** Quantity */ + private BigDecimal m_qty = null; + + /** + * Get the Amount + * (loaded in loadDocumentDetails) + * + * @param AmtType see AMTTYPE_* + * @return Amount + */ + public BigDecimal getAmount(int AmtType) + { + if (AmtType < 0 || AmtType >= m_Amounts.length) + return null; + return m_Amounts[AmtType]; + } // getAmount + + /** + * Set the Amount + * @param AmtType see AMTTYPE_* + * @param amt Amount + */ + protected void setAmount(int AmtType, BigDecimal amt) + { + if (AmtType < 0 || AmtType >= m_Amounts.length) + return; + if (amt == null) + m_Amounts[AmtType] = Env.ZERO; + else + m_Amounts[AmtType] = amt; + } // setAmount + + /** + * Get Amount with index 0 + * @return Amount (primary document amount) + */ + public BigDecimal getAmount() + { + return m_Amounts[0]; + } // getAmount + + /** + * Set Quantity + * @param qty Quantity + */ + protected void setQty (BigDecimal qty) + { + m_qty = qty; + } // setQty + + /** + * Get Quantity + * @return Quantity + */ + public BigDecimal getQty() + { + if (m_qty == null) + { + int index = p_po.get_ColumnIndex("Qty"); + if (index != -1) + m_qty = (BigDecimal)p_po.get_Value(index); + else + m_qty = Env.ZERO; + } + return m_qty; + } // getQty + + /*************************************************************************/ + + /** Account Type - Invoice - Charge */ + public static final int ACCTTYPE_Charge = 0; + /** Account Type - Invoice - AR */ + public static final int ACCTTYPE_C_Receivable = 1; + /** Account Type - Invoice - AP */ + public static final int ACCTTYPE_V_Liability = 2; + /** Account Type - Invoice - AP Service */ + public static final int ACCTTYPE_V_Liability_Services = 3; + /** Account Type - Invoice - AR Service */ + public static final int ACCTTYPE_C_Receivable_Services = 4; + + /** Account Type - Payment - Unallocated */ + public static final int ACCTTYPE_UnallocatedCash = 10; + /** Account Type - Payment - Transfer */ + public static final int ACCTTYPE_BankInTransit = 11; + /** Account Type - Payment - Selection */ + public static final int ACCTTYPE_PaymentSelect = 12; + /** Account Type - Payment - Prepayment */ + public static final int ACCTTYPE_C_Prepayment = 13; + /** Account Type - Payment - Prepayment */ + public static final int ACCTTYPE_V_Prepayment = 14; + + /** Account Type - Cash - Asset */ + public static final int ACCTTYPE_CashAsset = 20; + /** Account Type - Cash - Transfer */ + public static final int ACCTTYPE_CashTransfer = 21; + /** Account Type - Cash - Expense */ + public static final int ACCTTYPE_CashExpense = 22; + /** Account Type - Cash - Receipt */ + public static final int ACCTTYPE_CashReceipt = 23; + /** Account Type - Cash - Difference */ + public static final int ACCTTYPE_CashDifference = 24; + + /** Account Type - Allocation - Discount Expense (AR) */ + public static final int ACCTTYPE_DiscountExp = 30; + /** Account Type - Allocation - Discount Revenue (AP) */ + public static final int ACCTTYPE_DiscountRev = 31; + /** Account Type - Allocation - Write Off */ + public static final int ACCTTYPE_WriteOff = 32; + + /** Account Type - Bank Statement - Asset */ + public static final int ACCTTYPE_BankAsset = 40; + /** Account Type - Bank Statement - Interest Revenue */ + public static final int ACCTTYPE_InterestRev = 41; + /** Account Type - Bank Statement - Interest Exp */ + public static final int ACCTTYPE_InterestExp = 42; + + /** Inventory Accounts - Differnces */ + public static final int ACCTTYPE_InvDifferences = 50; + /** Inventory Accounts - NIR */ + public static final int ACCTTYPE_NotInvoicedReceipts = 51; + + /** Project Accounts - Assets */ + public static final int ACCTTYPE_ProjectAsset = 61; + /** Project Accounts - WIP */ + public static final int ACCTTYPE_ProjectWIP = 62; + + /** GL Accounts - PPV Offset */ + public static final int ACCTTYPE_PPVOffset = 101; + /** GL Accounts - Commitment Offset */ + public static final int ACCTTYPE_CommitmentOffset = 111; + + + /** + * Get the Valid Combination id for Accounting Schema + * @param AcctType see ACCTTYPE_* + * @param as accounting schema + * @return C_ValidCombination_ID + */ + public int getValidCombination_ID (int AcctType, MAcctSchema as) + { + int para_1 = 0; // first parameter (second is always AcctSchema) + String sql = null; + + /** Account Type - Invoice */ + if (AcctType == ACCTTYPE_Charge) // see getChargeAccount in DocLine + { + int cmp = getAmount(AMTTYPE_Charge).compareTo(Env.ZERO); + if (cmp == 0) + return 0; + else if (cmp < 0) + sql = "SELECT CH_Expense_Acct FROM C_Charge_Acct WHERE C_Charge_ID=? AND C_AcctSchema_ID=?"; + else + sql = "SELECT CH_Revenue_Acct FROM C_Charge_Acct WHERE C_Charge_ID=? AND C_AcctSchema_ID=?"; + para_1 = getC_Charge_ID(); + } + else if (AcctType == ACCTTYPE_V_Liability) + { + sql = "SELECT V_Liability_Acct FROM C_BP_Vendor_Acct WHERE C_BPartner_ID=? AND C_AcctSchema_ID=?"; + para_1 = getC_BPartner_ID(); + } + else if (AcctType == ACCTTYPE_V_Liability_Services) + { + sql = "SELECT V_Liability_Services_Acct FROM C_BP_Vendor_Acct WHERE C_BPartner_ID=? AND C_AcctSchema_ID=?"; + para_1 = getC_BPartner_ID(); + } + else if (AcctType == ACCTTYPE_C_Receivable) + { + sql = "SELECT C_Receivable_Acct FROM C_BP_Customer_Acct WHERE C_BPartner_ID=? AND C_AcctSchema_ID=?"; + para_1 = getC_BPartner_ID(); + } + else if (AcctType == ACCTTYPE_C_Receivable_Services) + { + sql = "SELECT C_Receivable_Services_Acct FROM C_BP_Customer_Acct WHERE C_BPartner_ID=? AND C_AcctSchema_ID=?"; + para_1 = getC_BPartner_ID(); + } + else if (AcctType == ACCTTYPE_V_Prepayment) + { + sql = "SELECT V_Prepayment_Acct FROM C_BP_Vendor_Acct WHERE C_BPartner_ID=? AND C_AcctSchema_ID=?"; + para_1 = getC_BPartner_ID(); + } + else if (AcctType == ACCTTYPE_C_Prepayment) + { + sql = "SELECT C_Prepayment_Acct FROM C_BP_Customer_Acct WHERE C_BPartner_ID=? AND C_AcctSchema_ID=?"; + para_1 = getC_BPartner_ID(); + } + + /** Account Type - Payment */ + else if (AcctType == ACCTTYPE_UnallocatedCash) + { + sql = "SELECT B_UnallocatedCash_Acct FROM C_BankAccount_Acct WHERE C_BankAccount_ID=? AND C_AcctSchema_ID=?"; + para_1 = getC_BankAccount_ID(); + } + else if (AcctType == ACCTTYPE_BankInTransit) + { + sql = "SELECT B_InTransit_Acct FROM C_BankAccount_Acct WHERE C_BankAccount_ID=? AND C_AcctSchema_ID=?"; + para_1 = getC_BankAccount_ID(); + } + else if (AcctType == ACCTTYPE_PaymentSelect) + { + sql = "SELECT B_PaymentSelect_Acct FROM C_BankAccount_Acct WHERE C_BankAccount_ID=? AND C_AcctSchema_ID=?"; + para_1 = getC_BankAccount_ID(); + } + + /** Account Type - Allocation */ + else if (AcctType == ACCTTYPE_DiscountExp) + { + sql = "SELECT a.PayDiscount_Exp_Acct FROM C_BP_Group_Acct a, C_BPartner bp " + + "WHERE a.C_BP_Group_ID=bp.C_BP_Group_ID AND bp.C_BPartner_ID=? AND a.C_AcctSchema_ID=?"; + para_1 = getC_BPartner_ID(); + } + else if (AcctType == ACCTTYPE_DiscountRev) + { + sql = "SELECT PayDiscount_Rev_Acct FROM C_BP_Group_Acct a, C_BPartner bp " + + "WHERE a.C_BP_Group_ID=bp.C_BP_Group_ID AND bp.C_BPartner_ID=? AND a.C_AcctSchema_ID=?"; + para_1 = getC_BPartner_ID(); + } + else if (AcctType == ACCTTYPE_WriteOff) + { + sql = "SELECT WriteOff_Acct FROM C_BP_Group_Acct a, C_BPartner bp " + + "WHERE a.C_BP_Group_ID=bp.C_BP_Group_ID AND bp.C_BPartner_ID=? AND a.C_AcctSchema_ID=?"; + para_1 = getC_BPartner_ID(); + } + + /** Account Type - Bank Statement */ + else if (AcctType == ACCTTYPE_BankAsset) + { + sql = "SELECT B_Asset_Acct FROM C_BankAccount_Acct WHERE C_BankAccount_ID=? AND C_AcctSchema_ID=?"; + para_1 = getC_BankAccount_ID(); + } + else if (AcctType == ACCTTYPE_InterestRev) + { + sql = "SELECT B_InterestRev_Acct FROM C_BankAccount_Acct WHERE C_BankAccount_ID=? AND C_AcctSchema_ID=?"; + para_1 = getC_BankAccount_ID(); + } + else if (AcctType == ACCTTYPE_InterestExp) + { + sql = "SELECT B_InterestExp_Acct FROM C_BankAccount_Acct WHERE C_BankAccount_ID=? AND C_AcctSchema_ID=?"; + para_1 = getC_BankAccount_ID(); + } + + /** Account Type - Cash */ + else if (AcctType == ACCTTYPE_CashAsset) + { + sql = "SELECT CB_Asset_Acct FROM C_CashBook_Acct WHERE C_CashBook_ID=? AND C_AcctSchema_ID=?"; + para_1 = getC_CashBook_ID(); + } + else if (AcctType == ACCTTYPE_CashTransfer) + { + sql = "SELECT CB_CashTransfer_Acct FROM C_CashBook_Acct WHERE C_CashBook_ID=? AND C_AcctSchema_ID=?"; + para_1 = getC_CashBook_ID(); + } + else if (AcctType == ACCTTYPE_CashExpense) + { + sql = "SELECT CB_Expense_Acct FROM C_CashBook_Acct WHERE C_CashBook_ID=? AND C_AcctSchema_ID=?"; + para_1 = getC_CashBook_ID(); + } + else if (AcctType == ACCTTYPE_CashReceipt) + { + sql = "SELECT CB_Receipt_Acct FROM C_CashBook_Acct WHERE C_CashBook_ID=? AND C_AcctSchema_ID=?"; + para_1 = getC_CashBook_ID(); + } + else if (AcctType == ACCTTYPE_CashDifference) + { + sql = "SELECT CB_Differences_Acct FROM C_CashBook_Acct WHERE C_CashBook_ID=? AND C_AcctSchema_ID=?"; + para_1 = getC_CashBook_ID(); + } + + /** Inventory Accounts */ + else if (AcctType == ACCTTYPE_InvDifferences) + { + sql = "SELECT W_Differences_Acct FROM M_Warehouse_Acct WHERE M_Warehouse_ID=? AND C_AcctSchema_ID=?"; + // "SELECT W_Inventory_Acct, W_Revaluation_Acct, W_InvActualAdjust_Acct FROM M_Warehouse_Acct WHERE M_Warehouse_ID=? AND C_AcctSchema_ID=?"; + para_1 = getM_Warehouse_ID(); + } + else if (AcctType == ACCTTYPE_NotInvoicedReceipts) + { + sql = "SELECT NotInvoicedReceipts_Acct FROM C_BP_Group_Acct a, C_BPartner bp " + + "WHERE a.C_BP_Group_ID=bp.C_BP_Group_ID AND bp.C_BPartner_ID=? AND a.C_AcctSchema_ID=?"; + para_1 = getC_BPartner_ID(); + } + + /** Project Accounts */ + else if (AcctType == ACCTTYPE_ProjectAsset) + { + sql = "SELECT PJ_Asset_Acct FROM C_Project_Acct WHERE C_Project_ID=? AND C_AcctSchema_ID=?"; + para_1 = getC_Project_ID(); + } + else if (AcctType == ACCTTYPE_ProjectWIP) + { + sql = "SELECT PJ_WIP_Acct FROM C_Project_Acct WHERE C_Project_ID=? AND C_AcctSchema_ID=?"; + para_1 = getC_Project_ID(); + } + + /** GL Accounts */ + else if (AcctType == ACCTTYPE_PPVOffset) + { + sql = "SELECT PPVOffset_Acct FROM C_AcctSchema_GL WHERE C_AcctSchema_ID=?"; + para_1 = -1; + } + else if (AcctType == ACCTTYPE_CommitmentOffset) + { + sql = "SELECT CommitmentOffset_Acct FROM C_AcctSchema_GL WHERE C_AcctSchema_ID=?"; + para_1 = -1; + } + + else + { + log.severe ("Not found AcctType=" + AcctType); + return 0; + } + // Do we have sql & Parameter + if (sql == null || para_1 == 0) + { + log.severe ("No Parameter for AcctType=" + AcctType + " - SQL=" + sql); + return 0; + } + + // Get Acct + int Account_ID = 0; + try + { + PreparedStatement pstmt = DB.prepareStatement(sql, null); + if (para_1 == -1) // GL Accounts + pstmt.setInt (1, as.getC_AcctSchema_ID()); + else + { + pstmt.setInt (1, para_1); + pstmt.setInt (2, as.getC_AcctSchema_ID()); + } + ResultSet rs = pstmt.executeQuery(); + if (rs.next()) + Account_ID = rs.getInt(1); + rs.close(); + pstmt.close(); + } + catch (SQLException e) + { + log.log(Level.SEVERE, "AcctType=" + AcctType + " - SQL=" + sql, e); + return 0; + } + // No account + if (Account_ID == 0) + { + log.severe ("NO account Type=" + + AcctType + ", Record=" + p_po.get_ID()); + return 0; + } + return Account_ID; + } // getAccount_ID + + /** + * Get the account for Accounting Schema + * @param AcctType see ACCTTYPE_* + * @param as accounting schema + * @return Account + */ + public final MAccount getAccount (int AcctType, MAcctSchema as) + { + int C_ValidCombination_ID = getValidCombination_ID(AcctType, as); + if (C_ValidCombination_ID == 0) + return null; + // Return Account + MAccount acct = MAccount.get (as.getCtx(), C_ValidCombination_ID); + return acct; + } // getAccount + + + /************************************************************************** + * Save to Disk - set posted flag + * @param trxName transaction name + * @return true if saved + */ + private final boolean save (String trxName) + { + log.fine(toString() + "->" + p_Status); + + StringBuffer sql = new StringBuffer("UPDATE "); + sql.append(get_TableName()).append(" SET Posted='").append(p_Status) + .append("',Processing='N' ") + .append("WHERE ") + .append(get_TableName()).append("_ID=").append(p_po.get_ID()); + int no = DB.executeUpdate(sql.toString(), trxName); + return no == 1; + } // save + + /** + * Get DocLine with ID + * @param Record_ID Record ID + * @return DocLine + */ + public DocLine getDocLine (int Record_ID) + { + if (p_lines == null || p_lines.length == 0 || Record_ID == 0) + return null; + + for (int i = 0; i < p_lines.length; i++) + { + if (p_lines[i].get_ID() == Record_ID) + return p_lines[i]; + } + return null; + } // getDocLine + + /** + * String Representation + * @return String + */ + public String toString() + { + return p_po.toString(); + } // toString + + + /** + * Get AD_Client_ID + * @return client + */ + public int getAD_Client_ID() + { + return p_po.getAD_Client_ID(); + } // getAD_Client_ID + + /** + * Get AD_Org_ID + * @return org + */ + public int getAD_Org_ID() + { + return p_po.getAD_Org_ID(); + } // getAD_Org_ID + + /** + * Get Document No + * @return document No + */ + public String getDocumentNo() + { + if (m_DocumentNo != null) + return m_DocumentNo; + int index = p_po.get_ColumnIndex("DocumentNo"); + if (index == -1) + index = p_po.get_ColumnIndex("Name"); + if (index == -1) + throw new UnsupportedOperationException("No DocumentNo"); + m_DocumentNo = (String)p_po.get_Value(index); + return m_DocumentNo; + } // getDocumentNo + + /** + * Get Description + * @return Description + */ + public String getDescription() + { + if (m_Description == null) + { + int index = p_po.get_ColumnIndex("Description"); + if (index != -1) + m_Description = (String)p_po.get_Value(index); + else + m_Description = ""; + } + return m_Description; + } // getDescription + + /** + * Get C_Currency_ID + * @return currency + */ + public int getC_Currency_ID() + { + if (m_C_Currency_ID == -1) + { + int index = p_po.get_ColumnIndex("C_Currency_ID"); + if (index != -1) + { + Integer ii = (Integer)p_po.get_Value(index); + if (ii != null) + m_C_Currency_ID = ii.intValue(); + } + if (m_C_Currency_ID == -1) + m_C_Currency_ID = NO_CURRENCY; + } + return m_C_Currency_ID; + } // getC_Currency_ID + + /** + * Set C_Currency_ID + * @param C_Currency_ID id + */ + public void setC_Currency_ID (int C_Currency_ID) + { + m_C_Currency_ID = C_Currency_ID; + } // setC_Currency_ID + + /** + * Is Multi Currency + * @return mc + */ + public boolean isMultiCurrency() + { + return m_MultiCurrency; + } // isMultiCurrency + + /** + * Set Multi Currency + * @param mc multi currency + */ + protected void setIsMultiCurrency (boolean mc) + { + m_MultiCurrency = mc; + } // setIsMultiCurrency + + /** + * Is Tax Included + * @return tax incl + */ + public boolean isTaxIncluded() + { + return m_TaxIncluded; + } // isTaxIncluded + + /** + * Set Tax Includedy + * @param ti Tax Included + */ + protected void setIsTaxIncluded (boolean ti) + { + m_TaxIncluded = ti; + } // setIsTaxIncluded + + /** + * Get C_ConversionType_ID + * @return ConversionType + */ + public int getC_ConversionType_ID() + { + int index = p_po.get_ColumnIndex("C_ConversionType_ID"); + if (index != -1) + { + Integer ii = (Integer)p_po.get_Value(index); + if (ii != null) + return ii.intValue(); + } + return 0; + } // getC_ConversionType_ID + + /** + * Get GL_Category_ID + * @return categoory + */ + public int getGL_Category_ID() + { + return m_GL_Category_ID; + } // getGL_Category_ID + + /** + * Get GL_Category_ID + * @return categoory + */ + public int getGL_Budget_ID() + { + int index = p_po.get_ColumnIndex("GL_Budget_ID"); + if (index != -1) + { + Integer ii = (Integer)p_po.get_Value(index); + if (ii != null) + return ii.intValue(); + } + return 0; + } // getGL_Budget_ID + + /** + * Get Accounting Date + * @return currency + */ + public Timestamp getDateAcct() + { + if (m_DateAcct != null) + return m_DateAcct; + int index = p_po.get_ColumnIndex("DateAcct"); + if (index != -1) + { + m_DateAcct = (Timestamp)p_po.get_Value(index); + if (m_DateAcct != null) + return m_DateAcct; + } + throw new IllegalStateException("No DateAcct"); + } // getDateAcct + + /** + * Set Date Acct + * @param da accounting date + */ + protected void setDateAcct (Timestamp da) + { + m_DateAcct = da; + } // setDateAcct + + /** + * Get Document Date + * @return currency + */ + public Timestamp getDateDoc() + { + if (m_DateDoc != null) + return m_DateDoc; + int index = p_po.get_ColumnIndex("DateDoc"); + if (index == -1) + index = p_po.get_ColumnIndex("MovementDate"); + if (index != -1) + { + m_DateDoc = (Timestamp)p_po.get_Value(index); + if (m_DateDoc != null) + return m_DateDoc; + } + throw new IllegalStateException("No DateDoc"); + } // getDateDoc + + /** + * Set Date Doc + * @param dd document date + */ + protected void setDateDoc (Timestamp dd) + { + m_DateDoc = dd; + } // setDateDoc + + /** + * Is Document Posted + * @return true if posted + */ + public boolean isPosted() + { + int index = p_po.get_ColumnIndex("Posted"); + if (index != -1) + { + Object posted = p_po.get_Value(index); + if (posted instanceof Boolean) + return ((Boolean)posted).booleanValue(); + if (posted instanceof String) + return "Y".equals(posted); + } + throw new IllegalStateException("No Posted"); + } // isPosted + + /** + * Is Sales Trx + * @return true if posted + */ + public boolean isSOTrx() + { + int index = p_po.get_ColumnIndex("IsSOTrx"); + if (index == -1) + index = p_po.get_ColumnIndex("IsReceipt"); + if (index != -1) + { + Object posted = p_po.get_Value(index); + if (posted instanceof Boolean) + return ((Boolean)posted).booleanValue(); + if (posted instanceof String) + return "Y".equals(posted); + } + return false; + } // isSOTrx + + /** + * Get C_DocType_ID + * @return DocType + */ + public int getC_DocType_ID() + { + int index = p_po.get_ColumnIndex("C_DocType_ID"); + if (index != -1) + { + Integer ii = (Integer)p_po.get_Value(index); + // DocType does not exist - get DocTypeTarget + if (ii != null && ii.intValue() == 0) + { + index = p_po.get_ColumnIndex("C_DocTypeTarget_ID"); + if (index != -1) + ii = (Integer)p_po.get_Value(index); + } + if (ii != null) + return ii.intValue(); + } + return 0; + } // getC_DocType_ID + + /** + * Get header level C_Charge_ID + * @return Charge + */ + public int getC_Charge_ID() + { + int index = p_po.get_ColumnIndex("C_Charge_ID"); + if (index != -1) + { + Integer ii = (Integer)p_po.get_Value(index); + if (ii != null) + return ii.intValue(); + } + return 0; + } // getC_Charge_ID + + /** + * Get SalesRep_ID + * @return SalesRep + */ + public int getSalesRep_ID() + { + int index = p_po.get_ColumnIndex("SalesRep_ID"); + if (index != -1) + { + Integer ii = (Integer)p_po.get_Value(index); + if (ii != null) + return ii.intValue(); + } + return 0; + } // getSalesRep_ID + + /** + * Get C_BankAccount_ID + * @return BankAccount + */ + public int getC_BankAccount_ID() + { + if (m_C_BankAccount_ID == -1) + { + int index = p_po.get_ColumnIndex("C_BankAccount_ID"); + if (index != -1) + { + Integer ii = (Integer)p_po.get_Value(index); + if (ii != null) + m_C_BankAccount_ID = ii.intValue(); + } + if (m_C_BankAccount_ID == -1) + m_C_BankAccount_ID = 0; + } + return m_C_BankAccount_ID; + } // getC_BankAccount_ID + + /** + * Set C_BankAccount_ID + * @param C_BankAccount_ID bank acct + */ + protected void setC_BankAccount_ID (int C_BankAccount_ID) + { + m_C_BankAccount_ID = C_BankAccount_ID; + } // setC_BankAccount_ID + + /** + * Get C_CashBook_ID + * @return CashBook + */ + public int getC_CashBook_ID() + { + if (m_C_CashBook_ID == -1) + { + int index = p_po.get_ColumnIndex("C_CashBook_ID"); + if (index != -1) + { + Integer ii = (Integer)p_po.get_Value(index); + if (ii != null) + m_C_CashBook_ID = ii.intValue(); + } + if (m_C_CashBook_ID == -1) + m_C_CashBook_ID = 0; + } + return m_C_CashBook_ID; + } // getC_CashBook_ID + + /** + * Set C_CashBook_ID + * @param C_CashBook_ID cash book + */ + protected void setC_CashBook_ID (int C_CashBook_ID) + { + m_C_CashBook_ID = C_CashBook_ID; + } // setC_CashBook_ID + + /** + * Get M_Warehouse_ID + * @return Warehouse + */ + public int getM_Warehouse_ID() + { + int index = p_po.get_ColumnIndex("M_Warehouse_ID"); + if (index != -1) + { + Integer ii = (Integer)p_po.get_Value(index); + if (ii != null) + return ii.intValue(); + } + return 0; + } // getM_Warehouse_ID + + + /** + * Get C_BPartner_ID + * @return BPartner + */ + public int getC_BPartner_ID() + { + if (m_C_BPartner_ID == -1) + { + int index = p_po.get_ColumnIndex("C_BPartner_ID"); + if (index != -1) + { + Integer ii = (Integer)p_po.get_Value(index); + if (ii != null) + m_C_BPartner_ID = ii.intValue(); + } + if (m_C_BPartner_ID == -1) + m_C_BPartner_ID = 0; + } + return m_C_BPartner_ID; + } // getC_BPartner_ID + + /** + * Set C_BPartner_ID + * @param C_BPartner_ID bp + */ + protected void setC_BPartner_ID (int C_BPartner_ID) + { + m_C_BPartner_ID = C_BPartner_ID; + } // setC_BPartner_ID + + /** + * Get C_BPartner_Location_ID + * @return BPartner Location + */ + public int getC_BPartner_Location_ID() + { + int index = p_po.get_ColumnIndex("C_BPartner_Location_ID"); + if (index != -1) + { + Integer ii = (Integer)p_po.get_Value(index); + if (ii != null) + return ii.intValue(); + } + return 0; + } // getC_BPartner_Location_ID + + /** + * Get C_Project_ID + * @return Project + */ + public int getC_Project_ID() + { + int index = p_po.get_ColumnIndex("C_Project_ID"); + if (index != -1) + { + Integer ii = (Integer)p_po.get_Value(index); + if (ii != null) + return ii.intValue(); + } + return 0; + } // getC_Project_ID + + /** + * Get C_SalesRegion_ID + * @return Sales Region + */ + public int getC_SalesRegion_ID() + { + int index = p_po.get_ColumnIndex("C_SalesRegion_ID"); + if (index != -1) + { + Integer ii = (Integer)p_po.get_Value(index); + if (ii != null) + return ii.intValue(); + } + return 0; + } // getC_SalesRegion_ID + + /** + * Get C_SalesRegion_ID + * @return Sales Region + */ + public int getBP_C_SalesRegion_ID() + { + if (m_BP_C_SalesRegion_ID == -1) + { + int index = p_po.get_ColumnIndex("C_SalesRegion_ID"); + if (index != -1) + { + Integer ii = (Integer)p_po.get_Value(index); + if (ii != null) + m_BP_C_SalesRegion_ID = ii.intValue(); + } + if (m_BP_C_SalesRegion_ID == -1) + m_BP_C_SalesRegion_ID = 0; + } + return m_BP_C_SalesRegion_ID; + } // getBP_C_SalesRegion_ID + + /** + * Set C_SalesRegion_ID + * @param C_SalesRegion_ID id + */ + protected void setBP_C_SalesRegion_ID (int C_SalesRegion_ID) + { + m_BP_C_SalesRegion_ID = C_SalesRegion_ID; + } // setBP_C_SalesRegion_ID + + /** + * Get C_Activity_ID + * @return Activity + */ + public int getC_Activity_ID() + { + int index = p_po.get_ColumnIndex("C_Activity_ID"); + if (index != -1) + { + Integer ii = (Integer)p_po.get_Value(index); + if (ii != null) + return ii.intValue(); + } + return 0; + } // getC_Activity_ID + + /** + * Get C_Campaign_ID + * @return Campaign + */ + public int getC_Campaign_ID() + { + int index = p_po.get_ColumnIndex("C_Campaign_ID"); + if (index != -1) + { + Integer ii = (Integer)p_po.get_Value(index); + if (ii != null) + return ii.intValue(); + } + return 0; + } // getC_Campaign_ID + + /** + * Get M_Product_ID + * @return Product + */ + public int getM_Product_ID() + { + int index = p_po.get_ColumnIndex("M_Product_ID"); + if (index != -1) + { + Integer ii = (Integer)p_po.get_Value(index); + if (ii != null) + return ii.intValue(); + } + return 0; + } // getM_Product_ID + + /** + * Get AD_OrgTrx_ID + * @return Trx Org + */ + public int getAD_OrgTrx_ID() + { + int index = p_po.get_ColumnIndex("AD_OrgTrx_ID"); + if (index != -1) + { + Integer ii = (Integer)p_po.get_Value(index); + if (ii != null) + return ii.intValue(); + } + return 0; + } // getAD_OrgTrx_ID + + /** + * Get C_LocFrom_ID + * @return loc from + */ + public int getC_LocFrom_ID() + { + return m_C_LocFrom_ID; + } // getC_LocFrom_ID + + /** + * Set C_LocFrom_ID + * @param C_LocFrom_ID loc from + */ + protected void setC_LocFrom_ID(int C_LocFrom_ID) + { + m_C_LocFrom_ID = C_LocFrom_ID; + } // setC_LocFrom_ID + + /** + * Get C_LocTo_ID + * @return loc to + */ + public int getC_LocTo_ID() + { + return m_C_LocTo_ID; + } // getC_LocTo_ID + + /** + * Set C_LocTo_ID + * @param C_LocTo_ID loc to + */ + protected void setC_LocTo_ID(int C_LocTo_ID) + { + m_C_LocTo_ID = C_LocTo_ID; + } // setC_LocTo_ID + + /** + * Get User1_ID + * @return Campaign + */ + public int getUser1_ID() + { + int index = p_po.get_ColumnIndex("User1_ID"); + if (index != -1) + { + Integer ii = (Integer)p_po.get_Value(index); + if (ii != null) + return ii.intValue(); + } + return 0; + } // getUser1_ID + + /** + * Get User2_ID + * @return Campaign + */ + public int getUser2_ID() + { + int index = p_po.get_ColumnIndex("User2_ID"); + if (index != -1) + { + Integer ii = (Integer)p_po.get_Value(index); + if (ii != null) + return ii.intValue(); + } + return 0; + } // getUser2_ID + + + /*************************************************************************/ + // To be overwritten by Subclasses + + /** + * Load Document Details + * @return error message or null + */ + protected abstract String loadDocumentDetails (); + + /** + * Get Source Currency Balance - subtracts line (and tax) amounts from total - no rounding + * @return positive amount, if total header is bigger than lines + */ + public abstract BigDecimal getBalance(); + + /** + * Create Facts (the accounting logic) + * @param as accounting schema + * @return Facts + */ + public abstract ArrayList createFacts (MAcctSchema as); + +} // Doc diff --git a/serverRoot/src/main/server/org/compiere/acct/DocLine.java b/serverRoot/src/main/server/org/compiere/acct/DocLine.java new file mode 100644 index 0000000000..ff6ecd0fc7 --- /dev/null +++ b/serverRoot/src/main/server/org/compiere/acct/DocLine.java @@ -0,0 +1,1016 @@ +/****************************************************************************** + * 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.acct; + +import java.math.*; +import java.sql.*; + +import org.compiere.model.*; +import org.compiere.util.*; + +/** + * Standard Document Line + * + * @author Jorg Janke + * @version $Id: DocLine.java,v 1.2 2006/07/30 00:53:33 jjanke Exp $ + */ +public class DocLine +{ + /** + * Create Document Line + * @param po line persistent object + * @param doc header + */ + public DocLine (PO po, Doc doc) + { + if (po == null) + throw new IllegalArgumentException("PO is null"); + p_po = po; + m_doc = doc; + // + // Document Consistency + if (p_po.getAD_Org_ID() == 0) + p_po.setAD_Org_ID(m_doc.getAD_Org_ID()); + } // DocLine + + /** Persistent Object */ + protected PO p_po = null; + /** Parent */ + private Doc m_doc = null; + /** Log */ + protected CLogger log = CLogger.getCLogger(getClass()); + + /** Qty */ + private BigDecimal m_qty = null; + + // -- GL Amounts + /** Debit Journal Amt */ + private BigDecimal m_AmtSourceDr = Env.ZERO; + /** Credit Journal Amt */ + private BigDecimal m_AmtSourceCr = Env.ZERO; + /** Net Line Amt */ + private BigDecimal m_LineNetAmt = null; + /** List Amount */ + private BigDecimal m_ListAmt = Env.ZERO; + /** Discount Amount */ + private BigDecimal m_DiscountAmt = Env.ZERO; + + /** Converted Amounts */ + private BigDecimal m_AmtAcctDr = null; + private BigDecimal m_AmtAcctCr = null; + /** Acct Schema */ + private int m_C_AcctSchema_ID = 0; + + /** Product Costs */ + private ProductCost m_productCost = null; + /** Production indicator */ + private boolean m_productionBOM = false; + /** Account used only for GL Journal */ + private MAccount m_account = null; + + /** Accounting Date */ + private Timestamp m_DateAcct = null; + /** Document Date */ + private Timestamp m_DateDoc = null; + /** Sales Region */ + private int m_C_SalesRegion_ID = -1; + /** Sales Region */ + private int m_C_BPartner_ID = -1; + /** Location From */ + private int m_C_LocFrom_ID = 0; + /** Location To */ + private int m_C_LocTo_ID = 0; + /** Item */ + private Boolean m_isItem = null; + /** Currency */ + private int m_C_Currency_ID = -1; + /** Conversion Type */ + private int m_C_ConversionType_ID = -1; + /** Period */ + private int m_C_Period_ID = -1; + + /** + * Get Currency + * @return c_Currency_ID + */ + public int getC_Currency_ID () + { + if (m_C_Currency_ID == -1) + { + int index = p_po.get_ColumnIndex("C_Currency_ID"); + if (index != -1) + { + Integer ii = (Integer)p_po.get_Value(index); + if (ii != null) + m_C_Currency_ID = ii.intValue(); + } + if (m_C_Currency_ID <= 0) + m_C_Currency_ID = m_doc.getC_Currency_ID(); + } + return m_C_Currency_ID; + } // getC_Currency_ID + + /** + * Get Conversion Type + * @return C_ConversionType_ID + */ + public int getC_ConversionType_ID () + { + if (m_C_ConversionType_ID == -1) + { + int index = p_po.get_ColumnIndex("C_ConversionType_ID"); + if (index != -1) + { + Integer ii = (Integer)p_po.get_Value(index); + if (ii != null) + m_C_ConversionType_ID = ii.intValue(); + } + if (m_C_ConversionType_ID <= 0) + m_C_ConversionType_ID = m_doc.getC_ConversionType_ID(); + } + return m_C_ConversionType_ID; + } // getC_ConversionType_ID + + /** + * Set C_ConversionType_ID + * @param C_ConversionType_ID id + */ + protected void setC_ConversionType_ID(int C_ConversionType_ID) + { + m_C_ConversionType_ID = C_ConversionType_ID; + } // setC_ConversionType_ID + + /** + * Set Amount (DR) + * @param sourceAmt source amt + */ + public void setAmount (BigDecimal sourceAmt) + { + m_AmtSourceDr = sourceAmt == null ? Env.ZERO : sourceAmt; + m_AmtSourceCr = Env.ZERO; + } // setAmounts + + /** + * Set Amounts + * @param amtSourceDr source amount dr + * @param amtSourceCr source amount cr + */ + public void setAmount (BigDecimal amtSourceDr, BigDecimal amtSourceCr) + { + m_AmtSourceDr = amtSourceDr == null ? Env.ZERO : amtSourceDr; + m_AmtSourceCr = amtSourceCr == null ? Env.ZERO : amtSourceCr; + } // setAmounts + + /** + * Set Converted Amounts + * @param C_AcctSchema_ID acct schema + * @param amtAcctDr acct amount dr + * @param amtAcctCr acct amount cr + */ + public void setConvertedAmt (int C_AcctSchema_ID, BigDecimal amtAcctDr, BigDecimal amtAcctCr) + { + m_C_AcctSchema_ID = C_AcctSchema_ID; + m_AmtAcctDr = amtAcctDr; + m_AmtAcctCr = amtAcctCr; + } // setConvertedAmt + + /** + * Line Net Amount or Dr-Cr + * @return balance + */ + public BigDecimal getAmtSource() + { + return m_AmtSourceDr.subtract(m_AmtSourceCr); + } // getAmount + + /** + * Get (Journal) Line Source Dr Amount + * @return DR source amount + */ + public BigDecimal getAmtSourceDr() + { + return m_AmtSourceDr; + } // getAmtSourceDr + + /** + * Get (Journal) Line Source Cr Amount + * @return CR source amount + */ + public BigDecimal getAmtSourceCr() + { + return m_AmtSourceCr; + } // getAmtSourceCr + + /** + * Line Journal Accounted Dr Amount + * @return DR accounted amount + */ + public BigDecimal getAmtAcctDr() + { + return m_AmtAcctDr; + } // getAmtAcctDr + + /** + * Line Journal Accounted Cr Amount + * @return CR accounted amount + */ + public BigDecimal getAmtAcctCr() + { + return m_AmtAcctCr; + } // getAmtAccrCr + + /** + * Charge Amount + * @return charge amount + */ + public BigDecimal getChargeAmt() + { + int index = p_po.get_ColumnIndex("ChargeAmt"); + if (index != -1) + { + BigDecimal bd = (BigDecimal)p_po.get_Value(index); + if (bd != null) + return bd; + } + return Env.ZERO; + } // getChargeAmt + + /** + * Set Product Amounts + * @param LineNetAmt Line Net Amt + * @param PriceList Price List + * @param Qty Qty for discount calc + */ + public void setAmount (BigDecimal LineNetAmt, BigDecimal PriceList, BigDecimal Qty) + { + m_LineNetAmt = LineNetAmt == null ? Env.ZERO : LineNetAmt; + + if (PriceList != null && Qty != null) + m_ListAmt = PriceList.multiply(Qty); + if (m_ListAmt.equals(Env.ZERO)) + m_ListAmt = m_LineNetAmt; + m_DiscountAmt = m_ListAmt.subtract(m_LineNetAmt); + // + setAmount (m_ListAmt, m_DiscountAmt); + // Log.trace(this,Log.l6_Database, "DocLine_Invoice.setAmount", + // "LineNet=" + m_LineNetAmt + ", List=" + m_ListAmt + ", Discount=" + m_DiscountAmt + // + " => Amount=" + getAmount()); + } // setAmounts + + /** + * Line Discount + * @return discount amount + */ + public BigDecimal getDiscount() + { + return m_DiscountAmt; + } // getDiscount + + /** + * Line List Amount + * @return list amount + */ + public BigDecimal getListAmount() + { + return m_ListAmt; + } // getListAmount + + /** + * Set Line Net Amt Difference + * @param diff difference (to be subtracted) + */ + public void setLineNetAmtDifference (BigDecimal diff) + { + String msg = "Diff=" + diff + + " - LineNetAmt=" + m_LineNetAmt; + m_LineNetAmt = m_LineNetAmt.subtract(diff); + m_DiscountAmt = m_ListAmt.subtract(m_LineNetAmt); + setAmount (m_ListAmt, m_DiscountAmt); + msg += " -> " + m_LineNetAmt; + log.fine(msg); + } // setLineNetAmtDifference + + /************************************************************************** + * Set Accounting Date + * @param dateAcct acct date + */ + public void setDateAcct (Timestamp dateAcct) + { + m_DateAcct = dateAcct; + } // setDateAcct + + /** + * Get Accounting Date + * @return accounting date + */ + public Timestamp getDateAcct () + { + if (m_DateAcct != null) + return m_DateAcct; + int index = p_po.get_ColumnIndex("DateAcct"); + if (index != -1) + { + m_DateAcct = (Timestamp)p_po.get_Value(index); + if (m_DateAcct != null) + return m_DateAcct; + } + m_DateAcct = m_doc.getDateAcct(); + return m_DateAcct; + } // getDateAcct + + /** + * Set Document Date + * @param dateDoc doc date + */ + public void setDateDoc (Timestamp dateDoc) + { + m_DateDoc = dateDoc; + } // setDateDoc + + /** + * Get Document Date + * @return document date + */ + public Timestamp getDateDoc () + { + if (m_DateDoc != null) + return m_DateDoc; + int index = p_po.get_ColumnIndex("DateDoc"); + if (index != -1) + { + m_DateDoc = (Timestamp)p_po.get_Value(index); + if (m_DateDoc != null) + return m_DateDoc; + } + m_DateDoc = m_doc.getDateDoc(); + return m_DateDoc; + } // getDateDoc + + + /************************************************************************** + * Set GL Journal Account + * @param acct account + */ + public void setAccount (MAccount acct) + { + m_account = acct; + } // setAccount + + /** + * Get GL Journal Account + * @return account + */ + public MAccount getAccount() + { + return m_account; + } // getAccount + + /** + * Line Account from Product (or Charge). + * + * @param AcctType see ProductCost.ACCTTYPE_* (0..3) + * @param as Accounting schema + * @return Requested Product Account + */ + public MAccount getAccount (int AcctType, MAcctSchema as) + { + // Charge Account + if (getM_Product_ID() == 0 && getC_Charge_ID() != 0) + { + BigDecimal amt = new BigDecimal (-1); // Revenue (-) + if (!m_doc.isSOTrx()) + amt = new BigDecimal (+1); // Expense (+) + MAccount acct = getChargeAccount(as, amt); + if (acct != null) + return acct; + } + // Product Account + return getProductCost().getAccount (AcctType, as); + } // getAccount + + /** + * Get Charge + * @return C_Charge_ID + */ + protected int getC_Charge_ID() + { + int index = p_po.get_ColumnIndex("C_Charge_ID"); + if (index != -1) + { + Integer ii = (Integer)p_po.get_Value(index); + if (ii != null) + return ii.intValue(); + } + return 0; + } // getC_Charge_ID + + /** + * Get Charge Account + * @param as account schema + * @param amount amount for expense(+)/revenue(-) + * @return Charge Account or null + */ + public MAccount getChargeAccount (MAcctSchema as, BigDecimal amount) + { + int C_Charge_ID = getC_Charge_ID(); + if (C_Charge_ID == 0) + return null; + return MCharge.getAccount(C_Charge_ID, as, amount); + } // getChargeAccount + + /** + * Get Period + * @return C_Period_ID + */ + protected int getC_Period_ID() + { + if (m_C_Period_ID == -1) + { + int index = p_po.get_ColumnIndex("C_Period_ID"); + if (index != -1) + { + Integer ii = (Integer)p_po.get_Value(index); + if (ii != null) + m_C_Period_ID = ii.intValue(); + } + if (m_C_Period_ID == -1) + m_C_Period_ID = 0; + } + return m_C_Period_ID; + } // getC_Period_ID + + /** + * Set C_Period_ID + * @param C_Period_ID id + */ + protected void setC_Period_ID (int C_Period_ID) + { + m_C_Period_ID = C_Period_ID; + } // setC_Period_ID + + /************************************************************************** + * Get (Journal) AcctSchema + * @return C_AcctSchema_ID + */ + public int getC_AcctSchema_ID() + { + return m_C_AcctSchema_ID; + } // getC_AcctSchema_ID + + /** + * Get Line ID + * @return id + */ + public int get_ID() + { + return p_po.get_ID(); + } // get_ID + + /** + * Get AD_Org_ID + * @return org + */ + public int getAD_Org_ID() + { + return p_po.getAD_Org_ID(); + } // getAD_Org_ID + + /** + * Get Order AD_Org_ID + * @return order org if defined + */ + public int getOrder_Org_ID() + { + int C_OrderLine_ID = getC_OrderLine_ID(); + if (C_OrderLine_ID != 0) + { + String sql = "SELECT AD_Org_ID FROM C_OrderLine WHERE C_OrderLine_ID=?"; + int AD_Org_ID = DB.getSQLValue(null, sql, C_OrderLine_ID); + if (AD_Org_ID > 0) + return AD_Org_ID; + } + return getAD_Org_ID(); + } // getOrder_Org_ID + + /** + * Product + * @return M_Product_ID + */ + public int getM_Product_ID() + { + int index = p_po.get_ColumnIndex("M_Product_ID"); + if (index != -1) + { + Integer ii = (Integer)p_po.get_Value(index); + if (ii != null) + return ii.intValue(); + } + return 0; + } // getM_Product_ID + + /** + * Is this an Item Product (vs. not a Service, a charge) + * @return true if product + */ + public boolean isItem() + { + if (m_isItem != null) + return m_isItem.booleanValue(); + + m_isItem = Boolean.FALSE; + if (getM_Product_ID() != 0) + { + MProduct product = MProduct.get(Env.getCtx(), getM_Product_ID()); + if (product.get_ID() == getM_Product_ID() && product.isItem()) + m_isItem = Boolean.TRUE; + } + return m_isItem.booleanValue(); + } // isItem + + /** + * ASI + * @return M_AttributeSetInstance_ID + */ + public int getM_AttributeSetInstance_ID() + { + int index = p_po.get_ColumnIndex("M_AttributeSetInstance_ID"); + if (index != -1) + { + Integer ii = (Integer)p_po.get_Value(index); + if (ii != null) + return ii.intValue(); + } + return 0; + } // getM_AttributeSetInstance_ID + + /** + * Get Warehouse Locator (from) + * @return M_Locator_ID + */ + public int getM_Locator_ID() + { + int index = p_po.get_ColumnIndex("M_Locator_ID"); + if (index != -1) + { + Integer ii = (Integer)p_po.get_Value(index); + if (ii != null) + return ii.intValue(); + } + return 0; + } // getM_Locator_ID + + /** + * Get Warehouse Locator To + * @return M_Locator_ID + */ + public int getM_LocatorTo_ID() + { + int index = p_po.get_ColumnIndex("M_LocatorTo_ID"); + if (index != -1) + { + Integer ii = (Integer)p_po.get_Value(index); + if (ii != null) + return ii.intValue(); + } + return 0; + } // getM_LocatorTo_ID + + /** + * Set Production BOM flag + * @param productionBOM flag + */ + public void setProductionBOM(boolean productionBOM) + { + m_productionBOM = productionBOM; + } // setProductionBOM + + /** + * Is this the BOM to be produced + * @return true if BOM + */ + public boolean isProductionBOM() + { + return m_productionBOM; + } // isProductionBOM + + /** + * Get Production Plan + * @return M_ProductionPlan_ID + */ + public int getM_ProductionPlan_ID() + { + int index = p_po.get_ColumnIndex("M_ProductionPlan_ID"); + if (index != -1) + { + Integer ii = (Integer)p_po.get_Value(index); + if (ii != null) + return ii.intValue(); + } + return 0; + } // getM_ProductionPlan_ID + + /** + * Get Order Line Reference + * @return C_OrderLine_ID + */ + public int getC_OrderLine_ID() + { + int index = p_po.get_ColumnIndex("C_OrderLine_ID"); + if (index != -1) + { + Integer ii = (Integer)p_po.get_Value(index); + if (ii != null) + return ii.intValue(); + } + return 0; + } // getC_OrderLine_ID + + /** + * Get C_LocFrom_ID + * @return loc from + */ + public int getC_LocFrom_ID() + { + return m_C_LocFrom_ID; + } // getC_LocFrom_ID + + /** + * Set C_LocFrom_ID + * @param C_LocFrom_ID loc from + */ + public void setC_LocFrom_ID(int C_LocFrom_ID) + { + m_C_LocFrom_ID = C_LocFrom_ID; + } // setC_LocFrom_ID + + /** + * Get C_LocTo_ID + * @return loc to + */ + public int getC_LocTo_ID() + { + return m_C_LocTo_ID; + } // getC_LocTo_ID + + /** + * Set C_LocTo_ID + * @param C_LocTo_ID loc to + */ + public void setC_LocTo_ID(int C_LocTo_ID) + { + m_C_LocTo_ID = C_LocTo_ID; + } // setC_LocTo_ID + + /** + * Get Product Cost Info + * @return product cost + */ + public ProductCost getProductCost() + { + if (m_productCost == null) + m_productCost = new ProductCost (Env.getCtx(), + getM_Product_ID(), getM_AttributeSetInstance_ID(), p_po.get_TrxName()); + return m_productCost; + } // getProductCost + + /** + * Get Total Product Costs + * @param as accounting schema + * @param AD_Org_ID trx org + * @param zeroCostsOK zero/no costs are OK + * @return costs + */ + public BigDecimal getProductCosts (MAcctSchema as, int AD_Org_ID, boolean zeroCostsOK) + { + ProductCost pc = getProductCost(); + int C_OrderLine_ID = getC_OrderLine_ID(); + String costingMethod = null; + BigDecimal costs = pc.getProductCosts(as, AD_Org_ID, costingMethod, + C_OrderLine_ID, zeroCostsOK); + if (costs != null) + return costs; + return Env.ZERO; + } // getProductCosts + + /** + * Get Product + * @return product or null if no product + */ + public MProduct getProduct() + { + if (m_productCost == null) + m_productCost = new ProductCost (Env.getCtx(), + getM_Product_ID(), getM_AttributeSetInstance_ID(), p_po.get_TrxName()); + if (m_productCost != null) + return m_productCost.getProduct(); + return null; + } // getProduct + + /** + * Get Revenue Recognition + * @return C_RevenueRecognition_ID or 0 + */ + public int getC_RevenueRecognition_ID() + { + MProduct product = getProduct(); + if (product != null) + return product.getC_RevenueRecognition_ID(); + return 0; + } // getC_RevenueRecognition_ID + + /** + * Quantity UOM + * @return Transaction or Storage M_UOM_ID + */ + public int getC_UOM_ID() + { + // Trx UOM + int index = p_po.get_ColumnIndex("C_UOM_ID"); + if (index != -1) + { + Integer ii = (Integer)p_po.get_Value(index); + if (ii != null) + return ii.intValue(); + } + // Storage UOM + MProduct product = getProduct(); + if (product != null) + return product.getC_UOM_ID(); + // + return 0; + } // getC_UOM + + /** + * Quantity + * @param qty transaction Qty + * @param isSOTrx SL order trx (i.e. negative qty) + */ + public void setQty (BigDecimal qty, boolean isSOTrx) + { + if (qty == null) + m_qty = Env.ZERO; + else if (isSOTrx) + m_qty = qty.negate(); + else + m_qty = qty; + getProductCost().setQty (qty); + } // setQty + + /** + * Quantity + * @return transaction Qty + */ + public BigDecimal getQty() + { + return m_qty; + } // getQty + + + + /** + * Description + * @return doc line description + */ + public String getDescription() + { + int index = p_po.get_ColumnIndex("Description"); + if (index != -1) + return (String)p_po.get_Value(index); + return null; + } // getDescription + + /** + * Line Tax + * @return C_Tax_ID + */ + public int getC_Tax_ID() + { + int index = p_po.get_ColumnIndex("C_Tax_ID"); + if (index != -1) + { + Integer ii = (Integer)p_po.get_Value(index); + if (ii != null) + return ii.intValue(); + } + return 0; + } // getC_Tax_ID + + /** + * Get Line Number + * @return line no + */ + public int getLine() + { + int index = p_po.get_ColumnIndex("Line"); + if (index != -1) + { + Integer ii = (Integer)p_po.get_Value(index); + if (ii != null) + return ii.intValue(); + } + return 0; + } // getLine + + /** + * Get BPartner + * @return C_BPartner_ID + */ + public int getC_BPartner_ID() + { + if (m_C_BPartner_ID == -1) + { + int index = p_po.get_ColumnIndex("C_BPartner_ID"); + if (index != -1) + { + Integer ii = (Integer)p_po.get_Value(index); + if (ii != null) + m_C_BPartner_ID = ii.intValue(); + } + if (m_C_BPartner_ID <= 0) + m_C_BPartner_ID = m_doc.getC_BPartner_ID(); + } + return m_C_BPartner_ID; + } // getC_BPartner_ID + + /** + * Set C_BPartner_ID + * @param C_BPartner_ID id + */ + protected void setC_BPartner_ID (int C_BPartner_ID) + { + m_C_BPartner_ID = C_BPartner_ID; + } // setC_BPartner_ID + + + /** + * Get C_BPartner_Location_ID + * @return BPartner Location + */ + public int getC_BPartner_Location_ID() + { + int index = p_po.get_ColumnIndex("C_BPartner_Location_ID"); + if (index != -1) + { + Integer ii = (Integer)p_po.get_Value(index); + if (ii != null) + return ii.intValue(); + } + return m_doc.getC_BPartner_Location_ID(); + } // getC_BPartner_Location_ID + + /** + * Get TrxOrg + * @return AD_OrgTrx_ID + */ + public int getAD_OrgTrx_ID() + { + int index = p_po.get_ColumnIndex("AD_OrgTrx_ID"); + if (index != -1) + { + Integer ii = (Integer)p_po.get_Value(index); + if (ii != null) + return ii.intValue(); + } + return 0; + } // getAD_OrgTrx_ID + + /** + * Get SalesRegion. + * - get Sales Region from BPartner + * @return C_SalesRegion_ID + */ + public int getC_SalesRegion_ID() + { + if (m_C_SalesRegion_ID == -1) // never tried + { + if (getC_BPartner_Location_ID() != 0) + // && m_acctSchema.isAcctSchemaElement(MAcctSchemaElement.ELEMENTTYPE_SalesRegion)) + { + String sql = "SELECT COALESCE(C_SalesRegion_ID,0) FROM C_BPartner_Location WHERE C_BPartner_Location_ID=?"; + m_C_SalesRegion_ID = DB.getSQLValue (null, + sql, getC_BPartner_Location_ID()); + log.fine("C_SalesRegion_ID=" + m_C_SalesRegion_ID + " (from BPL)" ); + if (m_C_SalesRegion_ID == 0) + m_C_SalesRegion_ID = -2; // don't try again + } + else + m_C_SalesRegion_ID = -2; // don't try again + } + if (m_C_SalesRegion_ID < 0) // invalid + return 0; + return m_C_SalesRegion_ID; + } // getC_SalesRegion_ID + + /** + * Get Project + * @return C_Project_ID + */ + public int getC_Project_ID() + { + int index = p_po.get_ColumnIndex("C_Project_ID"); + if (index != -1) + { + Integer ii = (Integer)p_po.get_Value(index); + if (ii != null) + return ii.intValue(); + } + return 0; + } // getC_Project_ID + + /** + * Get Campaign + * @return C_Campaign_ID + */ + public int getC_Campaign_ID() + { + int index = p_po.get_ColumnIndex("C_Campaign_ID"); + if (index != -1) + { + Integer ii = (Integer)p_po.get_Value(index); + if (ii != null) + return ii.intValue(); + } + return 0; + } // getC_Campaign_ID + + /** + * Get Activity + * @return C_Activity_ID + */ + public int getC_Activity_ID() + { + int index = p_po.get_ColumnIndex("C_Activity_ID"); + if (index != -1) + { + Integer ii = (Integer)p_po.get_Value(index); + if (ii != null) + return ii.intValue(); + } + return 0; + } // getC_Activity_ID + + /** + * Get User 1 + * @return user defined 1 + */ + public int getUser1_ID() + { + int index = p_po.get_ColumnIndex("User1_ID"); + if (index != -1) + { + Integer ii = (Integer)p_po.get_Value(index); + if (ii != null) + return ii.intValue(); + } + return 0; + } // getUser1_ID + + /** + * Get User 2 + * @return user defined 2 + */ + public int getUser2_ID() + { + int index = p_po.get_ColumnIndex("User2_ID"); + if (index != -1) + { + Integer ii = (Integer)p_po.get_Value(index); + if (ii != null) + return ii.intValue(); + } + return 0; + } // getUser2_ID + + /** + * String representation + * @return String + */ + public String toString() + { + StringBuffer sb = new StringBuffer("DocLine=["); + sb.append(p_po.get_ID()); + if (getDescription() != null) + sb.append(",").append(getDescription()); + if (getM_Product_ID() != 0) + sb.append(",M_Product_ID=").append(getM_Product_ID()); + sb.append(",Qty=").append(m_qty) + .append(",Amt=").append(getAmtSource()) + .append("]"); + return sb.toString(); + } // toString + +} // DocumentLine diff --git a/serverRoot/src/main/server/org/compiere/acct/DocLine_Allocation.java b/serverRoot/src/main/server/org/compiere/acct/DocLine_Allocation.java new file mode 100644 index 0000000000..3005fb2c2f --- /dev/null +++ b/serverRoot/src/main/server/org/compiere/acct/DocLine_Allocation.java @@ -0,0 +1,142 @@ +/****************************************************************************** + * 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.acct; + +import java.math.*; +import org.compiere.model.*; +import org.compiere.util.*; + +/** + * Allocation Line + * + * @author Jorg Janke + * @version $Id: DocLine_Allocation.java,v 1.2 2006/07/30 00:53:33 jjanke Exp $ + */ +public class DocLine_Allocation extends DocLine +{ + /** + * DocLine_Allocation + * @param line allocation line + * @param doc header + */ + public DocLine_Allocation (MAllocationLine line, Doc doc) + { + super (line, doc); + m_C_Payment_ID = line.getC_Payment_ID(); + m_C_CashLine_ID = line.getC_CashLine_ID(); + m_C_Invoice_ID = line.getC_Invoice_ID(); + m_C_Order_ID = line.getC_Order_ID(); + // + setAmount(line.getAmount()); + m_DiscountAmt = line.getDiscountAmt(); + m_WriteOffAmt = line.getWriteOffAmt(); + m_OverUnderAmt = line.getOverUnderAmt(); + } // DocLine_Allocation + + private int m_C_Invoice_ID; + private int m_C_Payment_ID; + private int m_C_CashLine_ID; + private int m_C_Order_ID; + private BigDecimal m_DiscountAmt; + private BigDecimal m_WriteOffAmt; + private BigDecimal m_OverUnderAmt; + + + /** + * Get Invoice C_Currency_ID + * @return 0 if no invoice -1 if not found + */ + public int getInvoiceC_Currency_ID() + { + if (m_C_Invoice_ID == 0) + return 0; + String sql = "SELECT C_Currency_ID " + + "FROM C_Invoice " + + "WHERE C_Invoice_ID=?"; + return DB.getSQLValue(null, sql, m_C_Invoice_ID); + } // getInvoiceC_Currency_ID + + /** + * String Representation + * @return info + */ + public String toString () + { + StringBuffer sb = new StringBuffer ("DocLine_Allocation["); + sb.append(get_ID()) + .append(",Amt=").append(getAmtSource()) + .append(",Discount=").append(getDiscountAmt()) + .append(",WriteOff=").append(getWriteOffAmt()) + .append(",OverUnderAmt=").append(getOverUnderAmt()) + .append(" - C_Payment_ID=").append(m_C_Payment_ID) + .append(",C_CashLine_ID=").append(m_C_CashLine_ID) + .append(",C_Invoice_ID=").append(m_C_Invoice_ID) + .append("]"); + return sb.toString (); + } // toString + + + /** + * @return Returns the c_Order_ID. + */ + public int getC_Order_ID () + { + return m_C_Order_ID; + } + /** + * @return Returns the discountAmt. + */ + public BigDecimal getDiscountAmt () + { + return m_DiscountAmt; + } + /** + * @return Returns the overUnderAmt. + */ + public BigDecimal getOverUnderAmt () + { + return m_OverUnderAmt; + } + /** + * @return Returns the writeOffAmt. + */ + public BigDecimal getWriteOffAmt () + { + return m_WriteOffAmt; + } + /** + * @return Returns the c_CashLine_ID. + */ + public int getC_CashLine_ID () + { + return m_C_CashLine_ID; + } + /** + * @return Returns the c_Invoice_ID. + */ + public int getC_Invoice_ID () + { + return m_C_Invoice_ID; + } + /** + * @return Returns the c_Payment_ID. + */ + public int getC_Payment_ID () + { + return m_C_Payment_ID; + } +} // DocLine_Allocation diff --git a/serverRoot/src/main/server/org/compiere/acct/DocLine_Bank.java b/serverRoot/src/main/server/org/compiere/acct/DocLine_Bank.java new file mode 100644 index 0000000000..112a24997b --- /dev/null +++ b/serverRoot/src/main/server/org/compiere/acct/DocLine_Bank.java @@ -0,0 +1,122 @@ +/****************************************************************************** + * 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.acct; + +import java.math.*; +import org.compiere.model.*; +import org.compiere.util.*; +//import org.compiere.model.*; + +/** + * Bank Statement Line + * + * @author Jorg Janke + * @version $Id: DocLine_Bank.java,v 1.2 2006/07/30 00:53:33 jjanke Exp $ + */ +public class DocLine_Bank extends DocLine +{ + /** + * Constructor + * @param line statement line + * @param doc header + */ + public DocLine_Bank (MBankStatementLine line, Doc_Bank doc) + { + super (line, doc); + m_C_Payment_ID = line.getC_Payment_ID(); + m_IsReversal = line.isReversal(); + // + m_StmtAmt = line.getStmtAmt(); + m_InterestAmt = line.getInterestAmt(); + m_TrxAmt = line.getTrxAmt(); + // + setDateDoc(line.getValutaDate()); + setC_BPartner_ID(line.getC_BPartner_ID()); + } // DocLine_Bank + + /** Reversal Flag */ + private boolean m_IsReversal = false; + /** Payment */ + private int m_C_Payment_ID = 0; + + private BigDecimal m_TrxAmt = Env.ZERO; + private BigDecimal m_StmtAmt = Env.ZERO; + private BigDecimal m_InterestAmt = Env.ZERO; + + /** + * Get Payment + * @return C_Paymnet_ID + */ + public int getC_Payment_ID() + { + return m_C_Payment_ID; + } // getC_Payment_ID + + /** + * Get AD_Org_ID + * @param payment if true get Org from payment + * @return org + */ + public int getAD_Org_ID (boolean payment) + { + if (payment && getC_Payment_ID() != 0) + { + String sql = "SELECT AD_Org_ID FROM C_Payment WHERE C_Payment_ID=?"; + int id = DB.getSQLValue(null, sql, getC_Payment_ID()); + if (id > 0) + return id; + } + return super.getAD_Org_ID(); + } // getAD_Org_ID + + /** + * Is Reversal + * @return true if reversal + */ + public boolean isReversal() + { + return m_IsReversal; + } // isReversal + + /** + * Get Interest + * @return InterestAmount + */ + public BigDecimal getInterestAmt() + { + return m_InterestAmt; + } // getInterestAmt + + /** + * Get Statement + * @return Starement Amount + */ + public BigDecimal getStmtAmt() + { + return m_StmtAmt; + } // getStrmtAmt + + /** + * Get Transaction + * @return transaction amount + */ + public BigDecimal getTrxAmt() + { + return m_TrxAmt; + } // getTrxAmt + +} // DocLine_Bank diff --git a/serverRoot/src/main/server/org/compiere/acct/DocLine_Cash.java b/serverRoot/src/main/server/org/compiere/acct/DocLine_Cash.java new file mode 100644 index 0000000000..d25c260d48 --- /dev/null +++ b/serverRoot/src/main/server/org/compiere/acct/DocLine_Cash.java @@ -0,0 +1,138 @@ +/****************************************************************************** + * 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.acct; + +import java.math.*; + +import org.compiere.model.*; +import org.compiere.util.*; + +/** + * Cash Journal Line + * + * @author Jorg Janke + * @version $Id: DocLine_Cash.java,v 1.3 2006/07/30 00:53:33 jjanke Exp $ + */ +public class DocLine_Cash extends DocLine +{ + /** + * Constructor + * @param line cash line + * @param doc header + */ + public DocLine_Cash (MCashLine line, Doc_Cash doc) + { + super (line, doc); + m_CashType = line.getCashType(); + m_C_BankAccount_ID = line.getC_BankAccount_ID(); + m_C_Invoice_ID = line.getC_Invoice_ID(); + // + if (m_C_Invoice_ID != 0) + { + MInvoice invoice = MInvoice.get(line.getCtx(), m_C_Invoice_ID); + setC_BPartner_ID(invoice.getC_BPartner_ID()); + } + + // + m_Amount = line.getAmount(); + m_DiscountAmt = line.getDiscountAmt(); + m_WriteOffAmt = line.getWriteOffAmt(); + setAmount(m_Amount); + + + } // DocLine_Cash + + /** Cash Type */ + private String m_CashType = ""; + + // AD_Reference_ID=217 + /** Charge - C */ + public static final String CASHTYPE_CHARGE = "C"; + /** Difference - D */ + public static final String CASHTYPE_DIFFERENCE = "D"; + /** Expense - E */ + public static final String CASHTYPE_EXPENSE = "E"; + /** Onvoice - I */ + public static final String CASHTYPE_INVOICE = "I"; + /** Receipt - R */ + public static final String CASHTYPE_RECEIPT = "R"; + /** Transfer - T */ + public static final String CASHTYPE_TRANSFER = "T"; + + // References + private int m_C_BankAccount_ID = 0; + private int m_C_Invoice_ID = 0; + + // Amounts + private BigDecimal m_Amount = Env.ZERO; + private BigDecimal m_DiscountAmt = Env.ZERO; + private BigDecimal m_WriteOffAmt = Env.ZERO; + + + /** + * Get Cash Type + * @return cash type + */ + public String getCashType() + { + return m_CashType; + } // getCashType + + /** + * Get Bank Account + * @return Bank Account + */ + public int getC_BankAccount_ID() + { + return m_C_BankAccount_ID; + } // getC_BankAccount_ID + + /** + * Get Invoice + * @return C_Invoice_ID + */ + public int getC_Invoice_ID() + { + return m_C_Invoice_ID; + } // getC_Invoice_ID + + /** + * Get Amount + * @return Payment Amount + */ + public BigDecimal getAmount() + { + return m_Amount; + } + /** + * Get Discount + * @return Discount Amount + */ + public BigDecimal getDiscountAmt() + { + return m_DiscountAmt; + } + /** + * Get WriteOff + * @return Write-Off Amount + */ + public BigDecimal getWriteOffAmt() + { + return m_WriteOffAmt; + } + +} // DocLine_Cash diff --git a/serverRoot/src/main/server/org/compiere/acct/DocTax.java b/serverRoot/src/main/server/org/compiere/acct/DocTax.java new file mode 100644 index 0000000000..3a3725f325 --- /dev/null +++ b/serverRoot/src/main/server/org/compiere/acct/DocTax.java @@ -0,0 +1,240 @@ +/****************************************************************************** + * 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.acct; + +import java.math.*; +import java.sql.*; +import org.compiere.model.*; +import java.util.logging.*; +import org.compiere.util.*; + +/** + * Document Tax Line + * + * @author Jorg Janke + * @version $Id: DocTax.java,v 1.3 2006/07/30 00:53:33 jjanke Exp $ + */ +public final class DocTax +{ + /** + * Create Tax + * @param C_Tax_ID tax + * @param name name + * @param rate rate + * @param taxBaseAmt tax base amount + * @param amount amount + * @param salesTax sales tax flag + */ + public DocTax (int C_Tax_ID, String name, BigDecimal rate, + BigDecimal taxBaseAmt, BigDecimal amount, boolean salesTax) + { + m_C_Tax_ID = C_Tax_ID; + m_name = name; + m_rate = rate; + m_amount = amount; + m_salesTax = salesTax; + } // DocTax + + /** Tax ID */ + private int m_C_Tax_ID = 0; + /** Amount */ + private BigDecimal m_amount = null; + /** Tax Rate */ + private BigDecimal m_rate = null; + /** Name */ + private String m_name = null; + /** Base Tax Amt */ + private BigDecimal m_taxBaseAmt = null; + /** Included Tax */ + private BigDecimal m_includedTax = Env.ZERO; + /** Sales Tax */ + private boolean m_salesTax = false; + + /** Logger */ + private static CLogger log = CLogger.getCLogger(DocTax.class); + + + /** Tax Due Acct */ + public static final int ACCTTYPE_TaxDue = 0; + /** Tax Liability */ + public static final int ACCTTYPE_TaxLiability = 1; + /** Tax Credit */ + public static final int ACCTTYPE_TaxCredit = 2; + /** Tax Receivables */ + public static final int ACCTTYPE_TaxReceivables = 3; + /** Tax Expense */ + public static final int ACCTTYPE_TaxExpense = 4; + + /** + * Get Account + * @param AcctType see ACCTTYPE_* + * @param as account schema + * @return Account + */ + public MAccount getAccount (int AcctType, MAcctSchema as) + { + if (AcctType < 0 || AcctType > 4) + return null; + // + String sql = "SELECT T_Due_Acct, T_Liability_Acct, T_Credit_Acct, T_Receivables_Acct, T_Expense_Acct " + + "FROM C_Tax_Acct WHERE C_Tax_ID=? AND C_AcctSchema_ID=?"; + int validCombination_ID = 0; + try + { + PreparedStatement pstmt = DB.prepareStatement(sql, null); + pstmt.setInt(1, m_C_Tax_ID); + pstmt.setInt(2, as.getC_AcctSchema_ID()); + ResultSet rs = pstmt.executeQuery(); + if (rs.next()) + validCombination_ID = rs.getInt(AcctType+1); // 1..5 + rs.close(); + pstmt.close(); + } + catch (SQLException e) + { + log.log(Level.SEVERE, sql, e); + } + if (validCombination_ID == 0) + return null; + return MAccount.get(as.getCtx(), validCombination_ID); + } // getAccount + + /** + * Get Amount + * @return gross amount + */ + public BigDecimal getAmount() + { + return m_amount; + } + + /** + * Get Base Amount + * @return net amount + */ + public BigDecimal getTaxBaseAmt() + { + return m_taxBaseAmt; + } + + /** + * Get Rate + * @return tax rate in percent + */ + public BigDecimal getRate() + { + return m_rate; + } + + /** + * Get Name of Tax + * @return name + */ + public String getName() + { + return m_name; + } + + /** + * Get C_Tax_ID + * @return tax id + */ + public int getC_Tax_ID() + { + return m_C_Tax_ID; + } // getC_Tax_ID + + /** + * Get Description (Tax Name and Base Amount) + * @return tax anme and base amount + */ + public String getDescription() + { + return m_name + " " + m_taxBaseAmt.toString(); + } // getDescription + + /** + * Add to Included Tax + * @param amt amount + */ + public void addIncludedTax (BigDecimal amt) + { + m_includedTax = m_includedTax.add(amt); + } // addIncludedTax + + /** + * Get Included Tax + * @return tax amount + */ + public BigDecimal getIncludedTax() + { + return m_includedTax; + } // getIncludedTax + + /** + * Get Included Tax Difference + * @return tax ampunt - included amount + */ + public BigDecimal getIncludedTaxDifference() + { + return m_amount.subtract(m_includedTax); + } // getIncludedTaxDifference + + /** + * Included Tax differs from tax amount + * @return true if difference + */ + public boolean isIncludedTaxDifference() + { + return Env.ZERO.compareTo(getIncludedTaxDifference()) != 0; + } // isIncludedTaxDifference + + /** + * Get AP Tax Type + * @return AP tax type (Credit or Expense) + */ + public int getAPTaxType() + { + if (isSalesTax()) + return ACCTTYPE_TaxExpense; + return ACCTTYPE_TaxCredit; + } // getAPTaxAcctType + + /** + * Is Sales Tax + * @return sales tax + */ + public boolean isSalesTax() + { + return m_salesTax; + } // isSalesTax + + + /** + * Return String representation + * @return tax anme and base amount + */ + public String toString() + { + StringBuffer sb = new StringBuffer("Tax=("); + sb.append(m_name); + sb.append(" Amt=").append(m_amount); + sb.append(")"); + return sb.toString(); + } // toString + +} // DocTax diff --git a/serverRoot/src/main/server/org/compiere/acct/Doc_Allocation.java b/serverRoot/src/main/server/org/compiere/acct/Doc_Allocation.java new file mode 100644 index 0000000000..4f17ae22bf --- /dev/null +++ b/serverRoot/src/main/server/org/compiere/acct/Doc_Allocation.java @@ -0,0 +1,892 @@ +/****************************************************************************** + * 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.acct; + +import java.math.*; +import java.sql.*; +import java.util.*; +import java.util.logging.*; +import org.apache.taglibs.standard.lang.jstl.*; +import org.compiere.model.*; +import org.compiere.util.*; + +/** + * Post Allocation Documents. + *
+ *  Table:              C_AllocationHdr
+ *  Document Types:     CMA
+ *  
+ * @author Jorg Janke + * @version $Id: Doc_Allocation.java,v 1.6 2006/07/30 00:53:33 jjanke Exp $ + */ +public class Doc_Allocation extends Doc +{ + /** + * Constructor + * @param ass accounting schemata + * @param rs record + * @param trxName trx + */ + protected Doc_Allocation (MAcctSchema[] ass, ResultSet rs, String trxName) + { + super (ass, MAllocationHdr.class, rs, DOCTYPE_Allocation, trxName); + } // Doc_Allocation + + /** Tolearance G&L */ + private static final BigDecimal TOLERANCE = new BigDecimal (0.02); + /** Facts */ + private ArrayList m_facts = null; + + + /** + * Load Specific Document Details + * @return error message or null + */ + protected String loadDocumentDetails () + { + MAllocationHdr alloc = (MAllocationHdr)getPO(); + setDateDoc(alloc.getDateTrx()); + // Contained Objects + p_lines = loadLines(alloc); + return null; + } // loadDocumentDetails + + /** + * Load Invoice Line + * @param alloc header + * @return DocLine Array + */ + private DocLine[] loadLines(MAllocationHdr alloc) + { + ArrayList list = new ArrayList(); + MAllocationLine[] lines = alloc.getLines(false); + for (int i = 0; i < lines.length; i++) + { + MAllocationLine line = lines[i]; + DocLine_Allocation docLine = new DocLine_Allocation(line, this); + + // Get Payment Conversion Rate + if (line.getC_Payment_ID() != 0) + { + MPayment payment = new MPayment (getCtx(), line.getC_Payment_ID(), getTrxName()); + int C_ConversionType_ID = payment.getC_ConversionType_ID(); + docLine.setC_ConversionType_ID(C_ConversionType_ID); + } + // + log.fine(docLine.toString()); + list.add (docLine); + } + + // Return Array + DocLine[] dls = new DocLine[list.size()]; + list.toArray(dls); + return dls; + } // loadLines + + + /************************************************************************** + * Get Source Currency Balance - subtracts line and tax amounts from total - no rounding + * @return positive amount, if total invoice is bigger than lines + */ + public BigDecimal getBalance() + { + BigDecimal retValue = Env.ZERO; + return retValue; + } // getBalance + + /** + * Create Facts (the accounting logic) for + * CMA. + *
+	 *  AR_Invoice_Payment
+	 *      UnAllocatedCash DR
+	 *      or C_Prepayment
+	 *      DiscountExp     DR
+	 *      WriteOff        DR
+	 *      Receivables             CR
+	 *  AR_Invoice_Cash
+	 *      CashTransfer    DR
+	 *      DiscountExp     DR
+	 *      WriteOff        DR
+	 *      Receivables             CR
+	 * 
+	 *  AP_Invoice_Payment
+	 *      Liability       DR
+	 *      DiscountRev             CR
+	 *      WriteOff                CR
+	 *      PaymentSelect           CR
+	 *      or V_Prepayment
+	 *  AP_Invoice_Cash
+	 *      Liability       DR
+	 *      DiscountRev             CR
+	 *      WriteOff                CR
+	 *      CashTransfer            CR
+	 *  CashBankTransfer
+	 *      -
+	 *  ==============================
+	 *  Realized Gain & Loss
+	 * 		AR/AP			DR		CR
+	 * 		Realized G/L	DR		CR
+	 * 
+	 *
+	 *  
+ * Tax needs to be corrected for discount & write-off; + * Currency gain & loss is realized here. + * @param as accounting schema + * @return Fact + */ + public ArrayList createFacts (MAcctSchema as) + { + m_facts = new ArrayList(); + + // create Fact Header + Fact fact = new Fact(this, as, Fact.POST_Actual); + + for (int i = 0; i < p_lines.length; i++) + { + DocLine_Allocation line = (DocLine_Allocation)p_lines[i]; + setC_BPartner_ID(line.getC_BPartner_ID()); + + // CashBankTransfer - all references null and Discount/WriteOff = 0 + if (line.getC_Payment_ID() != 0 + && line.getC_Invoice_ID() == 0 && line.getC_Order_ID() == 0 + && line.getC_CashLine_ID() == 0 && line.getC_BPartner_ID() == 0 + && Env.ZERO.compareTo(line.getDiscountAmt()) == 0 + && Env.ZERO.compareTo(line.getWriteOffAmt()) == 0) + continue; + + // Receivables/Liability Amt + BigDecimal allocationSource = line.getAmtSource() + .add(line.getDiscountAmt()) + .add(line.getWriteOffAmt()); + BigDecimal allocationAccounted = null; // AR/AP balance corrected + + FactLine fl = null; + MAccount bpAcct = null; // Liability/Receivables + // + MPayment payment = null; + if (line.getC_Payment_ID() != 0) + payment = new MPayment (getCtx(), line.getC_Payment_ID(), getTrxName()); + MInvoice invoice = null; + if (line.getC_Invoice_ID() != 0) + invoice = new MInvoice (getCtx(), line.getC_Invoice_ID(), null); + + // No Invoice + if (invoice == null) + { + // Payment Only + if (line.getC_Invoice_ID() == 0 && line.getC_Payment_ID() != 0) + { + fl = fact.createLine (line, getPaymentAcct(as, line.getC_Payment_ID()), + getC_Currency_ID(), line.getAmtSource(), null); + if (fl != null && payment != null) + fl.setAD_Org_ID(payment.getAD_Org_ID()); + } + else + { + p_Error = "Cannot determine SO/PO"; + log.log(Level.SEVERE, p_Error); + return null; + } + } + // Sales Invoice + else if (invoice.isSOTrx()) + { + // Payment/Cash DR + if (line.getC_Payment_ID() != 0) + { + fl = fact.createLine (line, getPaymentAcct(as, line.getC_Payment_ID()), + getC_Currency_ID(), line.getAmtSource(), null); + if (fl != null && payment != null) + fl.setAD_Org_ID(payment.getAD_Org_ID()); + } + else if (line.getC_CashLine_ID() != 0) + { + fl = fact.createLine (line, getCashAcct(as, line.getC_CashLine_ID()), + getC_Currency_ID(), line.getAmtSource(), null); + MCashLine cashLine = new MCashLine (getCtx(), line.getC_CashLine_ID(), getTrxName()); + if (fl != null && cashLine.get_ID() != 0) + fl.setAD_Org_ID(cashLine.getAD_Org_ID()); + } + // Discount DR + if (Env.ZERO.compareTo(line.getDiscountAmt()) != 0) + { + fl = fact.createLine (line, getAccount(Doc.ACCTTYPE_DiscountExp, as), + getC_Currency_ID(), line.getDiscountAmt(), null); + if (fl != null && payment != null) + fl.setAD_Org_ID(payment.getAD_Org_ID()); + } + // Write off DR + if (Env.ZERO.compareTo(line.getWriteOffAmt()) != 0) + { + fl = fact.createLine (line, getAccount(Doc.ACCTTYPE_WriteOff, as), + getC_Currency_ID(), line.getWriteOffAmt(), null); + if (fl != null && payment != null) + fl.setAD_Org_ID(payment.getAD_Org_ID()); + } + + // AR Invoice Amount CR + if (as.isAccrual()) + { + bpAcct = getAccount(Doc.ACCTTYPE_C_Receivable, as); + fl = fact.createLine (line, bpAcct, + getC_Currency_ID(), null, allocationSource); // payment currency + if (fl != null) + allocationAccounted = fl.getAcctBalance().negate(); + if (fl != null && invoice != null) + fl.setAD_Org_ID(invoice.getAD_Org_ID()); + } + else // Cash Based + { + allocationAccounted = createCashBasedAcct (as, fact, + invoice, allocationSource); + } + } + // Purchase Invoice + else + { + allocationSource = allocationSource.negate(); // allocation is negative + // AP Invoice Amount DR + if (as.isAccrual()) + { + bpAcct = getAccount(Doc.ACCTTYPE_V_Liability, as); + fl = fact.createLine (line, bpAcct, + getC_Currency_ID(), allocationSource, null); // payment currency + if (fl != null) + allocationAccounted = fl.getAcctBalance(); + if (fl != null && invoice != null) + fl.setAD_Org_ID(invoice.getAD_Org_ID()); + } + else // Cash Based + { + allocationAccounted = createCashBasedAcct (as, fact, + invoice, allocationSource); + } + + // Discount CR + if (Env.ZERO.compareTo(line.getDiscountAmt()) != 0) + { + fl = fact.createLine (line, getAccount(Doc.ACCTTYPE_DiscountRev, as), + getC_Currency_ID(), null, line.getDiscountAmt().negate()); + if (fl != null && payment != null) + fl.setAD_Org_ID(payment.getAD_Org_ID()); + } + // Write off CR + if (Env.ZERO.compareTo(line.getWriteOffAmt()) != 0) + { + fl = fact.createLine (line, getAccount(Doc.ACCTTYPE_WriteOff, as), + getC_Currency_ID(), null, line.getWriteOffAmt().negate()); + if (fl != null && payment != null) + fl.setAD_Org_ID(payment.getAD_Org_ID()); + } + // Payment/Cash CR + if (line.getC_Payment_ID() != 0) + { + fl = fact.createLine (line, getPaymentAcct(as, line.getC_Payment_ID()), + getC_Currency_ID(), null, line.getAmtSource().negate()); + if (fl != null && payment != null) + fl.setAD_Org_ID(payment.getAD_Org_ID()); + } + else if (line.getC_CashLine_ID() != 0) + { + fl = fact.createLine (line, getCashAcct(as, line.getC_CashLine_ID()), + getC_Currency_ID(), null, line.getAmtSource().negate()); + MCashLine cashLine = new MCashLine (getCtx(), line.getC_CashLine_ID(), getTrxName()); + if (fl != null && cashLine.get_ID() != 0) + fl.setAD_Org_ID(cashLine.getAD_Org_ID()); + } + } + + // VAT Tax Correction + if (invoice != null && as.isTaxCorrection()) + { + BigDecimal taxCorrectionAmt = Env.ZERO; + if (as.isTaxCorrectionDiscount()) + taxCorrectionAmt = line.getDiscountAmt(); + if (as.isTaxCorrectionWriteOff()) + taxCorrectionAmt = taxCorrectionAmt.add(line.getWriteOffAmt()); + // + if (taxCorrectionAmt.signum() != 0) + { + if (!createTaxCorrection(as, fact, line, + getAccount(invoice.isSOTrx() ? Doc.ACCTTYPE_DiscountExp : Doc.ACCTTYPE_DiscountRev, as), + getAccount(Doc.ACCTTYPE_WriteOff, as))) + { + p_Error = "Cannot create Tax correction"; + return null; + } + } + } + + // Realized Gain & Loss + if (invoice != null + && (getC_Currency_ID() != as.getC_Currency_ID() // payment allocation in foreign currency + || getC_Currency_ID() != line.getInvoiceC_Currency_ID())) // allocation <> invoice currency + { + p_Error = createRealizedGainLoss (as, fact, bpAcct, invoice, + allocationSource, allocationAccounted); + if (p_Error != null) + return null; + } + + } // for all lines + + // reset line info + setC_BPartner_ID(0); + // + m_facts.add(fact); + return m_facts; + } // createFact + + /** + * Create Cash Based Acct + * @param as accounting schema + * @param fact fact + * @param invoice invoice + * @param allocationSource allocation amount (incl discount, writeoff) + * @return Accounted Amt + */ + private BigDecimal createCashBasedAcct (MAcctSchema as, Fact fact, MInvoice invoice, + BigDecimal allocationSource) + { + BigDecimal allocationAccounted = Env.ZERO; + // Multiplier + double percent = invoice.getGrandTotal().doubleValue() / allocationSource.doubleValue(); + if (percent > 0.99 && percent < 1.01) + percent = 1.0; + log.config("Multiplier=" + percent + " - GrandTotal=" + invoice.getGrandTotal() + + " - Allocation Source=" + allocationSource); + + // Get Invoice Postings + Doc_Invoice docInvoice = (Doc_Invoice)Doc.get(new MAcctSchema[]{as}, + MInvoice.Table_ID, invoice.getC_Invoice_ID(), getTrxName()); + docInvoice.loadDocumentDetails(); + allocationAccounted = docInvoice.createFactCash(as, fact, new BigDecimal(percent)); + log.config("Allocation Accounted=" + allocationAccounted); + + // Cash Based Commitment Release + if (as.isCreateCommitment() && !invoice.isSOTrx()) + { + MInvoiceLine[] lines = invoice.getLines(); + for (int i = 0; i < lines.length; i++) + { + Fact factC = Doc_Order.getCommitmentRelease(as, this, + lines[i].getQtyInvoiced(), lines[i].getC_InvoiceLine_ID(), new BigDecimal(percent)); + if (factC == null) + return null; + m_facts.add(factC); + } + } // Commitment + + return allocationAccounted; + } // createCashBasedAcct + + + /** + * Get Payment (Unallocated Payment or Payment Selection) Acct of Bank Account + * @param as accounting schema + * @param C_Payment_ID payment + * @return acct + */ + private MAccount getPaymentAcct (MAcctSchema as, int C_Payment_ID) + { + setC_BankAccount_ID(0); + // Doc.ACCTTYPE_UnallocatedCash (AR) or C_Prepayment + // or Doc.ACCTTYPE_PaymentSelect (AP) or V_Prepayment + int accountType = Doc.ACCTTYPE_UnallocatedCash; + // + String sql = "SELECT p.C_BankAccount_ID, d.DocBaseType, p.IsReceipt, p.IsPrepayment " + + "FROM C_Payment p INNER JOIN C_DocType d ON (p.C_DocType_ID=d.C_DocType_ID) " + + "WHERE C_Payment_ID=?"; + PreparedStatement pstmt = null; + try + { + pstmt = DB.prepareStatement (sql, getTrxName()); + pstmt.setInt (1, C_Payment_ID); + ResultSet rs = pstmt.executeQuery (); + if (rs.next ()) + { + setC_BankAccount_ID(rs.getInt(1)); + if (DOCTYPE_APPayment.equals(rs.getString(2))) + accountType = Doc.ACCTTYPE_PaymentSelect; + // Prepayment + if ("Y".equals(rs.getString(4))) // Prepayment + { + if ("Y".equals(rs.getString(3))) // Receipt + accountType = Doc.ACCTTYPE_C_Prepayment; + else + accountType = Doc.ACCTTYPE_V_Prepayment; + } + } + rs.close (); + pstmt.close (); + pstmt = null; + } + catch (Exception e) + { + log.log(Level.SEVERE, sql, e); + } + try + { + if (pstmt != null) + pstmt.close (); + pstmt = null; + } + catch (Exception e) + { + pstmt = null; + } + + // + if (getC_BankAccount_ID() <= 0) + { + log.log(Level.SEVERE, "NONE for C_Payment_ID=" + C_Payment_ID); + return null; + } + return getAccount (accountType, as); + } // getPaymentAcct + + /** + * Get Cash (Transfer) Acct of CashBook + * @param as accounting schema + * @param C_CashLine_ID + * @return acct + */ + private MAccount getCashAcct (MAcctSchema as, int C_CashLine_ID) + { + String sql = "SELECT c.C_CashBook_ID " + + "FROM C_Cash c, C_CashLine cl " + + "WHERE c.C_Cash_ID=cl.C_Cash_ID AND cl.C_CashLine_ID=?"; + setC_CashBook_ID(DB.getSQLValue(null, sql, C_CashLine_ID)); + if (getC_CashBook_ID() <= 0) + { + log.log(Level.SEVERE, "NONE for C_CashLine_ID=" + C_CashLine_ID); + return null; + } + return getAccount(Doc.ACCTTYPE_CashTransfer, as); + } // getCashAcct + + + /************************************************************************** + * Create Realized Gain & Loss. + * Compares the Accounted Amount of the Invoice to the + * Accounted Amount of the Allocation + * @param as accounting schema + * @param fact fact + * @param acct account + * @param invoice invoice + * @param allocationSource source amt + * @param allocationAccounted acct amt + * @return Error Message or null if OK + */ + private String createRealizedGainLoss (MAcctSchema as, Fact fact, MAccount acct, + MInvoice invoice, BigDecimal allocationSource, BigDecimal allocationAccounted) + { + BigDecimal invoiceSource = null; + BigDecimal invoiceAccounted = null; + // + String sql = "SELECT " + + (invoice.isSOTrx() + ? "SUM(AmtSourceDr), SUM(AmtAcctDr)" // so + : "SUM(AmtSourceCr), SUM(AmtAcctCr)") // po + + " FROM Fact_Acct " + + "WHERE AD_Table_ID=318 AND Record_ID=?" // Invoice + + " AND C_AcctSchema_ID=?" + + " AND PostingType='A'"; + //AND C_Currency_ID=102 + PreparedStatement pstmt = null; + try + { + pstmt = DB.prepareStatement(sql, getTrxName()); + pstmt.setInt(1, invoice.getC_Invoice_ID()); + pstmt.setInt(2, as.getC_AcctSchema_ID()); + ResultSet rs = pstmt.executeQuery(); + if (rs.next()) + { + invoiceSource = rs.getBigDecimal(1); + invoiceAccounted = rs.getBigDecimal(2); + } + rs.close(); + pstmt.close(); + pstmt = null; + } + catch (Exception e) + { + log.log(Level.SEVERE, sql, e); + } + try + { + if (pstmt != null) + pstmt.close(); + pstmt = null; + } + catch (Exception e) + { + pstmt = null; + } + // Requires that Invoice is Posted + if (invoiceSource == null || invoiceAccounted == null) + return "Gain/Loss - Invoice not posted yet"; + // + String description = "Invoice=(" + invoice.getC_Currency_ID() + ")" + invoiceSource + "/" + invoiceAccounted + + " - Allocation=(" + getC_Currency_ID() + ")" + allocationSource + "/" + allocationAccounted; + log.fine(description); + // Allocation not Invoice Currency + if (getC_Currency_ID() != invoice.getC_Currency_ID()) + { + BigDecimal allocationSourceNew = MConversionRate.convert(getCtx(), + allocationSource, getC_Currency_ID(), + invoice.getC_Currency_ID(), getDateAcct(), + invoice.getC_ConversionType_ID(), invoice.getAD_Client_ID(), invoice.getAD_Org_ID()); + if (allocationSourceNew == null) + return "Gain/Loss - No Conversion from Allocation->Invoice"; + String d2 = "Allocation=(" + getC_Currency_ID() + ")" + allocationSource + + "->(" + invoice.getC_Currency_ID() + ")" + allocationSourceNew; + log.fine(d2); + description += " - " + d2; + allocationSource = allocationSourceNew; + } + + BigDecimal acctDifference = null; // gain is negative + // Full Payment in currency + if (allocationSource.compareTo(invoiceSource) == 0) + { + acctDifference = invoiceAccounted.subtract(allocationAccounted); // gain is negative + String d2 = "(full) = " + acctDifference; + log.fine(d2); + description += " - " + d2; + } + else // partial or MC + { + // percent of total payment + double multiplier = allocationSource.doubleValue() / invoiceSource.doubleValue(); + // Reduce Orig Invoice Accounted + invoiceAccounted = invoiceAccounted.multiply(new BigDecimal(multiplier)); + // Difference based on percentage of Orig Invoice + acctDifference = invoiceAccounted.subtract(allocationAccounted); // gain is negative + // ignore Tolerance + if (acctDifference.abs().compareTo(TOLERANCE) < 0) + acctDifference = Env.ZERO; + // Round + int precision = as.getStdPrecision(); + if (acctDifference.scale() > precision) + acctDifference = acctDifference.setScale(precision, BigDecimal.ROUND_HALF_UP); + String d2 = "(partial) = " + acctDifference + " - Multiplier=" + multiplier; + log.fine(d2); + description += " - " + d2; + } + + if (acctDifference.signum() == 0) + { + log.fine("No Difference"); + return null; + } + + MAccount gain = MAccount.get (as.getCtx(), as.getAcctSchemaDefault().getRealizedGain_Acct()); + MAccount loss = MAccount.get (as.getCtx(), as.getAcctSchemaDefault().getRealizedLoss_Acct()); + // + if (invoice.isSOTrx()) + { + FactLine fl = fact.createLine (null, loss, gain, + as.getC_Currency_ID(), acctDifference); + fl.setDescription(description); + fact.createLine (null, acct, + as.getC_Currency_ID(), acctDifference.negate()); + fl.setDescription(description); + } + else + { + fact.createLine (null, acct, + as.getC_Currency_ID(), acctDifference); + FactLine fl = fact.createLine (null, loss, gain, + as.getC_Currency_ID(), acctDifference.negate()); + } + return null; + } // createRealizedGainLoss + + + /************************************************************************** + * Create Tax Correction. + * Requirement: Adjust the tax amount, if you did not receive the full + * amount of the invoice (payment discount, write-off). + * Applies to many countries with VAT. + * Example: + * Invoice: Net $100 + Tax1 $15 + Tax2 $5 = Total $120 + * Payment: $115 (i.e. $5 underpayment) + * Tax Adjustment = Tax1 = 0.63 (15/120*5) Tax2 = 0.21 (5/120/5) + * + * @param as accounting schema + * @param fact fact + * @param line Allocation line + * @param DiscountAccount discount acct + * @param WriteOffAccoint write off acct + * @return true if created + */ + private boolean createTaxCorrection (MAcctSchema as, Fact fact, + DocLine_Allocation line, + MAccount DiscountAccount, MAccount WriteOffAccoint) + { + log.info (line.toString()); + BigDecimal discount = Env.ZERO; + if (as.isTaxCorrectionDiscount()) + discount = line.getDiscountAmt(); + BigDecimal writeOff = Env.ZERO; + if (as.isTaxCorrectionWriteOff()) + writeOff = line.getWriteOffAmt(); + + Doc_AllocationTax tax = new Doc_AllocationTax ( + DiscountAccount, discount, WriteOffAccoint, writeOff); + + // Get Source Amounts with account + String sql = "SELECT * " + + "FROM Fact_Acct " + + "WHERE AD_Table_ID=318 AND Record_ID=?" // Invoice + + " AND C_AcctSchema_ID=?" + + " AND Line_ID IS NULL"; // header lines like tax or total + PreparedStatement pstmt = null; + try + { + pstmt = DB.prepareStatement(sql, getTrxName()); + pstmt.setInt(1, line.getC_Invoice_ID()); + pstmt.setInt(2, as.getC_AcctSchema_ID()); + ResultSet rs = pstmt.executeQuery(); + while (rs.next()) + tax.addInvoiceFact (new MFactAcct(getCtx(), rs, fact.get_TrxName())); + rs.close(); + pstmt.close(); + pstmt = null; + } + catch (Exception e) + { + log.log(Level.SEVERE, sql, e); + } + try + { + if (pstmt != null) + pstmt.close(); + pstmt = null; + } + catch (Exception e) + { + pstmt = null; + } + // Invoice Not posted + if (tax.getLineCount() == 0) + { + log.warning ("Invoice not posted yet - " + line); + return false; + } + // size = 1 if no tax + if (tax.getLineCount() < 2) + return true; + return tax.createEntries (as, fact, line); + + } // createTaxCorrection + +} // Doc_Allocation + +/** + * Allocation Document Tax Handing + * + * @author Jorg Janke + * @version $Id: Doc_Allocation.java,v 1.6 2006/07/30 00:53:33 jjanke Exp $ + */ +class Doc_AllocationTax +{ + /** + * Allocation Tax Adjustment + * @param DiscountAccount discount acct + * @param DiscountAmt discount amt + * @param WriteOffAccount write off acct + * @param WriteOffAmt write off amt + */ + public Doc_AllocationTax (MAccount DiscountAccount, BigDecimal DiscountAmt, + MAccount WriteOffAccount, BigDecimal WriteOffAmt) + { + m_DiscountAccount = DiscountAccount; + m_DiscountAmt = DiscountAmt; + m_WriteOffAccount = WriteOffAccount; + m_WriteOffAmt = WriteOffAmt; + } // Doc_AllocationTax + + private CLogger log = CLogger.getCLogger(getClass()); + + private MAccount m_DiscountAccount; + private BigDecimal m_DiscountAmt; + private MAccount m_WriteOffAccount; + private BigDecimal m_WriteOffAmt; + + private ArrayList m_facts = new ArrayList(); + private int m_totalIndex = 0; + + /** + * Add Invoice Fact Line + * @param fact fact line + */ + public void addInvoiceFact (MFactAcct fact) + { + m_facts.add(fact); + } // addInvoiceLine + + /** + * Get Line Count + * @return number of lines + */ + public int getLineCount() + { + return m_facts.size(); + } // getLineCount + + /** + * Create Accounting Entries + * @param as account schema + * @param fact fact to add lines + * @param line line + * @return true if created + */ + public boolean createEntries (MAcctSchema as, Fact fact, DocLine line) + { + // get total index (the Receivables/Liabilities line) + BigDecimal total = Env.ZERO; + for (int i = 0; i < m_facts.size(); i++) + { + MFactAcct factAcct = (MFactAcct)m_facts.get(i); + if (factAcct.getAmtSourceDr().compareTo(total) > 0) + { + total = factAcct.getAmtSourceDr(); + m_totalIndex = i; + } + if (factAcct.getAmtSourceCr().compareTo(total) > 0) + { + total = factAcct.getAmtSourceCr(); + m_totalIndex = i; + } + } + + MFactAcct factAcct = (MFactAcct)m_facts.get(m_totalIndex); + log.info ("Total Invoice = " + total + " - " + factAcct); + int precision = as.getStdPrecision(); + for (int i = 0; i < m_facts.size(); i++) + { + // No Tax Line + if (i == m_totalIndex) + continue; + + factAcct = (MFactAcct)m_facts.get(i); + log.info (i + ": " + factAcct); + + // Create Tax Account + MAccount taxAcct = factAcct.getMAccount(); + if (taxAcct == null || taxAcct.get_ID() == 0) + { + log.severe ("Tax Account not found/created"); + return false; + } + + + // Discount Amount + if (m_DiscountAmt.signum() != 0) + { + // Original Tax is DR - need to correct it CR + if (Env.ZERO.compareTo(factAcct.getAmtSourceDr()) != 0) + { + BigDecimal amount = calcAmount(factAcct.getAmtSourceDr(), + total, m_DiscountAmt, precision); + if (amount.signum() != 0) + { + fact.createLine (line, m_DiscountAccount, + as.getC_Currency_ID(), amount, null); + fact.createLine (line, taxAcct, + as.getC_Currency_ID(), null, amount); + } + } + // Original Tax is CR - need to correct it DR + else + { + BigDecimal amount = calcAmount(factAcct.getAmtSourceCr(), + total, m_DiscountAmt, precision); + if (amount.signum() != 0) + { + fact.createLine (line, taxAcct, + as.getC_Currency_ID(), amount, null); + fact.createLine (line, m_DiscountAccount, + as.getC_Currency_ID(), null, amount); + } + } + } // Discount + + // WriteOff Amount + if (m_WriteOffAmt.signum() != 0) + { + // Original Tax is DR - need to correct it CR + if (Env.ZERO.compareTo(factAcct.getAmtSourceDr()) != 0) + { + BigDecimal amount = calcAmount(factAcct.getAmtSourceDr(), + total, m_WriteOffAmt, precision); + if (amount.signum() != 0) + { + fact.createLine (line, m_WriteOffAccount, + as.getC_Currency_ID(), amount, null); + fact.createLine (line, taxAcct, + as.getC_Currency_ID(), null, amount); + } + } + // Original Tax is CR - need to correct it DR + else + { + BigDecimal amount = calcAmount(factAcct.getAmtSourceCr(), + total, m_WriteOffAmt, precision); + if (amount.signum() != 0) + { + fact.createLine (line, taxAcct, + as.getC_Currency_ID(), amount, null); + fact.createLine (line, m_WriteOffAccount, + as.getC_Currency_ID(), null, amount); + } + } + } // WriteOff + + } // for all lines + return true; + } // createEntries + + /** + * Calc Amount tax / (total-tax) * amt + * @param tax tax + * @param total total + * @param amt reduction amt + * @param precision precision + * @return tax / total * amt + */ + private BigDecimal calcAmount (BigDecimal tax, BigDecimal total, BigDecimal amt, int precision) + { + log.fine("Amt=" + amt + " - Total=" + total + ", Tax=" + tax); + if (tax.signum() == 0 + || total.signum() == 0 + || amt.signum() == 0) + return Env.ZERO; + // + BigDecimal devisor = total.subtract(tax); + BigDecimal multiplier = tax.divide(devisor, 10, BigDecimal.ROUND_HALF_UP); + BigDecimal retValue = multiplier.multiply(amt); + if (retValue.scale() > precision) + retValue = retValue.setScale(precision, BigDecimal.ROUND_HALF_UP); + log.fine(retValue + " (Mult=" + multiplier + "(Prec=" + precision + ")"); + return retValue; + } // calcAmount + +} // Doc_AllocationTax diff --git a/serverRoot/src/main/server/org/compiere/acct/Doc_Bank.java b/serverRoot/src/main/server/org/compiere/acct/Doc_Bank.java new file mode 100644 index 0000000000..ee67d3b554 --- /dev/null +++ b/serverRoot/src/main/server/org/compiere/acct/Doc_Bank.java @@ -0,0 +1,223 @@ +/****************************************************************************** + * 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.acct; + +import java.math.*; +import java.sql.*; +import java.util.*; + +import org.compiere.model.*; +import org.compiere.util.*; + +/** + * Post Invoice Documents. + *
+ *  Table:              C_BankStatement (392)
+ *  Document Types:     CMB
+ *  
+ * @author Jorg Janke + * @version $Id: Doc_Bank.java,v 1.3 2006/07/30 00:53:33 jjanke Exp $ + */ +public class Doc_Bank extends Doc +{ + /** + * Constructor + * @param ass accounting schemata + * @param rs record + * @param trxName trx + */ + protected Doc_Bank (MAcctSchema[] ass, ResultSet rs, String trxName) + { + super (ass, MBankStatement.class, rs, DOCTYPE_BankStatement, trxName); + } // Doc_Bank + + /** Bank Account */ + private int m_C_BankAccount_ID = 0; + + /** + * Load Specific Document Details + * @return error message or null + */ + protected String loadDocumentDetails () + { + MBankStatement bs = (MBankStatement)getPO(); + setDateDoc(bs.getStatementDate()); + setDateAcct(bs.getStatementDate()); // Overwritten on Line Level + + m_C_BankAccount_ID = bs.getC_BankAccount_ID(); + // Amounts + setAmount(AMTTYPE_Gross, bs.getStatementDifference()); + + // Set Bank Account Info (Currency) + MBankAccount ba = MBankAccount.get (getCtx(), m_C_BankAccount_ID); + setC_Currency_ID (ba.getC_Currency_ID()); + + // Contained Objects + p_lines = loadLines(bs); + log.fine("Lines=" + p_lines.length); + return null; + } // loadDocumentDetails + + /** + * Load Invoice Line. + * @param bs bank statement + * 4 amounts + * AMTTYPE_Payment + * AMTTYPE_Statement2 + * AMTTYPE_Charge + * AMTTYPE_Interest + * @return DocLine Array + */ + private DocLine[] loadLines(MBankStatement bs) + { + ArrayList list = new ArrayList(); + MBankStatementLine[] lines = bs.getLines(false); + for (int i = 0; i < lines.length; i++) + { + MBankStatementLine line = lines[i]; + DocLine_Bank docLine = new DocLine_Bank(line, this); + // Set Date Acct + if (i == 0) + setDateAcct(line.getDateAcct()); + MPeriod period = MPeriod.get(getCtx(), line.getDateAcct()); + if (period != null && period.isOpen(DOCTYPE_BankStatement)) + docLine.setC_Period_ID(period.getC_Period_ID()); + // + list.add(docLine); + } + + // Return Array + DocLine[] dls = new DocLine[list.size()]; + list.toArray(dls); + return dls; + } // loadLines + + + /************************************************************************** + * Get Source Currency Balance - subtracts line amounts from total - no rounding + * @return positive amount, if total invoice is bigger than lines + */ + public BigDecimal getBalance() + { + BigDecimal retValue = Env.ZERO; + StringBuffer sb = new StringBuffer (" ["); + // Total + retValue = retValue.add(getAmount(Doc.AMTTYPE_Gross)); + sb.append(getAmount(Doc.AMTTYPE_Gross)); + // - Lines + for (int i = 0; i < p_lines.length; i++) + { + BigDecimal lineBalance = ((DocLine_Bank)p_lines[i]).getStmtAmt(); + retValue = retValue.subtract(lineBalance); + sb.append("-").append(lineBalance); + } + sb.append("]"); + // + log.fine(toString() + " Balance=" + retValue + sb.toString()); + return retValue; + } // getBalance + + /** + * Create Facts (the accounting logic) for + * CMB. + *
+	 *      BankAsset       DR      CR  (Statement)
+	 *      BankInTransit   DR      CR              (Payment)
+	 *      Charge          DR          (Charge)
+	 *      Interest        DR      CR  (Interest)
+	 *  
+ * @param as accounting schema + * @return Fact + */ + public ArrayList createFacts (MAcctSchema as) + { + // create Fact Header + Fact fact = new Fact(this, as, Fact.POST_Actual); + + // Header -- there may be different currency amounts + + FactLine fl = null; + int AD_Org_ID = getBank_Org_ID(); // Bank Account Org + // Lines + for (int i = 0; i < p_lines.length; i++) + { + DocLine_Bank line = (DocLine_Bank)p_lines[i]; + int C_BPartner_ID = line.getC_BPartner_ID(); + + // BankAsset DR CR (Statement) + fl = fact.createLine(line, + getAccount(Doc.ACCTTYPE_BankAsset, as), + line.getC_Currency_ID(), line.getStmtAmt()); + if (fl != null && AD_Org_ID != 0) + fl.setAD_Org_ID(AD_Org_ID); + if (fl != null && C_BPartner_ID != 0) + fl.setC_BPartner_ID(C_BPartner_ID); + + // BankInTransit DR CR (Payment) + fl = fact.createLine(line, + getAccount(Doc.ACCTTYPE_BankInTransit, as), + line.getC_Currency_ID(), line.getTrxAmt().negate()); + if (fl != null) + { + if (C_BPartner_ID != 0) + fl.setC_BPartner_ID(C_BPartner_ID); + if (AD_Org_ID != 0) + fl.setAD_Org_ID(AD_Org_ID); + else + fl.setAD_Org_ID(line.getAD_Org_ID(true)); // from payment + } + // Charge DR (Charge) + fl = fact.createLine(line, + line.getChargeAccount(as, line.getChargeAmt().negate()), + line.getC_Currency_ID(), line.getChargeAmt().negate(), null); + if (fl != null && C_BPartner_ID != 0) + fl.setC_BPartner_ID(C_BPartner_ID); + + // Interest DR CR (Interest) + if (line.getInterestAmt().signum() < 0) + fl = fact.createLine(line, + getAccount(Doc.ACCTTYPE_InterestExp, as), getAccount(Doc.ACCTTYPE_InterestExp, as), + line.getC_Currency_ID(), line.getInterestAmt().negate()); + else + fl = fact.createLine(line, + getAccount(Doc.ACCTTYPE_InterestRev, as), getAccount(Doc.ACCTTYPE_InterestRev, as), + line.getC_Currency_ID(), line.getInterestAmt().negate()); + if (fl != null && C_BPartner_ID != 0) + fl.setC_BPartner_ID(C_BPartner_ID); + // + // fact.createTaxCorrection(); + } + // + ArrayList facts = new ArrayList(); + facts.add(fact); + return facts; + } // createFact + + /** + * Get AD_Org_ID from Bank Account + * @return AD_Org_ID or 0 + */ + private int getBank_Org_ID () + { + if (m_C_BankAccount_ID == 0) + return 0; + // + MBankAccount ba = MBankAccount.get(getCtx(), m_C_BankAccount_ID); + return ba.getAD_Org_ID(); + } // getBank_Org_ID + +} // Doc_Bank diff --git a/serverRoot/src/main/server/org/compiere/acct/Doc_Cash.java b/serverRoot/src/main/server/org/compiere/acct/Doc_Cash.java new file mode 100644 index 0000000000..568031b9e7 --- /dev/null +++ b/serverRoot/src/main/server/org/compiere/acct/Doc_Cash.java @@ -0,0 +1,254 @@ +/****************************************************************************** + * 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.acct; + +import java.math.*; +import java.sql.*; +import java.util.*; + +import org.compiere.model.*; +import java.util.logging.*; +import org.compiere.util.*; + +/** + * Post Invoice Documents. + *
+ *  Table:              C_Cash (407)
+ *  Document Types:     CMC
+ *  
+ * @author Jorg Janke + * @version $Id: Doc_Cash.java,v 1.3 2006/07/30 00:53:33 jjanke Exp $ + */ +public class Doc_Cash extends Doc +{ + /** + * Constructor + * @param ass accounting schemata + * @param rs record + * @param trxName trx + */ + protected Doc_Cash (MAcctSchema[] ass, ResultSet rs, String trxName) + { + super(ass, MCash.class, rs, DOCTYPE_CashJournal, trxName); + } // Doc_Cash + + /** + * Load Specific Document Details + * @return error message or null + */ + protected String loadDocumentDetails () + { + MCash cash = (MCash)getPO(); + setDateDoc(cash.getStatementDate()); + + // Amounts + setAmount(Doc.AMTTYPE_Gross, cash.getStatementDifference()); + + // Set CashBook Org & Currency + MCashBook cb = MCashBook.get(getCtx(), cash.getC_CashBook_ID()); + setC_CashBook_ID(cb.getC_CashBook_ID()); + setC_Currency_ID(cb.getC_Currency_ID()); + + // Contained Objects + p_lines = loadLines(cash, cb); + log.fine("Lines=" + p_lines.length); + return null; + } // loadDocumentDetails + + + /** + * Load Cash Line + * @param cash journal + * @param cb cash book + * @return DocLine Array + */ + private DocLine[] loadLines(MCash cash, MCashBook cb) + { + ArrayList list = new ArrayList(); + MCashLine[] lines = cash.getLines(false); + for (int i = 0; i < lines.length; i++) + { + MCashLine line = lines[i]; + DocLine_Cash docLine = new DocLine_Cash (line, this); + // + list.add(docLine); + } + + // Return Array + DocLine[] dls = new DocLine[list.size()]; + list.toArray(dls); + return dls; + } // loadLines + + + /************************************************************************** + * Get Source Currency Balance - subtracts line amounts from total - no rounding + * @return positive amount, if total invoice is bigger than lines + */ + public BigDecimal getBalance() + { + BigDecimal retValue = Env.ZERO; + StringBuffer sb = new StringBuffer (" ["); + // Total + retValue = retValue.add(getAmount(Doc.AMTTYPE_Gross)); + sb.append(getAmount(Doc.AMTTYPE_Gross)); + // - Lines + for (int i = 0; i < p_lines.length; i++) + { + retValue = retValue.subtract(p_lines[i].getAmtSource()); + sb.append("-").append(p_lines[i].getAmtSource()); + } + sb.append("]"); + // + log.fine(toString() + " Balance=" + retValue + sb.toString()); + // return retValue; + return Env.ZERO; // Lines are balanced + } // getBalance + + /** + * Create Facts (the accounting logic) for + * CMC. + *
+	 *  Expense
+	 *          CashExpense     DR
+	 *          CashAsset               CR
+	 *  Receipt
+	 *          CashAsset       DR
+	 *          CashReceipt             CR
+	 *  Charge
+	 *          Charge          DR
+	 *          CashAsset               CR
+	 *  Difference
+	 *          CashDifference  DR
+	 *          CashAsset               CR
+	 *  Invoice
+	 *          CashAsset       DR
+	 *          CashTransfer            CR
+	 *  Transfer
+	 *          BankInTransit   DR
+	 *          CashAsset               CR
+	 *  
+ * @param as account schema + * @return Fact + */ + public ArrayList createFacts (MAcctSchema as) + { + // Need to have CashBook + if (getC_CashBook_ID() == 0) + { + p_Error = "C_CashBook_ID not set"; + log.log(Level.SEVERE, p_Error); + return null; + } + + // create Fact Header + Fact fact = new Fact(this, as, Fact.POST_Actual); + + // Header posting amt as Invoices and Transfer could be differenet currency + // CashAsset Total + BigDecimal assetAmt = Env.ZERO; + + // Lines + for (int i = 0; i < p_lines.length; i++) + { + DocLine_Cash line = (DocLine_Cash)p_lines[i]; + String CashType = line.getCashType(); + + if (CashType.equals(DocLine_Cash.CASHTYPE_EXPENSE)) + { // amount is negative + // CashExpense DR + // CashAsset CR + fact.createLine(line, getAccount(Doc.ACCTTYPE_CashExpense, as), + getC_Currency_ID(), line.getAmount().negate(), null); + // fact.createLine(line, getAccount(Doc.ACCTTYPE_CashAsset, as), + // p_vo.C_Currency_ID, null, line.getAmount().negate()); + assetAmt = assetAmt.subtract(line.getAmount().negate()); + } + else if (CashType.equals(DocLine_Cash.CASHTYPE_RECEIPT)) + { // amount is positive + // CashAsset DR + // CashReceipt CR + // fact.createLine(line, getAccount(Doc.ACCTTYPE_CashAsset, as), + // p_vo.C_Currency_ID, line.getAmount(), null); + assetAmt = assetAmt.add(line.getAmount()); + fact.createLine(line, getAccount(Doc.ACCTTYPE_CashReceipt, as), + getC_Currency_ID(), null, line.getAmount()); + } + else if (CashType.equals(DocLine_Cash.CASHTYPE_CHARGE)) + { // amount is negative + // Charge DR + // CashAsset CR + fact.createLine(line, line.getChargeAccount(as, getAmount()), + getC_Currency_ID(), line.getAmount().negate(), null); + // fact.createLine(line, getAccount(Doc.ACCTTYPE_CashAsset, as), + // p_vo.C_Currency_ID, null, line.getAmount().negate()); + assetAmt = assetAmt.subtract(line.getAmount().negate()); + } + else if (CashType.equals(DocLine_Cash.CASHTYPE_DIFFERENCE)) + { // amount is pos/neg + // CashDifference DR + // CashAsset CR + fact.createLine(line, getAccount(Doc.ACCTTYPE_CashDifference, as), + getC_Currency_ID(), line.getAmount().negate()); + // fact.createLine(line, getAccount(Doc.ACCTTYPE_CashAsset, as), + // p_vo.C_Currency_ID, line.getAmount()); + assetAmt = assetAmt.add(line.getAmount()); + } + else if (CashType.equals(DocLine_Cash.CASHTYPE_INVOICE)) + { // amount is pos/neg + // CashAsset DR dr -- Invoice is in Invoice Currency ! + // CashTransfer cr CR + if (line.getC_Currency_ID() == getC_Currency_ID()) + assetAmt = assetAmt.add (line.getAmount()); + else + fact.createLine(line, + getAccount(Doc.ACCTTYPE_CashAsset, as), + line.getC_Currency_ID(), line.getAmount()); + fact.createLine(line, + getAccount(Doc.ACCTTYPE_CashTransfer, as), + line.getC_Currency_ID(), line.getAmount().negate()); + } + else if (CashType.equals(DocLine_Cash.CASHTYPE_TRANSFER)) + { // amount is pos/neg + // BankInTransit DR dr -- Transfer is in Bank Account Currency + // CashAsset dr CR + int temp = getC_BankAccount_ID(); + setC_BankAccount_ID (line.getC_BankAccount_ID()); + fact.createLine(line, + getAccount(Doc.ACCTTYPE_BankInTransit, as), + line.getC_Currency_ID(), line.getAmount().negate()); + setC_BankAccount_ID(temp); + if (line.getC_Currency_ID() == getC_Currency_ID()) + assetAmt = assetAmt.add (line.getAmount()); + else + fact.createLine(line, + getAccount(Doc.ACCTTYPE_CashAsset, as), + line.getC_Currency_ID(), line.getAmount()); + } + } // lines + + // Cash Asset + fact.createLine(null, getAccount(Doc.ACCTTYPE_CashAsset, as), + getC_Currency_ID(), assetAmt); + + // + ArrayList facts = new ArrayList(); + facts.add(fact); + return facts; + } // createFact + +} // Doc_Cash diff --git a/serverRoot/src/main/server/org/compiere/acct/Doc_GLJournal.java b/serverRoot/src/main/server/org/compiere/acct/Doc_GLJournal.java new file mode 100644 index 0000000000..d7fa2da090 --- /dev/null +++ b/serverRoot/src/main/server/org/compiere/acct/Doc_GLJournal.java @@ -0,0 +1,168 @@ +/****************************************************************************** + * 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.acct; + +import java.math.*; +import java.sql.*; +import java.util.*; + +import org.compiere.model.*; +import java.util.logging.*; +import org.compiere.util.*; + +/** + * Post Invoice Documents. + *
+ *  Table:              GL_Journal (224)
+ *  Document Types:     GLJ
+ *  
+ * @author Jorg Janke + * @version $Id: Doc_GLJournal.java,v 1.3 2006/07/30 00:53:33 jjanke Exp $ + */ +public class Doc_GLJournal extends Doc +{ + /** + * Constructor + * @param ass accounting schemata + * @param rs record + * @param trxName trx + */ + protected Doc_GLJournal (MAcctSchema[] ass, ResultSet rs, String trxName) + { + super(ass, MJournal.class, rs, null, trxName); + } // Foc_GL_Journal + + /** Posting Type */ + private String m_PostingType = null; + private int m_C_AcctSchema_ID = 0; + + /** + * Load Specific Document Details + * @return error message or null + */ + protected String loadDocumentDetails () + { + MJournal journal = (MJournal)getPO(); + m_PostingType = journal.getPostingType(); + m_C_AcctSchema_ID = journal.getC_AcctSchema_ID(); + + // Contained Objects + p_lines = loadLines(journal); + log.fine("Lines=" + p_lines.length); + return null; + } // loadDocumentDetails + + + /** + * Load Invoice Line + * @param journal journal + * @return DocLine Array + */ + private DocLine[] loadLines(MJournal journal) + { + ArrayList list = new ArrayList(); + MJournalLine[] lines = journal.getLines(false); + for (int i = 0; i < lines.length; i++) + { + MJournalLine line = lines[i]; + DocLine docLine = new DocLine (line, this); + // -- Source Amounts + docLine.setAmount (line.getAmtSourceDr(), line.getAmtSourceCr()); + // -- Converted Amounts + docLine.setConvertedAmt (m_C_AcctSchema_ID, line.getAmtAcctDr(), line.getAmtAcctCr()); + // -- Account + MAccount account = line.getAccount(); + docLine.setAccount (account); + // -- Organization of Line was set to Org of Account + list.add(docLine); + } + // Return Array + int size = list.size(); + DocLine[] dls = new DocLine[size]; + list.toArray(dls); + return dls; + } // loadLines + + + /************************************************************************** + * Get Source Currency Balance - subtracts line and tax amounts from total - no rounding + * @return positive amount, if total invoice is bigger than lines + */ + public BigDecimal getBalance() + { + BigDecimal retValue = Env.ZERO; + StringBuffer sb = new StringBuffer (" ["); + // Lines + for (int i = 0; i < p_lines.length; i++) + { + retValue = retValue.add(p_lines[i].getAmtSource()); + sb.append("+").append(p_lines[i].getAmtSource()); + } + sb.append("]"); + // + log.fine(toString() + " Balance=" + retValue + sb.toString()); + return retValue; + } // getBalance + + /** + * Create Facts (the accounting logic) for + * GLJ. + * (only for the accounting scheme, it was created) + *
+	 *      account     DR          CR
+	 *  
+ * @param as acct schema + * @return Fact + */ + public ArrayList createFacts (MAcctSchema as) + { + ArrayList facts = new ArrayList(); + // Other Acct Schema + if (as.getC_AcctSchema_ID() != m_C_AcctSchema_ID) + return facts; + + // create Fact Header + Fact fact = new Fact (this, as, m_PostingType); + + // GLJ + if (getDocumentType().equals(DOCTYPE_GLJournal)) + { + // account DR CR + for (int i = 0; i < p_lines.length; i++) + { + if (p_lines[i].getC_AcctSchema_ID () == as.getC_AcctSchema_ID ()) + { + FactLine line = fact.createLine (p_lines[i], + p_lines[i].getAccount (), + getC_Currency_ID(), + p_lines[i].getAmtSourceDr (), + p_lines[i].getAmtSourceCr ()); + } + } // for all lines + } + else + { + p_Error = "DocumentType unknown: " + getDocumentType(); + log.log(Level.SEVERE, p_Error); + fact = null; + } + // + facts.add(fact); + return facts; + } // createFact + +} // Doc_GLJournal diff --git a/serverRoot/src/main/server/org/compiere/acct/Doc_InOut.java b/serverRoot/src/main/server/org/compiere/acct/Doc_InOut.java new file mode 100644 index 0000000000..4ca4a7b57a --- /dev/null +++ b/serverRoot/src/main/server/org/compiere/acct/Doc_InOut.java @@ -0,0 +1,283 @@ +/****************************************************************************** + * 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.acct; + +import java.math.*; +import java.sql.*; +import java.util.*; + +import org.compiere.model.*; +import java.util.logging.*; +import org.compiere.util.*; + +/** + * Post Shipment/Receipt Documents. + *
+ *  Table:              M_InOut (319)
+ *  Document Types:     MMS, MMR
+ *  
+ * @author Jorg Janke + * @version $Id: Doc_InOut.java,v 1.3 2006/07/30 00:53:33 jjanke Exp $ + */ +public class Doc_InOut extends Doc +{ + /** + * Constructor + * @param ass accounting schemata + * @param rs record + * @param trxName trx + */ + public Doc_InOut (MAcctSchema[] ass, ResultSet rs, String trxName) + { + super (ass, MInOut.class, rs, null, trxName); + } // DocInOut + + /** + * Load Document Details + * @return error message or null + */ + protected String loadDocumentDetails() + { + setC_Currency_ID(NO_CURRENCY); + MInOut inout = (MInOut)getPO(); + setDateDoc (inout.getMovementDate()); + // Contained Objects + p_lines = loadLines(inout); + log.fine("Lines=" + p_lines.length); + return null; + } // loadDocumentDetails + + /** + * Load Invoice Line + * @param inout shipment/receipt + * @return DocLine Array + */ + private DocLine[] loadLines(MInOut inout) + { + ArrayList list = new ArrayList(); + MInOutLine[] lines = inout.getLines(false); + for (int i = 0; i < lines.length; i++) + { + MInOutLine line = lines[i]; + if (line.isDescription() + || line.getM_Product_ID() == 0 + || line.getMovementQty().signum() == 0) + { + log.finer("Ignored: " + line); + continue; + } + + DocLine docLine = new DocLine (line, this); + BigDecimal Qty = line.getMovementQty(); + docLine.setQty (Qty, getDocumentType().equals(DOCTYPE_MatShipment)); // sets Trx and Storage Qty + // + log.fine(docLine.toString()); + list.add (docLine); + } + + // Return Array + DocLine[] dls = new DocLine[list.size()]; + list.toArray(dls); + return dls; + } // loadLines + + /** + * Get Balance + * @return Zero (always balanced) + */ + public BigDecimal getBalance() + { + BigDecimal retValue = Env.ZERO; + return retValue; + } // getBalance + + /** + * Create Facts (the accounting logic) for + * MMS, MMR. + *
+	 *  Shipment
+	 *      CoGS (RevOrg)   DR
+	 *      Inventory               CR
+	 *  Shipment of Project Issue
+	 *      CoGS            DR
+	 *      Project                 CR
+	 *  Receipt
+	 *      Inventory       DR
+	 *      NotInvoicedReceipt      CR
+	 *  
+ * @param as accounting schema + * @return Fact + */ + public ArrayList createFacts (MAcctSchema as) + { + // create Fact Header + Fact fact = new Fact(this, as, Fact.POST_Actual); + setC_Currency_ID (as.getC_Currency_ID()); + + // Line pointers + FactLine dr = null; + FactLine cr = null; + + // *** Sales - Shipment + if (getDocumentType().equals(DOCTYPE_MatShipment)) + { + for (int i = 0; i < p_lines.length; i++) + { + DocLine line = p_lines[i]; + BigDecimal costs = line.getProductCosts(as, line.getAD_Org_ID(), true); + if (costs == null || costs.signum() == 0) // zero costs OK + { + MProduct product = line.getProduct(); + if (product.isStocked()) + { + p_Error = "No Costs for " + line.getProduct().getName(); + log.log(Level.WARNING, p_Error); + return null; + } + else // ignore service + continue; + } + // CoGS DR + dr = fact.createLine(line, + line.getAccount(ProductCost.ACCTTYPE_P_Cogs, as), + as.getC_Currency_ID(), costs, null); + if (dr == null) + { + p_Error = "FactLine DR not created: " + line; + log.log(Level.WARNING, p_Error); + return null; + } + dr.setM_Locator_ID(line.getM_Locator_ID()); + dr.setLocationFromLocator(line.getM_Locator_ID(), true); // from Loc + dr.setLocationFromBPartner(getC_BPartner_Location_ID(), false); // to Loc + dr.setAD_Org_ID(line.getOrder_Org_ID()); // Revenue X-Org + dr.setQty(line.getQty().negate()); + + // Inventory CR + cr = fact.createLine(line, + line.getAccount(ProductCost.ACCTTYPE_P_Asset, as), + as.getC_Currency_ID(), null, costs); + if (cr == null) + { + p_Error = "FactLine CR not created: " + line; + log.log(Level.WARNING, p_Error); + return null; + } + cr.setM_Locator_ID(line.getM_Locator_ID()); + cr.setLocationFromLocator(line.getM_Locator_ID(), true); // from Loc + cr.setLocationFromBPartner(getC_BPartner_Location_ID(), false); // to Loc + // + if (line.getM_Product_ID() != 0) + { + MCostDetail.createShipment(as, line.getAD_Org_ID(), + line.getM_Product_ID(), line.getM_AttributeSetInstance_ID(), + line.get_ID(), 0, + costs, line.getQty(), + line.getDescription(), true, getTrxName()); + } + } // for all lines + updateProductInfo(as.getC_AcctSchema_ID()); // only for SO! + } // Shipment + + // *** Purchasing - Receipt + else if (getDocumentType().equals(DOCTYPE_MatReceipt)) + { + for (int i = 0; i < p_lines.length; i++) + { + DocLine line = p_lines[i]; + BigDecimal costs = line.getProductCosts(as, line.getAD_Org_ID(), false); // non-zero costs + MProduct product = line.getProduct(); + if (costs == null || costs.signum() == 0) + { + p_Error = "Resubmit - No Costs for " + product.getName(); + log.log(Level.WARNING, p_Error); + return null; + } + // Inventory/Asset DR + MAccount assets = line.getAccount(ProductCost.ACCTTYPE_P_Asset, as); + if (product.isService()) + assets = line.getAccount(ProductCost.ACCTTYPE_P_Expense, as); + dr = fact.createLine(line, assets, + as.getC_Currency_ID(), costs, null); + if (dr == null) + { + p_Error = "DR not created: " + line; + log.log(Level.WARNING, p_Error); + return null; + } + dr.setM_Locator_ID(line.getM_Locator_ID()); + dr.setLocationFromBPartner(getC_BPartner_Location_ID(), true); // from Loc + dr.setLocationFromLocator(line.getM_Locator_ID(), false); // to Loc + // NotInvoicedReceipt CR + cr = fact.createLine(line, + getAccount(Doc.ACCTTYPE_NotInvoicedReceipts, as), + as.getC_Currency_ID(), null, costs); + if (cr == null) + { + p_Error = "CR not created: " + line; + log.log(Level.WARNING, p_Error); + return null; + } + cr.setM_Locator_ID(line.getM_Locator_ID()); + cr.setLocationFromBPartner(getC_BPartner_Location_ID(), true); // from Loc + cr.setLocationFromLocator(line.getM_Locator_ID(), false); // to Loc + cr.setQty(line.getQty().negate()); + } + } // Receipt + else + { + p_Error = "DocumentType unknown: " + getDocumentType(); + log.log(Level.SEVERE, p_Error); + return null; + } + // + ArrayList facts = new ArrayList(); + facts.add(fact); + return facts; + } // createFact + + + /** + * Update Sales Order Costing Product Info (old). + * Purchase side handeled in Invoice Matching. + *
+ * decrease average cumulatives + * @param C_AcctSchema_ID accounting schema + * @deprecated old costing + */ + private void updateProductInfo (int C_AcctSchema_ID) + { + log.fine("M_InOut_ID=" + get_ID()); + // Old Model + StringBuffer sql = new StringBuffer( + "UPDATE M_Product_Costing pc " + + "SET (CostAverageCumQty, CostAverageCumAmt)=" + + "(SELECT CostAverageCumQty - SUM(il.MovementQty)," + + " CostAverageCumAmt - SUM(il.MovementQty*CurrentCostPrice) " + + "FROM M_InOutLine il " + + "WHERE pc.M_Product_ID=il.M_Product_ID" + + " AND il.M_InOut_ID=").append(get_ID()).append(") ") + .append("WHERE EXISTS (SELECT * " + + "FROM M_InOutLine il " + + "WHERE pc.M_Product_ID=il.M_Product_ID" + + " AND il.M_InOut_ID=").append(get_ID()).append(")"); + int no = DB.executeUpdate(sql.toString(), getTrxName()); + log.fine("M_Product_Costing - Updated=" + no); + // + } // updateProductInfo + +} // Doc_InOut diff --git a/serverRoot/src/main/server/org/compiere/acct/Doc_Inventory.java b/serverRoot/src/main/server/org/compiere/acct/Doc_Inventory.java new file mode 100644 index 0000000000..fc8e902f51 --- /dev/null +++ b/serverRoot/src/main/server/org/compiere/acct/Doc_Inventory.java @@ -0,0 +1,179 @@ +/****************************************************************************** + * 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.acct; + +import java.math.*; +import java.sql.*; +import java.util.*; + +import org.compiere.model.*; +import org.compiere.util.*; + +/** + * Post Inventory Documents. + *
+ *  Table:              M_Inventory (321)
+ *  Document Types:     MMI
+ *  
+ * @author Jorg Janke + * @version $Id: Doc_Inventory.java,v 1.3 2006/07/30 00:53:33 jjanke Exp $ + */ +public class Doc_Inventory extends Doc +{ + /** + * Constructor + * @param ass accounting schemata + * @param rs record + * @param trxName trx + */ + public Doc_Inventory (MAcctSchema[] ass, ResultSet rs, String trxName) + { + super (ass, MInventory.class, rs, DOCTYPE_MatInventory, trxName); + } // Doc_Inventory + + /** + * Load Document Details + * @return error message or null + */ + protected String loadDocumentDetails() + { + setC_Currency_ID (NO_CURRENCY); + MInventory inventory = (MInventory)getPO(); + setDateDoc (inventory.getMovementDate()); + setDateAcct(inventory.getMovementDate()); + // Contained Objects + p_lines = loadLines(inventory); + log.fine("Lines=" + p_lines.length); + return null; + } // loadDocumentDetails + + /** + * Load Invoice Line + * @param inventory inventory + * @return DocLine Array + */ + private DocLine[] loadLines(MInventory inventory) + { + ArrayList list = new ArrayList(); + MInventoryLine[] lines = inventory.getLines(false); + for (int i = 0; i < lines.length; i++) + { + MInventoryLine line = lines[i]; + // nothing to post + if (line.getQtyBook().compareTo(line.getQtyCount()) == 0 + && line.getQtyInternalUse().signum() == 0) + continue; + // + DocLine docLine = new DocLine (line, this); + BigDecimal Qty = line.getQtyInternalUse(); + if (Qty.signum() != 0) + Qty = Qty.negate(); // Internal Use entered positive + else + { + BigDecimal QtyBook = line.getQtyBook(); + BigDecimal QtyCount = line.getQtyCount(); + Qty = QtyCount.subtract(QtyBook); + } + docLine.setQty (Qty, false); // -5 => -5 + // + log.fine(docLine.toString()); + list.add (docLine); + } + + // Return Array + DocLine[] dls = new DocLine[list.size()]; + list.toArray(dls); + return dls; + } // loadLines + + /** + * Get Balance + * @return Zero (always balanced) + */ + public BigDecimal getBalance() + { + BigDecimal retValue = Env.ZERO; + return retValue; + } // getBalance + + /** + * Create Facts (the accounting logic) for + * MMI. + *
+	 *  Inventory
+	 *      Inventory       DR      CR
+	 *      InventoryDiff   DR      CR   (or Charge)
+	 *  
+ * @param as account schema + * @return Fact + */ + public ArrayList createFacts (MAcctSchema as) + { + // create Fact Header + Fact fact = new Fact(this, as, Fact.POST_Actual); + setC_Currency_ID(as.getC_Currency_ID()); + + // Line pointers + FactLine dr = null; + FactLine cr = null; + + for (int i = 0; i < p_lines.length; i++) + { + DocLine line = p_lines[i]; + BigDecimal costs = line.getProductCosts(as, line.getAD_Org_ID(), false); + if (costs == null || costs.signum() == 0) + { + p_Error = "No Costs for " + line.getProduct().getName(); + return null; + } + // Inventory DR CR + dr = fact.createLine(line, + line.getAccount(ProductCost.ACCTTYPE_P_Asset, as), + as.getC_Currency_ID(), costs); + // may be zero difference - no line created. + if (dr == null) + continue; + dr.setM_Locator_ID(line.getM_Locator_ID()); + + // InventoryDiff DR CR + // or Charge + MAccount invDiff = line.getChargeAccount(as, costs.negate()); + if (invDiff == null) + invDiff = getAccount(Doc.ACCTTYPE_InvDifferences, as); + cr = fact.createLine(line, invDiff, + as.getC_Currency_ID(), costs.negate()); + if (cr == null) + continue; + cr.setM_Locator_ID(line.getM_Locator_ID()); + cr.setQty(line.getQty().negate()); + if (line.getC_Charge_ID() != 0) // explicit overwrite for charge + cr.setAD_Org_ID(line.getAD_Org_ID()); + + // Cost Detail + MCostDetail.createInventory(as, line.getAD_Org_ID(), + line.getM_Product_ID(), line.getM_AttributeSetInstance_ID(), + line.get_ID(), 0, + costs, line.getQty(), + line.getDescription(), getTrxName()); + } + // + ArrayList facts = new ArrayList(); + facts.add(fact); + return facts; + } // createFact + +} // Doc_Inventory diff --git a/serverRoot/src/main/server/org/compiere/acct/Doc_Invoice.java b/serverRoot/src/main/server/org/compiere/acct/Doc_Invoice.java new file mode 100644 index 0000000000..d61444dd37 --- /dev/null +++ b/serverRoot/src/main/server/org/compiere/acct/Doc_Invoice.java @@ -0,0 +1,953 @@ +/****************************************************************************** + * 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.acct; + +import java.math.*; +import java.sql.*; +import java.util.*; +import java.util.logging.*; +import org.compiere.model.*; +import org.compiere.util.*; + +/** + * Post Invoice Documents. + *
+ *  Table:              C_Invoice (318)
+ *  Document Types:     ARI, ARC, ARF, API, APC
+ *  
+ * @author Jorg Janke + * @version $Id: Doc_Invoice.java,v 1.2 2006/07/30 00:53:33 jjanke Exp $ + */ +public class Doc_Invoice extends Doc +{ + /** + * Constructor + * @param ass accounting schemata + * @param rs record + * @param trxName trx + */ + protected Doc_Invoice(MAcctSchema[] ass, ResultSet rs, String trxName) + { + super (ass, MInvoice.class, rs, null, trxName); + } // Doc_Invoice + + /** Contained Optional Tax Lines */ + private DocTax[] m_taxes = null; + /** Currency Precision */ + private int m_precision = -1; + /** All lines are Service */ + private boolean m_allLinesService = true; + /** All lines are product item */ + private boolean m_allLinesItem = true; + + /** + * Load Specific Document Details + * @return error message or null + */ + protected String loadDocumentDetails () + { + MInvoice invoice = (MInvoice)getPO(); + setDateDoc(invoice.getDateInvoiced()); + setIsTaxIncluded(invoice.isTaxIncluded()); + // Amounts + setAmount(Doc.AMTTYPE_Gross, invoice.getGrandTotal()); + setAmount(Doc.AMTTYPE_Net, invoice.getTotalLines()); + setAmount(Doc.AMTTYPE_Charge, invoice.getChargeAmt()); + + // Contained Objects + m_taxes = loadTaxes(); + p_lines = loadLines(invoice); + log.fine("Lines=" + p_lines.length + ", Taxes=" + m_taxes.length); + return null; + } // loadDocumentDetails + + /** + * Load Invoice Taxes + * @return DocTax Array + */ + private DocTax[] loadTaxes() + { + ArrayList list = new ArrayList(); + String sql = "SELECT it.C_Tax_ID, t.Name, t.Rate, it.TaxBaseAmt, it.TaxAmt, t.IsSalesTax " + + "FROM C_Tax t, C_InvoiceTax it " + + "WHERE t.C_Tax_ID=it.C_Tax_ID AND it.C_Invoice_ID=?"; + try + { + PreparedStatement pstmt = DB.prepareStatement(sql, getTrxName()); + pstmt.setInt(1, get_ID()); + ResultSet rs = pstmt.executeQuery(); + // + while (rs.next()) + { + int C_Tax_ID = rs.getInt(1); + String name = rs.getString(2); + BigDecimal rate = rs.getBigDecimal(3); + BigDecimal taxBaseAmt = rs.getBigDecimal(4); + BigDecimal amount = rs.getBigDecimal(5); + boolean salesTax = "Y".equals(rs.getString(6)); + // + DocTax taxLine = new DocTax(C_Tax_ID, name, rate, + taxBaseAmt, amount, salesTax); + log.fine(taxLine.toString()); + list.add(taxLine); + } + // + rs.close(); + pstmt.close(); + } + catch (SQLException e) + { + log.log(Level.SEVERE, sql, e); + return null; + } + + // Return Array + DocTax[] tl = new DocTax[list.size()]; + list.toArray(tl); + return tl; + } // loadTaxes + + /** + * Load Invoice Line + * @param invoice invoice + * @return DocLine Array + */ + private DocLine[] loadLines (MInvoice invoice) + { + ArrayList list = new ArrayList(); + // + MInvoiceLine[] lines = invoice.getLines(false); + for (int i = 0; i < lines.length; i++) + { + MInvoiceLine line = lines[i]; + if (line.isDescription()) + continue; + DocLine docLine = new DocLine(line, this); + // Qty + BigDecimal Qty = line.getQtyInvoiced(); + boolean cm = getDocumentType().equals(DOCTYPE_ARCredit) + || getDocumentType().equals(DOCTYPE_APCredit); + docLine.setQty(cm ? Qty.negate() : Qty, invoice.isSOTrx()); + // + BigDecimal LineNetAmt = line.getLineNetAmt(); + BigDecimal PriceList = line.getPriceList(); + int C_Tax_ID = docLine.getC_Tax_ID(); + // Correct included Tax + if (isTaxIncluded() && C_Tax_ID != 0) + { + MTax tax = MTax.get(getCtx(), C_Tax_ID); + if (!tax.isZeroTax()) + { + BigDecimal LineNetAmtTax = tax.calculateTax(LineNetAmt, true, getStdPercision()); + log.fine("LineNetAmt=" + LineNetAmt + " - Tax=" + LineNetAmtTax); + LineNetAmt = LineNetAmt.subtract(LineNetAmtTax); + for (int t = 0; t < m_taxes.length; t++) + { + if (m_taxes[t].getC_Tax_ID() == C_Tax_ID) + { + m_taxes[t].addIncludedTax(LineNetAmtTax); + break; + } + } + BigDecimal PriceListTax = tax.calculateTax(PriceList, true, getStdPercision()); + PriceList = PriceList.subtract(PriceListTax); + } + } // correct included Tax + + docLine.setAmount (LineNetAmt, PriceList, Qty); // qty for discount calc + if (docLine.isItem()) + m_allLinesService = false; + else + m_allLinesItem = false; + // + log.fine(docLine.toString()); + list.add(docLine); + } + + // Convert to Array + DocLine[] dls = new DocLine[list.size()]; + list.toArray(dls); + + // Included Tax - make sure that no difference + if (isTaxIncluded()) + { + for (int i = 0; i < m_taxes.length; i++) + { + if (m_taxes[i].isIncludedTaxDifference()) + { + BigDecimal diff = m_taxes[i].getIncludedTaxDifference(); + for (int j = 0; j < dls.length; j++) + { + if (dls[j].getC_Tax_ID() == m_taxes[i].getC_Tax_ID()) + { + dls[j].setLineNetAmtDifference(diff); + break; + } + } // for all lines + } // tax difference + } // for all taxes + } // Included Tax difference + + // Return Array + return dls; + } // loadLines + + /** + * Get Currency Percision + * @return precision + */ + private int getStdPercision() + { + if (m_precision == -1) + m_precision = MCurrency.getStdPrecision(getCtx(), getC_Currency_ID()); + return m_precision; + } // getPrecision + + + /************************************************************************** + * Get Source Currency Balance - subtracts line and tax amounts from total - no rounding + * @return positive amount, if total invoice is bigger than lines + */ + public BigDecimal getBalance() + { + BigDecimal retValue = Env.ZERO; + StringBuffer sb = new StringBuffer (" ["); + // Total + retValue = retValue.add(getAmount(Doc.AMTTYPE_Gross)); + sb.append(getAmount(Doc.AMTTYPE_Gross)); + // - Header Charge + retValue = retValue.subtract(getAmount(Doc.AMTTYPE_Charge)); + sb.append("-").append(getAmount(Doc.AMTTYPE_Charge)); + // - Tax + for (int i = 0; i < m_taxes.length; i++) + { + retValue = retValue.subtract(m_taxes[i].getAmount()); + sb.append("-").append(m_taxes[i].getAmount()); + } + // - Lines + for (int i = 0; i < p_lines.length; i++) + { + retValue = retValue.subtract(p_lines[i].getAmtSource()); + sb.append("-").append(p_lines[i].getAmtSource()); + } + sb.append("]"); + // + log.fine(toString() + " Balance=" + retValue + sb.toString()); + return retValue; + } // getBalance + + /** + * Create Facts (the accounting logic) for + * ARI, ARC, ARF, API, APC. + *
+	 *  ARI, ARF
+	 *      Receivables     DR
+	 *      Charge                  CR
+	 *      TaxDue                  CR
+	 *      Revenue                 CR
+	 *
+	 *  ARC
+	 *      Receivables             CR
+	 *      Charge          DR
+	 *      TaxDue          DR
+	 *      Revenue         RR
+	 *
+	 *  API
+	 *      Payables                CR
+	 *      Charge          DR
+	 *      TaxCredit       DR
+	 *      Expense         DR
+	 *
+	 *  APC
+	 *      Payables        DR
+	 *      Charge                  CR
+	 *      TaxCredit               CR
+	 *      Expense                 CR
+	 *  
+ * @param as accounting schema + * @return Fact + */ + public ArrayList createFacts (MAcctSchema as) + { + // + ArrayList facts = new ArrayList(); + // create Fact Header + Fact fact = new Fact(this, as, Fact.POST_Actual); + + // Cash based accounting + if (!as.isAccrual()) + return facts; + + // ** ARI, ARF + if (getDocumentType().equals(DOCTYPE_ARInvoice) + || getDocumentType().equals(DOCTYPE_ARProForma)) + { + BigDecimal grossAmt = getAmount(Doc.AMTTYPE_Gross); + BigDecimal serviceAmt = Env.ZERO; + + // Header Charge CR + BigDecimal amt = getAmount(Doc.AMTTYPE_Charge); + if (amt != null && amt.signum() != 0) + fact.createLine(null, getAccount(Doc.ACCTTYPE_Charge, as), + getC_Currency_ID(), null, amt); + // TaxDue CR + for (int i = 0; i < m_taxes.length; i++) + { + amt = m_taxes[i].getAmount(); + if (amt != null && amt.signum() != 0) + { + FactLine tl = fact.createLine(null, m_taxes[i].getAccount(DocTax.ACCTTYPE_TaxDue, as), + getC_Currency_ID(), null, amt); + if (tl != null) + tl.setC_Tax_ID(m_taxes[i].getC_Tax_ID()); + } + } + // Revenue CR + for (int i = 0; i < p_lines.length; i++) + { + amt = p_lines[i].getAmtSource(); + BigDecimal dAmt = null; + if (as.isTradeDiscountPosted()) + { + BigDecimal discount = p_lines[i].getDiscount(); + if (discount != null && discount.signum() != 0) + { + amt = amt.add(discount); + dAmt = discount; + } + } + fact.createLine (p_lines[i], + p_lines[i].getAccount(ProductCost.ACCTTYPE_P_Revenue, as), + getC_Currency_ID(), dAmt, amt); + if (!p_lines[i].isItem()) + { + grossAmt = grossAmt.subtract(amt); + serviceAmt = serviceAmt.add(amt); + } + } + // Set Locations + FactLine[] fLines = fact.getLines(); + for (int i = 0; i < fLines.length; i++) + { + if (fLines[i] != null) + { + fLines[i].setLocationFromOrg(fLines[i].getAD_Org_ID(), true); // from Loc + fLines[i].setLocationFromBPartner(getC_BPartner_Location_ID(), false); // to Loc + } + } + + // Receivables DR + int receivables_ID = getValidCombination_ID(Doc.ACCTTYPE_C_Receivable, as); + int receivablesServices_ID = getValidCombination_ID (Doc.ACCTTYPE_C_Receivable_Services, as); + if (m_allLinesItem || !as.isPostServices() + || receivables_ID == receivablesServices_ID) + { + grossAmt = getAmount(Doc.AMTTYPE_Gross); + serviceAmt = Env.ZERO; + } + else if (m_allLinesService) + { + serviceAmt = getAmount(Doc.AMTTYPE_Gross); + grossAmt = Env.ZERO; + } + if (grossAmt.signum() != 0) + fact.createLine(null, MAccount.get(getCtx(), receivables_ID), + getC_Currency_ID(), grossAmt, null); + if (serviceAmt.signum() != 0) + fact.createLine(null, MAccount.get(getCtx(), receivablesServices_ID), + getC_Currency_ID(), serviceAmt, null); + } + // ARC + else if (getDocumentType().equals(DOCTYPE_ARCredit)) + { + BigDecimal grossAmt = getAmount(Doc.AMTTYPE_Gross); + BigDecimal serviceAmt = Env.ZERO; + + // Header Charge DR + BigDecimal amt = getAmount(Doc.AMTTYPE_Charge); + if (amt != null && amt.signum() != 0) + fact.createLine(null, getAccount(Doc.ACCTTYPE_Charge, as), + getC_Currency_ID(), amt, null); + // TaxDue DR + for (int i = 0; i < m_taxes.length; i++) + { + amt = m_taxes[i].getAmount(); + if (amt != null && amt.signum() != 0) + { + FactLine tl = fact.createLine(null, m_taxes[i].getAccount(DocTax.ACCTTYPE_TaxDue, as), + getC_Currency_ID(), amt, null); + if (tl != null) + tl.setC_Tax_ID(m_taxes[i].getC_Tax_ID()); + } + } + // Revenue CR + for (int i = 0; i < p_lines.length; i++) + { + amt = p_lines[i].getAmtSource(); + BigDecimal dAmt = null; + if (as.isTradeDiscountPosted()) + { + BigDecimal discount = p_lines[i].getDiscount(); + if (discount != null && discount.signum() != 0) + { + amt = amt.add(discount); + dAmt = discount; + } + } + fact.createLine (p_lines[i], + p_lines[i].getAccount (ProductCost.ACCTTYPE_P_Revenue, as), + getC_Currency_ID(), amt, dAmt); + if (!p_lines[i].isItem()) + { + grossAmt = grossAmt.subtract(amt); + serviceAmt = serviceAmt.add(amt); + } + } + // Set Locations + FactLine[] fLines = fact.getLines(); + for (int i = 0; i < fLines.length; i++) + { + if (fLines[i] != null) + { + fLines[i].setLocationFromOrg(fLines[i].getAD_Org_ID(), true); // from Loc + fLines[i].setLocationFromBPartner(getC_BPartner_Location_ID(), false); // to Loc + } + } + // Receivables CR + int receivables_ID = getValidCombination_ID (Doc.ACCTTYPE_C_Receivable, as); + int receivablesServices_ID = getValidCombination_ID (Doc.ACCTTYPE_C_Receivable_Services, as); + if (m_allLinesItem || !as.isPostServices() + || receivables_ID == receivablesServices_ID) + { + grossAmt = getAmount(Doc.AMTTYPE_Gross); + serviceAmt = Env.ZERO; + } + else if (m_allLinesService) + { + serviceAmt = getAmount(Doc.AMTTYPE_Gross); + grossAmt = Env.ZERO; + } + if (grossAmt.signum() != 0) + fact.createLine(null, MAccount.get(getCtx(), receivables_ID), + getC_Currency_ID(), null, grossAmt); + if (serviceAmt.signum() != 0) + fact.createLine(null, MAccount.get(getCtx(), receivablesServices_ID), + getC_Currency_ID(), null, serviceAmt); + } + + // ** API + else if (getDocumentType().equals(DOCTYPE_APInvoice)) + { + BigDecimal grossAmt = getAmount(Doc.AMTTYPE_Gross); + BigDecimal serviceAmt = Env.ZERO; + + // Charge DR + fact.createLine(null, getAccount(Doc.ACCTTYPE_Charge, as), + getC_Currency_ID(), getAmount(Doc.AMTTYPE_Charge), null); + // TaxCredit DR + for (int i = 0; i < m_taxes.length; i++) + { + FactLine tl = fact.createLine(null, m_taxes[i].getAccount(m_taxes[i].getAPTaxType(), as), + getC_Currency_ID(), m_taxes[i].getAmount(), null); + if (tl != null) + tl.setC_Tax_ID(m_taxes[i].getC_Tax_ID()); + } + // Expense DR + for (int i = 0; i < p_lines.length; i++) + { + DocLine line = p_lines[i]; + boolean landedCost = landedCost(as, fact, line, true); + if (landedCost && as.isExplicitCostAdjustment()) + { + fact.createLine (line, line.getAccount(ProductCost.ACCTTYPE_P_Expense, as), + getC_Currency_ID(), line.getAmtSource(), null); + // + FactLine fl = fact.createLine (line, line.getAccount(ProductCost.ACCTTYPE_P_Expense, as), + getC_Currency_ID(), null, line.getAmtSource()); + String desc = line.getDescription(); + if (desc == null) + desc = "100%"; + else + desc += " 100%"; + fl.setDescription(desc); + } + if (!landedCost) + { + MAccount expense = line.getAccount(ProductCost.ACCTTYPE_P_Expense, as); + if (line.isItem()) + expense = line.getAccount (ProductCost.ACCTTYPE_P_InventoryClearing, as); + BigDecimal amt = line.getAmtSource(); + BigDecimal dAmt = null; + if (as.isTradeDiscountPosted() && !line.isItem()) + { + BigDecimal discount = line.getDiscount(); + if (discount != null && discount.signum() != 0) + { + amt = amt.add(discount); + dAmt = discount; + } + } + fact.createLine (line, expense, + getC_Currency_ID(), amt, dAmt); + if (!line.isItem()) + { + grossAmt = grossAmt.subtract(amt); + serviceAmt = serviceAmt.add(amt); + } + // + if (line.getM_Product_ID() != 0 + && line.getProduct().isService()) // otherwise Inv Matching + MCostDetail.createInvoice(as, line.getAD_Org_ID(), + line.getM_Product_ID(), line.getM_AttributeSetInstance_ID(), + line.get_ID(), 0, // No Cost Element + line.getAmtSource(), line.getQty(), + line.getDescription(), getTrxName()); + } + } + // Set Locations + FactLine[] fLines = fact.getLines(); + for (int i = 0; i < fLines.length; i++) + { + if (fLines[i] != null) + { + fLines[i].setLocationFromBPartner(getC_BPartner_Location_ID(), true); // from Loc + fLines[i].setLocationFromOrg(fLines[i].getAD_Org_ID(), false); // to Loc + } + } + + // Liability CR + int payables_ID = getValidCombination_ID (Doc.ACCTTYPE_V_Liability, as); + int payablesServices_ID = getValidCombination_ID (Doc.ACCTTYPE_V_Liability_Services, as); + if (m_allLinesItem || !as.isPostServices() + || payables_ID == payablesServices_ID) + { + grossAmt = getAmount(Doc.AMTTYPE_Gross); + serviceAmt = Env.ZERO; + } + else if (m_allLinesService) + { + serviceAmt = getAmount(Doc.AMTTYPE_Gross); + grossAmt = Env.ZERO; + } + if (grossAmt.signum() != 0) + fact.createLine(null, MAccount.get(getCtx(), payables_ID), + getC_Currency_ID(), null, grossAmt); + if (serviceAmt.signum() != 0) + fact.createLine(null, MAccount.get(getCtx(), payablesServices_ID), + getC_Currency_ID(), null, serviceAmt); + // + updateProductPO(as); // Only API + updateProductInfo (as.getC_AcctSchema_ID()); // only API + } + // APC + else if (getDocumentType().equals(DOCTYPE_APCredit)) + { + BigDecimal grossAmt = getAmount(Doc.AMTTYPE_Gross); + BigDecimal serviceAmt = Env.ZERO; + // Charge CR + fact.createLine (null, getAccount(Doc.ACCTTYPE_Charge, as), + getC_Currency_ID(), null, getAmount(Doc.AMTTYPE_Charge)); + // TaxCredit CR + for (int i = 0; i < m_taxes.length; i++) + { + FactLine tl = fact.createLine (null, m_taxes[i].getAccount(m_taxes[i].getAPTaxType(), as), + getC_Currency_ID(), null, m_taxes[i].getAmount()); + if (tl != null) + tl.setC_Tax_ID(m_taxes[i].getC_Tax_ID()); + } + // Expense CR + for (int i = 0; i < p_lines.length; i++) + { + DocLine line = p_lines[i]; + boolean landedCost = landedCost(as, fact, line, false); + if (landedCost && as.isExplicitCostAdjustment()) + { + fact.createLine (line, line.getAccount(ProductCost.ACCTTYPE_P_Expense, as), + getC_Currency_ID(), null, line.getAmtSource()); + // + FactLine fl = fact.createLine (line, line.getAccount(ProductCost.ACCTTYPE_P_Expense, as), + getC_Currency_ID(), line.getAmtSource(), null); + String desc = line.getDescription(); + if (desc == null) + desc = "100%"; + else + desc += " 100%"; + fl.setDescription(desc); + } + if (!landedCost) + { + MAccount expense = line.getAccount(ProductCost.ACCTTYPE_P_Expense, as); + if (line.isItem()) + expense = line.getAccount (ProductCost.ACCTTYPE_P_InventoryClearing, as); + BigDecimal amt = line.getAmtSource(); + BigDecimal dAmt = null; + if (as.isTradeDiscountPosted() && !line.isItem()) + { + BigDecimal discount = line.getDiscount(); + if (discount != null && discount.signum() != 0) + { + amt = amt.add(discount); + dAmt = discount; + } + } + fact.createLine (line, expense, + getC_Currency_ID(), dAmt, amt); + if (!line.isItem()) + { + grossAmt = grossAmt.subtract(amt); + serviceAmt = serviceAmt.add(amt); + } + // + if (line.getM_Product_ID() != 0 + && line.getProduct().isService()) // otherwise Inv Matching + MCostDetail.createInvoice(as, line.getAD_Org_ID(), + line.getM_Product_ID(), line.getM_AttributeSetInstance_ID(), + line.get_ID(), 0, // No Cost Element + line.getAmtSource().negate(), line.getQty(), + line.getDescription(), getTrxName()); + } + } + // Set Locations + FactLine[] fLines = fact.getLines(); + for (int i = 0; i < fLines.length; i++) + { + if (fLines[i] != null) + { + fLines[i].setLocationFromBPartner(getC_BPartner_Location_ID(), true); // from Loc + fLines[i].setLocationFromOrg(fLines[i].getAD_Org_ID(), false); // to Loc + } + } + // Liability DR + int payables_ID = getValidCombination_ID (Doc.ACCTTYPE_V_Liability, as); + int payablesServices_ID = getValidCombination_ID (Doc.ACCTTYPE_V_Liability_Services, as); + if (m_allLinesItem || !as.isPostServices() + || payables_ID == payablesServices_ID) + { + grossAmt = getAmount(Doc.AMTTYPE_Gross); + serviceAmt = Env.ZERO; + } + else if (m_allLinesService) + { + serviceAmt = getAmount(Doc.AMTTYPE_Gross); + grossAmt = Env.ZERO; + } + if (grossAmt.signum() != 0) + fact.createLine(null, MAccount.get(getCtx(), payables_ID), + getC_Currency_ID(), grossAmt, null); + if (serviceAmt.signum() != 0) + fact.createLine(null, MAccount.get(getCtx(), payablesServices_ID), + getC_Currency_ID(), serviceAmt, null); + } + else + { + p_Error = "DocumentType unknown: " + getDocumentType(); + log.log(Level.SEVERE, p_Error); + fact = null; + } + // + facts.add(fact); + return facts; + } // createFact + + /** + * Create Fact Cash Based (i.e. only revenue/expense) + * @param as accounting schema + * @param fact fact to add lines to + * @param multiplier source amount multiplier + * @return accounted amount + */ + public BigDecimal createFactCash (MAcctSchema as, Fact fact, BigDecimal multiplier) + { + boolean creditMemo = getDocumentType().equals(DOCTYPE_ARCredit) + || getDocumentType().equals(DOCTYPE_APCredit); + boolean payables = getDocumentType().equals(DOCTYPE_APInvoice) + || getDocumentType().equals(DOCTYPE_APCredit); + BigDecimal acctAmt = Env.ZERO; + FactLine fl = null; + // Revenue/Cost + for (int i = 0; i < p_lines.length; i++) + { + DocLine line = p_lines[i]; + boolean landedCost = false; + if (payables) + landedCost = landedCost(as, fact, line, false); + if (landedCost && as.isExplicitCostAdjustment()) + { + fact.createLine (line, line.getAccount(ProductCost.ACCTTYPE_P_Expense, as), + getC_Currency_ID(), null, line.getAmtSource()); + // + fl = fact.createLine (line, line.getAccount(ProductCost.ACCTTYPE_P_Expense, as), + getC_Currency_ID(), line.getAmtSource(), null); + String desc = line.getDescription(); + if (desc == null) + desc = "100%"; + else + desc += " 100%"; + fl.setDescription(desc); + } + if (!landedCost) + { + MAccount acct = line.getAccount( + payables ? ProductCost.ACCTTYPE_P_Expense : ProductCost.ACCTTYPE_P_Revenue, as); + if (payables) + { + // if Fixed Asset + if (line.isItem()) + acct = line.getAccount (ProductCost.ACCTTYPE_P_InventoryClearing, as); + } + BigDecimal amt = line.getAmtSource().multiply(multiplier); + BigDecimal amt2 = null; + if (creditMemo) + { + amt2 = amt; + amt = null; + } + if (payables) // Vendor = DR + fl = fact.createLine (line, acct, + getC_Currency_ID(), amt, amt2); + else // Customer = CR + fl = fact.createLine (line, acct, + getC_Currency_ID(), amt2, amt); + if (fl != null) + acctAmt = acctAmt.add(fl.getAcctBalance()); + } + } + // Tax + for (int i = 0; i < m_taxes.length; i++) + { + BigDecimal amt = m_taxes[i].getAmount(); + BigDecimal amt2 = null; + if (creditMemo) + { + amt2 = amt; + amt = null; + } + FactLine tl = null; + if (payables) + tl = fact.createLine (null, m_taxes[i].getAccount(m_taxes[i].getAPTaxType(), as), + getC_Currency_ID(), amt, amt2); + else + tl = fact.createLine (null, m_taxes[i].getAccount(DocTax.ACCTTYPE_TaxDue, as), + getC_Currency_ID(), amt2, amt); + if (tl != null) + tl.setC_Tax_ID(m_taxes[i].getC_Tax_ID()); + } + // Set Locations + FactLine[] fLines = fact.getLines(); + for (int i = 0; i < fLines.length; i++) + { + if (fLines[i] != null) + { + if (payables) + { + fLines[i].setLocationFromBPartner(getC_BPartner_Location_ID(), true); // from Loc + fLines[i].setLocationFromOrg(fLines[i].getAD_Org_ID(), false); // to Loc + } + else + { + fLines[i].setLocationFromOrg(fLines[i].getAD_Org_ID(), true); // from Loc + fLines[i].setLocationFromBPartner(getC_BPartner_Location_ID(), false); // to Loc + } + } + } + return acctAmt; + } // createFactCash + + + /** + * Create Landed Cost accounting & Cost lines + * @param as accounting schema + * @param fact fact + * @param line document line + * @param dr DR entry (normal api) + * @return true if landed costs were created + */ + private boolean landedCost (MAcctSchema as, Fact fact, DocLine line, boolean dr) + { + int C_InvoiceLine_ID = line.get_ID(); + MLandedCostAllocation[] lcas = MLandedCostAllocation.getOfInvoiceLine( + getCtx(), C_InvoiceLine_ID, getTrxName()); + if (lcas.length == 0) + return false; + + // Delete Old + String sql = "DELETE M_CostDetail WHERE C_InvoiceLine_ID=" + C_InvoiceLine_ID; + int no = DB.executeUpdate(sql, getTrxName()); + if (no != 0) + log.config("CostDetail Deleted #" + no); + + // Calculate Total Base + double totalBase = 0; + for (int i = 0; i < lcas.length; i++) + totalBase += lcas[i].getBase().doubleValue(); + + // Create New + MInvoiceLine il = new MInvoiceLine (getCtx(), C_InvoiceLine_ID, getTrxName()); + for (int i = 0; i < lcas.length; i++) + { + MLandedCostAllocation lca = lcas[i]; + if (lca.getBase().signum() == 0) + continue; + double percent = totalBase / lca.getBase().doubleValue(); + String desc = il.getDescription(); + if (desc == null) + desc = percent + "%"; + else + desc += " - " + percent + "%"; + if (line.getDescription() != null) + desc += " - " + line.getDescription(); + + // Accounting + ProductCost pc = new ProductCost (Env.getCtx(), + lca.getM_Product_ID(), lca.getM_AttributeSetInstance_ID(), getTrxName()); + BigDecimal drAmt = null; + BigDecimal crAmt = null; + if (dr) + drAmt = lca.getAmt(); + else + crAmt = lca.getAmt(); + FactLine fl = fact.createLine (line, pc.getAccount(ProductCost.ACCTTYPE_P_CostAdjustment, as), + getC_Currency_ID(), drAmt, crAmt); + fl.setDescription(desc); + + // Cost Detail - Convert to AcctCurrency + BigDecimal allocationAmt = lca.getAmt(); + if (getC_Currency_ID() != as.getC_Currency_ID()) + allocationAmt = MConversionRate.convert(getCtx(), allocationAmt, + getC_Currency_ID(), as.getC_Currency_ID(), + getDateAcct(), getC_ConversionType_ID(), + getAD_Client_ID(), getAD_Org_ID()); + if (allocationAmt.scale() > as.getCostingPrecision()) + allocationAmt = allocationAmt.setScale(as.getCostingPrecision(), BigDecimal.ROUND_HALF_UP); + if (!dr) + allocationAmt = allocationAmt.negate(); + MCostDetail cd = new MCostDetail (as, lca.getAD_Org_ID(), + lca.getM_Product_ID(), lca.getM_AttributeSetInstance_ID(), + lca.getM_CostElement_ID(), + allocationAmt, Env.ZERO, // Qty + desc, getTrxName()); + cd.setC_InvoiceLine_ID(C_InvoiceLine_ID); + boolean ok = cd.save(); + if (ok && !cd.isProcessed()) + { + MClient client = MClient.get(as.getCtx(), as.getAD_Client_ID()); + if (client.isCostImmediate()) + cd.process(); + } + } + + log.config("Created #" + lcas.length); + return true; + } // landedCosts + + /** + * Update ProductPO PriceLastInv + * @param as accounting schema + */ + private void updateProductPO (MAcctSchema as) + { + MClientInfo ci = MClientInfo.get(getCtx(), as.getAD_Client_ID()); + if (ci.getC_AcctSchema1_ID() != as.getC_AcctSchema_ID()) + return; + + StringBuffer sql = new StringBuffer ( + "UPDATE M_Product_PO po " + + "SET PriceLastInv = " + // select + + "(SELECT currencyConvert(il.PriceActual,i.C_Currency_ID,po.C_Currency_ID,i.DateInvoiced,i.C_ConversionType_ID,i.AD_Client_ID,i.AD_Org_ID) " + + "FROM C_Invoice i, C_InvoiceLine il " + + "WHERE i.C_Invoice_ID=il.C_Invoice_ID" + + " AND po.M_Product_ID=il.M_Product_ID AND po.C_BPartner_ID=i.C_BPartner_ID" + + " AND ROWNUM=1 AND i.C_Invoice_ID=").append(get_ID()).append(") ") + // update + .append("WHERE EXISTS (SELECT * " + + "FROM C_Invoice i, C_InvoiceLine il " + + "WHERE i.C_Invoice_ID=il.C_Invoice_ID" + + " AND po.M_Product_ID=il.M_Product_ID AND po.C_BPartner_ID=i.C_BPartner_ID" + + " AND i.C_Invoice_ID=").append(get_ID()).append(")"); + int no = DB.executeUpdate(sql.toString(), getTrxName()); + log.fine("Updated=" + no); + } // updateProductPO + + /** + * Update Product Info (old). + * - Costing (PriceLastInv) + * - PO (PriceLastInv) + * @param C_AcctSchema_ID accounting schema + * @deprecated old costing + */ + private void updateProductInfo (int C_AcctSchema_ID) + { + log.fine("C_Invoice_ID=" + get_ID()); + + /** @todo Last.. would need to compare document/last updated date + * would need to maintain LastPriceUpdateDate on _PO and _Costing */ + + // update Product Costing + // requires existence of currency conversion !! + // if there are multiple lines of the same product last price uses first + // -> TotalInvAmt is sometimes NULL !! -> error + // begin globalqss 2005-10-19 + // postgresql doesn't support LIMIT on UPDATE or DELETE statements + /* + StringBuffer sql = new StringBuffer ( + "UPDATE M_Product_Costing pc " + + "SET (PriceLastInv, TotalInvAmt,TotalInvQty) = " + // select + + "(SELECT currencyConvert(il.PriceActual,i.C_Currency_ID,a.C_Currency_ID,i.DateInvoiced,i.C_ConversionType_ID,i.AD_Client_ID,i.AD_Org_ID)," + + " currencyConvert(il.LineNetAmt,i.C_Currency_ID,a.C_Currency_ID,i.DateInvoiced,i.C_ConversionType_ID,i.AD_Client_ID,i.AD_Org_ID),il.QtyInvoiced " + + "FROM C_Invoice i, C_InvoiceLine il, C_AcctSchema a " + + "WHERE i.C_Invoice_ID=il.C_Invoice_ID" + + " AND pc.M_Product_ID=il.M_Product_ID AND pc.C_AcctSchema_ID=a.C_AcctSchema_ID" + + " AND ROWNUM=1" + + " AND pc.C_AcctSchema_ID=").append(C_AcctSchema_ID).append(" AND i.C_Invoice_ID=") + .append(get_ID()).append(") ") + // update + .append("WHERE EXISTS (SELECT * " + + "FROM C_Invoice i, C_InvoiceLine il, C_AcctSchema a " + + "WHERE i.C_Invoice_ID=il.C_Invoice_ID" + + " AND pc.M_Product_ID=il.M_Product_ID AND pc.C_AcctSchema_ID=a.C_AcctSchema_ID" + + " AND pc.C_AcctSchema_ID=").append(C_AcctSchema_ID).append(" AND i.C_Invoice_ID=") + .append(get_ID()).append(")"); + */ + // the next command is equivalent and works in postgresql and oracle + StringBuffer sql = new StringBuffer ( + "UPDATE M_Product_Costing pc " + + "SET (PriceLastInv, TotalInvAmt,TotalInvQty) = " + // select + + "(SELECT currencyConvert(il.PriceActual,i.C_Currency_ID,a.C_Currency_ID,i.DateInvoiced,i.C_ConversionType_ID,i.AD_Client_ID,i.AD_Org_ID)," + + " currencyConvert(il.LineNetAmt,i.C_Currency_ID,a.C_Currency_ID,i.DateInvoiced,i.C_ConversionType_ID,i.AD_Client_ID,i.AD_Org_ID),il.QtyInvoiced " + + "FROM C_Invoice i, C_InvoiceLine il, C_AcctSchema a " + + "WHERE i.C_Invoice_ID=il.C_Invoice_ID" + + " AND il.c_invoiceline_id = (SELECT MIN(C_InvoiceLine_ID) FROM C_InvoiceLine WHERE C_Invoice_ID=") + .append(get_ID()).append(")" + + " AND pc.M_Product_ID=il.M_Product_ID AND pc.C_AcctSchema_ID=a.C_AcctSchema_ID" + + " AND pc.C_AcctSchema_ID=").append(C_AcctSchema_ID).append(" AND i.C_Invoice_ID=") + .append(get_ID()).append(") ") + // update + .append("WHERE EXISTS (SELECT * " + + "FROM C_Invoice i, C_InvoiceLine il, C_AcctSchema a " + + "WHERE i.C_Invoice_ID=il.C_Invoice_ID" + + " AND pc.M_Product_ID=il.M_Product_ID AND pc.C_AcctSchema_ID=a.C_AcctSchema_ID" + + " AND pc.C_AcctSchema_ID=").append(C_AcctSchema_ID).append(" AND i.C_Invoice_ID=") + .append(get_ID()).append(")"); + // end globalqss 2005-10-19 + int no = DB.executeUpdate(sql.toString(), getTrxName()); + log.fine("M_Product_Costing - Updated=" + no); + } // updateProductInfo + +} // Doc_Invoice diff --git a/serverRoot/src/main/server/org/compiere/acct/Doc_MatchInv.java b/serverRoot/src/main/server/org/compiere/acct/Doc_MatchInv.java new file mode 100644 index 0000000000..f6e253ce71 --- /dev/null +++ b/serverRoot/src/main/server/org/compiere/acct/Doc_MatchInv.java @@ -0,0 +1,327 @@ +/****************************************************************************** + * 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.acct; + +import java.math.*; +import java.sql.*; +import java.util.*; +import org.compiere.model.*; +import org.compiere.util.*; + +/** + * Post MatchInv Documents. + *
+ *  Table:              M_MatchInv (472)
+ *  Document Types:     MXI
+ *  
+ * Update Costing Records + * @author Jorg Janke + * @version $Id: Doc_MatchInv.java,v 1.3 2006/07/30 00:53:33 jjanke Exp $ + */ +public class Doc_MatchInv extends Doc +{ + /** + * Constructor + * @param ass accounting schemata + * @param rs record + * @param trxName trx + */ + protected Doc_MatchInv (MAcctSchema[] ass, ResultSet rs, String trxName) + { + super(ass, MMatchInv.class, rs, DOCTYPE_MatMatchInv, trxName); + } // Doc_MatchInv + + /** Invoice Line */ + private MInvoiceLine m_invoiceLine = null; + /** Material Receipt */ + private MInOutLine m_receiptLine = null; + + private ProductCost m_pc = null; + + /** Commitments */ + private DocLine[] m_commitments = null; + + /** + * Load Specific Document Details + * @return error message or null + */ + protected String loadDocumentDetails () + { + setC_Currency_ID (Doc.NO_CURRENCY); + MMatchInv matchInv = (MMatchInv)getPO(); + setDateDoc(matchInv.getDateTrx()); + setQty (matchInv.getQty()); + // Invoice Info + int C_InvoiceLine_ID = matchInv.getC_InvoiceLine_ID(); + m_invoiceLine = new MInvoiceLine (getCtx(), C_InvoiceLine_ID, null); + // BP for NotInvoicedReceipts + int C_BPartner_ID = m_invoiceLine.getParent().getC_BPartner_ID(); + setC_BPartner_ID(C_BPartner_ID); + // + int M_InOutLine_ID = matchInv.getM_InOutLine_ID(); + m_receiptLine = new MInOutLine (getCtx(), M_InOutLine_ID, null); + // + m_pc = new ProductCost (Env.getCtx(), + getM_Product_ID(), matchInv.getM_AttributeSetInstance_ID(), null); + m_pc.setQty(getQty()); + + return null; + } // loadDocumentDetails + + + /************************************************************************** + * Get Source Currency Balance - subtracts line and tax amounts from total - no rounding + * @return Zero (always balanced) + */ + public BigDecimal getBalance() + { + return Env.ZERO; + } // getBalance + + + /** + * Create Facts (the accounting logic) for + * MXI. + * (single line) + *
+	 *      NotInvoicedReceipts     DR			(Receipt Org)
+	 *      InventoryClearing               CR
+	 *      InvoicePV               DR      CR  (difference)
+	 *  Commitment
+	 * 		Expense							CR
+	 * 		Offset					DR
+	 *  
+ * @param as accounting schema + * @return Fact + */ + public ArrayList createFacts (MAcctSchema as) + { + ArrayList facts = new ArrayList(); + // Nothing to do + if (getM_Product_ID() == 0 // no Product + || getQty().signum() == 0 + || m_receiptLine.getMovementQty().signum() == 0) // Qty = 0 + { + log.fine("No Product/Qty - M_Product_ID=" + getM_Product_ID() + + ",Qty=" + getQty() + ",InOutQty=" + m_receiptLine.getMovementQty()); + return facts; + } + MMatchInv matchInv = (MMatchInv)getPO(); + + // create Fact Header + Fact fact = new Fact(this, as, Fact.POST_Actual); + setC_Currency_ID (as.getC_Currency_ID()); + + /** Needs to be handeled in PO Matching as no Receipt info + if (m_pc.isService()) + { + log.fine("Service - skipped"); + return fact; + } + **/ + + + // NotInvoicedReceipt DR + // From Receipt + BigDecimal multiplier = getQty() + .divide(m_receiptLine.getMovementQty(), 12, BigDecimal.ROUND_HALF_UP) + .abs(); + FactLine dr = fact.createLine (null, + getAccount(Doc.ACCTTYPE_NotInvoicedReceipts, as), + as.getC_Currency_ID(), Env.ONE, null); // updated below + if (dr == null) + { + p_Error = "No Product Costs"; + return null; + } + dr.setQty(getQty()); + // dr.setM_Locator_ID(m_receiptLine.getM_Locator_ID()); + // MInOut receipt = m_receiptLine.getParent(); + // dr.setLocationFromBPartner(receipt.getC_BPartner_Location_ID(), true); // from Loc + // dr.setLocationFromLocator(m_receiptLine.getM_Locator_ID(), false); // to Loc + BigDecimal temp = dr.getAcctBalance(); + // Set AmtAcctCr/Dr from Receipt (sets also Project) + if (!dr.updateReverseLine (MInOut.Table_ID, // Amt updated + m_receiptLine.getM_InOut_ID(), m_receiptLine.getM_InOutLine_ID(), + multiplier)) + { + p_Error = "Mat.Receipt not posted yet"; + return null; + } + log.fine("CR - Amt(" + temp + "->" + dr.getAcctBalance() + + ") - " + dr.toString()); + + // InventoryClearing CR + // From Invoice + MAccount expense = m_pc.getAccount(ProductCost.ACCTTYPE_P_InventoryClearing, as); + if (m_pc.isService()) + expense = m_pc.getAccount(ProductCost.ACCTTYPE_P_Expense, as); + BigDecimal LineNetAmt = m_invoiceLine.getLineNetAmt(); + multiplier = getQty() + .divide(m_invoiceLine.getQtyInvoiced(), 12, BigDecimal.ROUND_HALF_UP) + .abs(); + if (multiplier.compareTo(Env.ONE) != 0) + LineNetAmt = LineNetAmt.multiply(multiplier); + if (m_pc.isService()) + LineNetAmt = dr.getAcctBalance(); // book out exact receipt amt + FactLine cr = null; + if (as.isAccrual()) + { + cr = fact.createLine (null, expense, + as.getC_Currency_ID(), null, LineNetAmt); // updated below + if (cr == null) + { + log.fine("Line Net Amt=0 - M_Product_ID=" + getM_Product_ID() + + ",Qty=" + getQty() + ",InOutQty=" + m_receiptLine.getMovementQty()); + facts.add(fact); + return facts; + } + cr.setQty(getQty().negate()); + temp = cr.getAcctBalance(); + // Set AmtAcctCr/Dr from Invoice (sets also Project) + if (as.isAccrual() && !cr.updateReverseLine (MInvoice.Table_ID, // Amt updated + m_invoiceLine.getC_Invoice_ID(), m_invoiceLine.getC_InvoiceLine_ID(), multiplier)) + { + p_Error = "Invoice not posted yet"; + return null; + } + log.fine("DR - Amt(" + temp + "->" + cr.getAcctBalance() + + ") - " + cr.toString()); + } + else // Cash Acct + { + MInvoice invoice = m_invoiceLine.getParent(); + if (as.getC_Currency_ID() == invoice.getC_Currency_ID()) + LineNetAmt = MConversionRate.convert(getCtx(), LineNetAmt, + invoice.getC_Currency_ID(), as.getC_Currency_ID(), + invoice.getDateAcct(), invoice.getC_ConversionType_ID(), + invoice.getAD_Client_ID(), invoice.getAD_Org_ID()); + cr = fact.createLine (null, expense, + as.getC_Currency_ID(), null, LineNetAmt); + cr.setQty(getQty().multiply(multiplier).negate()); + } + cr.setC_Activity_ID(m_invoiceLine.getC_Activity_ID()); + cr.setC_Campaign_ID(m_invoiceLine.getC_Campaign_ID()); + cr.setC_Project_ID(m_invoiceLine.getC_Project_ID()); + cr.setC_UOM_ID(m_invoiceLine.getC_UOM_ID()); + cr.setUser1_ID(m_invoiceLine.getUser1_ID()); + cr.setUser2_ID(m_invoiceLine.getUser2_ID()); + + + // Invoice Price Variance difference + BigDecimal ipv = cr.getAcctBalance().add(dr.getAcctBalance()).negate(); + if (ipv.signum() != 0) + { + FactLine pv = fact.createLine(null, + m_pc.getAccount(ProductCost.ACCTTYPE_P_IPV, as), + as.getC_Currency_ID(), ipv); + pv.setC_Activity_ID(m_invoiceLine.getC_Activity_ID()); + pv.setC_Campaign_ID(m_invoiceLine.getC_Campaign_ID()); + pv.setC_Project_ID(m_invoiceLine.getC_Project_ID()); + pv.setC_UOM_ID(m_invoiceLine.getC_UOM_ID()); + pv.setUser1_ID(m_invoiceLine.getUser1_ID()); + pv.setUser2_ID(m_invoiceLine.getUser2_ID()); + } + log.fine("IPV=" + ipv + "; Balance=" + fact.getSourceBalance()); + + // Cost Detail Record - data from Expense/IncClearing (CR) record + MCostDetail.createInvoice(as, getAD_Org_ID(), + getM_Product_ID(), matchInv.getM_AttributeSetInstance_ID(), + m_invoiceLine.getC_InvoiceLine_ID(), 0, // No cost element + cr.getAcctBalance().negate(), getQty(), // correcting + getDescription(), getTrxName()); + + // Update Costing + updateProductInfo(as.getC_AcctSchema_ID(), + MAcctSchema.COSTINGMETHOD_StandardCosting.equals(as.getCostingMethod())); + // + facts.add(fact); + + /** Commitment release ****/ + if (as.isAccrual() && as.isCreateCommitment()) + { + fact = Doc_Order.getCommitmentRelease(as, this, + getQty(), m_invoiceLine.getC_InvoiceLine_ID(), Env.ONE); + if (fact == null) + return null; + facts.add(fact); + } // Commitment + + return facts; + } // createFact + + /** + * Update Product Info (old). + * - Costing (CostStandardCumQty, CostStandardCumAmt, CostAverageCumQty, CostAverageCumAmt) + * @param C_AcctSchema_ID accounting schema + * @param standardCosting true if std costing + * @return true if updated + * @deprecated old costing + */ + private boolean updateProductInfo (int C_AcctSchema_ID, boolean standardCosting) + { + log.fine("M_MatchInv_ID=" + get_ID()); + + // update Product Costing Qty/Amt + // requires existence of currency conversion !! + StringBuffer sql = new StringBuffer ( + "UPDATE M_Product_Costing pc " + + "SET (CostStandardCumQty,CostStandardCumAmt, CostAverageCumQty,CostAverageCumAmt) = " + + "(SELECT pc.CostStandardCumQty + m.Qty," + + "pc.CostStandardCumAmt + currencyConvert(il.PriceActual,i.C_Currency_ID,a.C_Currency_ID,i.DateInvoiced,i.C_ConversionType_ID,i.AD_Client_ID,i.AD_Org_ID)*m.Qty, " + + "pc.CostAverageCumQty + m.Qty," + + "pc.CostAverageCumAmt + currencyConvert(il.PriceActual,i.C_Currency_ID,a.C_Currency_ID,i.DateInvoiced,i.C_ConversionType_ID,i.AD_Client_ID,i.AD_Org_ID)*m.Qty " + + "FROM M_MatchInv m" + + " INNER JOIN C_InvoiceLine il ON (m.C_InvoiceLine_ID=il.C_InvoiceLine_ID)" + + " INNER JOIN C_Invoice i ON (il.C_Invoice_ID=i.C_Invoice_ID)," + + " C_AcctSchema a " + + "WHERE pc.C_AcctSchema_ID=a.C_AcctSchema_ID" + + " AND pc.M_Product_ID=m.M_Product_ID" + + " AND m.M_MatchInv_ID=").append(get_ID()).append(")" + // + + "WHERE pc.C_AcctSchema_ID=").append(C_AcctSchema_ID).append( + " AND EXISTS (SELECT * FROM M_MatchInv m " + + "WHERE pc.M_Product_ID=m.M_Product_ID" + + " AND m.M_MatchInv_ID=").append(get_ID()).append(")"); + int no = DB.executeUpdate(sql.toString(), getTrxName()); + log.fine("M_Product_Costing - Qty/Amt Updated #=" + no); + + // Update Average Cost + sql = new StringBuffer ( + "UPDATE M_Product_Costing " + + "SET CostAverage = CostAverageCumAmt/DECODE(CostAverageCumQty, 0,1, CostAverageCumQty) " + + "WHERE C_AcctSchema_ID=").append(C_AcctSchema_ID) + .append(" AND M_Product_ID=").append(getM_Product_ID()); + no = DB.executeUpdate(sql.toString(), getTrxName()); + log.fine("M_Product_Costing - AvgCost Updated #=" + no); + + + // Update Current Cost + if (!standardCosting) + { + sql = new StringBuffer ( + "UPDATE M_Product_Costing " + + "SET CurrentCostPrice = CostAverage " + + "WHERE C_AcctSchema_ID=").append(C_AcctSchema_ID) + .append(" AND M_Product_ID=").append(getM_Product_ID()); + no = DB.executeUpdate(sql.toString(), getTrxName()); + log.fine("M_Product_Costing - CurrentCost Updated=" + no); + } + return true; + } // updateProductInfo + +} // Doc_MatchInv diff --git a/serverRoot/src/main/server/org/compiere/acct/Doc_MatchPO.java b/serverRoot/src/main/server/org/compiere/acct/Doc_MatchPO.java new file mode 100644 index 0000000000..cb2ca28575 --- /dev/null +++ b/serverRoot/src/main/server/org/compiere/acct/Doc_MatchPO.java @@ -0,0 +1,243 @@ +/****************************************************************************** + * 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.acct; + +import java.math.*; +import java.sql.*; +import java.util.*; +import java.util.logging.*; + +import org.compiere.model.*; +import org.compiere.util.*; + +/** + * Post MatchPO Documents. + *
+ *  Table:              C_MatchPO (473)
+ *  Document Types:     MXP
+ *  
+ * @author Jorg Janke + * @version $Id: Doc_MatchPO.java,v 1.3 2006/07/30 00:53:33 jjanke Exp $ + */ +public class Doc_MatchPO extends Doc +{ + /** + * Constructor + * @param ass accounting schemata + * @param rs record + * @param trxName trx + */ + protected Doc_MatchPO (MAcctSchema[] ass, ResultSet rs, String trxName) + { + super(ass, MMatchPO.class, rs, DOCTYPE_MatMatchPO, trxName); + } // Doc_MatchPO + + private int m_C_OrderLine_ID = 0; + private MOrderLine m_oLine = null; + // + private int m_M_InOutLine_ID = 0; + private int m_C_InvoiceLine_ID = 0; + private ProductCost m_pc; + private int m_M_AttributeSetInstance_ID = 0; + + /** + * Load Specific Document Details + * @return error message or null + */ + protected String loadDocumentDetails () + { + setC_Currency_ID (Doc.NO_CURRENCY); + MMatchPO matchPO = (MMatchPO)getPO(); + setDateDoc(matchPO.getDateTrx()); + // + m_M_AttributeSetInstance_ID = matchPO.getM_AttributeSetInstance_ID(); + setQty (matchPO.getQty()); + // + m_C_OrderLine_ID = matchPO.getC_OrderLine_ID(); + m_oLine = new MOrderLine (getCtx(), m_C_OrderLine_ID, getTrxName()); + // + m_M_InOutLine_ID = matchPO.getM_InOutLine_ID(); + m_C_InvoiceLine_ID = matchPO.getC_InvoiceLine_ID(); + // + m_pc = new ProductCost (Env.getCtx(), + getM_Product_ID(), m_M_AttributeSetInstance_ID, getTrxName()); + m_pc.setQty(getQty()); + return null; + } // loadDocumentDetails + + + /************************************************************************** + * Get Source Currency Balance - subtracts line and tax amounts from total - no rounding + * @return Zero - always balanced + */ + public BigDecimal getBalance() + { + return Env.ZERO; + } // getBalance + + + /** + * Create Facts (the accounting logic) for + * MXP. + *
+	 *      Product PPV     
+	 *      PPV_Offset                  
+	 *  
+ * @param as accounting schema + * @return Fact + */ + public ArrayList createFacts (MAcctSchema as) + { + ArrayList facts = new ArrayList(); + // + if (getM_Product_ID() == 0 // Nothing to do if no Product + || getQty().signum() == 0 + || m_M_InOutLine_ID == 0) // No posting if not matched to Shipment + { + log.fine("No Product/Qty - M_Product_ID=" + getM_Product_ID() + + ",Qty=" + getQty()); + return facts; + } + + // create Fact Header + Fact fact = new Fact(this, as, Fact.POST_Actual); + setC_Currency_ID(as.getC_Currency_ID()); + + // Purchase Order Line + BigDecimal poCost = m_oLine.getPriceCost(); + if (poCost == null || poCost.signum() == 0) + poCost = m_oLine.getPriceActual(); + poCost = poCost.multiply(getQty()); // Delivered so far + // Different currency + if (m_oLine.getC_Currency_ID() != as.getC_Currency_ID()) + { + MOrder order = m_oLine.getParent(); + BigDecimal rate = MConversionRate.getRate( + order.getC_Currency_ID(), as.getC_Currency_ID(), + order.getDateAcct(), order.getC_ConversionType_ID(), + m_oLine.getAD_Client_ID(), m_oLine.getAD_Org_ID()); + if (rate == null) + { + p_Error = "Purchase Order not convertible - " + as.getName(); + return null; + } + poCost = poCost.multiply(rate); + if (poCost.scale() > as.getCostingPrecision()) + poCost = poCost.setScale(as.getCostingPrecision(), BigDecimal.ROUND_HALF_UP); + } + + // Create PO Cost Detail Record firs + MCostDetail.createOrder(as, m_oLine.getAD_Org_ID(), + getM_Product_ID(), m_M_AttributeSetInstance_ID, + m_C_OrderLine_ID, 0, // no cost element + poCost, getQty(), // Delivered + m_oLine.getDescription(), getTrxName()); + + // Current Costs + String costingMethod = as.getCostingMethod(); + MProduct product = MProduct.get(getCtx(), getM_Product_ID()); + MProductCategoryAcct pca = MProductCategoryAcct.get(getCtx(), + product.getM_Product_Category_ID(), as.getC_AcctSchema_ID(), getTrxName()); + if (pca.getCostingMethod() != null) + costingMethod = pca.getCostingMethod(); + BigDecimal costs = m_pc.getProductCosts(as, getAD_Org_ID(), + costingMethod, m_C_OrderLine_ID, false); // non-zero costs + + // No Costs yet - no PPV + if (costs == null || costs.signum() == 0) + { + p_Error = "Resubmit - No Costs for " + product.getName(); + log.log(Level.SEVERE, p_Error); + return null; + } + + // Difference + BigDecimal difference = poCost.subtract(costs); + // Nothing to post + if (difference.signum() == 0) + { + log.log(Level.FINE, "No Cost Difference for M_Product_ID=" + getM_Product_ID()); + facts.add(fact); + return facts; + } + + // Product PPV + FactLine cr = fact.createLine(null, + m_pc.getAccount(ProductCost.ACCTTYPE_P_PPV, as), + as.getC_Currency_ID(), difference); + if (cr != null) + { + cr.setQty(getQty()); + cr.setC_BPartner_ID(m_oLine.getC_BPartner_ID()); + cr.setC_Activity_ID(m_oLine.getC_Activity_ID()); + cr.setC_Campaign_ID(m_oLine.getC_Campaign_ID()); + cr.setC_Project_ID(m_oLine.getC_Project_ID()); + cr.setC_UOM_ID(m_oLine.getC_UOM_ID()); + cr.setUser1_ID(m_oLine.getUser1_ID()); + cr.setUser2_ID(m_oLine.getUser2_ID()); + } + + // PPV Offset + FactLine dr = fact.createLine(null, + getAccount(Doc.ACCTTYPE_PPVOffset, as), + as.getC_Currency_ID(), difference.negate()); + if (dr != null) + { + dr.setQty(getQty().negate()); + dr.setC_BPartner_ID(m_oLine.getC_BPartner_ID()); + dr.setC_Activity_ID(m_oLine.getC_Activity_ID()); + dr.setC_Campaign_ID(m_oLine.getC_Campaign_ID()); + dr.setC_Project_ID(m_oLine.getC_Project_ID()); + dr.setC_UOM_ID(m_oLine.getC_UOM_ID()); + dr.setUser1_ID(m_oLine.getUser1_ID()); + dr.setUser2_ID(m_oLine.getUser2_ID()); + } + + // + facts.add(fact); + return facts; + } // createFact + + /** + * Update Product Info (old). + * - Costing (CostStandardPOQty, CostStandardPOAmt) + * @param C_AcctSchema_ID accounting schema + * @deprecated old costing + */ + private void updateProductInfo (int C_AcctSchema_ID) + { + log.fine("M_MatchPO_ID=" + get_ID()); + + // update Product Costing + // requires existence of currency conversion !! + StringBuffer sql = new StringBuffer ( + "UPDATE M_Product_Costing pc " + + "SET (CostStandardPOQty,CostStandardPOAmt) = " + + "(SELECT CostStandardPOQty + m.Qty," + + " CostStandardPOAmt + currencyConvert(ol.PriceActual,ol.C_Currency_ID,a.C_Currency_ID,ol.DateOrdered,null,ol.AD_Client_ID,ol.AD_Org_ID)*m.Qty " + + "FROM M_MatchPO m, C_OrderLine ol, C_AcctSchema a " + + "WHERE m.C_OrderLine_ID=ol.C_OrderLine_ID" + + " AND pc.M_Product_ID=ol.M_Product_ID" + + " AND pc.C_AcctSchema_ID=a.C_AcctSchema_ID" + + "AND m.M_MatchPO_ID=").append(get_ID()).append(") ") + .append("WHERE pc.C_AcctSchema_ID=").append(C_AcctSchema_ID) + .append(" AND pc.M_Product_ID=").append(getM_Product_ID()); + int no = DB.executeUpdate(sql.toString(), getTrxName()); + log.fine("M_Product_Costing - Updated=" + no); + } // updateProductInfo + +} // Doc_MatchPO diff --git a/serverRoot/src/main/server/org/compiere/acct/Doc_Movement.java b/serverRoot/src/main/server/org/compiere/acct/Doc_Movement.java new file mode 100644 index 0000000000..5a2ab5b025 --- /dev/null +++ b/serverRoot/src/main/server/org/compiere/acct/Doc_Movement.java @@ -0,0 +1,179 @@ +/****************************************************************************** + * 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.acct; + +import java.math.*; +import java.sql.*; +import java.util.*; + +import org.compiere.model.*; +import org.compiere.util.*; + +/** + * Post Invoice Documents. + *
+ *  Table:              M_Movement (323)
+ *  Document Types:     MMM
+ *  
+ * @author Jorg Janke + * @version $Id: Doc_Movement.java,v 1.3 2006/07/30 00:53:33 jjanke Exp $ + */ +public class Doc_Movement extends Doc +{ + /** + * Constructor + * @param ass accounting schemata + * @param rs record + * @param trxName trx + */ + public Doc_Movement (MAcctSchema[] ass, ResultSet rs, String trxName) + { + super (ass, MMovement.class, rs, DOCTYPE_MatMovement, trxName); + } // Doc_Movement + + /** + * Load Document Details + * @return error message or null + */ + protected String loadDocumentDetails() + { + setC_Currency_ID(NO_CURRENCY); + MMovement move = (MMovement)getPO(); + setDateDoc (move.getMovementDate()); + setDateAcct(move.getMovementDate()); + // Contained Objects + p_lines = loadLines(move); + log.fine("Lines=" + p_lines.length); + return null; + } // loadDocumentDetails + + /** + * Load Invoice Line + * @param move move + * @return document lines (DocLine_Material) + */ + private DocLine[] loadLines(MMovement move) + { + ArrayList list = new ArrayList(); + MMovementLine[] lines = move.getLines(false); + for (int i = 0; i < lines.length; i++) + { + MMovementLine line = lines[i]; + DocLine docLine = new DocLine (line, this); + docLine.setQty(line.getMovementQty(), false); + // + log.fine(docLine.toString()); + list.add (docLine); + } + + // Return Array + DocLine[] dls = new DocLine[list.size()]; + list.toArray(dls); + return dls; + } // loadLines + + /** + * Get Balance + * @return balance (ZERO) - always balanced + */ + public BigDecimal getBalance() + { + BigDecimal retValue = Env.ZERO; + return retValue; + } // getBalance + + /** + * Create Facts (the accounting logic) for + * MMM. + *
+	 *  Movement
+	 *      Inventory       DR      CR
+	 *      InventoryTo     DR      CR
+	 *  
+ * @param as account schema + * @return Fact + */ + public ArrayList createFacts (MAcctSchema as) + { + // create Fact Header + Fact fact = new Fact(this, as, Fact.POST_Actual); + setC_Currency_ID(as.getC_Currency_ID()); + + // Line pointers + FactLine dr = null; + FactLine cr = null; + + for (int i = 0; i < p_lines.length; i++) + { + DocLine line = p_lines[i]; + BigDecimal costs = line.getProductCosts(as, line.getAD_Org_ID(), false); + + // ** Inventory DR CR + dr = fact.createLine(line, + line.getAccount(ProductCost.ACCTTYPE_P_Asset, as), + as.getC_Currency_ID(), costs.negate()); // from (-) CR + if (dr == null) + continue; + dr.setM_Locator_ID(line.getM_Locator_ID()); + dr.setQty(line.getQty().negate()); // outgoing + + // ** InventoryTo DR CR + cr = fact.createLine(line, + line.getAccount(ProductCost.ACCTTYPE_P_Asset, as), + as.getC_Currency_ID(), costs); // to (+) DR + if (cr == null) + continue; + cr.setM_Locator_ID(line.getM_LocatorTo_ID()); + cr.setQty(line.getQty()); + + // Only for between-org movements + if (dr.getAD_Org_ID() != cr.getAD_Org_ID()) + { + String costingLevel = as.getCostingLevel(); + MProductCategoryAcct pca = MProductCategoryAcct.get(getCtx(), + line.getProduct().getM_Product_Category_ID(), + as.getC_AcctSchema_ID(), getTrxName()); + if (pca.getCostingLevel() != null) + costingLevel = pca.getCostingLevel(); + if (!MAcctSchema.COSTINGLEVEL_Organization.equals(costingLevel)) + continue; + // + String description = line.getDescription(); + if (description == null) + description = ""; + // Cost Detail From + MCostDetail.createMovement(as, dr.getAD_Org_ID(), // locator org + line.getM_Product_ID(), line.getM_AttributeSetInstance_ID(), + line.get_ID(), 0, + costs.negate(), line.getQty().negate(), true, + description + "(|->)", getTrxName()); + // Cost Detail To + MCostDetail.createMovement(as, cr.getAD_Org_ID(), // locator org + line.getM_Product_ID(), line.getM_AttributeSetInstance_ID(), + line.get_ID(), 0, + costs, line.getQty(), false, + description + "(|<-)", getTrxName()); + } + } + + // + ArrayList facts = new ArrayList(); + facts.add(fact); + return facts; + } // createFact + +} // Doc_Movement diff --git a/serverRoot/src/main/server/org/compiere/acct/Doc_Order.java b/serverRoot/src/main/server/org/compiere/acct/Doc_Order.java new file mode 100644 index 0000000000..05f5ec643e --- /dev/null +++ b/serverRoot/src/main/server/org/compiere/acct/Doc_Order.java @@ -0,0 +1,624 @@ +/****************************************************************************** + * 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.acct; + +import java.math.*; +import java.sql.*; +import java.util.*; +import java.util.logging.*; +import org.compiere.model.*; +import org.compiere.util.*; + +/** + * Post Order Documents. + *
+ *  Table:              C_Order (259)
+ *  Document Types:     SOO, POO
+ *  
+ * @author Jorg Janke + * @version $Id: Doc_Order.java,v 1.3 2006/07/30 00:53:33 jjanke Exp $ + */ +public class Doc_Order extends Doc +{ + /** + * Constructor + * @param ass accounting schemata + * @param rs record + * @param trxName trx + */ + protected Doc_Order (MAcctSchema[] ass, ResultSet rs, String trxName) + { + super (ass, MOrder.class, rs, null, trxName); + } // Doc_Order + + /** Contained Optional Tax Lines */ + private DocTax[] m_taxes = null; + /** Requisitions */ + private DocLine[] m_requisitions = null; + /** Order Currency Precision */ + private int m_precision = -1; + + /** + * Load Specific Document Details + * @return error message or null + */ + protected String loadDocumentDetails () + { + MOrder order = (MOrder)getPO(); + setDateDoc(order.getDateOrdered()); + setIsTaxIncluded(order.isTaxIncluded()); + // Amounts + setAmount(AMTTYPE_Gross, order.getGrandTotal()); + setAmount(AMTTYPE_Net, order.getTotalLines()); + setAmount(AMTTYPE_Charge, order.getChargeAmt()); + + // Contained Objects + m_taxes = loadTaxes(); + p_lines = loadLines(order); + // log.fine( "Lines=" + p_lines.length + ", Taxes=" + m_taxes.length); + return null; + } // loadDocumentDetails + + + /** + * Load Invoice Line + * @param order order + * @return DocLine Array + */ + private DocLine[] loadLines(MOrder order) + { + ArrayList list = new ArrayList(); + MOrderLine[] lines = order.getLines(); + for (int i = 0; i < lines.length; i++) + { + MOrderLine line = lines[i]; + DocLine docLine = new DocLine (line, this); + BigDecimal Qty = line.getQtyOrdered(); + docLine.setQty(Qty, order.isSOTrx()); + // + BigDecimal PriceActual = line.getPriceActual(); + BigDecimal PriceCost = null; + if (getDocumentType().equals(DOCTYPE_POrder)) // PO + PriceCost = line.getPriceCost(); + BigDecimal LineNetAmt = null; + if (PriceCost != null && PriceCost.signum() != 0) + LineNetAmt = Qty.multiply(PriceCost); + else + LineNetAmt = line.getLineNetAmt(); + docLine.setAmount (LineNetAmt); // DR + BigDecimal PriceList = line.getPriceList(); + int C_Tax_ID = docLine.getC_Tax_ID(); + // Correct included Tax + if (isTaxIncluded() && C_Tax_ID != 0) + { + MTax tax = MTax.get(getCtx(), C_Tax_ID); + if (!tax.isZeroTax()) + { + BigDecimal LineNetAmtTax = tax.calculateTax(LineNetAmt, true, getStdPrecision()); + log.fine("LineNetAmt=" + LineNetAmt + " - Tax=" + LineNetAmtTax); + LineNetAmt = LineNetAmt.subtract(LineNetAmtTax); + for (int t = 0; t < m_taxes.length; t++) + { + if (m_taxes[t].getC_Tax_ID() == C_Tax_ID) + { + m_taxes[t].addIncludedTax(LineNetAmtTax); + break; + } + } + BigDecimal PriceListTax = tax.calculateTax(PriceList, true, getStdPrecision()); + PriceList = PriceList.subtract(PriceListTax); + } + } // correct included Tax + + docLine.setAmount (LineNetAmt, PriceList, Qty); + list.add(docLine); + } + + // Return Array + DocLine[] dl = new DocLine[list.size()]; + list.toArray(dl); + return dl; + } // loadLines + + + /** + * Load Requisitions + * @return requisition lines of Order + */ + private DocLine[] loadRequisitions () + { + MOrder order = (MOrder)getPO(); + MOrderLine[] oLines = order.getLines(); + HashMap qtys = new HashMap(); + for (int i = 0; i < oLines.length; i++) + { + MOrderLine line = oLines[i]; + qtys.put(new Integer(line.getC_OrderLine_ID()), line.getQtyOrdered()); + } + // + ArrayList list = new ArrayList(); + String sql = "SELECT * FROM M_RequisitionLine rl " + + "WHERE EXISTS (SELECT * FROM C_Order o " + + " INNER JOIN C_OrderLine ol ON (o.C_Order_ID=ol.C_Order_ID) " + + "WHERE ol.C_OrderLine_ID=rl.C_OrderLine_ID" + + " AND o.C_Order_ID=?) " + + "ORDER BY rl.C_OrderLine_ID"; + PreparedStatement pstmt = null; + try + { + pstmt = DB.prepareStatement (sql, null); + pstmt.setInt (1, order.getC_Order_ID()); + ResultSet rs = pstmt.executeQuery (); + while (rs.next ()) + { + MRequisitionLine line = new MRequisitionLine (getCtx(), rs, null); + DocLine docLine = new DocLine (line, this); + // Quantity - not more then OrderLine + // Issue: Split of Requisition to multiple POs & different price + Integer key = new Integer(line.getC_OrderLine_ID()); + BigDecimal maxQty = qtys.get(key); + BigDecimal Qty = line.getQty().max(maxQty); + if (Qty.signum() == 0) + continue; + docLine.setQty (Qty, false); + qtys.put(key, maxQty.subtract(Qty)); + // + BigDecimal PriceActual = line.getPriceActual(); + BigDecimal LineNetAmt = line.getLineNetAmt(); + if (line.getQty().compareTo(Qty) != 0) + LineNetAmt = PriceActual.multiply(Qty); + docLine.setAmount (LineNetAmt); // DR + list.add (docLine); + } + rs.close (); + pstmt.close (); + pstmt = null; + } + catch (Exception e) + { + log.log (Level.SEVERE, sql, e); + } + try + { + if (pstmt != null) + pstmt.close (); + pstmt = null; + } + catch (Exception e) + { + pstmt = null; + } + + // Return Array + DocLine[] dls = new DocLine[list.size ()]; + list.toArray (dls); + return dls; + } // loadRequisitions + + + /** + * Get Currency Precision + * @return precision + */ + private int getStdPrecision() + { + if (m_precision == -1) + m_precision = MCurrency.getStdPrecision(getCtx(), getC_Currency_ID()); + return m_precision; + } // getPrecision + + /** + * Load Invoice Taxes + * @return DocTax Array + */ + private DocTax[] loadTaxes() + { + ArrayList list = new ArrayList(); + String sql = "SELECT it.C_Tax_ID, t.Name, t.Rate, it.TaxBaseAmt, it.TaxAmt, t.IsSalesTax " + + "FROM C_Tax t, C_OrderTax it " + + "WHERE t.C_Tax_ID=it.C_Tax_ID AND it.C_Order_ID=?"; + try + { + PreparedStatement pstmt = DB.prepareStatement(sql, getTrxName()); + pstmt.setInt(1, get_ID()); + ResultSet rs = pstmt.executeQuery(); + // + while (rs.next()) + { + int C_Tax_ID = rs.getInt(1); + String name = rs.getString(2); + BigDecimal rate = rs.getBigDecimal(3); + BigDecimal taxBaseAmt = rs.getBigDecimal(4); + BigDecimal amount = rs.getBigDecimal(5); + boolean salesTax = "Y".equals(rs.getString(6)); + // + DocTax taxLine = new DocTax(C_Tax_ID, name, rate, + taxBaseAmt, amount, salesTax); + list.add(taxLine); + } + // + rs.close(); + pstmt.close(); + } + catch (SQLException e) + { + log.log(Level.SEVERE, sql, e); + } + + // Return Array + DocTax[] tl = new DocTax[list.size()]; + list.toArray(tl); + return tl; + } // loadTaxes + + + /************************************************************************** + * Get Source Currency Balance - subtracts line and tax amounts from total - no rounding + * @return positive amount, if total invoice is bigger than lines + */ + public BigDecimal getBalance() + { + BigDecimal retValue = new BigDecimal(0.0); + StringBuffer sb = new StringBuffer (" ["); + // Total + retValue = retValue.add(getAmount(Doc.AMTTYPE_Gross)); + sb.append(getAmount(Doc.AMTTYPE_Gross)); + // - Header Charge + retValue = retValue.subtract(getAmount(Doc.AMTTYPE_Charge)); + sb.append("-").append(getAmount(Doc.AMTTYPE_Charge)); + // - Tax + if (m_taxes != null) + { + for (int i = 0; i < m_taxes.length; i++) + { + retValue = retValue.subtract(m_taxes[i].getAmount()); + sb.append("-").append(m_taxes[i].getAmount()); + } + } + // - Lines + if (p_lines != null) + { + for (int i = 0; i < p_lines.length; i++) + { + retValue = retValue.subtract(p_lines[i].getAmtSource()); + sb.append("-").append(p_lines[i].getAmtSource()); + } + sb.append("]"); + } + // + if (retValue.signum() != 0 // Sum of Cost(vs. Price) in lines may not add up + && getDocumentType().equals(DOCTYPE_POrder)) // PO + { + log.fine(toString() + " Balance=" + retValue + sb.toString() + " (ignored)"); + retValue = Env.ZERO; + } + else + log.fine(toString() + " Balance=" + retValue + sb.toString()); + return retValue; + } // getBalance + + + /************************************************************************* + * Create Facts (the accounting logic) for + * SOO, POO. + *
+	 *  Reservation (release)
+	 * 		Expense			DR
+	 * 		Offset					CR
+	 *  Commitment
+	 *  (to be released by Invoice Matching)
+	 * 		Expense					CR
+	 * 		Offset			DR
+	 *  
+ * @param as accounting schema + * @return Fact + */ + public ArrayList createFacts (MAcctSchema as) + { + ArrayList facts = new ArrayList(); + // Purchase Order + if (getDocumentType().equals(DOCTYPE_POrder)) + { + updateProductPO(as); + updateProductInfo(as.getC_AcctSchema_ID()); + + BigDecimal grossAmt = getAmount(Doc.AMTTYPE_Gross); + + // Commitment + if (as.isCreateCommitment()) + { + Fact fact = new Fact(this, as, Fact.POST_Commitment); + BigDecimal total = Env.ZERO; + for (int i = 0; i < p_lines.length; i++) + { + DocLine line = p_lines[i]; + BigDecimal cost = line.getAmtSource(); + total = total.add(cost); + + // Account + MAccount expense = line.getAccount(ProductCost.ACCTTYPE_P_Expense, as); + FactLine fl = fact.createLine (line, expense, + getC_Currency_ID(), cost, null); + } + // Offset + MAccount offset = getAccount(ACCTTYPE_CommitmentOffset, as); + if (offset == null) + { + p_Error = "@NotFound@ @CommitmentOffset_Acct@"; + log.log(Level.SEVERE, p_Error); + return null; + } + fact.createLine (null, offset, + getC_Currency_ID(), null, total); + // + facts.add(fact); + } + + // Reverse Reservation + if (as.isCreateReservation()) + { + Fact fact = new Fact(this, as, Fact.POST_Reservation); + BigDecimal total = Env.ZERO; + if (m_requisitions == null) + m_requisitions = loadRequisitions(); + for (int i = 0; i < m_requisitions.length; i++) + { + DocLine line = m_requisitions[i]; + BigDecimal cost = line.getAmtSource(); + total = total.add(cost); + + // Account + MAccount expense = line.getAccount(ProductCost.ACCTTYPE_P_Expense, as); + FactLine fl = fact.createLine (line, expense, + getC_Currency_ID(), null, cost); + } + // Offset + MAccount offset = getAccount(ACCTTYPE_CommitmentOffset, as); + if (offset == null) + { + p_Error = "@NotFound@ @CommitmentOffset_Acct@"; + log.log(Level.SEVERE, p_Error); + return null; + } + fact.createLine (null, offset, + getC_Currency_ID(), total, null); + // + facts.add(fact); + } // reservations + } + // SO + return facts; + } // createFact + + + /** + * Update ProductPO PriceLastPO + * @param as accounting schema + */ + private void updateProductPO(MAcctSchema as) + { + MClientInfo ci = MClientInfo.get(getCtx(), as.getAD_Client_ID()); + if (ci.getC_AcctSchema1_ID() != as.getC_AcctSchema_ID()) + return; + + StringBuffer sql = new StringBuffer ( + "UPDATE M_Product_PO po " + + "SET PriceLastPO = (SELECT currencyConvert(ol.PriceActual,ol.C_Currency_ID,po.C_Currency_ID,o.DateOrdered,o.C_ConversionType_ID,o.AD_Client_ID,o.AD_Org_ID) " + + "FROM C_Order o, C_OrderLine ol " + + "WHERE o.C_Order_ID=ol.C_Order_ID" + + " AND po.M_Product_ID=ol.M_Product_ID AND po.C_BPartner_ID=o.C_BPartner_ID" + + " AND ROWNUM=1 AND o.C_Order_ID=").append(get_ID()).append(") ") + .append("WHERE EXISTS (SELECT * " + + "FROM C_Order o, C_OrderLine ol " + + "WHERE o.C_Order_ID=ol.C_Order_ID" + + " AND po.M_Product_ID=ol.M_Product_ID AND po.C_BPartner_ID=o.C_BPartner_ID" + + " AND o.C_Order_ID=").append(get_ID()).append(")"); + int no = DB.executeUpdate(sql.toString(), getTrxName()); + log.fine("Updated=" + no); + } // updateProductPO + + + /** + * Get Commitments + * @param doc document + * @param maxQty Qty invoiced/matched + * @param C_InvoiceLine_ID invoice line + * @return commitments (order lines) + */ + protected static DocLine[] getCommitments(Doc doc, BigDecimal maxQty, int C_InvoiceLine_ID) + { + int precision = -1; + // + ArrayList list = new ArrayList(); + String sql = "SELECT * FROM C_OrderLine ol " + + "WHERE EXISTS " + + "(SELECT * FROM C_InvoiceLine il " + + "WHERE il.C_OrderLine_ID=ol.C_OrderLine_ID" + + " AND il.C_InvoiceLine_ID=?)" + + " OR EXISTS " + + "(SELECT * FROM M_MatchPO po " + + "WHERE po.C_OrderLine_ID=ol.C_OrderLine_ID" + + " AND po.C_InvoiceLine_ID=?)"; + PreparedStatement pstmt = null; + try + { + pstmt = DB.prepareStatement (sql, null); + pstmt.setInt (1, C_InvoiceLine_ID); + pstmt.setInt (2, C_InvoiceLine_ID); + ResultSet rs = pstmt.executeQuery (); + while (rs.next ()) + { + if (maxQty.signum() == 0) + continue; + MOrderLine line = new MOrderLine (doc.getCtx(), rs, null); + DocLine docLine = new DocLine (line, doc); + // Currency + if (precision == -1) + { + doc.setC_Currency_ID(docLine.getC_Currency_ID()); + precision = MCurrency.getStdPrecision(doc.getCtx(), docLine.getC_Currency_ID()); + } + // Qty + BigDecimal Qty = line.getQtyOrdered().max(maxQty); + docLine.setQty(Qty, false); + // + BigDecimal PriceActual = line.getPriceActual(); + BigDecimal PriceCost = line.getPriceCost(); + BigDecimal LineNetAmt = null; + if (PriceCost != null && PriceCost.signum() != 0) + LineNetAmt = Qty.multiply(PriceCost); + else if (Qty.equals(maxQty)) + LineNetAmt = line.getLineNetAmt(); + else + LineNetAmt = Qty.multiply(PriceActual); + maxQty = maxQty.subtract(Qty); + + docLine.setAmount (LineNetAmt); // DR + BigDecimal PriceList = line.getPriceList(); + int C_Tax_ID = docLine.getC_Tax_ID(); + // Correct included Tax + if (C_Tax_ID != 0 && line.getParent().isTaxIncluded()) + { + MTax tax = MTax.get(doc.getCtx(), C_Tax_ID); + if (!tax.isZeroTax()) + { + BigDecimal LineNetAmtTax = tax.calculateTax(LineNetAmt, true, precision); + s_log.fine("LineNetAmt=" + LineNetAmt + " - Tax=" + LineNetAmtTax); + LineNetAmt = LineNetAmt.subtract(LineNetAmtTax); + BigDecimal PriceListTax = tax.calculateTax(PriceList, true, precision); + PriceList = PriceList.subtract(PriceListTax); + } + } // correct included Tax + + docLine.setAmount (LineNetAmt, PriceList, Qty); + list.add(docLine); + } + rs.close (); + pstmt.close (); + pstmt = null; + } + catch (Exception e) + { + s_log.log (Level.SEVERE, sql, e); + } + try + { + if (pstmt != null) + pstmt.close (); + pstmt = null; + } + catch (Exception e) + { + pstmt = null; + } + + // Return Array + DocLine[] dl = new DocLine[list.size()]; + list.toArray(dl); + return dl; + } // getCommitments + + /** + * Get Commitment Release. + * Called from MatchInv for accrual and Allocation for Cash Based + * @param as accounting schema + * @param doc doc + * @param Qty qty invoiced/matched + * @param C_InvoiceLine_ID line + * @param multiplier 1 for accrual + * @return Fact + */ + protected static Fact getCommitmentRelease(MAcctSchema as, Doc doc, + BigDecimal Qty, int C_InvoiceLine_ID, BigDecimal multiplier) + { + Fact fact = new Fact(doc, as, Fact.POST_Commitment); + DocLine[] commitments = Doc_Order.getCommitments(doc, Qty, + C_InvoiceLine_ID); + + BigDecimal total = Env.ZERO; + int C_Currency_ID = -1; + for (int i = 0; i < commitments.length; i++) + { + DocLine line = commitments[i]; + if (C_Currency_ID == -1) + C_Currency_ID = line.getC_Currency_ID(); + else if (C_Currency_ID != line.getC_Currency_ID()) + { + doc.p_Error = "Different Currencies of Order Lines"; + s_log.log(Level.SEVERE, doc.p_Error); + return null; + } + BigDecimal cost = line.getAmtSource().multiply(multiplier); + total = total.add(cost); + + // Account + MAccount expense = line.getAccount(ProductCost.ACCTTYPE_P_Expense, as); + FactLine fl = fact.createLine (line, expense, + C_Currency_ID, null, cost); + } + // Offset + MAccount offset = doc.getAccount(ACCTTYPE_CommitmentOffset, as); + if (offset == null) + { + doc.p_Error = "@NotFound@ @CommitmentOffset_Acct@"; + s_log.log(Level.SEVERE, doc.p_Error); + return null; + } + fact.createLine (null, offset, + C_Currency_ID, total, null); + return fact; + } // getCommitmentRelease + + + /************************************************************************** + * Update Product Info (old) + * - Costing (PriceLastPO) + * - PO (PriceLastPO) + * @param C_AcctSchema_ID accounting schema + * @deprecated old costing + */ + private void updateProductInfo (int C_AcctSchema_ID) + { + log.fine("C_Order_ID=" + get_ID()); + + /** @todo Last.. would need to compare document/last updated date + * would need to maintain LastPriceUpdateDate on _PO and _Costing */ + + // update Product Costing + // requires existence of currency conversion !! + // if there are multiple lines of the same product last price uses first + StringBuffer sql = new StringBuffer ( + "UPDATE M_Product_Costing pc " + + "SET PriceLastPO = " + + "(SELECT currencyConvert(ol.PriceActual,ol.C_Currency_ID,a.C_Currency_ID,o.DateOrdered,o.C_ConversionType_ID,o.AD_Client_ID,o.AD_Org_ID) " + + "FROM C_Order o, C_OrderLine ol, C_AcctSchema a " + + "WHERE o.C_Order_ID=ol.C_Order_ID" + + " AND pc.M_Product_ID=ol.M_Product_ID AND pc.C_AcctSchema_ID=a.C_AcctSchema_ID" + + " AND ROWNUM=1" + + " AND pc.C_AcctSchema_ID=").append(C_AcctSchema_ID).append(" AND o.C_Order_ID=") + .append(get_ID()).append(") ") + .append("WHERE EXISTS (SELECT * " + + "FROM C_Order o, C_OrderLine ol, C_AcctSchema a " + + "WHERE o.C_Order_ID=ol.C_Order_ID" + + " AND pc.M_Product_ID=ol.M_Product_ID AND pc.C_AcctSchema_ID=a.C_AcctSchema_ID" + + " AND pc.C_AcctSchema_ID=").append(C_AcctSchema_ID).append(" AND o.C_Order_ID=") + .append(get_ID()).append(")"); + int no = DB.executeUpdate(sql.toString(), getTrxName()); + log.fine("M_Product_Costing - Updated=" + no); + } // updateProductInfo + +} // Doc_Order diff --git a/serverRoot/src/main/server/org/compiere/acct/Doc_Payment.java b/serverRoot/src/main/server/org/compiere/acct/Doc_Payment.java new file mode 100644 index 0000000000..766c357f74 --- /dev/null +++ b/serverRoot/src/main/server/org/compiere/acct/Doc_Payment.java @@ -0,0 +1,183 @@ +/****************************************************************************** + * 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.acct; + +import java.math.*; +import java.sql.*; +import java.util.*; +import java.util.logging.*; + +import org.compiere.model.*; +import org.compiere.util.*; + +/** + * Post Invoice Documents. + *
+ *  Table:              C_Payment (335)
+ *  Document Types      ARP, APP
+ *  
+ * @author Jorg Janke + * @version $Id: Doc_Payment.java,v 1.3 2006/07/30 00:53:33 jjanke Exp $ + */ +public class Doc_Payment extends Doc +{ + /** + * Constructor + * @param ass accounting schemata + * @param rs record + * @param trxName trx + */ + protected Doc_Payment (MAcctSchema[] ass, ResultSet rs, String trxName) + { + super (ass, MPayment.class, rs, null, trxName); + } // Doc_Payment + + /** Tender Type */ + private String m_TenderType = null; + /** Prepayment */ + private boolean m_Prepayment = false; + /** Bank Account */ + private int m_C_BankAccount_ID = 0; + + /** + * Load Specific Document Details + * @return error message or null + */ + protected String loadDocumentDetails () + { + MPayment pay = (MPayment)getPO(); + setDateDoc(pay.getDateTrx()); + m_TenderType = pay.getTenderType(); + m_Prepayment = pay.isPrepayment(); + m_C_BankAccount_ID = pay.getC_BankAccount_ID(); + // Amount + setAmount(Doc.AMTTYPE_Gross, pay.getPayAmt()); + return null; + } // loadDocumentDetails + + + /************************************************************************** + * Get Source Currency Balance - always zero + * @return Zero (always balanced) + */ + public BigDecimal getBalance() + { + BigDecimal retValue = Env.ZERO; + // log.config( toString() + " Balance=" + retValue); + return retValue; + } // getBalance + + /** + * Create Facts (the accounting logic) for + * ARP, APP. + *
+	 *  ARP
+	 *      BankInTransit   DR
+	 *      UnallocatedCash         CR
+	 *      or Charge/C_Prepayment
+	 *  APP
+	 *      PaymentSelect   DR
+	 *      or Charge/V_Prepayment
+	 *      BankInTransit           CR
+	 *  CashBankTransfer
+	 *      -
+	 *  
+ * @param as accounting schema + * @return Fact + */ + public ArrayList createFacts (MAcctSchema as) + { + // create Fact Header + Fact fact = new Fact(this, as, Fact.POST_Actual); + // Cash Transfer + if ("X".equals(m_TenderType)) + { + ArrayList facts = new ArrayList(); + facts.add(fact); + return facts; + } + + int AD_Org_ID = getBank_Org_ID(); // Bank Account Org + if (getDocumentType().equals(DOCTYPE_ARReceipt)) + { + // Asset + FactLine fl = fact.createLine(null, getAccount(Doc.ACCTTYPE_BankInTransit, as), + getC_Currency_ID(), getAmount(), null); + if (fl != null && AD_Org_ID != 0) + fl.setAD_Org_ID(AD_Org_ID); + // + MAccount acct = null; + if (getC_Charge_ID() != 0) + acct = MCharge.getAccount(getC_Charge_ID(), as, getAmount()); + else if (m_Prepayment) + acct = getAccount(Doc.ACCTTYPE_C_Prepayment, as); + else + acct = getAccount(Doc.ACCTTYPE_UnallocatedCash, as); + fl = fact.createLine(null, acct, + getC_Currency_ID(), null, getAmount()); + if (fl != null && AD_Org_ID != 0 + && getC_Charge_ID() == 0) // don't overwrite charge + fl.setAD_Org_ID(AD_Org_ID); + } + // APP + else if (getDocumentType().equals(DOCTYPE_APPayment)) + { + MAccount acct = null; + if (getC_Charge_ID() != 0) + acct = MCharge.getAccount(getC_Charge_ID(), as, getAmount()); + else if (m_Prepayment) + acct = getAccount(Doc.ACCTTYPE_V_Prepayment, as); + else + acct = getAccount(Doc.ACCTTYPE_PaymentSelect, as); + FactLine fl = fact.createLine(null, acct, + getC_Currency_ID(), getAmount(), null); + if (fl != null && AD_Org_ID != 0 + && getC_Charge_ID() == 0) // don't overwrite charge + fl.setAD_Org_ID(AD_Org_ID); + + // Asset + fl = fact.createLine(null, getAccount(Doc.ACCTTYPE_BankInTransit, as), + getC_Currency_ID(), null, getAmount()); + if (fl != null && AD_Org_ID != 0) + fl.setAD_Org_ID(AD_Org_ID); + } + else + { + p_Error = "DocumentType unknown: " + getDocumentType(); + log.log(Level.SEVERE, p_Error); + fact = null; + } + // + ArrayList facts = new ArrayList(); + facts.add(fact); + return facts; + } // createFact + + /** + * Get AD_Org_ID from Bank Account + * @return AD_Org_ID or 0 + */ + private int getBank_Org_ID () + { + if (m_C_BankAccount_ID == 0) + return 0; + // + MBankAccount ba = MBankAccount.get(getCtx(), m_C_BankAccount_ID); + return ba.getAD_Org_ID(); + } // getBank_Org_ID + +} // Doc_Payment diff --git a/serverRoot/src/main/server/org/compiere/acct/Doc_Production.java b/serverRoot/src/main/server/org/compiere/acct/Doc_Production.java new file mode 100644 index 0000000000..fb2615d4c0 --- /dev/null +++ b/serverRoot/src/main/server/org/compiere/acct/Doc_Production.java @@ -0,0 +1,216 @@ +/****************************************************************************** + * 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.acct; + +import java.math.*; +import java.sql.*; +import java.util.*; + +import org.compiere.model.*; +import java.util.logging.*; +import org.compiere.util.*; + +/** + * Post Invoice Documents. + *
+ *  Table:              M_Production (325)
+ *  Document Types:     MMP
+ *  
+ * @author Jorg Janke + * @version $Id: Doc_Production.java,v 1.3 2006/07/30 00:53:33 jjanke Exp $ + */ +public class Doc_Production extends Doc +{ + /** + * Constructor + * @param ass accounting schemata + * @param rs record + * @param trxName trx + */ + public Doc_Production (MAcctSchema[] ass, ResultSet rs, String trxName) + { + super (ass, X_M_Production.class, rs, DOCTYPE_MatProduction, trxName); + } // Doc_Production + + /** + * Load Document Details + * @return error message or null + */ + protected String loadDocumentDetails() + { + setC_Currency_ID (NO_CURRENCY); + X_M_Production prod = (X_M_Production)getPO(); + setDateDoc (prod.getMovementDate()); + setDateAcct(prod.getMovementDate()); + // Contained Objects + p_lines = loadLines(prod); + log.fine("Lines=" + p_lines.length); + return null; + } // loadDocumentDetails + + /** + * Load Invoice Line + * @param prod production + * @return DoaLine Array + */ + private DocLine[] loadLines(X_M_Production prod) + { + ArrayList list = new ArrayList(); + // Production + // -- ProductionPlan + // -- -- ProductionLine - the real level + String sqlPP = "SELECT * FROM M_ProductionPlan pp " + + "WHERE pp.M_Production_ID=? " + + "ORDER BY pp.Line"; + String sqlPL = "SELECT * FROM M_ProductionLine pl " + + "WHERE pl.M_ProductionPlan_ID=? " + + "ORDER BY pl.Line"; + + try + { + PreparedStatement pstmtPP = DB.prepareStatement(sqlPP, getTrxName()); + pstmtPP.setInt(1, get_ID()); + ResultSet rsPP = pstmtPP.executeQuery(); + // + while (rsPP.next()) + { + int M_Product_ID = rsPP.getInt("M_Product_ID"); + int M_ProductionPlan_ID = rsPP.getInt("M_ProductionPlan_ID"); + // + try + { + PreparedStatement pstmtPL = DB.prepareStatement(sqlPL, getTrxName()); + pstmtPL.setInt(1, M_ProductionPlan_ID); + ResultSet rsPL = pstmtPL.executeQuery(); + while (rsPL.next()) + { + X_M_ProductionLine line = new X_M_ProductionLine(getCtx(), rsPL, getTrxName()); + if (line.getMovementQty().signum() == 0) + { + log.info("LineQty=0 - " + line); + continue; + } + DocLine docLine = new DocLine (line, this); + docLine.setQty (line.getMovementQty(), false); + // Identify finished BOM Product + docLine.setProductionBOM(line.getM_Product_ID() == M_Product_ID); + // + log.fine(docLine.toString()); + list.add (docLine); + } + rsPL.close(); + pstmtPL.close(); + } + catch (Exception ee) + { + log.log(Level.SEVERE, sqlPL, ee); + } + } + rsPP.close(); + pstmtPP.close(); + } + catch (SQLException e) + { + log.log(Level.SEVERE, sqlPP, e); + } + // Return Array + DocLine[] dl = new DocLine[list.size()]; + list.toArray(dl); + return dl; + } // loadLines + + /** + * Get Balance + * @return Zero (always balanced) + */ + public BigDecimal getBalance() + { + BigDecimal retValue = Env.ZERO; + return retValue; + } // getBalance + + /** + * Create Facts (the accounting logic) for + * MMP. + *
+	 *  Production
+	 *      Inventory       DR      CR
+	 *  
+ * @param as account schema + * @return Fact + */ + public ArrayList createFacts (MAcctSchema as) + { + // create Fact Header + Fact fact = new Fact(this, as, Fact.POST_Actual); + setC_Currency_ID (as.getC_Currency_ID()); + + // Line pointer + FactLine fl = null; + for (int i = 0; i < p_lines.length; i++) + { + DocLine line = p_lines[i]; + // Calculate Costs + BigDecimal costs = null; + if (line.isProductionBOM()) + { + // Get BOM Cost - Sum of individual lines + BigDecimal bomCost = Env.ZERO; + for (int ii = 0; ii < p_lines.length; ii++) + { + DocLine line0 = p_lines[ii]; + if (line0.getM_ProductionPlan_ID() != line.getM_ProductionPlan_ID()) + continue; + if (!line0.isProductionBOM()) + bomCost = bomCost.add(line0.getProductCosts(as, line.getAD_Org_ID(), false)); + } + costs = bomCost.negate(); + } + else + costs = line.getProductCosts(as, line.getAD_Org_ID(), false); + + // Inventory DR CR + fl = fact.createLine(line, + line.getAccount(ProductCost.ACCTTYPE_P_Asset, as), + as.getC_Currency_ID(), costs); + if (fl == null) + { + p_Error = "No Costs for Line " + line.getLine() + " - " + line; + return null; + } + fl.setM_Locator_ID(line.getM_Locator_ID()); + fl.setQty(line.getQty()); + + // Cost Detail + String description = line.getDescription(); + if (description == null) + description = ""; + if (line.isProductionBOM()) + description += "(*)"; + MCostDetail.createProduction(as, line.getAD_Org_ID(), + line.getM_Product_ID(), line.getM_AttributeSetInstance_ID(), + line.get_ID(), 0, + costs, line.getQty(), + description, getTrxName()); + } + // + ArrayList facts = new ArrayList(); + facts.add(fact); + return facts; + } // createFact + +} // Doc_Production diff --git a/serverRoot/src/main/server/org/compiere/acct/Doc_ProjectIssue.java b/serverRoot/src/main/server/org/compiere/acct/Doc_ProjectIssue.java new file mode 100644 index 0000000000..f128dc07bc --- /dev/null +++ b/serverRoot/src/main/server/org/compiere/acct/Doc_ProjectIssue.java @@ -0,0 +1,221 @@ +/****************************************************************************** + * 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.acct; + +import java.math.*; +import java.sql.*; + +import org.compiere.model.*; +import java.util.*; +import java.util.logging.*; +import org.compiere.util.*; + +/** + * Project Issue. + * Note: + * Will load the default GL Category. + * Set up a document type to set the GL Category. + * + * @author Jorg Janke + * @version $Id: Doc_ProjectIssue.java,v 1.2 2006/07/30 00:53:33 jjanke Exp $ + */ +public class Doc_ProjectIssue extends Doc +{ + /** + * Constructor + * @param ass accounting schemata + * @param rs record + * @param trxName trx + */ + public Doc_ProjectIssue (MAcctSchema[] ass, ResultSet rs, String trxName) + { + super (ass, MProjectIssue.class, rs, DOCTYPE_ProjectIssue, trxName); + } // Doc_ProjectIssue + + /** Pseudo Line */ + private DocLine m_line = null; + /** Issue */ + private MProjectIssue m_issue = null; + + /** + * Load Document Details + * @return error message or null + */ + protected String loadDocumentDetails() + { + setC_Currency_ID(NO_CURRENCY); + m_issue = (MProjectIssue)getPO(); + setDateDoc (m_issue.getMovementDate()); + setDateAcct(m_issue.getMovementDate()); + + // Pseudo Line + m_line = new DocLine (m_issue, this); + m_line.setQty (m_issue.getMovementQty(), true); // sets Trx and Storage Qty + + // Pseudo Line Check + if (m_line.getM_Product_ID() == 0) + log.warning(m_line.toString() + " - No Product"); + log.fine(m_line.toString()); + return null; + } // loadDocumentDetails + + /** + * Get DocumentNo + * @return document no + */ + public String getDocumentNo () + { + MProject p = m_issue.getParent(); + if (p != null) + return p.getValue() + " #" + m_issue.getLine(); + return "(" + m_issue.get_ID() + ")"; + } // getDocumentNo + + /** + * Get Balance + * @return Zero (always balanced) + */ + public BigDecimal getBalance() + { + BigDecimal retValue = Env.ZERO; + return retValue; + } // getBalance + + /** + * Create Facts (the accounting logic) for + * PJI + *
+	 *  Issue
+	 *      ProjectWIP      DR
+	 *      Inventory               CR
+	 *  
+ * Project Account is either Asset or WIP depending on Project Type + * @param as accounting schema + * @return Fact + */ + public ArrayList createFacts (MAcctSchema as) + { + // create Fact Header + Fact fact = new Fact(this, as, Fact.POST_Actual); + setC_Currency_ID (as.getC_Currency_ID()); + + MProject project = new MProject (getCtx(), m_issue.getC_Project_ID(), null); + String ProjectCategory = project.getProjectCategory(); + MProduct product = MProduct.get(getCtx(), m_issue.getM_Product_ID()); + + // Line pointers + FactLine dr = null; + FactLine cr = null; + + // Issue Cost + BigDecimal cost = null; + if (m_issue.getM_InOutLine_ID() != 0) + cost = getPOCost(as); + else if (m_issue.getS_TimeExpenseLine_ID() != 0) + cost = getLaborCost(as); + if (cost == null) // standard Product Costs + cost = m_line.getProductCosts(as, getAD_Org_ID(), false); + + // Project DR + int acctType = ACCTTYPE_ProjectWIP; + if (MProject.PROJECTCATEGORY_AssetProject.equals(ProjectCategory)) + acctType = ACCTTYPE_ProjectAsset; + dr = fact.createLine(m_line, + getAccount(acctType, as), as.getC_Currency_ID(), cost, null); + dr.setQty(m_line.getQty().negate()); + + // Inventory CR + acctType = ProductCost.ACCTTYPE_P_Asset; + if (product.isService()) + acctType = ProductCost.ACCTTYPE_P_Expense; + cr = fact.createLine(m_line, + m_line.getAccount(acctType, as), + as.getC_Currency_ID(), null, cost); + cr.setM_Locator_ID(m_line.getM_Locator_ID()); + cr.setLocationFromLocator(m_line.getM_Locator_ID(), true); // from Loc + // + ArrayList facts = new ArrayList(); + facts.add(fact); + return facts; + } // createFact + + /** + * Get PO Costs in Currency of AcctSchema + * @param as Account Schema + * @return Unit PO Cost + */ + private BigDecimal getPOCost(MAcctSchema as) + { + BigDecimal retValue = null; + // Uses PO Date + String sql = "SELECT currencyConvert(ol.PriceActual, o.C_Currency_ID, ?, o.DateOrdered, o.C_ConversionType_ID, ?, ?) " + + "FROM C_OrderLine ol" + + " INNER JOIN M_InOutLine iol ON (iol.C_OrderLine_ID=ol.C_OrderLine_ID)" + + " INNER JOIN C_Order o ON (o.C_Order_ID=ol.C_Order_ID) " + + "WHERE iol.M_InOutLine_ID=?"; + PreparedStatement pstmt = null; + try + { + pstmt = DB.prepareStatement(sql, null); + pstmt.setInt(1, as.getC_Currency_ID()); + pstmt.setInt(2, getAD_Client_ID()); + pstmt.setInt(3, getAD_Org_ID()); + pstmt.setInt(4, m_issue.getM_InOutLine_ID()); + ResultSet rs = pstmt.executeQuery(); + if (rs.next()) + { + retValue = rs.getBigDecimal(1); + log.fine("POCost = " + retValue); + } + else + log.warning("Not found for M_InOutLine_ID=" + m_issue.getM_InOutLine_ID()); + rs.close(); + pstmt.close(); + pstmt = null; + } + catch (Exception e) + { + log.log(Level.SEVERE, sql, e); + } + try + { + if (pstmt != null) + pstmt.close(); + pstmt = null; + } + catch (Exception e) + { + pstmt = null; + } + return retValue; + } // getPOCost(); + + /** + * Get Labor Cost from Expense Report + * @param as Account Schema + * @return Unit Labor Cost + */ + private BigDecimal getLaborCost(MAcctSchema as) + { + BigDecimal retValue = null; + + /** TODO Labor Cost */ + return retValue; + } // getLaborCost + +} // DocProjectIssue + diff --git a/serverRoot/src/main/server/org/compiere/acct/Doc_Requisition.java b/serverRoot/src/main/server/org/compiere/acct/Doc_Requisition.java new file mode 100644 index 0000000000..75e6065ca2 --- /dev/null +++ b/serverRoot/src/main/server/org/compiere/acct/Doc_Requisition.java @@ -0,0 +1,153 @@ +/****************************************************************************** + * 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.acct; + +import java.math.*; +import java.util.*; +import java.util.logging.*; +import java.sql.*; + +import org.compiere.model.*; +import org.compiere.util.*; + +/** + * Post Order Documents. + * + *
+ *   Table:              M_Requisition
+ *   Document Types:     POR (Requisition)
+ * 
+ * + * @author Jorg Janke + * @version $Id: Doc_Requisition.java,v 1.3 2006/07/30 00:53:33 jjanke Exp $ + */ +public class Doc_Requisition extends Doc +{ + /** + * Constructor + * @param ass accounting schemata + * @param rs record + * @param trxName trx + */ + protected Doc_Requisition (MAcctSchema[] ass, ResultSet rs, String trxName) + { + super (ass, MRequisition.class, rs, DOCTYPE_PurchaseRequisition, trxName); + } // Doc_Requisition + + /** + * Load Specific Document Details + * @return error message or null + */ + protected String loadDocumentDetails () + { + setC_Currency_ID(NO_CURRENCY); + MRequisition req = (MRequisition)getPO(); + setDateDoc (req.getDateDoc()); + setDateAcct (req.getDateDoc()); + // Amounts + setAmount(AMTTYPE_Gross, req.getTotalLines()); + setAmount(AMTTYPE_Net, req.getTotalLines()); + // Contained Objects + p_lines = loadLines (req); + // log.fine( "Lines=" + p_lines.length + ", Taxes=" + m_taxes.length); + return null; + } // loadDocumentDetails + + /** + * Load Requisition Lines + * @param req requisition + * @return DocLine Array + */ + private DocLine[] loadLines (MRequisition req) + { + ArrayList list = new ArrayList (); + MRequisitionLine[] lines = req.getLines(); + for (int i = 0; i < lines.length; i++) + { + MRequisitionLine line = lines[i]; + DocLine docLine = new DocLine (line, this); + BigDecimal Qty = line.getQty(); + docLine.setQty (Qty, false); + BigDecimal PriceActual = line.getPriceActual(); + BigDecimal LineNetAmt = line.getLineNetAmt(); + docLine.setAmount (LineNetAmt); // DR + list.add (docLine); + } + // Return Array + DocLine[] dls = new DocLine[list.size ()]; + list.toArray (dls); + return dls; + } // loadLines + + /*************************************************************************** + * Get Source Currency Balance - subtracts line and tax amounts from total - + * no rounding + * + * @return positive amount, if total invoice is bigger than lines + */ + public BigDecimal getBalance () + { + BigDecimal retValue = new BigDecimal (0.0); + return retValue; + } // getBalance + + /*************************************************************************** + * Create Facts (the accounting logic) for POR. + *
+	 * Reservation
+	 * 	Expense		CR
+	 * 	Offset			DR
+	 * 
+ * @param as accounting schema + * @return Fact + */ + public ArrayList createFacts (MAcctSchema as) + { + ArrayList facts = new ArrayList(); + Fact fact = new Fact (this, as, Fact.POST_Reservation); + setC_Currency_ID(as.getC_Currency_ID()); + // + BigDecimal grossAmt = getAmount (Doc.AMTTYPE_Gross); + // Commitment + if (as.isCreateReservation ()) + { + BigDecimal total = Env.ZERO; + for (int i = 0; i < p_lines.length; i++) + { + DocLine line = p_lines[i]; + BigDecimal cost = line.getAmtSource(); + total = total.add (cost); + // Account + MAccount expense = line.getAccount(ProductCost.ACCTTYPE_P_Expense, as); + // + fact.createLine (line, expense, as.getC_Currency_ID(), cost, null); + } + // Offset + MAccount offset = getAccount (ACCTTYPE_CommitmentOffset, as); + if (offset == null) + { + p_Error = "@NotFound@ @CommitmentOffset_Acct@"; + log.log (Level.SEVERE, p_Error); + return null; + } + fact.createLine (null, offset, getC_Currency_ID(), null, total); + facts.add(fact); + } + + return facts; + } // createFact +} // Doc_Requisition diff --git a/serverRoot/src/main/server/org/compiere/acct/Fact.java b/serverRoot/src/main/server/org/compiere/acct/Fact.java new file mode 100644 index 0000000000..fbf8f092da --- /dev/null +++ b/serverRoot/src/main/server/org/compiere/acct/Fact.java @@ -0,0 +1,860 @@ +/****************************************************************************** + * 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.acct; + +import java.math.*; +import java.util.*; +import org.compiere.model.*; +import org.compiere.util.*; + +/** + * Accounting Fact + * + * @author Jorg Janke + * @version $Id: Fact.java,v 1.2 2006/07/30 00:53:33 jjanke Exp $ + */ +public final class Fact +{ + /** + * Constructor + * @param document pointer to document + * @param acctSchema Account Schema to create accounts + * @param defaultPostingType the default Posting type (actual,..) for this posting + */ + public Fact (Doc document, MAcctSchema acctSchema, String defaultPostingType) + { + m_doc = document; + m_acctSchema = acctSchema; + m_postingType = defaultPostingType; + // + log.config(toString()); + } // Fact + + + /** Log */ + private CLogger log = CLogger.getCLogger(getClass()); + + /** Document */ + private Doc m_doc = null; + /** Accounting Schema */ + private MAcctSchema m_acctSchema = null; + /** Transaction */ + private String m_trxName; + + /** Posting Type */ + private String m_postingType = null; + + /** Actual Balance Type */ + public static final String POST_Actual = MFactAcct.POSTINGTYPE_Actual; + /** Budget Balance Type */ + public static final String POST_Budget = MFactAcct.POSTINGTYPE_Budget;; + /** Encumbrance Posting */ + public static final String POST_Commitment = MFactAcct.POSTINGTYPE_Commitment; + /** Encumbrance Posting */ + public static final String POST_Reservation = MFactAcct.POSTINGTYPE_Reservation; + + + /** Is Converted */ + private boolean m_converted = false; + + /** Lines */ + private ArrayList m_lines = new ArrayList(); + + + /** + * Dispose + */ + public void dispose() + { + m_lines.clear(); + m_lines = null; + } // dispose + + /** + * Create and convert Fact Line. + * Used to create a DR and/or CR entry + * + * @param docLine the document line or null + * @param account if null, line is not created + * @param C_Currency_ID the currency + * @param debitAmt debit amount, can be null + * @param creditAmt credit amount, can be null + * @return Fact Line + */ + public FactLine createLine (DocLine docLine, MAccount account, + int C_Currency_ID, BigDecimal debitAmt, BigDecimal creditAmt) + { + // log.fine("createLine - " + account + " - Dr=" + debitAmt + ", Cr=" + creditAmt); + + // Data Check + if (account == null) + { + log.info("No account for " + docLine + + ": Amt=" + debitAmt + "/" + creditAmt + + " - " + toString()); + return null; + } + // + FactLine line = new FactLine (m_doc.getCtx(), m_doc.get_Table_ID(), + m_doc.get_ID(), + docLine == null ? 0 : docLine.get_ID(), m_trxName); + // Set Info & Account + line.setDocumentInfo(m_doc, docLine); + line.setPostingType(m_postingType); + line.setAccount(m_acctSchema, account); + + // Amounts - one needs to not zero + if (!line.setAmtSource(C_Currency_ID, debitAmt, creditAmt)) + { + if (docLine == null || docLine.getQty() == null || docLine.getQty().signum() == 0) + { + log.fine("Both amounts & qty = 0/Null - " + docLine + + " - " + toString()); + return null; + } + log.fine("Both amounts = 0/Null, Qty=" + docLine.getQty() + " - " + docLine + + " - " + toString()); + } + // Convert + line.convert(); + // Optionally overwrite Acct Amount + if (docLine != null + && (docLine.getAmtAcctDr() != null || docLine.getAmtAcctCr() != null)) + line.setAmtAcct(docLine.getAmtAcctDr(), docLine.getAmtAcctCr()); + // + log.fine(line.toString()); + add(line); + return line; + } // createLine + + /** + * Add Fact Line + * @param line fact line + */ + void add (FactLine line) + { + m_lines.add(line); + } // add + + /** + * Create and convert Fact Line. + * Used to create either a DR or CR entry + * + * @param docLine Document Line or null + * @param accountDr Account to be used if Amt is DR balance + * @param accountCr Account to be used if Amt is CR balance + * @param C_Currency_ID Currency + * @param Amt if negative Cr else Dr + * @return FactLine + */ + public FactLine createLine (DocLine docLine, MAccount accountDr, MAccount accountCr, + int C_Currency_ID, BigDecimal Amt) + { + if (Amt.signum() < 0) + return createLine (docLine, accountCr, C_Currency_ID, null, Amt.abs()); + else + return createLine (docLine, accountDr, C_Currency_ID, Amt, null); + } // createLine + + /** + * Create and convert Fact Line. + * Used to create either a DR or CR entry + * + * @param docLine Document line or null + * @param account Account to be used + * @param C_Currency_ID Currency + * @param Amt if negative Cr else Dr + * @return FactLine + */ + public FactLine createLine (DocLine docLine, MAccount account, + int C_Currency_ID, BigDecimal Amt) + { + if (Amt.signum() < 0) + return createLine (docLine, account, C_Currency_ID, null, Amt.abs()); + else + return createLine (docLine, account, C_Currency_ID, Amt, null); + } // createLine + + /** + * Is Posting Type + * @param PostingType - see POST_* + * @return true if document is posting type + */ + public boolean isPostingType (String PostingType) + { + return m_postingType.equals(PostingType); + } // isPostingType + + /** + * Is converted + * @return true if converted + */ + public boolean isConverted() + { + return m_converted; + } // isConverted + + /** + * Get AcctSchema + * @return AcctSchema + */ + public MAcctSchema getAcctSchema() + { + return m_acctSchema; + } // getAcctSchema + + + /************************************************************************** + * Are the lines Source Balanced + * @return true if source lines balanced + */ + public boolean isSourceBalanced() + { + // No lines -> balanded + if (m_lines.size() == 0) + return true; + BigDecimal balance = getSourceBalance(); + boolean retValue = balance.signum() == 0; + if (retValue) + log.finer(toString()); + else + log.warning ("NO - Diff=" + balance + " - " + toString()); + return retValue; + } // isSourceBalanced + + /** + * Return Source Balance + * @return source balance + */ + protected BigDecimal getSourceBalance() + { + BigDecimal result = Env.ZERO; + for (int i = 0; i < m_lines.size(); i++) + { + FactLine line = (FactLine)m_lines.get(i); + result = result.add (line.getSourceBalance()); + } + // log.fine("getSourceBalance - " + result.toString()); + return result; + } // getSourceBalance + + /** + * Create Source Line for Suspense Balancing. + * Only if Suspense Balancing is enabled and not a multi-currency document + * (double check as otherwise the rule should not have fired) + * If not balanced create balancing entry in currency of the document + * @return FactLine + */ + public FactLine balanceSource() + { + if (!m_acctSchema.isSuspenseBalancing() || m_doc.isMultiCurrency()) + return null; + BigDecimal diff = getSourceBalance(); + log.finer("Diff=" + diff); + + // new line + FactLine line = new FactLine (m_doc.getCtx(), m_doc.get_Table_ID(), + m_doc.get_ID(), 0, m_trxName); + line.setDocumentInfo(m_doc, null); + line.setPostingType(m_postingType); + + // Amount + if (diff.signum() < 0) // negative balance => DR + line.setAmtSource(m_doc.getC_Currency_ID(), diff.abs(), Env.ZERO); + else // positive balance => CR + line.setAmtSource(m_doc.getC_Currency_ID(), Env.ZERO, diff); + + // Account + line.setAccount(m_acctSchema, m_acctSchema.getSuspenseBalancing_Acct()); + + // Convert + line.convert(); + // + log.fine(line.toString()); + m_lines.add(line); + return line; + } // balancingSource + + + /************************************************************************** + * Are all segments balanced + * @return true if segments are balanced + */ + public boolean isSegmentBalanced() + { + if (m_lines.size() == 0) + return true; + + MAcctSchemaElement[] elements = m_acctSchema.getAcctSchemaElements(); + // check all balancing segments + for (int i = 0; i < elements.length; i++) + { + MAcctSchemaElement ase = elements[i]; + if (ase.isBalanced() && !isSegmentBalanced (ase.getElementType())) + return false; + } + return true; + } // isSegmentBalanced + + /** + * Is Source Segment balanced. + * @param segmentType - see AcctSchemaElement.SEGMENT_* + * Implemented only for Org + * Other sensible candidates are Project, User1/2 + * @return true if segments are balanced + */ + public boolean isSegmentBalanced (String segmentType) + { + if (segmentType.equals(MAcctSchemaElement.ELEMENTTYPE_Organization)) + { + HashMap map = new HashMap(); + // Add up values by key + for (int i = 0; i < m_lines.size(); i++) + { + FactLine line = (FactLine)m_lines.get(i); + Integer key = new Integer(line.getAD_Org_ID()); + BigDecimal bal = line.getSourceBalance(); + BigDecimal oldBal = (BigDecimal)map.get(key); + if (oldBal != null) + bal = bal.add(oldBal); + map.put(key, bal); + // System.out.println("Add Key=" + key + ", Bal=" + bal + " <- " + line); + } + // check if all keys are zero + Iterator values = map.values().iterator(); + while (values.hasNext()) + { + BigDecimal bal = (BigDecimal)values.next(); + if (bal.signum() != 0) + { + map.clear(); + log.warning ("(" + segmentType + ") NO - " + toString() + ", Balance=" + bal); + return false; + } + } + map.clear(); + log.finer("(" + segmentType + ") - " + toString()); + return true; + } + log.finer("(" + segmentType + ") (not checked) - " + toString()); + return true; + } // isSegmentBalanced + + /** + * Balance all segments. + * - For all balancing segments + * - For all segment values + * - If balance <> 0 create dueTo/dueFrom line + * overwriting the segment value + */ + public void balanceSegments() + { + MAcctSchemaElement[] elements = m_acctSchema.getAcctSchemaElements(); + // check all balancing segments + for (int i = 0; i < elements.length; i++) + { + MAcctSchemaElement ase = elements[i]; + if (ase.isBalanced()) + balanceSegment (ase.getElementType()); + } + } // balanceSegments + + /** + * Balance Source Segment + * @param elementType segment element type + */ + private void balanceSegment (String elementType) + { + // no lines -> balanced + if (m_lines.size() == 0) + return; + + log.fine ("(" + elementType + ") - " + toString()); + + // Org + if (elementType.equals(MAcctSchemaElement.ELEMENTTYPE_Organization)) + { + HashMap map = new HashMap(); + // Add up values by key + for (int i = 0; i < m_lines.size(); i++) + { + FactLine line = (FactLine)m_lines.get(i); + Integer key = new Integer(line.getAD_Org_ID()); + // BigDecimal balance = line.getSourceBalance(); + Balance oldBalance = (Balance)map.get(key); + if (oldBalance == null) + { + oldBalance = new Balance (line.getAmtSourceDr(), line.getAmtSourceCr()); + map.put(key, oldBalance); + } + else + oldBalance.add(line.getAmtSourceDr(), line.getAmtSourceCr()); + // log.info ("Key=" + key + ", Balance=" + balance + " - " + line); + } + + // Create entry for non-zero element + Iterator keys = map.keySet().iterator(); + while (keys.hasNext()) + { + Integer key = (Integer)keys.next(); + Balance difference = (Balance)map.get(key); + log.info (elementType + "=" + key + ", " + difference); + // + if (!difference.isZeroBalance()) + { + // Create Balancing Entry + FactLine line = new FactLine (m_doc.getCtx(), m_doc.get_Table_ID(), + m_doc.get_ID(), 0, m_trxName); + line.setDocumentInfo(m_doc, null); + line.setPostingType(m_postingType); + // Amount & Account + if (difference.getBalance().signum() < 0) + { + if (difference.isReversal()) + { + line.setAmtSource(m_doc.getC_Currency_ID(), Env.ZERO, difference.getPostBalance()); + line.setAccount(m_acctSchema, m_acctSchema.getDueTo_Acct(elementType)); + } + else + { + line.setAmtSource(m_doc.getC_Currency_ID(), difference.getPostBalance(), Env.ZERO); + line.setAccount(m_acctSchema, m_acctSchema.getDueFrom_Acct(elementType)); + } + } + else + { + if (difference.isReversal()) + { + line.setAmtSource(m_doc.getC_Currency_ID(), difference.getPostBalance(), Env.ZERO); + line.setAccount(m_acctSchema, m_acctSchema.getDueFrom_Acct(elementType)); + } + else + { + line.setAmtSource(m_doc.getC_Currency_ID(), Env.ZERO, difference.getPostBalance()); + line.setAccount(m_acctSchema, m_acctSchema.getDueTo_Acct(elementType)); + } + } + line.convert(); + line.setAD_Org_ID(key.intValue()); + // + m_lines.add(line); + log.fine("(" + elementType + ") - " + line); + } + } + map.clear(); + } + } // balanceSegment + + + /************************************************************************** + * Are the lines Accounting Balanced + * @return true if accounting lines are balanced + */ + public boolean isAcctBalanced() + { + // no lines -> balanced + if (m_lines.size() == 0) + return true; + BigDecimal balance = getAcctBalance(); + boolean retValue = balance.signum() == 0; + if (retValue) + log.finer(toString()); + else + log.warning("NO - Diff=" + balance + " - " + toString()); + return retValue; + } // isAcctBalanced + + /** + * Return Accounting Balance + * @return true if accounting lines are balanced + */ + protected BigDecimal getAcctBalance() + { + BigDecimal result = Env.ZERO; + for (int i = 0; i < m_lines.size(); i++) + { + FactLine line = (FactLine)m_lines.get(i); + result = result.add(line.getAcctBalance()); + } + // log.fine(result.toString()); + return result; + } // getAcctBalance + + /** + * Balance Accounting Currency. + * If the accounting currency is not balanced, + * if Currency balancing is enabled + * create a new line using the currency balancing account with zero source balance + * or + * adjust the line with the largest balance sheet account + * or if no balance sheet account exist, the line with the largest amount + * @return FactLine + */ + public FactLine balanceAccounting() + { + BigDecimal diff = getAcctBalance(); // DR-CR + log.fine("Balance=" + diff + + ", CurrBal=" + m_acctSchema.isCurrencyBalancing() + + " - " + toString()); + FactLine line = null; + + BigDecimal BSamount = Env.ZERO; + FactLine BSline = null; + BigDecimal PLamount = Env.ZERO; + FactLine PLline = null; + + // Find line biggest BalanceSheet or P&L line + for (int i = 0; i < m_lines.size(); i++) + { + FactLine l = (FactLine)m_lines.get(i); + BigDecimal amt = l.getAcctBalance().abs(); + if (l.isBalanceSheet() && amt.compareTo(BSamount) > 0) + { + BSamount = amt; + BSline = l; + } + else if (!l.isBalanceSheet() && amt.compareTo(PLamount) > 0) + { + PLamount = amt; + PLline = l; + } + } + + // Create Currency Balancing Entry + if (m_acctSchema.isCurrencyBalancing()) + { + line = new FactLine (m_doc.getCtx(), m_doc.get_Table_ID(), + m_doc.get_ID(), 0, m_trxName); + line.setDocumentInfo (m_doc, null); + line.setPostingType (m_postingType); + line.setAccount (m_acctSchema, m_acctSchema.getCurrencyBalancing_Acct()); + + // Amount + line.setAmtSource(m_doc.getC_Currency_ID(), Env.ZERO, Env.ZERO); + line.convert(); + // Accounted + BigDecimal drAmt = Env.ZERO; + BigDecimal crAmt = Env.ZERO; + boolean isDR = diff.signum() < 0; + BigDecimal difference = diff.abs(); + if (isDR) + drAmt = difference; + else + crAmt = difference; + // Switch sides + boolean switchIt = BSline != null + && ((BSline.isDrSourceBalance() && isDR) + || (!BSline.isDrSourceBalance() && !isDR)); + if (switchIt) + { + drAmt = Env.ZERO; + crAmt = Env.ZERO; + if (isDR) + crAmt = difference.negate(); + else + drAmt = difference.negate(); + } + line.setAmtAcct(drAmt, crAmt); + log.fine(line.toString()); + m_lines.add(line); + } + else // Adjust biggest (Balance Sheet) line amount + { + if (BSline != null) + line = BSline; + else + line = PLline; + if (line == null) + log.severe ("No Line found"); + else + { + log.fine("Adjusting Amt=" + diff + "; Line=" + line); + line.currencyCorrect(diff); + log.fine(line.toString()); + } + } // correct biggest amount + + return line; + } // balanceAccounting + + /** + * Check Accounts of Fact Lines + * @return true if success + */ + public boolean checkAccounts() + { + // no lines -> nothing to distribute + if (m_lines.size() == 0) + return true; + + // For all fact lines + for (int i = 0; i < m_lines.size(); i++) + { + FactLine line = (FactLine)m_lines.get(i); + MAccount account = line.getAccount(); + if (account == null) + { + log.warning("No Account for " + line); + return false; + } + MElementValue ev = account.getAccount(); + if (ev == null) + { + log.warning("No Element Value for " + account + + ": " + line); + return false; + } + if (ev.isSummary()) + { + log.warning("Cannot post to Summary Account " + ev + + ": " + line); + return false; + } + if (!ev.isActive()) + { + log.warning("Cannot post to Inactive Account " + ev + + ": " + line); + return false; + } + + } // for all lines + + return true; + } // checkAccounts + + /** + * GL Distribution of Fact Lines + * @return true if success + */ + public boolean distribute() + { + // no lines -> nothing to distribute + if (m_lines.size() == 0) + return true; + + ArrayList newLines = new ArrayList(); + // For all fact lines + for (int i = 0; i < m_lines.size(); i++) + { + FactLine dLine = (FactLine)m_lines.get(i); + MDistribution[] distributions = MDistribution.get (dLine.getAccount(), + m_postingType, m_doc.getC_DocType_ID()); + // No Distribution for this line + if (distributions == null || distributions.length == 0) + continue; + // Just the first + if (distributions.length > 1) + log.warning("More then one Distributiion for " + dLine.getAccount()); + MDistribution distribution = distributions[0]; + // Add Reversal + FactLine reversal = dLine.reverse(distribution.getName()); + log.info("Reversal=" + reversal); + newLines.add(reversal); // saved in postCommit + // Prepare + distribution.distribute(dLine.getAccount(), dLine.getSourceBalance(), dLine.getC_Currency_ID()); + MDistributionLine[] lines = distribution.getLines(false); + for (int j = 0; j < lines.length; j++) + { + MDistributionLine dl = lines[j]; + if (!dl.isActive() || dl.getAmt().signum() == 0) + continue; + FactLine factLine = new FactLine (m_doc.getCtx(), m_doc.get_Table_ID(), + m_doc.get_ID(), 0, m_trxName); + // Set Info & Account + factLine.setDocumentInfo(m_doc, dLine.getDocLine()); + factLine.setAccount(m_acctSchema, dl.getAccount()); + factLine.setPostingType(m_postingType); + if (dl.isOverwriteOrg()) // set Org explicitly + factLine.setAD_Org_ID(dl.getOrg_ID()); + // + if (dl.getAmt().signum() < 0) + factLine.setAmtSource(dLine.getC_Currency_ID(), null, dl.getAmt().abs()); + else + factLine.setAmtSource(dLine.getC_Currency_ID(), dl.getAmt(), null); + // Convert + factLine.convert(); + // + String description = distribution.getName() + " #" + dl.getLine(); + if (dl.getDescription() != null) + description += " - " + dl.getDescription(); + factLine.addDescription(description); + // + log.info(factLine.toString()); + newLines.add(factLine); + } + } // for all lines + + // Add Lines + for (int i = 0; i < newLines.size(); i++) + m_lines.add(newLines.get(i)); + + return true; + } // distribute + + + /************************************************************************** + * String representation + * @return String + */ + public String toString() + { + StringBuffer sb = new StringBuffer("Fact["); + sb.append(m_doc.toString()); + sb.append(",").append(m_acctSchema.toString()); + sb.append(",PostType=").append(m_postingType); + sb.append("]"); + return sb.toString(); + } // toString + + /** + * Get Lines + * @return FactLine Array + */ + public FactLine[] getLines() + { + FactLine[] temp = new FactLine[m_lines.size()]; + m_lines.toArray(temp); + return temp; + } // getLines + + /** + * Save Fact + * @param trxName transaction + * @return true if all lines were saved + */ + public boolean save (String trxName) + { + // save Lines + for (int i = 0; i < m_lines.size(); i++) + { + FactLine fl = (FactLine)m_lines.get(i); + // log.fine("save - " + fl); + if (!fl.save(trxName)) // abort on first error + return false; + } + return true; + } // commit + + /** + * Get Transaction + * @return trx + */ + public String get_TrxName() + { + return m_trxName; + } // getTrxName + + /** + * Set Transaction name + * @param trxName + */ + private void set_TrxName(String trxName) + { + m_trxName = trxName; + } // set_TrxName + + /** + * Fact Balance Utility + * + * @author Jorg Janke + * @version $Id: Fact.java,v 1.2 2006/07/30 00:53:33 jjanke Exp $ + */ + public class Balance + { + /** + * New Balance + * @param dr DR + * @param cr CR + */ + public Balance (BigDecimal dr, BigDecimal cr) + { + DR = dr; + CR = cr; + } + + /** DR Amount */ + public BigDecimal DR = Env.ZERO; + /** CR Amount */ + public BigDecimal CR = Env.ZERO; + + /** + * Add + * @param dr DR + * @param cr CR + */ + public void add (BigDecimal dr, BigDecimal cr) + { + DR = DR.add(dr); + CR = CR.add(cr); + } + + /** + * Get Balance + * @return balance + */ + public BigDecimal getBalance() + { + return DR.subtract(CR); + } // getBalance + + /** + * Get Post Balance + * @return absolute balance - negative if reversal + */ + public BigDecimal getPostBalance() + { + BigDecimal bd = getBalance().abs(); + if (isReversal()) + return bd.negate(); + return bd; + } // getPostBalance + + /** + * Zero Balance + * @return true if 0 + */ + public boolean isZeroBalance() + { + return getBalance().signum() == 0; + } // isZeroBalance + + /** + * Reversal + * @return true if both DR/CR are negative or zero + */ + public boolean isReversal() + { + return DR.signum() <= 0 && CR.signum() <= 0; + } // isReversal + + /** + * String Representation + * @return info + */ + public String toString () + { + StringBuffer sb = new StringBuffer ("Balance["); + sb.append ("DR=").append(DR) + .append ("-CR=").append(CR) + .append(" = ").append(getBalance()) + .append ("]"); + return sb.toString (); + } // toString + + } // Balance + +} // Fact diff --git a/serverRoot/src/main/server/org/compiere/acct/FactLine.java b/serverRoot/src/main/server/org/compiere/acct/FactLine.java new file mode 100644 index 0000000000..343364b561 --- /dev/null +++ b/serverRoot/src/main/server/org/compiere/acct/FactLine.java @@ -0,0 +1,949 @@ +/****************************************************************************** + * 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.acct; + +import java.math.*; +import java.sql.*; +import java.util.*; +import java.util.logging.*; +import org.compiere.model.*; +import org.compiere.util.*; + +/** + * Accounting Fact Entry. + * + * @author Jorg Janke + * @version $Id: FactLine.java,v 1.3 2006/07/30 00:53:33 jjanke Exp $ + */ +public final class FactLine extends X_Fact_Acct +{ + /** + * Constructor + * @param ctx context + * @param AD_Table_ID - Table of Document Source + * @param Record_ID - Record of document + * @param Line_ID - Optional line id + * @param trxName transaction + */ + public FactLine (Properties ctx, int AD_Table_ID, int Record_ID, int Line_ID, String trxName) + { + super(ctx, 0, trxName); + setAD_Client_ID(0); // do not derive + setAD_Org_ID(0); // do not derive + // + setAmtAcctCr (Env.ZERO); + setAmtAcctDr (Env.ZERO); + setAmtSourceCr (Env.ZERO); + setAmtSourceDr (Env.ZERO); + // Log.trace(this,Log.l1_User, "FactLine " + AD_Table_ID + ":" + Record_ID); + setAD_Table_ID (AD_Table_ID); + setRecord_ID (Record_ID); + setLine_ID (Line_ID); + } // FactLine + + /** Account */ + private MAccount m_acct = null; + /** Accounting Schema */ + private MAcctSchema m_acctSchema = null; + /** Document Header */ + private Doc m_doc = null; + /** Document Line */ + private DocLine m_docLine = null; + + /** + * Create Reversal (negate DR/CR) of the line + * @param description new description + * @return reversal line + */ + public FactLine reverse (String description) + { + FactLine reversal = new FactLine (getCtx(), getAD_Table_ID(), getRecord_ID(), getLine_ID(), get_TrxName()); + reversal.setClientOrg(this); // needs to be set explicitly + reversal.setDocumentInfo(m_doc, m_docLine); + reversal.setAccount(m_acctSchema, m_acct); + reversal.setPostingType(getPostingType()); + // + reversal.setAmtSource(getC_Currency_ID(), getAmtSourceDr().negate(), getAmtSourceCr().negate()); + reversal.convert(); + reversal.setDescription(description); + return reversal; + } // reverse + + /** + * Create Accrual (flip CR/DR) of the line + * @param description new description + * @return accrual line + */ + public FactLine accrue (String description) + { + FactLine accrual = new FactLine (getCtx(), getAD_Table_ID(), getRecord_ID(), getLine_ID(), get_TrxName()); + accrual.setClientOrg(this); // needs to be set explicitly + accrual.setDocumentInfo(m_doc, m_docLine); + accrual.setAccount(m_acctSchema, m_acct); + accrual.setPostingType(getPostingType()); + // + accrual.setAmtSource(getC_Currency_ID(), getAmtSourceCr(), getAmtSourceDr()); + accrual.convert(); + accrual.setDescription(description); + return accrual; + } // reverse + + /** + * Set Account Info + * @param acctSchema account schema + * @param acct account + */ + public void setAccount (MAcctSchema acctSchema, MAccount acct) + { + m_acctSchema = acctSchema; + setC_AcctSchema_ID (acctSchema.getC_AcctSchema_ID()); + // + m_acct = acct; + if (getAD_Client_ID() == 0) + setAD_Client_ID(m_acct.getAD_Client_ID()); + setAccount_ID (m_acct.getAccount_ID()); + setC_SubAcct_ID(m_acct.getC_SubAcct_ID()); + } // setAccount + + /** + * Set Source Amounts + * @param C_Currency_ID currency + * @param AmtSourceDr source amount dr + * @param AmtSourceCr source amount cr + * @return true, if any if the amount is not zero + */ + public boolean setAmtSource (int C_Currency_ID, BigDecimal AmtSourceDr, BigDecimal AmtSourceCr) + { + setC_Currency_ID (C_Currency_ID); + if (AmtSourceDr != null) + setAmtSourceDr (AmtSourceDr); + if (AmtSourceCr != null) + setAmtSourceCr (AmtSourceCr); + // one needs to be non zero + if (getAmtSourceDr().equals(Env.ZERO) && getAmtSourceCr().equals(Env.ZERO)) + return false; + // Currency Precision + int precision = MCurrency.getStdPrecision(getCtx(), C_Currency_ID); + if (AmtSourceDr != null && AmtSourceDr.scale() > precision) + { + BigDecimal AmtSourceDr1 = AmtSourceDr.setScale(precision, BigDecimal.ROUND_HALF_UP); + log.warning("Source DR Precision " + AmtSourceDr + " -> " + AmtSourceDr1); + setAmtSourceDr(AmtSourceDr1); + } + if (AmtSourceCr != null && AmtSourceCr.scale() > precision) + { + BigDecimal AmtSourceCr1 = AmtSourceCr.setScale(precision, BigDecimal.ROUND_HALF_UP); + log.warning("Source CR Precision " + AmtSourceCr + " -> " + AmtSourceCr1); + setAmtSourceCr(AmtSourceCr1); + } + return true; + } // setAmtSource + + /** + * Set Accounted Amounts (alternative: call convert) + * @param AmtAcctDr acct amount dr + * @param AmtAcctCr acct amount cr + */ + public void setAmtAcct(BigDecimal AmtAcctDr, BigDecimal AmtAcctCr) + { + setAmtAcctDr (AmtAcctDr); + setAmtAcctCr (AmtAcctCr); + } // setAmtAcct + + /** + * Set Document Info + * @param doc document + * @param docLine doc line + */ + public void setDocumentInfo(Doc doc, DocLine docLine) + { + m_doc = doc; + m_docLine = docLine; + // reset + setAD_Org_ID(0); + setC_SalesRegion_ID(0); + // Client + if (getAD_Client_ID() == 0) + setAD_Client_ID (m_doc.getAD_Client_ID()); + // Date Trx + setDateTrx (m_doc.getDateDoc()); + if (m_docLine != null && m_docLine.getDateDoc() != null) + setDateTrx (m_docLine.getDateDoc()); + // Date Acct + setDateAcct (m_doc.getDateAcct()); + if (m_docLine != null && m_docLine.getDateAcct() != null) + setDateAcct (m_docLine.getDateAcct()); + // Period, Tax + if (m_docLine != null && m_docLine.getC_Period_ID() != 0) + setC_Period_ID(m_docLine.getC_Period_ID()); + else + setC_Period_ID (m_doc.getC_Period_ID()); + if (m_docLine != null) + setC_Tax_ID (m_docLine.getC_Tax_ID()); + // Description + StringBuffer description = new StringBuffer(m_doc.getDocumentNo()); + if (m_docLine != null) + { + description.append(" #").append(m_docLine.getLine()); + if (m_docLine.getDescription() != null) + description.append(" (").append(m_docLine.getDescription()).append(")"); + else if (m_doc.getDescription() != null && m_doc.getDescription().length() > 0) + description.append(" (").append(m_doc.getDescription()).append(")"); + } + else if (m_doc.getDescription() != null && m_doc.getDescription().length() > 0) + description.append(" (").append(m_doc.getDescription()).append(")"); + setDescription(description.toString()); + // Journal Info + setGL_Budget_ID (m_doc.getGL_Budget_ID()); + setGL_Category_ID (m_doc.getGL_Category_ID()); + + // Product + if (m_docLine != null) + setM_Product_ID (m_docLine.getM_Product_ID()); + if (getM_Product_ID() == 0) + setM_Product_ID (m_doc.getM_Product_ID()); + // UOM + if (m_docLine != null) + setC_UOM_ID (m_docLine.getC_UOM_ID()); + // Qty + if (get_Value("Qty") == null) // not previously set + { + setQty (m_doc.getQty()); // neg = outgoing + if (m_docLine != null) + setQty (m_docLine.getQty()); + } + + // Loc From (maybe set earlier) + if (getC_LocFrom_ID() == 0 && m_docLine != null) + setC_LocFrom_ID (m_docLine.getC_LocFrom_ID()); + if (getC_LocFrom_ID() == 0) + setC_LocFrom_ID (m_doc.getC_LocFrom_ID()); + // Loc To (maybe set earlier) + if (getC_LocTo_ID() == 0 && m_docLine != null) + setC_LocTo_ID (m_docLine.getC_LocTo_ID()); + if (getC_LocTo_ID() == 0) + setC_LocTo_ID (m_doc.getC_LocTo_ID()); + // BPartner + if (m_docLine != null) + setC_BPartner_ID (m_docLine.getC_BPartner_ID()); + if (getC_BPartner_ID() == 0) + setC_BPartner_ID (m_doc.getC_BPartner_ID()); + // Sales Region from BPLocation/Sales Rep + // Trx Org + if (m_docLine != null) + setAD_OrgTrx_ID (m_docLine.getAD_OrgTrx_ID()); + if (getAD_OrgTrx_ID() == 0) + setAD_OrgTrx_ID (m_doc.getAD_OrgTrx_ID()); + // Project + if (m_docLine != null) + setC_Project_ID (m_docLine.getC_Project_ID()); + if (getC_Project_ID() == 0) + setC_Project_ID (m_doc.getC_Project_ID()); + // Campaign + if (m_docLine != null) + setC_Campaign_ID (m_docLine.getC_Campaign_ID()); + if (getC_Campaign_ID() == 0) + setC_Campaign_ID (m_doc.getC_Campaign_ID()); + // Activity + if (m_docLine != null) + setC_Activity_ID (m_docLine.getC_Activity_ID()); + if (getC_Activity_ID() == 0) + setC_Activity_ID (m_doc.getC_Activity_ID()); + // User List 1 + if (m_docLine != null) + setUser1_ID (m_docLine.getUser1_ID()); + if (getUser1_ID() == 0) + setUser1_ID (m_doc.getUser1_ID()); + // User List 2 + if (m_docLine != null) + setUser2_ID (m_docLine.getUser2_ID()); + if (getUser2_ID() == 0) + setUser2_ID (m_doc.getUser2_ID()); + // User Defined + + } // setDocumentInfo + + /** + * Get Document Line + * @return doc line + */ + protected DocLine getDocLine() + { + return m_docLine; + } // getDocLine + + /** + * Set Description + * @param description description + */ + public void addDescription (String description) + { + String original = getDescription(); + if (original == null || original.trim().length() == 0) + super.setDescription(description); + else + super.setDescription(original + " - " + description); + } // addDescription + + /** + * Set Warehouse Locator. + * - will overwrite Organization - + * @param M_Locator_ID locator + */ + public void setM_Locator_ID (int M_Locator_ID) + { + super.setM_Locator_ID (M_Locator_ID); + setAD_Org_ID(0); // reset + } // setM_Locator_ID + + + /************************************************************************** + * Set Location + * @param C_Location_ID location + * @param isFrom from + */ + public void setLocation (int C_Location_ID, boolean isFrom) + { + if (isFrom) + setC_LocFrom_ID (C_Location_ID); + else + setC_LocTo_ID (C_Location_ID); + } // setLocator + + /** + * Set Location from Locator + * @param M_Locator_ID locator + * @param isFrom from + */ + public void setLocationFromLocator (int M_Locator_ID, boolean isFrom) + { + if (M_Locator_ID == 0) + return; + int C_Location_ID = 0; + String sql = "SELECT w.C_Location_ID FROM M_Warehouse w, M_Locator l " + + "WHERE w.M_Warehouse_ID=l.M_Warehouse_ID AND l.M_Locator_ID=?"; + try + { + PreparedStatement pstmt = DB.prepareStatement(sql, get_TrxName()); + pstmt.setInt(1, M_Locator_ID); + ResultSet rs = pstmt.executeQuery(); + if (rs.next()) + C_Location_ID = rs.getInt(1); + rs.close(); + pstmt.close(); + } + catch (SQLException e) + { + log.log(Level.SEVERE, sql, e); + return; + } + if (C_Location_ID != 0) + setLocation (C_Location_ID, isFrom); + } // setLocationFromLocator + + /** + * Set Location from Busoness Partner Location + * @param C_BPartner_Location_ID bp location + * @param isFrom from + */ + public void setLocationFromBPartner (int C_BPartner_Location_ID, boolean isFrom) + { + if (C_BPartner_Location_ID == 0) + return; + int C_Location_ID = 0; + String sql = "SELECT C_Location_ID FROM C_BPartner_Location WHERE C_BPartner_Location_ID=?"; + try + { + PreparedStatement pstmt = DB.prepareStatement(sql, get_TrxName()); + pstmt.setInt(1, C_BPartner_Location_ID); + ResultSet rs = pstmt.executeQuery(); + if (rs.next()) + C_Location_ID = rs.getInt(1); + rs.close(); + pstmt.close(); + } + catch (SQLException e) + { + log.log(Level.SEVERE, sql, e); + return; + } + if (C_Location_ID != 0) + setLocation (C_Location_ID, isFrom); + } // setLocationFromBPartner + + /** + * Set Location from Organization + * @param AD_Org_ID org + * @param isFrom from + */ + public void setLocationFromOrg (int AD_Org_ID, boolean isFrom) + { + if (AD_Org_ID == 0) + return; + int C_Location_ID = 0; + String sql = "SELECT C_Location_ID FROM AD_OrgInfo WHERE AD_Org_ID=?"; + try + { + PreparedStatement pstmt = DB.prepareStatement(sql, get_TrxName()); + pstmt.setInt(1, AD_Org_ID); + ResultSet rs = pstmt.executeQuery(); + if (rs.next()) + C_Location_ID = rs.getInt(1); + rs.close(); + pstmt.close(); + } + catch (SQLException e) + { + log.log(Level.SEVERE, sql, e); + return; + } + if (C_Location_ID != 0) + setLocation (C_Location_ID, isFrom); + } // setLocationFromOrg + + + /************************************************************************** + * Returns Source Balance of line + * @return source balance + */ + public BigDecimal getSourceBalance() + { + if (getAmtSourceDr() == null) + setAmtSourceDr (Env.ZERO); + if (getAmtSourceCr() == null) + setAmtSourceCr (Env.ZERO); + // + return getAmtSourceDr().subtract(getAmtSourceCr()); + } // getSourceBalance + + /** + * Is Debit Source Balance + * @return true if DR source balance + */ + public boolean isDrSourceBalance() + { + return getSourceBalance().signum() != -1; + } // isDrSourceBalance + + /** + * Get Accounted Balance + * @return accounting balance + */ + public BigDecimal getAcctBalance() + { + if (getAmtAcctDr() == null) + setAmtAcctDr (Env.ZERO); + if (getAmtAcctCr() == null) + setAmtAcctCr (Env.ZERO); + return getAmtAcctDr().subtract(getAmtAcctCr()); + } // getAcctBalance + + /** + * Is Account on Balance Sheet + * @return true if account is a balance sheet account + */ + public boolean isBalanceSheet() + { + return m_acct.isBalanceSheet(); + } // isBalanceSheet + + /** + * Currect Accounting Amount. + *
+	 *  Example:    1       -1      1       -1
+	 *  Old         100/0   100/0   0/100   0/100
+	 *  New         99/0    101/0   0/99    0/101
+	 *  
+ * @param deltaAmount delta amount + */ + public void currencyCorrect (BigDecimal deltaAmount) + { + boolean negative = deltaAmount.compareTo(Env.ZERO) < 0; + boolean adjustDr = getAmtAcctDr().abs().compareTo(getAmtAcctCr().abs()) > 0; + + log.fine(deltaAmount.toString() + + "; Old-AcctDr=" + getAmtAcctDr() + ",AcctCr=" + getAmtAcctCr() + + "; Negative=" + negative + "; AdjustDr=" + adjustDr); + + if (adjustDr) + if (negative) + setAmtAcctDr (getAmtAcctDr().subtract(deltaAmount)); + else + setAmtAcctDr (getAmtAcctDr().subtract(deltaAmount)); + else + if (negative) + setAmtAcctCr (getAmtAcctCr().add(deltaAmount)); + else + setAmtAcctCr (getAmtAcctCr().add(deltaAmount)); + + log.fine("New-AcctDr=" + getAmtAcctDr() + ",AcctCr=" + getAmtAcctCr()); + } // currencyCorrect + + /** + * Convert to Accounted Currency + * @return true if converted + */ + public boolean convert () + { + // Document has no currency + if (getC_Currency_ID() == Doc.NO_CURRENCY) + setC_Currency_ID (m_acctSchema.getC_Currency_ID()); + + if (m_acctSchema.getC_Currency_ID() == getC_Currency_ID()) + { + setAmtAcctDr (getAmtSourceDr()); + setAmtAcctCr (getAmtSourceCr()); + return true; + } + // Get Conversion Type from Line or Header + int C_ConversionType_ID = 0; + int AD_Org_ID = 0; + if (m_docLine != null) // get from line + { + C_ConversionType_ID = m_docLine.getC_ConversionType_ID(); + AD_Org_ID = m_docLine.getAD_Org_ID(); + } + if (C_ConversionType_ID == 0) // get from header + { + if (m_doc == null) + { + log.severe ("No Document VO"); + return false; + } + C_ConversionType_ID = m_doc.getC_ConversionType_ID(); + if (AD_Org_ID == 0) + AD_Org_ID = m_doc.getAD_Org_ID(); + } + setAmtAcctDr (MConversionRate.convert (getCtx(), + getAmtSourceDr(), getC_Currency_ID(), m_acctSchema.getC_Currency_ID(), + getDateAcct(), C_ConversionType_ID, m_doc.getAD_Client_ID(), AD_Org_ID)); + if (getAmtAcctDr() == null) + return false; + setAmtAcctCr (MConversionRate.convert (getCtx(), + getAmtSourceCr(), getC_Currency_ID(), m_acctSchema.getC_Currency_ID(), + getDateAcct(), C_ConversionType_ID, m_doc.getAD_Client_ID(), AD_Org_ID)); + return true; + } // convert + + /** + * Get Account + * @return account + */ + public MAccount getAccount() + { + return m_acct; + } // getAccount + + /** + * To String + * @return String + */ + public String toString() + { + StringBuffer sb = new StringBuffer("FactLine=["); + sb.append(getAD_Table_ID()).append(":").append(getRecord_ID()) + .append(",").append(m_acct) + .append(",Cur=").append(getC_Currency_ID()) + .append(", DR=").append(getAmtSourceDr()).append("|").append(getAmtAcctDr()) + .append(", CR=").append(getAmtSourceCr()).append("|").append(getAmtAcctCr()) + .append("]"); + return sb.toString(); + } // toString + + + /** + * Get AD_Org_ID (balancing segment). + * (if not set directly - from document line, document, account, locator) + *

+ * Note that Locator needs to be set before - otherwise + * segment balancing might produce the wrong results + * @return AD_Org_ID + */ + public int getAD_Org_ID() + { + if (super.getAD_Org_ID() != 0) // set earlier + return super.getAD_Org_ID(); + // Prio 1 - get from locator - if exist + if (getM_Locator_ID() != 0) + { + String sql = "SELECT AD_Org_ID FROM M_Locator WHERE M_Locator_ID=? AND AD_Client_ID=?"; + try + { + PreparedStatement pstmt = DB.prepareStatement(sql, get_TrxName()); + pstmt.setInt(1, getM_Locator_ID()); + pstmt.setInt(2, getAD_Client_ID()); + ResultSet rs = pstmt.executeQuery(); + if (rs.next()) + { + setAD_Org_ID (rs.getInt(1)); + log.finer("AD_Org_ID=" + super.getAD_Org_ID() + " (1 from M_Locator_ID=" + getM_Locator_ID() + ")"); + } + else + log.log(Level.SEVERE, "AD_Org_ID - Did not find M_Locator_ID=" + getM_Locator_ID()); + rs.close(); + pstmt.close(); + } + catch (SQLException e) + { + log.log(Level.SEVERE, sql, e); + } + } // M_Locator_ID != 0 + + // Prio 2 - get from doc line - if exists (document context overwrites) + if (m_docLine != null && super.getAD_Org_ID() == 0) + { + setAD_Org_ID (m_docLine.getAD_Org_ID()); + log.finer("AD_Org_ID=" + super.getAD_Org_ID() + " (2 from DocumentLine)"); + } + // Prio 3 - get from doc - if not GL + if (m_doc != null && super.getAD_Org_ID() == 0) + { + if (Doc.DOCTYPE_GLJournal.equals (m_doc.getDocumentType())) + { + setAD_Org_ID (m_acct.getAD_Org_ID()); // inter-company GL + log.finer("AD_Org_ID=" + super.getAD_Org_ID() + " (3 from Acct)"); + } + else + { + setAD_Org_ID (m_doc.getAD_Org_ID()); + log.finer("AD_Org_ID=" + super.getAD_Org_ID() + " (3 from Document)"); + } + } + // Prio 4 - get from account - if not GL + if (m_doc != null && super.getAD_Org_ID() == 0) + { + if (Doc.DOCTYPE_GLJournal.equals (m_doc.getDocumentType())) + { + setAD_Org_ID (m_doc.getAD_Org_ID()); + log.finer("AD_Org_ID=" + super.getAD_Org_ID() + " (4 from Document)"); + } + else + { + setAD_Org_ID (m_acct.getAD_Org_ID()); + log.finer("AD_Org_ID=" + super.getAD_Org_ID() + " (4 from Acct)"); + } + } + return super.getAD_Org_ID(); + } // setAD_Org_ID + + + /** + * Get/derive Sales Region + * @return Sales Region + */ + public int getC_SalesRegion_ID () + { + if (super.getC_SalesRegion_ID() != 0) + return super.getC_SalesRegion_ID(); + // + if (m_docLine != null) + setC_SalesRegion_ID (m_docLine.getC_SalesRegion_ID()); + if (m_doc != null) + { + if (super.getC_SalesRegion_ID() == 0) + setC_SalesRegion_ID (m_doc.getC_SalesRegion_ID()); + if (super.getC_SalesRegion_ID() == 0 && m_doc.getBP_C_SalesRegion_ID() > 0) + setC_SalesRegion_ID (m_doc.getBP_C_SalesRegion_ID()); + // derive SalesRegion if AcctSegment + if (super.getC_SalesRegion_ID() == 0 + && m_doc.getC_BPartner_Location_ID() != 0 + && m_doc.getBP_C_SalesRegion_ID() == -1) // never tried + // && m_acctSchema.isAcctSchemaElement(MAcctSchemaElement.ELEMENTTYPE_SalesRegion)) + { + String sql = "SELECT COALESCE(C_SalesRegion_ID,0) FROM C_BPartner_Location WHERE C_BPartner_Location_ID=?"; + setC_SalesRegion_ID (DB.getSQLValue(null, + sql, m_doc.getC_BPartner_Location_ID())); + if (super.getC_SalesRegion_ID() != 0) // save in VO + { + m_doc.setBP_C_SalesRegion_ID(super.getC_SalesRegion_ID()); + log.fine("C_SalesRegion_ID=" + super.getC_SalesRegion_ID() + " (from BPL)" ); + } + else // From Sales Rep of Document -> Sales Region + { + sql = "SELECT COALESCE(MAX(C_SalesRegion_ID),0) FROM C_SalesRegion WHERE SalesRep_ID=?"; + setC_SalesRegion_ID (DB.getSQLValue(null, + sql, m_doc.getSalesRep_ID())); + if (super.getC_SalesRegion_ID() != 0) // save in VO + { + m_doc.setBP_C_SalesRegion_ID(super.getC_SalesRegion_ID()); + log.fine("C_SalesRegion_ID=" + super.getC_SalesRegion_ID() + " (from SR)" ); + } + else + m_doc.setBP_C_SalesRegion_ID(-2); // don't try again + } + } + if (m_acct != null && super.getC_SalesRegion_ID() == 0) + setC_SalesRegion_ID (m_acct.getC_SalesRegion_ID()); + } + // + // log.fine("C_SalesRegion_ID=" + super.getC_SalesRegion_ID() + // + ", C_BPartner_Location_ID=" + m_docVO.C_BPartner_Location_ID + // + ", BP_C_SalesRegion_ID=" + m_docVO.BP_C_SalesRegion_ID + // + ", SR=" + m_acctSchema.isAcctSchemaElement(MAcctSchemaElement.ELEMENTTYPE_SalesRegion)); + return super.getC_SalesRegion_ID(); + } // getC_SalesRegion_ID + + + /** + * Before Save + * @param newRecord new + * @return true + */ + protected boolean beforeSave (boolean newRecord) + { + if (newRecord) + { + log.fine(toString()); + // + getAD_Org_ID(); + getC_SalesRegion_ID(); + // Set Default Account Info + if (getM_Product_ID() == 0) + setM_Product_ID (m_acct.getM_Product_ID()); + if (getC_LocFrom_ID() == 0) + setC_LocFrom_ID (m_acct.getC_LocFrom_ID()); + if (getC_LocTo_ID() == 0) + setC_LocTo_ID (m_acct.getC_LocTo_ID()); + if (getC_BPartner_ID() == 0) + setC_BPartner_ID (m_acct.getC_BPartner_ID()); + if (getAD_OrgTrx_ID() == 0) + setAD_OrgTrx_ID (m_acct.getAD_OrgTrx_ID()); + if (getC_Project_ID() == 0) + setC_Project_ID (m_acct.getC_Project_ID()); + if (getC_Campaign_ID() == 0) + setC_Campaign_ID (m_acct.getC_Campaign_ID()); + if (getC_Activity_ID() == 0) + setC_Activity_ID (m_acct.getC_Activity_ID()); + if (getUser1_ID() == 0) + setUser1_ID (m_acct.getUser1_ID()); + if (getUser2_ID() == 0) + setUser2_ID (m_acct.getUser2_ID()); + + // Revenue Recognition for AR Invoices + if (m_doc.getDocumentType().equals(Doc.DOCTYPE_ARInvoice) + && m_docLine != null + && m_docLine.getC_RevenueRecognition_ID() != 0) + { + int AD_User_ID = 0; + setAccount_ID ( + createRevenueRecognition ( + m_docLine.getC_RevenueRecognition_ID(), m_docLine.get_ID(), + getAD_Client_ID(), getAD_Org_ID(), AD_User_ID, + getAccount_ID(), getC_SubAcct_ID(), + getM_Product_ID(), getC_BPartner_ID(), getAD_OrgTrx_ID(), + getC_LocFrom_ID(), getC_LocTo_ID(), + getC_SalesRegion_ID(), getC_Project_ID(), + getC_Campaign_ID(), getC_Activity_ID(), + getUser1_ID(), getUser2_ID(), + getUserElement1_ID(), getUserElement2_ID()) + ); + } + } + return true; + } // beforeSave + + + /************************************************************************** + * Revenue Recognition. + * Called from FactLine.save + *

+ * Create Revenue recognition plan and return Unearned Revenue account + * to be used instead of Revenue Account. If not found, it returns + * the revenue account. + * + * @param C_RevenueRecognition_ID revenue recognition + * @param C_InvoiceLine_ID invoice line + * @param AD_Client_ID client + * @param AD_Org_ID org + * @param AD_User_ID user + * @param Account_ID of Revenue Account + * @param C_SubAcct_ID sub account + * @param M_Product_ID product + * @param C_BPartner_ID bpartner + * @param AD_OrgTrx_ID trx org + * @param C_LocFrom_ID loc from + * @param C_LocTo_ID loc to + * @param C_SRegion_ID sales region + * @param C_Project_ID project + * @param C_Campaign_ID campaign + * @param C_Activity_ID activity + * @param User1_ID user1 + * @param User2_ID user2 + * @param UserElement1_ID user element 1 + * @param UserElement2_ID user element 2 + * @return Account_ID for Unearned Revenue or Revenue Account if not found + */ + private int createRevenueRecognition ( + int C_RevenueRecognition_ID, int C_InvoiceLine_ID, + int AD_Client_ID, int AD_Org_ID, int AD_User_ID, + int Account_ID, int C_SubAcct_ID, + int M_Product_ID, int C_BPartner_ID, int AD_OrgTrx_ID, + int C_LocFrom_ID, int C_LocTo_ID, int C_SRegion_ID, int C_Project_ID, + int C_Campaign_ID, int C_Activity_ID, + int User1_ID, int User2_ID, int UserElement1_ID, int UserElement2_ID) + { + log.fine("From Accout_ID=" + Account_ID); + // get VC for P_Revenue (from Product) + MAccount revenue = MAccount.get(getCtx(), + AD_Client_ID, AD_Org_ID, getC_AcctSchema_ID(), Account_ID, C_SubAcct_ID, + M_Product_ID, C_BPartner_ID, AD_OrgTrx_ID, C_LocFrom_ID, C_LocTo_ID, C_SRegion_ID, + C_Project_ID, C_Campaign_ID, C_Activity_ID, + User1_ID, User2_ID, UserElement1_ID, UserElement2_ID); + if (revenue != null && revenue.get_ID() == 0) + revenue.save(); + if (revenue == null || revenue.get_ID() == 0) + { + log.severe ("Revenue_Acct not found"); + return Account_ID; + } + int P_Revenue_Acct = revenue.get_ID(); + + // get Unearned Revenue Acct from BPartner Group + int UnearnedRevenue_Acct = 0; + int new_Account_ID = 0; + String sql = "SELECT ga.UnearnedRevenue_Acct, vc.Account_ID " + + "FROM C_BP_Group_Acct ga, C_BPartner p, C_ValidCombination vc " + + "WHERE ga.C_BP_Group_ID=p.C_BP_Group_ID" + + " AND ga.UnearnedRevenue_Acct=vc.C_ValidCombination_ID" + + " AND ga.C_AcctSchema_ID=? AND p.C_BPartner_ID=?"; + try + { + PreparedStatement pstmt = DB.prepareStatement(sql, get_TrxName()); + pstmt.setInt(1, getC_AcctSchema_ID()); + pstmt.setInt(2, C_BPartner_ID); + ResultSet rs = pstmt.executeQuery(); + if (rs.next()) + { + UnearnedRevenue_Acct = rs.getInt(1); + new_Account_ID = rs.getInt(2); + } + rs.close(); + pstmt.close(); + } + catch (SQLException e) + { + log.log(Level.SEVERE, sql, e); + } + if (new_Account_ID == 0) + { + log.severe ("UnearnedRevenue_Acct not found"); + return Account_ID; + } + + MRevenueRecognitionPlan plan = new MRevenueRecognitionPlan(getCtx(), 0, null); + plan.setC_RevenueRecognition_ID (C_RevenueRecognition_ID); + plan.setC_AcctSchema_ID (getC_AcctSchema_ID()); + plan.setC_InvoiceLine_ID (C_InvoiceLine_ID); + plan.setUnEarnedRevenue_Acct (UnearnedRevenue_Acct); + plan.setP_Revenue_Acct (P_Revenue_Acct); + plan.setC_Currency_ID (getC_Currency_ID()); + plan.setTotalAmt (getAcctBalance()); + if (!plan.save(get_TrxName())) + { + log.severe ("Plan NOT created"); + return Account_ID; + } + log.fine("From Acctount_ID=" + Account_ID + " to " + new_Account_ID + + " - Plan from UnearnedRevenue_Acct=" + UnearnedRevenue_Acct + " to Revenue_Acct=" + P_Revenue_Acct); + return new_Account_ID; + } // createRevenueRecognition + + + /************************************************************************** + * Update Line with reversed Original Amount in Accounting Currency. + * Also copies original dimensions like Project, etc. + * Called from Doc_MatchInv + * @param AD_Table_ID table + * @param Record_ID record + * @param Line_ID line + * @param multiplier targetQty/documentQty + * @return true if success + */ + public boolean updateReverseLine (int AD_Table_ID, int Record_ID, int Line_ID, + BigDecimal multiplier) + { + boolean success = false; + + String sql = "SELECT * " + + "FROM Fact_Acct " + + "WHERE C_AcctSchema_ID=? AND AD_Table_ID=? AND Record_ID=?" + + " AND Line_ID=? AND Account_ID=?"; + try + { + PreparedStatement pstmt = DB.prepareStatement(sql, get_TrxName()); + pstmt.setInt(1, getC_AcctSchema_ID()); + pstmt.setInt(2, AD_Table_ID); + pstmt.setInt(3, Record_ID); + pstmt.setInt(4, Line_ID); + pstmt.setInt(5, m_acct.getAccount_ID()); + ResultSet rs = pstmt.executeQuery(); + if (rs.next()) + { + MFactAcct fact = new MFactAcct(getCtx(), rs, get_TrxName()); + // Accounted Amounts - reverse + BigDecimal dr = fact.getAmtAcctDr(); + BigDecimal cr = fact.getAmtAcctCr(); + setAmtAcctDr (cr.multiply(multiplier)); + setAmtAcctCr (dr.multiply(multiplier)); + // Source Amounts + setAmtSourceDr (getAmtAcctDr()); + setAmtSourceCr (getAmtAcctCr()); + // + success = true; + log.fine(new StringBuffer("(Table=").append(AD_Table_ID) + .append(",Record_ID=").append(Record_ID) + .append(",Line=").append(Record_ID) + .append(", Account=").append(m_acct) + .append(",dr=").append(dr).append(",cr=").append(cr) + .append(") - DR=").append(getAmtSourceDr()).append("|").append(getAmtAcctDr()) + .append(", CR=").append(getAmtSourceCr()).append("|").append(getAmtAcctCr()) + .toString()); + // Dimensions + setAD_OrgTrx_ID(fact.getAD_OrgTrx_ID()); + setC_Project_ID (fact.getC_Project_ID()); + setC_Activity_ID(fact.getC_Activity_ID()); + setC_Campaign_ID(fact.getC_Campaign_ID()); + setC_SalesRegion_ID(fact.getC_SalesRegion_ID()); + setC_LocFrom_ID(fact.getC_LocFrom_ID()); + setC_LocTo_ID(fact.getC_LocTo_ID()); + setM_Product_ID(fact.getM_Product_ID()); + setM_Locator_ID(fact.getM_Locator_ID()); + setUser1_ID(fact.getUser1_ID()); + setUser2_ID(fact.getUser2_ID()); + setC_UOM_ID(fact.getC_UOM_ID()); + setC_Tax_ID(fact.getC_Tax_ID()); + // Org for cross charge + setAD_Org_ID (fact.getAD_Org_ID()); + } + else + log.warning(new StringBuffer("Not Found (try later) ") + .append(",C_AcctSchema_ID=").append(getC_AcctSchema_ID()) + .append(", AD_Table_ID=").append(AD_Table_ID) + .append(",Record_ID=").append(Record_ID) + .append(",Line_ID=").append(Line_ID) + .append(", Account_ID=").append(m_acct.getAccount_ID()).toString()); + rs.close(); + pstmt.close(); + } + catch (SQLException e) + { + log.log(Level.SEVERE, sql, e); + } + return success; + } // updateReverseLine + +} // FactLine diff --git a/serverRoot/src/main/server/org/compiere/acct/Matcher.java b/serverRoot/src/main/server/org/compiere/acct/Matcher.java new file mode 100644 index 0000000000..0a7e8886b3 --- /dev/null +++ b/serverRoot/src/main/server/org/compiere/acct/Matcher.java @@ -0,0 +1,165 @@ +/****************************************************************************** + * 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.acct; + +import java.math.*; +import java.sql.*; +import java.util.logging.*; +import org.compiere.util.*; + +/** + * Automatic Matching. + * Inv + * + * @author Jorg Janke + * @version $Id: Matcher.java,v 1.2 2006/07/30 00:53:33 jjanke Exp $ + */ +public class Matcher +{ + /** + * Constructor + * @param AD_Client_ID Client + * @param trxName transaction + */ + public Matcher (int AD_Client_ID, String trxName) + { + m_AD_Client_ID = AD_Client_ID; + m_trxName = trxName; + } // Matcher + + /** Client */ + private int m_AD_Client_ID; + /** Transaction */ + private String m_trxName = null; + /** Logger */ + protected CLogger log = CLogger.getCLogger (getClass()); + + /** + * Matching + *

+	 *  Derive Invoice-Receipt Match from PO-Invoice and PO-Receipt
+	 * 	Purchase Order (20)
+	 *  - Invoice1 (10)
+	 *  - Invoice2 (10)
+	 *  - Receipt1 (5)
+	 *  - Receipt2 (15)
+	 *
+	 * 	(a) Creates Directs
+	 * 		- Invoice1 - Receipt1 (5)
+	 * 		- Invoice2 - Receipt2 (10)
+	 *
+	 *  (b) Creates Indirects
+	 * 		- Invoice1 - Receipt2 (5)
+	 *  (Not imlemented)
+	 *
+	 *
+	 *  
+ * @return number of records created + */ + public int match() + { + int counter = 0; + // (a) Direct Matches + String sql = "SELECT m1.AD_Client_ID,m2.AD_Org_ID, " // 1..2 + + "m1.C_InvoiceLine_ID,m2.M_InOutLine_ID,m1.M_Product_ID, " // 3..5 + + "m1.DateTrx,m2.DateTrx, m1.Qty, m2.Qty " // 6..9 + + "FROM M_MatchPO m1, M_MatchPO m2 " + + "WHERE m1.C_OrderLine_ID=m2.C_OrderLine_ID" + + " AND m1.M_InOutLine_ID IS NULL" + + " AND m2.C_InvoiceLine_ID IS NULL" + + " AND m1.M_Product_ID=m2.M_Product_ID" + + " AND m1.AD_Client_ID=?" // #1 + // Not existing Inv Matches + + " AND NOT EXISTS (SELECT * FROM M_MatchInv mi " + + "WHERE mi.C_InvoiceLine_ID=m1.C_InvoiceLine_ID AND mi.M_InOutLine_ID=m2.M_InOutLine_ID)"; + try + { + PreparedStatement pstmt = DB.prepareStatement(sql, null); + pstmt.setInt(1, m_AD_Client_ID); + ResultSet rs = pstmt.executeQuery(); + while (rs.next()) + { + BigDecimal qty1 = rs.getBigDecimal(8); + BigDecimal qty2 = rs.getBigDecimal(9); + BigDecimal Qty = qty1.min(qty2); + if (Qty.equals(Env.ZERO)) + continue; + Timestamp dateTrx1 = rs.getTimestamp(6); + Timestamp dateTrx2 = rs.getTimestamp(7); + Timestamp DateTrx = dateTrx1; + if (dateTrx1.before(dateTrx2)) + DateTrx = dateTrx2; + // + int AD_Client_ID = rs.getInt(1); + int AD_Org_ID = rs.getInt(2); + int C_InvoiceLine_ID = rs.getInt(3); + int M_InOutLine_ID = rs.getInt(4); + int M_Product_ID = rs.getInt(5); + // + if (createMatchInv(AD_Client_ID, AD_Org_ID, + M_InOutLine_ID, C_InvoiceLine_ID, + M_Product_ID, DateTrx, Qty)) + counter++; + } + rs.close(); + pstmt.close(); + } + catch (SQLException e) + { + log.log(Level.SEVERE, "match", e); + } + log.fine("Matcher.match - Client_ID=" + m_AD_Client_ID + + ", Records created=" + counter); + return counter; + } // match + + /** + * Create MatchInv record + * @param AD_Client_ID Client + * @param AD_Org_ID Org + * @param M_InOutLine_ID Receipt + * @param C_InvoiceLine_ID Invoice + * @param M_Product_ID Product + * @param DateTrx Date + * @param Qty Qty + * @return true if record created + */ + private boolean createMatchInv (int AD_Client_ID, int AD_Org_ID, + int M_InOutLine_ID, int C_InvoiceLine_ID, + int M_Product_ID, Timestamp DateTrx, BigDecimal Qty) + { + log.fine("InvLine=" + C_InvoiceLine_ID + ",Rec=" + M_InOutLine_ID + ", Qty=" + Qty + ", " + DateTrx); + + // MMatchInv inv = new MMatchInv (); + int M_MatchInv_ID = DB.getNextID (AD_Client_ID, "M_MatchInv", m_trxName); + // + StringBuffer sql = new StringBuffer("INSERT INTO M_MatchInv (" + + "M_MatchInv_ID, " + + "AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy, " + + "M_InOutLine_ID,C_InvoiceLine_ID, " + + "M_Product_ID,DateTrx,Qty, " + + "Processing,Processed,Posted) VALUES (") + .append(M_MatchInv_ID).append(", ") + .append(AD_Client_ID).append(",").append(AD_Org_ID).append(",'Y',SysDate,0,SysDate,0, ") + .append(M_InOutLine_ID).append(",").append(C_InvoiceLine_ID).append(", ") + .append(M_Product_ID).append(",").append(DB.TO_DATE(DateTrx,true)).append(",").append(Qty) + .append(", 'N','Y','N')"); + int no = DB.executeUpdate(sql.toString(), m_trxName); + return no == 1; + } // createMatchInv + +} // Matcher diff --git a/serverRoot/src/main/server/org/compiere/acct/ProductInfo.java b/serverRoot/src/main/server/org/compiere/acct/ProductInfo.java new file mode 100644 index 0000000000..fd744a3644 --- /dev/null +++ b/serverRoot/src/main/server/org/compiere/acct/ProductInfo.java @@ -0,0 +1,390 @@ +/****************************************************************************** + * 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.acct; + +import java.math.*; +import java.sql.*; +import java.util.logging.*; +import org.compiere.model.*; +import org.compiere.util.*; + +/** + * Product Costing Information. + * + * @author Jorg Janke + * @version $Id: ProductInfo.java,v 1.2 2006/07/30 00:53:33 jjanke Exp $ + */ +public class ProductInfo +{ + /** + * Constructor + * @param M_Product_ID Product + * @param trxName transcation + */ + public ProductInfo (int M_Product_ID, String trxName) + { + m_trxName = trxName; + init (M_Product_ID); + } // ProductInfo + + /** The Product Key */ + private int m_M_Product_ID = 0; + /** Transaction */ + private String m_trxName = null; + + // Product Info + private int m_AD_Client_ID = 0; + private int m_AD_Org_ID = 0; + + private String m_productType = null; + private String m_ProductCategory = null; + + private boolean m_isBOM = false; + private boolean m_isStocked = true; + + private int m_C_RevenueRecognition_ID = 0; + + private int m_C_UOM_ID = 0; + private BigDecimal m_qty = Env.ZERO; + + /** Logger */ + protected CLogger log = CLogger.getCLogger (getClass()); + + /** + * Get Product Info (Service, Revenue Recognition). + * automatically called by constructor + * @param M_Product_ID Product + */ + private void init (int M_Product_ID) + { + m_M_Product_ID = M_Product_ID; + if (m_M_Product_ID == 0) + return; + + String sql = "SELECT p.ProductType, pc.Value, " // 1..2 + + "p.C_RevenueRecognition_ID,p.C_UOM_ID, " // 3..4 + + "p.AD_Client_ID,p.AD_Org_ID, " // 5..6 + + "p.IsBOM, p.IsStocked " // 7..8 + + "FROM M_Product_Category pc" + + " INNER JOIN M_Product p ON (pc.M_Product_Category_ID=p.M_Product_Category_ID) " + + "WHERE p.M_Product_ID=?"; // #1 + try + { + PreparedStatement pstmt = DB.prepareStatement(sql, null); + pstmt.setInt(1, m_M_Product_ID); + ResultSet rs = pstmt.executeQuery(); + if (rs.next()) + { + m_productType = rs.getString(1); + m_ProductCategory = rs.getString(2); + m_C_RevenueRecognition_ID = rs.getInt(3); + m_C_UOM_ID = rs.getInt(4); + // reference + m_AD_Client_ID = rs.getInt(5); + m_AD_Org_ID = rs.getInt(6); + // + m_isBOM = "Y".equals(rs.getString(7)); + m_isStocked = "Y".equals(rs.getString(8)); + } + rs.close(); + pstmt.close(); + } + catch (SQLException e) + { + log.log(Level.SEVERE, sql, e); + } + } // init + + /** + * Is Product/Item + * @return true if product + */ + public boolean isProduct() + { + return MProduct.PRODUCTTYPE_Item.equals(m_productType); + } // isProduct + + /** + * Is it a BOM + * @return true if BOM + */ + public boolean isBOM() + { + return m_isBOM; + } // isBOM + + /** + * Is it stocked + * @return true if stocked + */ + public boolean isStocked() + { + return m_isStocked; + } // isStocked + + /** + * Is Service + * @return true if service + */ + public boolean isService() + { + return MProduct.PRODUCTTYPE_Service.equals(m_productType); + } // isService + + /** + * Get Product Category (Value) + * @return M_Product_Category_ID + */ + public String getProductCategory() + { + return m_ProductCategory; + } // getProductCategory + + /** + * Has Revenue Recognition + * @return true if product/service has revenue recognition + */ + public boolean isRevenueRecognition() + { + return m_C_RevenueRecognition_ID != 0; + } // isRevenueRecognition + + /** + * Get Revenue Recognition + * @return C_RevenueRecognition_ID + */ + public int getC_RevenueRecognition_ID() + { + return m_C_RevenueRecognition_ID; + } // getC_RevenueRecognition_ID + + /** + * Quantity UOM + * @return C_UOM_ID + */ + public int getC_UOM_ID() + { + return m_C_UOM_ID; + } // getC_UOM_ID + + /*************************************************************************/ + + /** + * Set Quantity in Storage UOM + * @param qty quantity + */ + public void setQty (BigDecimal qty) + { + m_qty = qty; + } // setQty + + /** + * Set Quantity in UOM + * @param qty quantity + * @param C_UOM_ID UOM + */ + public void setQty (BigDecimal qty, int C_UOM_ID) + { + m_qty = MUOMConversion.convert (C_UOM_ID, m_C_UOM_ID, qty, true); // StdPrecision + if (qty != null && m_qty == null) // conversion error + { + log.severe ("Conversion error - set to " + qty); + m_qty = qty; + } + } // setQty + + /** + * Get Qty in Storage UOM + * @return qty + */ + public BigDecimal getQty() + { + return m_qty; + } // getQty + + + + /** + * Update/Create initial Cost Record. + * Check first for Purchase Price List, + * then Product Purchase Costs + * and then Price List + * @param as accounting schema + * @param create create record + * @return costs + */ + private BigDecimal updateCosts (MAcctSchema as, boolean create) + { + // Create Zero Record + if (create) + { + StringBuffer sql = new StringBuffer ("INSERT INTO M_Product_Costing " + + "(M_Product_ID,C_AcctSchema_ID," + + " AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy," + + " CurrentCostPrice,CostStandard,FutureCostPrice," + + " CostStandardPOQty,CostStandardPOAmt,CostStandardCumQty,CostStandardCumAmt," + + " CostAverage,CostAverageCumQty,CostAverageCumAmt," + + " PriceLastPO,PriceLastInv, TotalInvQty,TotalInvAmt) " + + "VALUES ("); + sql.append(m_M_Product_ID).append(",").append(as.getC_AcctSchema_ID()).append(",") + .append(m_AD_Client_ID).append(",").append(m_AD_Org_ID).append(",") + .append("'Y',SysDate,0,SysDate,0, 0,0,0, 0,0,0,0, 0,0,0, 0,0, 0,0)"); + int no = DB.executeUpdate(sql.toString(), m_trxName); + if (no == 1) + log.fine("CostingCreated"); + } + + // Try to find non ZERO Price + String costSource = "PriceList-PO"; + BigDecimal costs = getPriceList (as, true); + if (costs == null || costs.equals(Env.ZERO)) + { + costSource = "PO Cost"; + costs = getPOCost(as); + } + if (costs == null || costs.equals(Env.ZERO)) + { + costSource = "PriceList"; + costs = getPriceList (as, false); + } + + // if not found use $1 (to be able to do material transactions) + if (costs == null || costs.equals(Env.ZERO)) + { + costSource = "Not Found"; + costs = new BigDecimal("1"); + } + + // update current costs + StringBuffer sql = new StringBuffer ("UPDATE M_Product_Costing "); + sql.append("SET CurrentCostPrice=").append(costs) + .append(" WHERE M_Product_ID=").append(m_M_Product_ID) + .append(" AND C_AcctSchema_ID=").append(as.getC_AcctSchema_ID()); + int no = DB.executeUpdate(sql.toString(), m_trxName); + if (no == 1) + log.fine(costSource + " - " + costs); + return costs; + } // createCosts + + /** + * Get PO Price from PriceList - and convert it to AcctSchema Currency + * @param as accounting schema + * @param onlyPOPriceList use only PO price list + * @return po price + */ + private BigDecimal getPriceList (MAcctSchema as, boolean onlyPOPriceList) + { + StringBuffer sql = new StringBuffer ( + "SELECT pl.C_Currency_ID, pp.PriceList, pp.PriceStd, pp.PriceLimit " + + "FROM M_PriceList pl, M_PriceList_Version plv, M_ProductPrice pp " + + "WHERE pl.M_PriceList_ID = plv.M_PriceList_ID" + + " AND plv.M_PriceList_Version_ID = pp.M_PriceList_Version_ID" + + " AND pp.M_Product_ID=?"); + if (onlyPOPriceList) + sql.append(" AND pl.IsSOPriceList='N'"); + sql.append(" ORDER BY pl.IsSOPriceList ASC, plv.ValidFrom DESC"); + int C_Currency_ID = 0; + BigDecimal PriceList = null; + BigDecimal PriceStd = null; + BigDecimal PriceLimit = null; + try + { + PreparedStatement pstmt = DB.prepareStatement(sql.toString(), null); + pstmt.setInt(1, m_M_Product_ID); + ResultSet rs = pstmt.executeQuery(); + if (rs.next()) + { + C_Currency_ID = rs.getInt(1); + PriceList = rs.getBigDecimal(2); + PriceStd = rs.getBigDecimal(3); + PriceLimit = rs.getBigDecimal(4); + } + rs.close(); + pstmt.close(); + } + catch (SQLException e) + { + log.log(Level.SEVERE, sql.toString(), e); + } + // nothing found + if (C_Currency_ID == 0) + return null; + + BigDecimal price = PriceLimit; // best bet + if (price == null || price.equals(Env.ZERO)) + price = PriceStd; + if (price == null || price.equals(Env.ZERO)) + price = PriceList; + // Convert + if (price != null && !price.equals(Env.ZERO)) + price = MConversionRate.convert (as.getCtx(), + price, C_Currency_ID, as.getC_Currency_ID(), + as.getAD_Client_ID(), 0); + return price; + } // getPOPrice + + /** + * Get PO Cost from Purchase Info - and convert it to AcctSchema Currency + * @param as accounting schema + * @return po cost + */ + private BigDecimal getPOCost (MAcctSchema as) + { + String sql = "SELECT C_Currency_ID, PriceList,PricePO,PriceLastPO " + + "FROM M_Product_PO WHERE M_Product_ID=? " + + "ORDER BY IsCurrentVendor DESC"; + + int C_Currency_ID = 0; + BigDecimal PriceList = null; + BigDecimal PricePO = null; + BigDecimal PriceLastPO = null; + try + { + PreparedStatement pstmt = DB.prepareStatement(sql, null); + pstmt.setInt(1, m_M_Product_ID); + ResultSet rs = pstmt.executeQuery(); + if (rs.next()) + { + C_Currency_ID = rs.getInt(1); + PriceList = rs.getBigDecimal(2); + PricePO = rs.getBigDecimal(3); + PriceLastPO = rs.getBigDecimal(4); + } + rs.close(); + pstmt.close(); + } + catch (SQLException e) + { + log.log(Level.SEVERE, sql, e); + } + // nothing found + if (C_Currency_ID == 0) + return null; + + BigDecimal cost = PriceLastPO; // best bet + if (cost == null || cost.equals(Env.ZERO)) + cost = PricePO; + if (cost == null || cost.equals(Env.ZERO)) + cost = PriceList; + // Convert - standard precision!! - should be costing precision + if (cost != null && !cost.equals(Env.ZERO)) + cost = MConversionRate.convert (as.getCtx(), + cost, C_Currency_ID, as.getC_Currency_ID(), m_AD_Client_ID, m_AD_Org_ID); + return cost; + } // getPOCost + +} // ProductInfo diff --git a/serverRoot/src/main/server/org/compiere/ldap/LdapConnectionHandler.java b/serverRoot/src/main/server/org/compiere/ldap/LdapConnectionHandler.java new file mode 100644 index 0000000000..532c7062da --- /dev/null +++ b/serverRoot/src/main/server/org/compiere/ldap/LdapConnectionHandler.java @@ -0,0 +1,118 @@ +/****************************************************************************** + * 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. + * You may reach us at: ComPiere, Inc. - http://www.adempiere.org/license.html + * 2620 Augustine Dr. #245, Santa Clara, CA 95054, USA or info@adempiere.org + *****************************************************************************/ +package org.compiere.ldap; + +import java.io.*; +import java.net.*; +import java.util.logging.*; +import org.compiere.ldap.*; +import org.compiere.util.*; +import com.sun.jndi.ldap.*; + +/** + * LDAP Connection Handler + * + * @author Jorg Janke + * @version $Id: LdapConnectionHandler.java,v 1.1 2006/10/09 00:23:16 jjanke Exp $ + */ +public class LdapConnectionHandler extends Thread +{ + /** + * Ldap Connection Handler + * @param socket server socket + */ + public LdapConnectionHandler(Socket socket) + { + try + { + m_socket = socket; + m_socket.setTcpNoDelay(true); // should not be required + } + catch (Exception e) + { + log.log(Level.SEVERE, "", e); + } // no timeout + } // LdapConnectionHandler + + /** Socket */ + private Socket m_socket = null; + /** Logger */ + private static CLogger log = CLogger.getCLogger (LdapConnectionHandler.class); + + + /** + * Do Work + */ + public void run() + { + try + { + if (m_socket == null || m_socket.isClosed()) + return; + + boolean activeSession = true; + while (activeSession) + { + InputStream in = m_socket.getInputStream(); + BufferedOutputStream out = new BufferedOutputStream(m_socket.getOutputStream()); + // Read + byte[] buffer = new byte[512]; + int length = in.read(buffer, 0, 512); + + LdapMessage msg = new LdapMessage (buffer, length); + if (msg.getOperation() == LdapMessage.UNBIND_REQUEST) + { + activeSession = false; + out.close(); + } + else + { + LdapResult result = new LdapResult (); + byte[] bytes = result.bindResponse(); + // + out.write(bytes); + out.flush(); + } + } + } + catch (IOException e) + { + log.log(Level.SEVERE, "", e); + } + + try + { + m_socket.close(); + } + catch (Exception e) + { + log.log(Level.WARNING, "Socket", e); + } + m_socket = null; + } // run + + /** + * String Representation + * @return info + */ + public String toString() + { + StringBuffer sb = new StringBuffer ("LdapConnectionHandler["); + sb.append (hashCode()).append ("]"); + return sb.toString (); + } // toString + +} // LdapConnectionHandler diff --git a/serverRoot/src/main/server/org/compiere/ldap/LdapMessage.java b/serverRoot/src/main/server/org/compiere/ldap/LdapMessage.java new file mode 100644 index 0000000000..e3e0c26e0f --- /dev/null +++ b/serverRoot/src/main/server/org/compiere/ldap/LdapMessage.java @@ -0,0 +1,172 @@ +/****************************************************************************** + * 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. + * You may reach us at: ComPiere, Inc. - http://www.adempiere.org/license.html + * 2620 Augustine Dr. #245, Santa Clara, CA 95054, USA or info@adempiere.org + *****************************************************************************/ +package org.compiere.ldap; + +import java.util.logging.*; +import org.compiere.util.*; +import com.sun.jndi.ldap.*; + +/** + * Ldap Message + * + * @author Jorg Janke + * @version $Id: LdapMessage.java,v 1.1 2006/10/09 00:23:16 jjanke Exp $ + */ +public class LdapMessage +{ + /** + * Ldap Message + * @param data BER data + * @param length Ber data length + */ + public LdapMessage (byte[] data, int length) + { + try + { + decode(data, length); + } + catch (Exception e) + { + log.log(Level.SEVERE, data.toString(), e); + } + } // LdapMessage + + /** + LDAPMessage ::= SEQUENCE { + messageID MessageID, + protocolOp CHOICE { + bindRequest BindRequest, + bindResponse BindResponse, + unbindRequest UnbindRequest, + searchRequest SearchRequest, + searchResEntry SearchResultEntry, + searchResDone SearchResultDone, + searchResRef SearchResultReference, + modifyRequest ModifyRequest, + modifyResponse ModifyResponse, + addRequest AddRequest, + addResponse AddResponse, + delRequest DelRequest, + delResponse DelResponse, + modDNRequest ModifyDNRequest, + modDNResponse ModifyDNResponse, + compareRequest CompareRequest, + compareResponse CompareResponse, + abandonRequest AbandonRequest, + extendedReq ExtendedRequest, + extendedResp ExtendedResponse }, + controls [0] Controls OPTIONAL } + **/ + + static public final int BIND_REQUEST = 0; + static public final int BIND_RESPONSE = 1; + static public final int UNBIND_REQUEST = 2; + static public final int SEARCH_REQUEST = 3; + static public final int SEARCH_RESENTRY = 4; + static public final int SEARCH_RESDONE = 5; + static public final int MODIFY_REQUEST = 6; + static public final int MODIFY_RESPONSE = 7; + static public final int ADD_REQUEST = 8; + static public final int ADD_RESPONSE = 9; + static public final int DEL_REQUEST = 10; + static public final int DEL_RESPONSE = 11; + static public final int MODDN_REQUEST = 12; + static public final int MODDN_RESPONSE = 13; + static public final int COMPARE_REQUEST = 14; + static public final int COMPARE_RESPONSE = 15; + static public final int ABANDON_REQUEST = 16; + static public final int EXTENDED_REQUEST = 17; + static public final int EXTENDED_RESPONSE = 18; + + static public final int[] PROTOCOL_OP = { + BIND_REQUEST, BIND_RESPONSE, UNBIND_REQUEST, + SEARCH_REQUEST, SEARCH_RESENTRY, SEARCH_RESDONE, + MODIFY_REQUEST, MODIFY_RESPONSE, ADD_REQUEST, ADD_RESPONSE, + DEL_REQUEST, DEL_RESPONSE, MODDN_REQUEST, MODDN_RESPONSE, + COMPARE_REQUEST, COMPARE_RESPONSE, ABANDON_REQUEST, + EXTENDED_REQUEST, EXTENDED_RESPONSE}; + + + /** Logger */ + private static CLogger log = CLogger.getCLogger (LdapMessage.class); + /** Protocol Operation */ + private int m_protocolOp = -1; + + + /** + * Decode Message + * @param data data + * @param length length + * @throws Exception + */ + private void decode (byte[] data, int length) throws Exception + { + BerDecoder decoder = new BerDecoder(data, 0, length); + int left = decoder.bytesLeft(); + int pos = decoder.getParsePosition(); + // + int seq = decoder.parseSeq(null); + left = decoder.bytesLeft(); + pos = decoder.getParsePosition(); + // + int messageID = decoder.parseInt(); + left = decoder.bytesLeft(); + pos = decoder.getParsePosition(); + // + int peek = decoder.peekByte(); + m_protocolOp = decoder.parseSeq(PROTOCOL_OP); + m_protocolOp -= Ber.ASN_APPLICATION; + if (m_protocolOp - Ber.ASN_CONSTRUCTOR >= 0) + m_protocolOp -= Ber.ASN_CONSTRUCTOR; + left = decoder.bytesLeft(); + pos = decoder.getParsePosition(); + // + // Payload + if (m_protocolOp == BIND_REQUEST) + { + int version = decoder.parseInt(); + left = decoder.bytesLeft(); + pos = decoder.getParsePosition(); + // + byte[] dn = decoder.parseOctetString(Ber.ASN_OCTET_STR, null); + left = decoder.bytesLeft(); + pos = decoder.getParsePosition(); + // + byte[] authentification = decoder.parseOctetString(Ber.ASN_CONTEXT, null); + left = decoder.bytesLeft(); + pos = decoder.getParsePosition(); + // + log.info("#" + messageID + ": bind - version=" + version + ", dn=" + new String(dn) + + ", auth=" + new String (authentification)); + } + else if (m_protocolOp == UNBIND_REQUEST) + log.info("#" + messageID + ": unbind"); + else + { + log.warning("#" + messageID + ": Unknown Op + " + m_protocolOp); + } + } // decode + + /** + * Get Operation Code + * @return protocolOp + */ + public int getOperation() + { + return m_protocolOp; + } // getOperation + +} // LdapMessage diff --git a/serverRoot/src/main/server/org/compiere/ldap/LdapProcessor.java b/serverRoot/src/main/server/org/compiere/ldap/LdapProcessor.java new file mode 100644 index 0000000000..13a181595a --- /dev/null +++ b/serverRoot/src/main/server/org/compiere/ldap/LdapProcessor.java @@ -0,0 +1,141 @@ +/****************************************************************************** + * 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. + * You may reach us at: ComPiere, Inc. - http://www.adempiere.org/license.html + * 2620 Augustine Dr. #245, Santa Clara, CA 95054, USA or info@adempiere.org + *****************************************************************************/ +package org.compiere.ldap; + +import java.net.*; +import java.sql.*; +import java.util.*; +import java.util.logging.*; +import javax.naming.ldap.*; +import org.compiere.*; +import org.compiere.ldap.*; +import org.compiere.model.*; +import org.compiere.server.*; +import org.compiere.util.*; + +/** + * LDAP Server + * + * @author Jorg Janke + * @version $Id: LdapProcessor.java,v 1.1 2006/10/09 00:23:16 jjanke Exp $ + */ +public class LdapProcessor extends AdempiereServer +{ + /** + * Ldap Processor (Server) + * @param model Ldap Model + */ + public LdapProcessor (LdapProcessorModel model) + { + super (model, 300); + m_model = model; + init(); + } // LdapProcessor + + /** The Concrete Model */ + private LdapProcessorModel m_model = null; + /** Last Summary */ + private StringBuffer m_summary = new StringBuffer(); + /** Client info */ + private MClient m_client = null; + /** Server Socket */ + private ServerSocket m_serverSocket = null; + /** Counter */ + private int m_counter = 0; + + + /** + * Do Work + */ + protected void doWork() + { + // Close Socket + if (m_serverSocket != null) + { + try + { + m_serverSocket.close(); + } + catch (Exception e) + { + } + } + m_counter = 0; + // + m_summary = new StringBuffer(m_model.toString()) + .append(" - "); + // + + try + { + m_serverSocket = new ServerSocket(m_model.getLdapPort()); + log.log(Level.INFO, "Opened Port=" + m_model.getLdapPort()); + while (!isInterrupted()) + { + Socket socket = m_serverSocket.accept(); // waits for connection + log.log(Level.FINE, "Connection on Port=" + m_model.getLdapPort()); + LdapConnectionHandler handler = new LdapConnectionHandler (socket); + handler.start(); + m_counter++; + } + } + catch (Exception e) + { + log.log(Level.WARNING, "Port=" + m_model.getLdapPort(), e); + m_summary.append(e.toString()); + } + + } // doWork + + /** + * Initialize + */ + private void init() + { + try + { + InitialLdapContext lctx = new InitialLdapContext(); + // lctx.setRequestControls(critModCtls); + // lctx.modifyAttributes(name, mods); + Control[] respCtls = lctx.getResponseControls(); + } + catch (Exception e) + { + } + } // + + /** + * Get Server Info + * @return info + */ + public String getServerInfo() + { + return "#" + p_runCount + " - Last=" + m_summary.toString() + + "; Counter=" + m_counter; + } // getServerInfo + + /** + * Test + * @param args + */ + public static void main(String[] args) + { + Adempiere.startup(true); + new LdapProcessor(new LdapProcessorModel(new Properties())).doWork(); + } // main + +} // LdapProcessor + diff --git a/serverRoot/src/main/server/org/compiere/ldap/LdapProcessorModel.java b/serverRoot/src/main/server/org/compiere/ldap/LdapProcessorModel.java new file mode 100644 index 0000000000..b2b04e7a4a --- /dev/null +++ b/serverRoot/src/main/server/org/compiere/ldap/LdapProcessorModel.java @@ -0,0 +1,158 @@ +/****************************************************************************** + * 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. + * You may reach us at: ComPiere, Inc. - http://www.adempiere.org/license.html + * 2620 Augustine Dr. #245, Santa Clara, CA 95054, USA or info@adempiere.org + *****************************************************************************/ +package org.compiere.ldap; + +import java.sql.*; +import java.util.*; +import org.compiere.model.*; + +/** + * Interim LDAP Server Model + * + * @author Jorg Janke + * @version $Id: LdapProcessorModel.java,v 1.1 2006/10/09 00:23:16 jjanke Exp $ + */ +public class LdapProcessorModel implements AdempiereProcessor +{ + /** + * Ldap Processor Model + * @param ctx context + */ + public LdapProcessorModel (Properties ctx) + { + m_ctx = ctx; + } + // Properties + private Properties m_ctx = null; + + private Timestamp m_dateNextRun; + private Timestamp m_dateLastRun; + + + public int getLdapPort() + { + return 389; + } + + + + /** + * String Representation + * @return info + */ + public String toString() + { + StringBuffer sb = new StringBuffer (getName()); + sb.append (";Port=").append (getLdapPort()); + return sb.toString (); + } // toString + + + + + /************************************************************************** + * getAD_Client_ID + * @see org.compiere.model.AdempiereProcessor#getAD_Client_ID() + * @return 0 + */ + public int getAD_Client_ID() + { + return 0; + } + /** + * getName + * @see org.compiere.model.AdempiereProcessor#getName() + * @return name + */ + public String getName() + { + return "Adempiere LDAP Server"; + } + /** + * getDescription + * @see org.compiere.model.AdempiereProcessor#getDescription() + * @return - + */ + public String getDescription() + { + return "-"; + } + /** + * Get Ctx + * @return context + */ + public Properties getCtx() + { + return m_ctx; + } + /** + * GetFrequencyType + * @see org.compiere.model.AdempiereProcessor#getFrequencyType() + * @return min + */ + public String getFrequencyType() + { + return MRequestProcessor.FREQUENCYTYPE_Minute; + } + /** + * getFrequency + * @see org.compiere.model.AdempiereProcessor#getFrequency() + * @return 1 + */ + public int getFrequency() + { + return 1; + } + + /** + * Get Unique Server ID + * @return id + */ + public String getServerID() + { + return "Ldap"; + } + + public Timestamp getDateNextRun(boolean requery) + { + return m_dateNextRun; + } + + public void setDateNextRun(Timestamp dateNextWork) + { + m_dateNextRun = dateNextWork; + } + + public Timestamp getDateLastRun() + { + return m_dateLastRun; + } + + public void setDateLastRun(Timestamp dateLastRun) + { + m_dateLastRun = dateLastRun; + } + + public boolean save() + { + return true; + } + + public AdempiereProcessorLog[] getLogs() + { + return new AdempiereProcessorLog[0]; + } +} diff --git a/serverRoot/src/main/server/org/compiere/ldap/LdapResult.java b/serverRoot/src/main/server/org/compiere/ldap/LdapResult.java new file mode 100644 index 0000000000..7c459ed878 --- /dev/null +++ b/serverRoot/src/main/server/org/compiere/ldap/LdapResult.java @@ -0,0 +1,147 @@ +/****************************************************************************** + * 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. + * You may reach us at: ComPiere, Inc. - http://www.adempiere.org/license.html + * 2620 Augustine Dr. #245, Santa Clara, CA 95054, USA or info@adempiere.org + *****************************************************************************/ +package org.compiere.ldap; + +import java.io.*; +import java.util.logging.*; +import org.compiere.util.*; +import com.sun.jndi.ldap.*; + +/** + * Ldap Wire Response + * + * @author Jorg Janke + * @version $Id: LdapResult.java,v 1.1 2006/10/09 00:23:16 jjanke Exp $ + */ +public class LdapResult +{ + + public LdapResult() + { + super (); + } // LdapResult + + /** + LDAPResult ::= SEQUENCE { + resultCode ENUMERATED { + success (0), + operationsError (1), + protocolError (2), + timeLimitExceeded (3), + sizeLimitExceeded (4), + compareFalse (5), + compareTrue (6), + + authMethodNotSupported (7), + strongAuthRequired (8), + -- 9 reserved -- + referral (10), -- new + adminLimitExceeded (11), -- new + unavailableCriticalExtension (12), -- new + confidentialityRequired (13), -- new + saslBindInProgress (14), -- new + noSuchAttribute (16), + undefinedAttributeType (17), + inappropriateMatching (18), + constraintViolation (19), + attributeOrValueExists (20), + invalidAttributeSyntax (21), + noSuchObject (32), + aliasProblem (33), + invalidDNSyntax (34), + -- 35 reserved for undefined isLeaf -- + aliasDereferencingProblem (36), + -- 37-47 unused -- + inappropriateAuthentication (48), + invalidCredentials (49), + insufficientAccessRights (50), + busy (51), + unavailable (52), + unwillingToPerform (53), + loopDetect (54), + -- 55-63 unused -- + namingViolation (64), + objectClassViolation (65), + notAllowedOnNonLeaf (66), + notAllowedOnRDN (67), + entryAlreadyExists (68), + objectClassModsProhibited (69), + -- 70 reserved for CLDAP -- + affectsMultipleDSAs (71), -- new + -- 72-79 unused -- + other (80) }, + -- 81-90 reserved for APIs -- + matchedDN LDAPDN, + errorMessage LDAPString, + referral [3] Referral OPTIONAL } + **/ + + /** Encoder */ + private BerEncoder m_encoder = new BerEncoder(); + /** Logger */ + private static CLogger log = CLogger.getCLogger (LdapResult.class); + + /** + * Bind Response + * @return reponse + */ + public byte[] bindResponse() + { + try + { +/** + m_encoder.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR); + for (int i = 0; i < sortKeys.length; i++) { + ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR); + ber.encodeString(sortKeys[i].getAttributeID(), true); // v3 + if ((matchingRule = sortKeys[i].getMatchingRuleID()) != null) { + ber.encodeString(matchingRule, (Ber.ASN_CONTEXT | 0), true); + } + if (! sortKeys[i].isAscending()) { + ber.encodeBoolean(true, (Ber.ASN_CONTEXT | 1)); + } + ber.endSeq(); + } +*/ + // payload + m_encoder.beginSeq(Ber.ASN_APPLICATION | LdapMessage.BIND_RESPONSE); + // Response + m_encoder.encodeInt(0); // success + m_encoder.encodeOctetString("cn=testCN".getBytes(), 0); // matched DN + m_encoder.encodeOctetString("".getBytes(), 0); // error mag + // referral + // sasl + // + m_encoder.endSeq(); + log.info("Success"); + } + catch (Exception e) + { + log.log(Level.SEVERE, "", e); + } + return getResult(); + } // bindResponse + + /** + * Get BER Result as byte array + * @return byte array + */ + public byte[] getResult() + { + return m_encoder.getTrimmedBuf(); + } // getResult + +} // LdapResult diff --git a/serverRoot/src/main/server/org/compiere/server/AcctProcessor.java b/serverRoot/src/main/server/org/compiere/server/AcctProcessor.java new file mode 100644 index 0000000000..865aa2142b --- /dev/null +++ b/serverRoot/src/main/server/org/compiere/server/AcctProcessor.java @@ -0,0 +1,172 @@ +/****************************************************************************** + * 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.server; + +import java.sql.*; +import java.util.logging.*; +import org.compiere.acct.*; +import org.compiere.model.*; +import org.compiere.util.*; + + +/** + * Accounting Processor + * + * @author Jorg Janke + * @version $Id: AcctProcessor.java,v 1.3 2006/07/30 00:53:33 jjanke Exp $ + */ +public class AcctProcessor extends AdempiereServer +{ + /** + * Accounting Processor + * @param model model + */ + public AcctProcessor (MAcctProcessor model) + { + super (model, 30); // 30 seconds delay + m_model = model; + m_client = MClient.get(model.getCtx(), model.getAD_Client_ID()); + } // AcctProcessor + + /** The Concrete Model */ + private MAcctProcessor m_model = null; + /** Last Summary */ + private StringBuffer m_summary = new StringBuffer(); + /** Client onfo */ + private MClient m_client = null; + /** Accounting Schemata */ + private MAcctSchema[] m_ass = null; + + /** + * Work + */ + protected void doWork () + { + m_summary = new StringBuffer(); + // Get Schemata + if (m_model.getC_AcctSchema_ID() == 0) + m_ass = MAcctSchema.getClientAcctSchema(getCtx(), m_model.getAD_Client_ID()); + else // only specific accounting schema + m_ass = new MAcctSchema[] {new MAcctSchema (getCtx(), m_model.getC_AcctSchema_ID(), null)}; + // + postSession(); + MCost.create(m_client); + // + int no = m_model.deleteLog(); + m_summary.append("Logs deleted=").append(no); + // + MAcctProcessorLog pLog = new MAcctProcessorLog(m_model, m_summary.toString()); + pLog.setReference("#" + String.valueOf(p_runCount) + + " - " + TimeUtil.formatElapsed(new Timestamp(p_startWork))); + pLog.save(); + } // doWork + + /** + * Post Session + */ + private void postSession() + { + for (int i = 0; i < Doc.documentsTableID.length; i++) + { + int AD_Table_ID = Doc.documentsTableID[i]; + String TableName = Doc.documentsTableName[i]; + // Post only special documents + if (m_model.getAD_Table_ID() != 0 + && m_model.getAD_Table_ID() != AD_Table_ID) + continue; + // SELECT * FROM table + StringBuffer sql = new StringBuffer ("SELECT * FROM ").append(TableName) + .append(" WHERE AD_Client_ID=?") + .append(" AND Processed='Y' AND Posted='N' AND IsActive='Y'") + .append(" ORDER BY Created"); + // + int count = 0; + int countError = 0; + PreparedStatement pstmt = null; + try + { + pstmt = DB.prepareStatement(sql.toString(), null); + pstmt.setInt(1, m_model.getAD_Client_ID()); + ResultSet rs = pstmt.executeQuery(); + while (!isInterrupted() && rs.next()) + { + count++; + boolean ok = true; + try + { + Doc doc = Doc.get (m_ass, AD_Table_ID, rs, null); + if (doc == null) + { + log.severe(getName() + ": No Doc for " + TableName); + ok = false; + } + else + { + String error = doc.post(false, false); // post no force/repost + ok = error == null; + } + } + catch (Exception e) + { + log.log(Level.SEVERE, getName() + ": " + TableName, e); + ok = false; + } + if (!ok) + countError++; + } + rs.close(); + pstmt.close(); + pstmt = null; + } + catch (Exception e) + { + log.log(Level.SEVERE, sql.toString(), e); + } + if (pstmt != null) + { + try + { + pstmt.close(); + } + catch (Exception e) + { + } + } + // + if (count > 0) + { + m_summary.append(TableName).append("=").append(count); + if (countError > 0) + m_summary.append("(Errors=").append(countError).append(")"); + m_summary.append(" - "); + log.finer(getName() + ": " + m_summary.toString()); + } + else + log.finer(getName() + ": " + TableName + " - no work"); + } + } // postSession + + /** + * Get Server Info + * @return info + */ + public String getServerInfo() + { + return "#" + p_runCount + " - Last=" + m_summary.toString(); + } // getServerInfo + +} // AcctProcessor diff --git a/serverRoot/src/main/server/org/compiere/server/AdempiereServer.java b/serverRoot/src/main/server/org/compiere/server/AdempiereServer.java new file mode 100644 index 0000000000..4dde40ed9c --- /dev/null +++ b/serverRoot/src/main/server/org/compiere/server/AdempiereServer.java @@ -0,0 +1,388 @@ +/****************************************************************************** + * 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.server; + +import java.sql.*; +import java.util.*; +import java.util.logging.*; +import org.compiere.ldap.*; +import org.compiere.model.*; +import org.compiere.util.*; +import org.compiere.wf.*; + +/** + * Adempiere Server Base + * + * @author Jorg Janke + * @version $Id: AdempiereServer.java,v 1.3 2006/10/09 00:23:26 jjanke Exp $ + */ +public abstract class AdempiereServer extends Thread +{ + /** + * Create New Server Thead + * @param model model + * @return server tread or null + */ + public static AdempiereServer create (AdempiereProcessor model) + { + if (model instanceof MRequestProcessor) + return new RequestProcessor ((MRequestProcessor)model); + if (model instanceof MWorkflowProcessor) + return new WorkflowProcessor ((MWorkflowProcessor)model); + if (model instanceof MAcctProcessor) + return new AcctProcessor ((MAcctProcessor)model); + if (model instanceof MAlertProcessor) + return new AlertProcessor ((MAlertProcessor)model); + if (model instanceof MScheduler) + return new Scheduler ((MScheduler)model); + if (model instanceof LdapProcessorModel) + return new LdapProcessor((LdapProcessorModel)model); + // + throw new IllegalArgumentException("Unknown Processor"); + } // create + + + /************************************************************************** + * Server Base Class + * @param model model + * @param initialNap delay time running in sec + */ + protected AdempiereServer (AdempiereProcessor model, int initialNap) + { + super (AdempiereServerGroup.get(), null, model.getName(), 0); + p_model = model; + m_ctx = new Properties(model.getCtx()); + if (p_system == null) + p_system = MSystem.get(m_ctx); + p_client = MClient.get(m_ctx); + Env.setContext(m_ctx, "#AD_Client_ID", p_client.getAD_Client_ID()); + m_initialNap = initialNap; + // log.info(model.getName() + " - " + getThreadGroup()); + } // ServerBase + + /** The Processor Model */ + protected AdempiereProcessor p_model; + /** Initial nap is seconds */ + private int m_initialNap = 0; + + /** Miliseconds to sleep - 10 Min default */ + private long m_sleepMS = 600000; + /** Sleeping */ + private volatile boolean m_sleeping = false; + /** Server start time */ + private long m_start = 0; + /** Number of Work executions */ + protected int p_runCount = 0; + /** Tine start of work */ + protected long p_startWork = 0; + /** Number MS of last Run */ + private long m_runLastMS = 0; + /** Number of MS total */ + private long m_runTotalMS = 0; + /** When to run next */ + private long m_nextWork = 0; + + /** Logger */ + protected CLogger log = CLogger.getCLogger(getClass()); + /** Context */ + private Properties m_ctx = null; + /** System */ + protected static MSystem p_system = null; + /** Client */ + protected MClient p_client = null; + + /** + * Get Server Context + * @return context + */ + public Properties getCtx() + { + return m_ctx; + } // getCtx + + /** + * @return Returns the sleepMS. + */ + public long getSleepMS () + { + return m_sleepMS; + } // getSleepMS + + + /** + * Sleep for set time + * @return true if not interrupted + */ + public boolean sleep() + { + if (isInterrupted()) + { + log.info (getName() + ": interrupted"); + return false; + } + log.fine(getName() + ": sleeping " + TimeUtil.formatElapsed(m_sleepMS)); + m_sleeping = true; + try + { + sleep (m_sleepMS); + } + catch (InterruptedException e) + { + log.info (getName() + ": interrupted"); + m_sleeping = false; + return false; + } + m_sleeping = false; + return true; + } // sleep + + /** + * Run Now + */ + public void runNow() + { + log.info(getName()); + p_startWork = System.currentTimeMillis(); + doWork(); + long now = System.currentTimeMillis(); + // --------------- + + p_runCount++; + m_runLastMS = now - p_startWork; + m_runTotalMS += m_runLastMS; + // + p_model.setDateLastRun(new Timestamp(now)); + p_model.save(); + // + log.fine(getName() + ": " + getStatistics()); + } // runNow + + /************************************************************************** + * Run async + */ + public void run () + { + try + { + log.fine(getName() + ": pre-nap - " + m_initialNap); + sleep (m_initialNap * 1000); + } + catch (InterruptedException e) + { + log.log(Level.SEVERE, getName() + ": pre-nap interrupted", e); + return; + } + + m_start = System.currentTimeMillis(); + while (true) + { + if (m_nextWork == 0) + { + Timestamp dateNextRun = getDateNextRun(true); + if (dateNextRun != null) + m_nextWork = dateNextRun.getTime(); + } + long now = System.currentTimeMillis(); + if (m_nextWork > now) + { + m_sleepMS = m_nextWork - now; + if (!sleep ()) + break; + } + if (isInterrupted()) + { + log.info (getName() + ": interrupted"); + break; + } + + // --------------- + p_startWork = System.currentTimeMillis(); + doWork(); + now = System.currentTimeMillis(); + // --------------- + + p_runCount++; + m_runLastMS = now - p_startWork; + m_runTotalMS += m_runLastMS; + // + m_sleepMS = calculateSleep(); + m_nextWork = now + m_sleepMS; + // + p_model.setDateLastRun(new Timestamp(now)); + p_model.setDateNextRun(new Timestamp(m_nextWork)); + p_model.save(); + // + log.fine(getName() + ": " + getStatistics()); + if (!sleep()) + break; + } + m_start = 0; + } // run + + /** + * Get Run Statistics + * @return Statistic info + */ + public String getStatistics() + { + return "Run #" + p_runCount + + " - Last=" + TimeUtil.formatElapsed(m_runLastMS) + + " - Total=" + TimeUtil.formatElapsed(m_runTotalMS) + + " - Next " + TimeUtil.formatElapsed(m_nextWork - System.currentTimeMillis()); + } // getStatistics + + /** + * Do the actual Work + */ + protected abstract void doWork(); + + /** + * Get Server Info + * @return info + */ + public abstract String getServerInfo(); + + /** + * Get Unique ID + * @return Unique ID + */ + public String getServerID() + { + return p_model.getServerID(); + } // getServerID + + /** + * Get the date Next run + * @param requery requery database + * @return date next run + */ + public Timestamp getDateNextRun (boolean requery) + { + return p_model.getDateNextRun(requery); + } // getDateNextRun + + /** + * Get the date Last run + * @return date lext run + */ + public Timestamp getDateLastRun () + { + return p_model.getDateLastRun(); + } // getDateLastRun + + /** + * Get Description + * @return Description + */ + public String getDescription() + { + return p_model.getDescription(); + } // getDescription + + /** + * Get Model + * @return Model + */ + public AdempiereProcessor getModel() + { + return p_model; + } // getModel + + /** + * Calculate Sleep ms + * @return miliseconds + */ + private long calculateSleep () + { + String frequencyType = p_model.getFrequencyType(); + int frequency = p_model.getFrequency(); + if (frequency < 1) + frequency = 1; + // + long typeSec = 600; // 10 minutes + if (frequencyType == null) + typeSec = 300; // 5 minutes + else if (X_R_RequestProcessor.FREQUENCYTYPE_Minute.equals(frequencyType)) + typeSec = 60; + else if (X_R_RequestProcessor.FREQUENCYTYPE_Hour.equals(frequencyType)) + typeSec = 3600; + else if (X_R_RequestProcessor.FREQUENCYTYPE_Day.equals(frequencyType)) + typeSec = 86400; + // + return typeSec * 1000 * frequency; // ms + } // calculateSleep + + /** + * Is Sleeping + * @return sleeping + */ + public boolean isSleeping() + { + return m_sleeping; + } // isSleeping + + /** + * String Representation + * @return info + */ + public String toString () + { + StringBuffer sb = new StringBuffer (getName()) + .append (",Prio=").append(getPriority()) + .append (",").append (getThreadGroup()) + .append (",Alive=").append(isAlive()) + .append (",Sleeping=").append(m_sleeping) + .append (",Last=").append(getDateLastRun()); + if (m_sleeping) + sb.append (",Next=").append(getDateNextRun(false)); + return sb.toString (); + } // toString + + /** + * Get Seconds Alive + * @return seconds alive + */ + public int getSecondsAlive() + { + if (m_start == 0) + return 0; + long now = System.currentTimeMillis(); + long ms = (now-m_start) / 1000; + return (int)ms; + } // getSecondsAlive + + /** + * Get Start Time + * @return start time + */ + public Timestamp getStartTime() + { + if (m_start == 0) + return null; + return new Timestamp (m_start); + } // getStartTime + + /** + * Get Processor Logs + * @return logs + */ + public AdempiereProcessorLog[] getLogs() + { + return p_model.getLogs(); + } // getLogs + +} // AdempiereServer diff --git a/serverRoot/src/main/server/org/compiere/server/AdempiereServerGroup.java b/serverRoot/src/main/server/org/compiere/server/AdempiereServerGroup.java new file mode 100644 index 0000000000..d2a14d6358 --- /dev/null +++ b/serverRoot/src/main/server/org/compiere/server/AdempiereServerGroup.java @@ -0,0 +1,90 @@ +/****************************************************************************** + * 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.server; + +import org.compiere.util.*; + +/** + * Adempiere Server Group + * + * @author Jorg Janke + * @version $Id: AdempiereServerGroup.java,v 1.2 2006/07/30 00:53:33 jjanke Exp $ + */ +public class AdempiereServerGroup extends ThreadGroup +{ + /** + * Get Adempiere Server Group + * @return Server Group + */ + public static AdempiereServerGroup get() + { + if (s_group == null || s_group.isDestroyed()) + s_group = new AdempiereServerGroup(); + return s_group; + } // get + + /** Group */ + private static AdempiereServerGroup s_group = null; + + /** + * AdempiereServerGroup + */ + private AdempiereServerGroup () + { + super ("AdempiereServers"); + setDaemon(true); + setMaxPriority(Thread.MAX_PRIORITY); + log.info(getName() + " - Parent=" + getParent()); + } // AdempiereServerGroup + + /** Logger */ + protected CLogger log = CLogger.getCLogger(getClass()); + + /** + * Uncaught Exception + * @param t thread + * @param e exception + */ + public void uncaughtException (Thread t, Throwable e) + { + log.info ("uncaughtException = " + e.toString()); + super.uncaughtException (t, e); + } // uncaughtException + + /** + * String Representation + * @return name + */ + public String toString () + { + return getName(); + } // toString + + /** + * Dump Info + */ + public void dump () + { + log.fine(getName() + (isDestroyed() ? " (destroyed)" : "")); + log.fine("- Parent=" + getParent()); + Thread[] list = new Thread[activeCount()]; + log.fine("- Count=" + enumerate(list, true)); + for (int i = 0; i < list.length; i++) + log.fine("-- " + list[i]); + } // dump + +} // AdempiereServerGroup diff --git a/serverRoot/src/main/server/org/compiere/server/AdempiereServerMgr.java b/serverRoot/src/main/server/org/compiere/server/AdempiereServerMgr.java new file mode 100644 index 0000000000..5285c1cdd6 --- /dev/null +++ b/serverRoot/src/main/server/org/compiere/server/AdempiereServerMgr.java @@ -0,0 +1,532 @@ +/****************************************************************************** + * 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.server; + +import java.sql.*; +import java.util.*; +import java.util.logging.*; +import org.compiere.*; +import org.compiere.ldap.*; +import org.compiere.model.*; +import org.compiere.util.*; +import org.compiere.wf.*; + +/** + * Adempiere Server Manager + * + * @author Jorg Janke + * @version $Id: AdempiereServerMgr.java,v 1.4 2006/10/09 00:23:26 jjanke Exp $ + */ +public class AdempiereServerMgr +{ + /** + * Get Adempiere Server Manager + * @return mgr + */ + public static AdempiereServerMgr get() + { + if (m_serverMgr == null) + { + // for faster subsequent calls + m_serverMgr = new AdempiereServerMgr(); + m_serverMgr.startServers(); + m_serverMgr.log.info(m_serverMgr.toString()); + } + return m_serverMgr; + } // get + + /** Singleton */ + private static AdempiereServerMgr m_serverMgr = null; + /** Logger */ + protected CLogger log = CLogger.getCLogger(getClass()); + + /************************************************************************** + * Adempiere Server Manager + */ + private AdempiereServerMgr () + { + super(); + startEnvironment(); + // m_serverMgr.startServers(); + } // AdempiereServerMgr + + /** The Servers */ + private ArrayList m_servers = new ArrayList(); + /** Context */ + private Properties m_ctx = Env.getCtx(); + /** Start */ + private Timestamp m_start = new Timestamp(System.currentTimeMillis()); + + /** + * Start Environment + * @return true if started + */ + private boolean startEnvironment() + { + Adempiere.startup(false); + log.info(""); + + // Set Session + MSession session = MSession.get(getCtx(), true); + session.setWebStoreSession(false); + session.setWebSession("Server"); + session.save(); + // + return true; + } // startEnvironment + + /** + * Start Environment + * @return true if started + */ + private boolean startServers() + { + log.info(""); + int noServers = 0; + // Accounting + MAcctProcessor[] acctModels = MAcctProcessor.getActive(m_ctx); + for (int i = 0; i < acctModels.length; i++) + { + MAcctProcessor pModel = acctModels[i]; + AdempiereServer server = AdempiereServer.create(pModel); + server.start(); + server.setPriority(Thread.NORM_PRIORITY-2); + m_servers.add(server); + } + // Request + MRequestProcessor[] requestModels = MRequestProcessor.getActive(m_ctx); + for (int i = 0; i < requestModels.length; i++) + { + MRequestProcessor pModel = requestModels[i]; + AdempiereServer server = AdempiereServer.create(pModel); + server.start(); + server.setPriority(Thread.NORM_PRIORITY-2); + m_servers.add(server); + } + // Workflow + MWorkflowProcessor[] workflowModels = MWorkflowProcessor.getActive(m_ctx); + for (int i = 0; i < workflowModels.length; i++) + { + MWorkflowProcessor pModel = workflowModels[i]; + AdempiereServer server = AdempiereServer.create(pModel); + server.start(); + server.setPriority(Thread.NORM_PRIORITY-2); + m_servers.add(server); + } + // Alert + MAlertProcessor[] alertModels = MAlertProcessor.getActive(m_ctx); + for (int i = 0; i < alertModels.length; i++) + { + MAlertProcessor pModel = alertModels[i]; + AdempiereServer server = AdempiereServer.create(pModel); + server.start(); + server.setPriority(Thread.NORM_PRIORITY-2); + m_servers.add(server); + } + // Scheduler + MScheduler[] schedulerModels = MScheduler.getActive(m_ctx); + for (int i = 0; i < schedulerModels.length; i++) + { + MScheduler pModel = schedulerModels[i]; + AdempiereServer server = AdempiereServer.create(pModel); + server.start(); + server.setPriority(Thread.NORM_PRIORITY-2); + m_servers.add(server); + } + // LDAP + LdapProcessorModel lp = new LdapProcessorModel(m_ctx); + AdempiereServer server = AdempiereServer.create(lp); + server.start(); + server.setPriority(Thread.NORM_PRIORITY-2); + m_servers.add(server); + + + log.fine("#" + noServers); + return startAll(); + } // startEnvironment + + /** + * Get Server Context + * @return ctx + */ + public Properties getCtx() + { + return m_ctx; + } // getCtx + + /** + * Start all servers + * @return true if started + */ + public boolean startAll() + { + log.info (""); + AdempiereServer[] servers = getInActive(); + for (int i = 0; i < servers.length; i++) + { + AdempiereServer server = servers[i]; + try + { + if (server.isAlive()) + continue; + // Wait until dead + if (server.isInterrupted()) + { + int maxWait = 10; // 10 iterations = 1 sec + while (server.isAlive()) + { + if (maxWait-- == 0) + { + log.severe ("Wait timeout for interruped " + server); + break; + } + try + { + Thread.sleep(100); // 1/10 sec + } + catch (InterruptedException e) + { + log.log(Level.SEVERE, "While sleeping", e); + } + } + } + // Do start + if (!server.isAlive()) + { + // replace + server = AdempiereServer.create (server.getModel()); + if (server == null) + m_servers.remove(i); + else + m_servers.set(i, server); + server.start(); + server.setPriority(Thread.NORM_PRIORITY-2); + } + } + catch (Exception e) + { + log.log(Level.SEVERE, "Server: " + server, e); + } + } // for all servers + + // Final Check + int noRunning = 0; + int noStopped = 0; + for (int i = 0; i < servers.length; i++) + { + AdempiereServer server = servers[i]; + try + { + if (server.isAlive()) + { + log.info("Alive: " + server); + noRunning++; + } + else + { + log.warning("Dead: " + server); + noStopped++; + } + } + catch (Exception e) + { + log.log(Level.SEVERE, "(checking) - " + server, e); + noStopped++; + } + } + log.fine("Running=" + noRunning + ", Stopped=" + noStopped); + AdempiereServerGroup.get().dump(); + return noStopped == 0; + } // startAll + + /** + * Start Server if not started yet + * @param serverID server ID + * @return true if started + */ + public boolean start (String serverID) + { + AdempiereServer server = getServer(serverID); + if (server == null) + return false; + if (server.isAlive()) + return true; + + try + { + // replace + int index = m_servers.indexOf(server); + server = AdempiereServer.create (server.getModel()); + if (server == null) + m_servers.remove(index); + else + m_servers.set(index, server); + server.start(); + server.setPriority(Thread.NORM_PRIORITY-2); + Thread.yield(); + } + catch (Exception e) + { + log.log(Level.SEVERE, "Server=" + serverID, e); + return false; + } + log.info(server.toString()); + AdempiereServerGroup.get().dump(); + if (server == null) + return false; + return server.isAlive(); + } // startIt + + /** + * Stop all Servers + * @return true if stopped + */ + public boolean stopAll() + { + log.info (""); + AdempiereServer[] servers = getActive(); + // Interrupt + for (int i = 0; i < servers.length; i++) + { + AdempiereServer server = servers[i]; + try + { + if (server.isAlive() && !server.isInterrupted()) + { + server.setPriority(Thread.MAX_PRIORITY-1); + server.interrupt(); + } + } + catch (Exception e) + { + log.log(Level.SEVERE, "(interrupting) - " + server, e); + } + } // for all servers + Thread.yield(); + + // Wait for death + for (int i = 0; i < servers.length; i++) + { + AdempiereServer server = servers[i]; + try + { + int maxWait = 10; // 10 iterations = 1 sec + while (server.isAlive()) + { + if (maxWait-- == 0) + { + log.severe ("Wait timeout for interruped " + server); + break; + } + Thread.sleep(100); // 1/10 + } + } + catch (Exception e) + { + log.log(Level.SEVERE, "(waiting) - " + server, e); + } + } // for all servers + + // Final Check + int noRunning = 0; + int noStopped = 0; + for (int i = 0; i < servers.length; i++) + { + AdempiereServer server = servers[i]; + try + { + if (server.isAlive()) + { + log.warning ("Alive: " + server); + noRunning++; + } + else + { + log.info ("Stopped: " + server); + noStopped++; + } + } + catch (Exception e) + { + log.log(Level.SEVERE, "(checking) - " + server, e); + noRunning++; + } + } + log.fine("Running=" + noRunning + ", Stopped=" + noStopped); + AdempiereServerGroup.get().dump(); + return noRunning == 0; + } // stopAll + + /** + * Stop Server if not stopped + * @param serverID server ID + * @return true if interrupted + */ + public boolean stop (String serverID) + { + AdempiereServer server = getServer(serverID); + if (server == null) + return false; + if (!server.isAlive()) + return true; + + try + { + server.interrupt(); + Thread.sleep(10); // 1/100 sec + } + catch (Exception e) + { + log.log(Level.SEVERE, "stop", e); + return false; + } + log.info(server.toString()); + AdempiereServerGroup.get().dump(); + return !server.isAlive(); + } // stop + + + /** + * Destroy + */ + public void destroy () + { + log.info (""); + stopAll(); + m_servers.clear(); + } // destroy + + /** + * Get Active Servers + * @return array of active servers + */ + protected AdempiereServer[] getActive() + { + ArrayList list = new ArrayList(); + for (int i = 0; i < m_servers.size(); i++) + { + AdempiereServer server = (AdempiereServer)m_servers.get(i); + if (server != null && server.isAlive() && !server.isInterrupted()) + list.add (server); + } + AdempiereServer[] retValue = new AdempiereServer[list.size ()]; + list.toArray (retValue); + return retValue; + } // getActive + + /** + * Get InActive Servers + * @return array of inactive servers + */ + protected AdempiereServer[] getInActive() + { + ArrayList list = new ArrayList(); + for (int i = 0; i < m_servers.size(); i++) + { + AdempiereServer server = (AdempiereServer)m_servers.get(i); + if (server != null && (!server.isAlive() || !server.isInterrupted())) + list.add (server); + } + AdempiereServer[] retValue = new AdempiereServer[list.size()]; + list.toArray (retValue); + return retValue; + } // getInActive + + /** + * Get all Servers + * @return array of servers + */ + public AdempiereServer[] getAll() + { + AdempiereServer[] retValue = new AdempiereServer[m_servers.size()]; + m_servers.toArray (retValue); + return retValue; + } // getAll + + /** + * Get Server with ID + * @param serverID server id + * @return server or null + */ + public AdempiereServer getServer (String serverID) + { + if (serverID == null) + return null; + for (int i = 0; i < m_servers.size(); i++) + { + AdempiereServer server = (AdempiereServer)m_servers.get(i); + if (serverID.equals(server.getServerID())) + return server; + } + return null; + } // getServer + + /** + * String Representation + * @return info + */ + public String toString () + { + StringBuffer sb = new StringBuffer ("AdempiereServerMgr["); + sb.append("Servers=").append(m_servers.size()) + .append(",ContextSize=").append(m_ctx.size()) + .append(",Started=").append(m_start) + .append ("]"); + return sb.toString (); + } // toString + + /** + * Get Description + * @return description + */ + public String getDescription() + { + return "$Revision: 1.4 $"; + } // getDescription + + /** + * Get Number Servers + * @return no of servers + */ + public String getServerCount() + { + int noRunning = 0; + int noStopped = 0; + for (int i = 0; i < m_servers.size(); i++) + { + AdempiereServer server = (AdempiereServer)m_servers.get(i); + if (server.isAlive()) + noRunning++; + else + noStopped++; + } + String info = String.valueOf(m_servers.size()) + + " - Running=" + noRunning + + " - Stopped=" + noStopped; + return info; + } // getServerCount + + /** + * Get start date + * @return start date + */ + public Timestamp getStartTime() + { + return m_start; + } // getStartTime + +} // AdempiereServerMgr diff --git a/serverRoot/src/main/server/org/compiere/server/AlertProcessor.java b/serverRoot/src/main/server/org/compiere/server/AlertProcessor.java new file mode 100644 index 0000000000..1607a67d0a --- /dev/null +++ b/serverRoot/src/main/server/org/compiere/server/AlertProcessor.java @@ -0,0 +1,323 @@ +/****************************************************************************** + * 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.server; + +import java.sql.*; +import java.util.logging.*; +import org.compiere.*; +import org.compiere.model.*; +import org.compiere.util.*; + + +/** + * Alert Processor + * + * @author Jorg Janke + * @version $Id: AlertProcessor.java,v 1.4 2006/07/30 00:53:33 jjanke Exp $ + */ +public class AlertProcessor extends AdempiereServer +{ + /** + * Alert Processor + * @param model model + */ + public AlertProcessor (MAlertProcessor model) + { + super (model, 180); // 3 monute delay + m_model = model; + m_client = MClient.get(model.getCtx(), model.getAD_Client_ID()); + } // AlertProcessor + + /** The Concrete Model */ + private MAlertProcessor m_model = null; + /** Last Summary */ + private StringBuffer m_summary = new StringBuffer(); + /** Last Error Msg */ + private StringBuffer m_errors = new StringBuffer(); + /** Client onfo */ + private MClient m_client = null; + + /** + * Work + */ + protected void doWork () + { + m_summary = new StringBuffer(); + m_errors = new StringBuffer(); + // + int count = 0; + int countError = 0; + MAlert[] alerts = m_model.getAlerts(false); + for (int i = 0; i < alerts.length; i++) + { + if (!processAlert(alerts[i])) + countError++; + count++; + } + // + String summary = "Total=" + count; + if (countError > 0) + summary += ", Not processed=" + countError; + summary += " - "; + m_summary.insert(0, summary); + // + int no = m_model.deleteLog(); + m_summary.append("Logs deleted=").append(no); + // + MAlertProcessorLog pLog = new MAlertProcessorLog(m_model, m_summary.toString()); + pLog.setReference("#" + String.valueOf(p_runCount) + + " - " + TimeUtil.formatElapsed(new Timestamp(p_startWork))); + pLog.setTextMsg(m_errors.toString()); + pLog.save(); + } // doWork + + /** + * Process Alert + * @param alert alert + * @return true if processed + */ + private boolean processAlert (MAlert alert) + { + if (!alert.isValid()) + return false; + log.info("" + alert); + + StringBuffer message = new StringBuffer(alert.getAlertMessage()) + .append(Env.NL); + // + boolean valid = true; + boolean processed = false; + MAlertRule[] rules = alert.getRules(false); + for (int i = 0; i < rules.length; i++) + { + if (i > 0) + message.append(Env.NL).append("================================").append(Env.NL); + String trxName = null; // assume r/o + + MAlertRule rule = rules[i]; + if (!rule.isValid()) + continue; + log.fine("" + rule); + + // Pre + String sql = rule.getPreProcessing(); + if (sql != null && sql.length() > 0) + { + int no = DB.executeUpdate(sql, false, trxName); + if (no == -1) + { + ValueNamePair error = CLogger.retrieveError(); + rule.setErrorMsg("Pre=" + error.getName()); + m_errors.append("Pre=" + error.getName()); + rule.setIsValid(false); + rule.save(); + valid = false; + break; + } + } // Pre + + // The processing + sql = rule.getSql(); + if (alert.isEnforceRoleSecurity() + || alert.isEnforceClientSecurity()) + { + int AD_Role_ID = alert.getFirstAD_Role_ID(); + if (AD_Role_ID == -1) + AD_Role_ID = alert.getFirstUserAD_Role_ID(); + if (AD_Role_ID != -1) + { + MRole role = MRole.get(getCtx(), AD_Role_ID); + sql = role.addAccessSQL(sql, null, true, false); + } + } + + try + { + String text = listSqlSelect(sql, trxName); + if (text != null && text.length() > 0) + { + message.append(text); + processed = true; + } + } + catch (Exception e) + { + rule.setErrorMsg("Select=" + e.getLocalizedMessage()); + m_errors.append("Select=" + e.getLocalizedMessage()); + rule.setIsValid(false); + rule.save(); + valid = false; + break; + } + + // Post + sql = rule.getPostProcessing(); + if (sql != null && sql.length() > 0) + { + int no = DB.executeUpdate(sql, false, trxName); + if (no == -1) + { + ValueNamePair error = CLogger.retrieveError(); + rule.setErrorMsg("Post=" + error.getName()); + m_errors.append("Post=" + error.getName()); + rule.setIsValid(false); + rule.save(); + valid = false; + break; + } + } // Post + + /** Trx */ + if (trxName != null) + { + Trx trx = Trx.get(trxName, false); + if (trx != null) + { + trx.commit(); + trx.close(); + } + } + } // for all rules + + // Update header if error + if (!valid) + { + alert.setIsValid(false); + alert.save(); + return false; + } + + // Nothing to report + if (!processed) + { + m_summary.append(alert.getName()).append("=No Result - "); + return true; + } + + // Send Message + int countMail = 0; + MAlertRecipient[] recipients = alert.getRecipients(false); + for (int i = 0; i < recipients.length; i++) + { + MAlertRecipient recipient = recipients[i]; + if (recipient.getAD_User_ID() >= 0) // System == 0 + if (m_client.sendEMail(recipient.getAD_User_ID(), + alert.getAlertSubject(), message.toString(), null)) + countMail++; + if (recipient.getAD_Role_ID() >= 0) // SystemAdministrator == 0 + { + MUserRoles[] urs = MUserRoles.getOfRole(getCtx(), recipient.getAD_Role_ID()); + for (int j = 0; j < urs.length; j++) + { + MUserRoles ur = urs[j]; + if (!ur.isActive()) + continue; + if (m_client.sendEMail (ur.getAD_User_ID(), + alert.getAlertSubject(), message.toString(), null)) + countMail++; + } + } + } + + m_summary.append(alert.getName()).append(" (EMails=").append(countMail).append(") - "); + return valid; + } // processAlert + + /** + * List Sql Select + * @param sql sql select + * @param trxName transaction + * @return list of rows & values + * @throws Exception + */ + private String listSqlSelect (String sql, String trxName) throws Exception + { + StringBuffer result = new StringBuffer(); + PreparedStatement pstmt = null; + Exception error = null; + try + { + pstmt = DB.prepareStatement (sql, trxName); + ResultSet rs = pstmt.executeQuery (); + ResultSetMetaData meta = rs.getMetaData(); + while (rs.next ()) + { + result.append("------------------").append(Env.NL); + for (int col = 1; col <= meta.getColumnCount(); col++) + { + result.append(meta.getColumnLabel(col)).append(" = "); + result.append(rs.getString(col)); + result.append(Env.NL); + } // for all columns + } + if (result.length() == 0) + log.fine("No rows selected"); + rs.close (); + pstmt.close (); + pstmt = null; + } + catch (Exception e) + { + log.log(Level.SEVERE, sql, e); + error = e; + } + try + { + if (pstmt != null) + pstmt.close (); + pstmt = null; + } + catch (Exception e) + { + pstmt = null; + } + + // Error occured + if (error != null) + throw new Exception ("(" + sql + ") " + Env.NL + + error.getLocalizedMessage()); + + return result.toString(); + } // listSqlSelect + + + + /** + * Get Server Info + * @return info + */ + public String getServerInfo() + { + return "#" + p_runCount + " - Last=" + m_summary.toString(); + } // getServerInfo + + + /*************************************************************************** + * Test + * @param args ignored + */ + public static void main (String[] args) + { + Adempiere.startup(true); + MAlertProcessor model = new MAlertProcessor (Env.getCtx(), 100, null); + AlertProcessor ap = new AlertProcessor(model); + ap.start(); + + + } // main + +} // AlertProcessor diff --git a/serverRoot/src/main/server/org/compiere/server/EMailProcessor.java b/serverRoot/src/main/server/org/compiere/server/EMailProcessor.java new file mode 100644 index 0000000000..07fe99de59 --- /dev/null +++ b/serverRoot/src/main/server/org/compiere/server/EMailProcessor.java @@ -0,0 +1,700 @@ +/****************************************************************************** + * 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.server; + +import java.io.*; +import java.util.*; +import java.util.logging.*; +import javax.mail.*; +import org.compiere.*; +import org.compiere.model.*; +import org.compiere.util.*; + +/** + * Request Mail Processor + * + * @author Jorg Janke + * @version $Id: EMailProcessor.java,v 1.3 2006/07/30 00:53:33 jjanke Exp $ + */ +public class EMailProcessor +{ + /** + * EMail Processor + * @param client client + */ + public EMailProcessor (MClient client) + { + this (client.getSMTPHost(), client.getRequestUser(), client.getRequestUserPW()); + } // EMailProcessor + + /** + * EMail Processor + * @param host host + * @param user user id + * @param password password + */ + public EMailProcessor (String host, String user, String password) + { + m_host = host; + m_user = user; + m_pass = password; + } // EMail + + /** EMail Host Parameter */ + private String m_host = null; + /** EMail User Parameter */ + private String m_user = null; + /** Password Parameter */ + private String m_pass = null; + + /** Session */ + private Session m_session = null; + /** Store */ + private Store m_store = null; + + + /** Logger */ + protected CLogger log = CLogger.getCLogger(getClass()); + + /** Process Error */ + private static final int ERROR = 0; + /** Process Request */ + private static final int REQUEST = 1; + /** Process Workflow */ + private static final int WORKFLOW = 2; + + /** Process Delivery Confirm */ + private static final int DELIVERY = 9; + + /** + * Process Messages in InBox + * @return number of mails processed + */ + public int processMessages() + { + int processed = 0; + try + { + getSession(); + getStore(); + processed = processInBox(); + } + catch (Exception e) + { + log.log(Level.SEVERE, "processInBox", e); + } + // Cleanup + try + { + if (m_store.isConnected()) + m_store.close(); + } + catch (Exception e) + { + } + m_store = null; + return processed; + } // processMessages + + + /************************************************************************** + * Get Session + * @return Session + * @throws Exception + */ + private Session getSession() throws Exception + { + if (m_session != null) + return m_session; + + // Session + Properties props = System.getProperties(); + props.put("mail.store.protocol", "smtp"); + props.put("mail.transport.protocol", "smtp"); + props.put("mail.host", m_host); + props.put("mail.smtp.auth","true"); + EMailAuthenticator auth = new EMailAuthenticator (m_user, m_pass); + // + m_session = Session.getDefaultInstance(props, auth); + m_session.setDebug(CLogMgt.isLevelFinest()); + log.fine("getSession - " + m_session); + return m_session; + } // getSession + + + /** + * Get Store + * @return Store + * @throws Exception + */ + private Store getStore() throws Exception + { + if (m_store != null) + return m_store; + if (getSession() == null) + throw new IllegalStateException("No Session"); + + // Get IMAP Store + m_store = m_session.getStore("imap"); + // Connect + m_store.connect(); + // + log.fine("getStore - " + m_store); + return m_store; + } // getStore + + + /** + * Process InBox + * @return number of processed + * @throws Exception + */ + private int processInBox() throws Exception + { + // Folder + Folder folder; + folder = m_store.getDefaultFolder(); + if (folder == null) + throw new IllegalStateException("No default folder"); + // Open Inbox + Folder inbox = folder.getFolder("INBOX"); + if (!inbox.exists()) + throw new IllegalStateException("No Inbox"); + inbox.open(Folder.READ_WRITE); + log.fine("processInBox - " + inbox.getName() + + "; Messages Total=" + inbox.getMessageCount() + + "; New=" + inbox.getNewMessageCount()); + + // Open Request + Folder requestFolder = folder.getFolder("CRequest"); + if (!requestFolder.exists() && !requestFolder.create(Folder.HOLDS_MESSAGES)) + throw new IllegalStateException("Cannot create Request Folder"); + requestFolder.open(Folder.READ_WRITE); + + // Open Workflow + Folder workflowFolder = folder.getFolder("CWorkflow"); + if (!workflowFolder.exists() && !workflowFolder.create(Folder.HOLDS_MESSAGES)) + throw new IllegalStateException("Cannot create Workflow Folder"); + workflowFolder.open(Folder.READ_WRITE); + + // Open Error + Folder errorFolder = folder.getFolder("AdempiereError"); + if (!errorFolder.exists() && !errorFolder.create(Folder.HOLDS_MESSAGES)) + throw new IllegalStateException("Cannot create Error Folder"); + errorFolder.open(Folder.READ_WRITE); + + // Messages + Message[] messages = inbox.getMessages(); + /** + FetchProfile fp = new FetchProfile(); + fp.add(FetchProfile.Item.ENVELOPE); + fp.add(FetchProfile.Item.FLAGS); + fp.add("X-Mailer"); + inbox.fetch(messages, fp); + **/ + // + int noProcessed = 0; + int noError = 0; + for (int i = 0; i < messages.length; i++) +// for (int i = messages.length-1; i >= 0; i--) // newest first + { + Message msg = messages[i]; + int result = processMessage (msg); + if (result == REQUEST) + { + msg.setFlag(Flags.Flag.SEEN, true); + msg.setFlag(Flags.Flag.ANSWERED, true); + // Copy to processed + requestFolder.appendMessages(new Message[]{msg}); + } + else if (result == WORKFLOW) + { + msg.setFlag(Flags.Flag.SEEN, true); + msg.setFlag(Flags.Flag.ANSWERED, true); + // Copy to processed + workflowFolder.appendMessages(new Message[]{msg}); + } + else if (result == DELIVERY) + { + msg.setFlag(Flags.Flag.SEEN, true); + msg.setFlag(Flags.Flag.ANSWERED, true); + } + else // error + { + errorFolder.appendMessages(new Message[]{msg}); + noError++; + } + // Delete in InBox +// msg.setFlag(Flags.Flag.DELETED, true); +// Message[] deleted = inbox.expunge(); + // + noProcessed++; + } + + log.info("processInBox - Total=" + noProcessed + " - Errors=" + noError); + // Fini + errorFolder.close(false); + requestFolder.close(false); + workflowFolder.close(false); + // + inbox.close(true); + return noProcessed; + } // processInBox + + + /** + * Process Message + * @param msg message + * @return Type of Message + * @throws Exception + */ + private int processMessage (Message msg) throws Exception + { + dumpEnvelope (msg); + dumpBody (msg); + printOut (":::::::::::::::"); + printOut (getSubject(msg)); + printOut (":::::::::::::::"); + printOut (getMessage(msg)); + printOut (":::::::::::::::"); + String delivery = getDeliveryReport(msg); + printOut (delivery); + printOut (":::::::::::::::"); + + // if (delivery != null) + // return DELIVERY; + + // Unknown + return ERROR; + } // processMessage + + /** + * Get Subject + * @param msg message + * @return subject or "" + */ + private String getSubject (Message msg) + { + try + { + String str = msg.getSubject(); + if (str != null) + return str.trim(); + } + catch (MessagingException e) + { + log.log(Level.SEVERE, "getSubject", e); + } + return ""; + } // getSubject + + /** + * Get Message + * @param msg Message + * @return message or "" + */ + private String getMessage (Part msg) + { + StringBuffer sb = new StringBuffer(); + try + { + // Text + if (msg.isMimeType("text/plain")) + { + sb.append(msg.getContent()); + } + // Other Text (e.g. html/xml) + else if (msg.isMimeType("text/*")) + { + sb.append(msg.getContent()); + } + // Nested + else if (msg.isMimeType("message/rfc822")) + { + sb.append(msg.getContent()); + } + // Multi Part Alternative + else if (msg.isMimeType("multipart/alternative")) + { + String plainText = null; + String otherStuff = null; + // + Multipart mp = (Multipart)msg.getContent(); + int count = mp.getCount(); + for (int i = 0; i < count; i++) + { + Part part = mp.getBodyPart(i); + Object content = part.getContent(); + if (content == null || content.toString().trim().length() == 0) + continue; + if (part.isMimeType("text/plain")) + plainText = content.toString(); + else + otherStuff = content.toString(); + } + if (plainText != null) + sb.append(plainText); + else if (otherStuff != null) + sb.append(otherStuff); + } + // Multi Part + else if (msg.isMimeType("multipart/*")) + { + Multipart mp = (Multipart)msg.getContent(); + int count = mp.getCount(); + for (int i = 0; i < count; i++) + { + String str = getMessage(mp.getBodyPart(i)); + if (str.length() > 0) + { + if (sb.length() > 0) + sb.append("\n-----\n"); + sb.append(str); + } + } + } + else + { + /* + * If we actually want to see the data, and it's not a + * MIME type we know, fetch it and check its Java type. + */ + Object o = msg.getContent(); + if (o instanceof String) + { + sb.append(o); + } + } + } + catch (Exception e) + { + log.log(Level.SEVERE, "getMessage", e); + } + return sb.toString().trim(); + } // getMessage + + /** + * Get Delivery Report + * @param msg message + * @return delivery info or null + */ + private String getDeliveryReport (Part msg) + { + try + { + if (msg.isMimeType("multipart/report")) + { + String deliveryMessage = null; + String otherStuff = null; + // + Multipart mp = (Multipart)msg.getContent(); + int count = mp.getCount(); + for (int i = 0; i < count; i++) + { + Part part = mp.getBodyPart(i); + Object content = part.getContent(); + if (content == null) + continue; + if (part.isMimeType("message/*")) + deliveryMessage = getDeliveredReportDetail (part); + else + otherStuff = content.toString().trim(); + } + if (deliveryMessage != null) + return deliveryMessage; + return otherStuff; + } + else if (msg.isMimeType("message/*")) + { + return getDeliveredReportDetail (msg); + } + } + catch (Exception e) + { + log.log(Level.SEVERE, "getDeliveryReport", e); + } + // Nothing + return null; + } // getDeliveryReport + + /** + * Get Delivered Report Detail + * @param part Mime Type message/* + * @return info or null + * @throws Exception + */ + private String getDeliveredReportDetail (Part part) throws Exception + { + Object content = part.getContent(); + if (content == null) + return null; + + String deliveryMessage = null; + if (content instanceof InputStream) + { + StringBuffer sb = new StringBuffer(); + InputStream is = (InputStream)content; + int c; + while ((c = is.read()) != -1) + sb.append((char)c); + deliveryMessage = sb.toString().trim(); + } + else + deliveryMessage = content.toString().trim(); + // + if (deliveryMessage == null) + return null; + + // Final-Recipient: RFC822; jjanke@adempiere.org + int index = deliveryMessage.indexOf("Final-Recipient:"); + if (index != -1) + { + String finalRecipient = deliveryMessage.substring(index); + int atIndex = finalRecipient.indexOf("@"); + if (atIndex != -1) + { + index = finalRecipient.lastIndexOf(' ', atIndex); + if (index != -1) + finalRecipient = finalRecipient.substring(index+1); + atIndex = finalRecipient.indexOf("@"); + if (atIndex != -1) + index = finalRecipient.indexOf(' ', atIndex); + if (index != -1) + finalRecipient = finalRecipient.substring(0, index); + index = finalRecipient.indexOf('\n'); + if (index != -1) + finalRecipient = finalRecipient.substring(0, index); + return finalRecipient.trim(); + } + } + return deliveryMessage; + } // getDeliveredReportDetail + + + /************************************************************************** + * Print Envelope + * @param m message + * @throws Exception + */ + private void dumpEnvelope(Message m) throws Exception + { + printOut("-----------------------------------------------------------------"); + Address[] a; + // FROM + if ((a = m.getFrom()) != null) + { + for (int j = 0; j < a.length; j++) + printOut("FROM: " + a[j].toString()); + } + + // TO + if ((a = m.getRecipients(Message.RecipientType.TO)) != null) + { + for (int j = 0; j < a.length; j++) + printOut("TO: " + a[j].toString()); + } + + // SUBJECT + printOut("SUBJECT: " + m.getSubject()); + + // DATE + java.util.Date d = m.getSentDate(); + printOut("SendDate: " + (d != null ? d.toString() : "UNKNOWN")); + + // FLAGS + Flags flags = m.getFlags(); + StringBuffer sb = new StringBuffer(); + Flags.Flag[] sf = flags.getSystemFlags(); // get the system flags + + boolean first = true; + for (int i = 0; i < sf.length; i++) + { + String s; + Flags.Flag f = sf[i]; + if (f == Flags.Flag.ANSWERED) + s = "\\Answered"; + else if (f == Flags.Flag.DELETED) + s = "\\Deleted"; + else if (f == Flags.Flag.DRAFT) + s = "\\Draft"; + else if (f == Flags.Flag.FLAGGED) + s = "\\Flagged"; + else if (f == Flags.Flag.RECENT) + s = "\\Recent"; + else if (f == Flags.Flag.SEEN) + s = "\\Seen"; + else + continue; // skip it + if (first) + first = false; + else + sb.append(' '); + sb.append(s); + } + + String[] uf = flags.getUserFlags(); // get the user flag strings + for (int i = 0; i < uf.length; i++) + { + if (first) + first = false; + else + sb.append(' '); + sb.append(uf[i]); + } + printOut("FLAGS: " + sb.toString()); + + // X-MAILER + String[] hdrs = m.getHeader("X-Mailer"); + if (hdrs != null) + { + StringBuffer sb1 = new StringBuffer("X-Mailer: "); + for (int i = 0; i < hdrs.length; i++) + sb1.append(hdrs[i]).append(" "); + printOut(sb1.toString()); + } + else + printOut("X-Mailer NOT available"); + + // Message ID + hdrs = m.getHeader("Message-ID"); + if (hdrs != null) + { + StringBuffer sb1 = new StringBuffer("Message-ID: "); + for (int i = 0; i < hdrs.length; i++) + sb1.append(hdrs[i]).append(" "); + printOut(sb1.toString()); + } + else + printOut("Message-ID NOT available"); + + // All + printOut("ALL HEADERs:"); + Enumeration en = m.getAllHeaders(); + while (en.hasMoreElements()) + { + Header hdr = (Header)en.nextElement(); + printOut (" " + hdr.getName() + " = " + hdr.getValue()); + } + + + printOut("-----------------------------------------------------------------"); + } // printEnvelope + + /** + * Print Body + * @param p + * @throws Exception + */ + private void dumpBody (Part p) throws Exception + { + // http://www.iana.org/assignments/media-types/ + printOut("================================================================="); + printOut("CONTENT-TYPE: " + p.getContentType()); + /** + Enumeration en = p.getAllHeaders(); + while (en.hasMoreElements()) + { + Header hdr = (Header)en.nextElement(); + printOut (" " + hdr.getName() + " = " + hdr.getValue()); + } + printOut("= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = ="); + /** **/ + + /** + * Using isMimeType to determine the content type avoids + * fetching the actual content data until we need it. + */ + if (p.isMimeType("text/plain")) + { + printOut("Plain text ---------------------------"); + System.out.println((String)p.getContent()); + } + else if (p.getContentType().toUpperCase().startsWith("TEXT")) + { + printOut("Other text ---------------------------"); + System.out.println((String)p.getContent()); + } + else if (p.isMimeType("multipart/*")) + { + printOut("Multipart ---------------------------"); + Multipart mp = (Multipart)p.getContent(); + int count = mp.getCount(); + for (int i = 0; i < count; i++) + dumpBody(mp.getBodyPart(i)); + } + else if (p.isMimeType("message/rfc822")) + { + printOut("Nested ---------------------------"); + dumpBody((Part)p.getContent()); + } + else + { + /* + * If we actually want to see the data, and it's not a + * MIME type we know, fetch it and check its Java type. + */ + Object o = p.getContent(); + if (o instanceof String) + { + printOut("This is a string ---------------------------"); + System.out.println((String)o); + } + else if (o instanceof InputStream) + { + printOut("This is just an input stream ---------------------------"); + InputStream is = (InputStream)o; + int c; + while ((c = is.read()) != -1) + System.out.write(c); + } + else + { + printOut("This is an unknown type ---------------------------"); + printOut(o.toString()); + } + } + printOut("================================================================="); + } // printBody + + /** + * Print + * @param s string + */ + private static void printOut(String s) + { + // System.out.print(indentStr.substring(0, level * 2)); + System.out.println(s); + } + + + /************************************************************************** + * Main Test + * @param args ignored + */ + public static void main (String[] args) + { + Adempiere.startupEnvironment(true); + EMailProcessor m = new EMailProcessor("admin", "test", "testadempiere"); + m.processMessages(); + + // System.out.println(EMailServer.send("main", "jjanke@adempiere.org", "jjanke@yahoo.com", "test1", "test1 message")); + // System.out.println(EMailServer.send("main", "administrator@adempiere.org", "jjanke@yahoo.com", "test2", "test2 message")); + // System.out.println(EMailServer.send("main", "jjanke@adempiere.org", "jjanke@yahoo.com", "test3", "test3 message")); + + } // main + +} // EMailProcessor diff --git a/serverRoot/src/main/server/org/compiere/server/RequestProcessor.java b/serverRoot/src/main/server/org/compiere/server/RequestProcessor.java new file mode 100644 index 0000000000..15135e5852 --- /dev/null +++ b/serverRoot/src/main/server/org/compiere/server/RequestProcessor.java @@ -0,0 +1,655 @@ +/****************************************************************************** + * 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.server; + +import java.sql.*; +import java.util.*; +import java.util.logging.*; + +import org.compiere.model.*; +import org.compiere.util.*; + +/** + * Request Processor + * + * @author Jorg Janke + * @version $Id: RequestProcessor.java,v 1.3 2006/07/30 00:53:33 jjanke Exp $ + */ +public class RequestProcessor extends AdempiereServer +{ + /** + * RequestProcessor + * @param model model + */ + public RequestProcessor (MRequestProcessor model) + { + super (model, 60); // 1 minute delay + m_model = model; + m_client = MClient.get(model.getCtx(), model.getAD_Client_ID()); + } // RequestProcessor + + /** The Concrete Model */ + private MRequestProcessor m_model = null; + /** Last Summary */ + private StringBuffer m_summary = new StringBuffer(); + /** Client onfo */ + private MClient m_client = null; + + /************************************************************************** + * Do the actual Work + */ + protected void doWork() + { + m_summary = new StringBuffer(); + // + processEMail(); + findSalesRep (); + processRequests (); + processStatus(); + processECR(); + // + int no = m_model.deleteLog(); + m_summary.append("Logs deleted=").append(no); + // + MRequestProcessorLog pLog = new MRequestProcessorLog(m_model, m_summary.toString()); + pLog.setReference("#" + String.valueOf(p_runCount) + + " - " + TimeUtil.formatElapsed(new Timestamp(p_startWork))); + pLog.save(); + } // doWork + + + /************************************************************************** + * Process requests. + * Scheduled - are they due? + */ + private void processRequests () + { + /** + * Due Requests + */ + String sql = "SELECT * FROM R_Request " + + "WHERE DueType='" + MRequest.DUETYPE_Scheduled + "' AND Processed='N'" + + " AND DateNextAction > SysDate" + + " AND AD_Client_ID=?"; + if (m_model.getR_RequestType_ID() != 0) + sql += " AND R_RequestType_ID=?"; + PreparedStatement pstmt = null; + int count = 0; + int countEMails = 0; + try + { + pstmt = DB.prepareStatement (sql, null); + pstmt.setInt (1, m_model.getAD_Client_ID()); + if (m_model.getR_RequestType_ID() != 0) + pstmt.setInt(2, m_model.getR_RequestType_ID()); + ResultSet rs = pstmt.executeQuery (); + while (rs.next ()) + { + MRequest request = new MRequest (getCtx(), rs, null); + request.setDueType(); + if (request.isDue()) + { + if (request.getRequestType().isEMailWhenDue()) + { + if (sendEmail (request, "RequestDue")) + { + request.setDateLastAlert(); + countEMails++; + } + } + request.save(); + count++; + } + } + rs.close (); + pstmt.close (); + pstmt = null; + } + catch (Exception e) + { + log.log(Level.SEVERE, sql, e); + } + m_summary.append("New Due #").append(count); + if (countEMails > 0) + m_summary.append(" (").append(countEMails).append(" EMail)"); + m_summary.append (" - "); + + /** + * Overdue Requests. + * Due Requests - are they overdue? + */ + sql = "SELECT * FROM R_Request r " + + "WHERE r.DueType='" + MRequest.DUETYPE_Due + "' AND r.Processed='N'" + + " AND AD_Client_ID=?" + + " AND EXISTS (SELECT * FROM R_RequestType rt " + + "WHERE r.R_RequestType_ID=rt.R_RequestType_ID" + + " AND (r.DateNextAction+rt.DueDateTolerance) > SysDate)"; + if (m_model.getR_RequestType_ID() != 0) + sql += " AND r.R_RequestType_ID=?"; + count = 0; + countEMails = 0; + try + { + pstmt = DB.prepareStatement (sql, null); + pstmt.setInt (1, m_model.getAD_Client_ID()); + if (m_model.getR_RequestType_ID() != 0) + pstmt.setInt(2, m_model.getR_RequestType_ID()); + ResultSet rs = pstmt.executeQuery (); + while (rs.next ()) + { + MRequest request = new MRequest (getCtx(), rs, null); + request.setDueType(); + if (request.isOverdue()) + { + if (request.getRequestType().isEMailWhenOverdue() + && !TimeUtil.isSameDay(request.getDateLastAlert(), null)) + { + if (sendEmail (request, "RequestDue")) + { + request.setDateLastAlert(); + countEMails++; + } + } + request.save(); + count++; + } + } + rs.close (); + pstmt.close (); + pstmt = null; + } + catch (Exception e) + { + log.log(Level.SEVERE, sql, e); + } + m_summary.append("New Overdue #").append(count); + if (countEMails > 0) + m_summary.append(" (").append(countEMails).append(" EMail)"); + m_summary.append (" - "); + + /** + * Send (over)due alerts + */ + if (m_model.getOverdueAlertDays() > 0) + { + sql = "SELECT * FROM R_Request " + + "WHERE Processed='N'" + + " AND AD_Client_ID=?" + + " AND (DateNextAction+" + m_model.getOverdueAlertDays() + ") > SysDate" + + " AND (DateLastAlert IS NULL"; + if (m_model.getRemindDays() > 0) + sql += " OR (DateLastAlert+" + m_model.getRemindDays() + + ") > SysDate"; + sql += ")"; + if (m_model.getR_RequestType_ID() != 0) + sql += " AND R_RequestType_ID=?"; + count = 0; + countEMails = 0; + try + { + pstmt = DB.prepareStatement(sql, null); + pstmt.setInt(1, m_model.getAD_Client_ID()); + if (m_model.getR_RequestType_ID() != 0) + pstmt.setInt(2, m_model.getR_RequestType_ID()); + ResultSet rs = pstmt.executeQuery(); + while (rs.next()) + { + MRequest request = new MRequest (getCtx(), rs, null); + request.setDueType(); + if (request.getRequestType().isEMailWhenOverdue() + && (request.getDateLastAlert() == null + || !TimeUtil.isSameDay(request.getDateLastAlert(), null))) + { + if (sendEmail (request, "RequestAlert")) + { + request.setDateLastAlert(); + countEMails++; + } + } + request.save(); + count++; + } + rs.close(); + pstmt.close(); + } + catch (SQLException e) + { + log.log(Level.SEVERE, sql, e); + } + m_summary.append("Alerts #").append(count); + if (countEMails > 0) + m_summary.append(" (").append(countEMails).append(" EMail)"); + m_summary.append (" - "); + } // Overdue + + /** + * Escalate + */ + if (m_model.getOverdueAssignDays() > 0) + { + sql = "SELECT * FROM R_Request " + + "WHERE Processed='N'" + + " AND AD_Client_ID=?" + + " AND IsEscalated='N'" + + " AND (DateNextAction+" + m_model.getOverdueAssignDays() + + ") > SysDate"; + if (m_model.getR_RequestType_ID() != 0) + sql += " AND R_RequestType_ID=?"; + count = 0; + countEMails = 0; + try + { + pstmt = DB.prepareStatement(sql, null); + pstmt.setInt(1, m_model.getAD_Client_ID()); + if (m_model.getR_RequestType_ID() != 0) + pstmt.setInt(2, m_model.getR_RequestType_ID()); + ResultSet rs = pstmt.executeQuery(); + while (rs.next()) + { + MRequest request = new MRequest (getCtx(), rs, null); + if (escalate(request)) + count++; + } + rs.close(); + pstmt.close(); + } + catch (SQLException e) + { + log.log(Level.SEVERE, sql, e); + } + m_summary.append("Escalated #").append(count).append(" - "); + } // Esacalate + + /** + * Send inactivity alerts + */ + if (m_model.getInactivityAlertDays() > 0) + { + sql = "SELECT * FROM R_Request " + + "WHERE Processed='N'" + + " AND AD_Client_ID=?" + + " AND (Updated+" + m_model.getInactivityAlertDays() + ") > SysDate" + + " AND (DateLastAlert IS NULL"; + if (m_model.getRemindDays() > 0) + sql += " OR (DateLastAlert+" + m_model.getRemindDays() + + ") < SysDate"; + sql += ")"; + if (m_model.getR_RequestType_ID() != 0) + sql += " AND R_RequestType_ID=?"; + count = 0; + countEMails = 0; + try + { + pstmt = DB.prepareStatement(sql, null); + pstmt.setInt(1, m_model.getAD_Client_ID()); + if (m_model.getR_RequestType_ID() != 0) + pstmt.setInt(2, m_model.getR_RequestType_ID()); + ResultSet rs = pstmt.executeQuery(); + while (rs.next()) + { + MRequest request = new MRequest (getCtx(), rs, null); + request.setDueType(); + if (request.getDateLastAlert() == null + || !TimeUtil.isSameDay(request.getDateLastAlert(), null)) + { + if (sendEmail (request, "RequestInactive")) + { + request.setDateLastAlert(); + countEMails++; + } + request.save(); + count++; + } + } + rs.close(); + pstmt.close(); + } + catch (SQLException e) + { + log.log(Level.SEVERE, sql, e); + } + m_summary.append("Inactivity #").append(count); + if (countEMails > 0) + m_summary.append(" (").append(countEMails).append(" EMail)"); + m_summary.append (" - "); + } // Inactivity + + // + try + { + if (pstmt != null) + pstmt.close (); + pstmt = null; + } + catch (Exception e) + { + pstmt = null; + } + } // processRequests + + /** + * Send Alert EMail + * @param request request + * @param AD_Message message + * @return true if sent + */ + private boolean sendEmail (MRequest request, String AD_Message) + { + // Alert: Request {0} overdue + String subject = Msg.getMsg(m_client.getAD_Language(), AD_Message, + new String[] {request.getDocumentNo()}); + return m_client.sendEMail(request.getSalesRep_ID(), + subject, request.getSummary(), request.createPDF()); + } // sendAlert + + /** + * Escalate + * @param request request + * @return true if saved + */ + private boolean escalate (MRequest request) + { + // Get Supervisor + MUser supervisor = request.getSalesRep(); // self + int supervisor_ID = request.getSalesRep().getSupervisor_ID(); + if (supervisor_ID == 0 && m_model.getSupervisor_ID() != 0) + supervisor_ID = m_model.getSupervisor_ID(); + if (supervisor_ID != 0 && supervisor_ID != request.getAD_User_ID()) + supervisor = MUser.get(getCtx(), supervisor_ID); + + // Escalated: Request {0} to {1} + String subject = Msg.getMsg(m_client.getAD_Language(), "RequestEscalate", + new String[] {request.getDocumentNo(), supervisor.getName()}); + String to = request.getSalesRep().getEMail(); + if (to == null || to.length() == 0) + log.warning("SalesRep has no EMail - " + request.getSalesRep()); + else + m_client.sendEMail(request.getSalesRep_ID(), + subject, request.getSummary(), request.createPDF()); + + // Not the same - send mail to supervisor + if (request.getSalesRep_ID() != supervisor.getAD_User_ID()) + { + to = supervisor.getEMail(); + if (to == null || to.length() == 0) + log.warning("Supervisor has no EMail - " + supervisor); + else + m_client.sendEMail(supervisor.getAD_User_ID(), + subject, request.getSummary(), request.createPDF()); + } + + // ---------------- + request.setDueType(); + request.setIsEscalated(true); + request.setResult(subject); + return request.save(); + } // escalate + + + /************************************************************************** + * Process Request Status + */ + private void processStatus() + { + int count = 0; + // Requests with status with after timeout + String sql = "SELECT * FROM R_Request r WHERE EXISTS (" + + "SELECT * FROM R_Status s " + + "WHERE r.R_Status_ID=s.R_Status_ID" + + " AND s.TimeoutDays > 0 AND s.Next_Status_ID > 0" + + " AND r.Updated+s.TimeoutDays < SysDate" + + ") " + + "ORDER BY R_Status_ID"; + PreparedStatement pstmt = null; + MStatus status = null; + MStatus next = null; + try + { + pstmt = DB.prepareStatement (sql, null); + ResultSet rs = pstmt.executeQuery (); + while (rs.next ()) + { + MRequest r = new MRequest(getCtx(), rs, null); + // Get/Check Status + if (status == null || status.getR_Status_ID() != r.getR_Status_ID()) + status = MStatus.get(getCtx(), r.getR_Status_ID()); + if (status.getTimeoutDays() <= 0 + || status.getNext_Status_ID() == 0) + continue; + // Next Status + if (next == null || next.getR_Status_ID() != status.getNext_Status_ID()) + next = MStatus.get(getCtx(), status.getNext_Status_ID()); + // + String result = Msg.getMsg(getCtx(), "RequestStatusTimeout") + + ": " + status.getName() + " -> " + next.getName(); + r.setResult(result); + r.setR_Status_ID(status.getNext_Status_ID()); + if (r.save()) + count++; + } + rs.close (); + pstmt.close (); + pstmt = null; + } + catch (Exception e) + { + log.log (Level.SEVERE, sql, e); + } + try + { + if (pstmt != null) + pstmt.close (); + pstmt = null; + } + catch (Exception e) + { + pstmt = null; + } + + m_summary.append("Status Timeout #").append(count) + .append(" - "); + } // processStatus + + /** + * Create ECR + */ + private void processECR() + { + // Get Requests with Request Type-AutoChangeRequest and Group with info + String sql = "SELECT * FROM R_Request r " + + "WHERE M_ChangeRequest_ID IS NULL" + + " AND EXISTS (" + + "SELECT * FROM R_RequestType rt " + + "WHERE rt.R_RequestType_ID=r.R_RequestType_ID" + + " AND rt.IsAutoChangeRequest='Y')" + + "AND EXISTS (" + + "SELECT * FROM R_Group g " + + "WHERE g.R_Group_ID=r.R_Group_ID" + + " AND (g.M_BOM_ID IS NOT NULL OR g.M_ChangeNotice_ID IS NOT NULL) )"; + // + int count = 0; + int failure = 0; + // + PreparedStatement pstmt = null; + try + { + pstmt = DB.prepareStatement (sql, null); + ResultSet rs = pstmt.executeQuery (); + while (rs.next ()) + { + MRequest r = new MRequest (getCtx(), rs, null); + MGroup rg = MGroup.get(getCtx(), r.getR_Group_ID()); + MChangeRequest ecr = new MChangeRequest (r, rg); + if (r.save()) + { + r.setM_ChangeRequest_ID(ecr.getM_ChangeRequest_ID()); + if (r.save()) + count++; + else + failure++; + } + else + failure++; + } + rs.close (); + pstmt.close (); + pstmt = null; + } + catch (Exception e) + { + log.log (Level.SEVERE, sql, e); + } + try + { + if (pstmt != null) + pstmt.close (); + pstmt = null; + } + catch (Exception e) + { + pstmt = null; + } + m_summary.append("Auto Change Request #").append(count); + if (failure > 0) + m_summary.append("(fail=").append(failure).append(")"); + m_summary.append(" - "); + } // processECR + + + /************************************************************************** + * Create Reauest / Updates from EMail + */ + private void processEMail () + { + // m_summary.append("Mail #").append(count) + // .append(" - "); + } // processEMail + + + /************************************************************************** + * Allocate Sales Rep + */ + private void findSalesRep () + { + int changed = 0; + int notFound = 0; + Properties ctx = new Properties(); + // + String sql = "SELECT * FROM R_Request " + + "WHERE AD_Client_ID=?" + + " AND SalesRep_ID=0 AND Processed='N'"; + if (m_model.getR_RequestType_ID() != 0) + sql += " AND R_RequestType_ID=?"; + PreparedStatement pstmt = null; + try + { + pstmt = DB.prepareStatement(sql, null); + pstmt.setInt(1, m_model.getAD_Client_ID()); + if (m_model.getR_RequestType_ID() != 0) + pstmt.setInt(2, m_model.getR_RequestType_ID()); + ResultSet rs = pstmt.executeQuery(); + while (rs.next()) + { + MRequest request = new MRequest (ctx, rs, null); + if (request.getSalesRep_ID() != 0) + continue; + int SalesRep_ID = findSalesRep(request); + if (SalesRep_ID != 0) + { + request.setSalesRep_ID(SalesRep_ID); + request.save(); + changed++; + } + else + notFound++; + } + rs.close(); + pstmt.close(); + pstmt = null; + } + catch (SQLException ex) + { + log.log(Level.SEVERE, sql, ex); + } + try + { + if (pstmt != null) + pstmt.close(); + } + catch (SQLException ex1) + { + } + pstmt = null; + // + if (changed == 0 && notFound == 0) + m_summary.append("No unallocated Requests"); + else + m_summary.append("Allocated SalesRep=").append(changed); + if (notFound > 0) + m_summary.append(",Not=").append(notFound); + m_summary.append(" - "); + } // findSalesRep + + /** + * Find SalesRep/User based on Request Type and Question. + * @param request request + * @return SalesRep_ID user + */ + private int findSalesRep (MRequest request) + { + String QText = request.getSummary(); + if (QText == null) + QText = ""; + else + QText = QText.toUpperCase(); + // + MRequestProcessorRoute[] routes = m_model.getRoutes(false); + for (int i = 0; i < routes.length; i++) + { + MRequestProcessorRoute route = routes[i]; + + // Match first on Request Type + if (request.getR_RequestType_ID() == route.getR_RequestType_ID() + && route.getR_RequestType_ID() != 0) + return route.getAD_User_ID(); + + // Match on element of keyword + String keyword = route.getKeyword(); + if (keyword != null) + { + StringTokenizer st = new StringTokenizer(keyword.toUpperCase(), " ,;\t\n\r\f"); + while (st.hasMoreElements()) + { + if (QText.indexOf(st.nextToken()) != -1) + return route.getAD_User_ID(); + } + } + } // for all routes + + return m_model.getSupervisor_ID(); + } // findSalesRep + + /** + * Get Server Info + * @return info + */ + public String getServerInfo() + { + return "#" + p_runCount + " - Last=" + m_summary.toString(); + } // getServerInfo + +} // RequestProcessor diff --git a/serverRoot/src/main/server/org/compiere/server/Scheduler.java b/serverRoot/src/main/server/org/compiere/server/Scheduler.java new file mode 100644 index 0000000000..ac38d4154c --- /dev/null +++ b/serverRoot/src/main/server/org/compiere/server/Scheduler.java @@ -0,0 +1,290 @@ +/****************************************************************************** + * 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.server; + +import java.io.*; +import java.math.*; +import java.sql.*; +import java.util.logging.*; +import org.compiere.model.*; +import org.compiere.print.*; +import org.compiere.process.*; +import org.compiere.util.*; + + +/** + * Scheduler + * + * @author Jorg Janke + * @version $Id: Scheduler.java,v 1.5 2006/07/30 00:53:33 jjanke Exp $ + */ +public class Scheduler extends AdempiereServer +{ + /** + * Scheduler + * @param model model + */ + public Scheduler (MScheduler model) + { + super (model, 240); // nap + m_model = model; + // m_client = MClient.get(model.getCtx(), model.getAD_Client_ID()); + } // Scheduler + + /** The Concrete Model */ + private MScheduler m_model = null; + /** Last Summary */ + private StringBuffer m_summary = new StringBuffer(); + /** Transaction */ + private Trx m_trx = null; + + /** + * Work + */ + protected void doWork () + { + m_summary = new StringBuffer(m_model.toString()) + .append(" - "); + // + MProcess process = m_model.getProcess(); + try + { + m_trx = Trx.get(Trx.createTrxName("Scheduler"), true); + if (process.isReport()) + m_summary.append(runReport(process)); + else + m_summary.append(runProcess(process)); + m_trx.commit(); + } + catch (Exception e) + { + if (m_trx != null) + m_trx.rollback(); + log.log(Level.WARNING, process.toString(), e); + m_summary.append(e.toString()); + } + if (m_trx != null) + m_trx.close(); + // + int no = m_model.deleteLog(); + m_summary.append("Logs deleted=").append(no); + // + MSchedulerLog pLog = new MSchedulerLog(m_model, m_summary.toString()); + pLog.setReference("#" + String.valueOf(p_runCount) + + " - " + TimeUtil.formatElapsed(new Timestamp(p_startWork))); + pLog.save(); + } // doWork + + /** + * Run Report + * @param process + * @return summary + * @throws Exception + */ + private String runReport(MProcess process) throws Exception + { log.info(process.toString()); + if (!process.isReport() || process.getAD_ReportView_ID() == 0) + return "Not a Report AD_Process_ID=" + process.getAD_Process_ID() + + " - " + process.getName(); + // Process + int AD_Table_ID = 0; + int Record_ID = 0; + // + MPInstance pInstance = new MPInstance(process, Record_ID); + fillParameter(pInstance); + // + ProcessInfo pi = new ProcessInfo (process.getName(), process.getAD_Process_ID(), + AD_Table_ID, Record_ID); + pi.setAD_User_ID(m_model.getUpdatedBy()); + pi.setAD_Client_ID(m_model.getAD_Client_ID()); + pi.setAD_PInstance_ID(pInstance.getAD_PInstance_ID()); + if (!process.processIt(pi, m_trx) && pi.getClassName() != null) + return "Process failed: (" + pi.getClassName() + ") " + pi.getSummary(); + + // Report + ReportEngine re = ReportEngine.get(getCtx(), pi); + if (re == null) + return "Cannot create Report AD_Process_ID=" + process.getAD_Process_ID() + + " - " + process.getName(); + File report = re.getPDF(); + // Notice + int AD_Message_ID = 884; // HARDCODED SchedulerResult + Integer[] userIDs = m_model.getRecipientAD_User_IDs(); + for (int i = 0; i < userIDs.length; i++) + { + MNote note = new MNote(getCtx(), + AD_Message_ID, userIDs[i].intValue(), m_trx.getTrxName()); + note.setClientOrg(m_model.getAD_Client_ID(), m_model.getAD_Org_ID()); + note.setTextMsg(m_model.getName()); + note.setDescription(m_model.getDescription()); + note.setRecord(AD_Table_ID, Record_ID); + note.save(); + // Attachment + MAttachment attachment = new MAttachment (getCtx(), + X_AD_Note.Table_ID, note.getAD_Note_ID(), m_trx.getTrxName()); + attachment.setClientOrg(m_model.getAD_Client_ID(), m_model.getAD_Org_ID()); + attachment.addEntry(report); + attachment.setTextMsg(m_model.getName()); + attachment.save(); + } + // + return pi.getSummary(); + } // runReport + + /** + * Run Process + * @param process process + * @return summary + * @throws Exception + */ + private String runProcess(MProcess process) throws Exception + { + log.info(process.toString()); + // Process (see also MWFActivity.performWork + int AD_Table_ID = 0; + int Record_ID = 0; + // + MPInstance pInstance = new MPInstance(process, Record_ID); + fillParameter(pInstance); + // + ProcessInfo pi = new ProcessInfo (process.getName(), process.getAD_Process_ID(), + AD_Table_ID, Record_ID); + pi.setAD_User_ID(m_model.getUpdatedBy()); + pi.setAD_Client_ID(m_model.getAD_Client_ID()); + pi.setAD_PInstance_ID(pInstance.getAD_PInstance_ID()); + process.processIt(pi, m_trx); + return pi.getSummary(); + } // runProcess + + /** + * Fill Parameter + * @param pInstance process instance + */ + private void fillParameter(MPInstance pInstance) + { + MSchedulerPara[] sParams = m_model.getParameters (false); + MPInstancePara[] iParams = pInstance.getParameters(); + for (int pi = 0; pi < iParams.length; pi++) + { + MPInstancePara iPara = iParams[pi]; + for (int np = 0; np < sParams.length; np++) + { + MSchedulerPara sPara = sParams[np]; + if (iPara.getParameterName().equals(sPara.getColumnName())) + { + String variable = sPara.getParameterDefault(); + log.fine(sPara.getColumnName() + " = " + variable); + // Value - Constant/Variable + Object value = variable; + if (variable == null + || (variable != null && variable.length() == 0)) + value = null; + else if (variable.indexOf("@") != -1) // we have a variable + { + // Strip + int index = variable.indexOf("@"); + String columnName = variable.substring(index+1); + index = columnName.indexOf("@"); + if (index == -1) + { + log.warning(sPara.getColumnName() + + " - cannot evaluate=" + variable); + break; + } + columnName = columnName.substring(0, index); + // try Env + String env = Env.getContext(getCtx(), columnName); + if (env.length() == 0) + { + log.warning(sPara.getColumnName() + + " - not in environment =" + columnName + + "(" + variable + ")"); + break; + } + else + value = env; + } // @variable@ + + // No Value + if (value == null) + { + log.fine(sPara.getColumnName() + " - empty"); + break; + } + + // Convert to Type + try + { + if (DisplayType.isNumeric(sPara.getDisplayType()) + || DisplayType.isID(sPara.getDisplayType())) + { + BigDecimal bd = null; + if (value instanceof BigDecimal) + bd = (BigDecimal)value; + else if (value instanceof Integer) + bd = new BigDecimal (((Integer)value).intValue()); + else + bd = new BigDecimal (value.toString()); + iPara.setP_Number(bd); + log.fine(sPara.getColumnName() + + " = " + variable + " (=" + bd + "=)"); + } + else if (DisplayType.isDate(sPara.getDisplayType())) + { + Timestamp ts = null; + if (value instanceof Timestamp) + ts = (Timestamp)value; + else + ts = Timestamp.valueOf(value.toString()); + iPara.setP_Date(ts); + log.fine(sPara.getColumnName() + + " = " + variable + " (=" + ts + "=)"); + } + else + { + iPara.setP_String(value.toString()); + log.fine(sPara.getColumnName() + + " = " + variable + + " (=" + value + "=) " + value.getClass().getName()); + } + if (!iPara.save()) + log.warning("Not Saved - " + sPara.getColumnName()); + } + catch (Exception e) + { + log.warning(sPara.getColumnName() + + " = " + variable + " (" + value + + ") " + value.getClass().getName() + + " - " + e.getLocalizedMessage()); + } + break; + } // parameter match + } // scheduler parameter loop + } // instance parameter loop + } // fillParameter + + + /** + * Get Server Info + * @return info + */ + public String getServerInfo() + { + return "#" + p_runCount + " - Last=" + m_summary.toString(); + } // getServerInfo + +} // Scheduler diff --git a/serverRoot/src/main/server/org/compiere/server/WorkflowProcessor.java b/serverRoot/src/main/server/org/compiere/server/WorkflowProcessor.java new file mode 100644 index 0000000000..120bd0c0be --- /dev/null +++ b/serverRoot/src/main/server/org/compiere/server/WorkflowProcessor.java @@ -0,0 +1,485 @@ +/****************************************************************************** + * 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.server; + +import java.sql.*; +import java.util.*; +import java.io.*; + +import java.util.logging.*; +import org.compiere.util.*; +import org.compiere.wf.*; +import org.compiere.model.*; +import org.compiere.process.*; + + +/** + * Workflow Processor + * + * @author Jorg Janke + * @version $Id: WorkflowProcessor.java,v 1.4 2006/07/30 00:53:33 jjanke Exp $ + */ +public class WorkflowProcessor extends AdempiereServer +{ + /** + * WorkflowProcessor + * @param model model + */ + public WorkflowProcessor (MWorkflowProcessor model) + { + super (model, 120); // 2 minute dalay + m_model = model; + m_client = MClient.get(model.getCtx(), model.getAD_Client_ID()); + } // WorkflowProcessor + + /** The Concrete Model */ + private MWorkflowProcessor m_model = null; + /** Last Summary */ + private StringBuffer m_summary = new StringBuffer(); + /** Client onfo */ + private MClient m_client = null; + + /** + * Work + */ + protected void doWork () + { + m_summary = new StringBuffer(); + // + wakeup(); + dynamicPriority(); + sendAlerts(); + // + int no = m_model.deleteLog(); + m_summary.append("Logs deleted=").append(no); + // + MWorkflowProcessorLog pLog = new MWorkflowProcessorLog(m_model, m_summary.toString()); + pLog.setReference("#" + String.valueOf(p_runCount) + + " - " + TimeUtil.formatElapsed(new Timestamp(p_startWork))); + pLog.save(); + } // doWork + + /** + * Continue Workflow After Sleep + */ + private void wakeup() + { + String sql = "SELECT * " + + "FROM AD_WF_Activity a " + + "WHERE Processed='N' AND WFState='OS'" // suspended + + " AND EndWaitTime > SysDate" + + " AND AD_Client_ID=?" + + " AND EXISTS (SELECT * FROM AD_Workflow wf " + + " INNER JOIN AD_WF_Node wfn ON (wf.AD_Workflow_ID=wfn.AD_Workflow_ID) " + + "WHERE a.AD_WF_Node_ID=wfn.AD_WF_Node_ID" + + " AND wfn.Action='Z'" // sleeping + + " AND wf.AD_WorkflowProcessor_ID IS NULL OR wf.AD_WorkflowProcessor_ID=?)"; + PreparedStatement pstmt = null; + int count = 0; + int countEMails = 0; + try + { + pstmt = DB.prepareStatement (sql, null); + pstmt.setInt (1, m_model.getAD_Client_ID()); + pstmt.setInt (2, m_model.getAD_WorkflowProcessor_ID()); + ResultSet rs = pstmt.executeQuery (); + while (rs.next ()) + { + MWFActivity activity = new MWFActivity (getCtx(), rs, null); + activity.setWFState (StateEngine.STATE_Completed); + // saves and calls MWFProcess.checkActivities(); + count++; + } + rs.close (); + pstmt.close (); + pstmt = null; + } + catch (Exception e) + { + log.log(Level.SEVERE, "wakeup", e); + } + m_summary.append("Wakeup #").append(count).append (" - "); + } // wakeup + + /** + * Set/Increase Priority dynamically + */ + private void dynamicPriority() + { + // suspened activities with dynamic priority node + String sql = "SELECT * " + + "FROM AD_WF_Activity a " + + "WHERE Processed='N' AND WFState='OS'" // suspended + + " AND EXISTS (SELECT * FROM AD_Workflow wf" + + " INNER JOIN AD_WF_Node wfn ON (wf.AD_Workflow_ID=wfn.AD_Workflow_ID) " + + "WHERE a.AD_WF_Node_ID=wfn.AD_WF_Node_ID AND wf.AD_WorkflowProcessor_ID=?" + + " AND wfn.DynPriorityUnit IS NOT NULL AND wfn.DynPriorityChange IS NOT NULL)"; + PreparedStatement pstmt = null; + int count = 0; + int countEMails = 0; + try + { + pstmt = DB.prepareStatement (sql, null); + pstmt.setInt(1, m_model.getAD_WorkflowProcessor_ID()); + ResultSet rs = pstmt.executeQuery (); + while (rs.next ()) + { + MWFActivity activity = new MWFActivity (getCtx(), rs, null); + if (activity.getDynPriorityStart() == 0) + activity.setDynPriorityStart(activity.getPriority()); + long ms = System.currentTimeMillis() - activity.getCreated().getTime(); + MWFNode node = activity.getNode(); + int prioDiff = node.calculateDynamicPriority ((int)(ms / 1000)); + activity.setPriority(activity.getDynPriorityStart() + prioDiff); + activity.save(); + count++; + } + rs.close (); + pstmt.close (); + pstmt = null; + } + catch (Exception e) + { + log.log(Level.SEVERE, sql, e); + } + m_summary.append("DynPriority #").append(count).append (" - "); + + // Clean-up + try + { + if (pstmt != null) + pstmt.close (); + pstmt = null; + } + catch (Exception e) + { + pstmt = null; + } + } // setPriority + + + /** + * Send Alerts + */ + private void sendAlerts() + { + // Alert over Priority + if (m_model.getAlertOverPriority() > 0) + { + String sql = "SELECT * " + + "FROM AD_WF_Activity a " + + "WHERE Processed='N' AND WFState='OS'" // suspended + + " AND Priority >= ?" // ##1 + + " AND (DateLastAlert IS NULL"; + if (m_model.getRemindDays() > 0) + sql += " OR (DateLastAlert+" + m_model.getRemindDays() + + ") < SysDate"; + sql += ") AND EXISTS (SELECT * FROM AD_Workflow wf " + + " INNER JOIN AD_WF_Node wfn ON (wf.AD_Workflow_ID=wfn.AD_Workflow_ID) " + + "WHERE a.AD_WF_Node_ID=wfn.AD_WF_Node_ID" + + " AND wf.AD_WorkflowProcessor_ID IS NULL OR wf.AD_WorkflowProcessor_ID=?)"; + int count = 0; + int countEMails = 0; + try + { + PreparedStatement pstmt = DB.prepareStatement(sql, null); + pstmt.setInt (1, m_model.getAlertOverPriority()); + pstmt.setInt (2, m_model.getAD_WorkflowProcessor_ID()); + ResultSet rs = pstmt.executeQuery(); + while (rs.next()) + { + MWFActivity activity = new MWFActivity (getCtx(), rs, null); + boolean escalate = activity.getDateLastAlert() != null; + countEMails += sendEmail (activity, "ActivityOverPriority", + escalate, true); + activity.setDateLastAlert(new Timestamp(System.currentTimeMillis())); + activity.save(); + count++; + } + rs.close(); + pstmt.close(); + } + catch (SQLException e) + { + log.log(Level.SEVERE, "(Priority) - " + sql, e); + } + m_summary.append("OverPriority #").append(count); + if (countEMails > 0) + m_summary.append(" (").append(countEMails).append(" EMail)"); + m_summary.append (" - "); + } // Alert over Priority + + /** + * Over End Wait + */ + String sql = "SELECT * " + + "FROM AD_WF_Activity a " + + "WHERE Processed='N' AND WFState='OS'" // suspended + + " AND EndWaitTime > SysDate" + + " AND (DateLastAlert IS NULL"; + if (m_model.getRemindDays() > 0) + sql += " OR (DateLastAlert+" + m_model.getRemindDays() + + ") < SysDate"; + sql += ") AND EXISTS (SELECT * FROM AD_Workflow wf " + + " INNER JOIN AD_WF_Node wfn ON (wf.AD_Workflow_ID=wfn.AD_Workflow_ID) " + + "WHERE a.AD_WF_Node_ID=wfn.AD_WF_Node_ID" + + " AND wfn.Action<>'Z'" // not sleeping + + " AND wf.AD_WorkflowProcessor_ID IS NULL OR wf.AD_WorkflowProcessor_ID=?)"; + PreparedStatement pstmt = null; + int count = 0; + int countEMails = 0; + try + { + pstmt = DB.prepareStatement (sql, null); + pstmt.setInt(1, m_model.getAD_WorkflowProcessor_ID()); + ResultSet rs = pstmt.executeQuery (); + while (rs.next ()) + { + MWFActivity activity = new MWFActivity (getCtx(), rs, null); + boolean escalate = activity.getDateLastAlert() != null; + countEMails += sendEmail (activity, "ActivityEndWaitTime", + escalate, false); + activity.setDateLastAlert(new Timestamp(System.currentTimeMillis())); + activity.save(); + count++; + } + rs.close (); + pstmt.close (); + pstmt = null; + } + catch (Exception e) + { + log.log(Level.SEVERE, "(EndWaitTime) - " + sql, e); + } + m_summary.append("EndWaitTime #").append(count); + if (countEMails > 0) + m_summary.append(" (").append(countEMails).append(" EMail)"); + m_summary.append (" - "); + + /** + * Send inactivity alerts + */ + if (m_model.getInactivityAlertDays() > 0) + { + sql = "SELECT * " + + "FROM AD_WF_Activity a " + + "WHERE Processed='N' AND WFState='OS'" // suspended + + " AND (Updated+" + m_model.getInactivityAlertDays() + ") < SysDate" + + " AND (DateLastAlert IS NULL"; + if (m_model.getRemindDays() > 0) + sql += " OR (DateLastAlert+" + m_model.getRemindDays() + + ") < SysDate"; + sql += ") AND EXISTS (SELECT * FROM AD_Workflow wf " + + " INNER JOIN AD_WF_Node wfn ON (wf.AD_Workflow_ID=wfn.AD_Workflow_ID) " + + "WHERE a.AD_WF_Node_ID=wfn.AD_WF_Node_ID" + + " AND wf.AD_WorkflowProcessor_ID IS NULL OR wf.AD_WorkflowProcessor_ID=?)"; + count = 0; + countEMails = 0; + try + { + pstmt = DB.prepareStatement(sql, null); + pstmt.setInt (1, m_model.getAD_WorkflowProcessor_ID()); + ResultSet rs = pstmt.executeQuery(); + while (rs.next()) + { + MWFActivity activity = new MWFActivity (getCtx(), rs, null); + boolean escalate = activity.getDateLastAlert() != null; + countEMails += sendEmail (activity, "ActivityInactivity", + escalate, false); + activity.setDateLastAlert(new Timestamp(System.currentTimeMillis())); + activity.save(); + count++; + } + rs.close(); + pstmt.close(); + } + catch (SQLException e) + { + log.log(Level.SEVERE, "(Inactivity): " + sql, e); + } + m_summary.append("Inactivity #").append(count); + if (countEMails > 0) + m_summary.append(" (").append(countEMails).append(" EMail)"); + m_summary.append (" - "); + } // Inactivity + + + // Clean-up + try + { + if (pstmt != null) + pstmt.close (); + pstmt = null; + } + catch (Exception e) + { + pstmt = null; + } + } // sendAlerts + + /** + * Send Alert EMail + * @param activity activity + * @param AD_Message message + * @param toProcess true if to process owner + * @param toSupervisor true if to Supervisor + * @return number of mails sent + */ + private int sendEmail (MWFActivity activity, String AD_Message, + boolean toProcess, boolean toSupervisor) + { + if (m_client == null || m_client.getAD_Client_ID() != activity.getAD_Client_ID()) + m_client = MClient.get(getCtx(), activity.getAD_Client_ID()); + + MWFProcess process = new MWFProcess (getCtx(), activity.getAD_WF_Process_ID(), null); + + String subjectVar = activity.getNode().getName(); + String message = activity.getTextMsg(); + if (message == null || message.length() == 0) + message = process.getTextMsg(); + File pdf = null; + PO po = activity.getPO(); + if (po instanceof DocAction) + { + message = ((DocAction)po).getDocumentInfo() + "\n" + message; + pdf = ((DocAction)po).createPDF(); + } + + // Inactivity Alert: Workflow Activity {0} + String subject = Msg.getMsg(m_client.getAD_Language(), AD_Message, + new Object[] {subjectVar}); + + // Prevent duplicates + ArrayList list = new ArrayList(); + int counter = 0; + + // To Activity Owner + if (m_client.sendEMail(activity.getAD_User_ID(), subject, message, pdf)) + counter++; + list.add (new Integer(activity.getAD_User_ID())); + + // To Process Owner + if (toProcess + && process.getAD_User_ID() != activity.getAD_User_ID()) + { + if (m_client.sendEMail(process.getAD_User_ID(), subject, message, pdf)) + counter++; + list.add (new Integer(process.getAD_User_ID())); + } + + // To Activity Responsible + MWFResponsible responsible = MWFResponsible.get(getCtx(), activity.getAD_WF_Responsible_ID()); + counter += sendAlertToResponsible (responsible, list, process, + subject, message, pdf); + + // To Process Responsible + if (toProcess + && process.getAD_WF_Responsible_ID() != activity.getAD_WF_Responsible_ID()) + { + responsible = MWFResponsible.get(getCtx(), process.getAD_WF_Responsible_ID()); + counter += sendAlertToResponsible (responsible, list, process, + subject, message, pdf); + } + + // Processor SuperVisor + if (toSupervisor + && m_model.getSupervisor_ID() != 0 + && !list.contains(new Integer(m_model.getSupervisor_ID()))) + { + if (m_client.sendEMail(m_model.getSupervisor_ID(), subject, message, pdf)) + counter++; + list.add (new Integer(m_model.getSupervisor_ID())); + } + + return counter; + } // sendAlert + + /** + * Send Alert To Responsible + * @param responsible responsible + * @param list list of already sent users + * @param process process + * @param subject subject + * @param message message + * @param pdf optional pdf + * @return number of mail sent + */ + private int sendAlertToResponsible (MWFResponsible responsible, + ArrayList list, MWFProcess process, + String subject, String message, File pdf) + { + int counter = 0; + if (responsible.isInvoker()) + ; + // Human + else if (MWFResponsible.RESPONSIBLETYPE_Human.equals(responsible.getResponsibleType()) + && responsible.getAD_User_ID() != 0 + && !list.contains(new Integer(responsible.getAD_User_ID()))) + { + if (m_client.sendEMail(responsible.getAD_User_ID(), subject, message, pdf)) + counter++; + list.add (new Integer(responsible.getAD_User_ID())); + } + // Org of the Document + else if (MWFResponsible.RESPONSIBLETYPE_Organization.equals(responsible.getResponsibleType())) + { + PO document = process.getPO(); + if (document != null) + { + MOrgInfo org = MOrgInfo.get (getCtx(), document.getAD_Org_ID()); + if (org.getSupervisor_ID() != 0 + && !list.contains(new Integer(org.getSupervisor_ID()))) + { + if (m_client.sendEMail(org.getSupervisor_ID(), subject, message, pdf)) + counter++; + list.add (new Integer(org.getSupervisor_ID())); + } + } + } + // Role + else if (MWFResponsible.RESPONSIBLETYPE_Role.equals(responsible.getResponsibleType()) + && responsible.getAD_Role_ID() != 0) + { + MUserRoles[] userRoles = MUserRoles.getOfRole(getCtx(), responsible.getAD_Role_ID()); + for (int i = 0; i < userRoles.length; i++) + { + MUserRoles roles = userRoles[i]; + if (!roles.isActive()) + continue; + int AD_User_ID = roles.getAD_User_ID(); + if (!list.contains(new Integer(AD_User_ID))) + { + if (m_client.sendEMail(AD_User_ID, subject, message, pdf)) + counter++; + list.add (new Integer(AD_User_ID)); + } + } + } + return counter; + } // sendAlertToResponsible + + + /** + * Get Server Info + * @return info + */ + public String getServerInfo() + { + return "#" + p_runCount + " - Last=" + m_summary.toString(); + } // getServerInfo + +} // WorkflowProcessor diff --git a/serverRoot/src/main/servlet/org/compiere/web/AdempiereMonitor.java b/serverRoot/src/main/servlet/org/compiere/web/AdempiereMonitor.java new file mode 100644 index 0000000000..dcf1e54d1b --- /dev/null +++ b/serverRoot/src/main/servlet/org/compiere/web/AdempiereMonitor.java @@ -0,0 +1,869 @@ +/****************************************************************************** + * 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.web; + +import java.io.*; +import java.lang.management.*; +import java.sql.*; +import java.util.*; +import java.util.logging.*; +import javax.servlet.*; +import javax.servlet.http.*; +import org.apache.ecs.*; +import org.apache.ecs.xhtml.*; +import org.compiere.*; +import org.compiere.db.*; +import org.compiere.model.*; +import org.compiere.server.*; +import org.compiere.util.*; + +/** + * Adempiere Server Monitor + * + * @author Jorg Janke + * @version $Id: AdempiereMonitor.java,v 1.3 2006/07/30 00:53:33 jjanke Exp $ + */ +public class AdempiereMonitor extends HttpServlet +{ + /** Logger */ + private static CLogger log = CLogger.getCLogger(AdempiereMonitor.class); + /** The Server */ + private AdempiereServerMgr m_serverMgr = null; + /** Message */ + private p m_message = null; + + + + /** + * Get + * @param request request + * @param response response + * @throws javax.servlet.ServletException + * @throws java.io.IOException + */ + protected void doGet (HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException + { + m_message = null; + if (processLogParameter (request, response)) + return; + if (processTraceParameter (request, response)) + return; + if (processEMailParameter (request, response)) + return; + if (processCacheParameter (request, response)) + return; + // + if (processRunNowParameter (request)) + ; + else + processActionParameter (request); + createSummaryPage(request, response); + } // doGet + + /** + * Post + * @param request request + * @param response response + * @throws ServletException + * @throws IOException + */ + protected void doPost (HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException + { + doGet(request, response); + } // doPost + + /** + * Process Log Parameter and return log page + * @param request request + * @param response response + * @return true if it was a log request + * @throws ServletException + * @throws IOException + */ + private boolean processLogParameter (HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException + { + String serverID = WebUtil.getParameter (request, "Log"); + if (serverID == null || serverID.length() == 0) + return false; + + log.info ("ServerID=" + serverID); + AdempiereServer server = m_serverMgr.getServer(serverID); + if (server == null) + { + m_message = new p(); + m_message.addElement(new strong("Server not found: ")); + m_message.addElement(serverID); + return false; + } + + WebDoc doc = WebDoc.create ("Adempiere Server Monitor Log"); + // Body + body b = doc.getBody(); + // + p para = new p(); + a link = new a ("adempiereMonitor#" + serverID, "Return"); + para.addElement(link); + b.addElement(para); + // + b.addElement(new h2(server.getName())); + // + table table = new table(); + table.setBorder(1); + table.setCellSpacing(2); + table.setCellPadding(2); + + // Header + tr line = new tr(); + line.addElement(new th().addElement("Created")); + line.addElement(new th().addElement("Summary")); + // line.addElement(new th().addElement("Error")); + line.addElement(new th().addElement("Reference")); + line.addElement(new th().addElement("TextMsg")); + // line.addElement(new th().addElement("Description")); + table.addElement(line); + + AdempiereProcessorLog[] logs = server.getLogs(); + for (int i = 0; i < logs.length; i++) + { + AdempiereProcessorLog pLog = logs[i]; + line = new tr(); + line.addElement(new td().addElement(WebEnv.getCellContent(pLog.getCreated()))); + line.addElement(new td().addElement(WebEnv.getCellContent(pLog.getSummary()))); + line.addElement(new td().addElement(WebEnv.getCellContent(pLog.getReference()))); + line.addElement(new td().addElement(WebEnv.getCellContent(pLog.getTextMsg()))); + table.addElement(line); + } + // + b.addElement(table); + link = new a ("#top", "Top"); + b.addElement(link); + + // fini + WebUtil.createResponse (request, response, this, null, doc, false); + return true; + } // processLogParameter + + /** + * Process Run Parameter + * @param request request + * @return true if it was a Run request + * @throws ServletException + * @throws IOException + */ + private boolean processRunNowParameter (HttpServletRequest request) + throws ServletException, IOException + { + String serverID = WebUtil.getParameter (request, "RunNow"); + if (serverID == null || serverID.length() == 0) + return false; + + log.info ("ServerID=" + serverID); + AdempiereServer server = m_serverMgr.getServer(serverID); + if (server == null) + { + m_message = new p(); + m_message.addElement(new strong("Server not found: ")); + m_message.addElement(serverID); + return false; + } + // + server.runNow(); + // + return true; + } // processRunParameter + + /** + * Process Action Parameter + * @param request request + */ + private void processActionParameter (HttpServletRequest request) + { + String action = WebUtil.getParameter (request, "Action"); + if (action == null || action.length() == 0) + return; + log.info ("Action=" + action); + try + { + boolean start = action.startsWith("Start"); + m_message = new p(); + String msg = (start ? "Started" : "Stopped") + ": "; + m_message.addElement(new strong(msg)); + // + String serverID = action.substring(action.indexOf("_")+1); + boolean ok = false; + if (serverID.equals("All")) + { + if (start) + ok = m_serverMgr.startAll(); + else + ok = m_serverMgr.stopAll(); + m_message.addElement("All"); + } + else + { + AdempiereServer server = m_serverMgr.getServer(serverID); + if (server == null) + { + m_message = new p(); + m_message.addElement(new strong("Server not found: ")); + m_message.addElement(serverID); + return; + } + else + { + if (start) + ok = m_serverMgr.start (serverID); + else + ok = m_serverMgr.stop (serverID); + m_message.addElement(server.getName()); + } + } + m_message.addElement(ok ? " - OK" : " - Error!"); + } + catch (Exception e) + { + m_message = new p(); + m_message.addElement(new strong("Error processing parameter: " + action)); + m_message.addElement(new br()); + m_message.addElement(e.toString()); + } + } // processActionParameter + + /** + * Process Trace Parameter + * @param request request + * @param response response + * @return true if it was a trace request with output + * @throws ServletException + * @throws IOException + */ + private boolean processTraceParameter (HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException + { + String traceCmd = WebUtil.getParameter (request, "Trace"); + String traceLevel = WebUtil.getParameter (request, "TraceLevel"); + if (traceLevel != null && traceLevel.length() > 0) + { + log.info ("New Level: " + traceLevel); + CLogMgt.setLevel(traceLevel); + Ini.setProperty(Ini.P_TRACELEVEL, traceLevel); + Ini.saveProperties(false); + return false; + } + + if (traceCmd == null || traceCmd.length() == 0) + return false; + + log.info ("Command: " + traceCmd); + CLogFile fileHandler = CLogFile.get (false, null, false); + // + if (traceCmd.equals("ROTATE")) + { + if (fileHandler != null) + fileHandler.rotateLog(); + return false; // re-display + } + else if (traceCmd.equals("DELETE")) + { + File logDir = fileHandler.getLogDirectory(); + if (logDir != null && logDir.isDirectory()) + { + File[] logs = logDir.listFiles(); + for (int i = 0; i < logs.length; i++) + { + String fileName = logs[i].getAbsolutePath(); + if (fileName.equals(fileHandler.getFileName())) + continue; + if (logs[i].delete()) + log.warning("Deleted: " + fileName); + else + log.warning("Not Deleted: " + fileName); + } + } + return false; // re-display + } + + // Display current log File + if (fileHandler != null && fileHandler.getFileName().equals(traceCmd)) + fileHandler.flush(); + + // Spool File + File file = new File (traceCmd); + if (!file.exists()) + { + log.warning ("Did not find File: " + traceCmd); + return false; + } + if (file.length() == 0) + { + log.warning ("File Length=0: " + traceCmd); + return false; + } + + // Stream Log + log.info ("Streaming: " + traceCmd); + try + { + long time = System.currentTimeMillis(); // timer start + int fileLength = (int)file.length(); + int bufferSize = 2048; // 2k Buffer + byte[] buffer = new byte[bufferSize]; + // + response.setContentType("text/plain"); + response.setBufferSize(bufferSize); + response.setContentLength(fileLength); + // + FileInputStream fis = new FileInputStream(file); + ServletOutputStream out = response.getOutputStream (); + int read = 0; + while ((read = fis.read(buffer)) > 0) + out.write (buffer, 0, read); + out.flush(); + out.close(); + fis.close(); + // + time = System.currentTimeMillis() - time; + double speed = (fileLength/1024) / ((double)time/1000); + log.info("length=" + + fileLength + " - " + + time + " ms - " + + speed + " kB/sec"); + } + catch (IOException ex) + { + log.log(Level.SEVERE, "stream" + ex); + } + return true; + } // processTraceParameter + + /** + * Process EMail Parameter + * @param request request + * @param response response + * @return true if it was a email request with output + * @throws ServletException + * @throws IOException + */ + private boolean processEMailParameter (HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException + { + String email = WebUtil.getParameter (request, "EMail"); + if (email == null || email.length() == 0) + return false; + + int AD_Client_ID = -1; + try + { + AD_Client_ID = Integer.parseInt(email); + } + catch (Exception e) + { + log.warning("Parsing: " + email + " - " + e.toString()); + } + if (AD_Client_ID < 0) + { + m_message = new p(); + m_message.addElement("No EMail: " + email); + return false; + } + + // log.info ("Test EMail: " + AD_Client_ID); + MClient client = MClient.get(new Properties(), AD_Client_ID); + log.info ("Test: " + client); + + m_message = new p(); + m_message.addElement(client.getName() + ": " + client.testEMail()); + return false; + } // processEMailParameter + + + /** + * Process Cache Parameter + * @param request request + * @param response response + * @return true if it was a email request with output + * @throws ServletException + * @throws IOException + */ + private boolean processCacheParameter (HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException + { + String cmd = WebUtil.getParameter (request, "CacheReset"); + if (cmd == null || cmd.length() == 0) + return false; + String tableName = WebUtil.getParameter (request, "CacheTableName"); + String record_ID = WebUtil.getParameter (request, "CacheRecord_ID"); + + m_message = new p(); + try + { + if (tableName == null || tableName.length() == 0) + { + CacheMgt.get().reset(); + m_message.addElement("Cache Reset: All"); + } + else if (record_ID == null || record_ID.length() == 0) + { + CacheMgt.get().reset(tableName); + m_message.addElement("Cache Reset: " + tableName); + } + else + { + CacheMgt.get().reset(tableName, Integer.parseInt(record_ID)); + m_message.addElement("Cache Reset: " + tableName + ", Record_ID=" + record_ID); + } + } + catch (Exception e) + { + log.severe(e.toString()); + m_message.addElement("Error: " + e.toString()); + } + return false; // continue + } // processEMailParameter + + /************************************************************************** + * Create & Return Summary Page + * @param request request + * @param response response + * @throws ServletException + * @throws IOException + */ + private void createSummaryPage (HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException + { + WebDoc doc = WebDoc.create ("Adempiere Server Monitor"); + // log.info("ServletConfig=" + getServletConfig()); + // AdempiereServerGroup.get().dump(); + + // Body + body bb = doc.getBody(); + // Message + if (m_message != null) + { + bb.addElement(new hr()); + bb.addElement(m_message); + bb.addElement(new hr()); + } + + // Summary + table table = new table(); + table.setBorder(1); + table.setCellSpacing(2); + table.setCellPadding(2); + // + tr line = new tr(); + line.addElement(new th().addElement(Adempiere.getName())); + line.addElement(new td().addElement(Adempiere.getVersion())); + table.addElement(line); + line = new tr(); + line.addElement(new th().addElement(Adempiere.getImplementationVendor())); + line.addElement(new td().addElement(Adempiere.getImplementationVersion())); + table.addElement(line); + line = new tr(); + line.addElement(new th().addElement("Manager")); + line.addElement(new td().addElement(WebEnv.getCellContent(m_serverMgr.getDescription()))); + table.addElement(line); + line = new tr(); + line.addElement(new th().addElement("Start - Elapsed")); + line.addElement(new td().addElement(WebEnv.getCellContent(m_serverMgr.getStartTime()) + + " - " + TimeUtil.formatElapsed(m_serverMgr.getStartTime()))); + table.addElement(line); + line = new tr(); + line.addElement(new th().addElement("Servers")); + line.addElement(new td().addElement(WebEnv.getCellContent(m_serverMgr.getServerCount()))); + table.addElement(line); + line = new tr(); + line.addElement(new th().addElement("Last Updated")); + line.addElement(new td().addElement(new Timestamp(System.currentTimeMillis()).toString())); + table.addElement(line); + bb.addElement(table); + // + p para = new p(); + a link = new a ("adempiereMonitor?Action=Start_All", "Start All"); + para.addElement(link); + para.addElement(" - "); + link = new a ("adempiereMonitor?Action=Stop_All", "Stop All"); + para.addElement(link); + para.addElement(" - "); + link = new a ("adempiereMonitor", "Refresh"); + para.addElement(link); + bb.addElement(para); + + // ***** Server Links ***** + bb.addElement(new hr()); + para = new p(); + AdempiereServer[] servers = m_serverMgr.getAll(); + for (int i = 0; i < servers.length; i++) + { + if (i > 0) + para.addElement(new br()); + AdempiereServer server = servers[i]; + link = new a ("#" + server.getServerID(), server.getName()); + para.addElement(link); + font status = null; + if (server.isAlive()) + status = new font().setColor(HtmlColor.GREEN).addElement(" (Running)"); + else + status = new font().setColor(HtmlColor.RED).addElement(" (Stopped)"); + para.addElement(status); + } + bb.addElement(para); + + // **** Log Management **** + createLogMgtPage(bb); + + // ***** Server Details ***** + for (int i = 0; i < servers.length; i++) + { + AdempiereServer server = servers[i]; + bb.addElement(new hr()); + bb.addElement(new a().setName(server.getServerID())); + bb.addElement(new h2(server.getName())); + // + table = new table(); + table.setBorder(1); + table.setCellSpacing(2); + table.setCellPadding(2); + // Status + line = new tr(); + if (server.isAlive()) + { + String msg = "Stop"; + if (server.isInterrupted()) + msg += " (Interrupted)"; + link = new a ("adempiereMonitor?Action=Stop_" + server.getServerID(), msg); + if (server.isSleeping()) + { + line.addElement(new th().addElement("Sleeping")); + line.addElement(new td().addElement(link)); + } + else + { + line.addElement(new th().addElement("Running")); + line.addElement(new td().addElement(link)); + } + table.addElement(line); + line = new tr(); + line.addElement(new th().addElement("Start - Elapsed")); + line.addElement(new td().addElement(WebEnv.getCellContent(server.getStartTime()) + + " - " + TimeUtil.formatElapsed(server.getStartTime()))); + } + else + { + String msg = "Start"; + if (server.isInterrupted()) + msg += " (Interrupted)"; + line.addElement(new th().addElement("Not Started")); + link = new a ("adempiereMonitor?Action=Start_" + server.getServerID(), msg); + line.addElement(new td().addElement(link)); + } + table.addElement(line); + // + line = new tr(); + line.addElement(new th().addElement("Description")); + line.addElement(new td().addElement(WebEnv.getCellContent(server.getDescription()))); + table.addElement(line); + // + line = new tr(); + line.addElement(new th().addElement("Last Run")); + line.addElement(new td().addElement(WebEnv.getCellContent(server.getDateLastRun()))); + table.addElement(line); + line = new tr(); + line.addElement(new th().addElement("Info")); + line.addElement(new td().addElement(WebEnv.getCellContent(server.getServerInfo()))); + table.addElement(line); + // + line = new tr(); + line.addElement(new th().addElement("Next Run")); + td td = new td(); + td.addElement(WebEnv.getCellContent(server.getDateNextRun(false))); + td.addElement(" - "); + link = new a ("adempiereMonitor?RunNow=" + server.getServerID(), "(Run Now)"); + td.addElement(link); + line.addElement(td); + table.addElement(line); + // + line = new tr(); + line.addElement(new th().addElement("Statistics")); + line.addElement(new td().addElement(server.getStatistics())); + table.addElement(line); + // + + // Add table to Body + bb.addElement(table); + link = new a ("#top", "Top"); + bb.addElement(link); + bb.addElement(" - "); + link = new a ("adempiereMonitor?Log=" + server.getServerID(), "Log"); + bb.addElement(link); + bb.addElement(" - "); + link = new a ("adempiereMonitor", "Refresh"); + bb.addElement(link); + } + + // fini + WebUtil.createResponse (request, response, this, null, doc, false); + } // createSummaryPage + + /** + * Add Log Management to page + * @param bb body + */ + private void createLogMgtPage (body bb) + { + bb.addElement(new hr()); + + // Ini Parameters + table table = new table(); + table.setBorder(1); + table.setCellSpacing(2); + table.setCellPadding(2); + // + Properties ctx = new Properties(); + MSystem system = MSystem.get(ctx); + tr line = new tr(); + line.addElement(new th().addElement(system.getDBAddress())); + line.addElement(new td().addElement(Ini.getAdempiereHome())); + table.addElement(line); + // OS + Name + line = new tr(); + String info = System.getProperty("os.name") + + " " + System.getProperty("os.version"); + String s = System.getProperty("sun.os.patch.level"); + if (s != null && s.length() > 0) + info += " (" + s + ")"; + line.addElement(new th().addElement(info)); + info = system.getName(); + if (system.getCustomPrefix() != null) + info += " (" + system.getCustomPrefix() + ")"; + line.addElement(new td().addElement(info)); + table.addElement(line); + // Java + email + line = new tr(); + info = System.getProperty("java.vm.name") + + " " + System.getProperty("java.vm.version"); + line.addElement(new th().addElement(info)); + line.addElement(new td().addElement(system.getUserName())); + table.addElement(line); + // DB + Instance + line = new tr(); + CConnection cc = CConnection.get(); + AdempiereDatabase db = cc.getDatabase(); + info = db.getDescription(); + line.addElement(new th().addElement(info)); + line.addElement(new td().addElement(cc.getConnectionURL())); +// line.addElement(new td().addElement(system.getDBInstance())); + table.addElement(line); + // Processors/Support + line = new tr(); + line.addElement(new th().addElement("Processor/Support")); + line.addElement(new td().addElement(system.getNoProcessors() + "/" + system.getSupportUnits())); + table.addElement(line); + // Memory + line = new tr(); + MemoryMXBean memory = ManagementFactory.getMemoryMXBean(); + line.addElement(new th().addElement("VM Memory")); + line.addElement(new td().addElement(new CMemoryUsage(memory.getNonHeapMemoryUsage()).toString())); + table.addElement(line); + line = new tr(); + line.addElement(new th().addElement("Heap Memory")); + line.addElement(new td().addElement(new CMemoryUsage(memory.getHeapMemoryUsage()).toString())); + table.addElement(line); + // Runtime + line = new tr(); + RuntimeMXBean rt = ManagementFactory.getRuntimeMXBean(); + line.addElement(new th().addElement("Runtime " + rt.getName())); + line.addElement(new td().addElement(TimeUtil.formatElapsed(rt.getUptime()))); + table.addElement(line); + // Threads + line = new tr(); + ThreadMXBean th = ManagementFactory.getThreadMXBean(); + line.addElement(new th().addElement("Threads " + th.getThreadCount())); + line.addElement(new td().addElement("Peak=" + th.getPeakThreadCount() + + ", Demons=" + th.getDaemonThreadCount() + + ", Total=" + th.getTotalStartedThreadCount())); + table.addElement(line); + // Cache Reset + line = new tr(); + line.addElement(new th().addElement(CacheMgt.get().toStringX())); + line.addElement(new td().addElement(new a ("adempiereMonitor?CacheReset=Yes", "Reset Cache"))); + table.addElement(line); + + // Trace Level + line = new tr(); + line.addElement(new th().addElement(new label("TraceLevel").addElement("Trace Log Level"))); + form myForm = new form("adempiereMonitor", form.METHOD_POST, form.ENC_DEFAULT); + // LogLevel Selection + option[] options = new option[CLogMgt.LEVELS.length]; + for (int i = 0; i < options.length; i++) + { + options[i] = new option(CLogMgt.LEVELS[i].getName()); + options[i].addElement(CLogMgt.LEVELS[i].getName()); + if (CLogMgt.LEVELS[i] == CLogMgt.getLevel()) + options[i].setSelected(true); + } + select sel = new select("TraceLevel", options); + myForm.addElement(sel); + myForm.addElement(new input(input.TYPE_SUBMIT, "Set", "Set")); + line.addElement(new td().addElement(myForm)); + table.addElement(line); + // + line = new tr(); + CLogFile fileHandler = CLogFile.get (true, null, false); + line.addElement(new th().addElement("Trace File")); + line.addElement(new td().addElement(new a ("adempiereMonitor?Trace=" + fileHandler.getFileName(), "Current"))); + table.addElement(line); + // + line = new tr(); + line.addElement(new td().addElement(new a ("adempiereMonitor?Trace=ROTATE", "Rotate Trace Log"))); + line.addElement(new td().addElement(new a ("adempiereMonitor?Trace=DELETE", "Delete all Trace Logs"))); + table.addElement(line); + // + bb.addElement(table); + + // List Log Files + p p = new p(); + p.addElement(new b("All Log Files: ")); + // All in dir + File logDir = fileHandler.getLogDirectory(); + if (logDir != null && logDir.isDirectory()) + { + File[] logs = logDir.listFiles(); + for (int i = 0; i < logs.length; i++) + { + if (i != 0) + p.addElement(" - "); + String fileName = logs[i].getAbsolutePath(); + a link = new a ("adempiereMonitor?Trace=" + fileName, fileName); + p.addElement(link); + int size = (int)(logs[i].length()/1024); + if (size < 1024) + p.addElement(" (" + size + "k)"); + else + p.addElement(" (" + size/1024 + "M)"); + } + } + bb.addElement(p); + + // Clients and Web Stores + table = new table(); + table.setBorder(1); + table.setCellSpacing(2); + table.setCellPadding(2); + // + line = new tr(); + MClient[] clients = MClient.getAll(ctx); + line.addElement(new th().addElement("Client #" + clients.length + " - EMail Test:")); + p = new p(); + for (int i = 0; i < clients.length; i++) + { + MClient client = clients[i]; + if (i > 0) + p.addElement(" - "); + p.addElement(new a("adempiereMonitor?EMail=" + client.getAD_Client_ID(), client.getName())); + } + if (clients.length == 0) + p.addElement(" "); + line.addElement(new td().addElement(p)); + table.addElement(line); + // + line = new tr(); + MStore[] wstores = MStore.getActive(); + line.addElement(new th().addElement("Active Web Stores #" + wstores.length)); + p = new p(); + for (int i = 0; i < wstores.length; i++) + { + MStore store = wstores[i]; + if (i > 0) + p.addElement(" - "); + a a = new a(store.getWebContext(), store.getName()); + a.setTarget("t" + i); + p.addElement(a); + } + if (wstores.length == 0) + p.addElement(" "); + line.addElement(new td().addElement(p)); + table.addElement(line); + // + bb.addElement(table); + } // createLogMgtPage + + /************************************************************************** + * Init + * @param config config + * @throws javax.servlet.ServletException + */ + public void init (ServletConfig config) throws ServletException + { + WebEnv.initWeb(config); + log.info (""); + m_serverMgr = AdempiereServerMgr.get(); + } // init + + /** + * Destroy + */ + public void destroy () + { + log.info ("destroy"); + m_serverMgr = null; + } // destroy + + /** + * Log error/warning + * @param message message + * @param e exception + */ + public void log (String message, Throwable e) + { + if (e == null) + log.warning (message); + log.log(Level.SEVERE, message, e); + } // log + + /** + * Log debug + * @param message message + */ + public void log (String message) + { + log.fine(message); + } // log + + + /** + * Get Servlet Name + * @return servlet name + */ + public String getServletName () + { + return "AdempiereMonitor"; + } // getServletName + + /** + * Get Servlet Info + * @return servlet info + */ + public String getServletInfo () + { + return "Adempiere Server Monitor"; + } // getServletName + +} // AdempiereMonitor diff --git a/serverRoot/src/main/servlet/org/compiere/web/AdempiereMonitorFilter.java b/serverRoot/src/main/servlet/org/compiere/web/AdempiereMonitorFilter.java new file mode 100644 index 0000000000..ebdf14f890 --- /dev/null +++ b/serverRoot/src/main/servlet/org/compiere/web/AdempiereMonitorFilter.java @@ -0,0 +1,166 @@ +/****************************************************************************** + * 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.web; + +import java.io.*; +import java.util.logging.*; +import javax.servlet.*; +import javax.servlet.Filter; +import javax.servlet.http.*; +import org.compiere.model.*; +import org.compiere.util.*; +import sun.misc.*; + +/** + * Adempiere Monitor Filter. + * Application Server independent check of username/password + * + * @author Jorg Janke + * @version $Id: AdempiereMonitorFilter.java,v 1.2 2006/07/30 00:53:33 jjanke Exp $ + */ +public class AdempiereMonitorFilter implements Filter +{ + /** + * AdempiereMonitorFilter + */ + public AdempiereMonitorFilter () + { + super (); + m_authorization = new Long (System.currentTimeMillis()); + } // AdempiereMonitorFilter + + /** Logger */ + protected CLogger log = CLogger.getCLogger(getClass()); + + /** Authorization ID */ + private static final String AUTHORIZATION = "AdempiereAuthorization"; + /** Authorization Marker */ + private Long m_authorization = null; + + /** + * Init + * @param config configuration + * @throws ServletException + */ + public void init (FilterConfig config) + throws ServletException + { + log.info (""); + } // Init + + /** + * Filter + * @param request request + * @param response response + * @param chain chain + * @throws IOException + * @throws ServletException + */ + public void doFilter (ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException + { + boolean error = false; + String errorPage = "/error.html"; + boolean pass = false; + try + { + if (!(request instanceof HttpServletRequest && response instanceof HttpServletResponse)) + { + request.getRequestDispatcher(errorPage).forward(request, response); + return; + } + HttpServletRequest req = (HttpServletRequest)request; + HttpServletResponse resp = (HttpServletResponse)response; + // Previously checked + HttpSession session = req.getSession(true); + Long compare = (Long)session.getAttribute(AUTHORIZATION); + if (compare != null && compare.compareTo(m_authorization) == 0) + { + pass = true; + } + else if (checkAuthorization (req.getHeader("Authorization"))) + { + session.setAttribute(AUTHORIZATION, m_authorization); + pass = true; + } + // -------------------------------------------- + if (pass) + { + chain.doFilter(request, response); + } + else + { + resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + resp.setHeader("WWW-Authenticate", "BASIC realm=\"Adempiere Server\""); + } + return; + } + catch (Exception e) + { + log.log(Level.SEVERE, "filter", e); + } + request.getRequestDispatcher(errorPage).forward(request, response); + } // doFilter + + /** + * Check Authorization + * @param authorization authorization + * @return true if authenticated + */ + private boolean checkAuthorization (String authorization) + { + if (authorization == null) + return false; + try + { + String userInfo = authorization.substring(6).trim(); + BASE64Decoder decoder = new BASE64Decoder(); + String namePassword = new String (decoder.decodeBuffer(userInfo)); + // log.fine("checkAuthorization - Name:Password=" + namePassword); + int index = namePassword.indexOf(":"); + String name = namePassword.substring(0, index); + String password = namePassword.substring(index+1); + MUser user = MUser.get(Env.getCtx(), name, password); + if (user == null) + { + log.warning ("User not found: '" + name + "/" + password + "'"); + return false; + } + if (!user.isAdministrator()) + { + log.warning ("Not a Sys Admin = " + name); + return false; + } + log.info ("Name=" + name); + return true; + } + catch (Exception e) + { + log.log(Level.SEVERE, "check", e); + } + return false; + } // check + + /** + * Destroy + */ + public void destroy () + { + log.info (""); + } // destroy + +} // AdempiereMonitorFilter diff --git a/serverRoot/src/main/servlet/org/compiere/web/ServerStatus.java b/serverRoot/src/main/servlet/org/compiere/web/ServerStatus.java new file mode 100644 index 0000000000..95ef842027 --- /dev/null +++ b/serverRoot/src/main/servlet/org/compiere/web/ServerStatus.java @@ -0,0 +1,96 @@ +/****************************************************************************** + * 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.web; + +import java.io.*; + +import javax.servlet.*; +import javax.servlet.http.*; + + +/** + * + * + * @author Jorg Janke + * @version $Id: ServerStatus.java,v 1.2 2006/07/30 00:53:33 jjanke Exp $ + */ +public class ServerStatus extends HttpServlet +{ + /** + * doGet + * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) + * @param arg0 + * @param arg1 + * @throws javax.servlet.ServletException + * @throws java.io.IOException + */ + protected void doGet (HttpServletRequest arg0, HttpServletResponse arg1) + throws ServletException, IOException + { + super.doGet (arg0, arg1); + } + + /** + * doPost + * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) + * @param arg0 + * @param arg1 + * @throws javax.servlet.ServletException + * @throws java.io.IOException + */ + protected void doPost (HttpServletRequest arg0, HttpServletResponse arg1) + throws ServletException, IOException + { + // TODO Auto-generated method stub + super.doPost (arg0, arg1); + } + + /** + * getServletInfo + * @see javax.servlet.Servlet#getServletInfo() + * @return servlet info + */ + public String getServletInfo () + { + return super.getServletInfo (); + } + + /** + * init + * @see javax.servlet.GenericServlet#init() + * @throws javax.servlet.ServletException + */ + public void init () + throws ServletException + { + super.init (); + } + + /** + * init + * @see javax.servlet.Servlet#init(javax.servlet.ServletConfig) + * @param arg0 + * @throws javax.servlet.ServletException + */ + public void init (ServletConfig arg0) + throws ServletException + { + // TODO Auto-generated method stub + super.init (arg0); + } + +} // ServerStatus diff --git a/serverRoot/src/main/servlet/org/compiere/web/StatusInfo.java b/serverRoot/src/main/servlet/org/compiere/web/StatusInfo.java new file mode 100644 index 0000000000..d6a5be14fc --- /dev/null +++ b/serverRoot/src/main/servlet/org/compiere/web/StatusInfo.java @@ -0,0 +1,168 @@ +/****************************************************************************** + * 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.web; + +import javax.servlet.*; +import javax.servlet.http.*; +import java.io.*; +import javax.naming.*; + +import javax.sql.*; +import java.sql.*; + +import org.compiere.interfaces.*; + +/** + * Status Info Servlet + * + * @author Jorg Janke + * @version $Id: StatusInfo.java,v 1.3 2006/07/30 00:53:33 jjanke Exp $ + */ +public class StatusInfo extends HttpServlet +{ + static final private String CONTENT_TYPE = "text/html"; + /** + * Initialize global variables + * @throws ServletException + */ + public void init() throws ServletException + { + getServletContext().log("StatusInfo.init"); + } + + /** + * Get + * @param request + * @param response + * @throws ServletException + * @throws IOException + */ + public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + { + response.setContentType(CONTENT_TYPE); + PrintWriter out = response.getWriter(); + out.println(""); + out.println("Status Info"); + out.println(""); + + InitialContext context = null; + try + { + context = new InitialContext(); + } + catch (Exception ex) + { + out.println("

" + ex + "

"); + } + + try + { + StatusHome statusHome = (StatusHome)context.lookup (StatusHome.JNDI_NAME); + Status status = statusHome.create(); + out.println("

" + status.getStatus() + "

"); + status.remove(); + } + catch (Exception ex) + { + out.println("

" + ex + "

"); + } + + try + { + ServerHome serverHome = (ServerHome)context.lookup (ServerHome.JNDI_NAME); + Server server = serverHome.create(); + out.println("

" + server.getStatus() + "

"); + server.remove(); + } + catch (Exception ex) + { + out.println("

" + ex + "

"); + } + + try + { + out.println("

-- /

"); + NamingEnumeration ne = context.list("/"); + while (ne.hasMore()) + out.println("
" + ne.nextElement()); + out.println("

-- java

"); + ne = context.list("java:"); + while (ne.hasMore()) + out.println("
" + ne.nextElement()); + out.println("

-- ejb

"); + ne = context.list("ejb"); + while (ne.hasMore()) + out.println("
" + ne.nextElement()); + + // + + out.println("

-- DS

"); + DataSource ds = (DataSource)context.lookup("java:/OracleDS"); + out.println("
DataSource " + ds.getClass().getName() + " LoginTimeout=" + ds.getLoginTimeout()); + + Connection con = ds.getConnection("adempiere","adempiere"); + out.println("
Connection "); + + getServletContext().log("Connection closed=" + con.isClosed()); + DatabaseMetaData dbmd = con.getMetaData(); + getServletContext().log("DB " + dbmd.getDatabaseProductName()); + getServletContext().log("DB V " + dbmd.getDatabaseProductVersion()); + getServletContext().log("Driver " + dbmd.getDriverName()); + getServletContext().log("Driver V " + dbmd.getDriverVersion()); + getServletContext().log("JDBC " + dbmd.getJDBCMajorVersion()); + getServletContext().log("JDBC mV " + dbmd.getJDBCMinorVersion()); + + getServletContext().log("User " + dbmd.getUserName()); + + getServletContext().log("ANSI 92 " + dbmd.supportsANSI92FullSQL()); + getServletContext().log("Connection Alter Table ADD" + dbmd.supportsAlterTableWithAddColumn()); + getServletContext().log("Connection Alter Table DROP " + dbmd.supportsAlterTableWithDropColumn()); + getServletContext().log("Connection DDL&DML " + dbmd.supportsDataDefinitionAndDataManipulationTransactions()); + getServletContext().log("Connection CatalogsIn DML " + dbmd.supportsCatalogsInDataManipulation()); + getServletContext().log("Connection Schema In DML " + dbmd.supportsSchemasInDataManipulation()); + + + + } + catch (Exception e) + { + out.println("

" + e + "

"); + } + + + out.println(""); + } + + /** + * Put - Processes Get + * @param request + * @param response + * @throws ServletException + * @throws IOException + */ + public void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + { + doGet (request, response); + } + /** + * Destroy + */ + public void destroy() + { + getServletContext().log("StatusInfo.destroy"); + } +} diff --git a/serverRoot/src/web/AdemPiere150x50.gif b/serverRoot/src/web/AdemPiere150x50.gif new file mode 100644 index 0000000000000000000000000000000000000000..6244fce2252295ab388dcdcbf777c5dce9229a18 GIT binary patch literal 2603 zcmV+`3e@$9P)UXOsyW2t;*BQ7F=nhQrRxfI2lUj}X@O>x6 zN!46LRtHQ_#+BOo06qdf44=(q^v(vJQO0%8Ve2OePXIRpR{`^samTc?J*`H)GHwuX zP9k4|5Un#ZYrYS>p6J*xvx>oZt2>6zUZI|&fNOwP6WuQX)0J`i>_k6y8-z*r)kxF; zoY_w1RE-AUqC~b3INn>mC8Xa0?or0gLkO3VDRc7wRH+ef_1nO+z_ZG@-vX18c8>yX zMwb6iI^IAIItN6u7uv~|s!X(6=67h+xZO~i&fw%f%U_z4hBW2ux z*4DSX!CSo*_%<*TSO$y(?rl$-szC#?)n(WQJXc2l*(Cj$Y8&=ek5R@Q1)Lm2^}rRt z`Q^$f!hrC6LQ!aGTN z&sA*y9^|A`>^+3Wm!+=AgtR`jLOu%}p|52pqZ4{2n z2|$5fm>fRz{`+skQ0 zqzX6?7y+zO#tl`*T>&gd4lUghT?L#6Jgz||Cz3S^~h1m-B?ZVx2M z4Q1Tt5MTIq#L-Wmpku>;p{6 zX?GnkUK!WLTYXiWp^Q5Rc@T9u>bVH$p^R(vR&T4Mtc>dg%t0nYCG-ZK4r8(+$-4ZeL(3ElD*WSd8rPgMovi zyhDNC14AR(&uK}jw~zr_kIe4l%h>lW@HnszSxpB~=mLyV#?A9qPYZVU2L4D(QY}WD z@t&c*{i3`xknQLOD&sy{l4=F;G_Vyo1~@XxQ@|8u+?O*KLm77#ElIT*S(z3@vO8!= zsx`>U|2{AP*)cM70;VeC`liHc4;+xBvv}Dh6dn$?0B3uvAIb#5;hYHkEYfRK#vSRc zUJ~MLS10w)0?zYRZ;0wS8My^Ww}a<^)4bKMM!H`>R<(G*uLGuq-{TO{XLvKTH$AH3 zAjB_;PvqAq<0h9oj40zy1&&L^W4zT961@u$Qe@;l47tm_E2OVY>R17M$6LKUtf7qS zPvHawu)xT8WmU$V2|U0KU%e-AX-cfE$lhEEy$}a_mmr)l(t%nNzSBgqONsJd zpotyvwXEzCWTlGzsaJqu>8Z(EJyRJs9C(OATo#im=)Rzg8y4!%R<3@**?$QM6~wq8 zCRM0oU05}iv19V=7wnx%X{2NF8h|oxbP?T`fIdmtyMmqlBKZeEqqllxg|?cINk2*% zm$tQ>!c}r;O018()y0e?bl^mGA+eAXP5j_jdaKuj-@Oyv{^64ceUk4C(@Gu1z17o{ zah^gyvgippkZU1@TWB9-+l;q3@2xIM6mRt#%D8F3Pa|>PV!BBiF?s^mS16N~P3xFn zgjB`!IwLD{9`tUvQ=u}h53+58&{RTRYE1rJ-})HfT6!)5q7Fxp$aI;neqknOfAt9P_@L6jbdIPL{O;qdZ6*DSD{p{iBcX!#5WjC6uv!^6q?+refN98Kr&mbt3;bOf zcRjM(G$G-JvB(X`ao+0pv%;cyw&ODb{3VNl!y;Q zmi`wJALnM|4(~E$T-iIuoY>VXR)(X2hY`EyA~B?R?3lP6|wSQG4M5S^(|#|Mv~u$ zm>e#8cLr@mta5k@IIG0TtOdN)cOt8F9$QB$>{trV#D3lSVl8>NZD~Pp-Z!)h$(c6GWqjaTz2aTXGWKizG-ALhT zx&rZeVyr}ho@W3@Alvpkz`v1yD=jZk_mvc$m|N(1s~=UyJ%%{&gOHe3J@SU`9AsQt zyjO=@PsPtLeWSMpyw!7o0m`^zkuCNhBsjbQmi+Dr|*FKWc9p-oUDRt;@}8nJ3<6V!-RLz|#RtQy(`{{z)(Ay)EK-BJJm N002ovPDHLkV1gT};F|yd literal 0 HcmV?d00001 diff --git a/serverRoot/src/web/Adempiere120x60.gif b/serverRoot/src/web/Adempiere120x60.gif new file mode 100644 index 0000000000000000000000000000000000000000..07ca4d0ff095e51cf8bfd14fc3e473bb5232d662 GIT binary patch literal 2025 zcmV6CPK+3ZIn`8vaFO0qakGFO|C-s^vAQy+55Y{-+9q1_r~1s%$c*h zzh|G{v-|8mZ|9WC&H<-sa!6jj2i?jMwFX}JwThcdJPciU#o-vVPF|B$6M`tLg2m%W!%ZYE8gmIP2!`7 zCS_a+_&LSO1sDpfR>nqETNTM)%Zr1Vwct-cRvONK5`#!Y@Rg7#H-tN#s* z0za&M@z3pxs;jr$BQOviakIlYxE6ExXrS{d^VPBdKH*gMnR$ z+iCY!@5zw=6xque%DCy?>K76|C14^j6?iXlsa^zD11r7NodxeNA$v9%I2#z}tv;MQ zACm0ZX~4m-XUo0Se~5I=MGNd9gvB)AGBNKA1tFh?2pU0`0i$;!9^z;}R~khmkoB;aexxa(p@5zv~@4oY~P zjs&n5W%#rLp9G#&#!dBB|24C=GlBbo4<*B;Y$TR@B1-YGH zPR17_H@*nNk$w8OxBB5CqF_1J1MjcGYZUOPGHy&T^(f;`0UiU+D&Ttwum(7;3f~z> zOfW6GK_kOUv!CqkY82VruruV+OQ|S#C-5cYbYF=SN+PB!N>cPVhWxB5^hFtt-r?k?blu&yhR3@G9g%D8FSoAWHtkt6H~;FKzh zsfBZs_8xEbL1o+&WN{H+2R4QF&d^_u_#c2T0;>=+8BJ3M;+r-{Cd#;#z;CITx5L0# zZ}s-je=@`b8-e$Qv6*4ctVEt?fY!t{tW(CVL414*vL9Ci_huIx6!Ga~=og4djkq6i z8#6+GO1QZli0MAEC*JD$N&j--JxPBa;&(yP*>3=6ChPh_R>9g^-K_+vjJu1ji*FP& zllJ=X{lN^)k{Ucl<;;61RW5+H`VD2AMN%?=HxlJWntjT+MZga;JVz&FcVzv(w|Z+( z^3;q>S&T$Xf5bGZ=f_P*4&4C^jB*2aWavg``iraZ1Q=CskX5Qpk`HCv^+@3)VtM!; zm_uF-(JZAw)!z$fx7EQfof~2J>-FyQzGc@BKr6s(ME zLV}muli}ZZs~-sMG(~v@I6Ei(s3l8L-M5~4a9(Q!wFC+oMK$~-X?tYPs@C{>O2MO*aqkN4laSnTI`Dyn<|Sm)A|3+nL{9B+V68Il z^WN&s%DCac9AKBXI%}rtL2ho@AWfSxZU?ZyTkS(${1f1e@cayFf;gcl&B&sbf>#4i zgwHR5`E*t8R^X%F>K$<)w7;FuT!<9w_ao8a&A+{Jaun4+E=To27n!0sT^b4dOi zaa&r@$ia=kfzTd`1Xiyhg_c!FreN!k;Rw9dJArQ|L#;?Iy&tiHw*d2%aT6jzPbs(v z2a%MZ)mz=oUwW%=0~Tb4)8J$Pwe#6L^u!HLk^O;X5%8^Q6GHHJC9pdgK8~*bxlI{& z1`WVly%0&k(gT{x*QEx~j3m`jfknUZJ`Ze2+MP%m(HUM;4((24LEDkC-XrNB1quY7 ztBhM4gc=`&?L>+r%YYwcraYI(Qse{oCNz0zA3@GYlPBXM;Cv)fJ{~!&j{^(6)$1~} z8>m#W;;qG7y*~U*Iu}XrV!>z!Vs3BsR;Oj)RHl~;Zd|Ycqo#;zopGm#=Qe*@>cJEi=t-v4g(J4 z`SU)f;NC=@f_qb*oNM%w}i@w}*6%00000NkvXX Hu0mjf3U$m| literal 0 HcmV?d00001 diff --git a/serverRoot/src/web/Background.gif b/serverRoot/src/web/Background.gif new file mode 100644 index 0000000000000000000000000000000000000000..11988728edc48c6e0f92dfe0d47d2ae5a717e4d6 GIT binary patch literal 2301 zcmcgu`!^Ge8=p;7jP_Mix4yqVinEVtYV;k)lS-@oBK=ks|!=X}odoX>fFdY(9YJ1qF1%0U1C0LIy% z9VIG|uw%cBq-OOuH%mmu>k<|XI0Og+ zF|vbla**Six0jP~0Koo7IP?YQaN+z&M2Q+fjkmO*J*y~Pod5vh4uJOq9bCW#ADN!_ zuGnAYbigmQGF6mpbK(w?4 z+%huE?P604$MMTa8$FiPE%x|`k#7b?E=d#jFM!y`306#?1gkE#KYw{DRtGNV{59T5 z-eE`EbKjzVg?ybtogv(E`b1V^UVu_!QbvY7s+0RksjhG$Hy+Jd==MaBmya&min^Jr z_tOR${kf*zZ}#q6P4eHrstfh*=hDn7hy1wty&G@tP?{@rpFa{!V$#p%Ko&3q|C*9oeyosHd9_}aJ z>0E!9P;Go=I~QCI%b-+_UCus_-m(hXZ+|=7Tq=9FnQ~DW#xdcFWYjNU&*;>Y}$u7Bao*Fm=dOSry0!VgW0~rjzbG#n@MHfl+mYIGFc> zS3?-3(J#^aGz5Jb?G?us{Y6bY1n(6ONk2`0r6JHTGQiHL*RB*f@S_JJN|9P+xK^Xe zHby4vFp`KXL zOeV|rpXPhPxR&d2WHzPp#A1WgW50ojO#QEqeWFv7ZDpL8`WtX(_%Nu63 zvH;~l)sHvZjL4{&ru4Olcb^$sSTU(&)B#mr`eN=5IBpbXl0T@i2mVxBZ{h}ABk)UO z%jF?mwXyGs{ccxAwZ%0Bw2p*L6Xj0@m?rR6w*l54?fx|^c=)3;c1eG(*I%(y#}p6` zaKmuKksjj-%R_?p>RMf<4RY4guh##FYdIe-7q6B@EYE=Xo^ zkWs&l=FDQ75NhHYz*A@A5+sH*g0V=RNsOfBEUY75{>D8W3p>|XUOX}S1?;nCzWjG} zEAUL&sT#auL*TjVx4&RSz06QDdW(v#SH5!gukXJ?rc;=!=h5}$_)%BIjQCVuG{oNl zU2hJzR7!j*wGE=;|KfW;4cR^(WREsOz(@Q0nPyM&qfkZAV+yleRZ?$0*=dtj6v4af zmj9yU%|hS>R919xWG!N}+O2)Lnz`BsMd9h?)|@V~PpxskTR_L%;{&gm_NBX7efBXM z1}o!)K;)Zxh#h}Pen3M_se77-Q zLt%Rwv9+pG8yrD02sDHXazP$}#N6Sq?wG3N5o^v$zV`F$s)8jD^q6qd#lM_{=WjSn5oqejjy~(J0i62$|HrAwc)XY$oo7EmpEX|uF%_+;>HN%^kuc~7GL0jIx70zul5VObKE-c2Ikau=GSD7<`kX@K-4P48 zW$RvA-t`a7C-q60d6Ee{`)h8CyyNKCI%9;Vmd4WhbQD+s-Y} zoaNgiX^jV@fg^q{I9OTCR-<~_#+1nC113qCHr(uz-9IP4=Dn{Ppron>w9!DmUrF6p zlZh95+JSkU%ANaOogu?M7RAGV)2#G#XxY$2EAzK)jnY3nn_@AX zoPaf#2Ji{7ms%${eT7bSp$@32><&>h1UQgaq~#=e%o>bh-zLZ%-T>*@>mK+33X>U= zpHoe&QA4Z`6}WL@L)e0~6vYxnS-YH(YyZrqGhhD!4DKcdHZXm^F`RA3UkD6NGgpa5aRwD=I48_jIj$ zt5Iif88vZ2=%C^-Y25g8Vq7N0{KU!Wi=G>Z#|0OA9Xs`?J6_=DvTAlKyDeK5VH5Ws zIywZ2K>dMXajSjtnt`GmSa!ag5MQqfcgU=)0s;yxJz19Yb>{K~ptJf@~ literal 0 HcmV?d00001 diff --git a/serverRoot/src/web/C32.gif b/serverRoot/src/web/C32.gif new file mode 100644 index 0000000000000000000000000000000000000000..c83d267cbde6c015b5ddb52f19878b97fabeba6a GIT binary patch literal 2213 zcmV;W2wL}vP)WFU8GbZ8({Xk{QrNlj4iWF>9@00;?5L_t(o!=+bUY#dh= z{_g+G?s}cE*Xyq1rmkb+B#QE5HHlgwq*5qEGzgyP6A!%b#5)f>1gVOMgoKorLbLY;v zVHiG1Dg9QhRy+3SqmPc)YBf|U6>!cGMG<`8$DKQO?%ug`=XbvEUkHNWk0(x?==@&* zUVZgdv9hvq%oy|IdcA(|@ZrO#*XtmJfRN3fT5AzS5w2gqj@jASYwPRlCstQiFP=Je zD*N03UVQPz(e?H9pEJguIdI?rnVy~o=e(p$*z*EPTecU7-+4bltR5;C%*6RCWL(b$dMy| zdFP#X7C#lhnKNhhBuVmnrPSo!y?Ze}KHfborF6@AVToQ4#u!ja;dvg`*4F9(zBw~9 zbMexpOUn-gaQgJ=;UEZJE0@b(Xfzs_n3w=#43yHou@FjWS2hAEEI}!SloDYWRw<=V z?A^QfSC=ne?rZ|^_S`T2PZ0JH!wH#hf$we~xX<6wArxa)AmX*uTrA!sLg*Th=uO>+RC zlx_lAtJP30mr*K}o;`T*;NyK3xNzYDZ?#&#AcRbnN+pbskHc{sNGTzugb)IR5S;(} zM>zZLJdQl#!4-7Vz4wmRS`b2Dj6t5?1DzxZf*@d&(ub#~r(d~x^{U|j5XbQz08cuO z1Eo|KIp-XdQfRG_=QL{W5H2tk!n5JJFO3&!rbN1o@PlmdjHA}Mms(MdD>^ba?YX^VH{w1}Fb?8n+ao{)( zgb?`lPJtg?YT~79pTKhk%925GikaBPF9AP$dK^+t9|M)lM+sscu%=m>Ys1K+@@;*q>60EX%+-ht@jg zuIn1DwWgHPVo~o#{@NO*2P$}MY_yO0ZZ5Sk*XrQravOe};poFvlmzR3mun5*_mRYL zSGf=Zj4>oh0sw7F=_)Um%Xt(*Wn zBYeD`z+nWxxzWUg=U|&+$g>1A>%enZpK>8YH!KMu%e7i9r=z2z8Rz_Jmm2SbD5PLR z;GQEbE-s?gYJp}QJoQK!og_t;rikMX=091%t&f{vS%g|iK=m+bF;d!P|Esrd-OA{$ zUAwFh;RW@4gjrI>n+doVq=U`d7k%Wv|eizM72>4j#aULtcsg!IfRhi(-&irTkaDO zq9{6l{rdIWT>vk?{IaH$KBu)_OVhMl%@ilucqLnb*i|3EzQ!P$v4s%#))mRDkUYgDob#9V?Ai0d@#DugSH-ilvm-$eyu=v$n&){?N^MfwtGIVwR6-l8=0c(br&*qbA^>JfUcp!h$IhiZN!u7{l*}sAh~A&bj8CgL95N n&r_}SO1WIN588MC{{8en2DVv(%1P7t00000NkvXXu0mjfVR0cm literal 0 HcmV?d00001 diff --git a/serverRoot/src/web/Logo.gif b/serverRoot/src/web/Logo.gif new file mode 100644 index 0000000000000000000000000000000000000000..f3e3c9368219408ec01943c3936b8e5e93855372 GIT binary patch literal 3372 zcmV+{4b$?8P)qeIPYl)(m8dZX zmk5Q5C~8E5ao-b%#2OBTz<67VcS#^L`U?K1c@Fws)@Q}Cq zxtw+-CGAdAQbCk)Uk5G#js#jV+I|UK3tZ%_{&%Q?GVWerK%KI^1&jx-_E!J5&OS;? z8lpM=k20jc5Tm4A9u)!51$qm49-s&&gsK1gNlyMsaqk$cO*@(69RL4rSVrd3Y#%&2qrat8W z=mtzx#%r&u8C5`bDpm(CI3t&1x zmJXvu8Mh~}U*Ot2V5qnH;Y9g$%D8=i+kiC_*9Ix$cJo$0k#Wyi-sJp$QXU?G-9T2h$836ewQ;3<@X*0N&~uz!1VK$aplFMI##ewqF$?dC1Gq6{n{IdzXKYXe|sGVTmyV_x|m;C65Is|DJwhdkE?$e7GU z#^kZQF)7gZYRGtR49xRZ-(H}NirQ?5?4$kzycfyyQChne62ZcVqAzziPX(F^wl9RY zdbTp|I$%WN(hrq!-MrNcLhY1s-I2}w2w?mtz=O-6kDZ`UH1GHwWP9x}s*o_V1% zZU!*QTm5vbT@$kU{R&8Pn|`T`n+{wMnL9;gY6cEPR^A;`NM;q8uTUe2FsdNno_My2>J| zH}269Orcsqx&zw*&n7EJ{`x7%ER)68fMbB+$~fb#{zIN1QH)>`uy0P)E<~Xm2q2ntCNZt?|Ty@S##4G`UX+Lt&k-? zZ8Zzg1e^(cM;SLX?PXHN?TR=}LMC$>U4gO6xNU%AywzzIK_<$$Er17rZ`9cIcwlQ~ z+<-K5GO_~hq6X(rWb!@$Y+Iu|Lx881as9m2b0BP90o;TH?DBs8Sfc=StV*o;Yf z#s5*p^#L5UDMy@BlyT>TE-2&H1n!}B<*2YLwJArOLBI`>8IzQ2GbDgpi*m#{3W+<_ zOthN*h1!*)!XCh+$TUp$-2_}uZOYLd2}akd92LF|OpEq8sS46r3C%JUTa+lmJ`q?g zrTtsTY;+6o4C#BQF_FESw{F;T?$ z7>Q}z0lb(Y!$@V^9)+Y`p38yFQ?AWJ#>4>+0`pVq4F*mOlpjudTx~HjR*xpi&qJI8 zAwEM^mPyE%q&W-v5KR~>(;fuG8flG^kbAcL% ze;*#Wz5wXwt-dp9V|%M#N0i`&K)Zg?*_aB$2(P%Oc&pD&o>}o$f9S0qPI`V?D{!c{ zdR%f}5qXvdBb)go8?wB~ab%#*B4CKO`m&^l&s+T>vXM^CyZ}2Z+i8g8T;fd&;#qy8*u}P_HfcQA+vCuykV@+5QrQQjDae!F z>U%Oe1eV(T?M}3TJ1KDKooE#Rc>zPS+4{hb>%8@*DSub^b6yAD>MxaXw^BWkb^+Vjv#WYbbpog^CTGw$c_&QEB89WT`Au^9cQSY>IT#hJ5?gOfmUGJj)ed!l=v9MFRd85tesfe2|G)vAwHor^pR==r?dzzI?ksfiHJ!&63{D3 z$PL{B%t$-Cu8bQBJQVHg+M3Yk4{)oJFC_`dvj^~O6r-u4BTTGe%DBFWa#nuUKsIQ3 zUa`f%@Z`6r#JB|c%A%wjaTj2wGHxQWbti`{NX1MJotMH24)| z+|9sKNI-QHx16Vzn9G>gSYk zQ>Z>C)C0Iz8TT-9B622@N7x_na5e!Gm2m@;D@jL~SiPvv+weJXh_`xLS_N%}q$WCM_Ov zTr??7sO6po_yw}J3cX`{k)CbbAK9;-A1~}QCRPs513P6YM}_DV+_*aKu_`b{8P^^e z${nR3MVSd47$t<(gtz)1NHo8mJ;nHxoVU7(vJeg=J?|@vk;=H`(tA3)g1iHq4D|6< zzg)DDxB4_B7pT?`?WH=}6rnRK$m77S-s+;KNUQKxPe#tJd|YGG*D3B+u0@{5kd>m4 zM=r*@$cfISbjCp8`$z!rAmE$c>hblQgZ5TmjC^tSV+E?uL`<)tjUET>B+MXRu|7h~ zk-pyQ4;sB;lzFi|k}H)r$$X4t%YNTm{aT~uPD_=R-<0000 + + + Adempiere Root + $Id: web.xml,v 1.1 2006/04/21 18:04:14 jjanke Exp $ + + ServerRoot + 1 + + + AdempiereMonitorFilter + Adempiere Monitor Filter + org.compiere.web.AdempiereMonitorFilter + + + AdempiereMonitorFilter + /adempiereMonitor + + + JnlpDownloadServlet + JNLP Download Servlet + jnlp.sample.servlet.JnlpDownloadServlet + + logLevel + INFORMATIONAL + + 1 + + + StatusInfo + Server Status + Adempiere Server Status Info + org.compiere.web.StatusInfo + + + AdempiereMonitor + Adempiere Monitor + Adempiere Server Monitor + org.compiere.web.AdempiereMonitor + 1 + + + JnlpDownloadServlet + *.jnlp + + + StatusInfo + /statusInfo + + + AdempiereMonitor + /adempiereMonitor/* + + + 15 + + + jar + application/x-java-archive + + + jardiff + application/x-java-archive-diff + + + jnlp + application/x-java-jnlp-file + + + adempiere.html + + diff --git a/serverRoot/src/web/adempiere.html b/serverRoot/src/web/adempiere.html new file mode 100644 index 0000000000..eaf1a29be5 --- /dev/null +++ b/serverRoot/src/web/adempiere.html @@ -0,0 +1,242 @@ + + + + Adempiere Application Home + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + +
Adempiere Logo
+
+ + + + + + + + + + +
Welcome to the Adempiere 3.1.1 Home Page!
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

+ Web Start +

+ +
+

+ + Adempiere WebStart + + +Install  Java 1.5.0 JRE (Runtime) +and press WebStart
+ +Check also WebStart Details.
+ +If you have trouble starting, try this +(or via Applet) +

+ +
+

Local Install

+
Adempiere Client ZipInstall  Java 1.5.0 JRE (Runtime); Download and extract the Adempiere Client zip file on your Client;.
+ +Start Adempiere via RUN_Adempiere
+

Web Application

+
+

Adempiere Web Application Login (alternative)

+
+

Self Service

+
Web Store - Send Request
+

Server Admin

+
+

Adempiere Server Managment + +

+
+

Adempiere Support

+
+

Wiki - Manual - IRC Channel
+ + Forums - Support Requests

+
+
+ + + + + + + + + + +
© Copyright 2006 Adempiere - All rights reserved - Adempiere License is GPL
+
+ +

 

+ + + diff --git a/serverRoot/src/web/adempiere.jnlp b/serverRoot/src/web/adempiere.jnlp new file mode 100644 index 0000000000..858d1e54d3 --- /dev/null +++ b/serverRoot/src/web/adempiere.jnlp @@ -0,0 +1,30 @@ + + + + Adempiere Client 3.1.1 $$context + ComPiere, Inc. + + + Adempiere ERP+CRM ($$context) - Smart Business Solution for Distribution and Service - globally + Adempiere ERP+CRM ($$context) + Adempiere ERP+CRM + Adempiere ERP+CRM ($$context) + + + +
+ + + + + + + + + + + + + diff --git a/serverRoot/src/web/adempiere_logo.gif b/serverRoot/src/web/adempiere_logo.gif new file mode 100644 index 0000000000000000000000000000000000000000..2f61d547e1889f83546e30efc21586a22188107e GIT binary patch literal 7475 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 literal 0 HcmV?d00001 diff --git a/serverRoot/src/web/adempiere_logo.png b/serverRoot/src/web/adempiere_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..cd78410b72dc3997c3d1e0a84495998ee51fc107 GIT binary patch literal 2176 zcmV-`2!Hp9P)!Q` zodUk1QFttqPTPzii-(G_OMwS~eB!5W;GQh{oC{<^9&k6X$d@H?eJGi@J&v$mpv@MRuW>Q< z0I&}j)F}LK24k5LS&+9EP#SCApW=cTyBOFETu&`A)#NtP;ykblSOKgPV~+xZzyaV$ z@(;=aEuZte22=;a)q_6*>;mvk<7>c;)F)@Ab-lm`flmT=i?M6O*oCtl&s4}NRC&aI zCY{7X8iiwGY!3J;uq<)kObKgL-vwL++~!HVwAq17g-poPD106GHgE_x!T%GNS^V2K zxdmA6%W27(j<94Mxbdw7J}SoED8??Lg~%*DYuKT&dEg*#$>TDeOMy##-BO@ej6DME z)hHZmb|87+8sHHz_8Y)cZSYbnnBCkKm)`nf~NX+rx11tut=kDK#YA5w>Z$ueD@cP!e?e9OC8zf;jEzt zHy;kC)xwIy5M#FiTN()eHt=`gHDEVz3^?JhlemmvF>Zc|KbHgV!%YjlzzZ6M-xFiM z3#=wRVez#~qwsf4@RJH+Y!@zcD&Wlc%yjouXBZCvYjM$ja-LZZ+z#AE^Byt|ScibC zld?1Qe0LIO37J8oa2y!)xpkiydkQ!@l@XQ7oEXgnsEyk8`MP~gJo}Y)q+0-9 z;|6hFd!-m#Xw}hEygZL{$4jd(UY%J#i!WNa6_?B~STps7_AzaF<9xC^J1y`)dK z&H-C}`C(j0Fje~^@GXtPv&{`RJG5j;N{n5BTg81pj>v`nng<-ld8}bv`ZEFyXcV3i zWBY-POi%m{in&wz=;udfEo=+X_Vv7)&fVnuecW==#V$d{z^bULz>q!u@u$eEG@lHj7#pb4s4ai@+XVZsdHfrko$A9E_l{nls~H zkS&j|YO9oWFsEi)5yIG2z&EMguT7fo?~&jbTK*KGB{V3aI!HeFvu@iw*znuoP*1Abd3j$cjYMNb1abg6bnnxU@=naMr260S9bQ&%iJmCLB;z%$oX+&OyYjZ zXMRHNlC7Z5L#q@kAOo>2T+lpzc*SesHauO}?(H-~gpS7HwNFIO~dl&AIWIgl!BgNbs6iWR- z*%LTvz7n{$74y{+@DBOHZnk~yP^*IMjwKI3jQtF5n=tyl(#Mn*1I?gxWz4Q$KjL{VzCSZc1oTXeGXMSOv{Mpm` z!~W5%Wy|7xUaRpG&RMMS-{TDAaz>Y}*J$&Lb0t_udE0vMyJkCa`^MD};3l7)%n3T4 zJg5-(1n@U%b6)}OtX>*#CoTx`pt2G3E8w@(9@||A+%Xq2T`=Uu&?wx6+trI-M-bpj zF?K_{W$Act+X!OpGTf_J)eEW5YZSiPHW@Bd@&NpS?M=Azc5w*vj7H(^w)B4?lLy4d zbT{JmP~tf7HyVZero6EgeP8J0#qjU;-ikX3KLPw&qwrv+eO@f&0r9I>9|x|$omGBX zqi{I0{w^l+Vu-QV;x_0N=R^iF>E~i34=T=N1MbM|OB#i5w!DvvmAn{!Lw*hJsBBBm z`CatnL1lkrHSVnRKU($9$^Qj;F??9_F5qx02>c(orLx*IA0R*g0000CU}Br=z{QXl$H8{M_u3KWfDJye zaupIkE(AL_cKiq-V1yCa#1|VIn*bM(5g-JZhY-^0wc3Yvciz+Q?yCF8OpkhddsdH? zOzNCERnybcQ}yZZuj*I7>KVd0$A5+N@WT&x7>4l)$8k>QoX_W+&!d!fCzHv}L?WR$ zj#IAJ>yw=G{ms0mTrU6grkieh`Q2`NkjT3U^MMB*7_cns&u!bj%yFF4IyyR}Wy_Xf z(V|7jWHQh+ZN_IT%R;SI!|?Dhwr$&nLZPr%5QHZk$JwxE&6?kE&i!|xD*)alm=8Yq z;OcU@{2g7_&lN?HoOIGjSh{p6=FguGLdaoVYjScD+qZAW)~#D%7{(Z5?2emmy6LWW z1L_|X=1+d|lSSon`7XmSJ|YMLoi}eD&OiTrRf>d0xLP%SflwIOm*m(9zKWO6fF!(G0W)8U`1B%@}Ld>-9P|ZQ6udtp>}o+*XI0vRpaL5)jMHHJYl#=sbZ zVHnuFc{6IY8f@D}tycT#t+(EK73bVL7Ja72;-cUG{`XgpkB>h9AgL${R8_^Pr=AK; z(^?&kDhoiY$O8}>-uS$ra_Vyf2C(1VNYvHK>OZ=N$e0{m?WGvMfVYRVAO#-+T4dR}UP`{)BuPRlmAdHWn{WOwfM3j&#hjHnG&FRnuInd=q6kTnAP53v zS#AM~f)oUyr6R@{CTb=&?ka)o;g`N9gsTmAMk|aUXw2#b%1u#ctj*pLD6R0i#8YsZ? zJlKpN*;HXLhD8(S99OKGk1JO7f>MI@TSnk`42lSF+87o%rC^MM5e7ymIOoW!jiwv6 zi#Nxs7%mA|oKGO9iLL8KJp)k`K`BMCSZpbi5CYeAp(x4)gb$cjVXH{37);GHAoF(;Uu^PT`; zpv-W65#{Jf$++%kZ{yV76wY6q#XwHOqMU~Q<|Hy!b1+u3F{!&a?YInTw%45585pDB zLZjZ7N#gR8dmssP#+WElg1janr;4ar9!9GcIujBUVdf-NC={ltGO!lTITkEfu<%`j z31D7$)e#;$1BL$CQs8%cCA_&`$M%BJm{0@)S)h;wa^ST{kyS>|>4D*aQvvT^ zl7~PrZHX8caZkw-X+>zg@B2PH&x4{Ua9y`G5eCCSDwSF=7Z^u80Syli ztG@4ND5cX>8P1eaBq_t)A3GikvkEvz%Y-=xI1Xe07_C}Z)}08!X}p(DNXTj8A+L%! z=SWBtia?;-UhCNC=xFPvhbNz)lq9L|U4cmm>GM19PNehS6_|!$ba2k6 zK@2xA?8ga$fcn%pet7vZ{QU<9aYCndK&3#dzQ69ka(!e};lL!s7<6YQ%tKwjQv%eh zRXC12ZM`0}H5|$l1R-_Mj?a>gc+5;DlOwV$Pm3jnp$5mo@1qtUw1H}yO1bBZ$^RPcv#(+!Lz$cV3dP{!1fp_rh|-nAeE^?GaA z3EByu<(CG2D1h-J*F=(j^A+DDaUK0HKbJ$tL9G^PuwIFl!Zb8rfG(iH!Y$RfDC+N#@OhQ@&M*w z=6!vATolDZIFJ!GnP|*1{NC{JFmzpyHA}`AWP#%H72VAVsIl^?S}va3Rl;xHnL@>K zXEklxYuq%;vJmxyH+Mpc{!vScmJerLK&}ew6R6?y* zLw9#~OTFPx+tRKCR?bi2?a77{zirCE37rWH6-|s(Z7j)an4gl-nUFv!;QJg^+e5Kj zMV8u-MWO9YjRvLzpVKgmy>sF*rw@zbUdysh4FHA1U12buH+`ErIF~Vov9U3TqKJGx z4_TH09N4kE^u#=N6;1e@H-jFHWs=W0c9%?yR_&J0>RJZU#KDrB3{jYA9dS@2?ew>7 z*|K{sJmwVW=;-KpEa-eVwKMN%J;;nPOiWB*cz76vLIIX#fpdGu!t@Z@A+)FW-Cby@k2(n3FP>ELrl<Am&VTfZQL2z(CDb<-mZH()%exb}Xdn#`icRAVe9+j>#a$yPqq~*C4Kq!1$Jf60wY>mRgb^*LTeke1QtC%{3B#&sH*OsaX!O0MT?t$`kb&s|oA=wZ!k-Of zG+13I6rKd21$_dLB!sAk3-z#I5<;Zrmudi10P1bG-8MxjeIz;t#+h@w4j)cl+MUGv z2eT;I9PgCeSom=uqg%{D8y&|fKlRj8|9<@O$1BY~X`xahgvf`w6?{lALnf^Pki)sV zyIX$dnP-YBb z^q4DGu9R1-SRqT2Bm+=FSp}dRib@YU0TDt(08%qgtIwM^PZ}H?6!-1hR}lnZz2|v< z88k9%>fwnAnEtR<+;|JjTpotn0|3ZK1Q!k}*i*Jquo&j2m1z@EczGJlp69{yJUEUs z0^p|u0|S!p`>nBl`}Xa`wrv6+oGBs1<(#`{lV*k4H0uChxD@mGyx8C0FZA{GiM_qO z;@-V`?->{vxZHK!yytmvT^F9`wVpK!!-!5&0AgWAL55)lpFO@?g5~kXev>S;g&6+s z0H!F4_w@Dkao2UJuIqwnn%uT+?z%39hK6`_u@SmPEYP+vV?efYB9Rb!dU}NJ?rx#0 ztBdAxIVuQ(t7+Og!!Z80>$-yLx^P_=k|fROOtcQnd=&6t$_x)dA=CX=DLT#i<&Ra&pt1zp#pT;7Kr+a(R$qH zXZxODtaKa)mSt^SvSi7f%|{H0rfD>pOwx2ZO*5Gc&1SPSpU(@~Y&P6?F;t38u}w=e zl-V3;+vapS9RQ?>M1rWQN)$yQq9_tU5Rl1a9s#hVTCH9d-DnHl)oJzF6dP4x+_9)y z<5USWe%)qS*8W^Bw@%YE4?ycCNs;yX4z6IA%yeL^YPP^ zNeBr(*ivCA696QUNYDVFBuPXRMM5cUc@K;+EMC0$+lfTt)j*xLZ8xIchoPvr$6Df! zN6%?@+%!#CmSu>dczZsdFA0LcJ#j!4Jo~pTCqOiA2}9j4?DP9`3rXziQR0uOt$QC!3vsZQCuiv;%pl0NV`<(c?kgvMd;e zF-|GHW!}7bJ0wZsf*^3)wz=at0aPCHHi#&S2%xrs831ircdOD=$rFi$vwZpTukYNsb62fa`-|rFp=nxMvkjYikPc?@?aV!F z+J<4gE(pRmve|4&Q4}uAGV?r-o2JPv%WBQwp63N#1rM&#j4uRV63c}ULOJIwJZ6R= z(#QpZMv@T3mJ%Ju;g)4_(=@qZ7+h6VrYH)@<#KFbVBo>Av9UeHV)4rW5}Y@F0U?TF z?06Xk7zh4fAcN8{4AkrOUndfYe@P?~uA(SRQ4}VMA}f_jtX{7(!!TM<9mnCW>+--? zj@-yNpkV2h{UC=(@hsb=`Zj+3aRnmRSH&mSt8hmsz!1Wwlz32S5$O zV1b_*g35#4foL;g;I_yydk)9G}Y$+8?|P6%P8 zQi)Y6m1#h0wHgb2OT#c)rXN5>#LtXjL`c)0oB@xdJ8AmAue*jQi3~_M-<#tbhdk$um9>YvAQEz4+kKd!vRmFQaq7JP)U+@rc$ZD zDVNJ98;0>=*L6>qBqn+o70*$+mv0zEl%vydVQBz{(Gv9ld z2;v8)ecE$gs;&RZHK`A;xq@~sxIO0jJWJjCzOb$`!H9-*g zn*VtHeQ%A|RzCQb=Y4;mJ7YPH!wkdV^?IGx>-846YPHG(pyhIzl}aVQR4TDzvB-+W zqF*Q!*woaNUn~}VG<;0IP4LHr4koX;Ra)TGRdEf*|mw z^Ua^yK6z!$@|OJgg`JPxaPGplgRxxKbzZO6S-oE8A)qaV#;BC(x*nJ}!}Mikoayrb z<^iu>QqKecR+;!a{r`UJorYQyx5_7+{R~l4r*qeS8~_+&p1psUm9*T!lcm#*wD z5<<*GA~Edx{>T$s$2-qk(p5~zA`3OT<@#*iTTCtQ%^E3H@`JJb>eUPW@zs6h<1ZOZ zZl9Q#@B^67?XG0A)Yg-}ZZ;Ll!$9|1lIGWRz1o1vYqc7~$`5suZ5t7I5C=bIt z0F;nS!aZ00OOojDis!(VU}&$qb^_kuq7t1ebY*wx1uog;N!*A ziLYDq7MnXO0hcJ7YdbDDPQK&%)%n*IMImn#{oEa!_WzHGx_c1^B#FLII&)F>ZrA7P z%lj&y^chc60()_1>eoNI`aM59VP4wgoa4InFa6O|?@WHi@mV(kyq>i3?Ai|uetY%& zM0vxOeScoEyt6iJ-o5$KMe^?3pD$eW+LU*L$I%OLrcq(zxvKk(LANkktJT;GtpCKl z*)zW46Y)cAE!kQ}OzVCBl>pLJOVfo0tSeCn9Q9HiJK{7I7ZxC?T zX5^IL4%>I7$@nM#c%eG7c9ZpYhKE%$#YjS;dn-0uy>VCRO8_7cRP&m+sqXlLLsRvS zUiq&ZbB}%Ml)t(8S3BPS)Xws)1aNbzu*q;)-+0Zw=u3~jJ@1Dfnty}Od=3DWYK{H< zM(?bb3s?tmN2UBlkD<$C!ue0@UB{97_D_s>>C~?5zz;xZmiW%^jpBr@zwh&(LT27A z;H<`7>uCtm@kC8tqh0XHZs(0BuZbwMRp*>Hl-cz7o62mWV*Zn0{uICofCMDerB;k5r8^{?Dl ztvqgc$|_CZzrX!-@$)%N@*f>$C;V)ub2k9t+LKbB`;+5S!(%#A*E}`(qeO=fS_8h`)`!ws{sm)_|K)e z{k+c^DI)n9p9q65i6@+I$;u)Snx$stU$tMX`$n}|W%T^deU<>=R{ddao7;#eC6LVB zOeE!l$&cT5qW;s{-)aMNTGWU&71%K2TH(&xJ%7Cg0M$P5**;~(MOQYJ8AJv+0OqR$nX586M4)EB{mwHL2j9`D{)_xk}LO}*dgg@SF1 zA~{DB-+q)NM_o?(7t+ee*jUY{l>wz(W*0_G0Fh})mojoEI%IrOeUOVB5~${0gcs7NPQlW76AaC+Ve!AP+)F$ft;o^ z$2sRjP5miRQ-P&AoO4I$=vf5djTlw>(_l8$$pVWelm;IFky<~J3KR^0aGL9-^0M=K zq$Te;7y09E1EFac<$v=x|Hm%=p%b@5N`U&#<`eh)=EgiR?A<+Qty*oq`FllC$kWL) zuK)#H*K^0l#{6ak3Y-u60Kw$sr0+}2qm&Zg>v8}Qx@i**SpbOot>3#BU)?$J%Wa0- zvEQ0^F#x3e>bCu?<9q<9)^(54%#csz<20{+-^x9YeRqmx5+$u?UV7`}+YQ5DQy=*9 z9RT3iwrg3IkMSKkXZ}7yg#~_b?3UWSU;0_%LpL19ea|tU_`yh%M_fi!nP{4MGjt-f zP7J{W8jwL#xmz;V-g!&-W-z1jZ`FHlyxkT6ZR34E#vlLBE10O9_x9jr&jH*5NYeE9 zbu0G&ZnM+xF(`oNd2D=q+;2X3O1Lx#5Mckrq;FHlr@*;*MEC!|`Ud`@j5H>Z)~@ z3tfxWKoCv@XK!#@zt7wEmKB^6r^<${#xL38Vt3*2+-&t|RE1DViJ~a2+*~>mS(b?? ziotKxNpl;RgtjM0bOp3|#iw5Zc=^=Vet2PXxypkGmgjjv5C)Fpv_gVGb{r?_8^%o} zET$fpeI9`qyP?K8XKjKE90vl*(K-p00Z0yjFvf^!n#43s0)RAsyBNT11)Bq)lu{D+ zXd(b$1pv-5Ha6xrA9x5tW;_&}ZUOaue^y632I!$codL2f%ow1-mqkGa$EU%I)MteE z9LFJ!;}Bif3D5|+1cB$UDq~?b)7aRU7k+vq6fO%p)CU7On>wQqgV(VjXN4I88ig1J z87%`~?Lf|m0irR~kYH;#bUa!I0A!5vzk@NOTCIiyjXb72RG`siViaEM*ukEVJEVWy z3I&hbjgRf>vw@7pP-lfWOX89GT!F@cY^%m_I;7__n-@B;N}8^|Lscbad5zF(;Lu@4 zK}O3%1sc0V#386LoQ~+f;F`02I~BH5CfdA?E79h4kfM!_0~wS@s!nz&YMj>CBZ1la z@Byet3UaO{4jg@T9yY`v9hHA6Y>ugO9V?Fn=)ph+<-veXTge=0I;wy97_-YcS5?ji zH4bFB-VR{!nxQcr95n#P^v_5S0ynOV)(<5ejyjLngbyYCLI1!x0vBixaV};a)H#l_ x@(z~X)&FU4HDcy6=U^^U*kl_A{tI;E{{ye$Ba-+_V9fvk002ovPDHLkV1j6AV;}$k literal 0 HcmV?d00001 diff --git a/serverRoot/src/web/adempiere_logo3.png b/serverRoot/src/web/adempiere_logo3.png new file mode 100644 index 0000000000000000000000000000000000000000..679097b795c702320f0ff3285e3449c52e1f52e4 GIT binary patch literal 5712 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 + + + +Adempiere Client Applet Start + + + + +

Adempiere Client Applet Start

+
+

After the Adempiere Client started DO NOT close/use this window as that will crash the application!

+ + + + + + + + + + + + + +
Microsoft IE
+ + + + + + Internet Explorer: No Java 2 SDK, Standard Edition v 1.5.0 support for APPLET + +
+ Non IE Browsers
+ + + +
Browsers supporting the EMBED tag
+ Code commented out - modify source if required!
+ +
Browsers supporting the APPLET tag
+ Code commented out - modify source if required!
+ +
+

 

+ + diff --git a/serverRoot/src/web/error.html b/serverRoot/src/web/error.html new file mode 100644 index 0000000000..da01ce500d --- /dev/null +++ b/serverRoot/src/web/error.html @@ -0,0 +1,13 @@ + + + + +Adempiere Server Error + + + +

Adempiere Server Error

+

The Adempiere Server encountered a unrecoverable error.

+

Please notify the administrator.

+ + diff --git a/serverRoot/src/web/favicon.ico b/serverRoot/src/web/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..14c3ee6ebd599bfc8a5b055443b3114adf0b6b08 GIT binary patch literal 4286 zcmbtWYfP2d75xwgGuE_|n08{u>1g`MOgr^Q)7DIDUx`i3w3*swl6Gn{9Xpv?Q)6PP zrI6I2D8Ax*d?HeLDau1YMYwRe7vwE+fs4HEN_>v`YxjlO~& zKYsisF@E0J*%{p4-tOSjf8)lDJG9Z8H*d=N__wdCt1FupMo#aO%koE$9=$#?G7`jR z;H_J?{s(I5(xo%%LJ@QBhe_Qvg~V zs;{sAa(Q`q*0pQb(9+Uk;*u{LQ+bdt+n8h6W{+u(xJ~8Z_xs`Tc(T|(%UGX*Q#FW< zjSVO$DA-n1R5V5&X8lxLVTl)O5%M?DG$w0lp*8yqM{%)v=*gUpUA=ns5ZBF{iht?S zrEfZ&&VFidFcgE<+nB;)J))tZ0piD;g7q_J>;XLq-5~I zg$v)?xN+k+g;lFo1#&Fb?RG0qll0XZ*08~=t*tfRtE#G`B`PZ`$F0nrhN8>SsHv_t zoWzlRYhn=xJ9kNg<@4FsjKze5Kb!E#!+jKiITs)7NeM%a<>Il9`!ld}+S0rM2{xAK|ht z&G*8>LR`6W1^M~;kY0|I!^k-L7dSJ|q98vHib34OQ#_=nVvvr?hw`NLr%#{$v+#)% zkdcv*P7Y?B@TIr7OJn&_oWd0*Yt+n>?^0(HvLio6e)10F#D~I>5Qgle1ITlvoB84) zJrsk@LHP1@{`~pm;Nai@atTUJP3^Yv6|;@4m=%w3wMID*HL zY+UK6d={l_L2l%SC_W#7ikv7kHF(XqzKdT&Lp|%H)Nr>sC>QcA4H@(Oty{Owr4I9$ zi>DVaUNl(3mFD6i?5ZjcuDKFWkQjoz=rzbs_&cf!&LZdN@8LZ05vs2wF|RGOR$~+Q z2k}ttNI!AV->O~KpYRbL9{$>yGiT;Dx$; zSc>dJzeRD%zlr}l8&u&_P)Xm>Q`quB`!j5Igk6RDdf7T%P*avV$e}dZLRI}e} zf1A2rHv4^OYOFIH6q9tw&dx?!S{m5%kJqhR_eJVH=hUfFeQ|MdNJ>fqdjUC)EM$K6 zKJrih12x5IX!MrBU++OvO(7aQPUJ;>1ZU(Q;XbtnCCUFnYollU4D9OaM0+du;)ZIp z_&@Ju>7cq6c6xd`5)%^<6&2O9bLY-E?2%a~Po6B~yEqwLoH<#j^Hz@U8`4#=wfekh zZK^`?Io3kdhbV|$i=xC$C{Nph8g~rp%QI*f;qw-uy{*OMMY&MDs{dH)20JDu28RwE z%3r^Jy}5ryM@NSqK73f5kdTmIYEONtwc5KsKTo?*5cfCaMXrLs+KHkw8&DX}-b?x? zuBLB6wKEbvj|1Hue)RDCx9l|E^+e)+$aA8$7jDndwnr(_jaSJ*@G^BC3-r2=)2tu-bukbDR{n``*erZIijJk6JO(q6T%-ac{S1j53?VwW#p zKJC4qvCk(5&ygcXbUqk-@t20uKzhozbXUA~>~{Pb*LTfR?5aJpS932_-R<4Gw{pXV z4fE}or+YFFZ?P7-RVUgnbQe$@@@Ln9Fs94D##JYZUGv3Tv2%X|eRhS0hQ2i&=F~Cv z#}d}bb#YMM+EXp6{?z|2mkV51 zxnswU_jt#6X(sto*1~cz$+p4~=BZ@3PKceo_A~ zIxi(9#p3=HL|e!+;~m!YNgnQFmq@>|fg)AS?i|EIhHSpAK8r u8?ph*`cj)^&5^k+D{zVh@Le`(yzSY1t(D1-e2!Vxrv}3v2n@erkpBzBG36`( literal 0 HcmV?d00001 diff --git a/serverRoot/src/web/footer_bar.gif b/serverRoot/src/web/footer_bar.gif new file mode 100644 index 0000000000000000000000000000000000000000..f55164bf37b36634503a5ede1381da03cb6eee8c GIT binary patch literal 64062 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%0A4R5AfF6W(~y+n9a>oae=Nq<~m?2=}0% z@8EtYkJCsjTb!7#`IoV}TcKVT?{Hd+x20|Vt&u!~9=Z^?OzMnm_S^Ks)EZfEBYSmp zK9Ox^9r^Ys5N&#jWaFu>m)wtpDaPKQ=PQ~*m4p(e7C@}#HpD$^LY-x~8-Wm`6#l`d z*`jru)1nQVh`xK)?TfX8%A((%^T@b-4dJ*fdEo#nXc`-x#=B|Z7U@Q*3>Pvj#epL>PR~NCzmUg~{RpZ*_N8^?tD%K=fDD~y zc5ZXmdf!du1JpEGVomHwS;R9`?R<1UFAS1PnR|m7M-YZ*b@3@l9>3Yl5XB>vHQjHJ zLBjGG1Qt5QtNYW5hEQqCayp9Z$4OEX$VxU&Dp>Yg?Iz1USahA3`_qlWMKA7hqCPDd z(eA>R==K^CUMOLJlYFPc(SiU`Hb%5A??N3lEsL(TIkb?TbskwWYC5fHnsGFXYFWtMRRObMWR zaF-@@3;V63=M~&nh=072GY#ZoHD@7#6{xDn?^}mi9j4?}>+%p+aacD@%TFtOP@tpCw z+8*d7G|zzG5%qXrUY?ibKpXpclNx_QP~WbP=pRE*&FI0=y}4&q#)4gP5HG=I8}%6J zKfPpSg@2{9q_=Ku|90IVKfAZlj3}fq1bp5eMe5#2+hG=OT8eu3Oq?#w`W)M*e5r;M zuj14*1EIT@J$AvxF@i4bNOA7|Z-I9_(oHOotEYFGr~;#+)1Z1}#>rq)VfFB~g17hot!?> zv*#=)c%~N^_;-}CFZ~)C?5NQ^I#?BR{XPEa<{h8+j%EAT(G2ue4D)9di`u1U0kADL zkCv&XZRH=w%qds`u?@J zn&H3`lYZ-r@_c&xmdtyJMB5g>N%V)Ik+-5og3ui9qho$}g0C-Q+{8`%1hjNY;vpL* z(&1%^-v(KtCkkp8Vj|p($X#yngrGTP3#_bIM?7Fg-dd&ZA;~qeJ#LpRmnuSW!N%Tv zYzwZAO@%+qt&4_kr#w;5oYEc|xupw_Z8_&H{!(VSgi^Z29ibw?%U=k&4ZRmTlcgof ztzr4nqT;2sr7KeumRkTy;8WNQ^P?<(j-@<~E)6-t<4x%?`;ToB_>AdcVQKe6z6@2)HTDm}YptihQdl$2|$W?kDR z;=ecEWQ&S$IhKw70>=R$b?DqNxMh0cz3Gybx*(^$Dz uosN68VOH4l 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 JEV=6t_8(f{K05#a literal 0 HcmV?d00001 diff --git a/serverRoot/src/web/header_bar.jpg b/serverRoot/src/web/header_bar.jpg new file mode 100644 index 0000000000000000000000000000000000000000..dd96b2ab7510fb78f647ef7f8e9868ba766ee266 GIT binary patch literal 6757 zcmZu!3p|wR_kU-+GQ=8;+70z$2M@4x@4-k#??=X}rie9w8_&&X4&uuLZiLZmmf zgF{=!nHbB8R~jK066I4EjHW#fhZAcf8b;I7!nMW@=`>cbma&PcV5LMvJEjk( zbR#Vz%pL3VdTSfrCOZ3Z=ZD(;7OVY@hd$W9T3%7EwfTd8to8AzRgsY;^9|b0toSZf z;iQV+GtK|c!Q-$ezDqq;Be^}Z(z&9gP^Y(Dyw$xm;_{!97U%u(=VI%!UXOb(gVph+ z_~0_`+=IR9@PP*>%N65J4o>L*_iwp2gF_(?mxTxnmcemdBuYQWxh&eFyw1BeyP{-Q za!OU9e(wQs^h-g+<=!Dc?+Mx&AHbcp6|%PtK=#|Rhjl?ze0(yn8+h=d6v5k!#vAr7 z5tjj*kn$d=rj!xy#0?t@KeboTni0NjccKNE-M;V35``Ygt{$;#NeVjlj68Q<)Y4dg zKG3As^w3NrZ#+e**xUmcGb} z+b3OXua&(5m}PF^e-6sKHz)27djMLTOQ`sxCvev+z|oDhQEzvnEieMu5iIG<=7mq! zhKpjX%X%K2@0$8-|FtU>^iJQfTbEMnmjp^?>_6WLz6#Pe|LE!&skO$PzM<4<&R6KP zN`1PoXR}4e2bA_eLdD-#!`Gxd|M<#D9v|QqKCHSg8Lk;dbgd+VrC9C0-*P zqz-TX0*BSqq4eN?!S3-p3Q^79&X3dkL^Z>XGGRHMdUm--eiL{O0F$9K^;MzB}8 z>e!Ub5o|QU(6W!v2-3-3_hLIm$8P~ln*xdGWX+0DZlh9u7^|1yeFZcUkv@nfsGd#UJ$~ibw7M*$5yEglX zp;LtA8}dw9?fI7+q*$rySV?&+t1td_xZO86Qz~oz#SS4pkr(c`+XWRIYq-j5CEjd( z1w8)MAIq3`@VyFT|BqYMK_8uUsjkq#bhmxI{JC@AU9Of6G7_WyUGUkBk8w~bBhkLi zW*FHKVEOGx$MXfdqbz(T`^85ko%|T*AC&+&`JEAt-C$1uoHI%OYm-eRkadAF@F%C7 zfk5x7>|#Tw$mwSzy;Q(}*&nM|T5&kespJ8y+XfxMg%{(j!_SnZ_>rN`<6A9NrAzl+ z8M3u%_vL5iR@_~P0!TW4$ouPeAv%_-uKq+4@_eQ&RrzEWLO-7=EhJ=Djgd#Hp)He# z%(*Sj4^@wJ-74)6M_89$W}j|EfHklBH|ghc_W{S|D%X%G^_j9CQJwbaH8m?Rt8 z8edlJ^64k>f~06YJU(h-bN{B9JGuK}PgP4h8fhu> zx%?k<3t;Ng|+(nbkS}VWx_xk#_oyfvb`V0!VOw1X!tdApt2`+wvff+N?;N;z%kq zJbqi=rZMx@(IP%Imv#DHp~Sk_S#nR4$EkN2)JAGZnvYZIKQTAfD7H?qnLlQ~(sr3K*9;EP2_Cxe82_9uH{dc%IA5YWc zaD*JOJ_7cco8!>83cz*XNfrk!9mG|cC2(uE>jl|W9wleLI8O)$u!Xqbq0 z7`LYbnmi3aQF1noBe&G!2)R}Yf{*Lc#gM3&;4|!@S0Ee8sg0!}3N$M*(AMLN>>!A) z=Se6qE|Y+;XV!_=STrI{%p{m>5M0Dz=?S?^*vT0p)R1Gi0nLaDWQ&*#UDiG?;3)uU zL+NypAM~ALDL_vG#sG;6^)QU+tRhl%F%-uH2ds#}B!ifXgg^@yq0TZuCm1gdCv6`g z}U1%u} zdIhqfoOU>(z|@5^=z6-Yrw0MT8fkOs91+cwr{ijNV?~iJ&O*Zpvy!fd6@}8t!G%GE zhGcMv^^7dg5Qd$@l^+H@J*A9dt0E=~G(@2!2I30+xiebuF!XjO|DZyZXnsk_GbUjc}9D=i61br3b_Mi=@F=5Oom2a+k5nF z&wRA!Y4(3pd!M(eGi}fF%F4Us?>45cb~Sq}{Uf}3d0A{q&XWrt_NZdB{g*AjyG>Dd zE<8`?jf+GR{-$Sn=#)F1ns9mVedjAXhRrT_nV%r@ch+h4x$SkQ2L%ViOWyHQS0_$= ztExu%_Vxb5;bDq5&h%1osYc>;&U3$)w{A<7jNcB_q#a0`iphm4&rVc3#q{C=%KJ)3^!JeZ zp=E_39)HO)h-j0w<0l7mJ|L|p$dKVUfcI!bhbQV^glwCXTIZA@!q}%+LPgZGm zF)d4)|0Ym12SH5P=}&ecF|ym4XLX@5XXb(~9kwS7;|zeg0_vQ7ciuPOdDOc`#00hv zDXMvY&QKpkhAmPCkVg>pZST>$_Q$VZCTYbahm|iqTJAz6J2M#eZBE-aD%6G6=M`o7 zOHYzVC+bUMlFdS^DZinEX9i*f?Z@x!0Qq~r|D&bqYP1Vvy3zrgn^jkPXCDjbQ^qPf zBE6@s9W)Dts=7j|>$4X6!wF<1!MqH7+Qz+A)j*mf>qW=fi=bKrobvXIj?k%ZpTrE# z{kHqVtIgG#2-aSmxzU=8!$r?x_%IB~P^kMB^XJDj(RaRXIYP0zIlS^4KjUu5nWagT%n0CuPGm$XboKrOw{!8JjIY#q520 zqZ`JH1n-aCqQOvgK2~e;UiPz^Q);h98gJ+g!t5TwT0!CmQ2Sw*Cfji!F3Vh^yZ}tS zVzUDcpeXC4xQ)>u?Fg0yykEicR%9QHAc~RiS>8Y$-+aI9jyk}7_b-qJIkxz?pO(RS zP<8zQSb_u>=C=<;?ijY~GA}Rzxd+cXWOG#y3S*L|oSYg~q{VzK$2%!fy_(UK7uX z^R>nMB7J-INLoR}Iks6t?p_1Xbo0Uh}JLm`8HU!;$6mi$ARtMvi%8JCi0tvS$=(q=BX{uy}XNMlK_nag35#k<~pK3XPq zowF`$Xpw8(M3kfeLd?vc?jFU>+U$*R9UGvc%E0xF?MJ8po{j4flW36lA+WkrI&)Qw zCYts1KLIGaGf?qnmu^`VgDOY78s>fyvJMW^A&&Yl(osmx!$S@mW}GvcO%-*tVP3Jx z>YZ;Pg~JAucB9#INSu%rhnja)Hg=g;==V`;G$g;9+FK&`Og36C(tXazHwQ6yCMZq? zt@pa~5c;z&E5AO~b83@8pYoDh=>6A2fSCVSmX)9gEOdX|c<04{i`I_$VjL8dNABiLS()jyk&2){ggpOA0yE0AJUYGNQ|2};?t ztMZ;nQrsqgQ{F9&TY=zGz!;kU|DY`{8$}y)gICC-T!)kIMzG0Kn>NB_`v3F5)#&p# z4gZAOVg-eEfs7FxoKNyW8cb7b5PLK{3waG#ZP?hg5T#7pj$|4Dm$1dQVBX|%3*}o; z2*O`dyC5Y^lBd@SkSrkl?)v?6o@C|YAJYS}rw*+FvLU6vcO&KZgAC=Q-KoL$OiD#r98J&xk6q*+18xeuz8zv z`;mA9>4i%2`oiiWxyqw#J?_ym$_A@Ft;=#Qo(>>av;D^@UEsao!oR!Jk$oH99QPNj z-MB%vBnZLfTsR#NMTVjPSnX+@9j-^dNR|P}*l9y1^Y3{9#3atlboeJE)@x%eG&#XH zKqg@n*208;G`SX+p1w#tX0#hOy5aBOj)QO5q>~3Ys!vF`D2#7lsd0rEh#YQ4BIEO2{cvGXPgY!-qWPI|2*|MUiB%sH}Q4IgP_202_WpOrp^cX~rb< zAJioxf2eVYnedAVenP>o4f2mrnBjqb;!$5_nUm1Be??8 JXIdk7{|gCqlUx7* literal 0 HcmV?d00001 diff --git a/serverRoot/src/web/index.html b/serverRoot/src/web/index.html new file mode 100644 index 0000000000..b980ea8473 --- /dev/null +++ b/serverRoot/src/web/index.html @@ -0,0 +1,13 @@ + + +Adempiere + + + + + +

+ + diff --git a/serverRoot/src/web/robots.txt b/serverRoot/src/web/robots.txt new file mode 100644 index 0000000000..991b78a1d2 --- /dev/null +++ b/serverRoot/src/web/robots.txt @@ -0,0 +1,4 @@ +User-agent: * +Disallow: / +Disallow: /adempiere +Disallow: /jmx-console diff --git a/serverRoot/src/web/standard.css b/serverRoot/src/web/standard.css new file mode 100644 index 0000000000..f60bed7ed2 --- /dev/null +++ b/serverRoot/src/web/standard.css @@ -0,0 +1,151 @@ +/* Adempiere Root (c) Jorg Janke */ +/* $Id: standard.css,v 1.1 2006/04/21 18:04:14 jjanke Exp $ */ +body{ + background-color: #FFFFFF; + color: #000000; + font-size: 76%; + font-family: Verdana, Arial, sans-serif; + line-height: 1.3em; +} + +a{ + color: #3465a4; + text-decoration: none; +} + +a:hover{ + text-decoration: none; +} + +h1{ + color: #FF0000; + font-size: x-large; + margin-bottom: 10px; + margin-top: 0; +} + +h2{ + color: #000066; + font-size: large; +} + +h3{ + color: #0000CC; + font-size: medium; + font-style: normal; + font-weight: bold; +} + +h4{ + color: #6600CC; + font-size: medium; + font-style: italic; +} + +h5{ + color: #660099; + font-size: medium; + font-weight: normal; +} + +h6{ + font-size: larger; + font-weight: bold; +} + +hr{ + color: #000099; + padding-bottom: 0; + padding-top: 0; +} + +p{ + text-align: justify; +} + +th{ + background-color: #E6E6FA; + text-align: left; +} + +caption{ + color: #660099; + text-align: left; + font-style: italic; + font-weight: bolder; +} + + +.menuDetail{ + color: #660099; + font-family: Arial,Helvetica,sans-serif; + font-size: 12px; + padding-bottom: 0; + padding-left: 20px; + padding-top: 0; + text-decoration: none; +} + +.menuDetail:hover{ + color: #660099; + font-family: Arial,Helvetica,sans-serif; + font-size: 12px; + padding-bottom: 0; + padding-left: 20px; + padding-top: 0; + text-decoration: none; + background-color: #99FFFF; +} + +.menuMain{ + color: #000066; + font-family: Arial,Helvetica,sans-serif; + font-size: 16px; + text-align: left; + text-decoration: none; +} + +.menuMain:hover{ + color: #000066; + font-family: Arial,Helvetica,sans-serif; + font-size: 16px; + text-align: left; + text-decoration: none; + background-color: #99FFFF; +} + +.menuSub{ + color: #000066; + font-family: Arial,Helvetica,sans-serif; + font-size: 14px; + padding-left: 10px; + text-align: left; + text-decoration: none; +} + +.menuSub:hover{ + color: #000066; + font-family: Arial,Helvetica,sans-serif; + font-size: 14px; + padding-left: 10px; + text-align: left; + text-decoration: none; + background-color: #99FFFF; +} + +.Cerror{ +background:#FF4A4A; +} +.Cmandatory{ +background:#9DFFFF; +} +.Cbasket{ + font-size: 9px; + display: inline; +} +#imgButton{ +border-style:outset; +} +#imgButtonPressed{ +border-style:inset; +} diff --git a/serverRoot/src/web/webstart.jpg b/serverRoot/src/web/webstart.jpg new file mode 100644 index 0000000000000000000000000000000000000000..aa7d0661077fb883a3d4dc0b78b44946615329f2 GIT binary patch literal 4577 zcmd^BXHb*bzfJ<78YDmzLQM!&%2Gl{l-_#+QWc~ZDWStEx{%2?I*Zbklea^fyzcbHy%A7eL-qX3$*8paHZ9Q!O5C{b5o-M%X zB0vK`2L{v7(9qG+(w(C_`(?jKF8Mrk<(JR6shwQwePt(}>`&&Z~-B3JOpIeBXf!0Eb%lVjxvgNn7H#8TE$HKWKlvbC(V2{ZcTj-@LuN zb?(SHAcQ5}-EaOR&-G4c7yWIUT@xnH2r^RA0-U#DB|h(9@tqWT%eOp4KJyV8IZ1rT zEvY)}ApOm<)ppo8D;}$nnDKa1k|RpgzI;>2NVk4NVJB?QX+L8ke__Q5H)ZpQ3)gle z6-wDL-@PTJRp%2H>hvx*(v%zSO9Oocy`OFZJ2-VB<^LTZgX`# zMT#u+WBTx|ZyS%en>J-%aMsVCBSb}!arnG9>=Yn!a2q`WkA{(+)jFg5%UsRj-ZpD! zF@*CK|3;0l*W2^GGHq)c*Of+YFSH1cMb1)UdC3*mcnt1sR2j&k&ai)kXVTc==Q(LK>aH zxs#lse?oYyam~MjBoZV!V1g)XdS#f}lCdF_G-G$GK;Pcr6EY0q()aCV{+UE2re-}# z+Wf6!(!V4Foxdc|zuBGtu*xjq|E)#-ZJ3?^>TerMdjo>ZH=#eHag_sv9}pLvvzUqZ zdQZmTl!U{@_jhZM53krhVfER*4jdeEa6FJ%!7hn*WQId3g~{8<|5lv z-3Z!0*3iZb(he8ds%sYF0_EU&J} zPa#GLwPP!1Y&kbZzO>{}#xfg!e0Tn%ev=w8cxL57=i32%?BIU5W%`^~>buQ^yHO(n zh2P4fWL6${R?woa728wVqjl%JeniPRA_+}j%EKGkBe=|q?v$NHdaF!tH4r+_7gIvuGY}f}P3=*6vVW;hyKtGW0pI_0hZecg0yk7~l6z0b$(4|;ZI2=i-vEJm8+U#p6m=5rZm8+b+acW`6x{XH>DFN}OTGi!JXeJD8033o{E}ber%lK3f5X9oX*lid!7M8M=HO zIP%l_M(*!2B8ZR1byrX}Z+sLn6_}rwriGA{b7koy2K|Z4U4yrDI*_@=`8OR9%Z*$S3Zz?59i2>5S;@4a(HJkrvkA)7t?5C}iJRkj zk-@9Y8{ocw3Rr06e4AU=8NYRGsUaj;;%0#x$^Ak?yu`mRF+39c9Ejf9bvSc0J!!@v`pqI1k7~kM|#|)+3L|0k+8pzJjK93+yFGk_4brW}*t0O_S~D>;A7-v64tIvXPhe`(F4@zxkSikwigho` zW_zhqAj1h94UH9{()XKfxrGk9dWYB7FtoK;N#B<02o#vWHJ-3PHK+*`b<@ zbS9+>;J^7)ds|XHjZgd8G{TfvhMT4L)jS2DR3|k+%hcvR`~&qW@9o*8_&9@}p|M8x z9-Fm-c18LnWP5=`Wzkal>33w4S(Q+hIFInPfN8t2Jicy}`_rcFEb1ZpY*$nfi4d-b zZ7KRVowU*BTKZMEwCk7O5<8Y&c;q%eN;ip za_P{Nt9V23s_pgW4()UOEb2qV7{|;aJRZc3FZ^JCF1Ihrt<||?T0L%K&kHi$?c(}T zVzY@wYtB?6AkiTeS~fqx;m#bK{C>D?(E2L5*Sb6~*<5T3m57hj4fSL2Vv>(uI%oEF>5aem>35aYd!lHGs7$VqHDYP?N zmh2ae(UW>3WTKwW63ddC>~JKcehFc|+=AW3?gZa&$Z2W>y5n|L#8X6HxZPrt6}xKS zxm!_U2(wOlTbQ43wm9N1Il5Tqxn#7T8&&Lb4-Yr`ko{d=dNu!LU5j+V&_V{-N%ZFQ zyXU1Bw2gcl)+Q>k#NcXzf zdstUZm5RpstqrIk}!A&~TssUL|Jep(>%Q2jP+ z8C-9Z(0$Z4xM)%wiMq29S|gy4>EUAHy;;?w5Tu(&1hxlA@`ryc_N*B#kES*5CDvDgKXi$;NdNn#|qUh|Lo(LGUxJ!^<952O0ON6j)@H5z3l-tG zU=24St*5`mH2F$KasN19^*!NCtGIAlBTx(KnAWRS9 zg^XPr`+p--M|;V)MLrD3s5yz)7Tea3lu9jaSgkFjx&6J!I42Pz0 z?5L_GwV%QlcUr24u6O?ThSb_K`>N?O9rB&FVM{ou3j$07#*MMVfC+%7Y@j=)48SYG S;Qw(vH*sh5P8QYa{C@!=5@VSF literal 0 HcmV?d00001 diff --git a/serverRoot/src/web/webstart_s.jpg b/serverRoot/src/web/webstart_s.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7dc44d8c2e1b60f1bc656abe2fbf2bbb60490341 GIT binary patch literal 1750 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 P00000NkvXXu0mjft*m;! literal 0 HcmV?d00001 diff --git a/serverRoot/xdoclet-build.xml b/serverRoot/xdoclet-build.xml new file mode 100644 index 0000000000..3c61bae1ad --- /dev/null +++ b/serverRoot/xdoclet-build.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Click here if not forwarded automatically

Due to Browser incompatibilities, here is a list of different ways to start the Adempiere Java Client from a Browser. +This requires that your browser supports Java.
+
Applet Start Details +