物料清单是是描述企业产品组成的技术文件。在加工资本式行业,它表明了产品的总装件、分装件、组件、部件、零件、直到原材料之间的结构关系,以及所需的数量。在化工、制药和食品行业产品组成则对主要原料、中间体、辅助材料及其配方和所需数量的说明。BOM是将用图表示的产品组成改用数据表格的形式表示出来,它是MRPII系统中计算MRP过程中的重要控制文件。
原ERP系统需求。本次版本的所有功能需求都是建立在老版本ERP之上的,所以本文档的功能需求以及业务逻辑完全COPY原来版本,而修改的是实现方式。
树的设计
首先,新建一个物料清单(以下简称BOM)的时候,我们设计的页面是左边的树结构没有数据,而在一张BOM单据保存建立之后,相应的页面左边的树结构将会把本BOM所对应的BOM母件和子件以及子件的BOM信息数据加载过来,并且在页面的左下部其BOM单据的母件图片也会显示出来(如图示1、2)。
图示1(BOM新建时的页面显示状态)
图示2(BOM保存后生成单据后页面树结构显示状态)
另外,我们要在这里分析以下这个数的数据结构。首先从图示2中可以看到BOM树的根节点的文本是母件的品名和BOM版本号拼接起来的一个文本字符串,而子节点的文本是当前BOM下的所有子件品名信息,但是其中的子件文本信息还是有条件的显示的。如果这个子件单纯的是个子件的话,那就把子件品名信息文本显示出来就行了,且没有下级结点;而当该子件本身是一个BOM的是时候我们要分两种情况来判断他在当前BOM的显示状态,即:如果该子件的BOM有多个版本(两个或两个以上版本)的话,子件的显示状态是显示子件品名文本且该子节点有下级结点,那么这个子件的下级结点应该是该子件所有不同版本BOM的信息(该子件BOM信息也是由子件名称和相应的版本号拼接起来的);而如果该子件BOM只有一个版本的话呢,我们只需要把该子件BOM信息(由子件名称和相应的版本号拼接起来的文本)替代该子件的品名来显示(图示2就是这种情况)。
树的代码实现
首先,在ASPX页面上要用到EXT中的TreePanel控件,这个控件的属性以及相关事件的代码截图如:
图示2
从上面的截图看到,这棵树的根节点是一个异步树节点,而且在这个树控件上次根节点默认为不显示的;另外,在这棵树中好定义了一个BeforeLoad监听事件,这个事件的作用是加载当前树节点中其子节点的数据,而且吧这些数据装载到这棵树中。具体代码如下:
function nodeLoad(node) {//参数node是当前展开的节点对象
var billid = document.getElementById('txtIdentityID').value;//BOM单据的BillID
if (billid > 0) {
var splitIndex = node.id.indexOf("_");//请注意树节点的数据结构:
if (splitIndex == -1) {
//发送一次请求到服务器端进行数据处理
Ext.net.DirectMethods.NodeLoad(billid, node.id, {
success: function(result) {
var data = eval("(" + result + ")");
node.loadNodes(data);
},
failure: function(errorMsg) {
Ext.Msg.alert('Failure', errorMsg);
}
});
}
else {
var splitstr = node.id.split("_");
if (splitstr[0] == "1") {
//发送一次请求到服务器端进行数据处理
Ext.net.DirectMethods.AddSubModules(splitstr[1], {
success: function(result) {
var data = eval("(" + result + ")");
node.loadNodes(data);
},
failure: function(errorMsg) {
Ext.Msg.alert('Failure', errorMsg);
}
});
}
if (splitstr[0] == '11') {
//发送一次请求到服务器端进行数据处理
Ext.net.DirectMethods.NodeLoad(splitstr[1], splitstr[1], {
success: function(result) {
var data = eval("(" + result + ")");
node.loadNodes(data);
},
failure: function(errorMsg) {
Ext.Msg.alert('Failure', errorMsg);
}
});
}
}
}
}
可以看到在这个方法中,有几次条件判断以及在不同条件下执行了不同的请求;这些条件是通过树节点的nodeId来建立的,nodeId的数据结构是这样子的:DOM根节点(DOM母件节点)的nodeID是此BOM单据的ID,而其子节点的nodeID是有多个字符串拼接起来的,具体可以看一下下面的服务器端代码(注意红色标记部分):
#region 绑定Bom清单
[DirectMethod]
public string NodeLoad(int BomID, int nodeID)
{
string companyCD = ((UserInfoUtil)SessionUtil.Session["UserInfo"]).CompanyCD;
BomModel model = new BomModel();
model.CompanyCD = companyCD;
model.ID = BomID;
DataTable dt = BomBus.GetBomsInfoByID(model);
Ext.Net.TreeNodeCollection nodes = new Ext.Net.TreeNodeCollection();
if (dt.Rows.Count > 0)
{
string Bom_Nodeid = dt.Rows[0]["ID"].ToString();
string strDateTime = DateTime.Now.ToString("yyyyMMddhhmmss");
if (nodeID == 0)
{
Ext.Net.AsyncTreeNode asyncNode = new AsyncTreeNode();
if (!string.IsNullOrEmpty(dt.Rows[0]["Verson"].ToString()))
{
asyncNode.Text = dt.Rows[0]["ProductName"].ToString() + "(" + dt.Rows[0]["Verson"].ToString() + ")";
}
else
{
asyncNode.Text = dt.Rows[0]["ProductName"].ToString();
}
asyncNode.NodeID = dt.Rows[0]["ID"].ToString();//BOM根节点的NODEID
asyncNode.Listeners.Click.Handler = "LoadBomInfo(" + asyncNode.NodeID.ToString() + ",'checktree')";
nodes.Add(asyncNode);
}
else
{
foreach (DataRow row in dt.Select("ID='0'"))
{
Ext.Net.AsyncTreeNode asyncSubNode = new AsyncTreeNode();
if (row["IsBasicBom"].ToString() == "0")
{
asyncSubNode.Text = row["ProductName"].ToString();
asyncSubNode.NodeID = "0_" + row["ProductID"].ToString() + "_" + Bom_Nodeid + "_" + strDateTime;//一个子节点的NODEID
asyncSubNode.Leaf = true;
asyncSubNode.Listeners.Click.Handler = "LoadBomInfo(" + Bom_Nodeid.ToString() + ",'checktree')";
}
else
{
BomModel model1 = new BomModel();
model1.CompanyCD = companyCD;
model1.ProductID = row["ProductID"].ToString();
DataTable dt1 = BomBus.GetBomsByProductID(model1);
if (dt1.Rows.Count == 1)
{
if (!string.IsNullOrEmpty(dt1.Rows[0]["Verson"].ToString()))
{
asyncSubNode.Text = dt1.Rows[0]["ProductName"].ToString() + "(" + dt1.Rows[0]["Verson"].ToString() + ")";
}
else
{
asyncSubNode.Text = dt1.Rows[0]["ProductName"].ToString();
}
asyncSubNode.NodeID = "11_" + dt1.Rows[0]["ID"].ToString() + "_" + strDateTime;//子节点的NODEID
asyncSubNode.Listeners.Click.Handler = "LoadBomInfo(" + dt1.Rows[0]["ID"].ToString() + ",'checktree')";
}
else
{
asyncSubNode.Text = row["ProductName"].ToString();
asyncSubNode.NodeID = "1_" + row["ProductID"].ToString() + "_" + Bom_Nodeid + "_" + strDateTime;子节点的NODEID
asyncSubNode.Listeners.Click.Handler = "LoadBomInfo(" + Bom_Nodeid.ToString() + ",'checktree')";
}
}
nodes.Add(asyncSubNode);
}
}
}
return nodes.ToJson();
}
//递归
[DirectMethod]
public string AddSubModules(string productid)
{
string companyCD = ((UserInfoUtil)SessionUtil.Session["UserInfo"]).CompanyCD;
Ext.Net.TreeNodeCollection nodes = new Ext.Net.TreeNodeCollection();
string strDateTime = DateTime.Now.ToString("yyyyMMddhhmmss");
BomModel model = new BomModel();
model.CompanyCD = companyCD;
model.ProductID = productid;
DataTable dt = BomBus.GetBomsByProductID(model);
//DataRow[] rows = userModules.Select("SuperDeptID = '" + moduleId + "'");
foreach (DataRow row in dt.Rows)
{
Ext.Net.AsyncTreeNode asyncNode = new Ext.Net.AsyncTreeNode();
if (!string.IsNullOrEmpty(row["Verson"].ToString()))
{
asyncNode.Text = row["ProductName"].ToString() + "(" + row["Verson"].ToString() + ")";
}
else
{
asyncNode.Text = row["ProductName"].ToString();
}
asyncNode.NodeID = "11_" + row["ID"].ToString() + "_" + strDateTime;
asyncNode.Listeners.Click.Handler = "LoadBomInfo(" + row["ID"].ToString() + ",'checktree')";
nodes.Add(asyncNode);
}
return nodes.ToJson();
}
#endregion
这些红色字体标记部分就是通过“标记_子件物品ID或子件BOMID_当前时间”的格式拼接起来的,这样的拼接可以保证个节点的NODEID是不一样的,并且“标记”元素的设计可以在JS的方法nodeLoad()中作为很好的判断条件,这个标记是这样设计的:当子件不是BOM时,标记=0;当子件只有一个版本BOM时,标记=11; 而当子件有多个版本的BOM时,标记=1。
我们接着看上面这段代码。这段代码是获取DOM树中数据的一个方法,是和JS中的方法nodeLoad()相关联的,且看上面两段代码(js和cs两部分代码)中标记为绿色字体部分,这部分代码是关联在一起的,首先在一定条件下JS端(浏览器端)可以分别通过一个请求直接调用了CS代码中的NodeLoad(int BomID, int nodeID)方法或者string AddSubModules(string productid)方法,这两个CS中的代码使用递归的方式来获取所需数据,之后返回到JS端(浏览器端)使其接收到数据后,使用node.loadNodes(data);来给树添加数据。
至此,整个BOM树的数据处理过程基本已经结束。其中的关键是对这棵树结构的理解。