org.drools
diff --git a/lab-service/lab-lims/src/main/java/org/springblade/lims/controller/ExamineResultController.java b/lab-service/lab-lims/src/main/java/org/springblade/lims/controller/ExamineResultController.java
index 7fbe4c5..bac4c26 100644
--- a/lab-service/lab-lims/src/main/java/org/springblade/lims/controller/ExamineResultController.java
+++ b/lab-service/lab-lims/src/main/java/org/springblade/lims/controller/ExamineResultController.java
@@ -79,6 +79,18 @@ public class ExamineResultController extends BladeController {
return service.excel(file, examineId, reagentId);
}
+ /**
+ * 解析数据 + 试剂公式自动计算结果
+ *
+ * 与 /excel 接口参数一致,但会根据 reagentId 查找已启用的 ReagentFormula,
+ * 使用 Aviator 引擎逐样品计算结果,覆盖原始 result 字段。
+ * 未配置公式时降级为 /excel 的原始计算逻辑。
+ */
+ @PostMapping("/excelWithFormula")
+ public R excelWithFormula(MultipartFile file, String examineId, String reagentId) throws Exception {
+ return service.excelWithFormula(file, examineId, reagentId);
+ }
+
/**
* PCRExcel解析数据
*/
diff --git a/lab-service/lab-lims/src/main/java/org/springblade/lims/controller/ReagentFormulaController.java b/lab-service/lab-lims/src/main/java/org/springblade/lims/controller/ReagentFormulaController.java
new file mode 100644
index 0000000..589fbda
--- /dev/null
+++ b/lab-service/lab-lims/src/main/java/org/springblade/lims/controller/ReagentFormulaController.java
@@ -0,0 +1,255 @@
+package org.springblade.lims.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import lombok.AllArgsConstructor;
+import org.springblade.core.boot.ctrl.BladeController;
+import org.springblade.core.mp.support.Condition;
+import org.springblade.core.mp.support.Query;
+import org.springblade.core.tool.api.R;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.lims.entry.Reagent;
+import org.springblade.lims.entry.ReagentFormula;
+import org.springblade.lims.service.IReagentFormulaService;
+import org.springblade.lims.service.IReagentService;
+import org.springblade.lims.utils.FormulaValidationTool;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import static java.util.stream.Collectors.toMap;
+
+/**
+ * 试剂公式维护控制器
+ *
+ * @author blade
+ * @since 2026-05-27
+ */
+@RestController
+@AllArgsConstructor
+@RequestMapping("/reagentFormula")
+@Api(value = "试剂公式维护", tags = "试剂公式维护")
+public class ReagentFormulaController extends BladeController {
+
+ private final IReagentFormulaService service;
+ private final IReagentService reagentService;
+
+ /**
+ * 分页查询
+ */
+ @GetMapping("/list")
+ @ApiOperation(value = "分页查询", notes = "分页查询试剂公式")
+ public R> list(ReagentFormula entry, Query query) {
+ LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>();
+ if (entry.getName() != null && !entry.getName().isEmpty()) {
+ wrapper.like(ReagentFormula::getName, entry.getName());
+ }
+ wrapper.orderByDesc(ReagentFormula::getCreateTime);
+ IPage page = service.page(Condition.getPage(query), wrapper);
+
+ // populate reagent names
+ List records = page.getRecords();
+ if (records != null && !records.isEmpty()) {
+ Set reagentIds = records.stream()
+ .map(ReagentFormula::getReagentId)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toSet());
+ if (!reagentIds.isEmpty()) {
+ Map nameMap = reagentService.listByIds(reagentIds).stream()
+ .collect(toMap(Reagent::getId, Reagent::getName, (a, b) -> a));
+ records.forEach(f -> f.setReagentName(nameMap.get(f.getReagentId())));
+ }
+ }
+
+ return R.data(page);
+ }
+
+ /**
+ * 新增
+ */
+ @PostMapping("/insert")
+ @ApiOperation(value = "新增", notes = "新增试剂公式")
+ public R insert(@RequestBody ReagentFormula entry) {
+ entry.setCreateTime(new Date());
+ entry.setUpdateTime(new Date());
+ return R.data(service.save(entry));
+ }
+
+ /**
+ * 修改
+ */
+ @PostMapping("/update")
+ @ApiOperation(value = "修改", notes = "修改试剂公式")
+ public R update(@RequestBody ReagentFormula entry) {
+ entry.setUpdateTime(new Date());
+ return R.data(service.updateById(entry));
+ }
+
+ /**
+ * 逻辑删除
+ */
+ @PostMapping("/deleteByIds")
+ @ApiOperation(value = "逻辑删除", notes = "传入ids逻辑删除")
+ public R deleteByIds(@ApiParam(value = "主键集合", required = true) @RequestParam String ids) {
+ return R.status(service.deleteLogic(Func.toLongList(ids)));
+ }
+
+ /**
+ * 公式校验
+ */
+ @PostMapping("/validate")
+ @ApiOperation(value = "公式校验", notes = "使用Aviator引擎校验公式语法和安全")
+ public R validateFormula(@RequestBody Map body) {
+ String expression = body.get("expression");
+ if (expression == null || expression.trim().isEmpty()) {
+ return R.fail("公式表达式不能为空");
+ }
+ FormulaValidationTool.ValidationResult result = FormulaValidationTool.validateSafe(expression);
+ Map data = new HashMap<>(3);
+ data.put("valid", result.isValid());
+ data.put("message", result.getMessage());
+ data.put("variables", result.getVariables());
+ return R.data(data);
+ }
+
+ /**
+ * 获取试剂关联的可用变量
+ * 从 Reagent.resultDeterminationMethod 中语义提取
+ */
+ @GetMapping("/variables")
+ @ApiOperation(value = "获取可用变量", notes = "根据试剂ID获取公式可用变量")
+ public R getVariables(@ApiParam(value = "试剂ID") @RequestParam Long reagentId) {
+ Reagent reagent = reagentService.getById(reagentId);
+ if (reagent == null) {
+ return R.data(new ArrayList<>());
+ }
+
+ String method = reagent.getResultDeterminationMethod();
+ List