|
|
|
@ -3,24 +3,266 @@ package apelet.association.plugin.quotation; |
|
|
|
import apelet.association.utils.WordDocUtil; |
|
|
|
import apelet.association.utils.WordDocUtil; |
|
|
|
import apelet.common.core.object.ObjectValue; |
|
|
|
import apelet.common.core.object.ObjectValue; |
|
|
|
import apelet.common.online.abstractplugin.ListPlugin; |
|
|
|
import apelet.common.online.abstractplugin.ListPlugin; |
|
|
|
|
|
|
|
import apelet.common.online.abstractplugin.model.ListColumn; |
|
|
|
|
|
|
|
import apelet.common.online.dto.OnlineFilterDto; |
|
|
|
|
|
|
|
import apelet.common.online.model.constant.FieldFilterType; |
|
|
|
import apelet.common.orm.impl.Filter; |
|
|
|
import apelet.common.orm.impl.Filter; |
|
|
|
import apelet.common.orm.impl.FilterItem; |
|
|
|
import apelet.common.orm.impl.FilterItem; |
|
|
|
|
|
|
|
|
|
|
|
import java.time.LocalDate; |
|
|
|
import java.util.ArrayList; |
|
|
|
|
|
|
|
import java.util.List; |
|
|
|
import java.util.Map; |
|
|
|
import java.util.Map; |
|
|
|
|
|
|
|
import java.util.Set; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* |
|
|
|
/* |
|
|
|
合同列表界面设置过滤条件 |
|
|
|
合同列表界面插件 |
|
|
|
|
|
|
|
列表数据由 getSql 自定义 SQL 查询 contract 表,并拼接列表顶部的查询条件。 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
注意:走自定义 SQL 时,列表的列由 getColumns 定义,框架按 ListColumn 的 |
|
|
|
|
|
|
|
customFieldName 把查询结果映射到表格列上,因此 getSql 中 SELECT 出来的 |
|
|
|
|
|
|
|
列名(含别名)必须与 getColumns 里的 customFieldName 完全一致。 |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public class ContractPlugin extends ListPlugin { |
|
|
|
public class ContractPlugin extends ListPlugin { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* 列表查询字段。 |
|
|
|
|
|
|
|
* 字段名/别名必须与 getColumns 里 ListColumn 的 customFieldName 完全一致, |
|
|
|
|
|
|
|
* 框架按该名称把查询结果映射到表格列上。 |
|
|
|
|
|
|
|
* 其中 delivery_person(产品服务交付负责人)在库里存的是用户ID,这里关联用户表取姓名。 |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
private static final String LIST_FIELDS = |
|
|
|
|
|
|
|
"c.id, c.number, c.name, c.signing_date, c.delivery_date, c.get_date, " |
|
|
|
|
|
|
|
+ "u.show_name AS delivery_person, " |
|
|
|
|
|
|
|
+ "c.subcontractor, c.price, c.qty, c.payment_progress"; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* 合同列表查询 SQL。 |
|
|
|
|
|
|
|
* 注意:一旦返回自定义 SQL,框架会走 ListDataSqlService 分支, |
|
|
|
|
|
|
|
* 该分支不会使用 getFilter() 与 getSorter(),因此过滤条件与排序都必须写在 SQL 里。 |
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
* @param objectValue 当前事件数据对象 |
|
|
|
|
|
|
|
* @return 合同列表查询 SQL |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
@Override |
|
|
|
|
|
|
|
public String getSql(ObjectValue objectValue) { |
|
|
|
|
|
|
|
StringBuilder sql = new StringBuilder(); |
|
|
|
|
|
|
|
sql.append("SELECT ").append(LIST_FIELDS).append(" FROM contract c "); |
|
|
|
|
|
|
|
// 负责人字段存的是用户ID,左连接用户表取姓名(用户不存在时该列为空)
|
|
|
|
|
|
|
|
sql.append("LEFT JOIN xy_sys_user u ON u.id = c.delivery_person "); |
|
|
|
|
|
|
|
// 已删除、已作废的合同不显示
|
|
|
|
|
|
|
|
sql.append("WHERE c.deleted_flag = 1"); |
|
|
|
|
|
|
|
// 拼接列表顶部录入的查询条件
|
|
|
|
|
|
|
|
sql.append(buildConditionSql()); |
|
|
|
|
|
|
|
// 固定按创建时间倒序,保证翻页结果稳定
|
|
|
|
|
|
|
|
sql.append(" ORDER BY c.create_time DESC, c.id DESC"); |
|
|
|
|
|
|
|
return sql.toString(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* 数据过滤条件。 |
|
|
|
|
|
|
|
* 走 getSql 自定义 SQL 时框架不会调用本方法,此处仅为不使用自定义 SQL 时保持一致。 |
|
|
|
|
|
|
|
*/ |
|
|
|
@Override |
|
|
|
@Override |
|
|
|
protected Filter getFilter() { |
|
|
|
protected Filter getFilter() { |
|
|
|
Filter filter = new Filter(); |
|
|
|
Filter filter = new Filter(); |
|
|
|
filter.add(new FilterItem("status", FilterItem.equals, "3")); |
|
|
|
filter.add(new FilterItem("deleted_flag", FilterItem.equals, 1)); |
|
|
|
return filter; |
|
|
|
return filter; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* 列表列定义。 |
|
|
|
|
|
|
|
* 走自定义 SQL 时,表格列由本方法返回的列定义渲染, |
|
|
|
|
|
|
|
* 因此这里的 customFieldName 必须与 getSql 中 SELECT 的列名/别名完全一致。 |
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
* @param objectValue 当前事件数据对象 |
|
|
|
|
|
|
|
* @return 列表列定义集合 |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
@Override |
|
|
|
|
|
|
|
public List<ListColumn> getColumns(ObjectValue objectValue) { |
|
|
|
|
|
|
|
List<ListColumn> columns = new ArrayList<>(); |
|
|
|
|
|
|
|
// 主键:隐藏列,供行定位、打开详情等操作使用
|
|
|
|
|
|
|
|
columns.add(buildColumn("id", "主键", 0, false)); |
|
|
|
|
|
|
|
// 以下列顺序与列表界面上配置的列顺序保持一致
|
|
|
|
|
|
|
|
columns.add(buildColumn("signing_date", "合同签订日期", 160, true)); |
|
|
|
|
|
|
|
columns.add(buildColumn("delivery_date", "产品服务交付日期", 170, true)); |
|
|
|
|
|
|
|
columns.add(buildColumn("get_date", "收款计划", 160, true)); |
|
|
|
|
|
|
|
columns.add(buildColumn("delivery_person", "产品服务交付负责人", 180, true)); |
|
|
|
|
|
|
|
columns.add(buildColumn("subcontractor", "分包商", 200, true)); |
|
|
|
|
|
|
|
columns.add(buildColumn("price", "分包价格", 120, true)); |
|
|
|
|
|
|
|
columns.add(buildColumn("number", "编码", 180, true)); |
|
|
|
|
|
|
|
columns.add(buildColumn("name", "名称", 200, true)); |
|
|
|
|
|
|
|
columns.add(buildColumn("qty", "总报价", 120, true)); |
|
|
|
|
|
|
|
columns.add(buildColumn("payment_progress", "付款进度", 120, true)); |
|
|
|
|
|
|
|
return columns; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* 构造一个列表列定义。 |
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
* @param fieldName 字段名,必须与 getSql 查询出的列名/别名一致 |
|
|
|
|
|
|
|
* @param showName 列显示名称 |
|
|
|
|
|
|
|
* @param width 列宽,传 0 表示使用默认宽度 |
|
|
|
|
|
|
|
* @param show 该列是否显示 |
|
|
|
|
|
|
|
* @return 列定义对象 |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
private ListColumn buildColumn(String fieldName, String showName, int width, boolean show) { |
|
|
|
|
|
|
|
ListColumn column = new ListColumn(); |
|
|
|
|
|
|
|
column.setCustomFieldName(fieldName); |
|
|
|
|
|
|
|
column.setShowName(showName); |
|
|
|
|
|
|
|
// 自定义 SQL 查出的列不参与点击打开表单,也不可编辑
|
|
|
|
|
|
|
|
column.setClickOpen(false); |
|
|
|
|
|
|
|
column.setShowColumn(show); |
|
|
|
|
|
|
|
if (width > 0) { |
|
|
|
|
|
|
|
column.setColumnWidth(width); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return column; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* 把列表顶部的查询条件拼接成 SQL 的 AND 片段。 |
|
|
|
|
|
|
|
* 目前列表只有“名称”一个查询条件,这里按框架的过滤类型通用处理。 |
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
* @return 形如 " AND c.name LIKE '%关键字%'" 的片段,无条件时返回空串 |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
private String buildConditionSql() { |
|
|
|
|
|
|
|
Object filterObj = this.getDto().getListParams().get("filterDtoList"); |
|
|
|
|
|
|
|
if (!(filterObj instanceof List)) { |
|
|
|
|
|
|
|
return ""; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
StringBuilder condition = new StringBuilder(); |
|
|
|
|
|
|
|
for (Object item : (List<?>) filterObj) { |
|
|
|
|
|
|
|
if (!(item instanceof OnlineFilterDto)) { |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
OnlineFilterDto filterDto = (OnlineFilterDto) item; |
|
|
|
|
|
|
|
// 列名做白名单校验,避免拼接 SQL 时被注入
|
|
|
|
|
|
|
|
String columnName = filterDto.getColumnName(); |
|
|
|
|
|
|
|
if (columnName == null || !columnName.matches("[A-Za-z0-9_]+")) { |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// 查询条件都来自主表,统一加表别名
|
|
|
|
|
|
|
|
String column = "c." + columnName; |
|
|
|
|
|
|
|
Integer filterType = filterDto.getFilterType(); |
|
|
|
|
|
|
|
if (filterType == null) { |
|
|
|
|
|
|
|
filterType = FieldFilterType.EQUAL_FILTER; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// 范围过滤:起止值分别拼 >= 与 <=
|
|
|
|
|
|
|
|
if (filterType == FieldFilterType.RANGE_FILTER) { |
|
|
|
|
|
|
|
condition.append(buildRangeSql(column, filterDto)); |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// 列表过滤:拼 IN (...)
|
|
|
|
|
|
|
|
if (filterType == FieldFilterType.IN_LIST_FILTER) { |
|
|
|
|
|
|
|
condition.append(buildInSql(column, filterDto.getColumnValueList())); |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// 模糊过滤:前后加通配符
|
|
|
|
|
|
|
|
if (filterType == FieldFilterType.LIKE_FILTER || filterType == FieldFilterType.MULTI_LIKE) { |
|
|
|
|
|
|
|
String keyword = toPlainText(filterDto.getColumnValue()); |
|
|
|
|
|
|
|
if (keyword != null) { |
|
|
|
|
|
|
|
condition.append(" AND ").append(column) |
|
|
|
|
|
|
|
.append(" LIKE '%").append(keyword.replace("'", "''")).append("%'"); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// 其余情况按等于过滤处理
|
|
|
|
|
|
|
|
String value = toSqlValue(filterDto.getColumnValue()); |
|
|
|
|
|
|
|
if (value != null) { |
|
|
|
|
|
|
|
condition.append(" AND ").append(column).append(" = ").append(value); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return condition.toString(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* 拼接范围过滤条件。 |
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
* @param column 带表别名的列名 |
|
|
|
|
|
|
|
* @param filterDto 过滤参数 |
|
|
|
|
|
|
|
* @return 形如 " AND c.xxx >= '2026-01-01'" 的片段 |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
private String buildRangeSql(String column, OnlineFilterDto filterDto) { |
|
|
|
|
|
|
|
StringBuilder sql = new StringBuilder(); |
|
|
|
|
|
|
|
String start = toSqlValue(filterDto.getColumnValueStart()); |
|
|
|
|
|
|
|
if (start != null) { |
|
|
|
|
|
|
|
sql.append(" AND ").append(column).append(" >= ").append(start); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
String end = toSqlValue(filterDto.getColumnValueEnd()); |
|
|
|
|
|
|
|
if (end != null) { |
|
|
|
|
|
|
|
sql.append(" AND ").append(column).append(" <= ").append(end); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return sql.toString(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* 拼接 IN 过滤条件。 |
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
* @param column 带表别名的列名 |
|
|
|
|
|
|
|
* @param valueList 取值集合 |
|
|
|
|
|
|
|
* @return 形如 " AND c.xxx IN ('1', '2')" 的片段,集合为空时返回空串 |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
private String buildInSql(String column, Set<?> valueList) { |
|
|
|
|
|
|
|
if (valueList == null || valueList.isEmpty()) { |
|
|
|
|
|
|
|
return ""; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
StringBuilder values = new StringBuilder(); |
|
|
|
|
|
|
|
for (Object value : valueList) { |
|
|
|
|
|
|
|
String literal = toSqlValue(value); |
|
|
|
|
|
|
|
if (literal == null) { |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (values.length() > 0) { |
|
|
|
|
|
|
|
values.append(", "); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
values.append(literal); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (values.length() == 0) { |
|
|
|
|
|
|
|
return ""; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return " AND " + column + " IN (" + values + ")"; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* 把过滤值转成 SQL 字面量:数字与布尔直接输出,其余按字符串加引号并转义单引号。 |
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
* @param value 过滤值 |
|
|
|
|
|
|
|
* @return SQL 字面量,值为空时返回 null,表示该条件忽略 |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
private String toSqlValue(Object value) { |
|
|
|
|
|
|
|
if (value == null) { |
|
|
|
|
|
|
|
return null; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (value instanceof Number || value instanceof Boolean) { |
|
|
|
|
|
|
|
return value.toString(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
String text = String.valueOf(value); |
|
|
|
|
|
|
|
if (text.isEmpty()) { |
|
|
|
|
|
|
|
return null; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return "'" + text.replace("'", "''") + "'"; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* 取出过滤值的纯文本形式,空值返回 null。 |
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
* @param value 过滤值 |
|
|
|
|
|
|
|
* @return 文本内容,为空时返回 null |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
private String toPlainText(Object value) { |
|
|
|
|
|
|
|
if (value == null) { |
|
|
|
|
|
|
|
return null; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
String text = String.valueOf(value); |
|
|
|
|
|
|
|
return text.isEmpty() ? null : text; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* 合同导出:按当前表单数据渲染 Word 模板。 |
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
* @param widgetVariableName 按钮标识 |
|
|
|
|
|
|
|
* @param objectValue 当前事件数据对象 |
|
|
|
|
|
|
|
*/ |
|
|
|
@Override |
|
|
|
@Override |
|
|
|
public void buttonTriggered(String widgetVariableName, ObjectValue objectValue) { |
|
|
|
public void buttonTriggered(String widgetVariableName, ObjectValue objectValue) { |
|
|
|
if (widgetVariableName.equals("合同导出")) { |
|
|
|
if (widgetVariableName.equals("合同导出")) { |
|
|
|
|