fix(generator): 修复 create_record 必填字段生成流程

This commit is contained in:
王鹏
2026-07-15 17:28:49 +08:00
parent 8247c86862
commit 84d3c8fc05
15 changed files with 515 additions and 52 deletions

View File

@@ -7,6 +7,7 @@ public class BusinessActionDesign
{
private String code;
private String name;
private String actor;
private String ownerTable;
private String method;
private String path;
@@ -36,6 +37,8 @@ public class BusinessActionDesign
public void setCode(String code) { this.code = code; }
public String getName() { return 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 void setOwnerTable(String ownerTable) { this.ownerTable = ownerTable; }
public String getMethod() { return method; }

View File

@@ -56,6 +56,7 @@ public class TypedBusinessActionRenderModel
private boolean currentUserRequired;
private String auditUserSetter;
private String auditTimeSetter;
private List<RequestParameter> requestParameters = new ArrayList<RequestParameter>();
private List<Effect> effects = new ArrayList<Effect>();
public String getCode() { return code; }
@@ -104,10 +105,29 @@ public class TypedBusinessActionRenderModel
public void setAuditUserSetter(String auditUserSetter) { this.auditUserSetter = auditUserSetter; }
public String getAuditTimeSetter() { return 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 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
{
private String type;

View File

@@ -284,6 +284,7 @@ public class FlowConfigCompiler
BusinessActionDesign action = new BusinessActionDesign();
action.setCode(source.getCode());
action.setName(source.getName());
action.setActor(source.getActor());
action.setOwnerTable(ownerTable(config, source));
action.setMethod(defaultValue(source.getMethod(), "POST"));
action.setPath(defaultValue(source.getPath(), "/" + source.getCode()));

View File

@@ -120,6 +120,7 @@ public class BusinessLoopDatabaseContractReconciler
}
repairAuditFields(tables, flowConfig, maxColumnsPerTable, messages);
repairEffectFields(tables, flowConfig, maxColumnsPerTable, messages);
repairCreateRecordSourceRequirements(tables, flowConfig, messages);
if (!messages.isEmpty())
{
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)
{
for (DatabaseColumnDesign column : table == null

View File

@@ -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.factory.flow.FlowConfigCompilation;
import com.ruoyi.generator.factory.flow.FlowConfigSupport;
import com.ruoyi.generator.util.CreateRecordFieldSupport;
/** Validates compiled flow actions against the database before it is persisted. */
@Component
@@ -176,38 +175,17 @@ public class FlowConfigDatabaseContractValidator
throw new ServiceException("Flow config value types do not match: " + expression
+ " -> " + 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());
}
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)

View File

@@ -205,7 +205,7 @@ public final class BusinessActionRenderSupport
{
continue;
}
String expression = automaticInsertValueExpression(column);
String expression = automaticInsertValueExpression(action, column);
if (!isEmpty(expression))
{
values.put(column.getColumnName(), expression);
@@ -222,13 +222,14 @@ public final class BusinessActionRenderSupport
&& !values.containsKey(column.getColumnName());
}
private static String automaticInsertValueExpression(GenTableColumn column)
private static String automaticInsertValueExpression(BusinessActionDesign action, GenTableColumn column)
{
if (isCreationTimeColumn(column))
{
return "${now}";
}
if (CurrentUserFieldSupport.isCurrentUserIdField(column))
if (CurrentUserFieldSupport.isCurrentUserIdField(column,
action == null ? null : action.getActor()))
{
return "${current_user.id}";
}
@@ -328,10 +329,8 @@ public final class BusinessActionRenderSupport
}
private static boolean isCreationTimeColumn(GenTableColumn column)
{
String columnName = normalize(column.getColumnName());
String javaField = normalize(column.getJavaField());
boolean creationField = "created_at".equals(columnName) || "create_time".equals(columnName)
|| "createdat".equals(javaField) || "createtime".equals(javaField);
boolean creationField = CreateRecordFieldSupport.isCreationTimeField(
column.getColumnName(), column.getJavaField());
String columnType = normalize(column.getColumnType());
String javaType = normalize(column.getJavaType());
return creationField && (columnType.contains("date") || columnType.contains("time")

View File

@@ -23,6 +23,15 @@ public final class CurrentUserFieldSupport
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)
{
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]+", "");
}
}
}

View File

@@ -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.Dependency;
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. */
public final class TypedBusinessActionCompiler
@@ -94,9 +95,6 @@ public final class TypedBusinessActionCompiler
}
}
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() + "\")"));
if (source.getAudit() != null)
{
@@ -116,14 +114,15 @@ public final class TypedBusinessActionCompiler
for (SimpleFlowConfig.Effect sourceEffect : source.getEffects())
{
action.getEffects().add(compileEffect(model, sourceEffect, action, owner, tables,
dependencies, index++));
dependencies, source.getActor(), index++));
}
configureActionParameters(action, pk);
return action;
}
private static Effect compileEffect(TypedBusinessActionRenderModel model, SimpleFlowConfig.Effect source,
Action action, GenTable owner, Map<String, GenTable> tables, Map<String, Dependency> dependencies,
int index)
String actor, int index)
{
GenTable target = tables.get(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()))
{
appendImplicitCreationTimeAssignments(model, effect, target, assignedFields);
appendImplicitCreateAssignments(model, effect, target, assignedFields, action, actor);
}
}
return effect;
}
private static void appendImplicitCreationTimeAssignments(TypedBusinessActionRenderModel model, Effect effect,
GenTable target, Map<String, String> assignedFields)
private static void appendImplicitCreateAssignments(TypedBusinessActionRenderModel model, Effect effect,
GenTable target, Map<String, String> assignedFields, Action action, String actor)
{
if (target.getColumns() == null) return;
for (GenTableColumn column : target.getColumns())
{
if (assignedFields.containsKey(column.getColumnName())
|| assignedFields.containsValue(column.getJavaField())) continue;
if (!CreateRecordFieldSupport.isCreationTimeField(
column.getColumnName(), column.getJavaField())) continue;
String expression = currentTimeExpression(model, column.getJavaType());
if (column.isIncrement()) continue;
String expression = null;
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);
}
}
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)
{
if ("Date".equals(javaType) || "java.util.Date".equals(javaType))
@@ -275,6 +336,48 @@ public final class TypedBusinessActionCompiler
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)
{
if ("String".equals(javaType)) return "String.valueOf(" + source + ")";

View File

@@ -13,7 +13,13 @@ import ${packageName}.security.PortalAuthTokenStore;
#end
import ${packageName}.service.I${ClassName}Service;
import java.io.InputStream;
#if($typedBusinessUsesBigDecimal)
import java.math.BigDecimal;
#end
import java.net.URLEncoder;
#if($typedBusinessUsesDate)
import java.util.Date;
#end
import java.util.List;
import java.util.Map;
import javax.annotation.Resource;
@@ -179,6 +185,13 @@ public class ${ClassName}Controller
}
AuthPrincipal currentUser = requireCurrentUser(request);
${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});
return Result.success();
}
@@ -200,6 +213,24 @@ public class ${ClassName}Controller
}
#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)
{
return value != null && StringUtils.isNotBlank(String.valueOf(value));

View File

@@ -2,6 +2,12 @@ package ${packageName}.service;
import ${packageName}.entity.${ClassName};
import com.baomidou.mybatisplus.extension.service.IService;
#if($typedBusinessUsesBigDecimal)
import java.math.BigDecimal;
#end
#if($typedBusinessUsesDate)
import java.util.Date;
#end
/**
* ${functionName}Service接口

View File

@@ -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.Action;
import com.ruoyi.generator.domain.front.dto.SimpleFlowConfig.Audit;
import com.ruoyi.generator.domain.front.dto.SimpleFlowConfig.Effect;
public class BusinessLoopDatabaseContractReconcilerTest
{
@@ -120,6 +121,40 @@ public class BusinessLoopDatabaseContractReconcilerTest
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)
{
BusinessLoopMetric metric = new BusinessLoopMetric();

View File

@@ -124,7 +124,7 @@ public class FlowConfigDatabaseContractValidatorTest
}
@Test
public void rejectsCreateRecordThatOmitsNonGeneratedRequiredField()
public void acceptsCreateRecordThatUsesImplicitInputForRequiredField()
{
DatabaseTableDesign book = table("book_info", "id");
DatabaseTableDesign borrow = table("borrow_record", "id", "book_id", "reader_id", "status");
@@ -143,14 +143,41 @@ public class FlowConfigDatabaseContractValidatorTest
+ "\"reader_id\":\"currentUserId\",\"status\":\"pending\"}}]}]}",
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
{
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)
{
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());
}
}

View File

@@ -148,6 +148,28 @@ public class BusinessActionRenderSupportTest
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
public void infersTargetFieldsFromEffectsAndEveryRuleValueInRequestOrder()
{

View File

@@ -140,6 +140,62 @@ public class TypedBusinessActionCompilerTest
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
public void rendersJavaSafeMethodNameForKeywordActionCode()
{