Difference between revisions of "ZH/Case-Study-01-Journal-31"

From ADempiere
Jump to: navigation, search
This Wiki is read-only for reference purposes to avoid broken links.
(弹出对话框)
(链接)
Line 132: Line 132:
 
# Script响应速度明显要比编译好的Callout慢。
 
# Script响应速度明显要比编译好的Callout慢。
 
# Script的测试和排错的确不方便。
 
# Script的测试和排错的确不方便。
 +
 +
==2011-01-03==
 +
===冲突:脚本不完善导致与窗体新增记录无法保存===
 +
* 今天写脚本从 RfQ 询价单自动生成 Requisition 采购申请单,通过 SQL 实现,初步测试通过。
 +
* 但是进一步测试发现一个问题,通过脚本生成新的采购申请单后,在 Requisition 窗体上新建申请单保存失败,错误提示如下:
 +
<pre>
 +
===========> DB.executeUpdate: INSERT INTO M_Requisition (AD_Client_ID,AD_Org_ID,C_DocType_ID,Created,CreatedBy,DateDoc,
 +
DateRequired,DocAction,DocStatus,DocumentNo,IsActive,IsApproved,M_PriceList_ID,M_Requisition_ID,M_Warehouse_ID,Posted,PriorityRule,
 +
Processed,ProcessedOn,Processing,purchasingstaff_id,TotalLines,Updated,UpdatedBy) VALUES (11,50001,127,
 +
TO_TIMESTAMP('2011-01-03 13:16:25','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2011-01-03','YYYY-MM-DD'),
 +
TO_TIMESTAMP('2011-01-06','YYYY-MM-DD'),'CO','DR','TJ005','Y','N',101,1000002,50002,'N','5','N',0,'N',1000020,0,
 +
TO_TIMESTAMP('2011-01-03 13:16:25','YYYY-MM-DD HH24:MI:SS'),100) [POSave_a2f6503a-8143-410e-92b3-979f8c5f5b9e] [11]
 +
org.postgresql.util.PSQLException: ERROR: duplicate key value violates unique constraint "m_requisition_pkey"; State=23505; ErrorCode=0
 +
at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2062)
 +
... ...
 +
 +
===========> DB.saveError: DBExecuteError - ERROR: duplicate key value violates unique constraint "m_requisition_pkey" [11]
 +
-----------> MRequisition.saveNew: [POSave_a2f6503a-8143-410e-92b3-979f8c5f5b9e]Not inserted - M_Requisition [11]
 +
-----------> GridTable.saveWarning: SaveErrorNotUnique - ERROR: duplicate key value violates unique constraint "m_requisition_pkey" [11]
 +
</pre>
 +
 +
* 检查后初步认定与Requisition窗体保存新增记录时 m_requisition_id 的确认方法有关。于是去看窗体保存的代码,我找到 org.compiere.model.I_M_Requisition.java 与
 +
org.compiere.model.MRequisition.java 。 但是我没有找到有价值的信息,不知道是不是自己没有找对地方。
 +
* 于是折回去看 m_requisition 表中的数据,对比脚本生成的记录与窗体生成的记录的区别。
 +
* 结果发现有两个字段存在差异,窗体生成的记录当中 Processing = 'N', ProcessedOn=0,而我在脚本SQL中把这两个字段忽略了(留空)。
 +
* 于是将所有记录设置为 Processing = 'N'。然后再次测试,OK,问题解决。
 +
* 看来ADempiere里面暗藏的机关不少呀,还是得小心测试、反复验证为妙。
 +
===实现从RfQ生成Requistion===
 +
* 本节正在进行中,尚未完善... ...
 +
====新建一个中间表 Assist_Target_ID ====
 +
<pre>
 +
--创建一个中间表,用于存储中间数据。可以定期清除10天前的。
 +
--原因是 SELECT MAX(m_requisition_id) FROM m_requisition ) +1 不方便存储和赋值。
 +
-- 2011-01-03 Peanut Blake
 +
