refactor: restructure MCP server by replacing client-specific tool logic with a centralized campaign service and standardized models.

This commit is contained in:
2026-07-20 18:34:11 +07:00
parent 2de63d0bad
commit 00e26cedcf
155 changed files with 1039 additions and 2273 deletions

View File

@@ -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);
}

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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) {
}

View File

@@ -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);
}
}

View File

@@ -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) {
}

View File

@@ -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) {
}

View File

@@ -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) {
}

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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 + "'");
}
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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();
}

View File

@@ -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();
}
}

View File

@@ -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 {

View File

@@ -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");
}
}
}

View File

@@ -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()));
}
}

View File

@@ -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<>();
}

View File

@@ -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();
}
}

View File

@@ -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<>();
}

View File

@@ -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

View File

@@ -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

View File

@@ -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,

View File

@@ -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,

View File

@@ -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},

View File

@@ -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> {}

View File

@@ -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> {}

View File

@@ -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> {}

View File

@@ -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> {}

View File

@@ -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> {}

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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
}

View File

@@ -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) {

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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 {}

View File

@@ -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;
}

View File

@@ -12,7 +12,7 @@ import lombok.Setter;
*/
@Getter
@Setter
public class CampaignAwardLimitDto extends FwDto {
public class CampaignAwardLimit extends Fw {
private AwardLimitType limitType;

View File

@@ -12,7 +12,7 @@ import lombok.Setter;
*/
@Getter
@Setter
public class CampaignContributorDto extends FwDto {
public class CampaignContributor extends Fw {
private ReferenceData chainId;

View File

@@ -10,7 +10,7 @@ import lombok.Setter;
*/
@Getter
@Setter
public class CampaignCriteriaDto extends FwDto {
public class CampaignCriteria extends Fw {
@NotBlank
private String criteria;

View File

@@ -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;
}

View File

@@ -14,7 +14,7 @@ import lombok.Setter;
*/
@Getter
@Setter
public class CampaignFormulaEightDto extends FwDto {
public class CampaignFormulaEight extends Fw {
private ReferenceData attrGroup;

View File

@@ -13,7 +13,7 @@ import lombok.Setter;
*/
@Getter
@Setter
public class CampaignFormulaElevenDto extends FwDto {
public class CampaignFormulaEleven extends Fw {
@NotNull
private ReferenceData segmentId;

View File

@@ -10,7 +10,7 @@ import lombok.Setter;
*/
@Getter
@Setter
public class CampaignFormulaFiveDto extends FwDto {
public class CampaignFormulaFive extends Fw {
@NotNull
private Double multiply;

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -11,7 +11,7 @@ import lombok.Setter;
*/
@Getter
@Setter
public class CampaignFormulaOneDto extends FwDto {
public class CampaignFormulaOne extends Fw {
@Positive
@DecimalMax(value = "99999999999999.99")

View File

@@ -12,7 +12,7 @@ import lombok.Setter;
*/
@Getter
@Setter
public class CampaignFormulaSettingDto extends FwDto {
public class CampaignFormulaSetting extends Fw {
private String ruleId;

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -14,7 +14,7 @@ import lombok.Setter;
*/
@Getter
@Setter
public class CampaignFormulaTenDto extends FwDto {
public class CampaignFormulaTen extends Fw {
@NotNull
private AttributeData attributeId;

View File

@@ -10,7 +10,7 @@ import lombok.Setter;
*/
@Getter
@Setter
public class CampaignFormulaTwoDto extends FwDto {
public class CampaignFormulaTwo extends Fw {
@Positive
private Double fixedAmt;

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -6,7 +6,7 @@ import lombok.Setter;
@Getter
@Setter
public class CampaignRuleByTxnCodeDto {
public class CampaignRuleByTxnCode {
private String ruleId;

View File

@@ -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;
}

View File

@@ -12,7 +12,7 @@ import lombok.Setter;
@Getter
@Setter
public class CampaignRuleScheduleDto extends FwDto {
public class CampaignRuleSchedule extends Fw {
private String ruleId;

View File

@@ -10,7 +10,7 @@ import lombok.Setter;
*/
@Getter
@Setter
public class CampaignTcLinkageDto extends FwDto {
public class CampaignTcLinkage extends Fw {
@NotNull
private ReferenceData transactionCode;

View File

@@ -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;

View File

@@ -6,7 +6,7 @@ import lombok.Setter;
@Getter
@Setter
public class CounterCriteriaDto {
public class CounterCriteria {
private String key;

View File

@@ -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)

View File

@@ -15,7 +15,7 @@ import lombok.Setter;
@Getter
@Setter
public class CounterExtractRequestDto extends FwDto {
public class CounterExtractRequest extends Fw {
@NotNull
private ReferenceData store;

View File

@@ -6,12 +6,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 CounterStockDto extends FwDto {
public class CounterStock extends Fw {
private String counterId;

View File

@@ -8,12 +8,11 @@ import lombok.Getter;
import lombok.Setter;
/**
* @author AnhDT
* Created on 2023/12/12
* Created by SonPhung on Monday, 20-Jul-2026
*/
@Setter
@Getter
public class CurrencyRateDto extends FwDto {
public class CurrencyRate extends Fw {
@NotNull
private ReferenceData pcrCode;

View File

@@ -6,4 +6,4 @@ import lombok.Setter;
@Getter
@Setter
public class DecideTierViewDto extends ComponentDto {}
public class DecideTierView extends Component {}

View File

@@ -10,7 +10,7 @@ import lombok.Setter;
@Getter
@Setter
public class DeductionSequencesDto extends FwDto {
public class DeductionSequences extends Fw {
@NotBlank
@Size(min = 1, max = 50)
private String sequenceId;

Some files were not shown because too many files have changed in this diff Show More