feat: implement campaign rule management infrastructure including criteria models, services, and tooling support

This commit is contained in:
SonPhung
2026-07-20 23:41:53 +07:00
parent 00e26cedcf
commit 94588bb12f
25 changed files with 310 additions and 108 deletions

View File

@@ -4,7 +4,8 @@ import static org.springframework.security.oauth2.client.web.client.RequestAttri
import static org.springframework.security.oauth2.client.web.client.RequestAttributePrincipalResolver.principal; import static org.springframework.security.oauth2.client.web.client.RequestAttributePrincipalResolver.principal;
import org.springframework.http.HttpStatusCode; import org.springframework.http.HttpStatusCode;
import dev.sonpx.loyalty.mcp.reward.client.RewardClient; import dev.sonpx.loyalty.mcp.reward.client.CampaignClient;
import dev.sonpx.loyalty.mcp.reward.client.CampaignRuleClient;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@@ -20,13 +21,13 @@ import org.springframework.web.service.invoker.HttpServiceProxyFactory;
* Created by SonPhung on Sunday, 19-Jul-2026 * Created by SonPhung on Sunday, 19-Jul-2026
**/ **/
@Configuration @Configuration
public class ClientConfig { public class RestClientConfig {
@Value("${loyalty.core.base-url}") @Value("${loyalty.core.base-url}")
private String coreBaseUrl; private String coreBaseUrl;
@Bean @Bean
public RestClient rewardReClient(OAuth2AuthorizedClientManager authorizedClientManager) { public RestClient rewardRestClient(OAuth2AuthorizedClientManager authorizedClientManager) {
OAuth2ClientHttpRequestInterceptor oauth2Interceptor = OAuth2ClientHttpRequestInterceptor oauth2Interceptor =
new OAuth2ClientHttpRequestInterceptor(authorizedClientManager); new OAuth2ClientHttpRequestInterceptor(authorizedClientManager);
oauth2Interceptor.setClientRegistrationIdResolver(new RequestAttributeClientRegistrationIdResolver()); oauth2Interceptor.setClientRegistrationIdResolver(new RequestAttributeClientRegistrationIdResolver());
@@ -50,15 +51,20 @@ public class ClientConfig {
@Bean @Bean
public HttpServiceProxyFactory httpServiceProxyFactory(RestClient rewardReClient) { public HttpServiceProxyFactory rewardHttpServiceProxyFactory(RestClient rewardRestClient) {
return HttpServiceProxyFactory.builderFor(RestClientAdapter.create(rewardReClient)) return HttpServiceProxyFactory.builderFor(RestClientAdapter.create(rewardRestClient))
.customArgumentResolver(new PageableArgumentResolver()) .customArgumentResolver(new PageableArgumentResolver())
.customArgumentResolver(new PojoArgumentResolver()) .customArgumentResolver(new PojoArgumentResolver())
.build(); .build();
} }
@Bean @Bean
public RewardClient rewardClient(HttpServiceProxyFactory httpServiceProxyFactory) { public CampaignClient campaignClient(HttpServiceProxyFactory rewardHttpServiceProxyFactory) {
return httpServiceProxyFactory.createClient(RewardClient.class); return rewardHttpServiceProxyFactory.createClient(CampaignClient.class);
}
@Bean
public CampaignRuleClient campaignRuleClient(HttpServiceProxyFactory rewardHttpServiceProxyFactory) {
return rewardHttpServiceProxyFactory.createClient(CampaignRuleClient.class);
} }
} }

View File

@@ -13,7 +13,7 @@ import lombok.Data;
* Created by SonPhung on Monday, 20-Jul-2026 * Created by SonPhung on Monday, 20-Jul-2026
*/ */
@Data @Data
public abstract class AbstractFwCriteria implements FwCriteria { public abstract class AbstractFwCriteria {
protected StringFilter status; protected StringFilter status;
protected RangeFilter<Instant> lastUpdateDate; protected RangeFilter<Instant> lastUpdateDate;
@@ -31,8 +31,4 @@ public abstract class AbstractFwCriteria implements FwCriteria {
this.search = search; this.search = search;
} }
} }
protected abstract String[] getColumns();
protected abstract String[] getComponentColumns();
} }

View File