CREATE TABLE assist_target_id
 +
(
 +
  assist_mid_id numeric(10,0) NOT NULL PRIMARY KEY,
 +
  assist_target_id numeric(10,0) NOT NULL,
 +
  ad_client_id numeric(10,0) NOT NULL,
 +
  ad_table_name character varying(50),
 +
  ad_user_id numeric(10,0),
 +
  created timestamp without time zone NOT NULL DEFAULT now()
 +
);
 +
</pre>
 +
====脚本====
 +
<pre>
 +
/* ADempiere ERP 3.6.0
 +
* beanshell:M_RfQ_Requisition_Create
 +
* Create Requisition from RfQ
 +
* Create: Peanut Blake. DEC 30, 2010.
 +
* */
 +
 +
import org.compiere.util.*;
 +
import java.util.Random;
 +
 +
/* get randon*/
 +
Random ran_my=new Random();
 +
Integer assist_mid_id=Math.abs(ran_my.nextInt());
 +
 +
String result_msg;
 +
 +
/* insert assist_target_id */
 +
String sql_assist;
 +
sql_assist="INSERT INTO assist_target_id ( assist_mid_id, assist_target_id, ad_client_id,  ad_table_name, AD_User_ID) ";
 +
sql_assist += "VALUES ( " + assist_mid_id + ", (SELECT MAX(m_requisition_id) FROM m_requisition ) +1, " ;
 +
sql_assist += A_AD_Client_ID + ", 'm_requisition', "  +A_AD_User_ID + " );";
 +
 +
boolean condition_ok=false;
 +
if( P_C_RfQ_ID==void || P_Requistion_No==void || P_M_PriceList_ID == void)
 +
result_msg="Please fill the required fields!";
 +
else
 +
condition_ok=true;
 +
 +
/* INSERT m_requisition*/
 +
String sql_head;
 +
sql_head="INSERT INTO m_requisition ";
 +
sql_head += "(m_requisition_id,AD_Client_ID,AD_Org_ID,createdby,updatedby,documentno, c_rfq_id, Description,Help,m_pricelist_id,";
 +
sql_head += " m_warehouse_id, priorityrule, daterequired, docaction,docstatus,c_doctype_id, purchasingstaff_id, processing, processedon, c_bpartner_id_a,c_bpartner_id_b ) ";
 +
sql_head += " SELECT ( SELECT assist_target_id FROM assist_target_id WHERE assist_mid_id = " + assist_mid_id+ ") ,";
 +
sql_head += A_AD_Client_ID + "," + G_AD_Org_ID + "," + A_AD_User_ID + "," + A_AD_User_ID + ",'" + P_Requistion_No  + "',"+P_C_RfQ_ID+", (c_rfq.Name || c_rfq.Description) , c_rfq.Help ," + P_M_PriceList_ID + ",";
 +
sql_head += G_M_Warehouse_ID + ", 5, now(), 'CO','DR',127, "+ A_AD_User_ID+",'N',0,c_rfq.c_bpartner_id,c_rfq.c_bpartner_id_2 ";
 +
sql_head += "FROM c_rfq WHERE c_rfq_id=" + P_C_RfQ_ID + " AND ad_client_id=" + A_AD_Client_ID  + ";";
 +
 +
if ( condition_ok )
 +
{
 +
int no_assist = DB.executeUpdate(sql_assist, A_TrxName);
 +
int no_head = DB.executeUpdate(sql_head, A_TrxName);
 +
}
 +
 +
result = "OK";
 +
</pre>
  
 
=链接=
 
=链接=
  
 
[[Category:Chinese]]
 
[[Category:Chinese]]

Revision as of 22:25, 2 January 2011

<返回中文首页> <返回本案例研究首页>

实施日志-31-脚本编写与Rule引擎


2010-12-16

