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