public class Order {
...
// 此方法必须返回 true 才能使此订单有效
@AssertTrue(message="customer_order_invoice_must_match")
private boolean isInvoiceCustomerMatches() {
return invoice == null || // invoice is optional
invoice.getCustomer().getNumber() == getCustomer().getNumber();
}
}
您还必须将消息添加到 src/main/resources/i18n/invoicing-messages_zh.properties:customer_order_invoice_must_match=发票和订单的客户必须匹配
public class Order extends CommercialDocument {
@ManyToOne
@ReferenceView("NoCustomerNoOrders")
@OnChange(ShowHideCreateInvoiceAction.class)
@SearchAction("Order.searchInvoice") // 定义我们自己的动作来搜索发票
Invoice invoice;
...
}
通过这简单的方式,我们定义了当用户点击搜索发票时要执行的动作。 @SearchAction 的参数 Order.searchInvoice 是动作的名称,也就是在 controllers.xml 文件中定义的 Order 控制器的 searchInvoice 动作。现在我们必须编辑 controllers.xml 来添加我们新动作的定义:
<controller name="Order">
...
<action name="searchInvoice"
class="com.yourcompany.invoicing.actions.SearchInvoiceFromOrderAction"
hidden="true" icon="magnify"/>
<!--
hidden="true" : 因为我们不希望动作显示在模块的按钮栏中
icon="magnify" : 与标准搜索动作相同的图标
-->
</controller>
我们的动作从 ReferenceSearchAction 扩展而来,如下:
package com.yourcompany.invoicing.actions; // 在 actions 包
import org.openxava.actions.*; // 用于使用 ReferenceSearchAction
public class SearchInvoiceFromOrderAction
extends ReferenceSearchAction { // 搜索参照时的标准逻辑
public void execute() throws Exception {
int customerNumber =
getView().getValueInt("customer.number"); // 从视图中读取当前订单的客户编号
super.execute(); // 它会执行显示对话框的标准逻辑
if (customerNumber > 0) { // 如果有客户我们就用它
getTab().setBaseCondition("${customer.number} = " + customerNumber);
}
}
}
可以看到我们如何使用 getTab().setBaseCondition() 为列表选参照而建立条件。也就是说,您可以从 ReferenceSearchAction 使用 getTab() 来操纵列表的行为。abstract public class CommercialDocument extends Deletable {
@SearchKey // 添加此注解
@Column(length=4)
@DefaultValueCalculator(CurrentYearCalculator.class)
int year;
@SearchKey // 添加此注解
@Column(length=6)
@ReadOnly
int number;
...
}
这样,当用户从参照搜索订单或发票时,他必须输入年份和编号,然后相应的实体将从数据库中填充至用户界面。
public class Order extends CommercialDocument {
@ManyToOne
@ReferenceView("NoCustomerNoOrders")
@OnChange(ShowHideCreateInvoiceAction.class)
@OnChangeSearch(OnChangeSearchInvoiceAction.class) // 添加此注解
@SearchAction("Order.searchInvoice")
Invoice invoice;
...
}
从现在开始,当用户输入发票的新年份和编号时,将执行 OnChangeSearchInvoiceAction 的逻辑。此动作会从数据库中读取发票数据并更新用户界面。以下是动作的代码:
package com.yourcompany.invoicing.actions; // 在 actions 包
import java.util.*;
import org.openxava.actions.*; // 用于使用 OnChangeSearchAction
import org.openxava.model.*;
import org.openxava.view.*;
import com.yourcompany.invoicing.model.*;
public class OnChangeSearchInvoiceAction
extends OnChangeSearchAction { // 当搜索参照而用户界面中的键值有变化时的标准逻辑 (1)
public void execute() throws Exception {
super.execute(); // 它执行标准逻辑(2)
Map keyValues = getView()// getView() 这里是参照的视图,不是主视图 (3)
.getKeyValuesWithValue();
if (keyValues.isEmpty()) return; // 如果 key 为空,则不执行额外的逻辑
Invoice invoice = (Invoice) // 我们从输入的键搜索 Invoice 实体 (4)
MapFacade.findEntity(getView().getModelName(), keyValues);
View customerView = getView().getRoot().getSubview("customer"); // (5)
int customerNumber = customerView.getValueInt("number");
if (customerNumber == 0) { // 如果没有客户,我们填 (6)
customerView.setValue("number", invoice.getCustomer().getNumber());
customerView.refresh();
}
else { // 如果已经有客户,我们验证他是否跟发票的客户匹配 (7)
if (customerNumber != invoice.getCustomer().getNumber()) {
addError("invoice_customer_not_match",
invoice.getCustomer().getNumber(), invoice, customerNumber);
getView().clear();
}
}
}
}
鉴于该动作从 OnChangeSearchAction (1) 扩展而来,并且我们使用 super.execute() (2),它的行为只是标准方式,也就是当用户输入年份和编号时,发票的数据会填充侧用户界面。之后,我们使用 getView() (3) 获取所显示发票的 key 以使用 MapFacade (4) 找到对应的实体。从 OnChangeSearchAction getView() 内部返回是参照的子视图,而不是全局视图。因此,在这种情况下 getView() 是参照发票的视图。这允许您创建更多可重用的 @OnChangeSearch 动作。因此,您必须编写 getView().getRoot().getSubview("customer") (5) 才能访问客户视图。invoice_customer_not_match=发票 {1} 的客户编号 {0} 与当前订单的客户编号 {2} 不匹配
@OnChangeSearch 的有趣之处在于,当从列表中选发票时也会执行,因为在这种情况下,发票的年份和编号也会发生变化。因此,这是一个优化参照并填充视图的逻辑。
public class Invoice extends CommercialDocument {
@OneToMany(mappedBy="invoice")
@CollectionView("NoCustomerNoInvoice")
@AddAction("Invoice.addOrders") // 定义我们的添加订单的动作
Collection<Order> orders;
...
}
通过这简单的方式,我们定义了当用户点击添加订单时要执行的动作。 @AddAction 的参数 Invoice.addOrders 是动作的名称,也就是在 controllers.xml 文件中 Invoice 控制器定义的 addOrders 动作。<controller name="Invoice">
<extends controller="Invoicing"/>
<action name="addOrders"
class="com.yourcompany.invoicing.actions.GoAddOrdersToInvoiceAction"
hidden="true" icon="table-row-plus-after"/>
<!--
hidden="true" : 因为我们不希望动作显示在模块的按钮栏中
icon="table-row-plus-after" : 与标准動作相同的图标
-->
</controller>
这是动作的代码:
package com.yourcompany.invoicing.actions; // 在 actions 包
import org.openxava.actions.*; // 用于使用 GoAddElementsToCollectionAction
public class GoAddOrdersToInvoiceAction
extends GoAddElementsToCollectionAction { // 到列表的标准逻辑(将元素添加到集合的列表)
public void execute() throws Exception {
super.execute(); // 它执行标准逻辑,显示一个对话框
int customerNumber =
getPreviousView() // getPreviousView() 是主视图(我们在一个对话框中)
.getValueInt("customer.number"); // 读取当前发票视图的客户编号
getTab().setBaseCondition( // 要添加的订单列表的条件
"${customer.number} = " + customerNumber +
" and ${delivered} = true and ${invoice} is null"
);
}
}
请看我们如何使用 getTab().setBaseCondition() 在列表建立条件以选择要添加的实体。也就是说,您可以从 GoAddElementsToCollectionAction 使用 getTab() 来操作列表的行为方式。
public class GoAddOrdersToInvoiceAction ... {
...
public String getNextController() { // 添加这个方法
return "AddOrdersToInvoice"; // 具有动作的控制器(添加订单列表中的动作)
}
}
默认情况下,我们在实体列表中要添加的动作(添加和取消按钮)来自 OpenXava 标准控制器 AddToCollection。我们在动作中覆盖 getNextController() 以允许我们定义自己的控制器。请在 controllers.xml 中定义我们的控制器:
<controller name="AddOrdersToInvoice">
<extends controller="AddToCollection" /> <!-- 从标准控制器扩展 -->
<!-- 覆盖 add 的动作 -->
<action name="add"
class="com.yourcompany.invoicing.actions.AddOrdersToInvoiceAction" />
</controller>
这样,将订单添加到发票的动作是 AddOrdersToInvoiceAction。请记住,我们动作的目标是以通用的方式将订单添加到发票中,并且还将这些订单的详情复制到发票中。以下是动作的代码:package com.yourcompany.invoicing.actions; // 在 actions 包中
import java.rmi.*;
import java.util.*;
import javax.ejb.*;
import org.openxava.actions.*; // 用于使用 AddElementsToCollectionAction
import org.openxava.model.*;
import org.openxava.util.*;
import org.openxava.validators.*;
import com.yourcompany.invoicing.model.*;
public class AddOrdersToInvoiceAction
extends AddElementsToCollectionAction { // 将元素添加集合的标准逻辑
public void execute() throws Exception {
super.execute(); // 我们如实使用标准逻辑
getView().refresh(); // 用于显示新数据,包括重新计算的税额等
}
protected void associateEntity(Map keyValues) // 用於关联的方法,在本例中将每个订单关联到发票
throws ValidationException,
XavaException, ObjectNotFoundException,
FinderException, RemoteException
{
super.associateEntity(keyValues); //它执行标准逻辑 (1)
Order order = (Order) MapFacade.findEntity("Order", keyValues); // (2)
order.copyDetailsToInvoice(); // 将主要工作委托给实体 (3)
}
}
我们覆盖 execute() 方法只是为了在执行后刷新视图。我们想要的是优化订单与发票关联的逻辑。为此我们得覆盖 associateEntity() 方法。而这里的逻辑很简单,在执行标准逻辑(1)之后,我们搜索对应的 Order 实体,然后为该Order 调用 copyDetailsToInvoice()。幸运的是,我们已经有了将订单中的详情复制到发票的方法,我们只需调用此方法即可。