Product Category Tree enhancement - as discussed here:

http://sourceforge.net/forum/forum.php?thread_id=1721178&forum_id=611158
This commit is contained in:
kthiemann 2007-04-24 12:04:22 +00:00
parent 4edc8f6676
commit a4da34825a
5 changed files with 482 additions and 0 deletions

View File

@ -0,0 +1,146 @@
/******************************************************************************
* Product: Adempiere ERP & CRM Smart Business Solution *
* Copyright (C) 1999-2006 ComPiere, Inc. All Rights Reserved. *
* This program is free software; you can redistribute it and/or modify it *
* under the terms version 2 of the GNU General Public License as published *
* by the Free Software Foundation. This program is distributed in the hope *
* that it will be useful, but WITHOUT ANY WARRANTY; without even the implied *
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *
* See the GNU General Public License for more details. *
* You should have received a copy of the GNU General Public License along *
* with this program; if not, write to the Free Software Foundation, Inc., *
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. *
* For the text or an alternative of this public license, you may reach us *
* ComPiere, Inc., 2620 Augustine Dr. #245, Santa Clara, CA 95054, USA *
* or via info@compiere.org or http://www.compiere.org/license.html *
*****************************************************************************/
package org.compiere.model;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Iterator;
import java.util.Properties;
import java.util.Vector;
import java.util.logging.Level;
import org.compiere.util.DB;
/**
* Product Category Callouts
*
* @author Karsten Thiemann kthiemann@adempiere.org
*
*/
public class CalloutProductCategory extends CalloutEngine
{
/**
* Loop detection of product category tree.
*
* @param ctx context
* @param WindowNo current Window No
* @param mTab Grid Tab
* @param mField Grid Field
* @param value New Value
* @return "" or error message
*/
public String testForLoop (Properties ctx, int WindowNo, GridTab mTab, GridField mField, Object value)
{
if (isCalloutActive() || value == null)
return "";
setCalloutActive(true);
// get values
Integer newParentCategoryId = (Integer) mTab.getValue(MProductCategory.COLUMNNAME_M_Product_Category_Parent_ID);
Integer productCategoryId = (Integer) mTab.getValue(MProductCategory.COLUMNNAME_M_Product_Category_ID);
String sql = " SELECT M_Product_Category_ID, M_Product_Category_Parent_ID FROM M_Product_Category";
final Vector<SimpleTreeNode> categories = new Vector<SimpleTreeNode>(100);
try {
Statement stmt = DB.createStatement();
ResultSet rs = stmt.executeQuery(sql);
while (rs.next()) {
if(rs.getInt(1)==productCategoryId.intValue()) {
categories.add(new SimpleTreeNode(rs.getInt(1), newParentCategoryId));
}
categories.add(new SimpleTreeNode(rs.getInt(1), rs.getInt(2)));
}
rs.close();
stmt.close();
if(hasLoop(newParentCategoryId, categories, productCategoryId)) {
mField.setValue(0, false);
setCalloutActive(false);
return "ProductCategoryLoopDetected";
}
} catch (SQLException e) {
log.log(Level.SEVERE, sql, e);
setCalloutActive(false);
return e.getMessage();
}
setCalloutActive(false);
return "";
} // testForLoop
/**
* Recursive search for parent nodes - climbes the to the root.
* If there is a circle there is no root but it comes back to the start node.
* @param parentCategoryId
* @param categories
* @param loopIndicatorId
* @return
*/
private boolean hasLoop(int parentCategoryId, Vector<SimpleTreeNode> categories, int loopIndicatorId) {
final Iterator iter = categories.iterator();
boolean ret = false;
while (iter.hasNext()) {
SimpleTreeNode node = (SimpleTreeNode) iter.next();
if(node.getNodeId()==parentCategoryId){
if (node.getParentId()==0) {
//root node, all fine
return false;
}
if(node.getNodeId()==loopIndicatorId){
//loop found
return true;
}
ret = hasLoop(node.getParentId(), categories, loopIndicatorId);
}
}
return ret;
} //hasLoop
/**
* Simple class for tree nodes.
* @author Karsten Thiemann, kthiemann@adempiere.org
*
*/
private class SimpleTreeNode {
/** id of the node */
private int nodeId;
/** id of the nodes parent */
private int parentId;
/**
* Constructor.
* @param nodeId
* @param parentId
*/
public SimpleTreeNode(int nodeId, int parentId) {
this.nodeId = nodeId;
this.parentId = parentId;
}
public int getNodeId() {
return nodeId;
}
public int getParentId() {
return parentId;
}
}
} // CalloutProductCategory

