feat: initialize loyalty-mcp-server module with foundational DTOs, criteria, enums, and MCP tool infrastructure

This commit is contained in:
SonPhung
2026-07-19 23:54:26 +07:00
parent cda93365ca
commit 2de63d0bad
178 changed files with 5707 additions and 73 deletions

View File

@@ -3,7 +3,11 @@ package dev.sonpx.loyalty.mcp;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@SpringBootApplication(excludeName = {
"org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration",
"org.springframework.boot.hibernate.autoconfigure.HibernateJpaAutoConfiguration",
"org.springframework.boot.devtools.autoconfigure.DevToolsDataSourceAutoConfiguration"
})
public class McpServerApplication {
public static void main(String[] args) {

View File

@@ -1,9 +1,8 @@
package dev.sonpx.loyalty.mcp.client.api;
import dev.sonpx.loyalty.mcp.client.model.MemberTier;
import org.springframework.web.service.annotation.GetExchange;
import java.util.List;
import org.springframework.web.service.annotation.GetExchange;
public interface MemberTierApiClient {

View File

@@ -1,9 +1,8 @@
package dev.sonpx.loyalty.mcp.client.api;
import dev.sonpx.loyalty.mcp.client.model.PointPool;
import org.springframework.web.service.annotation.GetExchange;
import java.util.List;
import org.springframework.web.service.annotation.GetExchange;
public interface PointPoolApiClient {

View File

@@ -1,7 +1,6 @@
package dev.sonpx.loyalty.mcp.client.model;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import java.math.BigDecimal;
@JsonIgnoreProperties(ignoreUnknown = true)

View File

@@ -2,10 +2,9 @@ package dev.sonpx.loyalty.mcp.client.model.reward;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import java.util.LinkedHashMap;
import java.util.Map;
import lombok.Data;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)

View File

@@ -2,10 +2,9 @@ package dev.sonpx.loyalty.mcp.client.model.reward;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonValue;
import lombok.Data;
import java.time.OffsetDateTime;
import java.util.List;
import lombok.Data;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)

View File

@@ -3,7 +3,6 @@ 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;
@@ -28,7 +27,6 @@ public class ApiClientConfig {
private String coreBaseUrl;
@Bean
public RestClient loyaltyRestClient(OAuth2AuthorizedClientManager authorizedClientManager) {
return RestClient.builder()

View File

@@ -0,0 +1,17 @@
package dev.sonpx.loyalty.mcp.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Created by SonPhung on Sunday, 19-Jul-2026
**/
@Configuration
public class AppConfig {
@Bean
public ObjectMapper objectMapper() {
return new ObjectMapper();
}
}

View File

@@ -4,11 +4,10 @@ import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
@Component
public class AsyncTimeoutFilter extends OncePerRequestFilter {

View File

@@ -0,0 +1,57 @@
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 dev.sonpx.loyalty.mcp.reward.client.RewardClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.web.client.OAuth2ClientHttpRequestInterceptor;
import org.springframework.security.oauth2.client.web.client.RequestAttributeClientRegistrationIdResolver;
import org.springframework.security.oauth2.client.web.client.RequestAttributePrincipalResolver;
import org.springframework.web.client.RestClient;
import org.springframework.web.client.support.RestClientAdapter;
import org.springframework.web.service.invoker.HttpServiceProxyFactory;
/**
* Created by SonPhung on Sunday, 19-Jul-2026
**/
@Configuration
public class ClientConfig {
@Value("${loyalty.core.base-url}")
private String coreBaseUrl;
@Bean
public RestClient rewardReClient(OAuth2AuthorizedClientManager authorizedClientManager) {
OAuth2ClientHttpRequestInterceptor oauth2Interceptor =
new OAuth2ClientHttpRequestInterceptor(authorizedClientManager);
oauth2Interceptor.setClientRegistrationIdResolver(new RequestAttributeClientRegistrationIdResolver());
oauth2Interceptor.setPrincipalResolver(new RequestAttributePrincipalResolver());
return RestClient.builder()
.baseUrl(coreBaseUrl + "/svc/reward")
.requestInterceptor((request, body, execution) -> {
clientRegistrationId("keycloak").accept(request.getAttributes());
principal("loyalty-service").accept(request.getAttributes());
return oauth2Interceptor.intercept(request, body, execution);
})
.build();
}
@Bean
public HttpServiceProxyFactory httpServiceProxyFactory(RestClient rewardReClient) {
return HttpServiceProxyFactory.builderFor(RestClientAdapter.create(rewardReClient))
.customArgumentResolver(new PageableArgumentResolver())
.customArgumentResolver(new PojoArgumentResolver())
.build();
}
@Bean
public RewardClient rewardClient(HttpServiceProxyFactory httpServiceProxyFactory) {
return httpServiceProxyFactory.createClient(RewardClient.class);
}
}

View File

@@ -3,7 +3,6 @@ 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;

View File

@@ -0,0 +1,31 @@
package dev.sonpx.loyalty.mcp.config;
import org.springframework.core.MethodParameter;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.web.service.invoker.HttpRequestValues;
import org.springframework.web.service.invoker.HttpServiceArgumentResolver;
public class PageableArgumentResolver implements HttpServiceArgumentResolver {
@Override
public boolean resolve(Object argument, MethodParameter parameter, HttpRequestValues.Builder requestValues) {
if (!Pageable.class.isAssignableFrom(parameter.getParameterType())) {
return false;
}
if (argument instanceof Pageable pageable) {
if (pageable.isPaged()) {
requestValues.addRequestParameter("page", String.valueOf(pageable.getPageNumber()));
requestValues.addRequestParameter("size", String.valueOf(pageable.getPageSize()));
}
Sort sort = pageable.getSort();
if (sort.isSorted()) {
for (Sort.Order order : sort) {
requestValues.addRequestParameter("sort", order.getProperty() + "," + order.getDirection().name());
}
}
}
return true;
}
}

View File

@@ -0,0 +1,66 @@
package dev.sonpx.loyalty.mcp.config;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.service.invoker.HttpRequestValues;
import org.springframework.web.service.invoker.HttpServiceArgumentResolver;
import org.springframework.data.domain.Pageable;
import java.util.Map;
public class PojoArgumentResolver implements HttpServiceArgumentResolver {
private final ObjectMapper objectMapper;
public PojoArgumentResolver() {
this.objectMapper = new ObjectMapper();
}
@Override
public boolean resolve(Object argument, MethodParameter parameter, HttpRequestValues.Builder requestValues) {
// Skip if parameter is annotated with typical HTTP interface annotations
if (parameter.hasParameterAnnotation(RequestParam.class) ||
parameter.hasParameterAnnotation(RequestBody.class) ||
parameter.hasParameterAnnotation(PathVariable.class)) {
return false;
}
// Skip basic types and Pageable
if (parameter.getParameterType().isPrimitive() ||
parameter.getParameterType().getName().startsWith("java.lang") ||
Pageable.class.isAssignableFrom(parameter.getParameterType())) {
return false;
}
if (argument == null) {
return true;
}
Map<String, Object> map = objectMapper.convertValue(argument, new TypeReference<>() {});
flattenMap("", map, requestValues);
return true;
}
private void flattenMap(String prefix, Map<String, Object> map, HttpRequestValues.Builder requestValues) {
for (Map.Entry<String, Object> entry : map.entrySet()) {
String key = prefix.isEmpty() ? entry.getKey() : prefix + "." + entry.getKey();
Object value = entry.getValue();
if (value instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, Object> nestedMap = (Map<String, Object>) value;
flattenMap(key, nestedMap, requestValues);
} else if (value instanceof Iterable<?> iterable) {
for (Object item : iterable) {
if (item != null) {
requestValues.addRequestParameter(key, String.valueOf(item));
}
}
} else if (value != null) {
requestValues.addRequestParameter(key, String.valueOf(value));
}
}
}
}

View File

@@ -4,11 +4,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.oauth2.client.AuthorizedClientServiceOAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProviderBuilder;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.*;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.web.SecurityFilterChain;

View File

@@ -0,0 +1,38 @@
package dev.sonpx.loyalty.mcp.criteria;
import dev.sonpx.loyalty.mcp.filter.RangeFilter;
import dev.sonpx.loyalty.mcp.filter.StringFilter;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import lombok.Data;
/**
* @author AnhDT Created on 2025/05/23
*/
@Data
public abstract class AbstractFwCriteria implements FwCriteria {
protected StringFilter status;
protected RangeFilter<Instant> lastUpdateDate;
// for search text
protected String search;
protected String searchComponent;
//Handle URL-encoded search text (e.g. "%20", "%23") received from UI
//e.g: search=tìm%20kiếm -> tìm kiếm
public void setSearch(String search) {
try {
this.search = search == null ? null : URLDecoder.decode(search, StandardCharsets.UTF_8);
} catch (IllegalArgumentException e) {
this.search = search;
}
}
protected abstract String[] getColumns();
protected abstract String[] getComponentColumns();
}

View File

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

View File

@@ -1,13 +1,12 @@
package dev.sonpx.loyalty.mcp.draft;
import dev.sonpx.loyalty.mcp.draft.model.DraftSession;
import org.springframework.stereotype.Component;
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 {

View File

@@ -2,10 +2,9 @@ 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 lombok.Data;
import java.util.ArrayList;
import java.util.List;
import lombok.Data;
@Data
public class CampaignDraft {

View File

@@ -1,8 +1,7 @@
package dev.sonpx.loyalty.mcp.draft.model;
import lombok.Data;
import java.time.Instant;
import lombok.Data;
@Data
public class DraftSession {

View File

@@ -1,8 +1,8 @@
package dev.sonpx.loyalty.mcp.draft.model;
import lombok.Data;
import java.util.HashMap;
import java.util.Map;
import lombok.Data;
@Data
public class PoolDraft {

View File

@@ -0,0 +1,25 @@
package dev.sonpx.loyalty.mcp.enums;
import java.util.Arrays;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public enum AttributeDataType {
STRING("STRING"),
NUMBER("NUMBER"),
DATE("DATE"),
BOOLEAN("BOOLEAN"),
TIME("TIME");
private final String code;
public static AttributeDataType fromCode(String code) {
return Arrays.stream(AttributeDataType.values())
.filter(o -> code.equals(o.getCode()))
.findFirst()
.orElse(null);
}
}

View File

@@ -0,0 +1,37 @@
package dev.sonpx.loyalty.mcp.enums;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import java.util.Arrays;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
* @author Phudao
* Created on 2024/01/12
*/
@Getter
@RequiredArgsConstructor
public enum AttributeEntityType {
CLIENT("CLIENT"),
PRODUCT_ACCOUNT("PRODUCT_ACCOUNT"),
CARD("CARD"),
CORPORATION("CORPORATION"),
CHAIN("CHAIN"),
STORE("STORE"),
// TRANSACTIONS("TRANSACTIONS"),
CAMPAIGN("CAMPAIGN"),
ITEM("ITEM");
@JsonValue
private final String code;
@JsonCreator
public static AttributeEntityType fromCode(String code) {
return Arrays.stream(values())
.filter(e -> e.code.equalsIgnoreCase(code))
.findFirst()
.orElse(null);
}
}

View File

@@ -0,0 +1,27 @@
package dev.sonpx.loyalty.mcp.enums;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import java.util.Arrays;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public enum AttributeType {
CUSTOM("CUSTOM"),
DYNAMIC("DYNAMIC"),
STATIC("STATIC");
@JsonValue
private final String code;
@JsonCreator
public static AttributeType fromCode(String code) {
return Arrays.stream(values())
.filter(e -> e.code.equalsIgnoreCase(code))
.findFirst()
.orElse(null);
}
}

View File

@@ -0,0 +1,43 @@
package dev.sonpx.loyalty.mcp.enums;
import java.lang.reflect.Method;
import java.util.Optional;
public final class CodeEnumLookup {
private CodeEnumLookup() {}
public static <T extends Enum> T resolve(String source, Class<T> targetType) {
if (source == null || source.isBlank()) {
return null;
}
String normalized = source.trim();
for (T constant : targetType.getEnumConstants()) {
if (constant.name().equalsIgnoreCase(normalized)
|| codeOf(constant).map(code -> code.equalsIgnoreCase(normalized)).orElse(false)) {
return constant;
}
}
throw new IllegalArgumentException("Unsupported " + targetType.getSimpleName() + " value: " + source);
}
public static String codeOfEnum(Enum<?> constant) {
return codeOf(constant).orElseGet(constant::name);
}
private static Optional<String> codeOf(Object constant) {
if (constant instanceof EnumBase enumBase) {
return Optional.ofNullable(enumBase.getCode());
}
try {
Method getCode = constant.getClass().getMethod("getCode");
Object code = getCode.invoke(constant);
return code instanceof String value ? Optional.of(value) : Optional.empty();
} catch (ReflectiveOperationException ex) {
return Optional.empty();
}
}
}

View File

@@ -0,0 +1,27 @@
package dev.sonpx.loyalty.mcp.enums;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import java.util.Arrays;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public enum CounterBucket {
CURRENT_BUCKET("CB"),
PREVIOUS_BUCKET("PB"),
PERIOD_BEFORE_LAST("PBL");
@JsonValue
private final String code;
@JsonCreator
public static CounterBucket fromCode(String code) {
return Arrays.stream(values())
.filter(e -> e.code.equalsIgnoreCase(code))
.findFirst()
.orElse(null);
}
}

View File

@@ -0,0 +1,29 @@
package dev.sonpx.loyalty.mcp.enums;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import java.util.Arrays;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public enum CounterEntity {
CUSTOMER("CU"),
ACCOUNT("AC"),
CARD("CA"),
MERCHANT("ME"),
SYSTEM("SY");
@JsonValue
private final String code;
@JsonCreator
public static CounterEntity fromCode(String code) {
return Arrays.stream(values())
.filter(e -> e.code.equalsIgnoreCase(code))
.findFirst()
.orElse(null);
}
}

View File

@@ -0,0 +1,42 @@
package dev.sonpx.loyalty.mcp.enums;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import java.util.Arrays;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
* Created by SonPhung on Wednesday, 20-Dec-2023
*/
@Getter
@RequiredArgsConstructor
public enum CounterPeriod {
AOD_ANNIVERSARY("AA"),
DAY_COUNTER("DC"),
DAYS_FROM_AOD("DFA"),
DAYS_FROM_COD("DFC"),
FIXED_DATE("FD"),
HALF_YEAR("HY"),
MONTHS_FROM_AOD("MFA"),
MONTHS_COUNTER("MC"),
NON_EXPIRE("NE"),
QUARTER_COUNTER("QC"),
QUARTER_FROM_AOD("QFA"),
WEEK_COUNTER("WC"),
YEAR_COUNTER("YC"),
STATEMENT_CYCLE("SC");
@JsonValue
private final String code;
@JsonCreator
public static CounterPeriod fromCode(String code) {
return Arrays.stream(values())
.filter(e -> e.code.equalsIgnoreCase(code))
.findFirst()
.orElse(null);
}
}

View File

@@ -0,0 +1,44 @@
package dev.sonpx.loyalty.mcp.enums;
import java.util.Arrays;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
* @author AnhDT Created on 2025/11/17
*/
@Getter
@RequiredArgsConstructor
public enum DerivedType {
STRING("STRING", AttributeDataType.STRING),
INT("INT", AttributeDataType.NUMBER),
DECIMAL("DECIMAL", AttributeDataType.NUMBER),
DATE("DATE", AttributeDataType.DATE),
TIME("TIME", AttributeDataType.TIME),
BOOLEAN("BOOLEAN", AttributeDataType.BOOLEAN),
SEGMENT("SEGMENT", null),
AUDIENCE("AUDIENCE", null);
private final String code;
private final AttributeDataType baseType;
public static DerivedType fromCode(String code) {
return Arrays.stream(DerivedType.values())
.filter(o -> code.equals(o.getCode()))
.findFirst()
.orElse(null);
}
public static DerivedType fromBaseType(AttributeDataType baseType) {
return Arrays.stream(DerivedType.values())
.filter(o -> baseType.equals(o.getBaseType()))
.findFirst()
.orElse(null);
}
public static boolean isAudienceType(String code) {
return DerivedType.AUDIENCE.code.equalsIgnoreCase(code) || DerivedType.SEGMENT.code.equalsIgnoreCase(code);
}
}

View File

@@ -0,0 +1,31 @@
package dev.sonpx.loyalty.mcp.enums;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import java.util.Arrays;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
* Created by SonPhung on Wednesday, 20-Dec-2023
*/
@Getter
@RequiredArgsConstructor
public enum EntityLevel {
CUSTOMER("CU"),
ACCOUNT("AC"),
CARD("CA"),
LOYALTY_ACCOUNT("LA");
@JsonValue
private final String code;
@JsonCreator
public static EntityLevel fromCode(String code) {
return Arrays.stream(values())
.filter(e -> e.code.equalsIgnoreCase(code))
.findFirst()
.orElse(null);
}
}

View File

@@ -0,0 +1,6 @@
package dev.sonpx.loyalty.mcp.enums;
public interface EnumBase {
String getCode();
}

View File

@@ -0,0 +1,32 @@
package dev.sonpx.loyalty.mcp.enums;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import java.util.Arrays;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public enum ExpiryPolicy {
FIXED_DATE("FD"),
NO_EXPIRY("NE"),
ANNUAL_RECURRING_DATE("ARD"),
N_MONTHS_FROM_MONTH_OF_EARNING("MF"),
N_QUARTERS_FROM_QUARTER_OF_EARNING("QF"),
N_YEARS_FROM_YEAR_OF_EARNING("YF"),
SEMI_ANNUAL_OR_MID_AND_END_YEAR("SP"),
ANNIVERSARY_OF_MEMBERSHIP("AOM");
@JsonValue
private final String code;
@JsonCreator
public static ExpiryPolicy fromCode(String code) {
return Arrays.stream(values())
.filter(e -> e.code.equalsIgnoreCase(code))
.findFirst()
.orElse(null);
}
}

View File

@@ -0,0 +1,26 @@
package dev.sonpx.loyalty.mcp.enums;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import java.util.Arrays;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public enum FileStoreType {
S3("s3"), // For S3 or S3-compatible storage
FILE("file"); // For local file system storage
@JsonValue
private final String code;
@JsonCreator
public static FileStoreType fromCode(String code) {
return Arrays.stream(values())
.filter(e -> e.code.equalsIgnoreCase(code))
.findFirst()
.orElse(null);
}
}

View File

@@ -0,0 +1,11 @@
package dev.sonpx.loyalty.mcp.enums;
/**
* Created by SonPhung on Tuesday, 08-Apr-2025
*/
public interface IErrorCode {
String getCode();
String name();
}

View File

@@ -0,0 +1,14 @@
package dev.sonpx.loyalty.mcp.enums;
/**
* Created by SonPhung on Monday, 27-Nov-2023
*/
public enum IdentifierType {
MOBILE,
CARD,
EMAIL,
CIF,
PAID,
PSN,
}

View File

@@ -0,0 +1,27 @@
package dev.sonpx.loyalty.mcp.enums;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import java.util.Arrays;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Getter
public enum ImageSourceType {
UPLOAD("UPLOAD"),
URL("URL");
@JsonValue
private final String code;
@JsonCreator
public static ImageSourceType fromCode(String code) {
return Arrays.stream(values())
.filter(e -> e.code.equalsIgnoreCase(code))
.findFirst()
.orElse(null);
}
}

View File

@@ -0,0 +1,38 @@
package dev.sonpx.loyalty.mcp.enums;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Arrays;
import java.util.Date;
import java.util.Optional;
import lombok.Getter;
@Getter
public enum JobParamDataType {
STRING(String.class),
LONG(Long.class),
INTEGER(Integer.class),
DOUBLE(Double.class),
FLOAT(Float.class),
DATE(Date.class),
LOCAL_DATE(LocalDate.class), // yyyy-MM-dd
LOCAL_TIME(LocalTime.class),
LOCAL_DATE_TIME(LocalDateTime.class); // yyyy-MM-dd'T'HH:mm:ss
private final Class<?> type;
JobParamDataType(Class<?> type) {
this.type = type;
}
public boolean supports(Object value) {
return type.isInstance(value);
}
public static Optional<JobParamDataType> resolveType(Object value) {
if (value == null) return Optional.empty();
return Arrays.stream(values()).filter(t -> t.supports(value)).findFirst();
}
}

View File

@@ -0,0 +1,8 @@
package dev.sonpx.loyalty.mcp.enums;
public interface MetaEntityType {
String getCode();
String getServiceId();
}

View File

@@ -0,0 +1,11 @@
package dev.sonpx.loyalty.mcp.enums;
/**
* @author AnhDT
* Created on 2024/06/26
*/
public enum PeriodAction {
CURRENT,
LAST,
}

View File

@@ -0,0 +1,14 @@
package dev.sonpx.loyalty.mcp.enums;
/**
* @author AnhDT
* Created on 2024/06/26
*/
public enum PeriodType {
YEAR,
QUARTER,
MONTH,
WEEK,
DAY,
}

View File

@@ -0,0 +1,33 @@
package dev.sonpx.loyalty.mcp.enums;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public enum RecordStatus implements EnumBase {
/** Record is active and eligible for processing. DB code: {@code "A"}. */
ACTIVE("A"),
/** Record is logically inactive but retained. DB code: {@code "I"}. */
INACTIVE("I"),
/** Record is marked for deletion. DB code: {@code "D"}. */
DELETE("D");
@JsonValue
private final String code;
@JsonCreator
public static RecordStatus fromCode(String code) {
if (code == null) return null;
for (RecordStatus s : values()) {
if (s.code.equalsIgnoreCase(code)) return s;
}
return null;
}
}

View File

@@ -0,0 +1,42 @@
package dev.sonpx.loyalty.mcp.enums;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import java.util.Arrays;
import java.util.List;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
* @author AnhDT
* Created on 2024/06/26
* {@link #LAST_7_DAYS},
* {@link #LAST_30_DAYS},
* {@link #LAST_3_MONTHS},
* {@link #LAST_6_MONTHS}
*/
@Getter
@RequiredArgsConstructor
public enum StatisticPeriod {
TODAY("TODAY"),
LAST_7_DAYS("L7D"),
LAST_30_DAYS("L30D"),
LAST_3_MONTHS("L3M"),
LAST_6_MONTHS("L6M");
@JsonValue
private final String code;
@JsonCreator
public static StatisticPeriod fromCode(String code) {
return Arrays.stream(values())
.filter(e -> e.code.equalsIgnoreCase(code))
.findFirst()
.orElse(null);
}
public boolean isAny(StatisticPeriod... periods) {
return List.of(periods).contains(this);
}
}

View File

@@ -0,0 +1,56 @@
package dev.sonpx.loyalty.mcp.enums.schedule;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import dev.sonpx.loyalty.mcp.enums.EnumBase;
import java.util.Arrays;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public enum DayOfMonth implements EnumBase {
DAY01("1"),
DAY02("2"),
DAY03("3"),
DAY04("4"),
DAY05("5"),
DAY06("6"),
DAY07("7"),
DAY08("8"),
DAY09("9"),
DAY10("10"),
DAY11("11"),
DAY12("12"),
DAY13("13"),
DAY14("14"),
DAY15("15"),
DAY16("16"),
DAY17("17"),
DAY18("18"),
DAY19("19"),
DAY20("20"),
DAY21("21"),
DAY22("22"),
DAY23("23"),
DAY24("24"),
DAY25("25"),
DAY26("26"),
DAY27("27"),
DAY28("28"),
DAY29("29"),
DAY30("30"),
DAY31("31");
@JsonValue
private final String code;
@JsonCreator
public static DayOfMonth fromCode(String code) {
return Arrays.stream(values())
.filter(day -> day.getCode().equals(code))
.findFirst()
.orElse(null);
}
}

View File

@@ -0,0 +1,32 @@
package dev.sonpx.loyalty.mcp.enums.schedule;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import dev.sonpx.loyalty.mcp.enums.EnumBase;
import java.util.Arrays;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public enum DayOfWeek implements EnumBase {
MONDAY("1"),
TUESDAY("2"),
WEDNESDAY("3"),
THURSDAY("4"),
FRIDAY("5"),
SATURDAY("6"),
SUNDAY("7");
@JsonValue
private final String code;
@JsonCreator
public static DayOfWeek fromCode(String code) {
return Arrays.stream(values())
.filter(day -> day.getCode().equals(code))
.findFirst()
.orElse(null);
}
}

View File

@@ -0,0 +1,37 @@
package dev.sonpx.loyalty.mcp.enums.schedule;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import dev.sonpx.loyalty.mcp.enums.EnumBase;
import java.util.Arrays;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public enum MonthOfYear implements EnumBase {
JANUARY("1"),
FEBRUARY("2"),
MARCH("3"),
APRIL("4"),
MAY("5"),
JUNE("6"),
JULY("7"),
AUGUST("8"),
SEPTEMBER("9"),
OCTOBER("10"),
NOVEMBER("11"),
DECEMBER("12");
@JsonValue
private final String code;
@JsonCreator
public static MonthOfYear fromCode(String code) {
return Arrays.stream(values())
.filter(month -> month.getCode().equals(code))
.findFirst()
.orElse(null);
}
}

View File

@@ -0,0 +1,29 @@
package dev.sonpx.loyalty.mcp.enums.schedule;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import dev.sonpx.loyalty.mcp.enums.EnumBase;
import java.util.Arrays;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public enum PeriodicType implements EnumBase {
DAY("D"),
WEEK("W"),
MONTH("M"),
YEAR("Y");
@JsonValue
private final String code;
@JsonCreator
public static PeriodicType fromCode(String code) {
return Arrays.stream(values())
.filter(periodicType -> periodicType.getCode().equals(code))
.findFirst()
.orElse(null);
}
}

View File

@@ -0,0 +1,27 @@
package dev.sonpx.loyalty.mcp.enums.schedule;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import dev.sonpx.loyalty.mcp.enums.EnumBase;
import java.util.Arrays;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public enum ScheduleMonthType implements EnumBase {
DAY_OF_MONTH("DOM"),
DAY_OF_WEEK("DOW");
@JsonValue
private final String code;
@JsonCreator
public static ScheduleMonthType fromCode(String code) {
return Arrays.stream(values())
.filter(monthType -> monthType.getCode().equals(code))
.findFirst()
.orElse(null);
}
}

View File

@@ -0,0 +1,27 @@
package dev.sonpx.loyalty.mcp.enums.schedule;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import dev.sonpx.loyalty.mcp.enums.EnumBase;
import java.util.Arrays;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public enum ScheduleRecurrenceType implements EnumBase {
ONE_TIME("O"),
RECURRING("R");
@JsonValue
private final String code;
@JsonCreator
public static ScheduleRecurrenceType fromCode(String code) {
return Arrays.stream(values())
.filter(scheduleType -> scheduleType.getCode().equals(code))
.findFirst()
.orElse(null);
}
}

View File

@@ -0,0 +1,27 @@
package dev.sonpx.loyalty.mcp.enums.schedule;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import dev.sonpx.loyalty.mcp.enums.EnumBase;
import java.util.Arrays;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public enum TriggerMethod implements EnumBase {
SCHEDULE("S"),
EVENT("E");
@JsonValue
private final String code;
@JsonCreator
public static TriggerMethod fromCode(String code) {
return Arrays.stream(values())
.filter(triggerMethod -> triggerMethod.getCode().equals(code))
.findFirst()
.orElse(null);
}
}

View File

@@ -0,0 +1,31 @@
package dev.sonpx.loyalty.mcp.enums.schedule;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import dev.sonpx.loyalty.mcp.enums.EnumBase;
import java.util.Arrays;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public enum WeekOfMonth implements EnumBase {
FIRST("1"),
SECOND("2"),
THIRD("3"),
FOURTH("4"),
FIFTH("5"),
LAST("L");
@JsonValue
private final String code;
@JsonCreator
public static WeekOfMonth fromCode(String code) {
return Arrays.stream(values())
.filter(week -> week.getCode().equals(code))
.findFirst()
.orElse(null);
}
}

View File

@@ -0,0 +1,4 @@
package dev.sonpx.loyalty.mcp.filter;
public class BooleanFilter extends Filter<Boolean> {}

View File

@@ -0,0 +1,7 @@
package dev.sonpx.loyalty.mcp.filter;
/**
* @author AnhDT Created on 2025/12/11
*/
public class DoubleFilter extends RangeFilter<Double> {}

View File

@@ -0,0 +1,57 @@
package dev.sonpx.loyalty.mcp.filter;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
/**
* Handle for filtering with basic operators (equals, notEquals, specified, in, notIn)
*/
@Getter
@NoArgsConstructor
@EqualsAndHashCode
public class Filter<T> implements Serializable {
private T equals;
private T notEquals;
private Boolean specified;
private List<T> in;
private List<T> notIn;
public Filter(Filter<T> filter) {
this.equals = filter.equals;
this.notEquals = filter.notEquals;
this.specified = filter.specified;
this.in = filter.in == null ? null : new ArrayList<>(filter.in);
this.notIn = filter.notIn == null ? null : new ArrayList<>(filter.notIn);
}
public Filter<T> setEquals(T equals) {
this.equals = equals;
return this;
}
public Filter<T> setNotEquals(T notEquals) {
this.notEquals = notEquals;
return this;
}
public Filter<T> setSpecified(Boolean specified) {
this.specified = specified;
return this;
}
public Filter<T> setIn(List<T> in) {
this.in = in;
return this;
}
public Filter<T> setNotIn(List<T> notIn) {
this.notIn = notIn;
return this;
}
}

View File

@@ -0,0 +1,9 @@
package dev.sonpx.loyalty.mcp.filter;
import java.time.Instant;
/**
* @author AnhDT Created on 2025/12/11
*/
public class InstantFilter extends RangeFilter<Instant> {}

View File

@@ -0,0 +1,31 @@
package dev.sonpx.loyalty.mcp.filter;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
/**
* Handle for filtering String type with contains, doesNotContain
*/
@Getter
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class ListContainsFilter extends Filter<String> {
private String containsList;
private String doesNotContainList;
public ListContainsFilter setContainsList(String contains) {
this.containsList = contains;
return this;
}
public ListContainsFilter setDoesNotContainList(String doesNotContain) {
this.doesNotContainList = doesNotContain;
return this;
}
}

View File

@@ -0,0 +1,9 @@
package dev.sonpx.loyalty.mcp.filter;
import java.time.LocalDate;
/**
* @author AnhDT Created on 2025/12/11
*/
public class LocalDateFilter extends RangeFilter<LocalDate> {}

View File

@@ -0,0 +1,9 @@
package dev.sonpx.loyalty.mcp.filter;
import java.time.LocalDateTime;
/**
* @author AnhDT Created on 2025/12/11
*/
public class LocalDateTimeFilter extends RangeFilter<LocalDateTime> {}

View File

@@ -0,0 +1,7 @@
package dev.sonpx.loyalty.mcp.filter;
/**
* @author AnhDT Created on 2025/12/11
*/
public class LongFilter extends RangeFilter<Long> {}

View File

@@ -0,0 +1,41 @@
package dev.sonpx.loyalty.mcp.filter;
import java.util.Arrays;
import java.util.List;
import lombok.Builder;
import lombok.Data;
import org.springframework.util.CollectionUtils;
/**
* Created by SonPhung on Monday, 09-Oct-2023
*/
@Data
@Builder
public class OlsFilterSpec {
private Filter<?> filter;
private String column;
private String param;
private List<String> columns;
public boolean searchFilter() {
return !CollectionUtils.isEmpty(columns);
}
public static OlsFilterSpec of(Filter<?> filter, String column, String param) {
return OlsFilterSpec.builder()
.filter(filter)
.column(column)
.param(param)
.build();
}
public static OlsFilterSpec of(Filter<?> filter, String param, String... columns) {
return OlsFilterSpec.builder()
.filter(filter)
.columns(Arrays.stream(columns).toList())
.param(param)
.build();
}
}

View File

@@ -0,0 +1,49 @@
package dev.sonpx.loyalty.mcp.filter;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
/**
* Handle for filtering comparable types with range options
* Include types: Integer, Long, Float, Double, BigDecimal, Instant, LocalDate
*/
@Getter
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class RangeFilter<T extends Comparable<? super T>> extends Filter<T> {
private T greaterThan;
private T lessThan;
private T greaterThanOrEqual;
private T lessThanOrEqual;
public RangeFilter(RangeFilter<T> filter) {
super(filter);
this.greaterThan = filter.greaterThan;
this.lessThan = filter.lessThan;
this.greaterThanOrEqual = filter.greaterThanOrEqual;
this.lessThanOrEqual = filter.lessThanOrEqual;
}
public RangeFilter<T> setGreaterThan(T greaterThan) {
this.greaterThan = greaterThan;
return this;
}
public RangeFilter<T> setLessThan(T lessThan) {
this.lessThan = lessThan;
return this;
}
public RangeFilter<T> setGreaterThanOrEqual(T greaterThanOrEqual) {
this.greaterThanOrEqual = greaterThanOrEqual;
return this;
}
public RangeFilter<T> setLessThanOrEqual(T lessThanOrEqual) {
this.lessThanOrEqual = lessThanOrEqual;
return this;
}
}

View File

@@ -0,0 +1,12 @@
package dev.sonpx.loyalty.mcp.filter;
/**
* String filter alias for ReferenceData request params such as field.code.equals.
*/
public class ReferenceDataStringFilter extends StringFilter {
public StringFilter getCode() {
return this;
}
}

View File

@@ -0,0 +1,31 @@
package dev.sonpx.loyalty.mcp.filter;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
/**
* Handle for filtering String type with contains, doesNotContain
*/
@Getter
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class StringFilter extends Filter<String> {
private String contains;
private String doesNotContain;
public StringFilter setContains(String contains) {
this.contains = contains;
return this;
}
public StringFilter setDoesNotContain(String doesNotContain) {
this.doesNotContain = doesNotContain;
return this;
}
}

View File

@@ -0,0 +1,36 @@
package dev.sonpx.loyalty.mcp.model;
import java.io.Serial;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
/**
* Created by tuanngo on Wed, 10/01/2024
*/
@Getter
@Setter
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class AttributeData extends ReferenceData {
@Serial
private static final long serialVersionUID = 1L;
private String attributeType;
private String dataType;
private String semanticType;
private boolean attributeValuePredefined;
private boolean notFound;
public AttributeData(String code) {
this.code = code;
}
}

View File

@@ -0,0 +1,15 @@
package dev.sonpx.loyalty.mcp.model;
/**
* Created by tuanngo on Wed, 2025-09-10
*/
public record ProcessResult(String code, String message, String targetId) {
public static ProcessResult of(String code, String message) {
return new ProcessResult(code, message, null);
}
public static ProcessResult of(String code, String message, String targetId) {
return new ProcessResult(code, message, targetId);
}
}

View File

@@ -0,0 +1,75 @@
package dev.sonpx.loyalty.mcp.model;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
/**
* Created by tuanngo on Tue, 2025-04-16
*/
@Getter
@Setter
@NoArgsConstructor
public class ReferenceData implements Serializable {
protected String code;
protected String description;
protected boolean notFound;
protected String recordStatus;
protected Map<String, Object> attributes = new HashMap<>();
public ReferenceData(String code) {
this.code = code;
}
public ReferenceData(String code, String description) {
this.code = code;
this.description = description;
this.notFound = false;
}
public ReferenceData(String code, String description, Boolean notFound) {
this.code = code;
this.description = description;
this.notFound = notFound;
}
public ReferenceData(String code, String description, boolean notFound, String recordStatus) {
this.code = code;
this.description = description;
this.notFound = notFound;
this.recordStatus = recordStatus;
}
public ReferenceData(String code, String description, Map<String, Object> attributes) {
this.code = code;
this.description = description;
this.attributes = attributes;
}
public void addAttribute(String key, Object value) {
this.attributes.put(key, value);
}
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
ReferenceData that = (ReferenceData) o;
return Objects.equals(code, that.code) && Objects.equals(description, that.description);
}
@Override
public int hashCode() {
return Objects.hash(code, description);
}
@Override
public String toString() {
return "ReferenceData{" + "code='" + code + '\'' + ", notFound=" + notFound + '}';
}
}

View File

@@ -0,0 +1,51 @@
package dev.sonpx.loyalty.mcp.model.valueobject;
import lombok.Getter;
import lombok.ToString;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
/**
* Abstract generic class for value objects.
* Provides automatic generation of getter, equals, hashCode, and toString methods
* for the 'value' field using Lombok annotations.
*
* @param <T> the type of the value this class holds
*/
@Getter
@ToString
public abstract class BaseValue<T> {
protected T value;
/**
* Constructs a new BaseValue object with the specified value.
* @param value the value to set
*/
public BaseValue(T value) {
this.value = value;
}
public boolean isNull() {
return value == null;
}
public boolean isNotNull() {
return value != null;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof BaseValue<?> baseValue)) return false;
return new EqualsBuilder().append(getValue(), baseValue.getValue()).isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37).append(getValue()).toHashCode();
}
}

View File

@@ -0,0 +1,29 @@
package dev.sonpx.loyalty.mcp.model.valueobject;
public class BooleanValue extends BaseValue<Boolean> {
public static final BooleanValue FALSE = new BooleanValue(false);
BooleanValue(Boolean value) {
super(value);
}
public boolean isEqualTo(BooleanValue compareValue) {
return this.value.equals(compareValue.getValue());
}
public static BooleanValue of(Object value) {
BooleanValue falseValue = BooleanValue.FALSE;
if (value instanceof Boolean bool) {
return new BooleanValue(bool);
} else if (value instanceof String string) {
try {
return BooleanValue.of(Boolean.valueOf(string));
} catch (Exception ignored) {
//
}
}
return falseValue;
}
}

View File

@@ -0,0 +1,307 @@
package dev.sonpx.loyalty.mcp.model.valueobject;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.Map;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
@Slf4j
@Getter
public class DateTimeValue extends BaseValue<LocalDateTime> {
private int quarter = 0;
public static final DateTimeValue NULL = new DateTimeValue(null);
public static final DateTimeValue NOW = new DateTimeValue(LocalDateTime.now());
/**
* Mapping of weekday abbreviations to DayOfWeek enums, used by onWeekdays().
* Example: "Mon" -> DayOfWeek.MONDAY
*/
public static final Map<String, DayOfWeek> DAY_OF_WEEK = Map.ofEntries(
Map.entry("Mon", DayOfWeek.MONDAY),
Map.entry("Tue", DayOfWeek.TUESDAY),
Map.entry("Wed", DayOfWeek.WEDNESDAY),
Map.entry("Thu", DayOfWeek.THURSDAY),
Map.entry("Fri", DayOfWeek.FRIDAY),
Map.entry("Sat", DayOfWeek.SATURDAY),
Map.entry("Sun", DayOfWeek.SUNDAY));
/**
* Enum representing units of time for plus() and minus() operations.
*/
public enum TimeUnit {
DAY,
WEEK,
MONTH,
QUARTER,
YEAR,
}
public enum TimeOffset {
AGO,
AWAY,
}
/**
* Private constructor to enforce use of factory method of().
* @param value the LocalDateTime to wrap
*/
private DateTimeValue(LocalDateTime value) {
super(value);
setQuarter();
}
/**
* Creates a DateTimeValue from various input types.
* - LocalDateTime: Converts to start of day
* - LocalDate: Converts to start of day LocalDateTime
* - String: Parses ISO ("yyyy-MM-dd") or "dd/MM/yyyy" formats
* - Null or invalid: Returns NULL_VALUE
*
* @param value the input to convert
* @return a new DateTimeValue instance
*/
public static DateTimeValue of(Object value) {
return switch (value) {
case LocalDateTime dateTime -> toStartOfDay(dateTime);
case LocalDate localDate -> new DateTimeValue(localDate.atStartOfDay());
case String datetimePattern -> parseString(datetimePattern);
case null, default -> NULL;
};
}
private static DateTimeValue parseString(String datetimePattern) {
if (StringUtils.isBlank(datetimePattern)) {
return DateTimeValue.NULL;
}
LocalDateTime localDateTime = LocalDate.parse(datetimePattern).atStartOfDay();
return new DateTimeValue(localDateTime);
}
public static DateTimeValue toStartOfDay(LocalDateTime localDateTime) {
return new DateTimeValue(localDateTime.toLocalDate().atStartOfDay());
}
/**
* Checks if this date falls on any of the specified weekdays.
* Example: onWeekdays("Mon", "Fri") returns true if date is a Monday or Friday.
*
* @param days weekday abbreviations (e.g., "Mon", "Tue")
* @return true if the date matches any specified weekday, false if null or no match
*/
public boolean onWeekdays(String... days) {
if (isNull()) {
return false;
}
return Arrays.stream(days)
.map(DAY_OF_WEEK::get)
.anyMatch(dayOfWeek -> dayOfWeek != null && dayOfWeek.equals(value.getDayOfWeek()));
}
/**
* Adds a specified quantity of time to this DateTimeValue.
* Example: plus(3, TimeUnit. MONTH) adds 3 months.
*
* @param quantity the amount to add (positive or negative)
* @param unit the time unit to apply
* @return a new DateTimeValue with the adjusted time
* @throws IllegalStateException if this instance is null
*/
public DateTimeValue plus(long quantity, TimeUnit unit) {
if (isNull()) {
throw new IllegalStateException("Cannot modify a null DateTimeValue");
}
if (quantity == 0) {
return this; // No change
}
return switch (unit) {
case DAY -> DateTimeValue.of(value.plusDays(quantity));
case MONTH -> DateTimeValue.of(value.plusMonths(quantity));
case QUARTER -> DateTimeValue.of(value.plusMonths(quantity * 3));
case YEAR -> DateTimeValue.of(value.plusYears(quantity));
case WEEK -> DateTimeValue.of(value.plusWeeks(quantity));
default -> NULL;
};
}
/**
* Subtracts a specified amount of time from this DateTimeValue.
* Example: minus(2, TimeUnit.DAY) subtracts 2 days.
*
* @param amount the amount to subtract (positive; negative values add time)
* @param unit the time unit to apply
* @return a new DateTimeValue with the adjusted time
* @throws IllegalStateException if this instance is null
*/
public DateTimeValue minus(long amount, TimeUnit unit) {
if (isNull()) {
throw new IllegalStateException("Cannot modify a null DateTimeValue");
}
if (amount == 0) {
return this; // No change
}
return switch (unit) {
case DAY -> DateTimeValue.of(value.minusDays(amount));
case MONTH -> DateTimeValue.of(value.minusMonths(amount));
case QUARTER -> DateTimeValue.of(value.minusMonths(amount * 3));
case YEAR -> DateTimeValue.of(value.minusYears(amount));
case WEEK -> DateTimeValue.of(value.minusWeeks(amount));
default -> NULL;
};
}
/**
* Checks if this DateTimeValue is empty (null).
*
* @return true if null, false otherwise
*/
public boolean isEmpty() {
return isNull();
}
/**
* Checks if this DateTimeValue is not empty (not null).
*
* @return true if not null, false otherwise
*/
public boolean isNotEmpty() {
return !isNull();
}
/**
* Checks if this DateTimeValue equals another, ignoring time of day.
* Example: "2025-03-02" equals "2025-03-02".
*
* @param compareValue the DateTimeValue to compare with
* @return true if equal, false if either is null or not equal
*/
public boolean isEquals(DateTimeValue compareValue) {
if (isNull()) {
return false;
}
return this.value.isEqual(compareValue.value);
}
/**
* Checks if this DateTimeValue is not equal to another.
*
* @param compareValue the DateTimeValue to compare with
* @return true if not equal, false if either is null or equal
*/
public boolean isNotEquals(DateTimeValue compareValue) {
return !isEquals(compareValue);
}
/**
* Checks if this DateTimeValue is on or after another.
* Example: "2025-03-02" isOnOrAfter "2025-03-01" returns true.
*
* @param compareValue the DateTimeValue to compare with
* @return true if on or after, false if either is null or before
*/
public boolean isOnOrAfter(DateTimeValue compareValue) {
return value.isAfter(compareValue.value) || value.isEqual(compareValue.value);
}
/**
* Checks if this DateTimeValue is on or before another.
* Example: "2025-03-02" isOnOrBefore "2025-03-03" returns true.
*
* @param compareValue the DateTimeValue to compare with
* @return true if on or before, false if either is null or after
*/
public boolean isOnOrBefore(DateTimeValue compareValue) {
return value.isBefore(compareValue.value) || value.isEqual(compareValue.value);
}
/**
* Checks if this DateTimeValue falls between two others (inclusive).
* @param from the start of the range
* @param to the end of the range
* @return true if within range, false if null or outside range
*/
public boolean isBetween(DateTimeValue from, DateTimeValue to) {
return isOnOrAfter(from) && isOnOrBefore(to);
}
public boolean quarterAndYearEquals(DateTimeValue compareValue) {
if (isNull()) {
return false;
}
return this.quarter == compareValue.quarter && this.value.getYear() == compareValue.value.getYear();
}
public boolean quarterAndYearNotEquals(DateTimeValue compareValue) {
return !quarterAndYearEquals(compareValue);
}
private void setQuarter() {
if (!isNull()) {
this.quarter = (this.value.getMonthValue() - 1) / 3 + 1;
}
}
/**
* Checks if a date component falls within a specified range.
* Example: isInRangeWith(1, 31, TimeUnit.DAY) checks if day is between 1 and 31.
*
* @param start the start of the range
* @param end the end of the range
* @param timeUnit the unit to check (DAY, MONTH, YEAR)
* @return true if within range, false if null or outside range
*/
public boolean isInRangeWith(Integer start, Integer end, TimeUnit timeUnit) {
return switch (timeUnit) {
case DAY -> value.getDayOfMonth() >= start && value.getDayOfMonth() <= end;
case MONTH -> value.getMonthValue() >= start && value.getMonthValue() <= end;
case YEAR -> value.getYear() >= start && value.getYear() <= end;
case QUARTER -> quarter >= start && quarter <= end;
default -> false;
};
}
/**
* Calculates the difference between this DateTimeValue and another in the specified time unit.
* Returns the number of days, months, or years between the two dates, with positive values indicating
* this date is after the compared date, and negative values indicating it is before. Only DAY, MONTH,
* and YEAR units are supported; other units throw an exception.
*
* Usage:
* DateTimeValue start = DateTimeValue.of("2025-01-01");
* DateTimeValue end = DateTimeValue.of("2025-03-02");
* Long days = start.differenceInUnits(end, TimeUnit.DAY); // Returns 60 (Jan 1 to Mar 2)
* Long months = start.differenceInUnits(end, TimeUnit.MONTH); // Returns 2 (Jan to Mar)
* Long years = end.differenceInUnits(start, TimeUnit.YEAR); // Returns 0 (same year)
*
* Notes:
* - Time components are ignored since DateTimeValue normalizes to start of day.
* - Returns null if this instance is null; assumes compareValue is non-null (may throw NPE if not).
* - Throws UnsupportedOperationException for WEEK or QUARTER units, as they are not implemented.
* - The result is signed: positive if this date is after compareValue, negative if before.
*
* @param compareValue the DateTimeValue to compare against (non-null expected)
* @param unit the time unit to measure the difference in (DAY, MONTH, or YEAR)
* @return the difference as a Long (positive or negative), or null if this instance is null
* @throws UnsupportedOperationException if unit is not DAY, MONTH, or YEAR
* @throws NullPointerException if compareValue or its value is null
*/
public Long differenceInUnits(DateTimeValue compareValue, TimeUnit unit) {
if (isNull()) {
return null;
}
return switch (unit) {
case DAY -> ChronoUnit.DAYS.between(value, compareValue.value);
case MONTH -> ChronoUnit.MONTHS.between(value, compareValue.value);
case YEAR -> ChronoUnit.YEARS.between(value, compareValue.value);
case null, default -> throw new UnsupportedOperationException("Not supported");
};
}
}

View File

@@ -0,0 +1,163 @@
package dev.sonpx.loyalty.mcp.model.valueobject;
public class NumberValue extends BaseValue<Double> {
private static final double EPSILON = 1e-9;
public static NumberValue ZERO = new NumberValue(0.0);
/**
* Constructs a new NumberValue object with the specified value.
*
* @param value the value to set
*/
NumberValue(Double value) {
super(value);
}
/**
* Create a NumberValue instance from an object.
*
* @param value an object that could be a Number or String.
* @return a NumberValue instance representing the specified value.
*/
public static NumberValue of(Object value) {
if (value instanceof Number number) {
return new NumberValue(number.doubleValue());
} else if (value instanceof String string) {
return new NumberValue(Double.parseDouble(string));
}
return new NumberValue(null);
}
/**
* Check if the current NumberValue equals another NumberValue.
*
* @param compareValue the NumberValue to compare with.
* @return true if the current NumberValue is equal to compareValue, otherwise false.
*/
public boolean equals(NumberValue compareValue) {
if (this.isNull()) {
return false;
}
return Math.abs(this.value - compareValue.value) < EPSILON;
}
/**
* Check if the current NumberValue is not equal to another NumberValue.
*
* @param compareValue the NumberValue to compare with.
* @return true if the current NumberValue is not equal to compareValue, otherwise false.
*/
public boolean notEquals(NumberValue compareValue) {
if (this.isNull()) {
return false;
}
return !equals(compareValue);
}
/**
* Check if the current NumberValue is less than another NumberValue.
*
* @param compareValue the NumberValue to compare with.
* @return true if the current NumberValue is less than compareValue, otherwise false.
*/
public boolean lessThan(NumberValue compareValue) {
if (this.isNull()) {
return false;
}
return this.value < compareValue.value;
}
/**
* Check if the current NumberValue is less than or equal to another NumberValue.
*
* @param compareValue the NumberValue to compare with.
* @return true if the current NumberValue is less than or equal to compareValue, otherwise false.
*/
public boolean lessThanOrEqual(NumberValue compareValue) {
if (this.isNull()) {
return false;
}
return this.value < compareValue.value || equals(compareValue);
}
/**
* Check if the current NumberValue is greater than another NumberValue.
*
* @param compareValue the NumberValue to compare with.
* @return true if the current NumberValue is greater than compareValue, otherwise false.
*/
public boolean greaterThan(NumberValue compareValue) {
if (this.isNull()) {
return false;
}
return this.value > compareValue.value;
}
/**
* Check if the current NumberValue is greater than or equal to another NumberValue.
*
* @param compareValue the NumberValue to compare with.
* @return true if the current NumberValue is greater than or equal to compareValue, otherwise false.
*/
public boolean greaterThanOrEqual(NumberValue compareValue) {
if (this.isNull()) {
return false;
}
return this.value > compareValue.value || equals(compareValue);
}
/**
* Check if the current NumberValue is between two other NumberValues.
*
* @param start the starting NumberValue (inclusive).
* @param end the ending NumberValue (inclusive).
* @return true if the current NumberValue is between start and end, otherwise false.
*/
public boolean between(NumberValue start, NumberValue end) {
if (this.isNull()) {
return false;
} else if (start.isNull()) {
return lessThanOrEqual(end);
} else if (end.isNull()) {
return greaterThanOrEqual(start);
}
return this.greaterThanOrEqual(start) && this.lessThanOrEqual(end);
}
public Long toLong() {
if (this.isNull()) {
return null;
}
return this.value.longValue();
}
public NumberValue min(NumberValue with) {
return new NumberValue(Math.min(this.value, with.value));
}
public NumberValue max(NumberValue with) {
return new NumberValue(Math.max(this.value, with.value));
}
public NumberValue add(NumberValue with) {
return new NumberValue(this.value + with.value);
}
public NumberValue subtract(NumberValue with) {
return new NumberValue(this.value - with.value);
}
public NumberValue multiply(NumberValue with) {
return new NumberValue(this.value * with.value);
}
public NumberValue divide(NumberValue with) {
return new NumberValue(this.value / with.value);
}
public NumberValue percent(Integer value) {
return new NumberValue((this.value * value) / 100);
}
}

View File

@@ -0,0 +1,250 @@
package dev.sonpx.loyalty.mcp.model.valueobject;
import java.util.Arrays;
import java.util.regex.PatternSyntaxException;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Strings;
@Slf4j
@ToString
public class StringValue extends BaseValue<String> {
public static final StringValue EMPTY = new StringValue("");
public static final String PIPE_DELIMITED = "|";
private StringValue(String value) {
super(value);
}
/**
* Create a StringValue instance from an object.
*
* @param value the value to convert (String, EnumBase, or other objects).
* @return a StringValue instance or EMPTY if the value is null.
*/
public static StringValue of(Object value) {
if (value == null) {
return EMPTY;
} else if (value instanceof String string) {
return new StringValue(string.toLowerCase().trim());
}
return EMPTY;
}
/**
* Convert a glob pattern to a regex pattern.
*
* @param glob the glob pattern to convert.
* @return a regex pattern equivalent to the glob.
*/
public static String createRegexFromGlob(String glob) {
StringBuilder out = new StringBuilder();
for (int i = 0; i < glob.length(); ++i) {
final char c = glob.charAt(i);
switch (c) {
case '*':
out.append(".*");
break;
case '?':
out.append('.');
break;
case '.':
out.append("\\.");
break;
case '\\':
out.append("\\\\");
break;
case '|':
out.append("\\|");
break;
default:
out.append(c);
}
}
return "^" + out + "$";
}
/**
* Check if the current StringValue equals another StringValue.
*
* @param valueCompare the StringValue to compare with.
* @return true if both values are equal, otherwise false.
*/
public boolean equals(StringValue valueCompare) {
return Strings.CI.equals(this.value, valueCompare.value);
}
/**
* Check if the current StringValue is not equal to another StringValue.
*
* @param valueCompare the StringValue to compare with.
* @return true if both values are not equal, otherwise false.
*/
public boolean notEquals(StringValue valueCompare) {
return !equals(valueCompare);
}
/**
* Check if the current StringValue is empty or null.
*
* @return true if the current StringValue is empty or null, otherwise false.
*/
public boolean isEmpty() {
return StringUtils.isBlank(value);
}
/**
* Check if the current StringValue is not empty.
*
* @return true if the current StringValue is not empty, otherwise false.
*/
public boolean isNotEmpty() {
return StringUtils.isNotEmpty(value);
}
/**
* Check if the current StringValue contains another StringValue.
*
* @param valueCompares the StringValue to check.
* @return true if the current StringValue contains valueCompare, otherwise false.
*/
public boolean contains(StringValue... valueCompares) {
for (StringValue valueCompare : valueCompares) {
if (regularExpression(valueCompare)) {
return true;
}
}
return false;
}
/**
* Check if the current StringValue does not contain another StringValue.
*
* @param valueCompares the StringValue to check.
* @return true if the current StringValue does not contain valueCompare, otherwise false.
*/
public boolean doesNotContains(StringValue... valueCompares) {
return !contains(valueCompares);
}
/**
* Check if the current StringValue begins with another StringValue.
*
* @param valueCompares the StringValue to check.
* @return true if the current StringValue begins with valueCompare, otherwise false.
*/
public boolean beginsWith(StringValue... valueCompares) {
for (StringValue valueCompare : valueCompares) {
if (Strings.CI.startsWith(this.value, valueCompare.getValue())) {
return true;
}
}
return false;
}
/**
* Check if the current StringValue ends with another StringValue.
*
* @param filterValues the StringValue to check.
* @return true if the current StringValue ends with valueCompare, otherwise false.
*/
public boolean endsWith(StringValue... filterValues) {
for (StringValue valueCompare : filterValues) {
boolean isEndWith = Strings.CI.endsWith(this.value, valueCompare.getValue());
if (isEndWith) {
return true;
}
}
return false;
}
/**
* Check if the current StringValue does not begin with another StringValue.
*
* @param valueCompares the StringValue to check.
* @return true if the current StringValue does not begin with valueCompare, otherwise false.
*/
public boolean doesNotBeginWith(StringValue... valueCompares) {
for (StringValue valueCompare : valueCompares) {
boolean hasStartsWith = Strings.CI.startsWith(this.value, valueCompare.getValue());
if (hasStartsWith) {
return false;
}
}
return true;
}
/**
* Check if the current StringValue does not end with another StringValue.
*
* @param filterValues the StringValue to check.
* @return true if the current StringValue does not end with valueCompare, otherwise false.
*/
public boolean doesNotEndsWith(StringValue... filterValues) {
for (StringValue valueCompare : filterValues) {
if (Strings.CI.endsWith(this.value, valueCompare.getValue())) {
return false;
}
}
return true;
}
/**
* Check if the current StringValue matches a regular expression pattern.
*
* @param valueCompare the StringValue pattern to match.
* @return true if the current StringValue matches valueCompare, otherwise false.
*/
public boolean regularExpression(StringValue valueCompare) {
String regex = createRegexFromGlob(valueCompare.value);
try {
// Java Escape Characters: Replace newline and tab characters with spaces
value = value.replaceAll("[\\n\\t\\r]", " ");
return value.matches(regex);
} catch (PatternSyntaxException patternSyntaxException) {
log.warn("Invalid regular expression: {}", regex);
return false;
}
}
/**
* Check if the current StringValue is in a list of StringValues.
*
* @param filterValues the list of StringValues to check.
* @return true if the current StringValue is in valueCompare, otherwise false.
*/
public boolean in(StringValue... filterValues) {
if (filterValues == null) {
return false;
}
boolean isMatched = false;
for (StringValue filterValue : filterValues) {
if (equals(filterValue)) {
isMatched = true;
break;
}
}
return isMatched;
}
/**
* Check if the current StringValue is not in a list of StringValues.
*
* @param valueCompare the list of StringValues to check.
* @return true if the current StringValue is not in valueCompare, otherwise false.
*/
public boolean notIn(StringValue... valueCompare) {
return !in(valueCompare);
}
public static StringValue[] split(Object object) {
if (object instanceof String string) {
return Arrays.stream(string.split("\\|")).map(StringValue::of).toArray(StringValue[]::new);
}
return null;
}
}

View File

@@ -0,0 +1,104 @@
package dev.sonpx.loyalty.mcp.model.valueobject;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.Optional;
public class TimeValue extends BaseValue<LocalTime> {
public static ZoneId SYSTEM_ZONE = ZoneId.systemDefault();
private static final TimeValue NULL = new TimeValue(null);
private static final int DEFAULT_SECONDS = 0;
private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss");
private TimeValue(LocalTime value) {
super(value);
}
/**
* Create a TimeValue instance from an object.
*
* @param object an object that could be a LocalTime, LocalDateTime, LocalDate, or String.
* @return a TimeValue instance representing the specified value, or an NULL instance if not applicable.
*/
public static TimeValue of(Object object) {
if (object instanceof LocalTime localTime) {
return new TimeValue(localTime.withSecond(DEFAULT_SECONDS));
} else if (object instanceof LocalDateTime localDateTime) {
return new TimeValue(localDateTime.toLocalTime().withSecond(DEFAULT_SECONDS));
} else if (object instanceof LocalDate localDate) {
return new TimeValue(localDate.atStartOfDay().toLocalTime().withSecond(DEFAULT_SECONDS));
} else if (object instanceof String string) {
return parseString(string).orElse(NULL);
}
return NULL;
}
/**
* Parse a time string into an Optional TimeValue using a specific formatter.
*
* @param string the time string to parse.
* @return an Optional containing a TimeValue if parsing is successful, otherwise an empty Optional.
*/
private static Optional<TimeValue> parseString(String string) {
try {
LocalTime time = LocalTime.parse(string, TIME_FORMATTER);
return Optional.of(new TimeValue(time.withSecond(DEFAULT_SECONDS)));
} catch (DateTimeParseException e) {
try {
ZonedDateTime utcDateTime = ZonedDateTime.parse(string);
ZonedDateTime serverDateTime = utcDateTime.withZoneSameInstant(SYSTEM_ZONE);
return Optional.of(new TimeValue(serverDateTime.toLocalTime().withSecond(DEFAULT_SECONDS)));
} catch (DateTimeParseException ignored) {
return Optional.empty();
}
}
}
/**
* Check if the current TimeValue is on or before another TimeValue.
*
* @param compareValue the TimeValue to compare with.
* @return true if the current TimeValue is on or before compareValue, otherwise false.
*/
public boolean isOnOrBefore(TimeValue compareValue) {
if (isNull()) {
return false;
}
return this.value.isBefore(compareValue.value) || this.value.equals(compareValue.value);
}
/**
* Check if the current TimeValue is on or after another TimeValue.
*
* @param compareValue the TimeValue to compare with.
* @return true if the current TimeValue is on or after compareValue, otherwise false.
*/
public boolean isOnOrAfter(TimeValue compareValue) {
if (isNull()) {
return false;
}
return this.value.isAfter(compareValue.value) || this.value.equals(compareValue.value);
}
/**
* Check if the current TimeValue is between two other TimeValues.
*
* @param start the starting TimeValue (inclusive).
* @param end the ending TimeValue (inclusive).
* @return true if the current TimeValue is between start and end, otherwise false.
*/
public boolean isBetween(TimeValue start, TimeValue end) {
if (isNull()) {
return false;
} else if (start.isNull()) {
return this.isOnOrBefore(end);
} else if (end.isNull()) {
return this.isOnOrAfter(start);
}
return this.isOnOrAfter(start) && this.isOnOrBefore(end);
}
}

View File

@@ -0,0 +1,19 @@
package dev.sonpx.loyalty.mcp.reward.client;
import dev.sonpx.loyalty.mcp.reward.criteria.CampaignCriteria;
import dev.sonpx.loyalty.mcp.reward.model.CampaignDto;
import java.util.List;
import org.springframework.data.domain.Pageable;
import org.springframework.http.ResponseEntity;
import org.springframework.web.service.annotation.GetExchange;
import org.springframework.web.service.annotation.HttpExchange;
/**
* Created by SonPhung on Sunday, 19-Jul-2026
**/
@HttpExchange
public interface RewardClient {
@GetExchange("/api/campaign/csr/list")
ResponseEntity<List<CampaignDto>> getAll(CampaignCriteria criteria, Pageable pageable);
}

View File

@@ -0,0 +1,25 @@
package dev.sonpx.loyalty.mcp.reward.criteria;
import dev.sonpx.loyalty.mcp.criteria.AbstractFwCriteria;
import dev.sonpx.loyalty.mcp.filter.StringFilter;
import lombok.Data;
/**
* @author AnhDT
* Created on 2023/10/25
*/
@Data
public class CampaignCriteria extends AbstractFwCriteria {
private StringFilter campaignId;
private StringFilter ownerName;
private StringFilter name;
private StringFilter description;
private StringFilter campaignType;
// for search text and component
private String[] componentColumns = {"campaign_id", "name"};
private String[] columns = {"description"};
}

View File

@@ -0,0 +1,30 @@
package dev.sonpx.loyalty.mcp.reward.criteria;
import dev.sonpx.loyalty.mcp.criteria.AbstractFwCriteria;
import dev.sonpx.loyalty.mcp.filter.LocalDateTimeFilter;
import dev.sonpx.loyalty.mcp.filter.StringFilter;
import lombok.Data;
/**
* @author AnhDT
* Created on 2023/10/31
*/
@Data
public class CampaignRuleCriteria extends AbstractFwCriteria {
private StringFilter campaignId;
private StringFilter ruleId;
private StringFilter ruleName;
private StringFilter description;
private LocalDateTimeFilter effectiveFrom;
private LocalDateTimeFilter effectiveTo;
private StringFilter ruleType;
private StringFilter poolId;
// for search text and component
private String[] componentColumns = {"rule_id", "rule_name"};
private String[] columns = {"campaign_id", "description"};
}

View File

@@ -0,0 +1,24 @@
package dev.sonpx.loyalty.mcp.reward.criteria;
import dev.sonpx.loyalty.mcp.criteria.AbstractFwCriteria;
import dev.sonpx.loyalty.mcp.filter.StringFilter;
import lombok.Data;
/**
* @author AnhDT Created on 2025/05/22
*/
@Data
public class ComponentCriteria extends AbstractFwCriteria {
private StringFilter key;
private StringFilter code;
private StringFilter name;
// for search text
private String[] componentColumns = {"code", "name"};
private String[] columns = {};
}

View File

@@ -0,0 +1,35 @@
package dev.sonpx.loyalty.mcp.reward.criteria;
import dev.sonpx.loyalty.mcp.criteria.AbstractFwCriteria;
import dev.sonpx.loyalty.mcp.filter.LocalDateFilter;
import dev.sonpx.loyalty.mcp.filter.LongFilter;
import dev.sonpx.loyalty.mcp.filter.StringFilter;
import lombok.Data;
/**
* @author AnhDT
* Created on 2023/10/31
*/
@Data
public class CounterDefinitionCriteria extends AbstractFwCriteria {
private StringFilter counterId;
private StringFilter counterName;
private LocalDateFilter effectiveFrom;
private LocalDateFilter effectiveTo;
private StringFilter entity;
private StringFilter bucketPeriodUnit;
private LongFilter bucketPeriodDuration;
private StringFilter whatToCount;
private StringFilter resetType;
private LocalDateFilter firstStartDate;
private StringFilter stateCounter;
private StringFilter lateValuePosting;
// for search text
private String[] componentColumns = {"counter_id", "counter_name"};
private String[] columns = {"entity", "what_to_count"};
}

View File

@@ -0,0 +1,28 @@
package dev.sonpx.loyalty.mcp.reward.criteria;
import dev.sonpx.loyalty.mcp.criteria.AbstractFwCriteria;
import dev.sonpx.loyalty.mcp.filter.DoubleFilter;
import dev.sonpx.loyalty.mcp.filter.StringFilter;
import lombok.Data;
/**
* @author AnhDT
* Created on 2023/12/12
*/
@Data
public class CurrencyRateCriteria extends AbstractFwCriteria {
private StringFilter pcrCode;
private StringFilter effectiveFrom;
private StringFilter effectiveTo;
private DoubleFilter buyRate;
private DoubleFilter sellRate;
// for search text
private String[] componentColumns = {"pcr_code"};
private String[] columns = {};
}

View File

@@ -0,0 +1,24 @@
package dev.sonpx.loyalty.mcp.reward.criteria;
import dev.sonpx.loyalty.mcp.criteria.AbstractFwCriteria;
import dev.sonpx.loyalty.mcp.filter.LocalDateFilter;
import dev.sonpx.loyalty.mcp.filter.StringFilter;
import lombok.Data;
@Data
public class DeductionSequencesCriteria extends AbstractFwCriteria {
private StringFilter sequenceId;
private StringFilter sequenceName;
private StringFilter description;
private LocalDateFilter effectiveFrom;
private LocalDateFilter effectiveTo;
private StringFilter isDefault;
// for search and component
private String[] componentColumns = {"sequence_id", "sequence_name"};
private String[] columns = {"description", "effective_from", "effective_to", "is_default"};
}

View File

@@ -0,0 +1,24 @@
package dev.sonpx.loyalty.mcp.reward.criteria;
import dev.sonpx.loyalty.mcp.criteria.AbstractFwCriteria;
import dev.sonpx.loyalty.mcp.filter.StringFilter;
import lombok.Data;
/**
* @author AnhDT
* Created on 2023/10/31
*/
@Data
public class EventMaintenanceCriteria extends AbstractFwCriteria {
private StringFilter eventId;
private StringFilter description;
// for search text
private String[] componentColumns = {"event_id", "description"};
private String[] columns = {};
}

View File

@@ -0,0 +1,24 @@
package dev.sonpx.loyalty.mcp.reward.criteria;
import dev.sonpx.loyalty.mcp.criteria.AbstractFwCriteria;
import dev.sonpx.loyalty.mcp.filter.StringFilter;
import lombok.Data;
/**
* @author AnhDT
* Created on 2023/12/06
*/
@Data
public class PoolConversionRateCriteria extends AbstractFwCriteria {
private StringFilter code;
private StringFilter description;
// for search text
private String[] componentColumns = {"code", "description"};
private String[] columns = {};
}

View File

@@ -0,0 +1,28 @@
package dev.sonpx.loyalty.mcp.reward.criteria;
import dev.sonpx.loyalty.mcp.criteria.AbstractFwCriteria;
import dev.sonpx.loyalty.mcp.filter.StringFilter;
import lombok.Data;
/**
* @author AnhDT
* Created on 2023/10/25
*/
@Data
public class PoolDefinitionCriteria extends AbstractFwCriteria {
private StringFilter poolId;
private StringFilter poolName;
private StringFilter poolType;
private StringFilter entityLevel;
private StringFilter expiryPolicy;
private StringFilter atgGroupId;
// for search and component
private String[] componentColumns = {"pool_id", "pool_name"};
private String[] columns = {"description"};
}

View File

@@ -0,0 +1,24 @@
package dev.sonpx.loyalty.mcp.reward.criteria;
import dev.sonpx.loyalty.mcp.criteria.AbstractFwCriteria;
import dev.sonpx.loyalty.mcp.filter.StringFilter;
import lombok.Data;
/**
* @author AnhDT
* Created on 2023/10/25
*/
@Data
public class StatementOutputPoolCriteria extends AbstractFwCriteria {
private StringFilter poolId;
private StringFilter entityLevel;
// for search text
private String[] componentColumns = {"pool_id"};
private String[] columns = {"entity_level"};
}

View File

@@ -0,0 +1,25 @@
package dev.sonpx.loyalty.mcp.reward.criteria;
import dev.sonpx.loyalty.mcp.criteria.AbstractFwCriteria;
import dev.sonpx.loyalty.mcp.filter.StringFilter;
import lombok.Data;
/**
* @author AnhDT
* Created on 2023/10/31
*/
@Data
public class TransactionCategoryCriteria extends AbstractFwCriteria {
private StringFilter code;
private StringFilter name;
private StringFilter description;
// for search text and component
private String[] componentColumns = {"code", "name"};
private String[] columns = {};
}

View File

@@ -0,0 +1,26 @@
package dev.sonpx.loyalty.mcp.reward.criteria;
import dev.sonpx.loyalty.mcp.criteria.AbstractFwCriteria;
import dev.sonpx.loyalty.mcp.filter.BooleanFilter;
import dev.sonpx.loyalty.mcp.filter.StringFilter;
import lombok.Data;
/**
* @author AnhDT
* Created on 2023/10/25
*/
@Data
public class TransactionCodeCriteria extends AbstractFwCriteria {
private StringFilter code;
private StringFilter description;
private BooleanFilter reversalInd;
// for search text and component
private String[] componentColumns = {"code", "description"};
private String[] columns = {"reversal_ind"};
}

View File

@@ -0,0 +1,29 @@
package dev.sonpx.loyalty.mcp.reward.enums;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import java.util.Arrays;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
* @author AnhDT
* Created on 2024/01/08
*/
@Getter
@RequiredArgsConstructor
public enum AlertChannel {
EMAIL("E");
@JsonValue
private final String code;
@JsonCreator
public static AlertChannel fromCode(String code) {
return Arrays.stream(values())
.filter(e -> e.code.equalsIgnoreCase(code))
.findFirst()
.orElse(null);
}
}

View File

@@ -0,0 +1,30 @@
package dev.sonpx.loyalty.mcp.reward.enums;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import java.util.Arrays;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
* @author AnhDT
* Created on 2024/01/05
*/
@Getter
@RequiredArgsConstructor
public enum AmountToUseType {
GROSS_AMOUNT("GA"),
NETT_AMOUNT("NA");
@JsonValue
private final String code;
@JsonCreator
public static AmountToUseType fromCode(String code) {
return Arrays.stream(values())
.filter(e -> e.code.equalsIgnoreCase(code))
.findFirst()
.orElse(null);
}
}

View File

@@ -0,0 +1,30 @@
package dev.sonpx.loyalty.mcp.reward.enums;
import com.fasterxml.jackson.annotation.JsonCreator;
import java.util.Arrays;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
* @author AnhDT
* Created on 2024/03/11
*/
@Getter
@RequiredArgsConstructor
public enum AmountToUseViewType {
CODE(null, null),
ATTRIBUTE("A", "Attribute"),
COUNTER("C", "Counter");
private final String code;
private final String name;
@JsonCreator
public static AmountToUseViewType fromCode(String code) {
return Arrays.stream(values())
.filter(e -> e.code.equalsIgnoreCase(code))
.findFirst()
.orElse(null);
}
}

View File

@@ -0,0 +1,29 @@
package dev.sonpx.loyalty.mcp.reward.enums;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import java.util.Arrays;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
* Created by SonPhung on Friday, 10-Nov-2023
*/
@Getter
@RequiredArgsConstructor
public enum AwardBasisType {
APPLY_EACH_TIER("AAF"),
APPLY_HIGHEST_TIER("AHT");
@JsonValue
private final String code;
@JsonCreator
public static AwardBasisType fromCode(String code) {
return Arrays.stream(values())
.filter(e -> e.code.equalsIgnoreCase(code))
.findFirst()
.orElse(null);
}
}

View File

@@ -0,0 +1,29 @@
package dev.sonpx.loyalty.mcp.reward.enums;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import java.util.Arrays;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
* Created by SonPhung on Friday, 10-Nov-2023
*/
@Getter
@RequiredArgsConstructor
public enum AwardFactorType {
PERCENT("GA"),
POOL_UNIT("NA");
@JsonValue
private final String code;
@JsonCreator
public static AwardFactorType fromCode(String code) {
return Arrays.stream(values())
.filter(e -> e.code.equalsIgnoreCase(code))
.findFirst()
.orElse(null);
}
}

View File

@@ -0,0 +1,31 @@
package dev.sonpx.loyalty.mcp.reward.enums;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import java.util.Arrays;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
* Created by SonPhung on Friday, 10-Nov-2023
* This sets the cap on the sum of Result from the formulas set up in Rule.
* If the Result from Rule exceeds this cap, then this cap is used as the Result.
*/
@Getter
@RequiredArgsConstructor
public enum AwardLimitType {
NO_MORE_THAN("NM"),
AT_LEAST("AL");
@JsonValue
private final String code;
@JsonCreator
public static AwardLimitType fromCode(String code) {
return Arrays.stream(values())
.filter(e -> e.code.equalsIgnoreCase(code))
.findFirst()
.orElse(null);
}
}

View File

@@ -0,0 +1,30 @@
package dev.sonpx.loyalty.mcp.reward.enums;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import java.util.Arrays;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
* @author AnhDT
* Created on 2024/01/05
*/
@Getter
@RequiredArgsConstructor
public enum BaseDateType {
POST_DATE("PD"),
TRANSACTION_DATE("TD");
@JsonValue
private final String code;
@JsonCreator
public static BaseDateType fromCode(String code) {
return Arrays.stream(values())
.filter(e -> e.code.equalsIgnoreCase(code))
.findFirst()
.orElse(null);
}
}

View File

@@ -0,0 +1,30 @@
package dev.sonpx.loyalty.mcp.reward.enums;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import java.util.Arrays;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
* @author AnhDT
* Created on 2024/01/02
*/
@Getter
@RequiredArgsConstructor
public enum CampaignType {
BASE("B"),
TACTICAL("T");
@JsonValue
private final String code;
@JsonCreator
public static CampaignType fromCode(String code) {
return Arrays.stream(values())
.filter(e -> e.code.equalsIgnoreCase(code))
.findFirst()
.orElse(null);
}
}

View File

@@ -0,0 +1,29 @@
package dev.sonpx.loyalty.mcp.reward.enums;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import java.util.Arrays;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
* Created by SonPhung on Friday, 10-Nov-2023
*/
@Getter
@RequiredArgsConstructor
public enum CapAwardType {
TRACKED_IN_COUNTER("C"),
PER_TRANSACTION("T");
@JsonValue
private final String code;
@JsonCreator
public static CapAwardType fromCode(String code) {
return Arrays.stream(values())
.filter(e -> e.code.equalsIgnoreCase(code))
.findFirst()
.orElse(null);
}
}

View File

@@ -0,0 +1,32 @@
package dev.sonpx.loyalty.mcp.reward.enums;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import java.util.Arrays;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
* Created by SonPhung on Wednesday, 20-Dec-2023
*/
@Getter
@RequiredArgsConstructor
public enum CounterCountType {
GROSS_AMOUNT("GA"),
NET_AMOUNT("NA"),
POINT("P"),
POINT_VALUE("PV"),
TRANSACTION_FREQUENCY("TF");
@JsonValue
private final String code;
@JsonCreator
public static CounterCountType fromCode(String code) {
return Arrays.stream(values())
.filter(e -> e.code.equalsIgnoreCase(code))
.findFirst()
.orElse(null);
}
}

View File

@@ -0,0 +1,29 @@
package dev.sonpx.loyalty.mcp.reward.enums;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import java.util.Arrays;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
* Created by SonPhung on Wednesday, 20-Dec-2023
*/
@Getter
@RequiredArgsConstructor
public enum CounterResetType {
RESET_TO_REMAINDER("R"),
RESET_TO_ZERO("Z");
@JsonValue
private final String code;
@JsonCreator
public static CounterResetType fromCode(String code) {
return Arrays.stream(values())
.filter(e -> e.code.equalsIgnoreCase(code))
.findFirst()
.orElse(null);
}
}

View File

@@ -0,0 +1,30 @@
package dev.sonpx.loyalty.mcp.reward.enums;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import java.util.Arrays;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
* Created by SonPhung on Wednesday, 20-Dec-2023
*/
@Getter
@RequiredArgsConstructor
public enum CounterUpdateType {
ON_AWARD("A"),
ON_EXTRACT("E"),
NEVER("N");
@JsonValue
private final String code;
@JsonCreator
public static CounterUpdateType fromCode(String code) {
return Arrays.stream(values())
.filter(e -> e.code.equalsIgnoreCase(code))
.findFirst()
.orElse(null);
}
}

View File

@@ -0,0 +1,27 @@
package dev.sonpx.loyalty.mcp.reward.enums;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import java.util.Arrays;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public enum DeductionPriorityType {
START_DATE("STA"),
EXPIRY_DATE("EXP"),
CONTRIBUTOR("CON");
@JsonValue
private final String code;
@JsonCreator
public static DeductionPriorityType fromCode(String code) {
return Arrays.stream(DeductionPriorityType.values())
.filter(o -> o.getCode().equals(code))
.findFirst()
.orElse(null);
}
}

View File

@@ -0,0 +1,31 @@
package dev.sonpx.loyalty.mcp.reward.enums;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import java.util.Arrays;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
* @author AnhDT
* Created on 2024/01/09
*/
@Getter
@RequiredArgsConstructor
public enum EventFrequencyType {
ONCE("O"),
MONTHLY("M"),
ANNUALLY("A");
@JsonValue
private final String code;
@JsonCreator
public static EventFrequencyType fromCode(String code) {
return Arrays.stream(values())
.filter(e -> e.code.equalsIgnoreCase(code))
.findFirst()
.orElse(null);
}
}

View File

@@ -0,0 +1,28 @@
package dev.sonpx.loyalty.mcp.reward.enums;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import java.util.Arrays;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
// exc_blocked_card
@Getter
@RequiredArgsConstructor
public enum ExtractBlockedCard {
INCLUDE("I"),
EXCLUDE("E");
@JsonValue
private final String code;
@JsonCreator
public static ExtractBlockedCard fromCode(String code) {
return Arrays.stream(values())
.filter(e -> e.code.equalsIgnoreCase(code))
.findFirst()
.orElse(null);
}
}

View File

@@ -0,0 +1,26 @@
package dev.sonpx.loyalty.mcp.reward.enums;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import java.util.Arrays;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public enum ExtractNoCounter {
INCLUDE("I"),
EXCLUDE("E");
@JsonValue
private final String code;
@JsonCreator
public static ExtractNoCounter fromCode(String code) {
return Arrays.stream(values())
.filter(e -> e.code.equalsIgnoreCase(code))
.findFirst()
.orElse(null);
}
}

View File

@@ -0,0 +1,30 @@
package dev.sonpx.loyalty.mcp.reward.enums;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import java.util.Arrays;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
* @author AnhDT
* Created on 2024/01/08
*/
@Getter
@RequiredArgsConstructor
public enum LateValuePosting {
UPDATE_LATE_VALUE("L"),
UPDATE_CURRENT_BUCKET("C");
@JsonValue
private final String code;
@JsonCreator
public static LateValuePosting fromCode(String code) {
return Arrays.stream(values())
.filter(e -> e.code.equalsIgnoreCase(code))
.findFirst()
.orElse(null);
}
}

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