feat: implement MCP tools and services for campaign rules and pool definitions
This commit is contained in:
@@ -25,4 +25,10 @@ public class Result<T> {
|
||||
public void instruction(String instruction) {
|
||||
this.agentInstruction = instruction;
|
||||
}
|
||||
|
||||
public static <T> Result<T> failure(String errorMessage) {
|
||||
Result<T> result = of(null);
|
||||
result.instruction("VALIDATION_ERROR: " + errorMessage);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,4 +15,7 @@ public interface PoolDefinitionClient {
|
||||
|
||||
@GetExchange("/api/pool-definition/list-by-ids")
|
||||
List<PoolDefinition> findActiveByIds(@RequestParam("idList") List<String> idList);
|
||||
|
||||
@GetExchange("/api/pool-definition/csr/list")
|
||||
List<PoolDefinition> getAll(dev.sonpx.loyalty.mcp.reward.criteria.PoolDefinitionCriteria criteria, org.springframework.data.domain.Pageable pageable);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ package dev.sonpx.loyalty.mcp.reward.criteria;
|
||||
import dev.sonpx.loyalty.mcp.criteria.AbstractFwCriteria;
|
||||
import dev.sonpx.loyalty.mcp.filter.StringFilter;
|
||||
import lombok.Data;
|
||||
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
|
||||
|
||||
/**
|
||||
* Created by SonPhung on Monday, 20-Jul-2026
|
||||
@@ -13,18 +12,13 @@ import com.fasterxml.jackson.annotation.JsonPropertyDescription;
|
||||
@Data
|
||||
public class CampaignCriteria extends AbstractFwCriteria {
|
||||
|
||||
@JsonPropertyDescription("Bộ lọc theo Mã chiến dịch (bằng chính xác, chứa từ khóa, v.v.)")
|
||||
private StringFilter campaignId;
|
||||
|
||||
@JsonPropertyDescription("Bộ lọc theo Tên người/phòng ban sở hữu chiến dịch")
|
||||
private StringFilter ownerName;
|
||||
|
||||
@JsonPropertyDescription("Bộ lọc theo Tên chiến dịch")
|
||||
private StringFilter name;
|
||||
|
||||
@JsonPropertyDescription("Bộ lọc theo Mô tả của chiến dịch")
|
||||
private StringFilter description;
|
||||
|
||||
@JsonPropertyDescription("Bộ lọc theo Loại chiến dịch (ví dụ: POINT, VOUCHER, v.v.)")
|
||||
private StringFilter campaignType;
|
||||
}
|
||||
|
||||
@@ -35,4 +35,12 @@ public class PoolDefinitionService {
|
||||
Không dùng markdown table.
|
||||
""");
|
||||
}
|
||||
|
||||
public Result<List<PoolDefinition>> getAll(dev.sonpx.loyalty.mcp.reward.criteria.PoolDefinitionCriteria criteria) {
|
||||
List<PoolDefinition> pools = poolDefinitionClient.getAll(criteria, org.springframework.data.domain.Pageable.ofSize(10));
|
||||
return Result.of(pools, """
|
||||
Trả lời tự nhiên bằng tiếng Việt. Hiển thị danh sách các Pool, mỗi pool ghi: Tên, Mã, Loại Pool.
|
||||
Không dùng markdown table.
|
||||
""");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,13 +23,58 @@ public class CampaignRuleTools {
|
||||
|
||||
@McpTool(description = """
|
||||
TÌM KIẾM THỂ LỆ / RULE của chiến dịch. Trả về: tên rule, loại rule, chiến dịch gốc, công thức thưởng.
|
||||
- Param `search` (String): từ khóa tìm kiếm theo tên hoặc mã rule cụ thể.
|
||||
- QUAN TRỌNG: Khi người dùng hỏi lấy danh sách chung ("danh sách rule", "xem thể lệ"), KHÔNG truyền param `search` (để null hoặc rỗng ""). Tuyệt đối KHÔNG truyền các từ chung như "rule", "thể lệ", "danh sách" vào `search`.
|
||||
- Param `request`: đối tượng chứa các điều kiện lọc (có thể để null nếu không cần lọc).
|
||||
- QUAN TRỌNG: Khi người dùng hỏi lấy danh sách chung ("danh sách rule", "xem thể lệ"), KHÔNG truyền điều kiện nào.
|
||||
- KHÔNG dùng tool này khi người dùng chỉ hỏi về thông tin cơ bản chiến dịch (tên, owner, loại) → hãy dùng tool searchCampaigns.
|
||||
Hiển thị tối đa 10 bản ghi.""")
|
||||
public Result<List<CampaignRuleSummaryDto>> searchRules(String search) {
|
||||
public Result<List<CampaignRuleSummaryDto>> searchRules(dev.sonpx.loyalty.mcp.tool.request.CampaignRuleSearchRequest request) {
|
||||
CampaignRuleCriteria criteria = new CampaignRuleCriteria();
|
||||
criteria.setSearch(SearchUtils.sanitizeSearch(search));
|
||||
if (request != null) {
|
||||
if (request.campaignId() != null) {
|
||||
dev.sonpx.loyalty.mcp.filter.StringFilter filter = new dev.sonpx.loyalty.mcp.filter.StringFilter();
|
||||
filter.setEquals(request.campaignId());
|
||||
criteria.setCampaignId(filter);
|
||||
}
|
||||
if (request.ruleId() != null) {
|
||||
dev.sonpx.loyalty.mcp.filter.StringFilter filter = new dev.sonpx.loyalty.mcp.filter.StringFilter();
|
||||
filter.setEquals(request.ruleId());
|
||||
criteria.setRuleId(filter);
|
||||
}
|
||||
if (request.ruleName() != null) {
|
||||
dev.sonpx.loyalty.mcp.filter.StringFilter filter = new dev.sonpx.loyalty.mcp.filter.StringFilter();
|
||||
filter.setContains(request.ruleName());
|
||||
criteria.setRuleName(filter);
|
||||
}
|
||||
if (request.description() != null) {
|
||||
dev.sonpx.loyalty.mcp.filter.StringFilter filter = new dev.sonpx.loyalty.mcp.filter.StringFilter();
|
||||
filter.setContains(request.description());
|
||||
criteria.setDescription(filter);
|
||||
}
|
||||
if (request.ruleType() != null) {
|
||||
dev.sonpx.loyalty.mcp.filter.StringFilter filter = new dev.sonpx.loyalty.mcp.filter.StringFilter();
|
||||
filter.setEquals(request.ruleType());
|
||||
criteria.setRuleType(filter);
|
||||
}
|
||||
if (request.poolId() != null) {
|
||||
dev.sonpx.loyalty.mcp.filter.StringFilter filter = new dev.sonpx.loyalty.mcp.filter.StringFilter();
|
||||
filter.setEquals(request.poolId());
|
||||
criteria.setPoolId(filter);
|
||||
}
|
||||
try {
|
||||
if (request.effectiveFrom() != null) {
|
||||
dev.sonpx.loyalty.mcp.filter.LocalDateTimeFilter filter = new dev.sonpx.loyalty.mcp.filter.LocalDateTimeFilter();
|
||||
filter.setGreaterThanOrEqual(java.time.LocalDateTime.parse(request.effectiveFrom()));
|
||||
criteria.setEffectiveFrom(filter);
|
||||
}
|
||||
if (request.effectiveTo() != null) {
|
||||
dev.sonpx.loyalty.mcp.filter.LocalDateTimeFilter filter = new dev.sonpx.loyalty.mcp.filter.LocalDateTimeFilter();
|
||||
filter.setLessThanOrEqual(java.time.LocalDateTime.parse(request.effectiveTo()));
|
||||
criteria.setEffectiveTo(filter);
|
||||
}
|
||||
} catch (java.time.format.DateTimeParseException e) {
|
||||
return Result.failure("Invalid date format. Please use ISO-8601 format (e.g. '2026-07-27T10:00:00').");
|
||||
}
|
||||
}
|
||||
return campaignRuleService.getAll(criteria);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,12 +23,38 @@ public class CampaignTools {
|
||||
|
||||
@McpTool(description = """
|
||||
TÌM KIẾM CHIẾN DỊCH (Campaign). Trả về thông tin CƠ BẢN của chiến dịch: tên, mã, owner, loại, thời gian.
|
||||
- Param `search` (String): từ khóa tìm kiếm theo tên hoặc mã chiến dịch cụ thể.
|
||||
- QUAN TRỌNG: Khi người dùng hỏi lấy danh sách chung ("danh sách chiến dịch", "xem các chiến dịch"), KHÔNG truyền param `search` (để null hoặc rỗng ""). Tuyệt đối KHÔNG truyền các từ chung như "chiến dịch", "danh sách", "campaign" vào `search`.
|
||||
- Param `request`: đối tượng chứa các điều kiện lọc (có thể để null nếu không cần lọc).
|
||||
- QUAN TRỌNG: Khi người dùng hỏi lấy danh sách chung ("danh sách chiến dịch", "xem các chiến dịch"), KHÔNG truyền filter nào.
|
||||
- KHÔNG dùng tool này khi người dùng hỏi về: rule, thể lệ, quy tắc, điều kiện, công thức thưởng → hãy dùng tool searchRules.""")
|
||||
public Result<List<CampaignSummaryDto>> searchCampaigns(String search) {
|
||||
public Result<List<CampaignSummaryDto>> searchCampaigns(dev.sonpx.loyalty.mcp.tool.request.CampaignSearchRequest request) {
|
||||
CampaignCriteria criteria = new CampaignCriteria();
|
||||
criteria.setSearch(SearchUtils.sanitizeSearch(search));
|
||||
if (request != null) {
|
||||
if (request.campaignId() != null) {
|
||||
dev.sonpx.loyalty.mcp.filter.StringFilter filter = new dev.sonpx.loyalty.mcp.filter.StringFilter();
|
||||
filter.setContains(request.campaignId());
|
||||
criteria.setCampaignId(filter);
|
||||
}
|
||||
if (request.ownerName() != null) {
|
||||
dev.sonpx.loyalty.mcp.filter.StringFilter filter = new dev.sonpx.loyalty.mcp.filter.StringFilter();
|
||||
filter.setContains(request.ownerName());
|
||||
criteria.setOwnerName(filter);
|
||||
}
|
||||
if (request.name() != null) {
|
||||
dev.sonpx.loyalty.mcp.filter.StringFilter filter = new dev.sonpx.loyalty.mcp.filter.StringFilter();
|
||||
filter.setContains(request.name());
|
||||
criteria.setName(filter);
|
||||
}
|
||||
if (request.description() != null) {
|
||||
dev.sonpx.loyalty.mcp.filter.StringFilter filter = new dev.sonpx.loyalty.mcp.filter.StringFilter();
|
||||
filter.setContains(request.description());
|
||||
criteria.setDescription(filter);
|
||||
}
|
||||
if (request.campaignType() != null) {
|
||||
dev.sonpx.loyalty.mcp.filter.StringFilter filter = new dev.sonpx.loyalty.mcp.filter.StringFilter();
|
||||
filter.setEquals(request.campaignType());
|
||||
criteria.setCampaignType(filter);
|
||||
}
|
||||
}
|
||||
return campaignService.getAll(criteria);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,4 +23,42 @@ public class PoolDefinitionTools {
|
||||
public Result<List<PoolDefinition>> getPoolDefinitionsByIds(String ids) {
|
||||
return poolDefinitionService.findActiveByIds(ids);
|
||||
}
|
||||
|
||||
@McpTool(description = "TÌM KIẾM Point Pool. Trả về thông tin của pool. Param `request`: đối tượng chứa các điều kiện lọc (có thể để null nếu không cần lọc).")
|
||||
public Result<List<PoolDefinition>> searchPools(dev.sonpx.loyalty.mcp.tool.request.PoolDefinitionSearchRequest request) {
|
||||
dev.sonpx.loyalty.mcp.reward.criteria.PoolDefinitionCriteria criteria = new dev.sonpx.loyalty.mcp.reward.criteria.PoolDefinitionCriteria();
|
||||
if (request != null) {
|
||||
if (request.poolId() != null) {
|
||||
dev.sonpx.loyalty.mcp.filter.StringFilter filter = new dev.sonpx.loyalty.mcp.filter.StringFilter();
|
||||
filter.setEquals(request.poolId());
|
||||
criteria.setPoolId(filter);
|
||||
}
|
||||
if (request.poolName() != null) {
|
||||
dev.sonpx.loyalty.mcp.filter.StringFilter filter = new dev.sonpx.loyalty.mcp.filter.StringFilter();
|
||||
filter.setContains(request.poolName());
|
||||
criteria.setPoolName(filter);
|
||||
}
|
||||
if (request.poolType() != null) {
|
||||
dev.sonpx.loyalty.mcp.filter.StringFilter filter = new dev.sonpx.loyalty.mcp.filter.StringFilter();
|
||||
filter.setEquals(request.poolType());
|
||||
criteria.setPoolType(filter);
|
||||
}
|
||||
if (request.entityLevel() != null) {
|
||||
dev.sonpx.loyalty.mcp.filter.StringFilter filter = new dev.sonpx.loyalty.mcp.filter.StringFilter();
|
||||
filter.setEquals(request.entityLevel());
|
||||
criteria.setEntityLevel(filter);
|
||||
}
|
||||
if (request.expiryPolicy() != null) {
|
||||
dev.sonpx.loyalty.mcp.filter.StringFilter filter = new dev.sonpx.loyalty.mcp.filter.StringFilter();
|
||||
filter.setEquals(request.expiryPolicy());
|
||||
criteria.setExpiryPolicy(filter);
|
||||
}
|
||||
if (request.atgGroupId() != null) {
|
||||
dev.sonpx.loyalty.mcp.filter.StringFilter filter = new dev.sonpx.loyalty.mcp.filter.StringFilter();
|
||||
filter.setEquals(request.atgGroupId());
|
||||
criteria.setAtgGroupId(filter);
|
||||
}
|
||||
}
|
||||
return poolDefinitionService.getAll(criteria);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package dev.sonpx.loyalty.mcp.tool.request;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
|
||||
|
||||
public record CampaignRuleSearchRequest(
|
||||
@JsonPropertyDescription("Search by Campaign ID (exact match). Leave null if not filtering by Campaign ID.")
|
||||
String campaignId,
|
||||
|
||||
@JsonPropertyDescription("Search by Rule ID (exact match). Leave null if not filtering by Rule ID.")
|
||||
String ruleId,
|
||||
|
||||
@JsonPropertyDescription("Search by Rule Name (partial or exact match). Leave null if not filtering by Rule Name.")
|
||||
String ruleName,
|
||||
|
||||
@JsonPropertyDescription("Search by Rule Description (partial or exact match). Leave null if not filtering by Description.")
|
||||
String description,
|
||||
|
||||
@JsonPropertyDescription("Filter rules effective from this date/time (ISO-8601 format, e.g., '2026-07-27T10:00:00'). Leave null if not filtering by effective from.")
|
||||
String effectiveFrom,
|
||||
|
||||
@JsonPropertyDescription("Filter rules effective to this date/time (ISO-8601 format, e.g., '2026-07-27T10:00:00'). Leave null if not filtering by effective to.")
|
||||
String effectiveTo,
|
||||
|
||||
@JsonPropertyDescription("Search by Rule Type (exact match). Leave null if not filtering by Rule Type.")
|
||||
String ruleType,
|
||||
|
||||
@JsonPropertyDescription("Search by Pool ID (exact match). Leave null if not filtering by Pool ID.")
|
||||
String poolId
|
||||
) {}
|
||||
@@ -0,0 +1,20 @@
|
||||
package dev.sonpx.loyalty.mcp.tool.request;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
|
||||
|
||||
public record CampaignSearchRequest(
|
||||
@JsonPropertyDescription("Search by Campaign ID (partial or exact match). Leave null if not filtering by ID.")
|
||||
String campaignId,
|
||||
|
||||
@JsonPropertyDescription("Search by Owner Name/Department (partial or exact match). Leave null if not filtering by owner.")
|
||||
String ownerName,
|
||||
|
||||
@JsonPropertyDescription("Search by Campaign Name (partial or exact match). Leave null if not filtering by name.")
|
||||
String name,
|
||||
|
||||
@JsonPropertyDescription("Search by Campaign Description (partial or exact match). Leave null if not filtering by description.")
|
||||
String description,
|
||||
|
||||
@JsonPropertyDescription("Search by Campaign Type (exact match, e.g. POINT, VOUCHER). Leave null if not filtering by type.")
|
||||
String campaignType
|
||||
) {}
|
||||
@@ -0,0 +1,23 @@
|
||||
package dev.sonpx.loyalty.mcp.tool.request;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
|
||||
|
||||
public record PoolDefinitionSearchRequest(
|
||||
@JsonPropertyDescription("Search by Pool ID (exact match). Leave null if not filtering by Pool ID.")
|
||||
String poolId,
|
||||
|
||||
@JsonPropertyDescription("Search by Pool Name (partial or exact match). Leave null if not filtering by Pool Name.")
|
||||
String poolName,
|
||||
|
||||
@JsonPropertyDescription("Search by Pool Type (exact match). Leave null if not filtering by Pool Type.")
|
||||
String poolType,
|
||||
|
||||
@JsonPropertyDescription("Search by Entity Level (exact match). Leave null if not filtering by Entity Level.")
|
||||
String entityLevel,
|
||||
|
||||
@JsonPropertyDescription("Search by Expiry Policy (exact match). Leave null if not filtering by Expiry Policy.")
|
||||
String expiryPolicy,
|
||||
|
||||
@JsonPropertyDescription("Search by ATG Group ID (exact match). Leave null if not filtering by ATG Group ID.")
|
||||
String atgGroupId
|
||||
) {}
|
||||
@@ -6,6 +6,8 @@ if (-not (Test-Path $LogDir)) {
|
||||
New-Item -ItemType Directory -Force -Path $LogDir | Out-Null
|
||||
}
|
||||
|
||||
$env:JAVA_TOOL_OPTIONS = "-Duser.timezone=Asia/Ho_Chi_Minh"
|
||||
|
||||
if (Test-Path (Join-Path $ProjectRoot ".env")) {
|
||||
Write-Host "Loading environment variables from .env"
|
||||
Get-Content (Join-Path $ProjectRoot ".env") | Where-Object { $_ -match "^[^#]" -and $_ -match "=" } | ForEach-Object {
|
||||
|
||||
Reference in New Issue
Block a user