@@ -1,13 +0,0 @@
package dev.sonpx.loyalty.mcp.criteria;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
/**
* Created by SonPhung on Tuesday, 10-Oct-2023
*/
@JsonIgnoreProperties(value = {"columns", "columnsComponent"})
public interface FwCriteria {
}

View File

@@ -18,7 +18,7 @@ import org.springframework.web.service.annotation.PostExchange;
* Created by SonPhung on Sunday, 19-Jul-2026 * Created by SonPhung on Sunday, 19-Jul-2026
**/ **/
@HttpExchange @HttpExchange
public interface RewardClient { public interface CampaignClient {
@GetExchange("/api/campaign/csr/list") @GetExchange("/api/campaign/csr/list")
List<Campaign> getAll(CampaignCriteria criteria, Pageable pageable); List<Campaign> getAll(CampaignCriteria criteria, Pageable pageable);

View File

@@ -0,0 +1,43 @@
package dev.sonpx.loyalty.mcp.reward.client;
import dev.sonpx.loyalty.mcp.model.OperationResult;
import dev.sonpx.loyalty.mcp.reward.criteria.CampaignRuleCriteria;
import dev.sonpx.loyalty.mcp.reward.model.CampaignRule;
import jakarta.validation.Valid;
import java.util.List;
import java.util.Map;
import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.service.annotation.GetExchange;
import org.springframework.web.service.annotation.HttpExchange;
import org.springframework.web.service.annotation.PostExchange;
/**
* Created by SonPhung on Sunday, 19-Jul-2026
**/
@HttpExchange
public interface CampaignRuleClient {
@GetExchange("/api/campaign-rule/csr/list")
List<CampaignRule> getAll(CampaignRuleCriteria criteria, Pageable pageable);
@GetExchange("/api/campaign-rule/csr/count")
Long count(CampaignRuleCriteria criteria);
@GetExchange("/api/campaign-rule/csr/id/{id}")
CampaignRule findActiveById(@PathVariable String id);
@GetExchange("/api/campaign-rule/csr/id")
List<CampaignRule> findActiveByIds(@RequestParam Map<String, String> params);
@GetExchange("/api/campaign-rule/csr/id/generate")
String generateId();
@GetExchange("/api/campaign-rule/csr/id/check/{id}")
String checkId(@PathVariable String id);
@PostExchange("/api/campaign-rule/csr/create")
OperationResult<CampaignRule> create(@Valid @RequestBody CampaignRule dto);
}

View File

@@ -27,8 +27,4 @@ public class CampaignCriteria extends AbstractFwCriteria {
@JsonPropertyDescription("Bộ lọc theo Loại chiến dịch (ví dụ: POINT, VOUCHER, v.v.)") @JsonPropertyDescription("Bộ lọc theo Loại chiến dịch (ví dụ: POINT, VOUCHER, v.v.)")
private StringFilter campaignType; private StringFilter campaignType;
// for search text and component
private String[] componentColumns = {"campaign_id", "name"};
private String[] columns = {"description"};
} }

View File

@@ -21,9 +21,4 @@ public class CampaignRuleCriteria extends AbstractFwCriteria {
private LocalDateTimeFilter effectiveTo; private LocalDateTimeFilter effectiveTo;
private StringFilter ruleType; private StringFilter ruleType;
private StringFilter poolId; private StringFilter poolId;
// for search text and component
private String[] componentColumns = {"rule_id", "rule_name"};
private String[] columns = {"campaign_id", "description"};
} }

View File

@@ -15,10 +15,4 @@ public class ComponentCriteria extends AbstractFwCriteria {
private StringFilter key; private StringFilter key;
private StringFilter code; private StringFilter code;
private StringFilter name; private StringFilter name;
// for search text
private String[] componentColumns = {"code", "name"};
private String[] columns = {};
} }

View File

@@ -26,9 +26,4 @@ public class CounterDefinitionCriteria extends AbstractFwCriteria {
private LocalDateFilter firstStartDate; private LocalDateFilter firstStartDate;
private StringFilter stateCounter; private StringFilter stateCounter;
private StringFilter lateValuePosting; private StringFilter lateValuePosting;
// for search text
private String[] componentColumns = {"counter_id", "counter_name"};
private String[] columns = {"entity", "what_to_count"};
} }

View File