Rule Engine与脚本

  • 今天开始学习和探索Rule Engine(引擎)的Script(脚本编程)。
  • 主要目的:从于从RfQ(询价单)创建Purchase Request(采购申请)。
  • 介绍:ADempiere的Rule窗口可以写脚本,集成了BeanShell。
  • ADempiere的Rule Engine要用到BeanShell,于是了解了一下BeanShell。
  • 相关链接:

讨论栏

2010-12-17

Rule Engine下写脚本

  • 今天把Rule Engine(引擎)的Script弄清楚了。
  • Script Callout主要用于窗体,Script Process主要用于Process。
  • 对于从 询价单 生成 采购申请单,可以利用Script Process执行SQL语句来实现。
 org.compiere.util.DB.executeUpdate(sql, A_TrxName);

脚本使用体会

  1. 优点:脚本的功能很强大。同时脚本既不需要重新编译ADempiere源代码,也不需要重新布署ADempiere服务器,因为脚本规则都存储在数据库中了。
  2. 缺点:不容易进行错误调试。我今天测试的Script当中有个SQL语句语法错误,居然没有任何报错。

对Rule Engine的疑问

  • Rule Engine为什么要区分成不同Event Type?比如Callout与Process,为什么不合并在一起?
  • Rule Engine的Rule Type为什么是JSR 223 Scripting APIs,而不是BeanShell官方网站上JSR 274?
  • A_TrxName的TrxName是什么意思?
    • Hengsin的解答:
TrxName = Transaction Name. ADempiere transaction manager managed transaction via named transaction instead of the thread local transaction approach that J2EE and hibernate transaction manager used.Hengsin 09:09, 28 December 2010 (UTC)

弹出对话框

  • Rule Engine中弹出对话框,找到一段代码:
 javax.swing.JOptionPane.showMessageDialog(null,"Doing something in Callout!");
  • 2010年12月28日测试了一下,在C/S下可以正常弹出,但是在B/S下会报错。
  • 能否实现在C/S和B/S都可以弹出对话框?
    • Hengsin的解答:
At the moment, you have to check Ini.isClient() and use reflection to call zkwebui code. Hengsin 09:04, 28 December 2010 (UTC)

2010-12-28

自动选择最低报价

界面

  • 今天用Rule引擎编写脚本,实现了一个功能——通过比较价格1与价格2,自动选择最低报价。
  • 自动选择最低报价-询价单明细-网格界面:

自动选择最低报价-询价单明细-网格界面

  • 自动选择最低报价-询价单明细-窗体界面

自动选择最低报价-询价单明细-窗体界面


具体实现

  • Rule引擎:

自动选择最低报价-Rule引擎

  • Search Key: beanshell:C_RfQ_AutoChooseLowerPrice
  • Name: Auto Choose Lower Price in RfQ
  • Description: Auto choose lower price in RfQ Line, by comparing Price 1 with Price 2.
  • Comment/Help: Last update: Dec 28, 2010. Ver 1.1. Peanut
  • Event Type: Callout
  • Rule Type: JSR 223 Scripting APIs
  • Script:
int rowCount = A_Tab.getRowCount(); //统计行数

for(int i = 0; i< rowCount; i++){

   A_Tab.setCurrentRow(i);  //设置为当前行

   int iCompare=A_Tab.getValue("Price").compareTo(A_Tab.getValue("Price2")); //比较价格

   if (iCompare==-1)
      A_Tab.setValue("ChooseQuotation", "A"); // Price 1 < Price 2,则选择供应商A
   else if (iCompare==1)
      A_Tab.setValue("ChooseQuotation", "B"); // Price 1 > Price 2,则选择供应商B
}

result = "";

脚本使用问题小结

Yes/No字段

  • Yes/No字段不能直接赋值给字符字段,要转为String才行,可以使用方法 Boolean.toString() 。示例:
  A_Tab.setValue("Purpose",A_Tab.getValue("SelectBPartnerA").toString()); 

