From 4b8f8eefbff464b383dd9c3dab682008e8583529 Mon Sep 17 00:00:00 2001 From: myf Date: Sat, 15 Aug 2026 15:43:12 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=9D=E5=AD=98=E6=8F=92=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugin/member/MembershipSavePlugin.java | 360 +++++++++++++++++++++ 1 file changed, 360 insertions(+) create mode 100644 common/common-association/src/main/java/apelet/association/plugin/member/MembershipSavePlugin.java diff --git a/common/common-association/src/main/java/apelet/association/plugin/member/MembershipSavePlugin.java b/common/common-association/src/main/java/apelet/association/plugin/member/MembershipSavePlugin.java new file mode 100644 index 0000000..b36b002 --- /dev/null +++ b/common/common-association/src/main/java/apelet/association/plugin/member/MembershipSavePlugin.java @@ -0,0 +1,360 @@ +package apelet.association.plugin.member; + +import apelet.common.core.exception.MyRuntimeException; +import apelet.common.core.object.ObjectCollection; +import apelet.common.core.object.ObjectValue; +import apelet.common.online.abstractplugin.ExecutePluginParent; +import apelet.common.orm.impl.Filter; +import apelet.common.orm.impl.FilterItem; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.HashMap; +import java.util.Map; + +/** + * @ClassName: test + * @Date: 2026/8/14 + * @Description: PC 与移动端共用的"保存"插件 + * 场景:移动端小程序无法直连后端调试,将 PC 表单与移动端绑定同一个插件, + * 在 PC 上点击"保存"即可复现移动端小程序的保存逻辑,控制台/日志可看到保存报错。 + * + * 保存逻辑: + * 1. 根据表单 id 区分"新增"与"修改": + * - 新增(id 为空/0):先按 unit_name 验重,已存在则拒绝新增;否则生成单据号(S + yyyyMMdd + 4 位流水)后 addNew + * - 修改(id 非空):按 id 直接 update,不验重 + * 2. 无论新增还是修改,均写入默认值:flow_status / flow_approval_status / org / srcbillid / srcbillnumber / srcentryid = 0,history = 1 + */ +public class MembershipSavePlugin extends ExecutePluginParent { + + /** 保存的目标主表 */ + private static final String TABLE_NAME = "membership_apply"; + + /** 单据号前缀 */ + private static final String NUMBER_PREFIX = "S"; + + /** 单据号日期格式 */ + private static final String DATE_PATTERN = "yyyyMMdd"; + + /** 流水号位数(不足补零,如 0001) */ + private static final int SEQ_LENGTH = 4; + + /** 唯一键:单位名称数据库字段 */ + private static final String FIELD_UNIT_NAME = "unit_name"; + + /** 数据库字段 -> 表单控件 key 映射(与 UnitNameChangeFormInitPlugin 保持一致) */ + private static final Map DB_FIELD_WIDGET_MAPPING = new HashMap<>(); + + static { + DB_FIELD_WIDGET_MAPPING.put("create_user_id", "createUserId"); + DB_FIELD_WIDGET_MAPPING.put("create_time", "createTime"); + DB_FIELD_WIDGET_MAPPING.put("update_user_id", "updateUserId"); + DB_FIELD_WIDGET_MAPPING.put("update_time", "updateTime"); + DB_FIELD_WIDGET_MAPPING.put("deleted_flag", "deletedFlag"); + DB_FIELD_WIDGET_MAPPING.put("is_history", "history"); + DB_FIELD_WIDGET_MAPPING.put("nature_unit", "natureUnit"); + DB_FIELD_WIDGET_MAPPING.put("unit_name", "unitName"); + DB_FIELD_WIDGET_MAPPING.put("membership_manger_id", "membershipMangerId"); + DB_FIELD_WIDGET_MAPPING.put("membership_type", "membershipType"); + DB_FIELD_WIDGET_MAPPING.put("menbership_attributes", "menbershipAttributes"); + DB_FIELD_WIDGET_MAPPING.put("scope_business", "scopeBusiness"); + DB_FIELD_WIDGET_MAPPING.put("business_regist_number", "businessRegistNumber"); + DB_FIELD_WIDGET_MAPPING.put("regist_capital", "registCapital"); + DB_FIELD_WIDGET_MAPPING.put("regist_time", "registTime"); + DB_FIELD_WIDGET_MAPPING.put("company_address", "companyAddress"); + DB_FIELD_WIDGET_MAPPING.put("version_number", "versionNumber"); + DB_FIELD_WIDGET_MAPPING.put("change_reason", "changeReason"); + } + + /** 保存时需要跳过的非主表字段(objectValue 中的控件 key) */ + private static final String[] SKIP_WIDGET_KEYS = { + "id", // 主键,由框架自动生成或单独处理 + "eventparams", // 弹窗参数,非表字段 + "sourcebillid", // 来源单据参数 + "srcbillid", + "sourcebillnumber", + "srcbillnumber", + "membership_apply_entry", // 子表(本次保存不处理子表) + "table1777360622546", + "rowkeysubset" // 框架内部字段 + }; + + /** + * 按钮点击事件:处理"保存"按钮 + * + * @param widgetVariableName 按钮标识 + * @param objectValue 当前表单数据对象 + */ + @Override + public void buttonTriggered(String widgetVariableName, ObjectValue objectValue) { + // 仅响应"保存"按钮 + if (!"保存".equals(widgetVariableName)) { + return; + } + + try { + // 根据表单 id 区分新增/修改 + boolean isNew = isNewBill(objectValue); + if (isNew) { + // 新增:先按 unit_name 验重,再生成单据号入库 + saveAsNew(objectValue); + this.showMessage("保存成功(新增)"); + } else { + // 修改:按 id 直接更新,不验重 + saveAsUpdate(objectValue); + this.showMessage("保存成功(修改)"); + } + this.cancelOperate(); + } catch (Exception e) { + // 保存失败:打印完整堆栈到控制台/日志,便于定位移动端小程序保存报错 + e.printStackTrace(); + // 抛出异常,让 PC 端弹窗显示具体错误 + throw new MyRuntimeException("保存失败:" + e.getMessage()); + } + } + + /** + * 判断当前表单是新增还是修改 + * 表单 id 为空或 0 视为新增,否则视为修改 + * + * @param objectValue 表单数据对象 + * @return true 表示新增 + */ + private boolean isNewBill(ObjectValue objectValue) { + Object idObj = objectValue.get("id"); + if (idObj == null) { + return true; + } + String idStr = idObj.toString().trim(); + return idStr.isEmpty() || "0".equals(idStr); + } + + /** + * 新增逻辑:按 unit_name 验重 -> 生成单据号 -> addNew + * + * @param objectValue 表单数据对象 + */ + private void saveAsNew(ObjectValue objectValue) throws Exception { + // 取唯一键单位名称(兼容 unit_name / unitName 两种控件 key) + String unitName = getUnitName(objectValue); + + // 新增验重:unit_name 已存在则拒绝新增 + ObjectValue existBill = findExistBillByUnitName(unitName); + if (existBill != null) { + throw new MyRuntimeException("单位【" + unitName + "】已存在,请勿重复新增"); + } + + // 自动生成单据号(S + yyyyMMdd + 4 位流水号) + String number = generateNumber(); + // 组装数据库记录并新增 + ObjectValue newBill = buildNewBill(objectValue, number); + ormGenDataSourceUtil().addNew(TABLE_NAME, newBill); + } + + /** + * 修改逻辑:按 id 组装记录并 update(不验重) + * 先查出原记录的单据号 number 并填回,避免整体覆盖更新时把 number 置为 null + * + * @param objectValue 表单数据对象 + */ + private void saveAsUpdate(ObjectValue objectValue) throws Exception { + ObjectValue updateBill = buildUpdateBill(objectValue); + + // 按主键查询原记录,把单据号 number 填回(update 为整体覆盖,缺失字段会被置空) + Object idObj = objectValue.get("id"); + if (idObj != null) { + Filter filter = new Filter(); + filter.add(new FilterItem("id", FilterItem.equals, idObj)); + ObjectCollection collection = ormGenDataSourceUtil().query(TABLE_NAME, filter, null); + if (collection != null && !collection.isEmpty()) { + String existNumber = collection.getObject(0).getString("number"); + if (existNumber != null && !existNumber.isEmpty()) { + updateBill.put("number", existNumber); + } + } + } + + ormGenDataSourceUtil().update(TABLE_NAME, updateBill, null); + } + + /** + * 根据单位名称查询已存在的单据(用于新增验重) + * + * @param unitName 单位名称 + * @return 已存在的单据;不存在返回 null + */ + private ObjectValue findExistBillByUnitName(String unitName) throws Exception { + if (unitName == null || unitName.trim().isEmpty()) { + return null; + } + Filter filter = new Filter(); + filter.add(new FilterItem(FIELD_UNIT_NAME, FilterItem.equals, unitName)); + ObjectCollection collection = ormGenDataSourceUtil().query(TABLE_NAME, filter, null); + if (collection != null && !collection.isEmpty()) { + return collection.getObject(0); + } + return null; + } + + /** + * 组装新增记录:填充表单字段 + 单据号 + 默认值 + * + * @param objectValue 表单数据对象 + * @param number 自动生成的单据号 + * @return 组装好的数据库记录 + */ + private ObjectValue buildNewBill(ObjectValue objectValue, String number) { + ObjectValue newBill = new ObjectValue(TABLE_NAME); + fillFields(newBill, objectValue); + // 设置自动生成的单据号 + newBill.put("number", number); + // 设置默认值 + setDefaultValues(newBill); + return newBill; + } + + /** + * 组装修改记录:带主键 id,覆盖表单字段,单据号保留数据库原值 + * + * @param objectValue 表单数据对象 + * @return 组装好的数据库记录 + */ + private ObjectValue buildUpdateBill(ObjectValue objectValue) { + ObjectValue bill = new ObjectValue(TABLE_NAME); + // 主键 id 作为 update 条件 + Object idObj = objectValue.get("id"); + if (idObj != null) { + bill.put("id", idObj); + } + fillFields(bill, objectValue); + // 设置默认值 + setDefaultValues(bill); + return bill; + } + + /** + * 把表单字段复制到目标记录(跳过 id、number、非主表字段,控件 key 转数据库字段) + * + * @param target 目标数据库记录 + * @param source 表单数据对象 + */ + private void fillFields(ObjectValue target, ObjectValue source) { + Map values = source.getValues(); + if (values == null) { + return; + } + for (Object item : values.entrySet()) { + Map.Entry entry = (Map.Entry) item; + String widgetKey = entry.getKey().toString(); + + // 跳过主键、参数、子表等非主表字段 + if (shouldSkipField(widgetKey)) { + continue; + } + + // 控件 key 转成数据库字段名 + String dbKey = getDbFieldKey(widgetKey); + // id 由单独逻辑处理,number 保留数据库原值(新增时才生成) + if ("id".equals(dbKey) || "number".equals(dbKey)) { + continue; + } + target.put(dbKey, entry.getValue()); + } + } + + /** + * 设置默认值:流程状态/来源单据等字段 = 0 + * + * @param bill 数据库记录 + */ + private void setDefaultValues(ObjectValue bill) { + bill.put("flow_status", 0); + bill.put("flow_approval_status", 0); + bill.put("org", 0); + bill.put("srcbillid", 0); + bill.put("srcbillnumber", 0); + bill.put("srcentryid", 0); + bill.put("history", 0); + } + + /** + * 自动生成单据号:S + yyyyMMdd + 当天最大流水号 + 1(补 4 位零) + * 例如当天已有 S202608140001,则生成 S202608140002 + * + * @return 新单据号 + */ + private String generateNumber() throws Exception { + String prefix = NUMBER_PREFIX + LocalDate.now().format(DateTimeFormatter.ofPattern(DATE_PATTERN)); + + // 查询当天所有单据号,取最大流水号 + Filter filter = new Filter(); + filter.add(new FilterItem("number", FilterItem.like, prefix + "%")); + ObjectCollection collection = ormGenDataSourceUtil().query(TABLE_NAME, filter, null); + + int maxSeq = 0; + if (collection != null) { + for (int i = 0; i < collection.size(); i++) { + String number = collection.getObject(i).getString("number"); + if (number != null && number.length() > prefix.length()) { + try { + // 截取前缀后的流水号部分并比较 + int seq = Integer.parseInt(number.substring(prefix.length())); + if (seq > maxSeq) { + maxSeq = seq; + } + } catch (NumberFormatException ignored) { + // 忽略非数字流水号 + } + } + } + } + + // 流水号 +1 并补零到指定位数 + return prefix + String.format("%0" + SEQ_LENGTH + "d", maxSeq + 1); + } + + /** + * 获取单位名称(唯一键),兼容 unit_name / unitName 两种控件 key + * + * @param objectValue 表单数据对象 + * @return 单位名称 + */ + private String getUnitName(ObjectValue objectValue) { + String unitName = objectValue.getString(FIELD_UNIT_NAME); + if (unitName == null || unitName.isEmpty()) { + unitName = objectValue.getString("unitName"); + } + return unitName; + } + + /** + * 判断字段是否需要跳过(主键/弹窗参数/来源参数/子表等) + * + * @param widgetKey 控件 key + * @return true 表示跳过 + */ + private boolean shouldSkipField(String widgetKey) { + for (String skipKey : SKIP_WIDGET_KEYS) { + if (skipKey.equalsIgnoreCase(widgetKey)) { + return true; + } + } + return false; + } + + /** + * 控件 key 转数据库字段名(通过 DB_FIELD_WIDGET_MAPPING 反向查找) + * 若未匹配则原样返回(可能是数据库字段名或无需转换的字段) + * + * @param widgetKey 控件 key + * @return 数据库字段名 + */ + private String getDbFieldKey(String widgetKey) { + for (Map.Entry entry : DB_FIELD_WIDGET_MAPPING.entrySet()) { + if (entry.getValue().equalsIgnoreCase(widgetKey)) { + return entry.getKey(); + } + } + return widgetKey; + } +}