@@ -18,10 +18,4 @@ public class CurrencyRateCriteria extends AbstractFwCriteria {
private StringFilter effectiveTo; private StringFilter effectiveTo;
private DoubleFilter buyRate; private DoubleFilter buyRate;
private DoubleFilter sellRate; private DoubleFilter sellRate;
// for search text
private String[] componentColumns = {"pcr_code"};
private String[] columns = {};
} }

View File

@@ -15,10 +15,4 @@ public class DeductionSequencesCriteria extends AbstractFwCriteria {
private LocalDateFilter effectiveFrom; private LocalDateFilter effectiveFrom;
private LocalDateFilter effectiveTo; private LocalDateFilter effectiveTo;
private StringFilter isDefault; private StringFilter isDefault;
// for search and component
private String[] componentColumns = {"sequence_id", "sequence_name"};
private String[] columns = {"description", "effective_from", "effective_to", "is_default"};
} }

View File

@@ -14,10 +14,4 @@ public class EventMaintenanceCriteria extends AbstractFwCriteria {
private StringFilter eventId; private StringFilter eventId;
private StringFilter description; private StringFilter description;
// for search text
private String[] componentColumns = {"event_id", "description"};
private String[] columns = {};
} }

View File

@@ -14,10 +14,4 @@ public class PoolConversionRateCriteria extends AbstractFwCriteria {
private StringFilter code; private StringFilter code;
private StringFilter description; private StringFilter description;
// for search text
private String[] componentColumns = {"code", "description"};
private String[] columns = {};
} }

View File

@@ -18,10 +18,4 @@ public class PoolDefinitionCriteria extends AbstractFwCriteria {
private StringFilter entityLevel; private StringFilter entityLevel;
private StringFilter expiryPolicy; private StringFilter expiryPolicy;
private StringFilter atgGroupId; private StringFilter atgGroupId;
// for search and component
private String[] componentColumns = {"pool_id", "pool_name"};
private String[] columns = {"description"};
} }

View File

@@ -14,10 +14,4 @@ public class StatementOutputPoolCriteria extends AbstractFwCriteria {
private StringFilter poolId; private StringFilter poolId;
private StringFilter entityLevel; private StringFilter entityLevel;
// for search text
private String[] componentColumns = {"pool_id"};
private String[] columns = {"entity_level"};
} }

View File

@@ -15,10 +15,4 @@ public class TransactionCategoryCriteria extends AbstractFwCriteria {
private StringFilter code; private StringFilter code;
private StringFilter name; private StringFilter name;
private StringFilter description; private StringFilter description;
// for search text and component
private String[] componentColumns = {"code", "name"};
private String[] columns = {};
} }

View File

@@ -16,10 +16,4 @@ public class TransactionCodeCriteria extends AbstractFwCriteria {
private StringFilter code; private StringFilter code;
private StringFilter description; private StringFilter description;
private BooleanFilter reversalInd; private BooleanFilter reversalInd;
// for search text and component
private String[] componentColumns = {"code", "description"};
private String[] columns = {"reversal_ind"};
} }

View File

