
# 【背景】
部分客户的资金组织很多,业务单据查询时,经常需要选择并表单位的一些组织,或非并表单位的一些组织,很难选择。因此希望业务单据查询时,增加按资金组织视图过滤的条件。
# 【思考】
现有单据列表的过滤控件中,只允许添加本单据的字段,而“资金组织视图”字段不在本单据中,通过常规方法无法添加到过滤控件中。
# 【实现步骤】
## 1. 创建一个含有“资金组织视图”字段的单据


## 2. 创建单据列表插件
继承AbstractListPlugin,并重写父类方法filterContainerInit(过滤面板初始化)、filterContainerSearchClick(改变过滤条件事件)、setFilter(调整条件内容,追加条件)
- filterContainerInit:在过滤面板初始化时,手工构建“资金组织视图”过滤字段,并将该字段添加到过滤控件中的常用过滤控件中
```java
@Override
public void filterContainerInit(FilterContainerInitArgs args) {
super.filterContainerInit(args);
if (this.isBillNotHasMainOrg()){
// 列表绑定的单据,没有主业务单元字段:无需增加组织视图过滤字段
return;
}
this.initArgs = args;
this.orgFldId = this.getBillEntityType().getMainOrg() + ".id";
this.orgFldName = this.getBillEntityType().getMainOrg() + ".name";
// 增加组织视图过滤字段
List<FilterColumn> filterColumns = args.getCommonFilterColumns();
// 手工构建资金组织视图字段
CommonBaseDataFilterColumn orgViewColumn = this.buildOrgViewFilterColumn();
if(null != orgViewColumn && !filterColumns.contains(orgViewColumn)) {
// 将资金组织视图字段添加到常用过滤控件中,位置可自行调整,这儿放在首位
filterColumns.add(0, orgViewColumn);
initOrgCombos(filterColumns);
}
}
```
构建资金组织视图过滤字段的过程:
```java
/**
* 构建资金组织视图过滤条件
* @return
*/
private CommonBaseDataFilterColumn buildOrgViewFilterColumn() {
Class<CommonBaseDataFilterColumn> columnClass = CommonBaseDataFilterColumn.class;
CommonBaseDataFilterColumn orgViewColumn = null;
// 在FilterColumn类中,filterField字段是受保护的,无法直接设值,这儿使用反射设置filterField值
// realFilterField字段是在CommonFilterColumn类中
try {
Field filterField = columnClass.getSuperclass().getSuperclass().getDeclaredField("filterField");
Field realFilterField = columnClass.getSuperclass().getDeclaredField("realFilterField");
orgViewColumn = columnClass.newInstance();
ReflectionUtils.makeAccessible(filterField);
ReflectionUtils.makeAccessible(realFilterField);
// 这儿的实体类型fcs_orgview_test 就是自己创建的含有资金组织视图的实体
MainEntityType orgViewType = EntityMetadataCache.getDataEntityType("fcs_orgview_test");
// 创建过滤字段
FilterField filterFieldValue = FilterField.create(orgViewType, "orgview.name");
filterField.set(orgViewColumn, filterFieldValue);
realFilterField.set(orgViewColumn, filterFieldValue.convertToId());
// 设值资金组织视图过滤字段的属性
orgViewColumn.setEntityType(orgViewType);
orgViewColumn.setKey(ORGVIEWID);
orgViewColumn.setCaption(new LocaleString(ResManager.loadKDString("资金组织视图","TmcListAddOrgViewPlugin_01", "tmc-fbp-formplugin")));
orgViewColumn.setEntityField(false);
orgViewColumn.setFieldName(ORGVIEWID);
orgViewColumn.setMustInput(true);
orgViewColumn.setEntryEntity("fcs_orgview_test");
orgViewColumn.setView(this.getView());
// 获取登录用户,有权限的资金组织视图
List<ComboItem> combos = this.buildOrgViewComboItems();
// 设置资金组织视图过滤字段的可选项
orgViewColumn.setComboItems(combos);
orgViewColumn.setType("enum");
orgViewColumn.setDefaultValue(String.valueOf(DEFAULT_ORG_VIEW_ID));
} catch (Exception e) {
logger.error("增加资金组织视图过滤字段失败:", e);
}
return orgViewColumn;
}
```
设置资金组织视图过滤条件的可选项,查询当前登录用户有权限的资金组织视图:
```java
/**
* 获取有权限的资金组织视图
* @return
*/
private List<ComboItem> buildOrgViewComboItems() {
// 尝试取缓存的过滤字段
String cacheString = this.getPageCache().get(CACHEKEY_ORGVIEWCOMBOITEMS);
if (StringUtils.isNotBlank(cacheString)){
// 已缓存:把缓存的字符串,反序列化为过滤字段
return (List<ComboItem>) SerializationUtils.fromJsonStringToList(cacheString, ComboItem.class);
}
List<ComboItem> combos = new ArrayList<>();
long userId = RequestContext.get().getCurrUserId();
// 获取有权限的资金组织视图集合
DataSet orgViewDataSet = TmcOrgDataHelper.getAuthorizedBankOrgViewDataSet(userId);
Iterator<Row> orgViewIter = orgViewDataSet.iterator();
while(orgViewIter.hasNext()){
Row row = orgViewDataSet.next();
ComboItem item = new ComboItem();
item.setId(row.getString(ID_STR));
item.setCaption(new LocaleString(row.getString("name")));
item.setValue(row.getString(ID_STR));
combos.add(item);
}
cacheString = SerializationUtils.toJsonString(combos);
this.getPageCache().put(CACHEKEY_ORGVIEWCOMBOITEMS, cacheString);
return combos;
}
```
- filterContainerSearchClick:当页面“资金组织视图”变更时,需要同步修改主业务组织字段的可选项
```java
@Override
public void filterContainerSearchClick(FilterContainerSearchClickArgs args) {
super.filterContainerSearchClick(args);
// 列表绑定的单据,没有主业务单元字段:无需按组织过滤
if (this.isBillNotHasMainOrg()){
return;
}
// 获取用户在过滤面板中设置的过滤条件:从中搜索出过滤组织视图值
Map<String, List<Map<String, List<Object>>>> filterValues = args.getSearchClickEvent().getFilterValues();
List<Map<String, List<Object>>> customFiterList = filterValues.get("customfilter");
if(customFiterList == null) return ;
this.orgViewId = 8;
// 逐项条件匹配,找出自定义的组织视图过滤字段
for(int i = customFiterList.size()-1; i >= 0 ; i--){
Map<String, List<Object>> customFiter = customFiterList.get(i);
List<Object> fieldNames = customFiter.get(FIELD_NAME_STR);
if(fieldNames == null || fieldNames.isEmpty()) continue;
if(StringUtils.equals(ORGVIEWID, (String)fieldNames.get(0))){
// 找到了自定义的组织视图过滤字段
List<Object> orgViewIds = customFiter.get(VALUE_STR);
if(orgViewIds == null || orgViewIds.isEmpty()) continue;
// 取用户在过滤面板上,选择的组织视图,放在本地变量中(后续在setFilter事件中要用到)
this.orgViewId = Long.parseLong((String)orgViewIds.get(0));
break;
}
}
// 重新调用过滤面板初始化方法,设置主业务组织字段的可选项
initOrgCombos(this.initArgs.getCommonFilterColumns());
// 设置主业务组织字段的默认值
setDefaultOrgValues(customFiterList);
}
```
根据所选资金组织视图,动态设置主业务组织字段的可选项:
```java
private void initOrgCombos(List<FilterColumn> filterColumns) {
// 已经有组织视图,且组织视图变更时,需同步变更主业务单元字段的过滤项
for (FilterColumn filterColumn : filterColumns) {
String fieldName = filterColumn.getFieldName();
if (orgFldId.equals(fieldName) || orgFldName.equals(fieldName)) {
initOrgCombos(filterColumn);
}
}
}
/**
* 根据所选资金组织视图,动态展示组织可选项
* @param filterColumn
*/
private void initOrgCombos(FilterColumn filterColumn) {
List<Long> orgIds = getPermOrgIds(this.orgViewId);
QFilter filter = new QFilter("fisbankroll", QFilter.equals, "1").and("enable", QFilter.equals, "1");
if (orgIds != null) {
filter = filter.and(ID_STR, QFilter.in, orgIds);
}
Map<Object, DynamicObject> orgMap = TmcDataServiceHelper.loadFromCache(TmcEntityConst.ENTITY_ORG, "id,name", filter.toArray());
this.orgMap = orgMap;
List<ComboItem> combos = new ArrayList<>();
for (Map.Entry<Object, DynamicObject> entry : orgMap.entrySet()) {
ComboItem item = new ComboItem();
DynamicObject org = entry.getValue();
item.setId(org.getString(ID_STR));
item.setCaption(new LocaleString(org.getString("name")));
item.setValue(org.getString(ID_STR));
combos.add(item);
}
CommonFilterColumn commonFilterColumn = (CommonFilterColumn) filterColumn;
commonFilterColumn.setComboItems(combos);
}
/**
* 获取组织视图下有权限的组织
* @param orgViewId
* @return
*/
private List<Long> getPermOrgIds(Long orgViewId) {
HasPermOrgResult result = PermissionServiceHelper.getAllPermOrgs(RequestContext.get().getCurrUserId(), OrgViewTypeEnum.IS_BANKROLL.getViewType(),
getBillEntityType().getAppId(), getBillEntityType().getName(), PermItemEnum.QUERY);
if (result.hasAllOrgPerm()) {
return TmcOrgDataHelper.getAllOrgIdsByViewId(orgViewId);
} else {
List<Long> orgIds = new ArrayList<>();
List<Long> orgIdList = result.getHasPermOrgs();
DataSet orgDateSet = TmcOrgDataHelper.getOrgDateSet(orgViewId);
DataSet defaultOrg = orgDateSet.filter("orgid in orgIdList", Collections.singletonMap("orgIdList", orgIdList));
for (Row next : defaultOrg) {
orgIds.add(next.getLong("orgid"));
}
return orgIds;
}
}
```
设置主业务组织字段的默认值:当资金组织视图中的组织包含当前登录组织时,默认选择当前登录组织;不包含时,默认不限
```java
/**
* 设置组织过滤条件的默认值:当资金组织视图中的组织包含当前登录组织时,默认选择当前登录组织;不包含时,默认不限
* @param customFiterList
*/
private void setDefaultOrgValues(List<Map<String, List<Object>>> customFiterList) {
long orgId = RequestContext.get().getOrgId();
if (!constainsCurrOrg(orgId)){
doSetDefaultOrgValues(customFiterList, "");
} else {
doSetDefaultOrgValues(customFiterList, String.valueOf(orgId));
}
}
private void doSetDefaultOrgValues(List<Map<String, List<Object>>> customFiterList, String orgId) {
for (Map<String, List<Object>> customFiter : customFiterList) {
List<Object> fieldNames = customFiter.get(FIELD_NAME_STR);
if(fieldNames == null || fieldNames.isEmpty()) continue;
String fieldName = (String) fieldNames.get(0);
if (orgFldId.equals(fieldName) || orgFldName.equals(fieldName)) {
customFiter.put(VALUE_STR, Collections.singletonList(orgId));
}
}
}
/**
* 判断当前登录组织是否在所选资金组织视图中
* @param currOrgId
* @return
*/
private boolean constainsCurrOrg(long currOrgId) {
if (this.orgMap != null) {
for (Map.Entry<Object, DynamicObject> entry : orgMap.entrySet()) {
DynamicObject org = entry.getValue();
if (org.getLong(ID_STR) == currOrgId) {
return true;
}
}
}
return false;
}
```
- setFilter:根据所选资金组织视图,添加过滤条件
```java
@Override
public void setFilter(SetFilterEvent e) {
super.setFilter(e);
String orgFldName = this.getBillEntityType().getMainOrg() + ".id";
List<Long> orgIds = TmcOrgDataHelper.getAllOrgIdsByViewId(this.orgViewId);
QFilter orgFilter = new QFilter(orgFldName, QCP.in, orgIds);
List<QFilter> qFilters = e.getQFilters();
qFilters.add(orgFilter);
// 将资金组织视图过滤条件去除
qFilters.removeIf(filter -> filter.getProperty().equals(ORGVIEWID));
}
```
- 其他工具方法
```java
/**
* 获取列表绑定的单据主实体
* @return
*/
protected BillEntityType getBillEntityType(){
return (BillEntityType)((IListView)this.getView()).getListModel().getDataEntityType();
}
/**
* 获取列表绑定的单据是否有主业务单元字段
* @return
*/
protected boolean isBillNotHasMainOrg(){
return StringUtils.isBlank(this.getBillEntityType().getMainOrg());
}
```
- 一些全局属性
```java
/** 缓存标识 */
private final static String CACHEKEY_ORGVIEWCOMBOITEMS = "orgviewcomboitems";
/** 资金业务视图默认方案id */
private static final Long DEFAULT_ORG_VIEW_ID = 8L;
private static final String ORGVIEWID = "orgview.id";
private static final String ID_STR = "id";
private static final String FIELD_NAME_STR = "FieldName";
private static final String VALUE_STR = "Value";
private FilterContainerInitArgs initArgs;
private Map<Object, DynamicObject> orgMap;
private String orgFldId;
private String orgFldName;
private long orgViewId = 8;
```
## 3.将创建的列表插件注册到需要的单据列表中

## 4.效果展示
紧凑布局:

平铺布局:

# 【总结】
在单据列表中添加本单据中不存在的过滤字段时,通过常规方法无法正常创建,这里通过引用其他含有此字段的单据来创建过滤字段的属性,并使用反射的方式来将它赋值给此过滤字段控件,以此来实现我们想要的效果。
**注:因添加的过滤字段在本单据中不存在,所以在setFilter中还需要手工将此过滤条件删除**