View File

@ -22,6 +22,7 @@ import java.math.*;
import java.sql.*;
import java.util.*;
import java.util.logging.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;
@ -616,9 +617,12 @@ public final class Find extends CDialog
// globalqss - Carlos Ruiz - 20060711
// fix a bug with virtualColumn + isSelectionColumn not yielding results
GridField field = getTargetMField(ColumnName);
boolean isProductCategoryField = isProductCategoryField(field.getAD_Column_ID());
String ColumnSQL = field.getColumnSQL(false);
if (value.toString().indexOf('%') != -1)
m_query.addRestriction(ColumnSQL, MQuery.LIKE, value, ColumnName, ved.getDisplay());
else if (isProductCategoryField && value instanceof Integer)
m_query.addRestriction(getSubCategoryWhereClause(((Integer) value).intValue()));
else
m_query.addRestriction(ColumnSQL, MQuery.EQUAL, value, ColumnName, ved.getDisplay());
/*
@ -698,6 +702,7 @@ public final class Find extends CDialog
String infoName = column.toString();
//
GridField field = getTargetMField(ColumnName);
boolean isProductCategoryField = isProductCategoryField(field.getAD_Column_ID());
String ColumnSQL = field.getColumnSQL(false);
// Op
Object op = advancedTable.getValueAt(row, INDEX_OPERATOR);
@ -730,12 +735,118 @@ public final class Find extends CDialog
m_query.addRangeRestriction(ColumnSQL, parsedValue, parsedValue2,
infoName, infoDisplay, infoDisplay_to);
}
else if (isProductCategoryField && MQuery.OPERATORS[MQuery.EQUAL_INDEX].equals(op)) {
if (!(parsedValue instanceof Integer)) {
continue;
}
m_query
.addRestriction(getSubCategoryWhereClause(((Integer) parsedValue).intValue()));
}
else
m_query.addRestriction(ColumnSQL, Operator, parsedValue,
infoName, infoDisplay);
}
} // cmd_save
/**
* Checks the given column.
* @param columnId
* @return true if the column is a product category column
*/
private boolean isProductCategoryField(int columnId) {
X_AD_Column col = new X_AD_Column(Env.getCtx(), columnId, null);
if (col.get_ID() == 0) {
return false; // column not found...
}
return MProduct.COLUMNNAME_M_Product_Category_ID.equals(col.getColumnName());
}
/**
* Returns a sql where string with the given category id and all of its subcategory ids.
* It is used as restriction in MQuery.
* @param productCategoryId
* @return
*/
private String getSubCategoryWhereClause(int productCategoryId) {
//if a node with this id is found later in the search we have a loop in the tree
int subTreeRootParentId = 0;
String retString = " M_Product_Category_ID IN (";
String sql = " SELECT M_Product_Category_ID, M_Product_Category_Parent_ID FROM M_Product_Category";
final Vector<SimpleTreeNode> categories = new Vector<SimpleTreeNode>(100);
try {
Statement stmt = DB.createStatement();
ResultSet rs = stmt.executeQuery(sql);
while (rs.next()) {
if(rs.getInt(1)==productCategoryId) {
subTreeRootParentId = rs.getInt(2);
}
categories.add(new SimpleTreeNode(rs.getInt(1), rs.getInt(2)));
}
retString += getSubCategoriesString(productCategoryId, categories, subTreeRootParentId);
retString += ") ";
rs.close();
stmt.close();
} catch (SQLException e) {
log.log(Level.SEVERE, sql, e);
retString = "";
} catch (AdempiereSystemError e) {
log.log(Level.SEVERE, sql, e);
retString = "";
}
return retString;
}
/**
* Recursive search for subcategories with loop detection.
* @param productCategoryId
* @param categories
* @param loopIndicatorId
* @return comma seperated list of category ids
* @throws AdempiereSystemError if a loop is detected
*/
private String getSubCategoriesString(int productCategoryId, Vector<SimpleTreeNode> categories, int loopIndicatorId) throws AdempiereSystemError {
String ret = "";
final Iterator iter = categories.iterator();
while (iter.hasNext()) {
SimpleTreeNode node = (SimpleTreeNode) iter.next();
if (node.getParentId() == productCategoryId) {
if (node.getNodeId() == loopIndicatorId) {
throw new AdempiereSystemError("The product category tree contains a loop on categoryId: " + loopIndicatorId);
}
ret = ret + getSubCategoriesString(node.getNodeId(), categories, loopIndicatorId) + ",";
}
}
log.fine(ret);
return ret + productCategoryId;
}
/**
* Simple tree node class for product category tree search.
* @author Karsten Thiemann, kthiemann@adempiere.org
*
*/
private class SimpleTreeNode {
private int nodeId;
private int parentId;
public SimpleTreeNode(int nodeId, int parentId) {
this.nodeId = nodeId;
this.parentId = parentId;
}
public int getNodeId() {
return nodeId;
}
public int getParentId() {
return parentId;
}
}
/**
* Parse Value
* @param field column

View File

@ -232,6 +232,27 @@ return ii.intValue();
}
/** Column name M_Product_Category_ID */
public static final String COLUMNNAME_M_Product_Category_ID = "M_Product_Category_ID";
/** M_Product_Category_Parent_ID AD_Reference_ID=163 */
public static final int M_PRODUCT_CATEGORY_PARENT_ID_AD_Reference_ID=163;
/** Set Parent Product Category.
@param M_Product_Category_Parent_ID Parent Product Category */
public void setM_Product_Category_Parent_ID (int M_Product_Category_Parent_ID)
{
if (M_Product_Category_Parent_ID <= 0) set_Value ("M_Product_Category_Parent_ID", null);
else
set_Value ("M_Product_Category_Parent_ID", Integer.valueOf(M_Product_Category_Parent_ID));
}
/** Get Parent Product Category.
@return Parent Product Category */
public int getM_Product_Category_Parent_ID()
{
Integer ii = (Integer)get_Value("M_Product_Category_Parent_ID");
if (ii == null) return 0;
return ii.intValue();
}
/** Column name M_Product_Category_Parent_ID */
public static final String COLUMNNAME_M_Product_Category_Parent_ID = "M_Product_Category_Parent_ID";
/** Set Name.
@param Name Alphanumeric identifier of the entity */
public void setName (String Name)

