refactor: restructure MCP server by replacing client-specific tool logic with a centralized campaign service and standardized models.
This commit is contained in:
38
.cursor/rules/codegraph.mdc
Normal file
38
.cursor/rules/codegraph.mdc
Normal file
@@ -0,0 +1,38 @@
|
||||
---
|
||||
description: CodeGraph MCP usage guide — when to use which tool
|
||||
alwaysApply: true
|
||||
---
|
||||
<!-- CODEGRAPH_START -->
|
||||
## CodeGraph
|
||||
|
||||
This project has a CodeGraph MCP server (`codegraph_*` tools) configured. CodeGraph is a tree-sitter-parsed knowledge graph of every symbol, edge, and file. Reads are sub-millisecond and return structural information grep cannot.
|
||||
|
||||
### When to prefer codegraph over native search
|
||||
|
||||
Use codegraph for **structural** questions — what calls what, what would break, where is X defined, what is X's signature. Use native grep/read only for **literal text** queries (string contents, comments, log messages) or after you already have a specific file open.
|
||||
|
||||
| Question | Tool |
|
||||
|---|---|
|
||||
| "Where is X defined?" / "Find symbol named X" | `codegraph_search` |
|
||||
| "What calls function Y?" | `codegraph_callers` |
|
||||
| "What does Y call?" | `codegraph_callees` |
|
||||
| "What would break if I changed Z?" | `codegraph_impact` |
|
||||
| "Show me Y's signature / source / docstring" | `codegraph_node` |
|
||||
| "Give me focused context for a task/area" | `codegraph_context` |
|
||||
| "See several related symbols' source at once" | `codegraph_explore` |
|
||||
| "What files exist under path/" | `codegraph_files` |
|
||||
| "Is the index healthy?" | `codegraph_status` |
|
||||
|
||||
### Rules of thumb
|
||||
|
||||
- **Answer directly — don't delegate exploration.** For "how does X work" / architecture / trace questions, answer with 2-3 codegraph calls: `codegraph_context` first, then ONE `codegraph_explore` for the source of the symbols it surfaces. Codegraph IS the pre-built index, so spawning a separate file-reading sub-task/agent — or running a grep + read loop — repeats work codegraph already did and costs more for the same answer.
|
||||
- **Trust codegraph results.** They come from a full AST parse. Do NOT re-verify them with grep — that's slower, less accurate, and wastes context.
|
||||
- **Don't grep first** when looking up a symbol by name. `codegraph_search` is faster and returns kind + location + signature in one call.
|
||||
- **Don't chain `codegraph_search` + `codegraph_node`** when you just want context — `codegraph_context` is one call.
|
||||
- **Don't loop `codegraph_node` over many symbols** — one `codegraph_explore` call returns several symbols' source grouped in a single capped call, while each separate node/Read call re-reads the whole context and costs far more.
|
||||
- **Index lag**: the file watcher debounces ~500ms behind writes; don't re-query immediately after editing a file in the same turn.
|
||||
|
||||
### If `.codegraph/` doesn't exist
|
||||
|
||||
The MCP server returns "not initialized." Ask the user: *"I notice this project doesn't have CodeGraph initialized. Want me to run `codegraph init -i` to build the index?"*
|
||||
<!-- CODEGRAPH_END -->
|
||||
@@ -1,5 +1,6 @@
|
||||
package dev.sonpx.loyalty.agent.config;
|
||||
|
||||
import java.util.UUID;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
|
||||
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
|
||||
@@ -25,7 +26,7 @@ public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
|
||||
@Override
|
||||
protected java.security.Principal determineUser(org.springframework.http.server.ServerHttpRequest request, org.springframework.web.socket.WebSocketHandler wsHandler, java.util.Map<String, Object> attributes) {
|
||||
return new java.security.Principal() {
|
||||
private final String id = java.util.UUID.randomUUID().toString();
|
||||
private final String id = UUID.randomUUID().toString();
|
||||
@Override
|
||||
public String getName() {
|
||||
return id;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package dev.sonpx.loyalty.agent.controller;
|
||||
|
||||
import dev.sonpx.loyalty.agent.service.LoyaltyAgentService;
|
||||
import org.springframework.messaging.handler.annotation.Header;
|
||||
import java.security.Principal;
|
||||
import org.springframework.messaging.handler.annotation.MessageMapping;
|
||||
import org.springframework.messaging.handler.annotation.Payload;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.CrossOrigin;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@CrossOrigin(origins = "*")
|
||||
@@ -24,7 +24,7 @@ public class AgentController {
|
||||
public record ChatRequest(String conversationId, String messageId, String prompt, String activeCampaignId) {}
|
||||
|
||||
@MessageMapping("/chat")
|
||||
public void chat(@Payload ChatRequest request, java.security.Principal principal) {
|
||||
public void chat(@Payload ChatRequest request, Principal principal) {
|
||||
agentService.chat(request, principal.getName());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,24 +1,27 @@
|
||||
package dev.sonpx.loyalty.agent.service;
|
||||
|
||||
import org.springframework.ai.chat.client.ChatClient;
|
||||
import org.springframework.ai.tool.ToolCallbackProvider;
|
||||
import org.springframework.stereotype.Service;
|
||||
import dev.sonpx.loyalty.agent.controller.AgentController;
|
||||
import dev.sonpx.loyalty.agent.domain.AgentEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.ai.chat.client.ChatClient;
|
||||
import org.springframework.ai.chat.model.ChatModel;
|
||||
import org.springframework.ai.tool.ToolCallback;
|
||||
import org.springframework.ai.tool.ToolCallbackProvider;
|
||||
import org.springframework.ai.tool.definition.ToolDefinition;
|
||||
import org.springframework.messaging.simp.SimpMessagingTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class LoyaltyAgentService {
|
||||
|
||||
private final org.springframework.ai.chat.model.ChatModel chatModel;
|
||||
private final ChatModel chatModel;
|
||||
private final List<ToolCallbackProvider> toolProviders;
|
||||
private final org.springframework.messaging.simp.SimpMessagingTemplate messagingTemplate;
|
||||
private final SimpMessagingTemplate messagingTemplate;
|
||||
|
||||
public LoyaltyAgentService(org.springframework.ai.chat.model.ChatModel chatModel, List<ToolCallbackProvider> toolProviders, org.springframework.messaging.simp.SimpMessagingTemplate messagingTemplate) {
|
||||
this.chatModel = chatModel;
|
||||
this.toolProviders = toolProviders;
|
||||
this.messagingTemplate = messagingTemplate;
|
||||
}
|
||||
|
||||
public void chat(dev.sonpx.loyalty.agent.controller.AgentController.ChatRequest request, String sessionId) {
|
||||
public void chat(AgentController.ChatRequest request, String sessionId) {
|
||||
String prompt = request.prompt();
|
||||
if (request.activeCampaignId() != null && !request.activeCampaignId().isEmpty()) {
|
||||
prompt = "Context: Active Campaign ID is " + request.activeCampaignId() + ".\n\n" + prompt;
|
||||
@@ -27,21 +30,21 @@ public class LoyaltyAgentService {
|
||||
prompt = "System Context: The current conversationId (session ID) is " + request.conversationId() + ". You MUST provide this conversationId as a parameter to any tool that requires it.\n\n" + prompt;
|
||||
}
|
||||
|
||||
List<org.springframework.ai.tool.ToolCallback> wrappedTools = new java.util.ArrayList<>();
|
||||
List<ToolCallback> wrappedTools = new ArrayList<>();
|
||||
for (ToolCallbackProvider provider : toolProviders) {
|
||||
for (org.springframework.ai.tool.ToolCallback tool : provider.getToolCallbacks()) {
|
||||
wrappedTools.add(new org.springframework.ai.tool.ToolCallback() {
|
||||
for (ToolCallback tool : provider.getToolCallbacks()) {
|
||||
wrappedTools.add(new ToolCallback() {
|
||||
@Override
|
||||
public org.springframework.ai.tool.definition.ToolDefinition getToolDefinition() {
|
||||
public ToolDefinition getToolDefinition() {
|
||||
return tool.getToolDefinition();
|
||||
}
|
||||
@Override
|
||||
public String call(String toolInput) {
|
||||
messagingTemplate.convertAndSendToUser(sessionId, "/queue/chat-events", dev.sonpx.loyalty.agent.domain.AgentEvent.toolStatus(request.messageId(), tool.getToolDefinition().name(), "running"));
|
||||
messagingTemplate.convertAndSendToUser(sessionId, "/queue/chat-events", AgentEvent.toolStatus(request.messageId(), tool.getToolDefinition().name(), "running"));
|
||||
try {
|
||||
return tool.call(toolInput);
|
||||
} finally {
|
||||
messagingTemplate.convertAndSendToUser(sessionId, "/queue/chat-events", dev.sonpx.loyalty.agent.domain.AgentEvent.toolStatus(request.messageId(), tool.getToolDefinition().name(), "done"));
|
||||
messagingTemplate.convertAndSendToUser(sessionId, "/queue/chat-events", AgentEvent.toolStatus(request.messageId(), tool.getToolDefinition().name(), "done"));
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -54,14 +57,8 @@ public class LoyaltyAgentService {
|
||||
Your primary job is to orchestrate operations for our Loyalty core system.
|
||||
|
||||
CRITICAL RULES:
|
||||
1. DO NOT create a model.client.dev.sonpx.loyalty.agent.Campaign unless you verify that Member Tiers and Point Pools exist first.
|
||||
2. Use the `listMemberTiers` tool to check for valid tier IDs.
|
||||
3. Use the `listPointPools` tool to check for valid pool IDs and ensure they have a sufficient balance.
|
||||
4. Use the `listStaticAttributes` tool when asked about configurations, properties, or static values.
|
||||
5. Only use the `createCampaign` tool once verification is complete.
|
||||
6. Always answer the user in the language they used (e.g., Vietnamese).
|
||||
7. If a tool response contains a `presentation.content` field, you MUST display its content exactly as provided to the user. Do not summarize or alter its formatting. Use the `data` field only for your internal reasoning.
|
||||
8. When asked for information, details, or status of an existing campaign, ALWAYS use the `listCampaigns` tool. DO NOT use `createCampaign` unless explicitly asked to create or add a new one.
|
||||
1. Always answer the user in the language they used (e.g., Vietnamese).
|
||||
2. If a tool response contains a `presentation.markdown` field, you MUST display its content exactly as provided to the user. Do not summarize or alter its formatting. Use the `data` field only for your internal reasoning.
|
||||
""")
|
||||
.defaultTools(wrappedTools)
|
||||
.build();
|
||||
@@ -70,14 +67,14 @@ public class LoyaltyAgentService {
|
||||
.user(prompt)
|
||||
.stream()
|
||||
.content()
|
||||
.doOnComplete(() -> messagingTemplate.convertAndSendToUser(sessionId, "/queue/chat-events", dev.sonpx.loyalty.agent.domain.AgentEvent.done(request.messageId())))
|
||||
.doOnComplete(() -> messagingTemplate.convertAndSendToUser(sessionId, "/queue/chat-events", AgentEvent.done(request.messageId())))
|
||||
.subscribe(
|
||||
content -> {
|
||||
if (content != null && !content.isEmpty()) {
|
||||
messagingTemplate.convertAndSendToUser(sessionId, "/queue/chat-events", dev.sonpx.loyalty.agent.domain.AgentEvent.token(request.messageId(), content));
|
||||
messagingTemplate.convertAndSendToUser(sessionId, "/queue/chat-events", AgentEvent.token(request.messageId(), content));
|
||||
}
|
||||
},
|
||||
error -> messagingTemplate.convertAndSendToUser(sessionId, "/queue/chat-events", dev.sonpx.loyalty.agent.domain.AgentEvent.error(request.messageId(), error.getMessage()))
|
||||
error -> messagingTemplate.convertAndSendToUser(sessionId, "/queue/chat-events", AgentEvent.error(request.messageId(), error.getMessage()))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
package dev.sonpx.loyalty.mcp.client.api;
|
||||
|
||||
import dev.sonpx.loyalty.mcp.client.model.Campaign;
|
||||
import dev.sonpx.loyalty.mcp.client.model.OperationResult;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.service.annotation.GetExchange;
|
||||
import org.springframework.web.service.annotation.PostExchange;
|
||||
|
||||
public interface CampaignApiClient {
|
||||
|
||||
@GetExchange("/api/campaign/csr/id/generate")
|
||||
String generateCampaignId();
|
||||
|
||||
@PostExchange("/api/campaign/csr/create-draft")
|
||||
OperationResult<Campaign> createCampaignDraft(@RequestBody Campaign request);
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package dev.sonpx.loyalty.mcp.client.api;
|
||||
|
||||
import dev.sonpx.loyalty.mcp.client.model.MemberTier;
|
||||
import java.util.List;
|
||||
import org.springframework.web.service.annotation.GetExchange;
|
||||
|
||||
public interface MemberTierApiClient {
|
||||
|
||||
@GetExchange("/svc/reward/api/decide-tier-view/list?page=0&size=100&status.in=A")
|
||||
List<MemberTier> listMemberTiers();
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package dev.sonpx.loyalty.mcp.client.api;
|
||||
|
||||
import dev.sonpx.loyalty.mcp.client.model.PointPool;
|
||||
import java.util.List;
|
||||
import org.springframework.web.service.annotation.GetExchange;
|
||||
|
||||
public interface PointPoolApiClient {
|
||||
|
||||
@GetExchange("/api/pool-definition/all")
|
||||
List<PointPool> listPointPools();
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package dev.sonpx.loyalty.mcp.client.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public record Campaign(String campaignId, String name, String targetTierId, String rewardPoolId, String status) {
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package dev.sonpx.loyalty.mcp.client.model;
|
||||
|
||||
public record CreateCampaignRequest(String campaignId, String name, String targetTierId, String rewardPoolId) {
|
||||
|
||||
public CreateCampaignRequest(String name, String targetTierId, String rewardPoolId) {
|
||||
this(null, name, targetTierId, rewardPoolId);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package dev.sonpx.loyalty.mcp.client.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAlias;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public record MemberTier(
|
||||
@JsonProperty("key") @JsonAlias("tierId") String tierId,
|
||||
@JsonAlias("name") String tierName,
|
||||
Integer requiredPoints) {
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package dev.sonpx.loyalty.mcp.client.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public record OperationResult<T>(String errorCode, String message, T entity) {
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package dev.sonpx.loyalty.mcp.client.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public record PointPool(String poolId, String poolName, BigDecimal balance) {
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package dev.sonpx.loyalty.mcp.client.model.attribute;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class StaticAttributeDto {
|
||||
private String attributeId;
|
||||
private String attributeType;
|
||||
private String attributeValue;
|
||||
private String status;
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package dev.sonpx.loyalty.mcp.client.model.reward;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAnySetter;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class CampaignDto {
|
||||
private String campaignId;
|
||||
private String name;
|
||||
private String targetTierId;
|
||||
private String rewardPoolId;
|
||||
private String status;
|
||||
private String description;
|
||||
|
||||
private Map<String, Object> extraData = new LinkedHashMap<>();
|
||||
|
||||
@JsonAnySetter
|
||||
public void addExtraData(String key, Object value) {
|
||||
this.extraData.put(key, value);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package dev.sonpx.loyalty.mcp.client.model.reward;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class CampaignFormulaOneDto {
|
||||
private Double awardPerTxnAmt;
|
||||
private Double txnAmt;
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
package dev.sonpx.loyalty.mcp.client.model.reward;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class CampaignRuleDto {
|
||||
private ReferenceData campaignId;
|
||||
private String ruleId;
|
||||
private String ruleName;
|
||||
private RuleTypeEnum ruleType;
|
||||
private String description;
|
||||
private OffsetDateTime effectiveFrom;
|
||||
private OffsetDateTime effectiveTo;
|
||||
private ReferenceData poolId;
|
||||
private CampaignFormulaOneDto campaignFormulaOne;
|
||||
private List<String> formulaSequence;
|
||||
|
||||
public enum RuleTypeEnum {
|
||||
AWD("AWD"),
|
||||
RED("RED"),
|
||||
ADJ("ADJ"),
|
||||
REP("REP"),
|
||||
MAWD("MAWD"),
|
||||
CEP("CEP");
|
||||
|
||||
private String value;
|
||||
|
||||
RuleTypeEnum(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@JsonValue
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.valueOf(value);
|
||||
}
|
||||
|
||||
public static RuleTypeEnum fromValue(String value) {
|
||||
for (RuleTypeEnum b : RuleTypeEnum.values()) {
|
||||
if (b.value.equals(value)) {
|
||||
return b;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Unexpected value '" + value + "'");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package dev.sonpx.loyalty.mcp.client.model.reward;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class OperationResultCampaignRuleDto {
|
||||
private Boolean success;
|
||||
private String errorCode;
|
||||
private String errorMessage;
|
||||
private CampaignRuleDto data;
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package dev.sonpx.loyalty.mcp.client.model.reward;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class ReferenceData {
|
||||
private String code;
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
package dev.sonpx.loyalty.mcp.config;
|
||||
|
||||
import dev.sonpx.loyalty.mcp.client.api.CampaignApiClient;
|
||||
import dev.sonpx.loyalty.mcp.client.api.MemberTierApiClient;
|
||||
import dev.sonpx.loyalty.mcp.client.api.PointPoolApiClient;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.authentication.AnonymousAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
|
||||
import org.springframework.web.client.RestClient;
|
||||
import org.springframework.web.client.support.RestClientAdapter;
|
||||
import org.springframework.web.service.invoker.HttpServiceProxyFactory;
|
||||
|
||||
/**
|
||||
* Configures OAuth2-enabled RestClient instances and Spring HTTP interface clients.
|
||||
*/
|
||||
@Configuration
|
||||
public class ApiClientConfig {
|
||||
|
||||
@Value("${loyalty.core.base-url}")
|
||||
private String coreBaseUrl;
|
||||
|
||||
|
||||
@Bean
|
||||
public RestClient loyaltyRestClient(OAuth2AuthorizedClientManager authorizedClientManager) {
|
||||
return RestClient.builder()
|
||||
.baseUrl(coreBaseUrl)
|
||||
.requestInterceptor((request, body, execution) -> {
|
||||
Authentication principal =
|
||||
new AnonymousAuthenticationToken(
|
||||
"key", "loyalty-agent-service",
|
||||
AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"));
|
||||
OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest
|
||||
.withClientRegistrationId("keycloak")
|
||||
.principal(principal)
|
||||
.build();
|
||||
try {
|
||||
OAuth2AuthorizedClient authorizedClient = authorizedClientManager.authorize(authorizeRequest);
|
||||
if (authorizedClient != null && authorizedClient.getAccessToken() != null) {
|
||||
request.getHeaders().setBearerAuth(authorizedClient.getAccessToken().getTokenValue());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Failed to authorize loyalty API request", e);
|
||||
}
|
||||
return execution.execute(request, body);
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RestClient rewardRestClient(OAuth2AuthorizedClientManager authorizedClientManager) {
|
||||
return RestClient.builder()
|
||||
.baseUrl(coreBaseUrl + "/svc/reward")
|
||||
.requestInterceptor((request, body, execution) -> {
|
||||
org.springframework.security.core.Authentication principal =
|
||||
new org.springframework.security.authentication.AnonymousAuthenticationToken(
|
||||
"key", "loyalty-agent-service",
|
||||
org.springframework.security.core.authority.AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"));
|
||||
OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest
|
||||
.withClientRegistrationId("keycloak")
|
||||
.principal(principal)
|
||||
.build();
|
||||
try {
|
||||
OAuth2AuthorizedClient authorizedClient = authorizedClientManager.authorize(authorizeRequest);
|
||||
if (authorizedClient != null && authorizedClient.getAccessToken() != null) {
|
||||
request.getHeaders().setBearerAuth(authorizedClient.getAccessToken().getTokenValue());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Failed to authorize reward API request", e);
|
||||
}
|
||||
return execution.execute(request, body);
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MemberTierApiClient memberTierApiClient(@Qualifier("loyaltyRestClient") RestClient loyaltyRestClient) {
|
||||
return createClient(loyaltyRestClient, MemberTierApiClient.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PointPoolApiClient pointPoolApiClient(@Qualifier("rewardRestClient") RestClient rewardRestClient) {
|
||||
return createClient(rewardRestClient, PointPoolApiClient.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CampaignApiClient campaignApiClient(@Qualifier("rewardRestClient") RestClient rewardRestClient) {
|
||||
return createClient(rewardRestClient, CampaignApiClient.class);
|
||||
}
|
||||
|
||||
private <T> T createClient(RestClient restClient, Class<T> clientType) {
|
||||
HttpServiceProxyFactory factory = HttpServiceProxyFactory
|
||||
.builderFor(RestClientAdapter.create(restClient))
|
||||
.build();
|
||||
return factory.createClient(clientType);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package dev.sonpx.loyalty.mcp.config;
|
||||
import static org.springframework.security.oauth2.client.web.client.RequestAttributeClientRegistrationIdResolver.clientRegistrationId;
|
||||
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 org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@@ -38,6 +39,12 @@ public class ClientConfig {
|
||||
principal("loyalty-service").accept(request.getAttributes());
|
||||
return oauth2Interceptor.intercept(request, body, execution);
|
||||
})
|
||||
.defaultStatusHandler(
|
||||
HttpStatusCode::isError,
|
||||
(request, response) -> {
|
||||
throw new RuntimeException("API Error: HTTP Status " + response.getStatusCode());
|
||||
}
|
||||
)
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
package dev.sonpx.loyalty.mcp.config;
|
||||
|
||||
import dev.sonpx.loyalty.mcp.client.api.CampaignApiClient;
|
||||
import dev.sonpx.loyalty.mcp.client.api.MemberTierApiClient;
|
||||
import dev.sonpx.loyalty.mcp.client.api.PointPoolApiClient;
|
||||
import dev.sonpx.loyalty.mcp.tool.LoyaltyAgentTools;
|
||||
import dev.sonpx.loyalty.mcp.tool.reward.CampaignRuleTools;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.client.RestClient;
|
||||
|
||||
@Configuration
|
||||
public class McpServerConfig {
|
||||
|
||||
@Bean
|
||||
public LoyaltyAgentTools loyaltyAgentTools(
|
||||
MemberTierApiClient memberTierApiClient,
|
||||
PointPoolApiClient pointPoolApiClient,
|
||||
CampaignApiClient campaignApiClient,
|
||||
@org.springframework.beans.factory.annotation.Qualifier("loyaltyRestClient") RestClient restClient) {
|
||||
return new LoyaltyAgentTools(memberTierApiClient, pointPoolApiClient, campaignApiClient, restClient);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public org.springframework.ai.tool.ToolCallbackProvider loyaltyToolsProvider(
|
||||
LoyaltyAgentTools loyaltyAgentTools,
|
||||
CampaignRuleTools campaignRuleTools,
|
||||
dev.sonpx.loyalty.mcp.tool.reward.CampaignTools campaignTools) {
|
||||
return org.springframework.ai.tool.method.MethodToolCallbackProvider.builder()
|
||||
.toolObjects(loyaltyAgentTools, campaignRuleTools, campaignTools)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import java.time.Instant;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author AnhDT Created on 2025/05/23
|
||||
* Created by SonPhung on Monday, 20-Jul-2026
|
||||
*/
|
||||
@Data
|
||||
public abstract class AbstractFwCriteria implements FwCriteria {
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
package dev.sonpx.loyalty.mcp.draft;
|
||||
|
||||
import dev.sonpx.loyalty.mcp.client.model.reward.CampaignDto;
|
||||
import dev.sonpx.loyalty.mcp.draft.model.DraftSession;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class DependencyValidator {
|
||||
|
||||
public void validateCampaignRequiredFields(CampaignDto campaign) {
|
||||
if (campaign == null) {
|
||||
throw new IllegalArgumentException("Campaign draft is empty");
|
||||
}
|
||||
if (campaign.getCampaignId() == null || campaign.getCampaignId().isBlank()) {
|
||||
throw new IllegalArgumentException("Campaign ID is required");
|
||||
}
|
||||
if (campaign.getName() == null || campaign.getName().isBlank()) {
|
||||
throw new IllegalArgumentException("Campaign name is required");
|
||||
}
|
||||
}
|
||||
|
||||
public void validateRulePrerequisites(DraftSession session, String targetCampaignId, String transactionCode, boolean needsPoolId, String poolId) {
|
||||
if (targetCampaignId == null || targetCampaignId.isBlank()) {
|
||||
throw new IllegalArgumentException("Target Campaign ID is required to add a rule");
|
||||
}
|
||||
|
||||
// In a real system, we might check if targetCampaignId matches the one in draft,
|
||||
// or check the Core API if the campaign exists.
|
||||
|
||||
if (transactionCode == null || transactionCode.isBlank()) {
|
||||
throw new IllegalArgumentException("Transaction Code is required");
|
||||
}
|
||||
|
||||
if (needsPoolId && (poolId == null || poolId.isBlank())) {
|
||||
throw new IllegalArgumentException("Pool ID is required for the selected rule flow");
|
||||
}
|
||||
}
|
||||
|
||||
public void validateSopPrerequisites(String poolId) {
|
||||
if (poolId == null || poolId.isBlank()) {
|
||||
throw new IllegalArgumentException("Existing Pool ID is required to create a Statement Output Pool");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package dev.sonpx.loyalty.mcp.draft;
|
||||
|
||||
import dev.sonpx.loyalty.mcp.draft.model.DraftSession;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Consumer;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class DraftSessionManager {
|
||||
|
||||
private final Map<String, DraftSession> sessions = new ConcurrentHashMap<>();
|
||||
private static final Duration TTL = Duration.ofHours(1);
|
||||
|
||||
public DraftSession getSession(String conversationId) {
|
||||
if (conversationId == null) {
|
||||
throw new IllegalArgumentException("conversationId cannot be null");
|
||||
}
|
||||
cleanupExpiredSessions();
|
||||
return sessions.compute(conversationId, (id, session) -> {
|
||||
if (session == null || isExpired(session)) {
|
||||
return new DraftSession(id);
|
||||
}
|
||||
session.setLastUpdated(Instant.now());
|
||||
return session;
|
||||
});
|
||||
}
|
||||
|
||||
public void updateSession(String conversationId, Consumer<DraftSession> updater) {
|
||||
if (conversationId == null) {
|
||||
throw new IllegalArgumentException("conversationId cannot be null");
|
||||
}
|
||||
cleanupExpiredSessions();
|
||||
sessions.compute(conversationId, (id, session) -> {
|
||||
if (session == null || isExpired(session)) {
|
||||
session = new DraftSession(id);
|
||||
}
|
||||
updater.accept(session);
|
||||
session.setLastUpdated(Instant.now());
|
||||
return session;
|
||||
});
|
||||
}
|
||||
|
||||
public void removeSession(String conversationId) {
|
||||
sessions.remove(conversationId);
|
||||
}
|
||||
|
||||
private boolean isExpired(DraftSession session) {
|
||||
return Instant.now().isAfter(session.getLastUpdated().plus(TTL));
|
||||
}
|
||||
|
||||
private void cleanupExpiredSessions() {
|
||||
sessions.entrySet().removeIf(entry -> isExpired(entry.getValue()));
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package dev.sonpx.loyalty.mcp.draft.model;
|
||||
|
||||
import dev.sonpx.loyalty.mcp.client.model.reward.CampaignDto;
|
||||
import dev.sonpx.loyalty.mcp.client.model.reward.CampaignRuleDto;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class CampaignDraft {
|
||||
private CampaignDto campaign = new CampaignDto();
|
||||
private List<CampaignRuleDto> rules = new ArrayList<>();
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package dev.sonpx.loyalty.mcp.draft.model;
|
||||
|
||||
import java.time.Instant;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class DraftSession {
|
||||
private String conversationId;
|
||||
private Instant lastUpdated;
|
||||
private CampaignDraft campaignDraft;
|
||||
private PoolDraft poolDraft;
|
||||
|
||||
public DraftSession(String conversationId) {
|
||||
this.conversationId = conversationId;
|
||||
this.lastUpdated = Instant.now();
|
||||
this.campaignDraft = new CampaignDraft();
|
||||
this.poolDraft = new PoolDraft();
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package dev.sonpx.loyalty.mcp.draft.model;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class PoolDraft {
|
||||
private Map<String, Object> poolDefinition = new HashMap<>();
|
||||
private Map<String, Object> statementOutputPool = new HashMap<>();
|
||||
}
|
||||
@@ -8,8 +8,7 @@ import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
/**
|
||||
* @author Phudao
|
||||
* Created on 2024/01/12
|
||||
* Created by SonPhung on Monday, 20-Jul-2026
|
||||
*/
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
|
||||
@@ -6,7 +6,7 @@ import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
/**
|
||||
* @author AnhDT Created on 2025/11/17
|
||||
* Created by SonPhung on Monday, 20-Jul-2026
|
||||
*/
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
package dev.sonpx.loyalty.mcp.enums;
|
||||
|
||||
/**
|
||||
* @author AnhDT
|
||||
* Created on 2024/06/26
|
||||
* Created by SonPhung on Monday, 20-Jul-2026
|
||||
*/
|
||||
public enum PeriodAction {
|
||||
CURRENT,
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
package dev.sonpx.loyalty.mcp.enums;
|
||||
|
||||
/**
|
||||
* @author AnhDT
|
||||
* Created on 2024/06/26
|
||||
* Created by SonPhung on Monday, 20-Jul-2026
|
||||
*/
|
||||
public enum PeriodType {
|
||||
YEAR,
|
||||
|
||||
@@ -9,8 +9,7 @@ import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
/**
|
||||
* @author AnhDT
|
||||
* Created on 2024/06/26
|
||||
* Created by SonPhung on Monday, 20-Jul-2026
|
||||
* {@link #LAST_7_DAYS},
|
||||
* {@link #LAST_30_DAYS},
|
||||
* {@link #LAST_3_MONTHS},
|
||||
|
||||
@@ -2,6 +2,6 @@
|
||||
package dev.sonpx.loyalty.mcp.filter;
|
||||
|
||||
/**
|
||||
* @author AnhDT Created on 2025/12/11
|
||||
* Created by SonPhung on Monday, 20-Jul-2026
|
||||
*/
|
||||
public class DoubleFilter extends RangeFilter<Double> {}
|
||||
|
||||
@@ -4,6 +4,6 @@ package dev.sonpx.loyalty.mcp.filter;
|
||||
import java.time.Instant;
|
||||
|
||||
/**
|
||||
* @author AnhDT Created on 2025/12/11
|
||||
* Created by SonPhung on Monday, 20-Jul-2026
|
||||
*/
|
||||
public class InstantFilter extends RangeFilter<Instant> {}
|
||||
|
||||
@@ -4,6 +4,6 @@ package dev.sonpx.loyalty.mcp.filter;
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* @author AnhDT Created on 2025/12/11
|
||||
* Created by SonPhung on Monday, 20-Jul-2026
|
||||
*/
|
||||
public class LocalDateFilter extends RangeFilter<LocalDate> {}
|
||||
|
||||
@@ -4,6 +4,6 @@ package dev.sonpx.loyalty.mcp.filter;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* @author AnhDT Created on 2025/12/11
|
||||
* Created by SonPhung on Monday, 20-Jul-2026
|
||||
*/
|
||||
public class LocalDateTimeFilter extends RangeFilter<LocalDateTime> {}
|
||||
|
||||
@@ -2,6 +2,6 @@
|
||||
package dev.sonpx.loyalty.mcp.filter;
|
||||
|
||||
/**
|
||||
* @author AnhDT Created on 2025/12/11
|
||||
* Created by SonPhung on Monday, 20-Jul-2026
|
||||
*/
|
||||
public class LongFilter extends RangeFilter<Long> {}
|
||||
|
||||
@@ -8,7 +8,7 @@ import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* Created by tuanngo on Wed, 10/01/2024
|
||||
* Created by SonPhung on Monday, 20-Jul-2026
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
package dev.sonpx.loyalty.mcp.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class McpResponseWrapper<T> {
|
||||
private T data;
|
||||
private Presentation presentation;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package dev.sonpx.loyalty.mcp.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Created by SonPhung on Wednesday, 25-Oct-2023
|
||||
*/
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@Data
|
||||
@Builder
|
||||
public class OperationResult<D> {
|
||||
|
||||
private String errorCode;
|
||||
private String message;
|
||||
private D entity;
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package dev.sonpx.loyalty.mcp.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class Presentation {
|
||||
private String type; // e.g. "markdown"
|
||||
private String content; // The formatted string
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
package dev.sonpx.loyalty.mcp.model;
|
||||
|
||||
/**
|
||||
* Created by tuanngo on Wed, 2025-09-10
|
||||
* Created by SonPhung on Monday, 20-Jul-2026
|
||||
*/
|
||||
public record ProcessResult(String code, String message, String targetId) {
|
||||
public static ProcessResult of(String code, String message) {
|
||||
|
||||
@@ -10,7 +10,7 @@ import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* Created by tuanngo on Tue, 2025-04-16
|
||||
* Created by SonPhung on Monday, 20-Jul-2026
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package dev.sonpx.loyalty.mcp.model;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
/**
|
||||
* Created by SonPhung on Monday, 20-Jul-2026
|
||||
*/
|
||||
@Getter
|
||||
@RequiredArgsConstructor(staticName = "of")
|
||||
public class Result<T> {
|
||||
|
||||
private final T data;
|
||||
|
||||
private Map<String, String> presentation;
|
||||
|
||||
public static <T> Result<T> of(T data, String content) {
|
||||
Result<T> result = of(data);
|
||||
result.markdown(content);
|
||||
return result;
|
||||
}
|
||||
|
||||
public void markdown(String content) {
|
||||
this.presentation = new HashMap<>();
|
||||
this.presentation.put("markdown", content);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,18 @@
|
||||
package dev.sonpx.loyalty.mcp.reward.client;
|
||||
|
||||
import dev.sonpx.loyalty.mcp.model.OperationResult;
|
||||
import dev.sonpx.loyalty.mcp.reward.criteria.CampaignCriteria;
|
||||
import dev.sonpx.loyalty.mcp.reward.model.CampaignDto;
|
||||
import dev.sonpx.loyalty.mcp.reward.model.Campaign;
|
||||
import jakarta.validation.Valid;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
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
|
||||
@@ -15,5 +21,23 @@ import org.springframework.web.service.annotation.HttpExchange;
|
||||
public interface RewardClient {
|
||||
|
||||
@GetExchange("/api/campaign/csr/list")
|
||||
ResponseEntity<List<CampaignDto>> getAll(CampaignCriteria criteria, Pageable pageable);
|
||||
List<Campaign> getAll(CampaignCriteria criteria, Pageable pageable);
|
||||
|
||||
@GetExchange("/csr/count")
|
||||
Long count(CampaignCriteria criteria);
|
||||
|
||||
@GetExchange("/csr/id/{id}")
|
||||
Campaign findActiveById(@PathVariable String id);
|
||||
|
||||
@GetExchange("/csr/id")
|
||||
List<Campaign> findActiveByIds(@RequestParam Map<String, String> params);
|
||||
|
||||
@GetExchange("/csr/id/generate")
|
||||
String generateId();
|
||||
|
||||
@GetExchange("/csr/id/check/{id}")
|
||||
String checkId(@PathVariable String id);
|
||||
|
||||
@PostExchange("/csr/create")
|
||||
OperationResult<Campaign> create(@Valid @RequestBody Campaign dto);
|
||||
}
|
||||
|
||||
@@ -5,18 +5,27 @@ 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;
|
||||
|
||||
/**
|
||||
* @author AnhDT
|
||||
* Created on 2023/10/25
|
||||
* Created by SonPhung on Monday, 20-Jul-2026
|
||||
*/
|
||||
@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;
|
||||
|
||||
// for search text and component
|
||||
|
||||
@@ -8,8 +8,7 @@ import dev.sonpx.loyalty.mcp.filter.StringFilter;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author AnhDT
|
||||
* Created on 2023/10/31
|
||||
* Created by SonPhung on Monday, 20-Jul-2026
|
||||
*/
|
||||
@Data
|
||||
public class CampaignRuleCriteria extends AbstractFwCriteria {
|
||||
|
||||
@@ -7,7 +7,7 @@ import dev.sonpx.loyalty.mcp.filter.StringFilter;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author AnhDT Created on 2025/05/22
|
||||
* Created by SonPhung on Monday, 20-Jul-2026
|
||||
*/
|
||||
@Data
|
||||
public class ComponentCriteria extends AbstractFwCriteria {
|
||||
|
||||
@@ -9,8 +9,7 @@ import dev.sonpx.loyalty.mcp.filter.StringFilter;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author AnhDT
|
||||
* Created on 2023/10/31
|
||||
* Created by SonPhung on Monday, 20-Jul-2026
|
||||
*/
|
||||
@Data
|
||||
public class CounterDefinitionCriteria extends AbstractFwCriteria {
|
||||
|
||||
@@ -8,8 +8,7 @@ import dev.sonpx.loyalty.mcp.filter.StringFilter;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author AnhDT
|
||||
* Created on 2023/12/12
|
||||
* Created by SonPhung on Monday, 20-Jul-2026
|
||||
*/
|
||||
@Data
|
||||
public class CurrencyRateCriteria extends AbstractFwCriteria {
|
||||
|
||||
@@ -7,8 +7,7 @@ import dev.sonpx.loyalty.mcp.filter.StringFilter;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author AnhDT
|
||||
* Created on 2023/10/31
|
||||
* Created by SonPhung on Monday, 20-Jul-2026
|
||||
*/
|
||||
@Data
|
||||
public class EventMaintenanceCriteria extends AbstractFwCriteria {
|
||||
|
||||
@@ -7,8 +7,7 @@ import dev.sonpx.loyalty.mcp.filter.StringFilter;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author AnhDT
|
||||
* Created on 2023/12/06
|
||||
* Created by SonPhung on Monday, 20-Jul-2026
|
||||
*/
|
||||
@Data
|
||||
public class PoolConversionRateCriteria extends AbstractFwCriteria {
|
||||
|
||||
@@ -7,8 +7,7 @@ import dev.sonpx.loyalty.mcp.filter.StringFilter;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author AnhDT
|
||||
* Created on 2023/10/25
|
||||
* Created by SonPhung on Monday, 20-Jul-2026
|
||||
*/
|
||||
@Data
|
||||
public class PoolDefinitionCriteria extends AbstractFwCriteria {
|
||||
|
||||
@@ -7,8 +7,7 @@ import dev.sonpx.loyalty.mcp.filter.StringFilter;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author AnhDT
|
||||
* Created on 2023/10/25
|
||||
* Created by SonPhung on Monday, 20-Jul-2026
|
||||
*/
|
||||
@Data
|
||||
public class StatementOutputPoolCriteria extends AbstractFwCriteria {
|
||||
|
||||
@@ -7,8 +7,7 @@ import dev.sonpx.loyalty.mcp.filter.StringFilter;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author AnhDT
|
||||
* Created on 2023/10/31
|
||||
* Created by SonPhung on Monday, 20-Jul-2026
|
||||
*/
|
||||
@Data
|
||||
public class TransactionCategoryCriteria extends AbstractFwCriteria {
|
||||
|
||||
@@ -8,8 +8,7 @@ import dev.sonpx.loyalty.mcp.filter.StringFilter;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author AnhDT
|
||||
* Created on 2023/10/25
|
||||
* Created by SonPhung on Monday, 20-Jul-2026
|
||||
*/
|
||||
@Data
|
||||
public class TransactionCodeCriteria extends AbstractFwCriteria {
|
||||
|
||||
@@ -8,8 +8,7 @@ import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
/**
|
||||
* @author AnhDT
|
||||
* Created on 2024/01/08
|
||||
* Created by SonPhung on Monday, 20-Jul-2026
|
||||
*/
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
|
||||
@@ -8,8 +8,7 @@ import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
/**
|
||||
* @author AnhDT
|
||||
* Created on 2024/01/05
|
||||
* Created by SonPhung on Monday, 20-Jul-2026
|
||||
*/
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
|
||||
@@ -7,8 +7,7 @@ import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
/**
|
||||
* @author AnhDT
|
||||
* Created on 2024/03/11
|
||||
* Created by SonPhung on Monday, 20-Jul-2026
|
||||
*/
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
|
||||
@@ -8,8 +8,7 @@ import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
/**
|
||||
* @author AnhDT
|
||||
* Created on 2024/01/05
|
||||
* Created by SonPhung on Monday, 20-Jul-2026
|
||||
*/
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
|
||||
@@ -8,8 +8,7 @@ import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
/**
|
||||
* @author AnhDT
|
||||
* Created on 2024/01/02
|
||||
* Created by SonPhung on Monday, 20-Jul-2026
|
||||
*/
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
|
||||
@@ -8,8 +8,7 @@ import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
/**
|
||||
* @author AnhDT
|
||||
* Created on 2024/01/09
|
||||
* Created by SonPhung on Monday, 20-Jul-2026
|
||||
*/
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
|
||||
@@ -8,8 +8,7 @@ import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
/**
|
||||
* @author AnhDT
|
||||
* Created on 2024/01/08
|
||||
* Created by SonPhung on Monday, 20-Jul-2026
|
||||
*/
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
|
||||
@@ -9,8 +9,7 @@ import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
/**
|
||||
* @author AnhDT
|
||||
* Created on 2024/01/15
|
||||
* Created by SonPhung on Monday, 20-Jul-2026
|
||||
*/
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
|
||||
@@ -8,8 +8,7 @@ import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
/**
|
||||
* @author AnhDT
|
||||
* Created on 2024/01/02
|
||||
* Created by SonPhung on Monday, 20-Jul-2026
|
||||
*/
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
|
||||
@@ -8,8 +8,7 @@ import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
/**
|
||||
* @author AnhDT
|
||||
* Created on 2024/01/02
|
||||
* Created by SonPhung on Monday, 20-Jul-2026
|
||||
*/
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
|
||||
@@ -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,13 +14,28 @@ import lombok.RequiredArgsConstructor;
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public enum RuleType {
|
||||
@JsonPropertyDescription("Thưởng điểm")
|
||||
AWARD("AWD"),
|
||||
|
||||
@JsonPropertyDescription("Đổi điểm lấy quà")
|
||||
REDEEM("RED"),
|
||||
|
||||
@JsonPropertyDescription("Đổi quà đặc biệt")
|
||||
ITEM_REDEEM("IRED"),
|
||||
|
||||
@JsonPropertyDescription("Điều chỉnh điểm (tăng/giảm)")
|
||||
ADJUSTMENT("ADJ"),
|
||||
|
||||
@JsonPropertyDescription("Sự kiện trích xuất bộ đếm")
|
||||
COUNTER_EXTRACT("CEP"),
|
||||
|
||||
@JsonPropertyDescription("Đổi điểm trích xuất từ hệ thống khác")
|
||||
REDEEM_EXTRACT("REP"),
|
||||
|
||||
@JsonPropertyDescription("Giao dịch trích xuất (Tích lũy theo GD)")
|
||||
TRANSACTION_EXTRACT("TEP"),
|
||||
|
||||
@JsonPropertyDescription("Thưởng Marketing qua sự kiện/app")
|
||||
MARKETING_AWARD("MAWD");
|
||||
|
||||
@JsonValue
|
||||
|
||||
@@ -8,8 +8,7 @@ import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
/**
|
||||
* @author AnhDT
|
||||
* Created on 2024/01/09
|
||||
* Created by SonPhung on Monday, 20-Jul-2026
|
||||
*/
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
|
||||
@@ -8,8 +8,7 @@ import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
/**
|
||||
* @author AnhDT
|
||||
* Created on 2024/01/02
|
||||
* Created by SonPhung on Monday, 20-Jul-2026
|
||||
*/
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
|
||||
@@ -8,8 +8,7 @@ import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
/**
|
||||
* @author AnhDT
|
||||
* Created on 2024/01/02
|
||||
* Created by SonPhung on Monday, 20-Jul-2026
|
||||
*/
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
|
||||
@@ -8,8 +8,7 @@ import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
/**
|
||||
* @author AnhDT
|
||||
* Created on 2024/01/02
|
||||
* Created by SonPhung on Monday, 20-Jul-2026
|
||||
*/
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
|
||||
@@ -8,8 +8,7 @@ import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
/**
|
||||
* @author AnhDT
|
||||
* Created on 2024/01/02
|
||||
* Created by SonPhung on Monday, 20-Jul-2026
|
||||
*/
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
|
||||
@@ -7,12 +7,11 @@ import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* @author AnhDT
|
||||
* Created on 2023/12/20
|
||||
* Created by SonPhung on Monday, 20-Jul-2026
|
||||
*/
|
||||
@Setter
|
||||
@Getter
|
||||
public class AlertManagementDto extends FwDto {
|
||||
public class AlertManagement extends Fw {
|
||||
|
||||
@NotBlank
|
||||
@Size(min = 1, max = 10)
|
||||
@@ -5,9 +5,8 @@ import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* @author AnhDT
|
||||
* Created on 2024/01/18
|
||||
* Created by SonPhung on Monday, 20-Jul-2026
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class AmountToUseViewDto extends ComponentDto {}
|
||||
public class AmountToUseView extends Component {}
|
||||
@@ -0,0 +1,51 @@
|
||||
|
||||
package dev.sonpx.loyalty.mcp.reward.model;
|
||||
|
||||
import dev.sonpx.loyalty.mcp.reward.enums.CampaignType;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import java.time.LocalDate;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class Campaign extends Fw {
|
||||
|
||||
@NotBlank
|
||||
@Size(min = 1, max = 10)
|
||||
@JsonPropertyDescription("Mã chiến dịch (tối đa 10 ký tự, ví dụ: 'CMP01')")
|
||||
private String campaignId;
|
||||
|
||||
@Size(max = 50)
|
||||
@JsonPropertyDescription("Tên người hoặc phòng ban sở hữu chiến dịch")
|
||||
private String ownerName;
|
||||
|
||||
@NotBlank
|
||||
@Size(min = 1, max = 50)
|
||||
@JsonPropertyDescription("Tên hiển thị của chiến dịch")
|
||||
private String name;
|
||||
|
||||
@Size(max = 500)
|
||||
@JsonPropertyDescription("Mô tả chi tiết về chiến dịch")
|
||||
private String description;
|
||||
|
||||
@JsonPropertyDescription("Loại chiến dịch, xác định chiến dịch thuộc loại nào (ví dụ: POINT, VOUCHER)")
|
||||
private CampaignType campaignType;
|
||||
|
||||
@JsonPropertyDescription("Số lượng khách hàng mục tiêu dự kiến của chiến dịch")
|
||||
private Long noOfCustomerTarget;
|
||||
|
||||
@JsonPropertyDescription("Mục tiêu giá trị giao dịch trung bình (Target ATV)")
|
||||
private Double targetAtv;
|
||||
|
||||
@JsonPropertyDescription("Ngày bắt đầu có hiệu lực của chiến dịch")
|
||||
private LocalDate effectiveFrom;
|
||||
|
||||
@JsonPropertyDescription("Ngày kết thúc hiệu lực của chiến dịch")
|
||||
private LocalDate effectiveTo;
|
||||
|
||||
@JsonPropertyDescription("Số lượng quy tắc (rules) cấu hình trong chiến dịch")
|
||||
private Integer numOfRule;
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import lombok.Setter;
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class CampaignAwardLimitDto extends FwDto {
|
||||
public class CampaignAwardLimit extends Fw {
|
||||
|
||||
private AwardLimitType limitType;
|
||||
|
||||
@@ -12,7 +12,7 @@ import lombok.Setter;
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class CampaignContributorDto extends FwDto {
|
||||
public class CampaignContributor extends Fw {
|
||||
|
||||
private ReferenceData chainId;
|
||||
|
||||
@@ -10,7 +10,7 @@ import lombok.Setter;
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class CampaignCriteriaDto extends FwDto {
|
||||
public class CampaignCriteria extends Fw {
|
||||
|
||||
@NotBlank
|
||||
private String criteria;
|
||||
@@ -1,40 +0,0 @@
|
||||
|
||||
package dev.sonpx.loyalty.mcp.reward.model;
|
||||
|
||||
import dev.sonpx.loyalty.mcp.reward.enums.CampaignType;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import java.time.LocalDate;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class CampaignDto extends FwDto {
|
||||
|
||||
@NotBlank
|
||||
@Size(min = 1, max = 10)
|
||||
private String campaignId;
|
||||
|
||||
@Size(max = 50)
|
||||
private String ownerName;
|
||||
|
||||
@NotBlank
|
||||
@Size(min = 1, max = 50)
|
||||
private String name;
|
||||
|
||||
@Size(max = 500)
|
||||
private String description;
|
||||
|
||||
private CampaignType campaignType;
|
||||
|
||||
private Long noOfCustomerTarget;
|
||||
|
||||
private Double targetAtv;
|
||||
|
||||
private LocalDate effectiveFrom;
|
||||
|
||||
private LocalDate effectiveTo;
|
||||
|
||||
private Integer numOfRule;
|
||||
}
|
||||
@@ -14,7 +14,7 @@ import lombok.Setter;
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class CampaignFormulaEightDto extends FwDto {
|
||||
public class CampaignFormulaEight extends Fw {
|
||||
|
||||
private ReferenceData attrGroup;
|
||||
|
||||
@@ -13,7 +13,7 @@ import lombok.Setter;
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class CampaignFormulaElevenDto extends FwDto {
|
||||
public class CampaignFormulaEleven extends Fw {
|
||||
|
||||
@NotNull
|
||||
private ReferenceData segmentId;
|
||||
@@ -10,7 +10,7 @@ import lombok.Setter;
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class CampaignFormulaFiveDto extends FwDto {
|
||||
public class CampaignFormulaFive extends Fw {
|
||||
|
||||
@NotNull
|
||||
private Double multiply;
|
||||
@@ -15,7 +15,7 @@ import lombok.Setter;
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class CampaignFormulaFourDto extends FwDto {
|
||||
public class CampaignFormulaFour extends Fw {
|
||||
|
||||
private AwardFactorType factorType;
|
||||
|
||||
@@ -23,5 +23,5 @@ public class CampaignFormulaFourDto extends FwDto {
|
||||
|
||||
@Valid
|
||||
@NotEmpty
|
||||
private List<CampaignFormulaFourTierDto> tiers;
|
||||
private List<CampaignFormulaFourTier> tiers;
|
||||
}
|
||||
@@ -8,14 +8,14 @@ import jakarta.validation.constraints.Positive;
|
||||
import lombok.*;
|
||||
|
||||
/**
|
||||
* Created by AnhNt on Friday, 24-Nov-2023
|
||||
* Created by SonPhung on Monday, 20-Jul-2026
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class CampaignFormulaFourTierDto {
|
||||
public class CampaignFormulaFourTier {
|
||||
|
||||
@Min(1)
|
||||
private int tier;
|
||||
@@ -11,7 +11,7 @@ import lombok.Setter;
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class CampaignFormulaOneDto extends FwDto {
|
||||
public class CampaignFormulaOne extends Fw {
|
||||
|
||||
@Positive
|
||||
@DecimalMax(value = "99999999999999.99")
|
||||
@@ -12,7 +12,7 @@ import lombok.Setter;
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class CampaignFormulaSettingDto extends FwDto {
|
||||
public class CampaignFormulaSetting extends Fw {
|
||||
|
||||
private String ruleId;
|
||||
|
||||
@@ -14,7 +14,7 @@ import lombok.Setter;
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class CampaignFormulaSixDto extends FwDto {
|
||||
public class CampaignFormulaSix extends Fw {
|
||||
|
||||
@NotNull
|
||||
private AwardFactorType factorType;
|
||||
@@ -24,5 +24,5 @@ public class CampaignFormulaSixDto extends FwDto {
|
||||
|
||||
@Valid
|
||||
@NotEmpty
|
||||
private List<CampaignFormulaSixTierDto> tiers;
|
||||
private List<CampaignFormulaSixTier> tiers;
|
||||
}
|
||||
@@ -7,15 +7,14 @@ import jakarta.validation.constraints.NotNull;
|
||||
import lombok.*;
|
||||
|
||||
/**
|
||||
* @author AnhDT
|
||||
* Created on 2024/01/09
|
||||
* Created by SonPhung on Monday, 20-Jul-2026
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class CampaignFormulaSixTierDto {
|
||||
public class CampaignFormulaSixTier {
|
||||
|
||||
@Min(1)
|
||||
private int tier;
|
||||
@@ -14,7 +14,7 @@ import lombok.Setter;
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class CampaignFormulaTenDto extends FwDto {
|
||||
public class CampaignFormulaTen extends Fw {
|
||||
|
||||
@NotNull
|
||||
private AttributeData attributeId;
|
||||
@@ -10,7 +10,7 @@ import lombok.Setter;
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class CampaignFormulaTwoDto extends FwDto {
|
||||
public class CampaignFormulaTwo extends Fw {
|
||||
|
||||
@Positive
|
||||
private Double fixedAmt;
|
||||
@@ -0,0 +1,207 @@
|
||||
|
||||
package dev.sonpx.loyalty.mcp.reward.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
|
||||
import dev.sonpx.loyalty.mcp.enums.ExpiryPolicy;
|
||||
import dev.sonpx.loyalty.mcp.reward.enums.BaseDateType;
|
||||
import dev.sonpx.loyalty.mcp.reward.enums.NotificationChannel;
|
||||
import dev.sonpx.loyalty.mcp.reward.enums.RuleType;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* Created by SonPhung on Monday, 13-Mon-2023
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class CampaignRule extends Fw {
|
||||
|
||||
/**
|
||||
* General Rule Infomation
|
||||
*/
|
||||
@NotNull
|
||||
@JsonPropertyDescription("Tham chiếu đến Campaign chứa rule này")
|
||||
private ReferenceData campaignId;
|
||||
|
||||
@NotBlank
|
||||
@Size(min = 1, max = 10)
|
||||
@JsonPropertyDescription("Mã định danh của rule (tối đa 10 ký tự)")
|
||||
private String ruleId;
|
||||
|
||||
@NotBlank
|
||||
@Size(min = 1, max = 100)
|
||||
@JsonPropertyDescription("Tên của rule")
|
||||
private String ruleName;
|
||||
|
||||
@JsonPropertyDescription("Loại rule: RED (Đổi điểm), REP (Đổi quà), AWD (Thưởng), ADJ (Điều chỉnh), MAWD (Thưởng Marketing), CEP (Sự kiện)")
|
||||
private RuleType ruleType;
|
||||
|
||||
@Size(max = 500)
|
||||
@JsonPropertyDescription("Mô tả chi tiết về rule")
|
||||
private String description;
|
||||
|
||||
@JsonPropertyDescription("Thời gian bắt đầu có hiệu lực của rule")
|
||||
private LocalDateTime effectiveFrom;
|
||||
|
||||
@JsonPropertyDescription("Thời gian kết thúc hiệu lực của rule")
|
||||
private LocalDateTime effectiveTo;
|
||||
|
||||
@JsonPropertyDescription("Loại ngày làm cơ sở để tính toán hiệu lực (nếu có)")
|
||||
private BaseDateType effectivePeriodIsBase;
|
||||
|
||||
@JsonPropertyDescription("Mẫu tin nhắn áp dụng cho rule này")
|
||||
private ReferenceData messageTemplateId;
|
||||
|
||||
@JsonPropertyDescription("Có dừng xử lý các rule tiếp theo nếu criteria của rule này thỏa mãn không?")
|
||||
private Boolean stopIfCriteriaMet;
|
||||
|
||||
@JsonPropertyDescription("Không cập nhật số dư Pool?")
|
||||
private Boolean notUpdatePool;
|
||||
|
||||
@Valid
|
||||
@JsonPropertyDescription("Danh sách liên kết điều khoản (T&C)")
|
||||
private List<CampaignTcLinkage> campaignTcLinkages;
|
||||
|
||||
/**
|
||||
* Rule Criteria
|
||||
*/
|
||||
@Valid
|
||||
@JsonPropertyDescription("Điều kiện cấu hình chi tiết cho rule")
|
||||
private CampaignCriteria campaignCriteria;
|
||||
|
||||
/**
|
||||
* Reward Setting - AWD/RED
|
||||
*/
|
||||
@JsonPropertyDescription("Tham chiếu đến Pool chính")
|
||||
private ReferenceData poolId;
|
||||
|
||||
@JsonPropertyDescription("Tham chiếu đến Sub-Pool")
|
||||
private ReferenceData subPoolId;
|
||||
|
||||
@JsonPropertyDescription("Chính sách hết hạn của điểm/quà")
|
||||
private ExpiryPolicy expiryPolicy;
|
||||
|
||||
@JsonFormat(shape = JsonFormat.Shape.STRING)
|
||||
@JsonPropertyDescription("Tham số cho chính sách hết hạn (ví dụ: số tháng)")
|
||||
private Integer expiryPolicyParam;
|
||||
|
||||
@JsonPropertyDescription("Ngày hết hạn cố định (nếu có)")
|
||||
private LocalDate fixedDate;
|
||||
|
||||
@JsonFormat(shape = JsonFormat.Shape.STRING)
|
||||
@JsonPropertyDescription("Ngày trong tháng (dùng cho chính sách hết hạn)")
|
||||
private Integer dayOfMonth;
|
||||
|
||||
@JsonFormat(shape = JsonFormat.Shape.STRING)
|
||||
@JsonPropertyDescription("Tháng trong năm (dùng cho chính sách hết hạn)")
|
||||
private Integer monthOfYear;
|
||||
|
||||
@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;
|
||||
|
||||
@Valid
|
||||
@JsonPropertyDescription("Cấu hình công thức tính toán chung")
|
||||
private CampaignFormulaSetting campaignFormulaSetting;
|
||||
|
||||
@Valid
|
||||
@JsonPropertyDescription("Danh sách giới hạn phần thưởng")
|
||||
private List<CampaignAwardLimit> campaignAwardLimits;
|
||||
|
||||
@Valid
|
||||
@JsonPropertyDescription("Cấu hình Formula 1")
|
||||
private CampaignFormulaOne campaignFormulaOne;
|
||||
|
||||
@Valid
|
||||
@JsonPropertyDescription("Cấu hình Formula 2")
|
||||
private CampaignFormulaTwo campaignFormulaTwo;
|
||||
|
||||
@Valid
|
||||
@JsonPropertyDescription("Cấu hình Formula 4")
|
||||
private CampaignFormulaFour campaignFormulaFour;
|
||||
|
||||
@Valid
|
||||
@JsonPropertyDescription("Danh sách cấu hình Formula 5")
|
||||
private List<CampaignFormulaFive> campaignFormulaFives;
|
||||
|
||||
@Valid
|
||||
@JsonPropertyDescription("Cấu hình Formula 6")
|
||||
private CampaignFormulaSix campaignFormulaSix;
|
||||
|
||||
@Valid
|
||||
@JsonPropertyDescription("Danh sách cấu hình Formula 8")
|
||||
private List<CampaignFormulaEight> campaignFormulaEights;
|
||||
|
||||
@Valid
|
||||
@JsonPropertyDescription("Danh sách cấu hình Formula 10")
|
||||
private List<CampaignFormulaTen> campaignFormulaTens;
|
||||
|
||||
@Valid
|
||||
@JsonPropertyDescription("Danh sách cấu hình Formula 11")
|
||||
private List<CampaignFormulaEleven> campaignFormulaElevens;
|
||||
|
||||
@Valid
|
||||
@JsonPropertyDescription("Danh sách các Contributor cho Campaign")
|
||||
private List<CampaignContributor> campaignContributors;
|
||||
|
||||
@JsonPropertyDescription("Thứ tự ưu tiên áp dụng các Formula")
|
||||
private List<String> formulaSequence;
|
||||
|
||||
/**
|
||||
* Redemption Extract Rule Type
|
||||
*/
|
||||
@JsonPropertyDescription("Cấu hình cho loại rule Redemption Extract")
|
||||
private RedemptionExtractRequest redemptionExtract;
|
||||
|
||||
/**
|
||||
* Counter Extract Rule Type
|
||||
*/
|
||||
@Valid
|
||||
@JsonPropertyDescription("Cấu hình cho loại rule Counter Extract")
|
||||
private CounterExtractRequest counterExtractRequest;
|
||||
|
||||
/**
|
||||
* Marketing Reward Rule Type
|
||||
*/
|
||||
@Valid
|
||||
@JsonPropertyDescription("Cấu hình cho loại rule Marketing Reward")
|
||||
private MarketingRewardRequest marketingRewardRequest;
|
||||
|
||||
@Valid
|
||||
@JsonPropertyDescription("Cấu hình lịch trình chạy cho rule")
|
||||
private CampaignRuleSchedule campaignRuleSchedule;
|
||||
|
||||
/**
|
||||
* Notification Info
|
||||
*/
|
||||
@JsonPropertyDescription("Kênh gửi thông báo (ví dụ: SMS, EMAIL, NOTIFICATION)")
|
||||
private NotificationChannel channel;
|
||||
|
||||
@Valid
|
||||
@JsonPropertyDescription("Nội dung thông báo qua SMS")
|
||||
private RewardContentSms rewardContentSms;
|
||||
|
||||
@Valid
|
||||
@JsonPropertyDescription("Nội dung thông báo qua Email")
|
||||
private RewardContentEmail rewardContentEmail;
|
||||
|
||||
@Valid
|
||||
@JsonPropertyDescription("Nội dung thông báo In-App Notification")
|
||||
private RewardContentNotification rewardContentNotification;
|
||||
|
||||
}
|
||||
@@ -7,13 +7,12 @@ import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* @author AnhDT
|
||||
* Created on 2024/03/15
|
||||
* Created by SonPhung on Monday, 20-Jul-2026
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
public class CampaignRuleByPoolDto {
|
||||
public class CampaignRuleByPool {
|
||||
|
||||
private String poolId;
|
||||
|
||||
@@ -6,7 +6,7 @@ import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class CampaignRuleByTxnCodeDto {
|
||||
public class CampaignRuleByTxnCode {
|
||||
|
||||
private String ruleId;
|
||||
|
||||
@@ -1,163 +0,0 @@
|
||||
|
||||
package dev.sonpx.loyalty.mcp.reward.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import dev.sonpx.loyalty.mcp.enums.ExpiryPolicy;
|
||||
import dev.sonpx.loyalty.mcp.reward.enums.BaseDateType;
|
||||
import dev.sonpx.loyalty.mcp.reward.enums.NotificationChannel;
|
||||
import dev.sonpx.loyalty.mcp.reward.enums.RuleType;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* Created by SonPhung on Monday, 13-Mon-2023
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class CampaignRuleDto extends FwDto {
|
||||
|
||||
/**
|
||||
* General Rule Infomation
|
||||
*/
|
||||
@NotNull
|
||||
private ReferenceData campaignId;
|
||||
|
||||
@NotBlank
|
||||
@Size(min = 1, max = 10)
|
||||
private String ruleId;
|
||||
|
||||
@NotBlank
|
||||
@Size(min = 1, max = 100)
|
||||
private String ruleName;
|
||||
|
||||
private RuleType ruleType;
|
||||
|
||||
@Size(max = 500)
|
||||
private String description;
|
||||
|
||||
private LocalDateTime effectiveFrom;
|
||||
|
||||
private LocalDateTime effectiveTo;
|
||||
|
||||
private BaseDateType effectivePeriodIsBase;
|
||||
|
||||
private ReferenceData messageTemplateId;
|
||||
|
||||
private Boolean stopIfCriteriaMet;
|
||||
|
||||
private Boolean notUpdatePool;
|
||||
|
||||
@Valid
|
||||
private List<CampaignTcLinkageDto> campaignTcLinkages;
|
||||
|
||||
/**
|
||||
* Rule Criteria
|
||||
*/
|
||||
@Valid
|
||||
private CampaignCriteriaDto campaignCriteria;
|
||||
|
||||
/**
|
||||
* Reward Setting - AWD/RED
|
||||
*/
|
||||
private ReferenceData poolId;
|
||||
|
||||
private ReferenceData subPoolId;
|
||||
|
||||
private ExpiryPolicy expiryPolicy;
|
||||
|
||||
@JsonFormat(shape = JsonFormat.Shape.STRING)
|
||||
private Integer expiryPolicyParam;
|
||||
|
||||
private LocalDate fixedDate;
|
||||
|
||||
@JsonFormat(shape = JsonFormat.Shape.STRING)
|
||||
private Integer dayOfMonth;
|
||||
|
||||
@JsonFormat(shape = JsonFormat.Shape.STRING)
|
||||
private Integer monthOfYear;
|
||||
|
||||
private ReferenceData itemCode;
|
||||
|
||||
// Aplly the first criterion satified in Formula 10
|
||||
private Boolean attrApplyFirstFlag;
|
||||
|
||||
// Aplly the first criterion satified in Formula 11
|
||||
private Boolean segApplyFirstFlag;
|
||||
|
||||
@Valid
|
||||
private CampaignFormulaSettingDto campaignFormulaSetting;
|
||||
|
||||
@Valid
|
||||
private List<CampaignAwardLimitDto> campaignAwardLimits;
|
||||
|
||||
@Valid
|
||||
private CampaignFormulaOneDto campaignFormulaOne;
|
||||
|
||||
@Valid
|
||||
private CampaignFormulaTwoDto campaignFormulaTwo;
|
||||
|
||||
@Valid
|
||||
private CampaignFormulaFourDto campaignFormulaFour;
|
||||
|
||||
@Valid
|
||||
private List<CampaignFormulaFiveDto> campaignFormulaFives;
|
||||
|
||||
@Valid
|
||||
private CampaignFormulaSixDto campaignFormulaSix;
|
||||
|
||||
@Valid
|
||||
private List<CampaignFormulaEightDto> campaignFormulaEights;
|
||||
|
||||
@Valid
|
||||
private List<CampaignFormulaTenDto> campaignFormulaTens;
|
||||
|
||||
@Valid
|
||||
private List<CampaignFormulaElevenDto> campaignFormulaElevens;
|
||||
|
||||
@Valid
|
||||
private List<CampaignContributorDto> campaignContributors;
|
||||
|
||||
private List<String> formulaSequence;
|
||||
|
||||
/**
|
||||
* Redemption Extract Rule Type
|
||||
*/
|
||||
private RedemptionExtractRequestDto redemptionExtract;
|
||||
|
||||
/**
|
||||
* Counter Extract Rule Type
|
||||
*/
|
||||
@Valid
|
||||
private CounterExtractRequestDto counterExtractRequest;
|
||||
|
||||
/**
|
||||
* Marketing Reward Rule Type
|
||||
*/
|
||||
@Valid
|
||||
private MarketingRewardRequestDto marketingRewardRequest;
|
||||
|
||||
@Valid
|
||||
private CampaignRuleScheduleDto campaignRuleSchedule;
|
||||
|
||||
/**
|
||||
* Notification Info
|
||||
*/
|
||||
private NotificationChannel channel;
|
||||
|
||||
@Valid
|
||||
private RewardContentSmsDto rewardContentSms;
|
||||
|
||||
@Valid
|
||||
private RewardContentEmailDto rewardContentEmail;
|
||||
|
||||
@Valid
|
||||
private RewardContentNotificationDto rewardContentNotification;
|
||||
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class CampaignRuleScheduleDto extends FwDto {
|
||||
public class CampaignRuleSchedule extends Fw {
|
||||
|
||||
private String ruleId;
|
||||
|
||||
@@ -10,7 +10,7 @@ import lombok.Setter;
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class CampaignTcLinkageDto extends FwDto {
|
||||
public class CampaignTcLinkage extends Fw {
|
||||
|
||||
@NotNull
|
||||
private ReferenceData transactionCode;
|
||||
@@ -5,12 +5,11 @@ import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* @author AnhDT
|
||||
* Created on 2025/10/28
|
||||
* Created by SonPhung on Monday, 20-Jul-2026
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class ComponentDto {
|
||||
public class Component {
|
||||
|
||||
protected String key;
|
||||
|
||||
@@ -6,7 +6,7 @@ import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class CounterCriteriaDto {
|
||||
public class CounterCriteria {
|
||||
|
||||
private String key;
|
||||
|
||||
@@ -18,12 +18,11 @@ import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* @author AnhDT
|
||||
* Created on 2023/10/31
|
||||
* Created by SonPhung on Monday, 20-Jul-2026
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class CounterDefinitionDto extends FwDto {
|
||||
public class CounterDefinition extends Fw {
|
||||
|
||||
@NotBlank
|
||||
@Size(max = 10)
|
||||
@@ -15,7 +15,7 @@ import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class CounterExtractRequestDto extends FwDto {
|
||||
public class CounterExtractRequest extends Fw {
|
||||
|
||||
@NotNull
|
||||
private ReferenceData store;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user