diff --git a/application-tenant/tenant-admin/pom.xml b/application-tenant/tenant-admin/pom.xml
index 02afce9..c4ac3a4 100644
--- a/application-tenant/tenant-admin/pom.xml
+++ b/application-tenant/tenant-admin/pom.xml
@@ -156,7 +156,7 @@
apelet
common-orm
- 1.0.2
+ 1.0.3
apelet
diff --git a/common/common-association/pom.xml b/common/common-association/pom.xml
index cf5e4e5..8ef017c 100644
--- a/common/common-association/pom.xml
+++ b/common/common-association/pom.xml
@@ -52,7 +52,7 @@
common-orm
apelet
- 1.0.2
+ 1.0.3
common-msg-notice
diff --git a/common/common-association/src/main/java/apelet/association/controller/OnlineFormExtController.java b/common/common-association/src/main/java/apelet/association/controller/OnlineFormExtController.java
new file mode 100644
index 0000000..d1c47b2
--- /dev/null
+++ b/common/common-association/src/main/java/apelet/association/controller/OnlineFormExtController.java
@@ -0,0 +1,47 @@
+package apelet.association.controller;
+
+import apelet.common.core.annotation.MyRequestBody;
+import apelet.common.core.object.ResponseResult;
+import apelet.common.online.controller.OnlineFormController;
+import apelet.common.online.dto.OnlineFormDto;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 在线表单新增/修改独立接口。
+ * 仅提供对外的独立入口,业务逻辑不做任何改动,直接调用 OnlineFormController 的 add/update。
+ */
+@RestController
+@RequestMapping({"/onlineFormExt"})
+public class OnlineFormExtController {
+
+ /**
+ * 在线表单控制器,复用其已有的新增/修改逻辑(含事务、校验、日志等)。
+ */
+ @Autowired
+ private OnlineFormController onlineFormController;
+
+ /**
+ * 新增在线表单。
+ *
+ * @param onlineFormDto 表单信息
+ * @return 新增成功后的表单主键 formId
+ */
+ @PostMapping({"/add"})
+ public ResponseResult add(@MyRequestBody OnlineFormDto onlineFormDto) {
+ return onlineFormController.add(onlineFormDto);
+ }
+
+ /**
+ * 修改在线表单。
+ *
+ * @param onlineFormDto 表单信息
+ * @return 无业务数据返回
+ */
+ @PostMapping({"/update"})
+ public ResponseResult update(@MyRequestBody OnlineFormDto onlineFormDto) {
+ return onlineFormController.update(onlineFormDto);
+ }
+}
diff --git a/common/common-association/src/main/java/apelet/association/plugin/quotation/ContractPlugin.java b/common/common-association/src/main/java/apelet/association/plugin/quotation/ContractPlugin.java
index 6380ec7..fd47a12 100644
--- a/common/common-association/src/main/java/apelet/association/plugin/quotation/ContractPlugin.java
+++ b/common/common-association/src/main/java/apelet/association/plugin/quotation/ContractPlugin.java
@@ -3,31 +3,273 @@ package apelet.association.plugin.quotation;
import apelet.association.utils.WordDocUtil;
import apelet.common.core.object.ObjectValue;
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.FilterItem;
-import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Map;
+import java.util.Set;
/*
-合同列表界面设置过滤条件
+合同列表界面插件
+列表数据由 getSql 自定义 SQL 查询 contract 表,并拼接列表顶部的查询条件。
+
+注意:走自定义 SQL 时,列表的列由 getColumns 定义,框架按 ListColumn 的
+customFieldName 把查询结果映射到表格列上,因此 getSql 中 SELECT 出来的
+列名(含别名)必须与 getColumns 里的 customFieldName 完全一致。
*/
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
protected Filter getFilter() {
Filter filter = new Filter();
- filter.add(new FilterItem("status", FilterItem.equals, "3"));
+ filter.add(new FilterItem("deleted_flag", FilterItem.equals, 1));
return filter;
}
+ /**
+ * 列表列定义。
+ * 走自定义 SQL 时,表格列由本方法返回的列定义渲染,
+ * 因此这里的 customFieldName 必须与 getSql 中 SELECT 的列名/别名完全一致。
+ *
+ * @param objectValue 当前事件数据对象
+ * @return 列表列定义集合
+ */
+ @Override
+ public List getColumns(ObjectValue objectValue) {
+ List 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
public void buttonTriggered(String widgetVariableName, ObjectValue objectValue) {
- if(widgetVariableName.equals("合同导出")){
+ if (widgetVariableName.equals("合同导出")) {
try {
Map values = objectValue.getValues();
String template = System.getProperty("user.dir") + "\\zz-resource\\quotation_template.docx";
- String outPath = System.getProperty("user.dir") + "\\zz-resource\\quotation_"+ System.currentTimeMillis()+".docx";
+ String outPath = System.getProperty("user.dir") + "\\zz-resource\\quotation_" + System.currentTimeMillis() + ".docx";
WordDocUtil.renderLocalDoc(template, outPath, values);
} catch (Exception e) {
throw new RuntimeException(e);
diff --git a/common/common-qy/pom.xml b/common/common-qy/pom.xml
index 829f1ad..ce1d7f7 100644
--- a/common/common-qy/pom.xml
+++ b/common/common-qy/pom.xml
@@ -27,7 +27,7 @@
apelet
common-orm
- 1.0.2
+ 1.0.3
apelet
diff --git a/pom.xml b/pom.xml
index f504ec9..ff54398 100644
--- a/pom.xml
+++ b/pom.xml
@@ -289,7 +289,7 @@
apelet
common-orm
- 1.0.2
+ 1.0.3