fix(generator): 修复 create_record 必填字段生成流程
This commit is contained in:
@@ -7,6 +7,7 @@ public class BusinessActionDesign
|
|||||||
{
|
{
|
||||||
private String code;
|
private String code;
|
||||||
private String name;
|
private String name;
|
||||||
|
private String actor;
|
||||||
private String ownerTable;
|
private String ownerTable;
|
||||||
private String method;
|
private String method;
|
||||||
private String path;
|
private String path;
|
||||||
@@ -36,6 +37,8 @@ public class BusinessActionDesign
|
|||||||
public void setCode(String code) { this.code = code; }
|
public void setCode(String code) { this.code = code; }
|
||||||
public String getName() { return name; }
|
public String getName() { return name; }
|
||||||
public void setName(String name) { this.name = name; }
|
public void setName(String name) { this.name = name; }
|
||||||
|
public String getActor() { return actor; }
|
||||||
|
public void setActor(String actor) { this.actor = actor; }
|
||||||
public String getOwnerTable() { return ownerTable; }
|
public String getOwnerTable() { return ownerTable; }
|
||||||
public void setOwnerTable(String ownerTable) { this.ownerTable = ownerTable; }
|
public void setOwnerTable(String ownerTable) { this.ownerTable = ownerTable; }
|
||||||
public String getMethod() { return method; }
|
public String getMethod() { return method; }
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ public class TypedBusinessActionRenderModel
|
|||||||
private boolean currentUserRequired;
|
private boolean currentUserRequired;
|
||||||
private String auditUserSetter;
|
private String auditUserSetter;
|
||||||
private String auditTimeSetter;
|
private String auditTimeSetter;
|
||||||
|
private List<RequestParameter> requestParameters = new ArrayList<RequestParameter>();
|
||||||
private List<Effect> effects = new ArrayList<Effect>();
|
private List<Effect> effects = new ArrayList<Effect>();
|
||||||
|
|
||||||
public String getCode() { return code; }
|
public String getCode() { return code; }
|
||||||
@@ -104,10 +105,29 @@ public class TypedBusinessActionRenderModel
|
|||||||
public void setAuditUserSetter(String auditUserSetter) { this.auditUserSetter = auditUserSetter; }
|
public void setAuditUserSetter(String auditUserSetter) { this.auditUserSetter = auditUserSetter; }
|
||||||
public String getAuditTimeSetter() { return auditTimeSetter; }
|
public String getAuditTimeSetter() { return auditTimeSetter; }
|
||||||
public void setAuditTimeSetter(String auditTimeSetter) { this.auditTimeSetter = auditTimeSetter; }
|
public void setAuditTimeSetter(String auditTimeSetter) { this.auditTimeSetter = auditTimeSetter; }
|
||||||
|
public List<RequestParameter> getRequestParameters() { return requestParameters; }
|
||||||
|
public void setRequestParameters(List<RequestParameter> requestParameters) { this.requestParameters = requestParameters; }
|
||||||
public List<Effect> getEffects() { return effects; }
|
public List<Effect> getEffects() { return effects; }
|
||||||
public void setEffects(List<Effect> effects) { this.effects = effects; }
|
public void setEffects(List<Effect> effects) { this.effects = effects; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class RequestParameter
|
||||||
|
{
|
||||||
|
private String field;
|
||||||
|
private String variable;
|
||||||
|
private String javaType;
|
||||||
|
private String controllerExpression;
|
||||||
|
|
||||||
|
public String getField() { return field; }
|
||||||
|
public void setField(String field) { this.field = field; }
|
||||||
|
public String getVariable() { return variable; }
|
||||||
|
public void setVariable(String variable) { this.variable = variable; }
|
||||||
|
public String getJavaType() { return javaType; }
|
||||||
|
public void setJavaType(String javaType) { this.javaType = javaType; }
|
||||||
|
public String getControllerExpression() { return controllerExpression; }
|
||||||
|
public void setControllerExpression(String controllerExpression) { this.controllerExpression = controllerExpression; }
|
||||||
|
}
|
||||||
|
|
||||||
public static class Effect
|
public static class Effect
|
||||||
{
|
{
|
||||||
private String type;
|
private String type;
|
||||||
|
|||||||
@@ -284,6 +284,7 @@ public class FlowConfigCompiler
|
|||||||
BusinessActionDesign action = new BusinessActionDesign();
|
BusinessActionDesign action = new BusinessActionDesign();
|
||||||
action.setCode(source.getCode());
|
action.setCode(source.getCode());
|
||||||
action.setName(source.getName());
|
action.setName(source.getName());
|
||||||
|
action.setActor(source.getActor());
|
||||||
action.setOwnerTable(ownerTable(config, source));
|
action.setOwnerTable(ownerTable(config, source));
|
||||||
action.setMethod(defaultValue(source.getMethod(), "POST"));
|
action.setMethod(defaultValue(source.getMethod(), "POST"));
|
||||||
action.setPath(defaultValue(source.getPath(), "/" + source.getCode()));
|
action.setPath(defaultValue(source.getPath(), "/" + source.getCode()));
|
||||||
|
|||||||
@@ -120,6 +120,7 @@ public class BusinessLoopDatabaseContractReconciler
|
|||||||
}
|
}
|
||||||
repairAuditFields(tables, flowConfig, maxColumnsPerTable, messages);
|
repairAuditFields(tables, flowConfig, maxColumnsPerTable, messages);
|
||||||
repairEffectFields(tables, flowConfig, maxColumnsPerTable, messages);
|
repairEffectFields(tables, flowConfig, maxColumnsPerTable, messages);
|
||||||
|
repairCreateRecordSourceRequirements(tables, flowConfig, messages);
|
||||||
if (!messages.isEmpty())
|
if (!messages.isEmpty())
|
||||||
{
|
{
|
||||||
List<String> allMessages = database.getRepairMessages() == null
|
List<String> allMessages = database.getRepairMessages() == null
|
||||||
@@ -183,6 +184,43 @@ public class BusinessLoopDatabaseContractReconciler
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void repairCreateRecordSourceRequirements(Map<String, DatabaseTableDesign> tables,
|
||||||
|
SimpleFlowConfig flowConfig, List<String> messages)
|
||||||
|
{
|
||||||
|
if (flowConfig == null) return;
|
||||||
|
for (SimpleFlowConfig flow : FlowConfigSupport.leafFlows(flowConfig))
|
||||||
|
{
|
||||||
|
for (Action action : safe(flow.getActions()))
|
||||||
|
{
|
||||||
|
if (action == null) continue;
|
||||||
|
String ownerTableName = StringUtils.defaultIfBlank(action.getOwnerTable(), flow.getMainTable());
|
||||||
|
DatabaseTableDesign ownerTable = tables.get(normalize(ownerTableName));
|
||||||
|
for (Effect effect : safe(action.getEffects()))
|
||||||
|
{
|
||||||
|
if (effect == null || !"create_record".equals(effect.getType())) continue;
|
||||||
|
DatabaseTableDesign targetTable = tables.get(normalize(effect.getTable()));
|
||||||
|
for (Map.Entry<String, String> value : effect.getValues().entrySet())
|
||||||
|
{
|
||||||
|
String expression = StringUtils.defaultString(value.getValue()).trim();
|
||||||
|
if (!expression.startsWith("record.")) continue;
|
||||||
|
DatabaseColumnDesign target = findColumn(targetTable, value.getKey());
|
||||||
|
DatabaseColumnDesign source = findColumn(ownerTable,
|
||||||
|
expression.substring("record.".length()));
|
||||||
|
if (target == null || source == null || !"1".equals(target.getIsRequired())
|
||||||
|
|| "1".equals(source.getIsRequired()))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
source.setIsRequired("1");
|
||||||
|
messages.add("Required create_record source field: " + ownerTableName + "."
|
||||||
|
+ source.getColumnName() + " -> " + effect.getTable() + "."
|
||||||
|
+ target.getColumnName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private DatabaseColumnDesign primaryKey(DatabaseTableDesign table)
|
private DatabaseColumnDesign primaryKey(DatabaseTableDesign table)
|
||||||
{
|
{
|
||||||
for (DatabaseColumnDesign column : table == null
|
for (DatabaseColumnDesign column : table == null
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import com.ruoyi.generator.domain.front.dto.SimpleFlowConfig.Effect;
|
|||||||
import com.ruoyi.generator.domain.front.dto.SimpleFlowConfig.Metric;
|
import com.ruoyi.generator.domain.front.dto.SimpleFlowConfig.Metric;
|
||||||
import com.ruoyi.generator.factory.flow.FlowConfigCompilation;
|
import com.ruoyi.generator.factory.flow.FlowConfigCompilation;
|
||||||
import com.ruoyi.generator.factory.flow.FlowConfigSupport;
|
import com.ruoyi.generator.factory.flow.FlowConfigSupport;
|
||||||
import com.ruoyi.generator.util.CreateRecordFieldSupport;
|
|
||||||
|
|
||||||
/** Validates compiled flow actions against the database before it is persisted. */
|
/** Validates compiled flow actions against the database before it is persisted. */
|
||||||
@Component
|
@Component
|
||||||
@@ -176,38 +175,17 @@ public class FlowConfigDatabaseContractValidator
|
|||||||
throw new ServiceException("Flow config value types do not match: " + expression
|
throw new ServiceException("Flow config value types do not match: " + expression
|
||||||
+ " -> " + targetTable + "." + value.getKey());
|
+ " -> " + targetTable + "." + value.getKey());
|
||||||
}
|
}
|
||||||
|
if ("create_record".equals(effect.getType())
|
||||||
|
&& "1".equals(target.getIsRequired())
|
||||||
|
&& !"1".equals(source.getIsRequired()))
|
||||||
|
{
|
||||||
|
throw new ServiceException("Flow config create_record source may be null for required field: "
|
||||||
|
+ ownerTable + "." + source.getColumnName() + " -> "
|
||||||
|
+ targetTable + "." + target.getColumnName());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else validateLiteral(expression, target, targetTable + "." + value.getKey());
|
else validateLiteral(expression, target, targetTable + "." + value.getKey());
|
||||||
}
|
}
|
||||||
if ("create_record".equals(effect.getType()))
|
|
||||||
{
|
|
||||||
validateRequiredCreateFields(schema.get(normalize(targetTable)), effect, targetTable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void validateRequiredCreateFields(Map<String, DatabaseColumnDesign> columns, Effect effect,
|
|
||||||
String targetTable)
|
|
||||||
{
|
|
||||||
if (columns == null) return;
|
|
||||||
Map<String, String> values = effect.getValues();
|
|
||||||
for (DatabaseColumnDesign column : columns.values())
|
|
||||||
{
|
|
||||||
if (column == null || !"1".equals(column.getIsRequired())) continue;
|
|
||||||
if ("1".equals(column.getIsIncrement()) || hasUsableDefault(column.getDefaultValue())) continue;
|
|
||||||
if (values != null && values.containsKey(column.getColumnName())) continue;
|
|
||||||
if (CreateRecordFieldSupport.isCreationTimeField(
|
|
||||||
column.getColumnName(), column.getJavaField()) && family(column) == TypeFamily.TEMPORAL)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
throw new ServiceException("Flow config create_record missing required field: "
|
|
||||||
+ targetTable + "." + column.getColumnName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean hasUsableDefault(String defaultValue)
|
|
||||||
{
|
|
||||||
return StringUtils.isNotBlank(defaultValue) && !"null".equalsIgnoreCase(defaultValue.trim());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean requiresNumericMetric(Metric metric)
|
private boolean requiresNumericMetric(Metric metric)
|
||||||
|
|||||||
@@ -205,7 +205,7 @@ public final class BusinessActionRenderSupport
|
|||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
String expression = automaticInsertValueExpression(column);
|
String expression = automaticInsertValueExpression(action, column);
|
||||||
if (!isEmpty(expression))
|
if (!isEmpty(expression))
|
||||||
{
|
{
|
||||||
values.put(column.getColumnName(), expression);
|
values.put(column.getColumnName(), expression);
|
||||||
@@ -222,13 +222,14 @@ public final class BusinessActionRenderSupport
|
|||||||
&& !values.containsKey(column.getColumnName());
|
&& !values.containsKey(column.getColumnName());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String automaticInsertValueExpression(GenTableColumn column)
|
private static String automaticInsertValueExpression(BusinessActionDesign action, GenTableColumn column)
|
||||||
{
|
{
|
||||||
if (isCreationTimeColumn(column))
|
if (isCreationTimeColumn(column))
|
||||||
{
|
{
|
||||||
return "${now}";
|
return "${now}";
|
||||||
}
|
}
|
||||||
if (CurrentUserFieldSupport.isCurrentUserIdField(column))
|
if (CurrentUserFieldSupport.isCurrentUserIdField(column,
|
||||||
|
action == null ? null : action.getActor()))
|
||||||
{
|
{
|
||||||
return "${current_user.id}";
|
return "${current_user.id}";
|
||||||
}
|
}
|
||||||
@@ -328,10 +329,8 @@ public final class BusinessActionRenderSupport
|
|||||||
}
|
}
|
||||||
private static boolean isCreationTimeColumn(GenTableColumn column)
|
private static boolean isCreationTimeColumn(GenTableColumn column)
|
||||||
{
|
{
|
||||||
String columnName = normalize(column.getColumnName());
|
boolean creationField = CreateRecordFieldSupport.isCreationTimeField(
|
||||||
String javaField = normalize(column.getJavaField());
|
column.getColumnName(), column.getJavaField());
|
||||||
boolean creationField = "created_at".equals(columnName) || "create_time".equals(columnName)
|
|
||||||
|| "createdat".equals(javaField) || "createtime".equals(javaField);
|
|
||||||
String columnType = normalize(column.getColumnType());
|
String columnType = normalize(column.getColumnType());
|
||||||
String javaType = normalize(column.getJavaType());
|
String javaType = normalize(column.getJavaType());
|
||||||
return creationField && (columnType.contains("date") || columnType.contains("time")
|
return creationField && (columnType.contains("date") || columnType.contains("time")
|
||||||
|
|||||||
@@ -23,6 +23,15 @@ public final class CurrentUserFieldSupport
|
|||||||
column.getColumnComment());
|
column.getColumnComment());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isCurrentUserIdField(GenTableColumn column, String actor)
|
||||||
|
{
|
||||||
|
if (isCurrentUserIdField(column)) return true;
|
||||||
|
if (column == null || StringUtils.isBlank(actor)) return false;
|
||||||
|
String actorId = compact(actor) + "id";
|
||||||
|
return actorId.equals(compact(column.getColumnName()))
|
||||||
|
|| actorId.equals(compact(column.getJavaField()));
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean isCurrentUserIdField(FrontProjectColumn column)
|
public static boolean isCurrentUserIdField(FrontProjectColumn column)
|
||||||
{
|
{
|
||||||
return column != null && isCurrentUserIdField(column.getColumnName(), column.getJavaField(),
|
return column != null && isCurrentUserIdField(column.getColumnName(), column.getJavaField(),
|
||||||
@@ -111,4 +120,4 @@ public final class CurrentUserFieldSupport
|
|||||||
{
|
{
|
||||||
return StringUtils.defaultString(value).trim().toLowerCase(Locale.ENGLISH).replaceAll("[^a-z0-9]+", "");
|
return StringUtils.defaultString(value).trim().toLowerCase(Locale.ENGLISH).replaceAll("[^a-z0-9]+", "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import com.ruoyi.generator.domain.render.TypedBusinessActionRenderModel.Action;
|
|||||||
import com.ruoyi.generator.domain.render.TypedBusinessActionRenderModel.Assignment;
|
import com.ruoyi.generator.domain.render.TypedBusinessActionRenderModel.Assignment;
|
||||||
import com.ruoyi.generator.domain.render.TypedBusinessActionRenderModel.Dependency;
|
import com.ruoyi.generator.domain.render.TypedBusinessActionRenderModel.Dependency;
|
||||||
import com.ruoyi.generator.domain.render.TypedBusinessActionRenderModel.Effect;
|
import com.ruoyi.generator.domain.render.TypedBusinessActionRenderModel.Effect;
|
||||||
|
import com.ruoyi.generator.domain.render.TypedBusinessActionRenderModel.RequestParameter;
|
||||||
|
|
||||||
/** Converts flow_config to explicit, strongly typed Java render instructions. */
|
/** Converts flow_config to explicit, strongly typed Java render instructions. */
|
||||||
public final class TypedBusinessActionCompiler
|
public final class TypedBusinessActionCompiler
|
||||||
@@ -94,9 +95,6 @@ public final class TypedBusinessActionCompiler
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
action.setCurrentUserRequired(currentUserRequired);
|
action.setCurrentUserRequired(currentUserRequired);
|
||||||
action.setServiceParameters(pk.getJavaType() + " " + pk.getJavaField()
|
|
||||||
+ (currentUserRequired ? ", Long currentUserId" : ""));
|
|
||||||
action.setServiceArguments(pk.getJavaField() + (currentUserRequired ? ", currentUser.getUserId()" : ""));
|
|
||||||
action.setControllerIdExpression(idExpression(pk.getJavaType(), "params.get(\"" + pk.getJavaField() + "\")"));
|
action.setControllerIdExpression(idExpression(pk.getJavaType(), "params.get(\"" + pk.getJavaField() + "\")"));
|
||||||
if (source.getAudit() != null)
|
if (source.getAudit() != null)
|
||||||
{
|
{
|
||||||
@@ -116,14 +114,15 @@ public final class TypedBusinessActionCompiler
|
|||||||
for (SimpleFlowConfig.Effect sourceEffect : source.getEffects())
|
for (SimpleFlowConfig.Effect sourceEffect : source.getEffects())
|
||||||
{
|
{
|
||||||
action.getEffects().add(compileEffect(model, sourceEffect, action, owner, tables,
|
action.getEffects().add(compileEffect(model, sourceEffect, action, owner, tables,
|
||||||
dependencies, index++));
|
dependencies, source.getActor(), index++));
|
||||||
}
|
}
|
||||||
|
configureActionParameters(action, pk);
|
||||||
return action;
|
return action;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Effect compileEffect(TypedBusinessActionRenderModel model, SimpleFlowConfig.Effect source,
|
private static Effect compileEffect(TypedBusinessActionRenderModel model, SimpleFlowConfig.Effect source,
|
||||||
Action action, GenTable owner, Map<String, GenTable> tables, Map<String, Dependency> dependencies,
|
Action action, GenTable owner, Map<String, GenTable> tables, Map<String, Dependency> dependencies,
|
||||||
int index)
|
String actor, int index)
|
||||||
{
|
{
|
||||||
GenTable target = tables.get(source.getTable());
|
GenTable target = tables.get(source.getTable());
|
||||||
if (target == null) throw new ServiceException("flow_config 引用了不存在的表: " + source.getTable());
|
if (target == null) throw new ServiceException("flow_config 引用了不存在的表: " + source.getTable());
|
||||||
@@ -165,27 +164,89 @@ public final class TypedBusinessActionCompiler
|
|||||||
}
|
}
|
||||||
if ("create_record".equals(source.getType()))
|
if ("create_record".equals(source.getType()))
|
||||||
{
|
{
|
||||||
appendImplicitCreationTimeAssignments(model, effect, target, assignedFields);
|
appendImplicitCreateAssignments(model, effect, target, assignedFields, action, actor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return effect;
|
return effect;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void appendImplicitCreationTimeAssignments(TypedBusinessActionRenderModel model, Effect effect,
|
private static void appendImplicitCreateAssignments(TypedBusinessActionRenderModel model, Effect effect,
|
||||||
GenTable target, Map<String, String> assignedFields)
|
GenTable target, Map<String, String> assignedFields, Action action, String actor)
|
||||||
{
|
{
|
||||||
if (target.getColumns() == null) return;
|
if (target.getColumns() == null) return;
|
||||||
for (GenTableColumn column : target.getColumns())
|
for (GenTableColumn column : target.getColumns())
|
||||||
{
|
{
|
||||||
if (assignedFields.containsKey(column.getColumnName())
|
if (assignedFields.containsKey(column.getColumnName())
|
||||||
|| assignedFields.containsValue(column.getJavaField())) continue;
|
|| assignedFields.containsValue(column.getJavaField())) continue;
|
||||||
if (!CreateRecordFieldSupport.isCreationTimeField(
|
if (column.isIncrement()) continue;
|
||||||
column.getColumnName(), column.getJavaField())) continue;
|
String expression = null;
|
||||||
String expression = currentTimeExpression(model, column.getJavaType());
|
if (CreateRecordFieldSupport.isCreationTimeField(
|
||||||
|
column.getColumnName(), column.getJavaField()))
|
||||||
|
{
|
||||||
|
expression = currentTimeExpression(model, column.getJavaType());
|
||||||
|
}
|
||||||
|
else if (!column.isRequired())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (CurrentUserFieldSupport.isCurrentUserIdField(column, actor))
|
||||||
|
{
|
||||||
|
action.setCurrentUserRequired(true);
|
||||||
|
expression = "currentUserId";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
expression = ensureRequestParameter(model, action, column).getVariable();
|
||||||
|
}
|
||||||
if (expression != null) addAssignment(effect, column, expression);
|
if (expression != null) addAssignment(effect, column, expression);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static RequestParameter ensureRequestParameter(TypedBusinessActionRenderModel model,
|
||||||
|
Action action, GenTableColumn column)
|
||||||
|
{
|
||||||
|
for (RequestParameter existing : action.getRequestParameters())
|
||||||
|
{
|
||||||
|
if (!StringUtils.equals(existing.getField(), column.getColumnName())) continue;
|
||||||
|
if (!StringUtils.equals(existing.getJavaType(), renderJavaType(column.getJavaType())))
|
||||||
|
{
|
||||||
|
throw new ServiceException("create_record input field type conflict: "
|
||||||
|
+ column.getColumnName());
|
||||||
|
}
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
String javaType = renderJavaType(column.getJavaType());
|
||||||
|
RequestParameter parameter = new RequestParameter();
|
||||||
|
parameter.setField(column.getColumnName());
|
||||||
|
parameter.setVariable("input" + Character.toUpperCase(column.getJavaField().charAt(0))
|
||||||
|
+ column.getJavaField().substring(1));
|
||||||
|
parameter.setJavaType(javaType);
|
||||||
|
parameter.setControllerExpression(requestParameterExpression(model, javaType,
|
||||||
|
"params.get(\"" + javaEscape(column.getColumnName()) + "\")",
|
||||||
|
column.getColumnName()));
|
||||||
|
action.getRequestParameters().add(parameter);
|
||||||
|
return parameter;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void configureActionParameters(Action action, GenTableColumn pk)
|
||||||
|
{
|
||||||
|
StringBuilder parameters = new StringBuilder(pk.getJavaType()).append(" ").append(pk.getJavaField());
|
||||||
|
StringBuilder arguments = new StringBuilder(pk.getJavaField());
|
||||||
|
if (action.isCurrentUserRequired())
|
||||||
|
{
|
||||||
|
parameters.append(", Long currentUserId");
|
||||||
|
arguments.append(", currentUser.getUserId()");
|
||||||
|
}
|
||||||
|
for (RequestParameter parameter : action.getRequestParameters())
|
||||||
|
{
|
||||||
|
parameters.append(", ").append(parameter.getJavaType()).append(" ")
|
||||||
|
.append(parameter.getVariable());
|
||||||
|
arguments.append(", ").append(parameter.getVariable());
|
||||||
|
}
|
||||||
|
action.setServiceParameters(parameters.toString());
|
||||||
|
action.setServiceArguments(arguments.toString());
|
||||||
|
}
|
||||||
|
|
||||||
private static String currentTimeExpression(TypedBusinessActionRenderModel model, String javaType)
|
private static String currentTimeExpression(TypedBusinessActionRenderModel model, String javaType)
|
||||||
{
|
{
|
||||||
if ("Date".equals(javaType) || "java.util.Date".equals(javaType))
|
if ("Date".equals(javaType) || "java.util.Date".equals(javaType))
|
||||||
@@ -275,6 +336,48 @@ public final class TypedBusinessActionCompiler
|
|||||||
return normalized;
|
return normalized;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String renderJavaType(String javaType)
|
||||||
|
{
|
||||||
|
return StringUtils.defaultIfBlank(javaType, "String");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String requestParameterExpression(TypedBusinessActionRenderModel model,
|
||||||
|
String javaType, String source, String field)
|
||||||
|
{
|
||||||
|
if ("String".equals(javaType)) return "String.valueOf(" + source + ")";
|
||||||
|
if ("Integer".equals(javaType) || "int".equals(javaType))
|
||||||
|
return "Integer.valueOf(String.valueOf(" + source + "))";
|
||||||
|
if ("Long".equals(javaType) || "long".equals(javaType))
|
||||||
|
return "Long.valueOf(String.valueOf(" + source + "))";
|
||||||
|
if ("Short".equals(javaType) || "short".equals(javaType))
|
||||||
|
return "Short.valueOf(String.valueOf(" + source + "))";
|
||||||
|
if ("Double".equals(javaType) || "double".equals(javaType))
|
||||||
|
return "Double.valueOf(String.valueOf(" + source + "))";
|
||||||
|
if ("Float".equals(javaType) || "float".equals(javaType))
|
||||||
|
return "Float.valueOf(String.valueOf(" + source + "))";
|
||||||
|
if ("Boolean".equals(javaType) || "boolean".equals(javaType))
|
||||||
|
return "Boolean.valueOf(String.valueOf(" + source + "))";
|
||||||
|
if ("BigDecimal".equals(javaType) || "java.math.BigDecimal".equals(javaType))
|
||||||
|
{
|
||||||
|
model.setUsesBigDecimal(true);
|
||||||
|
return "new BigDecimal(String.valueOf(" + source + "))";
|
||||||
|
}
|
||||||
|
if ("Date".equals(javaType) || "java.util.Date".equals(javaType))
|
||||||
|
{
|
||||||
|
model.setUsesDate(true);
|
||||||
|
return "parseBusinessActionDate(" + source + ", \"" + javaEscape(field) + "\")";
|
||||||
|
}
|
||||||
|
if ("LocalDate".equals(javaType) || "java.time.LocalDate".equals(javaType))
|
||||||
|
return "java.time.LocalDate.parse(String.valueOf(" + source + "))";
|
||||||
|
if ("LocalDateTime".equals(javaType) || "java.time.LocalDateTime".equals(javaType))
|
||||||
|
return "java.time.LocalDateTime.parse(String.valueOf(" + source + "))";
|
||||||
|
if ("Instant".equals(javaType) || "java.time.Instant".equals(javaType))
|
||||||
|
return "java.time.Instant.parse(String.valueOf(" + source + "))";
|
||||||
|
if ("Timestamp".equals(javaType) || "java.sql.Timestamp".equals(javaType))
|
||||||
|
return "java.sql.Timestamp.valueOf(String.valueOf(" + source + ").replace('T', ' '))";
|
||||||
|
return "(" + javaType + ") " + source;
|
||||||
|
}
|
||||||
|
|
||||||
private static String idExpression(String javaType, String source)
|
private static String idExpression(String javaType, String source)
|
||||||
{
|
{
|
||||||
if ("String".equals(javaType)) return "String.valueOf(" + source + ")";
|
if ("String".equals(javaType)) return "String.valueOf(" + source + ")";
|
||||||
|
|||||||
@@ -13,7 +13,13 @@ import ${packageName}.security.PortalAuthTokenStore;
|
|||||||
#end
|
#end
|
||||||
import ${packageName}.service.I${ClassName}Service;
|
import ${packageName}.service.I${ClassName}Service;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
#if($typedBusinessUsesBigDecimal)
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
#end
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
|
#if($typedBusinessUsesDate)
|
||||||
|
import java.util.Date;
|
||||||
|
#end
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
@@ -179,6 +185,13 @@ public class ${ClassName}Controller
|
|||||||
}
|
}
|
||||||
AuthPrincipal currentUser = requireCurrentUser(request);
|
AuthPrincipal currentUser = requireCurrentUser(request);
|
||||||
${action.pkType} ${action.pkField} = ${action.controllerIdExpression};
|
${action.pkType} ${action.pkField} = ${action.controllerIdExpression};
|
||||||
|
#foreach($parameter in $action.requestParameters)
|
||||||
|
if (!hasValue(params.get("${parameter.field}")))
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("Missing business action parameter: ${parameter.field}");
|
||||||
|
}
|
||||||
|
${parameter.javaType} ${parameter.variable} = ${parameter.controllerExpression};
|
||||||
|
#end
|
||||||
${className}Service.${action.javaMethodName}(${action.serviceArguments});
|
${className}Service.${action.javaMethodName}(${action.serviceArguments});
|
||||||
return Result.success();
|
return Result.success();
|
||||||
}
|
}
|
||||||
@@ -200,6 +213,24 @@ public class ${ClassName}Controller
|
|||||||
}
|
}
|
||||||
#end
|
#end
|
||||||
|
|
||||||
|
#if($hasTypedBusinessActions)
|
||||||
|
#if($typedBusinessUsesDate)
|
||||||
|
private Date parseBusinessActionDate(Object value, String field)
|
||||||
|
{
|
||||||
|
String text = String.valueOf(value).trim().replace('T', ' ');
|
||||||
|
String pattern = text.length() <= 10 ? "yyyy-MM-dd" : "yyyy-MM-dd HH:mm:ss";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return new java.text.SimpleDateFormat(pattern).parse(text);
|
||||||
|
}
|
||||||
|
catch (java.text.ParseException e)
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("Invalid business action date parameter: " + field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#end
|
||||||
|
#end
|
||||||
private boolean hasValue(Object value)
|
private boolean hasValue(Object value)
|
||||||
{
|
{
|
||||||
return value != null && StringUtils.isNotBlank(String.valueOf(value));
|
return value != null && StringUtils.isNotBlank(String.valueOf(value));
|
||||||
|
|||||||
@@ -2,6 +2,12 @@ package ${packageName}.service;
|
|||||||
|
|
||||||
import ${packageName}.entity.${ClassName};
|
import ${packageName}.entity.${ClassName};
|
||||||
import com.baomidou.mybatisplus.extension.service.IService;
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
#if($typedBusinessUsesBigDecimal)
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
#end
|
||||||
|
#if($typedBusinessUsesDate)
|
||||||
|
import java.util.Date;
|
||||||
|
#end
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ${functionName}Service接口
|
* ${functionName}Service接口
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import com.ruoyi.generator.domain.front.dto.DatabaseTableDesign;
|
|||||||
import com.ruoyi.generator.domain.front.dto.SimpleFlowConfig;
|
import com.ruoyi.generator.domain.front.dto.SimpleFlowConfig;
|
||||||
import com.ruoyi.generator.domain.front.dto.SimpleFlowConfig.Action;
|
import com.ruoyi.generator.domain.front.dto.SimpleFlowConfig.Action;
|
||||||
import com.ruoyi.generator.domain.front.dto.SimpleFlowConfig.Audit;
|
import com.ruoyi.generator.domain.front.dto.SimpleFlowConfig.Audit;
|
||||||
|
import com.ruoyi.generator.domain.front.dto.SimpleFlowConfig.Effect;
|
||||||
|
|
||||||
public class BusinessLoopDatabaseContractReconcilerTest
|
public class BusinessLoopDatabaseContractReconcilerTest
|
||||||
{
|
{
|
||||||
@@ -120,6 +121,40 @@ public class BusinessLoopDatabaseContractReconcilerTest
|
|||||||
new FlowConfigDatabaseContractValidator().validate(database, flowConfig, null);
|
new FlowConfigDatabaseContractValidator().validate(database, flowConfig, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void requiresNullableSourceUsedByRequiredCreateRecordTarget()
|
||||||
|
{
|
||||||
|
DatabaseColumnDesign opportunityAmount = column("amount", "decimal(10,2)");
|
||||||
|
DatabaseTableDesign opportunity = new DatabaseTableDesign();
|
||||||
|
opportunity.setTableName("opportunity");
|
||||||
|
opportunity.setColumns(Arrays.asList(column("id", "bigint(20)"), opportunityAmount));
|
||||||
|
DatabaseColumnDesign contractAmount = column("amount", "decimal(10,2)");
|
||||||
|
contractAmount.setIsRequired("1");
|
||||||
|
DatabaseTableDesign contract = new DatabaseTableDesign();
|
||||||
|
contract.setTableName("contract");
|
||||||
|
contract.setColumns(Arrays.asList(column("id", "bigint(20)"), contractAmount));
|
||||||
|
DatabaseDesignResponse database = new DatabaseDesignResponse();
|
||||||
|
database.setTables(Arrays.asList(opportunity, contract));
|
||||||
|
|
||||||
|
Effect create = new Effect();
|
||||||
|
create.setType("create_record");
|
||||||
|
create.setTable("contract");
|
||||||
|
create.getValues().put("amount", "record.amount");
|
||||||
|
Action action = new Action();
|
||||||
|
action.setOwnerTable("opportunity");
|
||||||
|
action.setEffects(Collections.singletonList(create));
|
||||||
|
SimpleFlowConfig flowConfig = new SimpleFlowConfig();
|
||||||
|
flowConfig.setMainTable("contract");
|
||||||
|
flowConfig.setActions(Collections.singletonList(action));
|
||||||
|
|
||||||
|
java.util.List<String> messages = reconciler.repair(database, new BusinessLoopPlan(),
|
||||||
|
flowConfig, 30);
|
||||||
|
|
||||||
|
assertEquals("1", opportunityAmount.getIsRequired());
|
||||||
|
assertTrue(messages.toString(), messages.toString().contains(
|
||||||
|
"opportunity.amount -> contract.amount"));
|
||||||
|
}
|
||||||
|
|
||||||
private BusinessLoopPlan metricPlan(String field, String aggregate, String unit)
|
private BusinessLoopPlan metricPlan(String field, String aggregate, String unit)
|
||||||
{
|
{
|
||||||
BusinessLoopMetric metric = new BusinessLoopMetric();
|
BusinessLoopMetric metric = new BusinessLoopMetric();
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ public class FlowConfigDatabaseContractValidatorTest
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void rejectsCreateRecordThatOmitsNonGeneratedRequiredField()
|
public void acceptsCreateRecordThatUsesImplicitInputForRequiredField()
|
||||||
{
|
{
|
||||||
DatabaseTableDesign book = table("book_info", "id");
|
DatabaseTableDesign book = table("book_info", "id");
|
||||||
DatabaseTableDesign borrow = table("borrow_record", "id", "book_id", "reader_id", "status");
|
DatabaseTableDesign borrow = table("borrow_record", "id", "book_id", "reader_id", "status");
|
||||||
@@ -143,14 +143,41 @@ public class FlowConfigDatabaseContractValidatorTest
|
|||||||
+ "\"reader_id\":\"currentUserId\",\"status\":\"pending\"}}]}]}",
|
+ "\"reader_id\":\"currentUserId\",\"status\":\"pending\"}}]}]}",
|
||||||
SimpleFlowConfig.class);
|
SimpleFlowConfig.class);
|
||||||
|
|
||||||
|
validator.validate(database, flow, new FlowConfigCompiler().compile(flow));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void rejectsNullableRecordSourceForRequiredCreateField()
|
||||||
|
{
|
||||||
|
DatabaseTableDesign opportunity = table("opportunity", "id", "status");
|
||||||
|
DatabaseColumnDesign amount = column("amount");
|
||||||
|
amount.setColumnType("decimal(10,2)");
|
||||||
|
opportunity.getColumns().add(amount);
|
||||||
|
DatabaseTableDesign contract = table("contract", "id", "status");
|
||||||
|
contract.getColumns().add(requiredColumn("amount", "decimal(10,2)"));
|
||||||
|
DatabaseDesignResponse database = new DatabaseDesignResponse();
|
||||||
|
database.setTables(Arrays.asList(opportunity, contract));
|
||||||
|
database.setBusinessActions(Collections.<BusinessActionDesign>emptyList());
|
||||||
|
SimpleFlowConfig flow = JSON.parseObject("{"
|
||||||
|
+ "\"code\":\"contract_flow\",\"name\":\"Contract\","
|
||||||
|
+ "\"mainTable\":\"contract\",\"statusField\":\"status\","
|
||||||
|
+ "\"states\":[{\"code\":\"pending\",\"label\":\"Pending\"},"
|
||||||
|
+ "{\"code\":\"active\",\"label\":\"Active\"}],"
|
||||||
|
+ "\"actions\":[{\"code\":\"create_contract\",\"name\":\"Create contract\","
|
||||||
|
+ "\"ownerTable\":\"opportunity\",\"effects\":[{\"type\":\"create_record\","
|
||||||
|
+ "\"table\":\"contract\",\"values\":{\"amount\":\"record.amount\","
|
||||||
|
+ "\"status\":\"pending\"}}]}]}",
|
||||||
|
SimpleFlowConfig.class);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
validator.validate(database, flow, new FlowConfigCompiler().compile(flow));
|
validator.validate(database, flow, new FlowConfigCompiler().compile(flow));
|
||||||
fail("Expected missing required create_record field validation to fail");
|
fail("Expected nullable create_record source validation to fail");
|
||||||
}
|
}
|
||||||
catch (ServiceException e)
|
catch (ServiceException e)
|
||||||
{
|
{
|
||||||
assertEquals("Flow config create_record missing required field: borrow_record.tenant_id", e.getMessage());
|
assertEquals("Flow config create_record source may be null for required field: "
|
||||||
|
+ "opportunity.amount -> contract.amount", e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -148,6 +148,28 @@ public class BusinessActionRenderSupportTest
|
|||||||
assertEquals(Arrays.asList("book_id", "due_date"), fields(action));
|
assertEquals(Arrays.asList("book_id", "due_date"), fields(action));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void usesCustomActionActorIdAsTrustedCurrentUserInput()
|
||||||
|
{
|
||||||
|
BusinessActionDesign action = new BusinessActionDesign();
|
||||||
|
action.setActor("salesperson");
|
||||||
|
action.setOwnerTable("customer_info");
|
||||||
|
BusinessActionEffectDesign effect = new BusinessActionEffectDesign();
|
||||||
|
effect.setType("INSERT_ROW");
|
||||||
|
effect.setTargetTable("opportunity");
|
||||||
|
action.setEffects(Arrays.asList(effect));
|
||||||
|
GenTableColumn salespersonId = column(
|
||||||
|
"salesperson_id", "salespersonId", "Salesperson", "input", "");
|
||||||
|
salespersonId.setIsRequired("1");
|
||||||
|
GenTable opportunity = table("opportunity", salespersonId);
|
||||||
|
|
||||||
|
BusinessActionRenderSupport.enrich(action, Arrays.asList(opportunity));
|
||||||
|
|
||||||
|
assertEquals("${current_user.id}", effect.getValues().get("salesperson_id"));
|
||||||
|
assertEquals(Arrays.asList("salesperson_id"), action.getCurrentUserFields());
|
||||||
|
assertEquals(Arrays.asList(), fields(action));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void infersTargetFieldsFromEffectsAndEveryRuleValueInRequestOrder()
|
public void infersTargetFieldsFromEffectsAndEveryRuleValueInRequestOrder()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -140,6 +140,62 @@ public class TypedBusinessActionCompilerTest
|
|||||||
assertTrue(TypedBusinessActionCompiler.compile(project, borrowRecord).getActions().isEmpty());
|
assertTrue(TypedBusinessActionCompiler.compile(project, borrowRecord).getActions().isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void rendersMissingRequiredCreateFieldsAsTypedActionInputs()
|
||||||
|
{
|
||||||
|
GenTable customer = table("customer_info", "CustomerInfo", "Customer",
|
||||||
|
column("id", "Long", "id", true, "Customer ID"),
|
||||||
|
column("name", "String", "name", false, "Customer name"));
|
||||||
|
GenTable opportunity = table("opportunity", "Opportunity", "Opportunity",
|
||||||
|
column("id", "Long", "id", true, "Opportunity ID"),
|
||||||
|
requiredColumn("customer_id", "Long", "customerId", "Customer"),
|
||||||
|
requiredColumn("opportunity_name", "String", "opportunityName", "Opportunity name"),
|
||||||
|
requiredColumn("amount", "BigDecimal", "amount", "Amount"),
|
||||||
|
requiredColumn("status", "String", "status", "Status"),
|
||||||
|
requiredColumn("salesperson_id", "Long", "salespersonId", "Salesperson ID"),
|
||||||
|
requiredColumn("create_time", "Date", "createTime", "Create time"));
|
||||||
|
GenProject project = new GenProject();
|
||||||
|
project.setPackageName("com.example.crm");
|
||||||
|
project.setTables(Arrays.asList(customer, opportunity));
|
||||||
|
project.setFlowConfig(JSON.parseObject("{"
|
||||||
|
+ "\"code\":\"opportunity_flow\",\"name\":\"Opportunity flow\","
|
||||||
|
+ "\"mainTable\":\"opportunity\",\"statusField\":\"status\","
|
||||||
|
+ "\"states\":[{\"code\":\"new\",\"label\":\"New\"}],"
|
||||||
|
+ "\"actions\":[{\"code\":\"create_opportunity\",\"name\":\"Create opportunity\","
|
||||||
|
+ "\"actor\":\"salesperson\","
|
||||||
|
+ "\"ownerTable\":\"customer_info\",\"method\":\"POST\","
|
||||||
|
+ "\"path\":\"/customer-info/create-opportunity\",\"effects\":[{"
|
||||||
|
+ "\"type\":\"create_record\",\"table\":\"opportunity\","
|
||||||
|
+ "\"values\":{\"customer_id\":\"record.id\",\"status\":\"new\"}}]}]}",
|
||||||
|
SimpleFlowConfig.class));
|
||||||
|
|
||||||
|
TypedBusinessActionRenderModel model = TypedBusinessActionCompiler.compile(project, customer);
|
||||||
|
VelocityContext context = VelocityUtils.prepareContext(customer);
|
||||||
|
context.put("typedBusinessActionModel", model);
|
||||||
|
context.put("typedBusinessActions", model.getActions());
|
||||||
|
context.put("typedBusinessDependencies", model.getDependencies());
|
||||||
|
context.put("hasTypedBusinessActions", !model.getActions().isEmpty());
|
||||||
|
context.put("typedBusinessUsesBigDecimal", model.isUsesBigDecimal());
|
||||||
|
context.put("typedBusinessUsesDate", model.isUsesDate());
|
||||||
|
|
||||||
|
VelocityInitializer.initVelocity();
|
||||||
|
String implementation = render("qing/serviceImpl.java.vm", context);
|
||||||
|
String service = render("qing/service.java.vm", context);
|
||||||
|
String controller = render("qing/controller.java.vm", context);
|
||||||
|
|
||||||
|
assertTrue(implementation.contains("opportunity.setOpportunityName(inputOpportunityName);"));
|
||||||
|
assertTrue(implementation.contains("opportunity.setAmount(inputAmount);"));
|
||||||
|
assertTrue(implementation.contains("opportunity.setSalespersonId(currentUserId);"));
|
||||||
|
assertTrue(implementation.contains("opportunity.setCreateTime(new Date());"));
|
||||||
|
assertTrue(service.contains("void create_opportunity(Long id, Long currentUserId, "
|
||||||
|
+ "String inputOpportunityName, BigDecimal inputAmount);"));
|
||||||
|
assertTrue(controller.contains("Missing business action parameter: opportunity_name"));
|
||||||
|
assertTrue(controller.contains("String inputOpportunityName = String.valueOf("));
|
||||||
|
assertTrue(controller.contains("BigDecimal inputAmount = new BigDecimal(String.valueOf("));
|
||||||
|
assertTrue(controller.contains("customerInfoService.create_opportunity(id, currentUser.getUserId(), "
|
||||||
|
+ "inputOpportunityName, inputAmount);"));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void rendersJavaSafeMethodNameForKeywordActionCode()
|
public void rendersJavaSafeMethodNameForKeywordActionCode()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,135 @@
|
|||||||
|
-- Align managed flow/database Prompts with deterministic create_record action inputs.
|
||||||
|
-- Idempotent: each Prompt is versioned only when its current release lacks the marker.
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
|
||||||
|
DROP PROCEDURE IF EXISTS upgrade_create_record_action_input_prompts_20260715$$
|
||||||
|
|
||||||
|
CREATE PROCEDURE upgrade_create_record_action_input_prompts_20260715()
|
||||||
|
BEGIN
|
||||||
|
DECLARE v_done INT DEFAULT 0;
|
||||||
|
DECLARE v_template_id BIGINT;
|
||||||
|
DECLARE v_current_version_id BIGINT;
|
||||||
|
DECLARE v_new_version_id BIGINT;
|
||||||
|
DECLARE v_version_no INT;
|
||||||
|
DECLARE v_version_label VARCHAR(50);
|
||||||
|
DECLARE v_generate_type VARCHAR(50);
|
||||||
|
DECLARE v_prompt_code VARCHAR(100);
|
||||||
|
DECLARE v_system_prompt LONGTEXT;
|
||||||
|
DECLARE v_user_prompt_contract VARCHAR(255);
|
||||||
|
DECLARE v_user_prompt_template LONGTEXT;
|
||||||
|
DECLARE v_target_system_prompt LONGTEXT;
|
||||||
|
DECLARE v_target_user_prompt LONGTEXT;
|
||||||
|
DECLARE v_provider_code VARCHAR(50);
|
||||||
|
DECLARE v_model VARCHAR(100);
|
||||||
|
DECLARE v_pipeline_release_code VARCHAR(100);
|
||||||
|
DECLARE v_content_hash CHAR(64);
|
||||||
|
DECLARE v_marker VARCHAR(100);
|
||||||
|
DECLARE v_rule LONGTEXT;
|
||||||
|
|
||||||
|
DECLARE prompt_cursor CURSOR FOR
|
||||||
|
SELECT t.prompt_template_id, t.current_version_id, t.generate_type, t.prompt_code,
|
||||||
|
v.system_prompt, v.user_prompt_contract, v.user_prompt_template,
|
||||||
|
v.provider_code, v.model, v.pipeline_release_code
|
||||||
|
FROM factory_prompt_template t
|
||||||
|
JOIN factory_prompt_version v ON v.prompt_version_id = t.current_version_id
|
||||||
|
WHERE t.prompt_code IN ('front.flow_config', 'front.database')
|
||||||
|
ORDER BY t.prompt_template_id;
|
||||||
|
|
||||||
|
DECLARE EXIT HANDLER FOR SQLEXCEPTION
|
||||||
|
BEGIN
|
||||||
|
ROLLBACK;
|
||||||
|
RESIGNAL;
|
||||||
|
END;
|
||||||
|
DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_done = 1;
|
||||||
|
|
||||||
|
START TRANSACTION;
|
||||||
|
OPEN prompt_cursor;
|
||||||
|
|
||||||
|
prompt_loop: LOOP
|
||||||
|
FETCH prompt_cursor INTO v_template_id, v_current_version_id, v_generate_type, v_prompt_code,
|
||||||
|
v_system_prompt, v_user_prompt_contract, v_user_prompt_template,
|
||||||
|
v_provider_code, v_model, v_pipeline_release_code;
|
||||||
|
IF v_done = 1 THEN
|
||||||
|
LEAVE prompt_loop;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
SET v_target_system_prompt = v_system_prompt;
|
||||||
|
SET v_target_user_prompt = v_user_prompt_template;
|
||||||
|
|
||||||
|
IF v_prompt_code = 'front.flow_config' THEN
|
||||||
|
SET v_marker = 'CREATE_RECORD_ACTION_INPUT_V1';
|
||||||
|
SET v_rule = CONCAT(
|
||||||
|
'13. CREATE_RECORD_ACTION_INPUT_V1: Rule 7 coverage applies to values derivable ',
|
||||||
|
'from currentUserId, now, ownerTable record fields, or constants before the database ',
|
||||||
|
'schema is finalized. Meaningful business fields that must be entered when the action ',
|
||||||
|
'runs may be omitted from values; after schema finalization every omitted non-generated ',
|
||||||
|
'required column becomes a required typed action input. Never invent record.x unless x ',
|
||||||
|
'is a real ownerTable field.');
|
||||||
|
IF LOCATE(v_marker, v_system_prompt) > 0 THEN
|
||||||
|
ITERATE prompt_loop;
|
||||||
|
END IF;
|
||||||
|
SET v_target_system_prompt = CONCAT(v_system_prompt, CHAR(10), v_rule);
|
||||||
|
ELSE
|
||||||
|
SET v_marker = 'CREATE_RECORD_NULLABILITY_V1';
|
||||||
|
SET v_rule = CONCAT(
|
||||||
|
'CREATE_RECORD_NULLABILITY_V1: Required create_record target columns omitted from ',
|
||||||
|
'flowConfig values are intentional typed action inputs; keep meaningful columns required ',
|
||||||
|
'instead of weakening them to nullable. When a create_record value maps record.source_field ',
|
||||||
|
'into a target column with isRequired=1, the ownerTable source_field must also use ',
|
||||||
|
'isRequired=1 so the generated insert cannot propagate null.');
|
||||||
|
IF LOCATE(v_marker, v_user_prompt_template) > 0 THEN
|
||||||
|
ITERATE prompt_loop;
|
||||||
|
END IF;
|
||||||
|
SET v_target_user_prompt = CONCAT(v_user_prompt_template, CHAR(10), v_rule);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
SELECT COALESCE(MAX(version_no), 0) + 1
|
||||||
|
INTO v_version_no
|
||||||
|
FROM factory_prompt_version
|
||||||
|
WHERE prompt_template_id = v_template_id
|
||||||
|
FOR UPDATE;
|
||||||
|
SET v_version_label = CONCAT('V', v_version_no);
|
||||||
|
SET v_content_hash = SHA2(CONCAT(
|
||||||
|
'{"generateType":', JSON_QUOTE(v_generate_type),
|
||||||
|
',"promptCode":', JSON_QUOTE(v_prompt_code),
|
||||||
|
',"systemPrompt":', JSON_QUOTE(v_target_system_prompt),
|
||||||
|
',"userPromptContract":', JSON_QUOTE(v_user_prompt_contract),
|
||||||
|
',"userPromptTemplate":', JSON_QUOTE(v_target_user_prompt),
|
||||||
|
',"version":', JSON_QUOTE(v_version_label), '}'), 256);
|
||||||
|
|
||||||
|
INSERT INTO factory_prompt_version
|
||||||
|
(prompt_template_id, version_no, version_label, system_prompt,
|
||||||
|
user_prompt_contract, user_prompt_template, provider_code, model,
|
||||||
|
content_hash, status, parent_version_id, pipeline_release_code,
|
||||||
|
published_by, published_at, create_by, create_time, remark)
|
||||||
|
VALUES
|
||||||
|
(v_template_id, v_version_no, v_version_label, v_target_system_prompt,
|
||||||
|
v_user_prompt_contract, v_target_user_prompt, v_provider_code, v_model,
|
||||||
|
v_content_hash, 'DRAFT', v_current_version_id, v_pipeline_release_code,
|
||||||
|
'', NULL, 'system-upgrade', NOW(),
|
||||||
|
'Align create_record required fields with typed action inputs');
|
||||||
|
SET v_new_version_id = LAST_INSERT_ID();
|
||||||
|
|
||||||
|
UPDATE factory_prompt_version
|
||||||
|
SET status = 'RETIRED'
|
||||||
|
WHERE prompt_template_id = v_template_id
|
||||||
|
AND status = 'PUBLISHED'
|
||||||
|
AND prompt_version_id <> v_new_version_id;
|
||||||
|
UPDATE factory_prompt_version
|
||||||
|
SET status = 'PUBLISHED', published_by = 'system-upgrade', published_at = NOW()
|
||||||
|
WHERE prompt_version_id = v_new_version_id AND status = 'DRAFT';
|
||||||
|
UPDATE factory_prompt_template
|
||||||
|
SET current_version_id = v_new_version_id,
|
||||||
|
update_by = 'system-upgrade', update_time = NOW()
|
||||||
|
WHERE prompt_template_id = v_template_id;
|
||||||
|
END LOOP;
|
||||||
|
|
||||||
|
CLOSE prompt_cursor;
|
||||||
|
COMMIT;
|
||||||
|
END$$
|
||||||
|
|
||||||
|
CALL upgrade_create_record_action_input_prompts_20260715()$$
|
||||||
|
DROP PROCEDURE upgrade_create_record_action_input_prompts_20260715$$
|
||||||
|
|
||||||
|
DELIMITER ;
|
||||||
Reference in New Issue
Block a user