View File

@ -0,0 +1,102 @@
INSERT INTO ad_element
(ad_element_id, ad_client_id, ad_org_id, isactive,
created, createdby,
updated, updatedby,
columnname, entitytype, NAME,
printname
)
VALUES (50070, 0, 0, 'Y',
TO_DATE ('04/24/2007 12:30:00', 'MM/DD/YYYY HH24:MI:SS'), 100,
TO_DATE ('04/24/2007 12:30:00', 'MM/DD/YYYY HH24:MI:SS'), 100,
'M_Product_Category_Parent_ID', 'D', 'Parent Product Category',
'Parent Product Category'
);
INSERT INTO ad_column
(ad_column_id, ad_client_id, ad_org_id, isactive,
created,
updated, createdby,
updatedby, name, description, help, version,
entitytype, columnname, ad_table_id, ad_reference_id,
ad_reference_value_id,
fieldlength, iskey, isparent, ismandatory, isupdateable,
isidentifier, seqno, istranslated, isencrypted,
isselectioncolumn, ad_element_id, callout, issyncdatabase,
isalwaysupdateable
)
VALUES (50211, 0, 0, 'Y',
TO_DATE ('04/24/2007 12:30:00', 'MM/DD/YYYY HH24:MI:SS'),
TO_DATE ('04/24/2007 12:30:00', 'MM/DD/YYYY HH24:MI:SS'), 100,
100, 'Parent Product Category', 'Parent Product Category', 'The parent product category is used to build a category tree.', 1,
'D', 'M_Product_Category_Parent_ID', 209, 18,
163,
22, 'N', 'N', 'N', 'Y',
'N', 0, 'N', 'N',
'N', 50070, 'org.compiere.model.CalloutProductCategory.testForLoop', 'N',
'N'
);
INSERT INTO ad_field
(ad_field_id, ad_client_id, ad_org_id, isactive,
created, createdby,
updated, updatedby,
name, description, iscentrallymaintained, seqno, ad_tab_id,
ad_column_id, isdisplayed, displaylength, isreadonly,
issameline, isheading, isfieldonly, isencrypted, entitytype
)
VALUES (50181, 0, 0, 'Y',
TO_DATE ('04/24/2007 12:30:00', 'MM/DD/YYYY HH24:MI:SS'), 100,
TO_DATE ('04/24/2007 12:30:00', 'MM/DD/YYYY HH24:MI:SS'), 100,
'Parent Product Category', 'Parent Product Category', 'Y', 60, 189,
50211, 'Y', 22, 'N',
'N', 'N', 'N', 'N', 'D'
);
INSERT INTO ad_message
(ad_message_id, ad_client_id, ad_org_id, isactive,
created, createdby,
updated, updatedby,
value, msgtext, msgtype
)
VALUES (50014, 0, 0, 'Y',
TO_DATE ('04/24/2007 12:30:00', 'MM/DD/YYYY HH24:MI:SS'), 100,
TO_DATE ('04/24/2007 12:30:00', 'MM/DD/YYYY HH24:MI:SS'), 100,
'ProductCategoryLoopDetected',
'A loop in the product category tree has been detected - the old value will be restored','E'
);
COMMIT ;
UPDATE ad_sequence
SET currentnextsys = (SELECT MAX (ad_element_id) + 1
FROM ad_element
WHERE ad_element_id < 1000000)
WHERE NAME = 'AD_Element';
UPDATE ad_sequence
SET currentnextsys = (SELECT MAX (ad_column_id) + 1
FROM ad_column
WHERE ad_column_id < 1000000)
WHERE NAME = 'AD_Column';
UPDATE ad_sequence
SET currentnextsys = (SELECT MAX (ad_field_id) + 1
FROM ad_field
WHERE ad_field_id < 1000000)
WHERE NAME = 'AD_Field';
UPDATE ad_sequence
SET currentnextsys = (SELECT MAX (ad_message_id) + 1
FROM ad_message
WHERE ad_message_id < 1000000)
WHERE NAME = 'AD_Message';
ALTER TABLE M_Product_Category ADD M_Product_Category_Parent_ID NUMBER(10,0);
ALTER TABLE M_Product_Category ADD CONSTRAINT MProductCat_ParentCat FOREIGN KEY (M_Product_Category_Parent_ID)
REFERENCES M_Product_Category (M_Product_Category_ID);
UPDATE AD_Column SET IsSelectionColumn='Y' WHERE AD_Column_ID=2012;
COMMIT ;

View File

@ -0,0 +1,102 @@
INSERT INTO ad_element
(ad_element_id, ad_client_id, ad_org_id, isactive,
created, createdby,
updated, updatedby,
columnname, entitytype, NAME,
printname
)
VALUES (50070, 0, 0, 'Y',
TO_DATE ('04/24/2007 12:30:00', 'MM/DD/YYYY HH24:MI:SS'), 100,
TO_DATE ('04/24/2007 12:30:00', 'MM/DD/YYYY HH24:MI:SS'), 100,
'M_Product_Category_Parent_ID', 'D', 'Parent Product Category',
'Parent Product Category'
);
INSERT INTO ad_column
(ad_column_id, ad_client_id, ad_org_id, isactive,
created,
updated, createdby,
updatedby, name, description, help, version,
entitytype, columnname, ad_table_id, ad_reference_id,
ad_reference_value_id,
fieldlength, iskey, isparent, ismandatory, isupdateable,
isidentifier, seqno, istranslated, isencrypted,
isselectioncolumn, ad_element_id, callout, issyncdatabase,
isalwaysupdateable
)
VALUES (50211, 0, 0, 'Y',
TO_DATE ('04/24/2007 12:30:00', 'MM/DD/YYYY HH24:MI:SS'),
TO_DATE ('04/24/2007 12:30:00', 'MM/DD/YYYY HH24:MI:SS'), 100,
100, 'Parent Product Category', 'Parent Product Category', 'The parent product category is used to build a category tree.', 1,
'D', 'M_Product_Category_Parent_ID', 209, 18,
163,
22, 'N', 'N', 'N', 'Y',
'N', 0, 'N', 'N',
'N', 50070, 'org.compiere.model.CalloutProductCategory.testForLoop', 'N',
'N'
);
INSERT INTO ad_field
(ad_field_id, ad_client_id, ad_org_id, isactive,
created, createdby,
updated, updatedby,
name, description, iscentrallymaintained, seqno, ad_tab_id,
ad_column_id, isdisplayed, displaylength, isreadonly,
issameline, isheading, isfieldonly, isencrypted, entitytype
)
VALUES (50181, 0, 0, 'Y',
TO_DATE ('04/24/2007 12:30:00', 'MM/DD/YYYY HH24:MI:SS'), 100,
TO_DATE ('04/24/2007 12:30:00', 'MM/DD/YYYY HH24:MI:SS'), 100,
'Parent Product Category', 'Parent Product Category', 'Y', 60, 189,
50211, 'Y', 22, 'N',
'N', 'N', 'N', 'N', 'D'
);
INSERT INTO ad_message
(ad_message_id, ad_client_id, ad_org_id, isactive,
created, createdby,
updated, updatedby,
value, msgtext, msgtype
)
VALUES (50014, 0, 0, 'Y',
TO_DATE ('04/24/2007 12:30:00', 'MM/DD/YYYY HH24:MI:SS'), 100,
TO_DATE ('04/24/2007 12:30:00', 'MM/DD/YYYY HH24:MI:SS'), 100,
'ProductCategoryLoopDetected',
'A loop in the product category tree has been detected - the old value will be restored','E'
);
COMMIT ;
UPDATE ad_sequence
SET currentnextsys = (SELECT MAX (ad_element_id) + 1
FROM ad_element
WHERE ad_element_id < 1000000)
WHERE NAME = 'AD_Element';
UPDATE ad_sequence
SET currentnextsys = (SELECT MAX (ad_column_id) + 1
FROM ad_column
WHERE ad_column_id < 1000000)
WHERE NAME = 'AD_Column';
UPDATE ad_sequence
SET currentnextsys = (SELECT MAX (ad_field_id) + 1
FROM ad_field
WHERE ad_field_id < 1000000)
WHERE NAME = 'AD_Field';
UPDATE ad_sequence
SET currentnextsys = (SELECT MAX (ad_message_id) + 1
FROM ad_message
WHERE ad_message_id < 1000000)
WHERE NAME = 'AD_Message';
ALTER TABLE M_Product_Category ADD M_Product_Category_Parent_ID NUMERIC(10);
ALTER TABLE M_Product_Category ADD CONSTRAINT MProductCat_ParentCat FOREIGN KEY (M_Product_Category_Parent_ID)
REFERENCES M_Product_Category (M_Product_Category_ID);
UPDATE AD_Column SET IsSelectionColumn='Y' WHERE AD_Column_ID=2012;
COMMIT ;