From 94588bb12fbeed67e6ef9c7faefc6ac4ca89cadc Mon Sep 17 00:00:00 2001 From: SonPhung Date: Mon, 20 Jul 2026 23:41:53 +0700 Subject: [PATCH] feat: implement campaign rule management infrastructure including criteria models, services, and tooling support --- ...lientConfig.java => RestClientConfig.java} | 20 ++-- .../mcp/criteria/AbstractFwCriteria.java | 6 +- .../loyalty/mcp/criteria/FwCriteria.java | 13 --- ...{RewardClient.java => CampaignClient.java} | 2 +- .../mcp/reward/client/CampaignRuleClient.java | 43 +++++++++ .../mcp/reward/criteria/CampaignCriteria.java | 4 - .../reward/criteria/CampaignRuleCriteria.java | 5 - .../reward/criteria/ComponentCriteria.java | 6 -- .../criteria/CounterDefinitionCriteria.java | 5 - .../reward/criteria/CurrencyRateCriteria.java | 6 -- .../criteria/DeductionSequencesCriteria.java | 6 -- .../criteria/EventMaintenanceCriteria.java | 6 -- .../criteria/PoolConversionRateCriteria.java | 6 -- .../criteria/PoolDefinitionCriteria.java | 6 -- .../criteria/StatementOutputPoolCriteria.java | 6 -- .../criteria/TransactionCategoryCriteria.java | 6 -- .../criteria/TransactionCodeCriteria.java | 6 -- .../mcp/reward/enums/BaseDateType.java | 5 + .../mcp/reward/model/CampaignCriteria.java | 2 + .../mcp/reward/model/CampaignRule.java | 6 +- .../mcp/service/CampaignRuleService.java | 94 +++++++++++++++++++ .../loyalty/mcp/service/CampaignService.java | 18 ++-- .../loyalty/mcp/tool/CampaignRuleTools.java | 60 ++++++++++++ .../{ApiMcpTools.java => CampaignTools.java} | 2 +- .../loyalty/mcp/util/MarkdownGenerator.java | 79 ++++++++++++++++ 25 files changed, 310 insertions(+), 108 deletions(-) rename loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/config/{ClientConfig.java => RestClientConfig.java} (75%) delete mode 100644 loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/criteria/FwCriteria.java rename loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/client/{RewardClient.java => CampaignClient.java} (94%) create mode 100644 loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/client/CampaignRuleClient.java create mode 100644 loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/service/CampaignRuleService.java create mode 100644 loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/tool/CampaignRuleTools.java rename loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/tool/{ApiMcpTools.java => CampaignTools.java} (98%) diff --git a/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/config/ClientConfig.java b/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/config/RestClientConfig.java similarity index 75% rename from loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/config/ClientConfig.java rename to loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/config/RestClientConfig.java index bf7f44b..3510384 100644 --- a/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/config/ClientConfig.java +++ b/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/config/RestClientConfig.java @@ -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 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.context.annotation.Bean; 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 **/ @Configuration -public class ClientConfig { +public class RestClientConfig { @Value("${loyalty.core.base-url}") private String coreBaseUrl; @Bean - public RestClient rewardReClient(OAuth2AuthorizedClientManager authorizedClientManager) { + public RestClient rewardRestClient(OAuth2AuthorizedClientManager authorizedClientManager) { OAuth2ClientHttpRequestInterceptor oauth2Interceptor = new OAuth2ClientHttpRequestInterceptor(authorizedClientManager); oauth2Interceptor.setClientRegistrationIdResolver(new RequestAttributeClientRegistrationIdResolver()); @@ -50,15 +51,20 @@ public class ClientConfig { @Bean - public HttpServiceProxyFactory httpServiceProxyFactory(RestClient rewardReClient) { - return HttpServiceProxyFactory.builderFor(RestClientAdapter.create(rewardReClient)) + public HttpServiceProxyFactory rewardHttpServiceProxyFactory(RestClient rewardRestClient) { + return HttpServiceProxyFactory.builderFor(RestClientAdapter.create(rewardRestClient)) .customArgumentResolver(new PageableArgumentResolver()) .customArgumentResolver(new PojoArgumentResolver()) .build(); } @Bean - public RewardClient rewardClient(HttpServiceProxyFactory httpServiceProxyFactory) { - return httpServiceProxyFactory.createClient(RewardClient.class); + public CampaignClient campaignClient(HttpServiceProxyFactory rewardHttpServiceProxyFactory) { + return rewardHttpServiceProxyFactory.createClient(CampaignClient.class); + } + + @Bean + public CampaignRuleClient campaignRuleClient(HttpServiceProxyFactory rewardHttpServiceProxyFactory) { + return rewardHttpServiceProxyFactory.createClient(CampaignRuleClient.class); } } diff --git a/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/criteria/AbstractFwCriteria.java b/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/criteria/AbstractFwCriteria.java index a1f8c38..67c4724 100644 --- a/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/criteria/AbstractFwCriteria.java +++ b/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/criteria/AbstractFwCriteria.java @@ -13,7 +13,7 @@ import lombok.Data; * Created by SonPhung on Monday, 20-Jul-2026 */ @Data -public abstract class AbstractFwCriteria implements FwCriteria { +public abstract class AbstractFwCriteria { protected StringFilter status; protected RangeFilter lastUpdateDate; @@ -31,8 +31,4 @@ public abstract class AbstractFwCriteria implements FwCriteria { this.search = search; } } - - protected abstract String[] getColumns(); - - protected abstract String[] getComponentColumns(); } diff --git a/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/criteria/FwCriteria.java b/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/criteria/FwCriteria.java deleted file mode 100644 index 3f5f93c..0000000 --- a/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/criteria/FwCriteria.java +++ /dev/null @@ -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 { - -} diff --git a/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/client/RewardClient.java b/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/client/CampaignClient.java similarity index 94% rename from loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/client/RewardClient.java rename to loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/client/CampaignClient.java index fb8c9df..9ca55be 100644 --- a/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/client/RewardClient.java +++ b/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/client/CampaignClient.java @@ -18,7 +18,7 @@ import org.springframework.web.service.annotation.PostExchange; * Created by SonPhung on Sunday, 19-Jul-2026 **/ @HttpExchange -public interface RewardClient { +public interface CampaignClient { @GetExchange("/api/campaign/csr/list") List getAll(CampaignCriteria criteria, Pageable pageable); diff --git a/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/client/CampaignRuleClient.java b/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/client/CampaignRuleClient.java new file mode 100644 index 0000000..2826e83 --- /dev/null +++ b/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/client/CampaignRuleClient.java @@ -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 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 findActiveByIds(@RequestParam Map 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 create(@Valid @RequestBody CampaignRule dto); +} diff --git a/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/criteria/CampaignCriteria.java b/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/criteria/CampaignCriteria.java index 9685b39..b31e576 100644 --- a/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/criteria/CampaignCriteria.java +++ b/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/criteria/CampaignCriteria.java @@ -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.)") private StringFilter campaignType; - - // for search text and component - private String[] componentColumns = {"campaign_id", "name"}; - private String[] columns = {"description"}; } diff --git a/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/criteria/CampaignRuleCriteria.java b/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/criteria/CampaignRuleCriteria.java index 06475ae..7eecc50 100644 --- a/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/criteria/CampaignRuleCriteria.java +++ b/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/criteria/CampaignRuleCriteria.java @@ -21,9 +21,4 @@ public class CampaignRuleCriteria extends AbstractFwCriteria { private LocalDateTimeFilter effectiveTo; private StringFilter ruleType; private StringFilter poolId; - - // for search text and component - private String[] componentColumns = {"rule_id", "rule_name"}; - private String[] columns = {"campaign_id", "description"}; - } diff --git a/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/criteria/ComponentCriteria.java b/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/criteria/ComponentCriteria.java index 70f11ae..6e601f0 100644 --- a/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/criteria/ComponentCriteria.java +++ b/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/criteria/ComponentCriteria.java @@ -15,10 +15,4 @@ public class ComponentCriteria extends AbstractFwCriteria { private StringFilter key; private StringFilter code; private StringFilter name; - - // for search text - private String[] componentColumns = {"code", "name"}; - private String[] columns = {}; - - } diff --git a/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/criteria/CounterDefinitionCriteria.java b/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/criteria/CounterDefinitionCriteria.java index 867ff04..c63db84 100644 --- a/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/criteria/CounterDefinitionCriteria.java +++ b/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/criteria/CounterDefinitionCriteria.java @@ -26,9 +26,4 @@ public class CounterDefinitionCriteria extends AbstractFwCriteria { private LocalDateFilter firstStartDate; private StringFilter stateCounter; private StringFilter lateValuePosting; - - // for search text - private String[] componentColumns = {"counter_id", "counter_name"}; - private String[] columns = {"entity", "what_to_count"}; - } diff --git a/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/criteria/CurrencyRateCriteria.java b/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/criteria/CurrencyRateCriteria.java index de77564..fd319b5 100644 --- a/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/criteria/CurrencyRateCriteria.java +++ b/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/criteria/CurrencyRateCriteria.java @@ -18,10 +18,4 @@ public class CurrencyRateCriteria extends AbstractFwCriteria { private StringFilter effectiveTo; private DoubleFilter buyRate; private DoubleFilter sellRate; - - // for search text - private String[] componentColumns = {"pcr_code"}; - private String[] columns = {}; - - } diff --git a/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/criteria/DeductionSequencesCriteria.java b/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/criteria/DeductionSequencesCriteria.java index fae9d8c..9bbd214 100644 --- a/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/criteria/DeductionSequencesCriteria.java +++ b/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/criteria/DeductionSequencesCriteria.java @@ -15,10 +15,4 @@ public class DeductionSequencesCriteria extends AbstractFwCriteria { private LocalDateFilter effectiveFrom; private LocalDateFilter effectiveTo; private StringFilter isDefault; - - // for search and component - private String[] componentColumns = {"sequence_id", "sequence_name"}; - private String[] columns = {"description", "effective_from", "effective_to", "is_default"}; - - } diff --git a/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/criteria/EventMaintenanceCriteria.java b/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/criteria/EventMaintenanceCriteria.java index 26f025e..b262c54 100644 --- a/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/criteria/EventMaintenanceCriteria.java +++ b/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/criteria/EventMaintenanceCriteria.java @@ -14,10 +14,4 @@ public class EventMaintenanceCriteria extends AbstractFwCriteria { private StringFilter eventId; private StringFilter description; - - // for search text - private String[] componentColumns = {"event_id", "description"}; - private String[] columns = {}; - - } diff --git a/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/criteria/PoolConversionRateCriteria.java b/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/criteria/PoolConversionRateCriteria.java index 2873d57..25bc4e2 100644 --- a/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/criteria/PoolConversionRateCriteria.java +++ b/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/criteria/PoolConversionRateCriteria.java @@ -14,10 +14,4 @@ public class PoolConversionRateCriteria extends AbstractFwCriteria { private StringFilter code; private StringFilter description; - - // for search text - private String[] componentColumns = {"code", "description"}; - private String[] columns = {}; - - } diff --git a/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/criteria/PoolDefinitionCriteria.java b/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/criteria/PoolDefinitionCriteria.java index 15219b3..8c539ac 100644 --- a/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/criteria/PoolDefinitionCriteria.java +++ b/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/criteria/PoolDefinitionCriteria.java @@ -18,10 +18,4 @@ public class PoolDefinitionCriteria extends AbstractFwCriteria { private StringFilter entityLevel; private StringFilter expiryPolicy; private StringFilter atgGroupId; - - // for search and component - private String[] componentColumns = {"pool_id", "pool_name"}; - private String[] columns = {"description"}; - - } diff --git a/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/criteria/StatementOutputPoolCriteria.java b/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/criteria/StatementOutputPoolCriteria.java index 2b1e184..c32cf52 100644 --- a/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/criteria/StatementOutputPoolCriteria.java +++ b/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/criteria/StatementOutputPoolCriteria.java @@ -14,10 +14,4 @@ public class StatementOutputPoolCriteria extends AbstractFwCriteria { private StringFilter poolId; private StringFilter entityLevel; - - // for search text - private String[] componentColumns = {"pool_id"}; - private String[] columns = {"entity_level"}; - - } diff --git a/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/criteria/TransactionCategoryCriteria.java b/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/criteria/TransactionCategoryCriteria.java index cd4b782..aea4f50 100644 --- a/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/criteria/TransactionCategoryCriteria.java +++ b/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/criteria/TransactionCategoryCriteria.java @@ -15,10 +15,4 @@ public class TransactionCategoryCriteria extends AbstractFwCriteria { private StringFilter code; private StringFilter name; private StringFilter description; - - // for search text and component - private String[] componentColumns = {"code", "name"}; - private String[] columns = {}; - - } diff --git a/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/criteria/TransactionCodeCriteria.java b/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/criteria/TransactionCodeCriteria.java index 5d001bf..4da305d 100644 --- a/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/criteria/TransactionCodeCriteria.java +++ b/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/criteria/TransactionCodeCriteria.java @@ -16,10 +16,4 @@ public class TransactionCodeCriteria extends AbstractFwCriteria { private StringFilter code; private StringFilter description; private BooleanFilter reversalInd; - - // for search text and component - private String[] componentColumns = {"code", "description"}; - private String[] columns = {"reversal_ind"}; - - } diff --git a/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/enums/BaseDateType.java b/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/enums/BaseDateType.java index 13e50c9..72ff0ca 100644 --- a/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/enums/BaseDateType.java +++ b/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/enums/BaseDateType.java @@ -2,6 +2,7 @@ package dev.sonpx.loyalty.mcp.reward.enums; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonPropertyDescription; import com.fasterxml.jackson.annotation.JsonValue; import java.util.Arrays; import lombok.Getter; @@ -13,7 +14,11 @@ import lombok.RequiredArgsConstructor; @Getter @RequiredArgsConstructor public enum BaseDateType { + + @JsonPropertyDescription("Ngày hệ thống ghi nhận giao dịch") POST_DATE("PD"), + + @JsonPropertyDescription("Ngày khách hàng thực hiện giao dịch") TRANSACTION_DATE("TD"); @JsonValue diff --git a/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/model/CampaignCriteria.java b/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/model/CampaignCriteria.java index b943e67..449a6a6 100644 --- a/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/model/CampaignCriteria.java +++ b/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/model/CampaignCriteria.java @@ -1,6 +1,7 @@ package dev.sonpx.loyalty.mcp.reward.model; +import com.fasterxml.jackson.annotation.JsonPropertyDescription; import jakarta.validation.constraints.NotBlank; import lombok.Getter; import lombok.Setter; @@ -13,5 +14,6 @@ import lombok.Setter; public class CampaignCriteria extends Fw { @NotBlank + @JsonPropertyDescription("Cấu hình các tiêu chí có định dạng json") private String criteria; } diff --git a/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/model/CampaignRule.java b/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/model/CampaignRule.java index df7511f..7f592d0 100644 --- a/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/model/CampaignRule.java +++ b/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/model/CampaignRule.java @@ -67,7 +67,7 @@ public class CampaignRule extends Fw { private Boolean notUpdatePool; @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 campaignTcLinkages; /** @@ -107,11 +107,9 @@ public class CampaignRule extends Fw { @JsonPropertyDescription("Mã sản phẩm/quà tặng") 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") 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") private Boolean segApplyFirstFlag; @@ -128,7 +126,7 @@ public class CampaignRule extends Fw { private CampaignFormulaOne campaignFormulaOne; @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; @Valid diff --git a/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/service/CampaignRuleService.java b/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/service/CampaignRuleService.java new file mode 100644 index 0000000..675ae01 --- /dev/null +++ b/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/service/CampaignRuleService.java @@ -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> getAll(CampaignRuleCriteria criteria) { + try { + List 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 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 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> findActiveByIds(String ids) { + try { + List 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 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 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> create(CampaignRule dto) { + try { + OperationResult opResult = campaignRuleClient.create(dto); + return Result.of(opResult, MarkdownGenerator.generateCampaignRuleOperationResultMD(opResult)); + } catch (Exception e) { + log.error(e.getMessage(), e); + return Result.of(null); + } + } +} diff --git a/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/service/CampaignService.java b/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/service/CampaignService.java index 338d48e..130e969 100644 --- a/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/service/CampaignService.java +++ b/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/service/CampaignService.java @@ -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.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.model.Campaign; import dev.sonpx.loyalty.mcp.util.MarkdownGenerator; @@ -22,11 +22,11 @@ import org.springframework.stereotype.Service; @RequiredArgsConstructor public class CampaignService { - private final RewardClient rewardClient; + private final CampaignClient campaignClient; public Result> getAll(CampaignCriteria criteria) { try { - List campaigns = rewardClient.getAll(criteria, Pageable.ofSize(10)); + List campaigns = campaignClient.getAll(criteria, Pageable.ofSize(10)); return Result.of(campaigns, generateCampaignMD(campaigns)); } catch (Exception e) { @@ -37,7 +37,7 @@ public class CampaignService { public Result count(CampaignCriteria criteria) { try { - Long count = rewardClient.count(criteria); + Long count = campaignClient.count(criteria); return Result.of(count, MarkdownGenerator.generateCampaignMD(count)); } catch (Exception e) { log.error(e.getMessage(), e); @@ -47,7 +47,7 @@ public class CampaignService { public Result findActiveById(String id) { try { - Campaign campaign = rewardClient.findActiveById(id); + Campaign campaign = campaignClient.findActiveById(id); return Result.of(campaign, MarkdownGenerator.generateCampaignMD(campaign)); } catch (Exception e) { log.error(e.getMessage(), e); @@ -57,7 +57,7 @@ public class CampaignService { public Result> findActiveByIds(String ids) { try { - List campaigns = rewardClient.findActiveByIds(java.util.Map.of("id", ids)); + List campaigns = campaignClient.findActiveByIds(java.util.Map.of("id", ids)); return Result.of(campaigns, MarkdownGenerator.generateCampaignMD(campaigns)); } catch (Exception e) { log.error(e.getMessage(), e); @@ -67,7 +67,7 @@ public class CampaignService { public Result generateId() { try { - String id = rewardClient.generateId(); + String id = campaignClient.generateId(); return Result.of(id, MarkdownGenerator.generateCampaignIdMD(id)); } catch (Exception e) { log.error(e.getMessage(), e); @@ -77,7 +77,7 @@ public class CampaignService { public Result checkId(String id) { try { - String checkResult = rewardClient.checkId(id); + String checkResult = campaignClient.checkId(id); return Result.of(checkResult, MarkdownGenerator.generateCheckResultMD(checkResult)); } catch (Exception e) { log.error(e.getMessage(), e); @@ -87,7 +87,7 @@ public class CampaignService { public Result> create(Campaign dto) { try { - OperationResult opResult = rewardClient.create(dto); + OperationResult opResult = campaignClient.create(dto); return Result.of(opResult, MarkdownGenerator.generateOperationResultMD(opResult)); } catch (Exception e) { log.error(e.getMessage(), e); diff --git a/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/tool/CampaignRuleTools.java b/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/tool/CampaignRuleTools.java new file mode 100644 index 0000000..4db1fb8 --- /dev/null +++ b/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/tool/CampaignRuleTools.java @@ -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> 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 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 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> getCampaignRulesByIds(String ids) { + return campaignRuleService.findActiveByIds(ids); + } + + @McpTool(description = "Tạo một ID Campaign Rule mới") + public Result 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 checkCampaignRuleId(String id) { + return campaignRuleService.checkId(id); + } + + @McpTool(description = "Tạo một Campaign Rule mới") + public Result> createCampaignRule(CampaignRule dto) { + return campaignRuleService.create(dto); + } +} diff --git a/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/tool/ApiMcpTools.java b/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/tool/CampaignTools.java similarity index 98% rename from loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/tool/ApiMcpTools.java rename to loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/tool/CampaignTools.java index 74c3d2f..f8c73c2 100644 --- a/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/tool/ApiMcpTools.java +++ b/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/tool/CampaignTools.java @@ -15,7 +15,7 @@ import org.springframework.stereotype.Component; */ @Component @RequiredArgsConstructor -public class ApiMcpTools { +public class CampaignTools { private final CampaignService campaignService; diff --git a/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/util/MarkdownGenerator.java b/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/util/MarkdownGenerator.java index 8ca4a87..39c18d4 100644 --- a/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/util/MarkdownGenerator.java +++ b/loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/util/MarkdownGenerator.java @@ -2,6 +2,7 @@ package dev.sonpx.loyalty.mcp.util; import dev.sonpx.loyalty.mcp.model.OperationResult; import dev.sonpx.loyalty.mcp.reward.model.Campaign; +import dev.sonpx.loyalty.mcp.reward.model.CampaignRule; import java.util.List; import org.springframework.util.CollectionUtils; @@ -60,6 +61,65 @@ public class MarkdownGenerator { 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 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) { if (result == null) { return "No check result available."; @@ -85,4 +145,23 @@ public class MarkdownGenerator { } return sb.toString(); } + + public static String generateCampaignRuleOperationResultMD(OperationResult 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(); + } }