Number对象的处理

  • 对于Number、Cost+Price、Quantity等对象,ADempiere是作为 java.math.BigDecimal 对象处理的。
  • BigDecimal 对象不能像 float 一样 BigDecimal_1 >= BigDecimal_2 来进行比较,而是要通过 BigDecimal.compareTo(BigDecimal val) 方法来比较。
  • 我一开始是直接比较:
 if (A_Tab.getValue("Price") > A_Tab.getValue("Price2") ) 
    A_Tab.setValue("ChooseQuotation", "B");
  • 于是得到错误代码:
===========> GridTab.processCallout:  [12]
javax.script.ScriptException: Sourced file: inline evaluation of: ``int rowCount = A_Tab.getRowCount();
 float priceA; float priceB; for(int i = 0; i . . . '' : Invalid types in binary operator : at Line: 8 :
 in file: inline evaluation of: ``int rowCount = A_Tab.getRowCount(); float priceA; float priceB; 
for(int i = 0; i . . . '' : ) { 
 in inline evaluation of: ``int rowCount = A_Tab.getRowCount(); float priceA; float priceB; for(int 
i = 0; i . . . '' at line number 8
	at bsh.engine.BshScriptEngine.evalSource(BshScriptEngine.java:92)
  • 后来尝试直接转为float:
float priceA=(float) A_Tab.getValue("Price");
float priceB=(float) A_Tab.getValue("Price2");
  • 于是得到错误代码:
java.awt.LightweightDispatcher.dispatchEvent(Container.java:4168)
caused by: java.lang.ClassCastException: Cannot cast java.math.BigDecimal to float
  • 这时才明白ADempiere将 Number 作为 BigDecimal 对象处理的。要想转为 float 的话可以用 BigDecimal.floatValue() 方法。

脚本使用体会

  1. Script响应速度明显要比编译好的Callout慢。
  2. Script的测试和排错的确不方便。

2011-01-03

冲突:脚本不完善导致与窗体新增记录无法保存

  • 今天写脚本从 RfQ 询价单自动生成 Requisition 采购申请单,通过 SQL 实现,初步测试通过。
  • 但是进一步测试发现一个问题,通过脚本生成新的采购申请单后,在 Requisition 窗体上新建申请单保存失败,错误提示如下:
===========> DB.executeUpdate: INSERT INTO M_Requisition (AD_Client_ID,AD_Org_ID,C_DocType_ID,Created,CreatedBy,DateDoc,
DateRequired,DocAction,DocStatus,DocumentNo,IsActive,IsApproved,M_PriceList_ID,M_Requisition_ID,M_Warehouse_ID,Posted,PriorityRule,
Processed,ProcessedOn,Processing,purchasingstaff_id,TotalLines,Updated,UpdatedBy) VALUES (11,50001,127,
TO_TIMESTAMP('2011-01-03 13:16:25','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2011-01-03','YYYY-MM-DD'),
TO_TIMESTAMP('2011-01-06','YYYY-MM-DD'),'CO','DR','TJ005','Y','N',101,1000002,50002,'N','5','N',0,'N',1000020,0,
TO_TIMESTAMP('2011-01-03 13:16:25','YYYY-MM-DD HH24:MI:SS'),100) [POSave_a2f6503a-8143-410e-92b3-979f8c5f5b9e] [11]
org.postgresql.util.PSQLException: ERROR: duplicate key value violates unique constraint "m_requisition_pkey"; State=23505; ErrorCode=0
	at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2062)
	... ...

===========> DB.saveError: DBExecuteError - ERROR: duplicate key value violates unique constraint "m_requisition_pkey" [11]
-----------> MRequisition.saveNew: [POSave_a2f6503a-8143-410e-92b3-979f8c5f5b9e]Not inserted - M_Requisition [11]
-----------> GridTable.saveWarning: SaveErrorNotUnique - ERROR: duplicate key value violates unique constraint "m_requisition_pkey" [11]
  • 检查后初步认定与Requisition窗体保存新增记录时 m_requisition_id 的确认方法有关。于是去看窗体保存的代码,我找到 org.compiere.model.I_M_Requisition.java 与

org.compiere.model.MRequisition.java 。 但是我没有找到有价值的信息,不知道是不是自己没有找对地方。

  • 于是折回去看 m_requisition 表中的数据,对比脚本生成的记录与窗体生成的记录的区别。
  • 结果发现有两个字段存在差异,窗体生成的记录当中 Processing = 'N', ProcessedOn=0,而我在脚本SQL中把这两个字段忽略了(留空)。
  • 于是将所有记录设置为 Processing = 'N'。然后再次测试,OK,问题解决。
  • 看来ADempiere里面暗藏的机关不少呀,还是得小心测试、反复验证为妙。

实现从RfQ生成Requistion

  • 本节正在进行中,尚未完善... ...

新建一个中间表 Assist_Target_ID

--创建一个中间表,用于存储中间数据。可以定期清除10天前的。
--原因是 SELECT MAX(m_requisition_id) FROM m_requisition ) +1 不方便存储和赋值。
-- 2011-01-03 Peanut Blake
CREATE TABLE assist_target_id
(
  assist_mid_id numeric(10,0) NOT NULL PRIMARY KEY,
  assist_target_id numeric(10,0) NOT NULL,
  ad_client_id numeric(10,0) NOT NULL,
  ad_table_name character varying(50),
  ad_user_id numeric(10,0),
  created timestamp without time zone NOT NULL DEFAULT now()
);

脚本

/* ADempiere ERP 3.6.0
 * beanshell:M_RfQ_Requisition_Create
 * Create Requisition from RfQ
 * Create: Peanut Blake. DEC 30, 2010.
 * */

import org.compiere.util.*;
import java.util.Random;

/* get randon*/
Random ran_my=new Random();
Integer assist_mid_id=Math.abs(ran_my.nextInt());

String result_msg;

/* insert assist_target_id */
String sql_assist;
sql_assist="INSERT INTO assist_target_id ( assist_mid_id, assist_target_id, ad_client_id,  ad_table_name, AD_User_ID) ";
sql_assist += "VALUES ( " + assist_mid_id + ", (SELECT MAX(m_requisition_id) FROM m_requisition ) +1, " ;
sql_assist += A_AD_Client_ID + ", 'm_requisition', "  +A_AD_User_ID + " );";

boolean condition_ok=false;
if( P_C_RfQ_ID==void || P_Requistion_No==void || P_M_PriceList_ID == void)
	result_msg="Please fill the required fields!";
else
	condition_ok=true;

/* INSERT m_requisition*/
String sql_head;
sql_head="INSERT INTO m_requisition ";
sql_head += "(m_requisition_id,AD_Client_ID,AD_Org_ID,createdby,updatedby,documentno, c_rfq_id, Description,Help,m_pricelist_id,";
sql_head += " m_warehouse_id, priorityrule, daterequired, docaction,docstatus,c_doctype_id, purchasingstaff_id, processing, processedon, c_bpartner_id_a,c_bpartner_id_b ) ";
sql_head += " SELECT ( SELECT assist_target_id FROM assist_target_id WHERE assist_mid_id = " + assist_mid_id+ ") ,";
sql_head += A_AD_Client_ID + "," + G_AD_Org_ID + "," + A_AD_User_ID + "," + A_AD_User_ID + ",'" + P_Requistion_No  + "',"+P_C_RfQ_ID+", (c_rfq.Name || c_rfq.Description) , c_rfq.Help ," + P_M_PriceList_ID + ",";
sql_head += G_M_Warehouse_ID + ", 5, now(), 'CO','DR',127, "+ A_AD_User_ID+",'N',0,c_rfq.c_bpartner_id,c_rfq.c_bpartner_id_2 ";
sql_head += "FROM c_rfq WHERE c_rfq_id=" + P_C_RfQ_ID + " AND ad_client_id=" + A_AD_Client_ID  + ";";

if ( condition_ok )
{
	int no_assist = DB.executeUpdate(sql_assist, A_TrxName);
	int no_head = DB.executeUpdate(sql_head, A_TrxName);	
}

result = "OK";

链接