IDEMPIERE-5567 Support of UUID as Key (FHCA-4195) (#1963)

* IDEMPIERE-5567 Support of UUID as Key (FHCA-4195)

- fix issues with archive reporting on UU based tables

* - implement change log in time line mode for UUID based tables

* IDEMPIERE-5567 Support of UUID as Key (FHCA-4195)

* Fixes and improvements for MoveClient
  * Fix for oracle not being able to insert AD_WF_Node (CLOB column)
  * Add management for UUID based tables
  * Add management for weak foreign key Fact_Acct.Line_ID
  * Add management for weak foreign key AD_Tree_Favorite_Node.Parent_ID
  * Add management for Record_UU columns
  * Add management for ChosenMultipleSelectionTable and ChosenMultipleSelectionSearch
  * Improve javadoc

* - Test and add support for SelectionGrid

* - fix error inserting a wrong UUID when it was set before by a Record_UU

* - Fix problem validating orphans in UUID based tables
This commit is contained in:
Carlos Ruiz 2023-08-22 14:08:33 +02:00 committed by GitHub
parent 7cd514e60a
commit 5aba739d37
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 512 additions and 199 deletions

View File

@ -0,0 +1,17 @@
SET SQLBLANKLINES ON
SET DEFINE OFF
-- IDEMPIERE-3916 Process to move client between databases
-- IDEMPIERE-5567 Support of UUID as Key (FHCA-4195)
DROP TABLE T_MoveClient
;
CREATE TABLE T_MoveClient (AD_PInstance_ID NUMBER(10), TableName VARCHAR2(40 CHAR), Source_Key VARCHAR2(36 CHAR), Target_Key VARCHAR2(36 CHAR))
;
CREATE UNIQUE INDEX UX_T_MoveClient ON T_MoveClient (AD_PInstance_ID,TableName,Source_Key)
;
SELECT register_migration_script('202308171647_IDEMPIERE-5567.sql') FROM dual
;

View File

@ -0,0 +1,14 @@
-- IDEMPIERE-3916 Process to move client between databases
-- IDEMPIERE-5567 Support of UUID as Key (FHCA-4195)
DROP TABLE T_MoveClient
;
CREATE TABLE T_MoveClient (AD_PInstance_ID NUMERIC(10), TableName VARCHAR(40), Source_Key VARCHAR(36), Target_Key VARCHAR(36))
;
CREATE UNIQUE INDEX UX_T_MoveClient ON T_MoveClient (AD_PInstance_ID,TableName,Source_Key)
;
SELECT register_migration_script('202308171647_IDEMPIERE-5567.sql') FROM dual
;

View File

@ -30,7 +30,6 @@ import java.util.Hashtable;
import java.util.Locale; import java.util.Locale;
import java.util.Properties; import java.util.Properties;
import org.adempiere.exceptions.AdempiereException;
import org.adempiere.exceptions.DBException; import org.adempiere.exceptions.DBException;
import org.compiere.db.AdempiereDatabase; import org.compiere.db.AdempiereDatabase;
import org.compiere.db.Database; import org.compiere.db.Database;
@ -54,7 +53,7 @@ public class MColumn extends X_AD_Column implements ImmutablePOSupport
/** /**
* *
*/ */
private static final long serialVersionUID = 4379933682905553553L; private static final long serialVersionUID = -971225879649586290L;
/** /**
* Get MColumn from Cache (immutable) * Get MColumn from Cache (immutable)
@ -765,26 +764,55 @@ public class MColumn extends X_AD_Column implements ImmutablePOSupport
return false; return false;
} }
final private static String sqlTableNameReference = ""
+ "SELECT tb.TableName "
+ "FROM AD_Column c "
+ " JOIN AD_Ref_Table rt ON ( rt.AD_Reference_ID = c.AD_Reference_Value_ID ) "
+ " JOIN AD_Table tb ON ( tb.AD_Table_ID = rt.AD_Table_ID ) "
+ "WHERE c.AD_Column_ID = ? "
+ " AND rt.IsActive = 'Y' "
+ " AND tb.IsActive = 'Y'";
final private static String sqlTableNameSelectionGrid = ""
+ "SELECT tb.TableName "
+ "FROM AD_Column c "
+ " JOIN AD_Field f ON ( f.AD_Column_ID = c.AD_Column_ID ) "
+ " JOIN AD_Tab t ON ( t.AD_Tab_ID = f.Included_Tab_ID ) "
+ " JOIN AD_Table tb ON ( tb.AD_Table_ID = t.AD_Table_ID ) "
+ "WHERE c.AD_Column_ID = ? "
+ " AND t.IsActive = 'Y' "
+ " AND tb.IsActive = 'Y' "
+ " AND f.IsActive = 'Y'";
private String foreignTableMulti = null;
/**
* Get the foreign table name that relates to this column when the column is multi selection
* @return
*/
public String getMultiReferenceTableName() {
if (foreignTableMulti != null)
return foreignTableMulti;
int refid = getAD_Reference_ID();
if (DisplayType.ChosenMultipleSelectionTable == refid || DisplayType.ChosenMultipleSelectionSearch == refid) {
foreignTableMulti = DB.getSQLValueStringEx(get_TrxName(), sqlTableNameReference, getAD_Column_ID());
} else if (DisplayType.SingleSelectionGrid == refid || DisplayType.MultipleSelectionGrid == refid) {
foreignTableMulti = DB.getSQLValueStringEx(get_TrxName(), sqlTableNameSelectionGrid, getAD_Column_ID());
}
return foreignTableMulti;
}
private String foreignTable = null;
/**
* Get the foreign table name that relates to this column
* @return
*/
public String getReferenceTableName() { public String getReferenceTableName() {
String foreignTable = null; if (foreignTable != null)
return foreignTable;
int refid = getAD_Reference_ID(); int refid = getAD_Reference_ID();
if (DisplayType.TableDir == refid || DisplayType.TableDirUU == refid || ((DisplayType.Search == refid || DisplayType.SearchUU == refid) && getAD_Reference_Value_ID() == 0)) { if (DisplayType.TableDir == refid || DisplayType.TableDirUU == refid || ((DisplayType.Search == refid || DisplayType.SearchUU == refid) && getAD_Reference_Value_ID() == 0)) {
foreignTable = getColumnName().substring(0, getColumnName().length()-3); foreignTable = getColumnName().substring(0, getColumnName().length()-3);
} else if (DisplayType.Table == refid || DisplayType.TableUU == refid || DisplayType.Search == refid || DisplayType.SearchUU == refid) { } else if (DisplayType.Table == refid || DisplayType.TableUU == refid || DisplayType.Search == refid || DisplayType.SearchUU == refid) {
MReference ref = MReference.get(getCtx(), getAD_Reference_Value_ID(), get_TrxName()); foreignTable = DB.getSQLValueStringEx(get_TrxName(), sqlTableNameReference, getAD_Column_ID());
if (MReference.VALIDATIONTYPE_TableValidation.equals(ref.getValidationType())) {
int cnt = DB.getSQLValueEx(get_TrxName(), "SELECT COUNT(*) FROM AD_Ref_Table WHERE AD_Reference_ID=?", getAD_Reference_Value_ID());
if (cnt == 1) {
MRefTable rt = MRefTable.get(getCtx(), getAD_Reference_Value_ID(), get_TrxName());
if (rt != null) {
MTable table = MTable.get(getCtx(), rt.getAD_Table_ID(), get_TrxName());
if (table == null) {
throw new AdempiereException("Table " + rt.getAD_Table_ID() + " not found");
}
foreignTable = table.getTableName();
}
}
}
} else if (DisplayType.Button == refid) { } else if (DisplayType.Button == refid) {
// C_BPartner.AD_OrgBP_ID and C_Project.C_ProjectType_ID are defined as buttons // C_BPartner.AD_OrgBP_ID and C_Project.C_ProjectType_ID are defined as buttons
if ("AD_OrgBP_ID".equalsIgnoreCase(getColumnName())) if ("AD_OrgBP_ID".equalsIgnoreCase(getColumnName()))

View File

@ -21,6 +21,7 @@ import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.sql.CallableStatement; import java.sql.CallableStatement;
import java.sql.Clob;
import java.sql.Connection; import java.sql.Connection;
import java.sql.DatabaseMetaData; import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
@ -854,6 +855,8 @@ public final class DB
pstmt.setString(index, ((Boolean)param).booleanValue() ? "Y" : "N"); pstmt.setString(index, ((Boolean)param).booleanValue() ? "Y" : "N");
else if (param instanceof byte[]) else if (param instanceof byte[])
pstmt.setBytes(index, (byte[]) param); pstmt.setBytes(index, (byte[]) param);
else if (param instanceof Clob)
pstmt.setClob(index, (Clob) param);
else else
throw new DBException("Unknown parameter type "+index+" - "+param); throw new DBException("Unknown parameter type "+index+" - "+param);
} }

View File

@ -124,6 +124,7 @@ public class MigraID extends SvrProcess {
// migrateRecordUU(); // migrateRecordUU();
// migrateAD_PreferenceUU(idCol); // migrateAD_PreferenceUU(idCol);
// migrateTreesUU(tableName); // migrateTreesUU(tableName);
// TODO: implement migration for SingleSelectionGrid, MultipleSelectionGrid, ChosenMultipleSelectionTable, ChosenMultipleSelectionSearch
} }
} else { } else {
boolean seqCheck = false; boolean seqCheck = false;
@ -156,7 +157,7 @@ public class MigraID extends SvrProcess {
// special preference C_DocTypeTarget_ID // special preference C_DocTypeTarget_ID
migrateAD_Preference("C_DocTypeTarget_ID"); migrateAD_Preference("C_DocTypeTarget_ID");
} }
// TODO: implement migration for SingleSelectionGrid and MultipleSelectionGrid // TODO: implement migration for SingleSelectionGrid, MultipleSelectionGrid, ChosenMultipleSelectionTable, ChosenMultipleSelectionSearch
if (seqCheck) { if (seqCheck) {
MSequence seq = MSequence.get(getCtx(), tableName, get_TrxName()); MSequence seq = MSequence.get(getCtx(), tableName, get_TrxName());

View File

@ -41,8 +41,41 @@ import org.compiere.model.MColumn;
import org.compiere.model.MProcessPara; import org.compiere.model.MProcessPara;
import org.compiere.model.MSequence; import org.compiere.model.MSequence;
import org.compiere.model.MTable; import org.compiere.model.MTable;
import org.compiere.model.PO;
import org.compiere.model.Query; import org.compiere.model.Query;
import org.compiere.model.X_AD_Package_UUID_Map; import org.compiere.model.X_AD_Package_UUID_Map;
import org.compiere.model.X_A_Asset_Addition;
import org.compiere.model.X_A_Asset_Disposed;
import org.compiere.model.X_A_Asset_Reval;
import org.compiere.model.X_A_Asset_Transfer;
import org.compiere.model.X_A_Depreciation_Entry;
import org.compiere.model.X_A_Depreciation_Exp;
import org.compiere.model.X_C_AllocationHdr;
import org.compiere.model.X_C_AllocationLine;
import org.compiere.model.X_C_BankStatement;
import org.compiere.model.X_C_BankStatementLine;
import org.compiere.model.X_C_Cash;
import org.compiere.model.X_C_CashLine;
import org.compiere.model.X_C_Invoice;
import org.compiere.model.X_C_InvoiceLine;
import org.compiere.model.X_C_Order;
import org.compiere.model.X_C_OrderLine;
import org.compiere.model.X_C_Payment;
import org.compiere.model.X_C_ProjectIssue;
import org.compiere.model.X_GL_Journal;
import org.compiere.model.X_GL_JournalLine;
import org.compiere.model.X_M_InOut;
import org.compiere.model.X_M_InOutLine;
import org.compiere.model.X_M_Inventory;
import org.compiere.model.X_M_InventoryLine;
import org.compiere.model.X_M_MatchInv;
import org.compiere.model.X_M_MatchPO;
import org.compiere.model.X_M_Movement;
import org.compiere.model.X_M_MovementLine;
import org.compiere.model.X_M_Production;
import org.compiere.model.X_M_ProductionLine;
import org.compiere.model.X_M_Requisition;
import org.compiere.model.X_M_RequisitionLine;
import org.compiere.process.ProcessInfoParameter; import org.compiere.process.ProcessInfoParameter;
import org.compiere.process.SvrProcess; import org.compiere.process.SvrProcess;
import org.compiere.util.AdempiereUserError; import org.compiere.util.AdempiereUserError;
@ -50,25 +83,39 @@ import org.compiere.util.DB;
import org.compiere.util.DisplayType; import org.compiere.util.DisplayType;
import org.compiere.util.Util; import org.compiere.util.Util;
/**
* Process to move a client from a external database to current, or copy a template in current database
*/
@org.adempiere.base.annotation.Process @org.adempiere.base.annotation.Process
public class MoveClient extends SvrProcess { public class MoveClient extends SvrProcess {
// Process to move a client from a external database to current, or copy a template in current database /** Define if the process is to copy a template client, or bring from external database */
private boolean p_IsCopyClient;
/** JDBC URL of the external database */
private String p_JDBC_URL;
/** optional to connect to the JDBC URL, if empty use the same as target */
private String p_UserName;
/** optional to connect to the JDBC URL, if empty use the same as target */
private String p_Password;
/** optional, comma separated list of tables to exclude */
private String p_TablesToExclude;
/** optional, comma separated list, if empty then all clients >= 1000000 will be moved */
private String p_ClientsToInclude;
/** optional, comma separated list of clients to exclude */
private String p_ClientsToExclude;
/** to do just validation and not execute the process */
private boolean p_IsValidateOnly;
/** optional, comma separated list of tables that require to preserve IDs, * for all */
private String p_TablesToPreserveIDs;
/** New client name when copying from template */
private String p_ClientName;
/** New client value when copying from template */
private String p_ClientValue;
/** skip some validations to make the process faster */
private boolean p_IsSkipSomeValidations;
private boolean p_IsCopyClient; // Define if the process is to copy a template client, or bring from external database final private static String insertConversionId = "INSERT INTO T_MoveClient (AD_PInstance_ID, TableName, Source_Key, Target_Key) VALUES (?, ?, ?, ?)";
private String p_JDBC_URL; // JDBC URL of the external database final private static String queryT_MoveClient = "SELECT Target_Key FROM T_MoveClient WHERE AD_PInstance_ID=? AND TableName=? AND Source_Key=?";
private String p_UserName; // optional to connect to the JDBC URL, if empty use the same as target
private String p_Password; // optional to connect to the JDBC URL, if empty use the same as target
private String p_TablesToExclude; // optional, comma separated list of tables to exclude
private String p_ClientsToInclude; // optional, comma separated list, if empty then all clients >= 1000000 will be moved
private String p_ClientsToExclude; // optional, comma separated list of clients to exclude
private boolean p_IsValidateOnly; // to do just validation and not execute the process
private String p_TablesToPreserveIDs; // optional, comma separated list of tables that require to preserve IDs, * for all
private String p_ClientName; // New client name when copying from template
private String p_ClientValue; // New client value when copying from template
private boolean p_IsSkipSomeValidations; // skip some validations to make the process faster
final static String insertConversionId = "INSERT INTO T_MoveClient (AD_PInstance_ID, TableName, Source_ID, Target_ID) VALUES (?, ?, ?, ?)";
private Connection externalConn; private Connection externalConn;
private StringBuffer p_excludeTablesWhere = new StringBuffer(); private StringBuffer p_excludeTablesWhere = new StringBuffer();
@ -81,6 +128,9 @@ public class MoveClient extends SvrProcess {
private List<String> p_idSystemConversionList = new ArrayList<String>(); // can consume lot of memory but it helps for performance private List<String> p_idSystemConversionList = new ArrayList<String>(); // can consume lot of memory but it helps for performance
private boolean p_isPreserveAll = false; private boolean p_isPreserveAll = false;
/**
* Prepare - e.g., get Parameters.
*/
@Override @Override
protected void prepare() { protected void prepare() {
// defaults // defaults
@ -118,6 +168,11 @@ public class MoveClient extends SvrProcess {
} }
} }
/**
* Perform process.
* @return Message (variables are parsed)
* @throws Exception if not successful
*/
@Override @Override
protected String doIt() throws Exception { protected String doIt() throws Exception {
// validate parameters // validate parameters
@ -210,7 +265,7 @@ public class MoveClient extends SvrProcess {
} }
} }
// Make the connection to external database // Make the connection to external database - or to the same local when copying template
externalConn = null; externalConn = null;
try { try {
try { try {
@ -244,6 +299,9 @@ public class MoveClient extends SvrProcess {
return "@OK@"; return "@OK@";
} }
/**
* Conduct data validations before proceeding with the actual inserts
*/
private void validate() { private void validate() {
if (p_IsCopyClient) { if (p_IsCopyClient) {
// Validate that the newtenant value/name doesn't exist // Validate that the newtenant value/name doesn't exist
@ -377,6 +435,10 @@ public class MoveClient extends SvrProcess {
} }
/**
* Conduct validations on a specific table
* @param tableName
*/
private void validateExternalTable(String tableName) { private void validateExternalTable(String tableName) {
statusUpdate("Validating table " + tableName); statusUpdate("Validating table " + tableName);
// if table doesn't have client data (taking into account include/exclude) in the source DB // if table doesn't have client data (taking into account include/exclude) in the source DB
@ -445,6 +507,13 @@ public class MoveClient extends SvrProcess {
p_tablesVerifiedList.add(tableName.toUpperCase()); p_tablesVerifiedList.add(tableName.toUpperCase());
} }
/**
* Conduct validations for a specific column
* @param tableName
* @param columnName
* @param refID
* @param length
*/
private void validateExternalColumn(String tableName, String columnName, int refID, int length) { private void validateExternalColumn(String tableName, String columnName, int refID, int length) {
// inform if column is not present in target (blocking as it has client data) // inform if column is not present in target (blocking as it has client data)
// statusUpdate("Validating column " + tableName + "." + columnName); // statusUpdate("Validating column " + tableName + "." + columnName);
@ -471,14 +540,6 @@ public class MoveClient extends SvrProcess {
.append(" JOIN AD_Client ON (").append(tableName).append(".AD_Client_ID=AD_Client.AD_Client_ID)") .append(" JOIN AD_Client ON (").append(tableName).append(".AD_Client_ID=AD_Client.AD_Client_ID)")
.append(" WHERE ").append(tableName).append(".").append(columnName).append(" IS NOT NULL") .append(" WHERE ").append(tableName).append(".").append(columnName).append(" IS NOT NULL")
.append(" AND ").append(p_whereClient); .append(" AND ").append(p_whereClient);
// validate if unsupported types have data
if (refID == DisplayType.SingleSelectionGrid || refID == DisplayType.MultipleSelectionGrid) {
int cntMI = countInExternal(sqlDataNotNullInColumn.toString());
if (cntMI > 0) {
// TODO: Implement ID conversion for multi-ID column types
throw new AdempiereUserError("There is data in unsupported Multi-ID column " + tableName + "." + columnName);
}
}
if (!p_IsSkipSomeValidations && refID > MTable.MAX_OFFICIAL_ID) { if (!p_IsSkipSomeValidations && refID > MTable.MAX_OFFICIAL_ID) {
int cntET = countInExternal(sqlDataNotNullInColumn.toString()); int cntET = countInExternal(sqlDataNotNullInColumn.toString());
if (cntET > 0) { if (cntET > 0) {
@ -488,27 +549,21 @@ public class MoveClient extends SvrProcess {
} }
// when the column is a foreign key // when the column is a foreign key
String foreignTable = localColumn.getReferenceTableName(); String foreignTableName = localColumn.getReferenceTableName();
if (foreignTable != null if ( foreignTableName != null
&& (foreignTable.equalsIgnoreCase(tableName) || "AD_PInstance_Log".equalsIgnoreCase(tableName))) { && (foreignTableName.equalsIgnoreCase(tableName) || "AD_PInstance_Log".equalsIgnoreCase(tableName))) {
foreignTable = ""; foreignTableName = "";
} else if ("C_BPartner".equalsIgnoreCase(tableName) && "AD_OrgBP_ID".equalsIgnoreCase(columnName)) {
// Special case for C_BPartner.AD_OrgBP_ID defined as Button in dictionary
foreignTable = "AD_Org";
} else if ("C_Project".equalsIgnoreCase(tableName) && "C_ProjectType_ID".equalsIgnoreCase(columnName)) {
// Special case for C_Project.C_ProjectType_ID defined as Button in dictionary
foreignTable = "C_ProjectType";
} }
if (! Util.isEmpty(foreignTable)) { if (! Util.isEmpty(foreignTableName)) {
// verify all foreign keys pointing to a different client // verify all foreign keys pointing to a different client
// if pointing to a different client non-system // if pointing to a different client non-system
// inform cross-client data corruption error // inform cross-client data corruption error
// if pointing to system // if pointing to system
// add to list of columns with system foreign keys // add to list of columns with system foreign keys
// inform if the system record is not in target database using uuid - blocking // inform if the system record is not in target database using uuid - blocking
String uuidCol = MTable.getUUIDColumnName(foreignTable); String uuidCol = PO.getUUIDColumnName(foreignTableName);
StringBuilder sqlForeignClientSB = new StringBuilder(); StringBuilder sqlForeignClientSB = new StringBuilder();
if ("AD_Ref_List".equalsIgnoreCase(foreignTable)) { if ("AD_Ref_List".equalsIgnoreCase(foreignTableName)) {
sqlForeignClientSB sqlForeignClientSB
.append("SELECT DISTINCT AD_Ref_List.AD_Client_ID, AD_Ref_List.AD_Ref_List_ID, AD_Ref_List.").append(uuidCol) .append("SELECT DISTINCT AD_Ref_List.AD_Client_ID, AD_Ref_List.AD_Ref_List_ID, AD_Ref_List.").append(uuidCol)
.append(" FROM ").append(tableName); .append(" FROM ").append(tableName);
@ -527,34 +582,41 @@ public class MoveClient extends SvrProcess {
.append(" WHERE UPPER(AD_Table.TableName)='").append(tableName.toUpperCase()) .append(" WHERE UPPER(AD_Table.TableName)='").append(tableName.toUpperCase())
.append("' AND UPPER(AD_Column.ColumnName)='").append(columnName.toUpperCase()).append("'))") .append("' AND UPPER(AD_Column.ColumnName)='").append(columnName.toUpperCase()).append("'))")
.append(" WHERE ").append(p_whereClient) .append(" WHERE ").append(p_whereClient)
.append(" AND ").append(foreignTable).append(".AD_Client_ID!=").append(tableName).append(".AD_Client_ID") .append(" AND ").append(foreignTableName).append(".AD_Client_ID!=").append(tableName).append(".AD_Client_ID")
.append(" ORDER BY 2"); .append(" ORDER BY 2");
} else { } else {
sqlForeignClientSB sqlForeignClientSB.append("SELECT DISTINCT ").append(foreignTableName).append(".AD_Client_ID, ");
.append("SELECT DISTINCT ").append(foreignTable).append(".AD_Client_ID, ") MTable foreignTable = MTable.get(getCtx(), foreignTableName);
.append(foreignTable).append(".").append(foreignTable).append("_ID, ") if (foreignTable.isUUIDKeyTable()) {
.append(foreignTable).append(".").append(uuidCol) sqlForeignClientSB.append(foreignTableName).append(".").append(uuidCol).append(", ");
.append(" FROM ").append(tableName); } else {
sqlForeignClientSB.append(foreignTableName).append(".").append(foreignTableName).append("_ID, ");
}
sqlForeignClientSB.append(foreignTableName).append(".").append(uuidCol).append(" FROM ").append(tableName);
if (! "AD_Client".equalsIgnoreCase(tableName)) { if (! "AD_Client".equalsIgnoreCase(tableName)) {
sqlForeignClientSB.append(" JOIN AD_Client ON (").append(tableName).append(".AD_Client_ID=AD_Client.AD_Client_ID)"); sqlForeignClientSB.append(" JOIN AD_Client ON (").append(tableName).append(".AD_Client_ID=AD_Client.AD_Client_ID)");
} }
if ("AD_Client".equalsIgnoreCase(foreignTable)) { // fix issue with foreign AD_Client_ID like AD_Replication.Remote_Client_ID if ("AD_Client".equalsIgnoreCase(foreignTableName)) { // fix issue with foreign AD_Client_ID like AD_Replication.Remote_Client_ID
sqlForeignClientSB.append(" JOIN ").append(foreignTable) sqlForeignClientSB.append(" JOIN ").append(foreignTableName)
.append(" c ON (").append(tableName).append(".").append(columnName).append("=c."); .append(" c ON (").append(tableName).append(".").append(columnName).append("=c.");
} else { } else {
sqlForeignClientSB.append(" JOIN ").append(foreignTable) sqlForeignClientSB.append(" JOIN ").append(foreignTableName)
.append(" ON (").append(tableName).append(".").append(columnName).append("=").append(foreignTable).append("."); .append(" ON (").append(tableName).append(".").append(columnName).append("=").append(foreignTableName).append(".");
} }
if ("AD_Language".equalsIgnoreCase(foreignTable) && !columnName.equalsIgnoreCase("AD_Language_ID")) { if ("AD_Language".equalsIgnoreCase(foreignTableName) && !columnName.equalsIgnoreCase("AD_Language_ID")) {
sqlForeignClientSB.append("AD_Language"); sqlForeignClientSB.append("AD_Language");
} else if ("AD_EntityType".equalsIgnoreCase(foreignTable) && !columnName.equalsIgnoreCase("AD_EntityType_ID")) { } else if ("AD_EntityType".equalsIgnoreCase(foreignTableName) && !columnName.equalsIgnoreCase("AD_EntityType_ID")) {
sqlForeignClientSB.append("EntityType"); sqlForeignClientSB.append("EntityType");
} else { } else {
sqlForeignClientSB.append(foreignTable).append("_ID"); if (foreignTable.isUUIDKeyTable()) {
sqlForeignClientSB.append(uuidCol);
} else {
sqlForeignClientSB.append(foreignTableName).append("_ID");
}
} }
sqlForeignClientSB.append(")") sqlForeignClientSB.append(")")
.append(" WHERE ").append(p_whereClient) .append(" WHERE ").append(p_whereClient)
.append(" AND ").append(foreignTable).append(".AD_Client_ID!=").append(tableName).append(".AD_Client_ID") .append(" AND ").append(foreignTableName).append(".AD_Client_ID!=").append(tableName).append(".AD_Client_ID")
.append(" ORDER BY 2"); .append(" ORDER BY 2");
} }
String sqlForeignClient = DB.getDatabase().convertStatement(sqlForeignClientSB.toString()); String sqlForeignClient = DB.getDatabase().convertStatement(sqlForeignClientSB.toString());
@ -565,16 +627,16 @@ public class MoveClient extends SvrProcess {
rsFC = stmtFC.executeQuery(); rsFC = stmtFC.executeQuery();
while (rsFC.next()) { while (rsFC.next()) {
int clientID = rsFC.getInt(1); int clientID = rsFC.getInt(1);
int foreignID = rsFC.getInt(2); Object foreignID = rsFC.getObject(2);
String foreignUU = rsFC.getString(3); String foreignUU = rsFC.getString(3);
if (clientID > 0) { if (clientID > 0) {
p_errorList.add("Column " + tableName + "." + columnName + " has invalid cross-tenant reference to tenant " + clientID + " on ID=" + foreignID); p_errorList.add("Column " + tableName + "." + columnName + " has invalid cross-tenant reference to tenant " + clientID + " on ID=" + foreignID);
continue; continue;
} }
if (foreignID > 0) { if (foreignID != null) {
if (! p_idSystemConversionList.contains(foreignTable.toUpperCase() + "." + foreignID)) { if (! p_idSystemConversionList.contains(foreignTableName.toUpperCase() + "." + foreignID)) {
int localID = getFromUUID(foreignTable, uuidCol, tableName, columnName, foreignUU, foreignID); Object localID = getFromUUID(foreignTableName, uuidCol, tableName, columnName, foreignUU, foreignID);
if (localID < 0) { if (localID == null || (localID instanceof Number && ((Number)localID).intValue() < 0)) {
continue; continue;
} }
} }
@ -590,8 +652,11 @@ public class MoveClient extends SvrProcess {
p_columnsVerifiedList.add(tableName.toUpperCase() + "." + columnName.toUpperCase()); p_columnsVerifiedList.add(tableName.toUpperCase() + "." + columnName.toUpperCase());
} }
/**
* Check for orphan records in a table
* @param tableName
*/
private void validateOrphan(String tableName) { private void validateOrphan(String tableName) {
// most of tables don't have a foreign key for AD_Org, so better validate here for potential orphan records not enforced in DB
MTable table = MTable.get(getCtx(), tableName); MTable table = MTable.get(getCtx(), tableName);
for (MColumn column : table.getColumns(false)) { for (MColumn column : table.getColumns(false)) {
if (!column.isActive() || column.getColumnSQL() != null) { if (!column.isActive() || column.getColumnSQL() != null) {
@ -601,19 +666,13 @@ public class MoveClient extends SvrProcess {
if ("AD_Client_ID".equalsIgnoreCase(columnName)) { if ("AD_Client_ID".equalsIgnoreCase(columnName)) {
continue; continue;
} }
String foreignTable = column.getReferenceTableName(); String foreignTableName = column.getReferenceTableName();
if ("C_BPartner".equalsIgnoreCase(tableName) && "AD_OrgBP_ID".equalsIgnoreCase(columnName)) { if (! Util.isEmpty(foreignTableName) && ! "AD_Ref_List".equalsIgnoreCase(foreignTableName)) {
// Special case for C_BPartner.AD_OrgBP_ID defined as Button in dictionary MTable tableFK = MTable.get(getCtx(), foreignTableName);
foreignTable = "AD_Org";
} else if ("C_Project".equalsIgnoreCase(tableName) && "C_ProjectType_ID".equalsIgnoreCase(columnName)) {
// Special case for C_Project.C_ProjectType_ID defined as Button in dictionary
foreignTable = "C_ProjectType";
}
if (! Util.isEmpty(foreignTable) && ! "AD_Ref_List".equalsIgnoreCase(foreignTable)) {
MTable tableFK = MTable.get(getCtx(), foreignTable);
if (tableFK == null || MTable.ACCESSLEVEL_SystemOnly.equals(tableFK.getAccessLevel())) { if (tableFK == null || MTable.ACCESSLEVEL_SystemOnly.equals(tableFK.getAccessLevel())) {
continue; continue;
} }
// validate if the table has columns where the constraint is not created in database, like most AD_Org_ID for example
StringBuilder sqlVerifFKSB = new StringBuilder() StringBuilder sqlVerifFKSB = new StringBuilder()
.append("SELECT COUNT(*) ") .append("SELECT COUNT(*) ")
.append("FROM AD_Table t ") .append("FROM AD_Table t ")
@ -626,21 +685,25 @@ public class MoveClient extends SvrProcess {
if (cntFk > 0) { if (cntFk > 0) {
statusUpdate("Validating orphans for " + table.getTableName() + "." + columnName); statusUpdate("Validating orphans for " + table.getTableName() + "." + columnName);
// target database has not defined a foreign key, validate orphans // target database has not defined a foreign key, validate orphans
StringBuilder sqlExternalOrgOrphanSB = new StringBuilder() MTable foreignTable = MTable.get(getCtx(), foreignTableName);
.append("SELECT COUNT(*) FROM ").append(tableName); StringBuilder sqlExternalOrgOrphanSB = new StringBuilder("SELECT COUNT(*) FROM ").append(tableName);
if (! "AD_Client".equalsIgnoreCase(tableName)) { if (! "AD_Client".equalsIgnoreCase(tableName)) {
sqlExternalOrgOrphanSB.append(" JOIN AD_Client ON (").append(tableName).append(".AD_Client_ID=AD_Client.AD_Client_ID)"); sqlExternalOrgOrphanSB.append(" JOIN AD_Client ON (").append(tableName).append(".AD_Client_ID=AD_Client.AD_Client_ID)");
} }
sqlExternalOrgOrphanSB.append(" WHERE ").append(tableName).append(".").append(columnName).append(">0 AND ") sqlExternalOrgOrphanSB.append(" WHERE ").append(tableName).append(".").append(columnName);
.append(" ").append(tableName).append(".").append(columnName).append(" NOT IN (") if (foreignTable.isUUIDKeyTable())
.append(" SELECT ").append(foreignTable).append(".").append(foreignTable).append("_ID") sqlExternalOrgOrphanSB.append(" IS NOT NULL AND ");
.append(" FROM ").append(foreignTable) else
.append(" WHERE ").append(foreignTable).append(".AD_Client_ID IN (0,").append(tableName).append(".AD_Client_ID)") sqlExternalOrgOrphanSB.append(">0 AND ");
.append(")") sqlExternalOrgOrphanSB.append(" ").append(tableName).append(".").append(columnName).append(" NOT IN (")
.append(" AND ").append(p_whereClient); .append(" SELECT ").append(foreignTableName).append(".").append(foreignTable.getKeyColumns()[0])
.append(" FROM ").append(foreignTableName)
.append(" WHERE ").append(foreignTableName).append(".AD_Client_ID IN (0,").append(tableName).append(".AD_Client_ID)")
.append(")")
.append(" AND ").append(p_whereClient);
int cntOr = countInExternal(sqlExternalOrgOrphanSB.toString()); int cntOr = countInExternal(sqlExternalOrgOrphanSB.toString());
if (cntOr > 0) { if (cntOr > 0) {
p_errorList.add("Column " + tableName + "." + columnName + " has orphan records in table " + foreignTable); p_errorList.add("Column " + tableName + "." + columnName + " has orphan records in table " + foreignTableName);
} }
} }
} }
@ -648,6 +711,11 @@ public class MoveClient extends SvrProcess {
} }
/**
* Execute a count query in the external connection and return the count
* @param sql
* @return
*/
private int countInExternal(String sql) { private int countInExternal(String sql) {
int cnt = 0; int cnt = 0;
sql = DB.getDatabase().convertStatement(sql); sql = DB.getDatabase().convertStatement(sql);
@ -666,6 +734,9 @@ public class MoveClient extends SvrProcess {
return cnt; return cnt;
} }
/**
* Convert the IDs in table T_MoveClient and then proceed to do the INSERTs of new records
*/
private void moveClient() { private void moveClient() {
// first do the validation, process cannot be executed if there are blocking situations // first do the validation, process cannot be executed if there are blocking situations
// validation construct the list of tables and columns to process // validation construct the list of tables and columns to process
@ -684,20 +755,26 @@ public class MoveClient extends SvrProcess {
if (! p_tablesVerifiedList.contains(tableName.toUpperCase())) { if (! p_tablesVerifiedList.contains(tableName.toUpperCase())) {
continue; continue;
} }
if (! p_columnsVerifiedList.contains(tableName.toUpperCase() + "." + tableName.toUpperCase() + "_ID")) { String uuidCol = PO.getUUIDColumnName(tableName);
String keyCol;
if (table.isUUIDKeyTable())
keyCol = uuidCol.toUpperCase();
else
keyCol = tableName.toUpperCase() + "_ID";
if (! p_columnsVerifiedList.contains(tableName.toUpperCase() + "." + keyCol))
continue; continue;
}
statusUpdate("Converting IDs for table " + tableName); statusUpdate("Converting IDs for table " + tableName);
StringBuilder selectVerifyIdSB = new StringBuilder() StringBuilder selectVerifyIdSB = new StringBuilder()
.append("SELECT ").append(tableName).append("_ID FROM ").append(tableName) .append("SELECT ").append(keyCol).append(" FROM ").append(tableName)
.append(" WHERE ").append(tableName).append("_ID=?"); .append(" WHERE ").append(keyCol).append("=?");
StringBuilder selectGetIdsSB = new StringBuilder() StringBuilder selectGetIdsSB = new StringBuilder()
.append("SELECT ").append(tableName).append(".").append(tableName).append("_ID FROM ").append(tableName); .append("SELECT ").append(tableName).append(".").append(keyCol).append(" FROM ").append(tableName);
if (! "AD_Client".equalsIgnoreCase(tableName)) { if (! "AD_Client".equalsIgnoreCase(tableName)) {
selectGetIdsSB.append(" JOIN AD_Client ON (").append(tableName).append(".AD_Client_ID=AD_Client.AD_Client_ID)"); selectGetIdsSB.append(" JOIN AD_Client ON (").append(tableName).append(".AD_Client_ID=AD_Client.AD_Client_ID)");
} }
selectGetIdsSB.append(" WHERE ").append(p_whereClient) selectGetIdsSB.append(" WHERE ").append(p_whereClient)
.append(" ORDER BY ").append(tableName).append("_ID"); .append(" ORDER BY ").append(keyCol);
String selectGetIds = DB.getDatabase().convertStatement(selectGetIdsSB.toString()); String selectGetIds = DB.getDatabase().convertStatement(selectGetIdsSB.toString());
PreparedStatement stmtGI = null; PreparedStatement stmtGI = null;
ResultSet rsGI = null; ResultSet rsGI = null;
@ -705,31 +782,37 @@ public class MoveClient extends SvrProcess {
stmtGI = externalConn.prepareStatement(selectGetIds, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); stmtGI = externalConn.prepareStatement(selectGetIds, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
rsGI = stmtGI.executeQuery(); rsGI = stmtGI.executeQuery();
while (rsGI.next()) { while (rsGI.next()) {
int sourceID = rsGI.getInt(1); Object source_Key = rsGI.getObject(1);
int targetID = -1; Object target_Key = null;
if (p_isPreserveAll || p_tablesToPreserveIDsList.contains(tableName.toUpperCase())) { if (p_isPreserveAll || p_tablesToPreserveIDsList.contains(tableName.toUpperCase())) {
int localID = DB.getSQLValueEx(get_TrxName(), selectVerifyIdSB.toString(), sourceID); List<Object> list = DB.getSQLValueObjectsEx(get_TrxName(), selectVerifyIdSB.toString(), source_Key);
if (localID < 0) { Object localID = null;
targetID = sourceID; if (list != null && list.size() == 1)
localID = list.get(0);
if (localID == null || (localID instanceof Number && ((Number)localID).intValue() < 0)) {
target_Key = source_Key;
} else { } else {
throw new AdempiereException("In " + tableName + "." + tableName + "_ID already exist the ID=" + sourceID); throw new AdempiereException("In " + tableName + "." + tableName + "_ID already exist the ID=" + source_Key);
} }
} else { } else {
if ("AD_ChangeLog".equalsIgnoreCase(tableName)) { if ("AD_ChangeLog".equalsIgnoreCase(tableName)) {
// AD_ChangeLog_ID is not really a unique key - validate if it was already converted before // AD_ChangeLog_ID is not really a unique key - validate if it was already converted before
int clId = DB.getSQLValueEx(get_TrxName(), int clId = DB.getSQLValueEx(get_TrxName(),
"SELECT Target_ID FROM T_MoveClient WHERE AD_PInstance_ID=? AND TableName=? AND Source_ID=?", queryT_MoveClient,
getAD_PInstance_ID(), "AD_CHANGELOG", sourceID); getAD_PInstance_ID(), "AD_CHANGELOG", String.valueOf(source_Key));
if (clId == -1) { if (clId == -1) {
targetID = DB.getNextID(getAD_Client_ID(), tableName, get_TrxName()); target_Key = DB.getNextID(getAD_Client_ID(), tableName, get_TrxName());
} }
} else { } else {
targetID = DB.getNextID(getAD_Client_ID(), tableName, get_TrxName()); if (table.isUUIDKeyTable())
target_Key = UUID.randomUUID().toString();
else
target_Key = DB.getNextID(getAD_Client_ID(), tableName, get_TrxName());
} }
} }
if (targetID >= 0) { if (target_Key != null || (target_Key instanceof Number && ((Number)target_Key).intValue() >= 0)) {
DB.executeUpdateEx(insertConversionId, DB.executeUpdateEx(insertConversionId,
new Object[] {getAD_PInstance_ID(), tableName.toUpperCase(), sourceID, targetID}, new Object[] {getAD_PInstance_ID(), tableName.toUpperCase(), source_Key, target_Key},
get_TrxName()); get_TrxName());
} }
} }
@ -757,8 +840,8 @@ public class MoveClient extends SvrProcess {
throw new AdempiereException("Error in parameter Tenants to Include, must be just one integer"); throw new AdempiereException("Error in parameter Tenants to Include, must be just one integer");
} }
newADClientID = DB.getSQLValueEx(get_TrxName(), newADClientID = DB.getSQLValueEx(get_TrxName(),
"SELECT Target_ID FROM T_MoveClient WHERE AD_PInstance_ID=? AND TableName=? AND Source_ID=?", queryT_MoveClient,
getAD_PInstance_ID(), "AD_CLIENT", clientInt); getAD_PInstance_ID(), "AD_CLIENT", String.valueOf(clientInt));
oldClientValue = DB.getSQLValueStringEx(get_TrxName(), oldClientValue = DB.getSQLValueStringEx(get_TrxName(),
"SELECT Value FROM AD_Client WHERE AD_Client_ID=?", clientInt); "SELECT Value FROM AD_Client WHERE AD_Client_ID=?", clientInt);
} }
@ -819,28 +902,40 @@ public class MoveClient extends SvrProcess {
for (int i = 0; i < ncols; i++) { for (int i = 0; i < ncols; i++) {
MColumn column = columns.get(i); MColumn column = columns.get(i);
String columnName = column.getColumnName(); String columnName = column.getColumnName();
// Obtain which is the table to convert the ID (the foreign table)
String convertTable = column.getReferenceTableName(); String convertTable = column.getReferenceTableName();
if ((tableName + "_ID").equalsIgnoreCase(columnName)) { if ((tableName + "_ID").equalsIgnoreCase(columnName)) {
convertTable = tableName; convertTable = tableName;
} else if ("C_BPartner".equalsIgnoreCase(tableName) && "AD_OrgBP_ID".equalsIgnoreCase(columnName)) { } else if ( column.getAD_Reference_ID() == DisplayType.ChosenMultipleSelectionTable
// Special case for C_BPartner.AD_OrgBP_ID defined as Button in dictionary || column.getAD_Reference_ID() == DisplayType.ChosenMultipleSelectionSearch
convertTable = "AD_Org"; || column.getAD_Reference_ID() == DisplayType.SingleSelectionGrid
} else if ("C_Project".equalsIgnoreCase(tableName) && "C_ProjectType_ID".equalsIgnoreCase(columnName)) { || column.getAD_Reference_ID() == DisplayType.MultipleSelectionGrid) {
// Special case for C_Project.C_ProjectType_ID defined as Button in dictionary convertTable = column.getMultiReferenceTableName();
convertTable = "C_ProjectType";
} else if (convertTable != null } else if (convertTable != null
&& ("AD_Ref_List".equalsIgnoreCase(convertTable) && ("AD_Ref_List".equalsIgnoreCase(convertTable)
|| "AD_Language".equalsIgnoreCase(columnName) || "AD_Language".equalsIgnoreCase(columnName)
|| "EntityType".equalsIgnoreCase(columnName))) { || "EntityType".equalsIgnoreCase(columnName))) {
convertTable = ""; convertTable = "";
} else if ("Record_ID".equalsIgnoreCase(columnName) && table.columnExistsInDB("AD_Table_ID")) { } else if (("Record_ID".equalsIgnoreCase(columnName) || "Record_UU".equalsIgnoreCase(columnName)) && table.columnExistsInDB("AD_Table_ID")) {
// Special case for Record_ID // Special case for Record_ID or Record_UU
int tableId = rsGD.getInt("AD_Table_ID"); int tableId = rsGD.getInt("AD_Table_ID");
if (tableId > 0) { if (tableId > 0) {
convertTable = getExternalTableName(tableId); convertTable = getExternalTableName(tableId);
} else { } else {
convertTable = ""; convertTable = "";
} }
} else if ("Line_ID".equalsIgnoreCase(columnName) && "Fact_Acct".equalsIgnoreCase(tableName)) {
// Special case for Fact_Acct.Line_ID
int tableId = rsGD.getInt("AD_Table_ID");
if (tableId > 0) {
convertTable = getAcctDetailTableName(tableId);
} else {
convertTable = "";
}
} else if ("Parent_ID".equalsIgnoreCase(columnName) && "AD_Tree_Favorite_Node".equalsIgnoreCase(tableName)) {
// Special case for AD_Tree_Favorite_Node.Parent_ID
convertTable = "AD_Tree_Favorite_Node";
} else if ("Node_ID".equalsIgnoreCase(columnName) && "AD_TreeBar".equalsIgnoreCase(tableName)) { } else if ("Node_ID".equalsIgnoreCase(columnName) && "AD_TreeBar".equalsIgnoreCase(tableName)) {
// Special case for AD_TreeBar.Node_ID // Special case for AD_TreeBar.Node_ID
convertTable = "AD_Menu"; convertTable = "AD_Menu";
@ -901,62 +996,66 @@ public class MoveClient extends SvrProcess {
convertTable = ""; convertTable = "";
} }
} }
// Fill the target value
if (! Util.isEmpty(convertTable)) { if (! Util.isEmpty(convertTable)) {
// Foreign - potential ID conversion // Foreign - potential ID conversion
int id = rsGD.getInt(i + 1); Object key = rsGD.getObject(i + 1);
if (rsGD.wasNull()) { if (rsGD.wasNull()) {
parameters[i] = null; parameters[i] = null;
} else { } else {
if (! (id == 0 && ("Parent_ID".equalsIgnoreCase(columnName) || "Node_ID".equalsIgnoreCase(columnName))) // Parent_ID/Node_ID=0 is valid if ( ! (key instanceof Number && ((Number)key).intValue() == 0 && ("Parent_ID".equalsIgnoreCase(columnName) || "Node_ID".equalsIgnoreCase(columnName))) // Parent_ID/Node_ID=0 is valid
&& (id >= MTable.MAX_OFFICIAL_ID || p_IsCopyClient)) { && (key instanceof String || (key instanceof Number && ((Number)key).intValue() >= MTable.MAX_OFFICIAL_ID) || p_IsCopyClient)) {
int convertedId = -1; Object convertedId = null;
final String query = "SELECT Target_ID FROM T_MoveClient WHERE AD_PInstance_ID=? AND TableName=? AND Source_ID=?"; if ( column.getAD_Reference_ID() == DisplayType.ChosenMultipleSelectionSearch
try { || column.getAD_Reference_ID() == DisplayType.ChosenMultipleSelectionTable
convertedId = DB.getSQLValueEx(get_TrxName(), || column.getAD_Reference_ID() == DisplayType.SingleSelectionGrid
query, || column.getAD_Reference_ID() == DisplayType.MultipleSelectionGrid) {
getAD_PInstance_ID(), convertTable.toUpperCase(), id); // multiple IDs or UUIDs separated by commas
} catch (Exception e) { String[] multiKeys = ((String)key).split(",");
throw new AdempiereException("Could not execute query: " + query + "\nCause = " + e.getLocalizedMessage()); for (String multiKey : multiKeys) {
} Object keyToConvert;
if (convertedId < 0) { if (Util.isUUID(multiKey))
// not found in the table - try to get it again - could be missed in first pass keyToConvert = multiKey;
convertedId = getLocalIDFor(convertTable, id, tableName); else
if (convertedId < 0) { keyToConvert = Integer.valueOf(multiKey);
if (("Record_ID".equalsIgnoreCase(columnName) && table.columnExistsInDB("AD_Table_ID")) Object multiConvertedId = getConvertedId(convertTable, keyToConvert, tableName, columnName);
|| (("Node_ID".equalsIgnoreCase(columnName) || "Parent_ID".equalsIgnoreCase(columnName)) if (multiConvertedId == null || (multiConvertedId instanceof Number && ((Number)multiConvertedId).intValue() < 0)) {
&& ( "AD_TreeNode".equalsIgnoreCase(tableName) if (canIgnoreNullConvertedId(table, tableName, columnName, convertTable)) {
|| "AD_TreeNodeMM".equalsIgnoreCase(tableName)
|| "AD_TreeNodeBP".equalsIgnoreCase(tableName)
|| "AD_TreeNodeCMC".equalsIgnoreCase(tableName)
|| "AD_TreeNodeCMM".equalsIgnoreCase(tableName)
|| "AD_TreeNodeCMS".equalsIgnoreCase(tableName)
|| "AD_TreeNodeCMT".equalsIgnoreCase(tableName)
|| "AD_TreeNodePR".equalsIgnoreCase(tableName)
|| "AD_TreeNodeU1".equalsIgnoreCase(tableName)
|| "AD_TreeNodeU2".equalsIgnoreCase(tableName)
|| "AD_TreeNodeU3".equalsIgnoreCase(tableName)
|| "AD_TreeNodeU4".equalsIgnoreCase(tableName)
|| "AD_TreeBar".equalsIgnoreCase(tableName)))) {
if (p_tablesToExcludeList.contains(convertTable.toUpperCase())) {
// record is pointing to a table that is not included, ignore it
insertRecord = false; insertRecord = false;
break; break;
} else {
throw new AdempiereException("Found orphan record in " + tableName + "." + columnName + ": " + multiKey + " related to table " + convertTable);
} }
} }
if ("AD_ChangeLog".equalsIgnoreCase(tableName)) { if (convertedId == null) {
// skip orphan records in AD_ChangeLog, can be log of deleted records, skip convertedId = "";
} else {
convertedId += ",";
}
convertedId += String.valueOf(multiConvertedId);
}
} else {
// single ID or UUID to convert
convertedId = getConvertedId(convertTable, key, tableName, columnName);
if (convertedId == null || (convertedId instanceof Number && ((Number)convertedId).intValue() < 0)) {
if (canIgnoreNullConvertedId(table, tableName, columnName, convertTable)) {
insertRecord = false; insertRecord = false;
break; break;
} else {
throw new AdempiereException("Found orphan record in " + tableName + "." + columnName + ": " + key + " related to table " + convertTable);
} }
throw new AdempiereException("Found orphan record in " + tableName + "." + columnName + ": " + id + " related to table " + convertTable);
} }
} }
id = convertedId; key = convertedId instanceof Number ? ((Number)convertedId).intValue() : convertedId.toString();
} }
if ("AD_Preference".equalsIgnoreCase(tableName) && "Value".equalsIgnoreCase(columnName)) { if ("AD_Preference".equalsIgnoreCase(tableName) && "Value".equalsIgnoreCase(columnName)) {
parameters[i] = String.valueOf(id); parameters[i] = String.valueOf(key);
} else { } else {
parameters[i] = id; if (DisplayType.isText(column.getAD_Reference_ID()))
parameters[i] = key.toString();
else
parameters[i] = Integer.valueOf(key.toString());
} }
} }
} else { } else {
@ -965,10 +1064,14 @@ public class MoveClient extends SvrProcess {
parameters[i] = null; parameters[i] = null;
} }
if (p_IsCopyClient) { if (p_IsCopyClient) {
String uuidCol = MTable.getUUIDColumnName(tableName); String uuidCol = PO.getUUIDColumnName(tableName);
if (columnName.equals(uuidCol)) { if (columnName.equals(uuidCol)) {
String oldUUID = (String) parameters[i]; String oldUUID = (String) parameters[i];
String newUUID = UUID.randomUUID().toString(); // it is possible that the UUID has been resolved before because of a foreign key Record_UU, so search in T_MoveClient first
String newUUID = DB.getSQLValueStringEx(get_TrxName(), queryT_MoveClient, getAD_PInstance_ID(), tableName.toUpperCase(), oldUUID);
if (newUUID == null) {
newUUID = UUID.randomUUID().toString();
}
parameters[i] = newUUID; parameters[i] = newUUID;
if (! Util.isEmpty(oldUUID)) { if (! Util.isEmpty(oldUUID)) {
X_AD_Package_UUID_Map map = new X_AD_Package_UUID_Map(getCtx(), 0, get_TrxName()); X_AD_Package_UUID_Map map = new X_AD_Package_UUID_Map(getCtx(), 0, get_TrxName());
@ -1041,6 +1144,80 @@ public class MoveClient extends SvrProcess {
} }
} }
/**
* Define if is acceptable to ignore a non existing converted ID
* @return
*/
private boolean canIgnoreNullConvertedId(MTable table, String tableName, String columnName, String convertTable) {
if ( (("Record_ID".equalsIgnoreCase(columnName) || "Record_UU".equalsIgnoreCase(columnName)) && table.columnExistsInDB("AD_Table_ID"))
|| ("Line_ID".equalsIgnoreCase(columnName) && "Fact_Acct".equalsIgnoreCase(tableName))
|| (("Node_ID".equalsIgnoreCase(columnName) || "Parent_ID".equalsIgnoreCase(columnName))
&& ( "AD_TreeNode".equalsIgnoreCase(tableName)
|| "AD_TreeNodeMM".equalsIgnoreCase(tableName)
|| "AD_TreeNodeBP".equalsIgnoreCase(tableName)
|| "AD_TreeNodeCMC".equalsIgnoreCase(tableName)
|| "AD_TreeNodeCMM".equalsIgnoreCase(tableName)
|| "AD_TreeNodeCMS".equalsIgnoreCase(tableName)
|| "AD_TreeNodeCMT".equalsIgnoreCase(tableName)
|| "AD_TreeNodePR".equalsIgnoreCase(tableName)
|| "AD_TreeNodeU1".equalsIgnoreCase(tableName)
|| "AD_TreeNodeU2".equalsIgnoreCase(tableName)
|| "AD_TreeNodeU3".equalsIgnoreCase(tableName)
|| "AD_TreeNodeU4".equalsIgnoreCase(tableName)
|| "AD_Tree_Favorite_Node".equalsIgnoreCase(tableName)
|| "AD_TreeBar".equalsIgnoreCase(tableName)))) {
if (p_tablesToExcludeList.contains(convertTable.toUpperCase())) {
// record is pointing to a table that is not included, ignore it
return true;
}
}
if ("AD_ChangeLog".equalsIgnoreCase(tableName)) {
// skip orphan records in AD_ChangeLog, can be log of deleted records, skip
return true;
}
return false;
}
/**
* Return a converted ID
* @param convertTable
* @param key
* @param tableName
* @param columnName
* @return
*/
private Object getConvertedId(String convertTable, Object key, String tableName, String columnName) {
Object convertedId = null;
try {
List<Object> list = DB.getSQLValueObjectsEx(get_TrxName(), queryT_MoveClient,
getAD_PInstance_ID(), convertTable.toUpperCase(), String.valueOf(key));
if (list != null && list.size() == 1)
convertedId = list.get(0);
} catch (Exception e) {
throw new AdempiereException("Could not execute query: " + queryT_MoveClient + "\nCause = " + e.getLocalizedMessage());
}
if (convertedId == null || (convertedId instanceof Number && ((Number)convertedId).intValue() < 0)) {
// when obtaining a UUID for a non-UUID table means to generate and insert the conversion
// for example AD_Attachment.Record_UU requires the UUID of a still not inserted record in another table
MTable cTable = MTable.get(getCtx(), convertTable);
if (key instanceof String && ! cTable.isUUIDKeyTable() && columnName.equals("Record_UU")) {
convertedId = UUID.randomUUID().toString();
DB.executeUpdateEx(insertConversionId,
new Object[] {getAD_PInstance_ID(), convertTable.toUpperCase(), key, convertedId},
get_TrxName());
} else {
// not found in the T_MoveClient table - try to get it again - could be missed in first pass
convertedId = getLocalKeyFor(convertTable, key, tableName);
}
}
return convertedId;
}
/**
* Return the table name of the table driving a tree
* @param treeId
* @return
*/
private String getExternalTableFromTree(int treeId) { private String getExternalTableFromTree(int treeId) {
String tableName = null; String tableName = null;
final String sqlTableTree = "" final String sqlTableTree = ""
@ -1086,6 +1263,11 @@ public class MoveClient extends SvrProcess {
return tableName; return tableName;
} }
/**
* Get the name of a table based on the AD_Table_ID - executed against the external connection
* @param tableId
* @return
*/
private String getExternalTableName(int tableId) { private String getExternalTableName(int tableId) {
String tableName = null; String tableName = null;
String sql = DB.getDatabase().convertStatement("SELECT TableName FROM AD_Table WHERE AD_Table_ID=?"); String sql = DB.getDatabase().convertStatement("SELECT TableName FROM AD_Table WHERE AD_Table_ID=?");
@ -1105,6 +1287,42 @@ public class MoveClient extends SvrProcess {
return tableName; return tableName;
} }
/**
* Get the Accounting Detail table based on the document table
* @param tableId
* @return
*/
private String getAcctDetailTableName(int tableId) {
String detailTableName = "";
switch (tableId) {
case X_A_Depreciation_Entry.Table_ID : detailTableName = X_A_Depreciation_Exp.Table_Name ; break;
case X_C_AllocationHdr.Table_ID : detailTableName = X_C_AllocationLine.Table_Name ; break;
case X_C_BankStatement.Table_ID : detailTableName = X_C_BankStatementLine.Table_Name ; break;
case X_C_Cash.Table_ID : detailTableName = X_C_CashLine.Table_Name ; break;
case X_C_Invoice.Table_ID : detailTableName = X_C_InvoiceLine.Table_Name ; break;
case X_C_Order.Table_ID : detailTableName = X_C_OrderLine.Table_Name ; break;
case X_C_ProjectIssue.Table_ID : detailTableName = X_C_ProjectIssue.Table_Name ; break;
case X_GL_Journal.Table_ID : detailTableName = X_GL_JournalLine.Table_Name ; break;
case X_M_InOut.Table_ID : detailTableName = X_M_InOutLine.Table_Name ; break;
case X_M_Inventory.Table_ID : detailTableName = X_M_InventoryLine.Table_Name ; break;
case X_M_Movement.Table_ID : detailTableName = X_M_MovementLine.Table_Name ; break;
case X_M_Production.Table_ID : detailTableName = X_M_ProductionLine.Table_Name ; break;
case X_M_Requisition.Table_ID : detailTableName = X_M_RequisitionLine.Table_Name ; break;
case X_A_Asset_Addition.Table_ID : break;
case X_A_Asset_Disposed.Table_ID : break;
case X_A_Asset_Reval.Table_ID : break;
case X_A_Asset_Transfer.Table_ID : break;
case X_M_MatchInv.Table_ID : break;
case X_M_MatchPO.Table_ID : break;
case X_C_Payment.Table_ID : break;
default: log.warning("Fact_Acct.Line_ID detail table not identified for table ID = " + tableId);
}
return detailTableName;
}
/**
* Check sequence for tables where the IDs must be preserved
*/
private void checkSequences() { private void checkSequences() {
if (p_isPreserveAll) { if (p_isPreserveAll) {
for (String tableName : p_tablesVerifiedList) { for (String tableName : p_tablesVerifiedList) {
@ -1123,48 +1341,78 @@ public class MoveClient extends SvrProcess {
} }
} }
private int getLocalIDFor(String tableName, int foreignId, String tableNameSource) { /**
String uuidCol = MTable.getUUIDColumnName(tableName); * Get the local converted ID or UUID for a record
StringBuilder sqlRemoteUUSB = new StringBuilder() * @param tableName
.append("SELECT ").append(uuidCol).append(" FROM ").append(tableName) * @param foreign_Key
.append(" WHERE ").append(tableName).append("_ID=?"); * @param tableNameSource
String sqlRemoteUU = DB.getDatabase().convertStatement(sqlRemoteUUSB.toString()); * @return
PreparedStatement stmtUU = null; */
ResultSet rs = null; private Object getLocalKeyFor(String tableName, Object foreign_Key, String tableNameSource) {
String uuidCol = PO.getUUIDColumnName(tableName);
MTable table = MTable.get(getCtx(), tableName);
String remoteUUID = null; String remoteUUID = null;
try { if (table.isUUIDKeyTable()) {
stmtUU = externalConn.prepareStatement(sqlRemoteUU, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); remoteUUID = foreign_Key.toString();
stmtUU.setInt(1, foreignId); } else {
rs = stmtUU.executeQuery(); StringBuilder sqlRemoteUUSB = new StringBuilder()
if (rs.next()) .append("SELECT ").append(uuidCol).append(" FROM ").append(tableName)
remoteUUID = rs.getString(1); .append(" WHERE ").append(tableName).append("_ID=?");
} catch (SQLException e) { String sqlRemoteUU = DB.getDatabase().convertStatement(sqlRemoteUUSB.toString());
throw new AdempiereException("Could not execute external query for table " + tableNameSource + ": " + sqlRemoteUU + "\nCause = " + e.getLocalizedMessage()); PreparedStatement stmtUU = null;
} finally { ResultSet rs = null;
DB.close(rs, stmtUU); try {
stmtUU = externalConn.prepareStatement(sqlRemoteUU, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
stmtUU.setObject(1, foreign_Key);
rs = stmtUU.executeQuery();
if (rs.next())
remoteUUID = rs.getString(1);
} catch (SQLException e) {
throw new AdempiereException("Could not execute external query for table " + tableNameSource + ": " + sqlRemoteUU + "\nCause = " + e.getLocalizedMessage());
} finally {
DB.close(rs, stmtUU);
}
} }
int localID = -1; Object local_Key = null;
if (remoteUUID != null) { if (remoteUUID != null) {
localID = getFromUUID(tableName, uuidCol, null, null, remoteUUID, foreignId); local_Key = getFromUUID(tableName, uuidCol, null, null, remoteUUID, foreign_Key);
} }
return localID; return local_Key;
} }
private int getFromUUID(String foreignTable, String uuidCol, String tableName, String columnName, String foreignUU, int foreignID) { /**
StringBuilder sqlCheckLocalUU = new StringBuilder() * Get the local ID or UUID based on a UUID
.append("SELECT ").append(foreignTable).append("_ID FROM ").append(foreignTable) * @param foreignTableName
.append(" WHERE ").append(uuidCol).append("=?"); * @param uuidCol
int localID = DB.getSQLValueEx(get_TrxName(), sqlCheckLocalUU.toString(), foreignUU); * @param tableName
if (localID < 0) { * @param columnName
* @param foreignUU
* @param foreign_Key
* @return
*/
private Object getFromUUID(String foreignTableName, String uuidCol, String tableName, String columnName, String foreignUU, Object foreign_Key) {
Object local_Key = null;
MTable foreignTable = MTable.get(getCtx(), foreignTableName);
StringBuilder sqlCheckLocalUU = new StringBuilder("SELECT ");
if (foreignTable.isUUIDKeyTable()) {
sqlCheckLocalUU.append(PO.getUUIDColumnName(foreignTableName));
} else {
sqlCheckLocalUU.append(foreignTableName).append("_ID");
}
sqlCheckLocalUU.append(" FROM ").append(foreignTableName).append(" WHERE ").append(uuidCol).append("=?");
List<Object> list = DB.getSQLValueObjectsEx(get_TrxName(), sqlCheckLocalUU.toString(), foreignUU);
if (list != null && list.size() == 1)
local_Key = list.get(0);
if (local_Key == null || (local_Key instanceof Number && ((Number)local_Key).intValue() < 0)) {
p_errorList.add("Column " + tableName + "." + columnName + " has system reference not convertible, " p_errorList.add("Column " + tableName + "." + columnName + " has system reference not convertible, "
+ foreignTable + "." + uuidCol + "=" + foreignUU); + foreignTableName + "." + uuidCol + "=" + foreignUU);
return -1; return -1;
} }
DB.executeUpdateEx(insertConversionId, DB.executeUpdateEx(insertConversionId,
new Object[] {getAD_PInstance_ID(), foreignTable.toUpperCase(), foreignID, localID}, new Object[] {getAD_PInstance_ID(), foreignTableName.toUpperCase(), foreign_Key, local_Key},
get_TrxName()); get_TrxName());
p_idSystemConversionList.add(foreignTable.toUpperCase() + "." + foreignID); p_idSystemConversionList.add(foreignTableName.toUpperCase() + "." + foreign_Key);
return localID; return local_Key;
} }
} }

View File

@ -163,9 +163,9 @@ public class WArchive implements EventListener<Event>
WArchiveViewer av = (WArchiveViewer) form.getICustomForm(); WArchiveViewer av = (WArchiveViewer) form.getICustomForm();
av.setShowQuery(false); av.setShowQuery(false);
if (e.getTarget() == m_documents) if (e.getTarget() == m_documents)
av.query(false, m_AD_Table_ID, m_Record_ID); av.query(false, m_AD_Table_ID, m_Record_ID, m_Record_UU);
else if (e.getTarget() == m_reports) else if (e.getTarget() == m_reports)
av.query(true, m_AD_Table_ID, m_Record_ID); av.query(true, m_AD_Table_ID, m_Record_ID, m_Record_UU);
else // all Reports else // all Reports
av.query(true, m_AD_Table_ID, 0); av.query(true, m_AD_Table_ID, 0);

View File

@ -99,6 +99,7 @@ public class RecordTimeLinePanel extends Vlayout {
return; return;
} }
int recordId = gridTab.getRecord_ID(); int recordId = gridTab.getRecord_ID();
String recordUU = gridTab.getRecord_UU();
int tableId = gridTab.getAD_Table_ID(); int tableId = gridTab.getAD_Table_ID();
ArrayList<String> docActionValues = new ArrayList<String>(); ArrayList<String> docActionValues = new ArrayList<String>();
ArrayList<String> docActionNames = new ArrayList<String>(); ArrayList<String> docActionNames = new ArrayList<String>();
@ -117,7 +118,7 @@ public class RecordTimeLinePanel extends Vlayout {
.append("JOIN AD_Column c ON l.ad_column_id=c.ad_column_id ") .append("JOIN AD_Column c ON l.ad_column_id=c.ad_column_id ")
.append("JOIN AD_User u ON l.createdby=u.ad_user_id ") .append("JOIN AD_User u ON l.createdby=u.ad_user_id ")
.append("WHERE l.AD_Table_ID=? ") .append("WHERE l.AD_Table_ID=? ")
.append("AND l.Record_ID=? ") .append("AND (l.Record_ID=? OR l.Record_UU=?) ")
.append("ORDER BY l.created desc, l.trxName "); .append("ORDER BY l.created desc, l.trxName ");
PreparedStatement stmt = null; PreparedStatement stmt = null;
ResultSet rs = null; ResultSet rs = null;
@ -125,6 +126,7 @@ public class RecordTimeLinePanel extends Vlayout {
stmt = DB.prepareStatement(sql.toString(), (String)null); stmt = DB.prepareStatement(sql.toString(), (String)null);
stmt.setInt(1, tableId); stmt.setInt(1, tableId);
stmt.setInt(2, recordId); stmt.setInt(2, recordId);
stmt.setString(3, recordUU);
rs = stmt.executeQuery(); rs = stmt.executeQuery();
List<String> columns = null; List<String> columns = null;
List<Integer> columnIds = null; List<Integer> columnIds = null;

View File

@ -178,7 +178,7 @@ public class Archive {
sql.append(" AND ((AD_Table_ID=").append(m_AD_Table_ID); sql.append(" AND ((AD_Table_ID=").append(m_AD_Table_ID);
if (m_Record_ID > 0) if (m_Record_ID > 0)
sql.append(" AND Record_ID=").append(m_Record_ID); sql.append(" AND Record_ID=").append(m_Record_ID);
if (!Util.isEmpty(m_Record_UU)) else if (!Util.isEmpty(m_Record_UU))
sql.append(" AND Record_UU=").append(DB.TO_STRING(m_Record_UU)); sql.append(" AND Record_UU=").append(DB.TO_STRING(m_Record_UU));
sql.append(")"); sql.append(")");
if (m_AD_Table_ID == MBPartner.Table_ID && m_Record_ID > 0) if (m_AD_Table_ID == MBPartner.Table_ID && m_Record_ID > 0)