@@ -2,6 +2,7 @@
package dev.sonpx.loyalty.mcp.reward.enums; package dev.sonpx.loyalty.mcp.reward.enums;
import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.annotation.JsonValue;
import java.util.Arrays; import java.util.Arrays;
import lombok.Getter; import lombok.Getter;
@@ -13,7 +14,11 @@ import lombok.RequiredArgsConstructor;
@Getter @Getter
@RequiredArgsConstructor @RequiredArgsConstructor
public enum BaseDateType { public enum BaseDateType {
@JsonPropertyDescription("Ngày hệ thống ghi nhận giao dịch")
POST_DATE("PD"), POST_DATE("PD"),
@JsonPropertyDescription("Ngày khách hàng thực hiện giao dịch")
TRANSACTION_DATE("TD"); TRANSACTION_DATE("TD");
@JsonValue @JsonValue

View File

@@ -1,6 +1,7 @@
package dev.sonpx.loyalty.mcp.reward.model; package dev.sonpx.loyalty.mcp.reward.model;
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
@@ -13,5 +14,6 @@ import lombok.Setter;
public class CampaignCriteria extends Fw { public class CampaignCriteria extends Fw {
@NotBlank @NotBlank
@JsonPropertyDescription("Cấu hình các tiêu chí có định dạng json")
private String criteria; private String criteria;
} }

View File

@@ -67,7 +67,7 @@ public class CampaignRule extends Fw {
private Boolean notUpdatePool; private Boolean notUpdatePool;
@Valid @Valid
@JsonPropertyDescription("Danh sách liên kết điều khoản (T&C)") @JsonPropertyDescription("Danh sách liên kết Transaction Code")
private List<CampaignTcLinkage> campaignTcLinkages; private List<CampaignTcLinkage> campaignTcLinkages;
/** /**
@@ -107,11 +107,9 @@ public class CampaignRule extends Fw {
@JsonPropertyDescription("Mã sản phẩm/quà tặng") @JsonPropertyDescription("Mã sản phẩm/quà tặng")
private ReferenceData itemCode; private ReferenceData itemCode;
// Aplly the first criterion satified in Formula 10
@JsonPropertyDescription("Chỉ áp dụng tiêu chí đầu tiên thỏa mãn trong Formula 10") @JsonPropertyDescription("Chỉ áp dụng tiêu chí đầu tiên thỏa mãn trong Formula 10")
private Boolean attrApplyFirstFlag; private Boolean attrApplyFirstFlag;
// Aplly the first criterion satified in Formula 11
@JsonPropertyDescription("Chỉ áp dụng tiêu chí đầu tiên thỏa mãn trong Formula 11") @JsonPropertyDescription("Chỉ áp dụng tiêu chí đầu tiên thỏa mãn trong Formula 11")
private Boolean segApplyFirstFlag; private Boolean segApplyFirstFlag;
@@ -128,7 +126,7 @@ public class CampaignRule extends Fw {
private CampaignFormulaOne campaignFormulaOne; private CampaignFormulaOne campaignFormulaOne;
@Valid @Valid
@JsonPropertyDescription("Cấu hình Formula 2") @JsonPropertyDescription("Cấu hình Formula 2, Thưởng điểm theo giá trị cố định")
private CampaignFormulaTwo campaignFormulaTwo; private CampaignFormulaTwo campaignFormulaTwo;
@Valid @Valid

View File

@@ -0,0 +1,94 @@
package dev.sonpx.loyalty.mcp.service;
import dev.sonpx.loyalty.mcp.model.OperationResult;
import dev.sonpx.loyalty.mcp.model.Result;
import dev.sonpx.loyalty.mcp.reward.client.CampaignRuleClient;
import dev.sonpx.loyalty.mcp.reward.criteria.CampaignRuleCriteria;
import dev.sonpx.loyalty.mcp.reward.model.CampaignRule;
import dev.sonpx.loyalty.mcp.util.MarkdownGenerator;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
/**
* Created by SonPhung on Monday, 20-Jul-2026
*/
@Service
@Slf4j
@RequiredArgsConstructor
public class CampaignRuleService {
private final CampaignRuleClient campaignRuleClient;
public Result<List<CampaignRule>> getAll(CampaignRuleCriteria criteria) {
try {
List<CampaignRule> rules = campaignRuleClient.getAll(criteria, Pageable.ofSize(10));
return Result.of(rules, MarkdownGenerator.generateCampaignRuleMD(rules));
} catch (Exception e) {
log.error(e.getMessage(), e);
return Result.of(null);
}
}
public Result<Long> count(CampaignRuleCriteria criteria) {
try {
Long count = campaignRuleClient.count(criteria);
return Result.of(count, MarkdownGenerator.generateCampaignRuleMD(count));
} catch (Exception e) {
log.error(e.getMessage(), e);
return Result.of(null);
}
}
public Result<CampaignRule> findActiveById(String id) {
try {
CampaignRule rule = campaignRuleClient.findActiveById(id);
return Result.of(rule, MarkdownGenerator.generateCampaignRuleMD(rule));
} catch (Exception e) {
log.error(e.getMessage(), e);
return Result.of(null);
}
}
public Result<List<CampaignRule>> findActiveByIds(String ids) {
try {
List<CampaignRule> rules = campaignRuleClient.findActiveByIds(java.util.Map.of("id", ids));
return Result.of(rules, MarkdownGenerator.generateCampaignRuleMD(rules));
} catch (Exception e) {
log.error(e.getMessage(), e);
return Result.of(null);
}
}
public Result<String> generateId() {
try {
String id = campaignRuleClient.generateId();
return Result.of(id, MarkdownGenerator.generateCampaignRuleIdMD(id));
} catch (Exception e) {
log.error(e.getMessage(), e);
return Result.of(null);
}
}
public Result<String> checkId(String id) {
try {
String checkResult = campaignRuleClient.checkId(id);
return Result.of(checkResult, MarkdownGenerator.generateCheckResultMD(checkResult));
} catch (Exception e) {
log.error(e.getMessage(), e);
return Result.of(null);
}
}
public Result<OperationResult<CampaignRule>> create(CampaignRule dto) {
try {
OperationResult<CampaignRule> opResult = campaignRuleClient.create(dto);
return Result.of(opResult, MarkdownGenerator.generateCampaignRuleOperationResultMD(opResult));
} catch (Exception e) {
log.error(e.getMessage(), e);
return Result.of(null);
}
}
}

View File

@@ -4,7 +4,7 @@ import static dev.sonpx.loyalty.mcp.util.MarkdownGenerator.generateCampaignMD;
import dev.sonpx.loyalty.mcp.model.OperationResult; import dev.sonpx.loyalty.mcp.model.OperationResult;
import dev.sonpx.loyalty.mcp.model.Result; import dev.sonpx.loyalty.mcp.model.Result;
import dev.sonpx.loyalty.mcp.reward.client.RewardClient; import dev.sonpx.loyalty.mcp.reward.client.CampaignClient;
import dev.sonpx.loyalty.mcp.reward.criteria.CampaignCriteria; import dev.sonpx.loyalty.mcp.reward.criteria.CampaignCriteria;
import dev.sonpx.loyalty.mcp.reward.model.Campaign; import dev.sonpx.loyalty.mcp.reward.model.Campaign;
import dev.sonpx.loyalty.mcp.util.MarkdownGenerator; import dev.sonpx.loyalty.mcp.util.MarkdownGenerator;
@@ -22,11 +22,11 @@ import org.springframework.stereotype.Service;
@RequiredArgsConstructor @RequiredArgsConstructor
public class CampaignService { public class CampaignService {
private final RewardClient rewardClient; private final CampaignClient campaignClient;
public Result<List<Campaign>> getAll(CampaignCriteria criteria) { public Result<List<Campaign>> getAll(CampaignCriteria criteria) {
try { try {
List<Campaign> campaigns = rewardClient.getAll(criteria, Pageable.ofSize(10)); List<Campaign> campaigns = campaignClient.getAll(criteria, Pageable.ofSize(10));
return Result.of(campaigns, generateCampaignMD(campaigns)); return Result.of(campaigns, generateCampaignMD(campaigns));
} catch (Exception e) { } catch (Exception e) {
@@ -37,7 +37,7 @@ public class CampaignService {
public Result<Long> count(CampaignCriteria criteria) { public Result<Long> count(CampaignCriteria criteria) {
try { try {
Long count = rewardClient.count(criteria); Long count = campaignClient.count(criteria);
return Result.of(count, MarkdownGenerator.generateCampaignMD(count)); return Result.of(count, MarkdownGenerator.generateCampaignMD(count));
} catch (Exception e) { } catch (Exception e) {
log.error(e.getMessage(), e); log.error(e.getMessage(), e);
@@ -47,7 +47,7 @@ public class CampaignService {
public Result<Campaign> findActiveById(String id) { public Result<Campaign> findActiveById(String id) {
try { try {
Campaign campaign = rewardClient.findActiveById(id); Campaign campaign = campaignClient.findActiveById(id);
return Result.of(campaign, MarkdownGenerator.generateCampaignMD(campaign)); return Result.of(campaign, MarkdownGenerator.generateCampaignMD(campaign));
} catch (Exception e) { } catch (Exception e) {
log.error(e.getMessage(), e); log.error(e.getMessage(), e);
@@ -57,7 +57,7 @@ public class CampaignService {
public Result<List<Campaign>> findActiveByIds(String ids) { public Result<List<Campaign>> findActiveByIds(String ids) {
try { try {
List<Campaign> campaigns = rewardClient.findActiveByIds(java.util.Map.of("id", ids)); List<Campaign> campaigns = campaignClient.findActiveByIds(java.util.Map.of("id", ids));
return Result.of(campaigns, MarkdownGenerator.generateCampaignMD(campaigns)); return Result.of(campaigns, MarkdownGenerator.generateCampaignMD(campaigns));
} catch (Exception e) { } catch (Exception e) {
log.error(e.getMessage(), e); log.error(e.getMessage(), e);
@@ -67,7 +67,7 @@ public class CampaignService {
public Result<String> generateId() { public Result<String> generateId() {
try { try {
String id = rewardClient.generateId(); String id = campaignClient.generateId();
return Result.of(id, MarkdownGenerator.generateCampaignIdMD(id)); return Result.of(id, MarkdownGenerator.generateCampaignIdMD(id));
} catch (Exception e) { } catch (Exception e) {
log.error(e.getMessage(), e); log.error(e.getMessage(), e);
@@ -77,7 +77,7 @@ public class CampaignService {
public Result<String> checkId(String id) { public Result<String> checkId(String id) {
try { try {
String checkResult = rewardClient.checkId(id); String checkResult = campaignClient.checkId(id);
return Result.of(checkResult, MarkdownGenerator.generateCheckResultMD(checkResult)); return Result.of(checkResult, MarkdownGenerator.generateCheckResultMD(checkResult));
} catch (Exception e) { } catch (Exception e) {
log.error(e.getMessage(), e); log.error(e.getMessage(), e);
@@ -87,7 +87,7 @@ public class CampaignService {
public Result<OperationResult<Campaign>> create(Campaign dto) { public Result<OperationResult<Campaign>> create(Campaign dto) {
try { try {
OperationResult<Campaign> opResult = rewardClient.create(dto); OperationResult<Campaign> opResult = campaignClient.create(dto);
return Result.of(opResult, MarkdownGenerator.generateOperationResultMD(opResult)); return Result.of(opResult, MarkdownGenerator.generateOperationResultMD(opResult));
} catch (Exception e) { } catch (Exception e) {
log.error(e.getMessage(), e); log.error(e.getMessage(), e);

View File

@@ -0,0 +1,60 @@
package dev.sonpx.loyalty.mcp.tool;
import dev.sonpx.loyalty.mcp.model.OperationResult;
import dev.sonpx.loyalty.mcp.model.Result;
import dev.sonpx.loyalty.mcp.reward.criteria.CampaignRuleCriteria;
import dev.sonpx.loyalty.mcp.reward.model.CampaignRule;
import dev.sonpx.loyalty.mcp.service.CampaignRuleService;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.ai.mcp.annotation.McpTool;
import org.springframework.stereotype.Component;
/**
* Created by SonPhung on Monday, 20-Jul-2026
*/
@Component
@RequiredArgsConstructor
public class CampaignRuleTools {
private final CampaignRuleService campaignRuleService;
@McpTool(description = "Lấy danh sách các Campaign Rule, cho phép tìm kiếm với param search, hiển thị tối đa 10 bản ghi")
public Result<List<CampaignRule>> campaignRules(String search) {
CampaignRuleCriteria criteria = new CampaignRuleCriteria();
criteria.setSearch(search);
return campaignRuleService.getAll(criteria);
}
@McpTool(description = "Đếm số lượng Campaign Rule, cho phép tìm kiếm với param search")
public Result<Long> countCampaignRules(String search) {
CampaignRuleCriteria criteria = new CampaignRuleCriteria();
criteria.setSearch(search);
return campaignRuleService.count(criteria);
}
@McpTool(description = "Lấy thông tin chi tiết một Campaign Rule theo ID")
public Result<CampaignRule> getCampaignRuleById(String id) {
return campaignRuleService.findActiveById(id);
}
@McpTool(description = "Lấy thông tin nhiều Campaign Rule theo danh sách ID, phân tách bằng dấu phẩy")
public Result<List<CampaignRule>> getCampaignRulesByIds(String ids) {
return campaignRuleService.findActiveByIds(ids);
}
@McpTool(description = "Tạo một ID Campaign Rule mới")
public Result<String> generateCampaignRuleId() {
return campaignRuleService.generateId();
}
@McpTool(description = "Kiểm tra trạng thái hoặc tính hợp lệ của một ID Campaign Rule")
public Result<String> checkCampaignRuleId(String id) {
return campaignRuleService.checkId(id);
}
@McpTool(description = "Tạo một Campaign Rule mới")
public Result<OperationResult<CampaignRule>> createCampaignRule(CampaignRule dto) {
return campaignRuleService.create(dto);
}
}

View File

@@ -15,7 +15,7 @@ import org.springframework.stereotype.Component;
*/ */
@Component @Component
@RequiredArgsConstructor @RequiredArgsConstructor
public class ApiMcpTools { public class CampaignTools {
private final CampaignService campaignService; private final CampaignService campaignService;

View File

@@ -2,6 +2,7 @@ package dev.sonpx.loyalty.mcp.util;
import dev.sonpx.loyalty.mcp.model.OperationResult; import dev.sonpx.loyalty.mcp.model.OperationResult;
import dev.sonpx.loyalty.mcp.reward.model.Campaign; import dev.sonpx.loyalty.mcp.reward.model.Campaign;
import dev.sonpx.loyalty.mcp.reward.model.CampaignRule;
import java.util.List; import java.util.List;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
@@ -60,6 +61,65 @@ public class MarkdownGenerator {
return "Campaign ID: " + id; return "Campaign ID: " + id;
} }
public static String generateCampaignRuleMD(CampaignRule rule) {
if (rule == null) {
return "No campaign rule data available.";
}
StringBuilder sb = new StringBuilder();
sb.append("### 📜 Campaign Rule Details\n\n");
sb.append("| Field | Value |\n");
sb.append("| :--- | :--- |\n");
sb.append("| **Rule ID** | ").append(rule.getRuleId()).append(" |\n");
sb.append("| **Name** | ").append(rule.getRuleName()).append(" |\n");
if (rule.getRuleType() != null) {
sb.append("| **Type** | ").append(rule.getRuleType()).append(" |\n");
}
if (rule.getCampaignId() != null) {
sb.append("| **Campaign ID** | ").append(rule.getCampaignId().getCode()).append(" |\n");
}
if (rule.getDescription() != null) {
sb.append("| **Description** | ").append(rule.getDescription()).append(" |\n");
}
return sb.toString();
}
public static String generateCampaignRuleMD(List<CampaignRule> rules) {
if (CollectionUtils.isEmpty(rules)) {
return "No campaign rules data available.";
}
StringBuilder sb = new StringBuilder();
sb.append("### 📜 Campaign Rule List\n\n");
sb.append("| Rule Id | Name | Type | Campaign Id | Description |\n");
sb.append("| :--- | :--- | :--- | :--- | :--- |\n");
rules.forEach((rule) -> {
sb.append("| ").append(rule.getRuleId());
sb.append(" | ").append(rule.getRuleName());
sb.append(" | ").append(rule.getRuleType() != null ? rule.getRuleType() : "");
sb.append(" | ").append(rule.getCampaignId() != null ? rule.getCampaignId().getCode() : "");
sb.append(" | ").append(rule.getDescription() != null ? rule.getDescription() : "").append(" |\n");
});
return sb.toString();
}
public static String generateCampaignRuleMD(Long count) {
if (count == null) {
return "Total campaign rules: 0";
}
return "Total campaign rules: " + count;
}
public static String generateCampaignRuleIdMD(String id) {
if (id == null) {
return "No campaign rule ID available.";
}
return "Campaign Rule ID: " + id;
}
public static String generateCheckResultMD(String result) { public static String generateCheckResultMD(String result) {
if (result == null) { if (result == null) {
return "No check result available."; return "No check result available.";
@@ -85,4 +145,23 @@ public class MarkdownGenerator {
} }
return sb.toString(); return sb.toString();
} }
public static String generateCampaignRuleOperationResultMD(OperationResult<CampaignRule> result) {
if (result == null) {
return "No operation result available.";
}
StringBuilder sb = new StringBuilder();
if ("00".equals(result.getErrorCode()) || result.getErrorCode() == null) {
sb.append("✅ Operation successful.\n\n");
if (result.getEntity() != null) {
sb.append(generateCampaignRuleMD(result.getEntity()));
}
} else {
sb.append("❌ Operation failed.\n\n");
sb.append("**Error Code:** ").append(result.getErrorCode()).append("\n");
sb.append("**Message:** ").append(result.getMessage()).append("\n");
}
return sb.toString();
}
} }