1 changed files with 360 additions and 0 deletions
@ -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<String, String> 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<String, String> entry : DB_FIELD_WIDGET_MAPPING.entrySet()) { |
||||||
|
if (entry.getValue().equalsIgnoreCase(widgetKey)) { |
||||||
|
return entry.getKey(); |
||||||
|
} |
||||||
|
} |
||||||
|
return widgetKey; |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue