feat: expand MCP server functionality with customer, catalogue, marketing, and user tools and integrate new health indicators and configuration models.

This commit is contained in:
2026-08-11 16:31:52 +07:00
parent 9a26c6ce2d
commit 8ecd27dd0e
188 changed files with 5672 additions and 983 deletions

View File

@@ -1,6 +1,23 @@
FROM eclipse-temurin:25-jre-alpine
# Non-root user for security
RUN addgroup -S app && adduser -S app -G app
VOLUME /tmp
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
EXPOSE 8081
ENTRYPOINT ["java","-jar","/app.jar"]
RUN chown app:app /app.jar
USER app
EXPOSE 9331
HEALTHCHECK --interval=30s --timeout=5s --retries=3 --start-period=60s \
CMD wget --quiet --tries=1 --spider http://localhost:9331/actuator/health/liveness || exit 1
ENTRYPOINT ["java", \
"-XX:+UseContainerSupport", \
"-XX:MaxRAMPercentage=75.0", \
"-Djava.security.egd=file:/dev/./urandom", \
"-jar", "/app.jar"]

File diff suppressed because one or more lines are too long

View File

@@ -14,6 +14,8 @@
<name>loyalty-mcp-server</name>
<description>MCP Server for Loyalty API Tools</description>
<dependencies>
<!-- Spring Boot Web -->
<dependency>
@@ -38,6 +40,12 @@
<version>3.4.0</version>
</dependency>
<!-- Actuator for Health Checks -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- WebClient (needed for generated API client if library is webclient) -->
<dependency>
<groupId>org.springframework.boot</groupId>
@@ -106,6 +114,11 @@
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
@@ -113,8 +126,8 @@
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>0.2.0</version>
</path>
</annotationProcessorPaths>
</configuration>

View File

@@ -6,10 +6,21 @@ import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.HttpServerErrorException;
import org.springframework.web.client.RestClientResponseException;
/**
* Global exception handler for @McpTool methods using AOP.
* Prevents internal stack traces from leaking to the MCP Client.
*
* Categorizes errors by HTTP status code so the LLM receives
* actionable messages instead of generic "system error":
* - 400 → Validation error (tell user what's wrong)
* - 404 → Entity not found
* - 409 → Duplicate/conflict
* - 401/403 → Auth error
* - 5xx → System error (generic message, details logged)
*/
@Slf4j
@Aspect
@@ -20,10 +31,73 @@ public class GlobalToolExceptionHandlerAspect {
public Object handleException(ProceedingJoinPoint pjp) throws Throwable {
try {
return pjp.proceed();
} catch (RestClientResponseException e) {
// HTTP error from Core API — categorize by status code
return categorizeHttpError(pjp.getSignature().getName(), e);
} catch (Exception e) {
log.error("Global AOP exception caught during execution of MCP tool {}: {}", pjp.getSignature().getName(), e.getMessage());
log.error("Global AOP exception caught during execution of MCP tool {}: {}", pjp.getSignature().getName(), e.getMessage(), e);
// Return a safe Result to prevent stack traces from leaking to the agent
return Result.of(null, "Lỗi hệ thống khi gọi tool tại MCP server. Không thể xử lý yêu cầu. Vui lòng báo cáo lỗi này.");
}
}
private Result<?> categorizeHttpError(String toolName, RestClientResponseException e) {
int status = e.getStatusCode().value();
String responseBody = e.getResponseBodyAsString();
switch (status) {
case 400 -> {
log.warn("Tool {} received HTTP 400 (Bad Request): {}", toolName, truncate(responseBody));
String detail = extractErrorMessage(responseBody);
return Result.failure("Dữ liệu không hợp lệ: " + detail);
}
case 404 -> {
log.warn("Tool {} received HTTP 404 (Not Found): {}", toolName, truncate(responseBody));
return Result.failure("Không tìm thấy đối tượng yêu cầu. Vui lòng kiểm tra lại ID.");
}
case 409 -> {
log.warn("Tool {} received HTTP 409 (Conflict): {}", toolName, truncate(responseBody));
return Result.failure("Đối tượng đã tồn tại hoặc xung đột dữ liệu. Vui lòng kiểm tra lại.");
}
case 401, 403 -> {
log.error("Tool {} received HTTP {} (Auth Error): {}", toolName, status, truncate(responseBody));
return Result.failure("Không có quyền truy cập. Vui lòng liên hệ quản trị viên.");
}
default -> {
if (status >= 500) {
log.error("Tool {} received HTTP {} (Server Error): {}", toolName, status, truncate(responseBody));
return Result.of(null, "Lỗi hệ thống nội bộ từ Core API. Vui lòng thử lại sau.");
}
log.error("Tool {} received unexpected HTTP {}: {}", toolName, status, truncate(responseBody));
return Result.of(null, "Lỗi không xác định từ Core API (HTTP " + status + "). Vui lòng thử lại.");
}
}
}
/**
* Extract a human-readable error message from Core API response body.
* Tries JSON "message" field first, falls back to raw body (truncated).
*/
private String extractErrorMessage(String responseBody) {
if (responseBody == null || responseBody.isBlank()) {
return "Vui lòng kiểm tra lại dữ liệu đầu vào.";
}
// Simple extraction: look for "message" field in JSON response
// Core API typically returns {"errorCode": "...", "message": "..."}
int msgIdx = responseBody.indexOf("\"message\"");
if (msgIdx >= 0) {
int colonIdx = responseBody.indexOf(':', msgIdx);
int quoteStart = responseBody.indexOf('"', colonIdx + 1);
int quoteEnd = responseBody.indexOf('"', quoteStart + 1);
if (quoteStart >= 0 && quoteEnd > quoteStart) {
return responseBody.substring(quoteStart + 1, quoteEnd);
}
}
return truncate(responseBody);
}
private String truncate(String s) {
if (s == null) return "";
return s.length() > 300 ? s.substring(0, 300) + "..." : s;
}
}

View File

@@ -0,0 +1,17 @@
package dev.sonpx.loyalty.mcp.attribute.config;
import dev.sonpx.loyalty.mcp.attribute.service.DynamicAttributeClient;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.service.invoker.HttpServiceProxyFactory;
@Configuration
public class DynamicAttributeConfig {
@Bean
public DynamicAttributeClient attributeClient(
@Qualifier("attributeHttpServiceProxyFactory") HttpServiceProxyFactory factory) {
return factory.createClient(DynamicAttributeClient.class);
}
}

View File

@@ -0,0 +1,12 @@
package dev.sonpx.loyalty.mcp.attribute.model;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class DynamicAttributeCriteria {
private String search;
private int page = 1;
private int size = 10;
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright© OneEmpower Pte Ltd. All rights reserved.
*
* This work contains trade secrets and confidential material of
* OneEmpower Pte Ltd, and its unauthorised dissemination, use or
* disclosure in whole or in part without explicit written
* permission of OneEmpower Pte Ltd is strictly prohibited.
*/
package dev.sonpx.loyalty.mcp.attribute.model;
import java.time.LocalDate;
import java.util.List;
import dev.sonpx.loyalty.mcp.model.AttributeDataType;
import dev.sonpx.loyalty.mcp.model.AttributeEntityType;
import dev.sonpx.loyalty.mcp.model.ReferenceData;
import lombok.Getter;
import lombok.Setter;
@Setter
@Getter
public class DynamicAttributeDto {
private AttributeEntityType entity;
private String attributeId;
private String attributeName;
private String description;
private LocalDate effectiveFrom;
private LocalDate effectiveTo;
private AttributeDataType dataType;
private String semanticType;
private String defaultValue;
private Boolean preDefineAttributeValue = false;
List<String> listOfValue;
private List<ReferenceData> attributeGroups;
}

View File

@@ -0,0 +1,21 @@
package dev.sonpx.loyalty.mcp.attribute.service;
import dev.sonpx.loyalty.mcp.model.Result;
import dev.sonpx.loyalty.mcp.attribute.model.DynamicAttributeDto;
import dev.sonpx.loyalty.mcp.attribute.model.DynamicAttributeCriteria;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.service.annotation.GetExchange;
import org.springframework.web.service.annotation.HttpExchange;
import org.springframework.web.bind.annotation.ModelAttribute;
import java.util.List;
@HttpExchange
public interface DynamicAttributeClient {
@GetExchange("/api/dynamic-attribute/csr/list")
Result<List<DynamicAttributeDto>> search(@ModelAttribute DynamicAttributeCriteria criteria);
@GetExchange("/api/dynamic-attribute/csr/id/{id}")
Result<DynamicAttributeDto> getById(@PathVariable("id") String id);
}

View File

@@ -0,0 +1,36 @@
package dev.sonpx.loyalty.mcp.attribute.service;
import dev.sonpx.loyalty.mcp.model.Result;
import dev.sonpx.loyalty.mcp.attribute.model.DynamicAttributeDto;
import dev.sonpx.loyalty.mcp.attribute.model.DynamicAttributeCriteria;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.List;
@Slf4j
@Service
@RequiredArgsConstructor
public class DynamicAttributeService {
private final DynamicAttributeClient client;
public Result<List<DynamicAttributeDto>> search(DynamicAttributeCriteria criteria) {
try {
return client.search(criteria);
} catch (Exception e) {
log.error("Failed to search DynamicAttribute", e);
return Result.failure(e.getMessage());
}
}
public Result<DynamicAttributeDto> getById(String id) {
try {
return client.getById(id);
} catch (Exception e) {
log.error("Failed to get DynamicAttribute by id", e);
return Result.failure(e.getMessage());
}
}
}

View File

@@ -0,0 +1,19 @@
package dev.sonpx.loyalty.mcp.catalogue.client;
import dev.sonpx.loyalty.mcp.catalogue.criteria.CatalogueCriteria;
import dev.sonpx.loyalty.mcp.catalogue.model.Catalogue;
import java.util.List;
import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.service.annotation.GetExchange;
import org.springframework.web.service.annotation.HttpExchange;
@HttpExchange
public interface CatalogueClient {
@GetExchange("/api/item/csr/list")
List<Catalogue> getAll(CatalogueCriteria criteria, Pageable pageable);
@GetExchange("/api/item/csr/id/{id}")
Catalogue findActiveById(@PathVariable String id);
}

View File

@@ -0,0 +1,11 @@
package dev.sonpx.loyalty.mcp.catalogue.criteria;
import dev.sonpx.loyalty.mcp.filter.StringFilter;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = false)
public class CatalogueCriteria {
private String search;
}

View File

@@ -0,0 +1,122 @@
/*
* Copyright© OneEmpower Pte Ltd. All rights reserved.
*
* This work contains trade secrets and confidential material of
* OneEmpower Pte Ltd, and its unauthorised dissemination, use or
* disclosure in whole or in part without explicit written
* permission of OneEmpower Pte Ltd is strictly prohibited.
*/
package dev.sonpx.loyalty.mcp.catalogue.model;
import dev.sonpx.loyalty.mcp.model.ReferenceData;
import java.time.LocalDate;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class Catalogue {
private String itemCode;
private String itemName;
private String defaultLanguage;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
private LocalDate effectiveFrom;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
private LocalDate effectiveTo;
private List<ReferenceData> category;
private ReferenceData supplierId;
private ReferenceData redTransactionCode;
private ReferenceData awdTransactionCode;
private String itemType;
private Double itemValue;
private String voucherSetupOption;
private ReferenceData voucherCode;
private ItemVoucherLinkedDto itemVoucherLinked;
private boolean trackQuantityOnHand;
private Integer quantityOnHand;
private String lowStockAlertId;
private String lowStockMessage;
private String lowStockAlertGroup;
/**
* Submodules
*/
private List<ItemDetailLocaleDto> itemDetails;
private List<ItemImageDto> itemImages;
private List<ItemPriceDto> itemPrices;
private List<LimitOnRedemptionDto> limitOnRedemptions;
private List<ItemContentSmsDto> itemContentSms;
private List<ItemContentEmailDto> itemContentEmail;
// -------------------------------------------------------------------
private ReferenceData catalogueCode;
private Double faceValue;
private Integer initQuantityOfStock;
private Integer quantityRedeem;
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
private Long numberExchange;
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
private Boolean hasRedeemed;
private ReferenceData storeId;
private String hotItem;
private String featuredItem;
private ReferenceData brandId;
private ReferenceData corporationId;
private List<ItemStoreDto> itemStores;
private List<ItemContentNotificationDto> itemContentNotifications;
private List<String> redemptionModes;
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
private String recordNoActive;
private Map<String, Long> numberItemsByCategories;
private List<ReferenceData> itemNameLanguages;
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright© OneEmpower Pte Ltd. All rights reserved.
*
* This work contains trade secrets and confidential material of
* OneEmpower Pte Ltd, and its unauthorised dissemination, use or
* disclosure in whole or in part without explicit written
* permission of OneEmpower Pte Ltd is strictly prohibited.
*/
package dev.sonpx.loyalty.mcp.catalogue.model;
import dev.sonpx.loyalty.mcp.model.ReferenceData;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class ItemContentEmailDto {
@JsonFormat(shape = JsonFormat.Shape.STRING)
private Long itemRecordNo;
private String itemCode;
private String sender;
private ReferenceData messageId;
private String fulfillmentStatus;
private String subject;
private String body;
private String design;
private String metadata;
private String attachments;
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright© OneEmpower Pte Ltd. All rights reserved.
*
* This work contains trade secrets and confidential material of
* OneEmpower Pte Ltd, and its unauthorised dissemination, use or
* disclosure in whole or in part without explicit written
* permission of OneEmpower Pte Ltd is strictly prohibited.
*/
package dev.sonpx.loyalty.mcp.catalogue.model;
import dev.sonpx.loyalty.mcp.model.ReferenceData;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonUnwrapped;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
/**
* @author AnhDT
* Created on 2024/10/21
*/
@Getter
@Setter
public class ItemContentNotificationDto {
@JsonFormat(shape = JsonFormat.Shape.STRING)
private Long itemRecordNo;
private String itemCode;
private String fulfillmentStatus;
private ReferenceData messageId;
private String notificationContentId;
private String languageCode;
private String title;
private String shortContent;
private String content;
private ReferenceData navigationType;
private String link;
private ReferenceData targetScreen;
private ReferenceData poolId;
private ReferenceData offerGroup;
private ReferenceData categoryId;
private ReferenceData referenceNo;
private ReferenceData campaignId;
private ReferenceData itemCodeContent;
private ReferenceData notificationType;
private Boolean richMedia;
private Object avatar;
private List<Object> relatedButtons;
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright© OneEmpower Pte Ltd. All rights reserved.
*
* This work contains trade secrets and confidential material of
* OneEmpower Pte Ltd, and its unauthorised dissemination, use or
* disclosure in whole or in part without explicit written
* permission of OneEmpower Pte Ltd is strictly prohibited.
*/
package dev.sonpx.loyalty.mcp.catalogue.model;
import dev.sonpx.loyalty.mcp.model.ReferenceData;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class ItemContentSmsDto {
@JsonFormat(shape = JsonFormat.Shape.STRING)
private Long itemRecordNo;
private String itemCode;
private ReferenceData smsBrand;
private ReferenceData messageId;
private String fulfillmentStatus;
private String message;
private Boolean refusedPromotion;
private String messageCode;
private String phone;
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright© OneEmpower Pte Ltd. All rights reserved.
*
* This work contains trade secrets and confidential material of
* OneEmpower Pte Ltd, and its unauthorised dissemination, use or
* disclosure in whole or in part without explicit written
* permission of OneEmpower Pte Ltd is strictly prohibited.
*/
package dev.sonpx.loyalty.mcp.catalogue.model;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class ItemDetailLocaleDto {
@JsonFormat(shape = JsonFormat.Shape.STRING)
protected Long itemRecordNo;
private String itemCode;
private String locale;
private String itemName;
private String itemDescription;
private String itemContent;
private String howToUse;
private String termAndCondition;
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright© OneEmpower Pte Ltd. All rights reserved.
*
* This work contains trade secrets and confidential material of
* OneEmpower Pte Ltd, and its unauthorised dissemination, use or
* disclosure in whole or in part without explicit written
* permission of OneEmpower Pte Ltd is strictly prohibited.
*/
package dev.sonpx.loyalty.mcp.catalogue.model;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class ItemImageDto {
@JsonFormat(shape = JsonFormat.Shape.STRING)
protected Long itemRecordNo;
private String itemCode;
private Object image;
private Integer displayOrder;
private Boolean isCover;
}

View File

@@ -0,0 +1,76 @@
/*
* Copyright© OneEmpower Pte Ltd. All rights reserved.
*
* This work contains trade secrets and confidential material of
* OneEmpower Pte Ltd, and its unauthorised dissemination, use or
* disclosure in whole or in part without explicit written
* permission of OneEmpower Pte Ltd is strictly prohibited.
*/
package dev.sonpx.loyalty.mcp.catalogue.model;
import dev.sonpx.loyalty.mcp.model.ReferenceData;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class ItemPriceDto {
@JsonFormat(shape = JsonFormat.Shape.STRING)
protected Long itemRecordNo;
private String itemCode;
private String priceCode;
private String priceName;
private LocalDate effectiveFrom;
private LocalDate effectiveTo;
private List<String> redemptionChannel;
private ReferenceData poolId;
private Object entityLevel;
private BigDecimal priceInPoint;
private BigDecimal faceValue;
private Boolean partialRedemption;
private String fullItemPriceInCash;
private String minPartialRedeemPoint;
private BigDecimal internalCost;
// ------------------------------------------
private Boolean useOnlyPoint;
private BigDecimal originalPrice;
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
private Double discountValue;
private BigDecimal cashValuePerPoint;
private String taxRate;
private BigDecimal cashPerTransaction;
private String pointPerTransaction;
private String criteria;
private ReferenceData campaignId;
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright© OneEmpower Pte Ltd. All rights reserved.
*
* This work contains trade secrets and confidential material of
* OneEmpower Pte Ltd, and its unauthorised dissemination, use or
* disclosure in whole or in part without explicit written
* permission of OneEmpower Pte Ltd is strictly prohibited.
*/
package dev.sonpx.loyalty.mcp.catalogue.model;
import dev.sonpx.loyalty.mcp.model.ReferenceData;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class ItemStoreDto {
@JsonFormat(shape = JsonFormat.Shape.STRING)
protected Long itemRecordNo;
private String itemCode;
private ReferenceData storeId;
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright© OneEmpower Pte Ltd. All rights reserved.
*
* This work contains trade secrets and confidential material of
* OneEmpower Pte Ltd, and its unauthorised dissemination, use or
* disclosure in whole or in part without explicit written
* permission of OneEmpower Pte Ltd is strictly prohibited.
*/
package dev.sonpx.loyalty.mcp.catalogue.model;
import dev.sonpx.loyalty.mcp.model.ReferenceData;
import java.time.LocalDate;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class ItemVoucherLinkedDto {
private String voucherCode;
private String itemCode;
private ReferenceData supplierId;
private String expiryPolicy;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
private LocalDate expiryDate;
private Integer expiryParams;
private Integer quantity;
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright© OneEmpower Pte Ltd. All rights reserved.
*
* This work contains trade secrets and confidential material of
* OneEmpower Pte Ltd, and its unauthorised dissemination, use or
* disclosure in whole or in part without explicit written
* permission of OneEmpower Pte Ltd is strictly prohibited.
*/
package dev.sonpx.loyalty.mcp.catalogue.model;
import java.math.BigDecimal;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class LimitOnRedemptionDto {
@JsonFormat(shape = JsonFormat.Shape.STRING)
private Long itemRecordNo;
private String itemCode;
private BigDecimal allowNotMoreThan;
private String unit;
private String perEntity;
private String perPeriod;
}

View File

@@ -35,14 +35,97 @@ public class RestClientConfig {
@Bean
public RestClient rewardRestClient(OAuth2AuthorizedClientManager authorizedClientManager) {
return createBaseRestClient("/svc/reward", authorizedClientManager);
}
@Bean
public HttpServiceProxyFactory rewardHttpServiceProxyFactory(RestClient rewardRestClient) {
return createHttpServiceProxyFactory(rewardRestClient);
}
@Bean
public RestClient catalogueRestClient(OAuth2AuthorizedClientManager authorizedClientManager) {
return createBaseRestClient("/svc/catalogue", authorizedClientManager);
}
@Bean
public HttpServiceProxyFactory catalogueHttpServiceProxyFactory(RestClient catalogueRestClient) {
return createHttpServiceProxyFactory(catalogueRestClient);
}
@Bean
public RestClient customerRestClient(OAuth2AuthorizedClientManager authorizedClientManager) {
return createBaseRestClient("/svc/customer", authorizedClientManager);
}
@Bean
public HttpServiceProxyFactory customerHttpServiceProxyFactory(RestClient customerRestClient) {
return createHttpServiceProxyFactory(customerRestClient);
}
@Bean
public RestClient transactionRestClient(OAuth2AuthorizedClientManager authorizedClientManager) {
return createBaseRestClient("/svc/transaction", authorizedClientManager);
}
@Bean
public HttpServiceProxyFactory transactionHttpServiceProxyFactory(RestClient transactionRestClient) {
return createHttpServiceProxyFactory(transactionRestClient);
}
@Bean
public RestClient identityRestClient(OAuth2AuthorizedClientManager authorizedClientManager) {
return createBaseRestClient("/svc/identity", authorizedClientManager);
}
@Bean
public HttpServiceProxyFactory identityHttpServiceProxyFactory(RestClient identityRestClient) {
return createHttpServiceProxyFactory(identityRestClient);
}
@Bean
public RestClient masterRestClient(OAuth2AuthorizedClientManager authorizedClientManager) {
return createBaseRestClient("/svc/master", authorizedClientManager);
}
@Bean
public HttpServiceProxyFactory masterHttpServiceProxyFactory(RestClient masterRestClient) {
return createHttpServiceProxyFactory(masterRestClient);
}
@Bean
public RestClient marketingRestClient(OAuth2AuthorizedClientManager authorizedClientManager) {
return createBaseRestClient("/svc/marketing", authorizedClientManager);
}
@Bean
public HttpServiceProxyFactory marketingHttpServiceProxyFactory(RestClient marketingRestClient) {
return createHttpServiceProxyFactory(marketingRestClient);
}
@Bean
public RestClient attributeRestClient(OAuth2AuthorizedClientManager authorizedClientManager) {
return createBaseRestClient("/svc/attribute", authorizedClientManager);
}
@Bean
public HttpServiceProxyFactory attributeHttpServiceProxyFactory(RestClient attributeRestClient) {
return createHttpServiceProxyFactory(attributeRestClient);
}
private RestClient createBaseRestClient(String servicePath, OAuth2AuthorizedClientManager authorizedClientManager) {
OAuth2ClientHttpRequestInterceptor oauth2Interceptor =
new OAuth2ClientHttpRequestInterceptor(authorizedClientManager);
oauth2Interceptor.setClientRegistrationIdResolver(new RequestAttributeClientRegistrationIdResolver());
oauth2Interceptor.setPrincipalResolver(new RequestAttributePrincipalResolver());
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setConnectTimeout(java.time.Duration.ofSeconds(10));
requestFactory.setReadTimeout(java.time.Duration.ofSeconds(30));
return RestClient.builder()
.baseUrl(coreBaseUrl + "/svc/reward")
.requestFactory(new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory()))
.baseUrl(coreBaseUrl + servicePath)
.requestFactory(new BufferingClientHttpRequestFactory(requestFactory))
.requestInterceptor((request, body, execution) -> {
clientRegistrationId("keycloak").accept(request.getAttributes());
principal("loyalty-service").accept(request.getAttributes());
@@ -55,8 +138,9 @@ public class RestClientConfig {
ClientHttpResponse response = oauth2Interceptor.intercept(request, body, execution);
log.info("MCP-Agent Call API [Response Status] <- {}", response.getStatusCode());
if (response.getBody() != null) {
log.info("MCP-Agent Call API [Response Body] <- {}", StreamUtils.copyToString(response.getBody(), StandardCharsets.UTF_8));
if (log.isDebugEnabled() && response.getBody() != null) {
String body2 = StreamUtils.copyToString(response.getBody(), StandardCharsets.UTF_8);
log.debug("MCP-Agent Call API [Response Body] <- {}", body2.length() > 2000 ? body2.substring(0, 2000) + "...(truncated)" : body2);
}
return response;
@@ -64,16 +148,19 @@ public class RestClientConfig {
.defaultStatusHandler(
HttpStatusCode::isError,
(request, response) -> {
throw new RuntimeException("API Error: HTTP Status " + response.getStatusCode() + " Body: " + new String(response.getBody().readAllBytes()));
String errorBody = new String(response.getBody().readAllBytes(), StandardCharsets.UTF_8);
log.error("Loyalty Core API error: HTTP {} for {} {}: {}", response.getStatusCode(), request.getMethod(), request.getURI(), errorBody.length() > 500 ? errorBody.substring(0, 500) : errorBody);
// Preserve HTTP status code for error categorization in GlobalToolExceptionHandlerAspect
throw org.springframework.web.client.HttpClientErrorException.create(
errorBody, response.getStatusCode(), response.getStatusText(),
response.getHeaders(), errorBody.getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8);
}
)
.build();
}
@Bean
public HttpServiceProxyFactory rewardHttpServiceProxyFactory(RestClient rewardRestClient) {
return HttpServiceProxyFactory.builderFor(RestClientAdapter.create(rewardRestClient))
private HttpServiceProxyFactory createHttpServiceProxyFactory(RestClient restClient) {
return HttpServiceProxyFactory.builderFor(RestClientAdapter.create(restClient))
.customArgumentResolver(new PageableArgumentResolver())
.customArgumentResolver(new PojoArgumentResolver())
.build();

View File

@@ -0,0 +1,19 @@
package dev.sonpx.loyalty.mcp.customer.client;
import dev.sonpx.loyalty.mcp.customer.criteria.CustomerCriteria;
import dev.sonpx.loyalty.mcp.customer.model.Customer;
import java.util.List;
import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.service.annotation.GetExchange;
import org.springframework.web.service.annotation.HttpExchange;
@HttpExchange
public interface CustomerClient {
@GetExchange("/api/client/csr/list")
List<Customer> getAll(CustomerCriteria criteria, Pageable pageable);
@GetExchange("/api/client/csr/id/{id}")
Customer findActiveById(@PathVariable String id);
}

View File

@@ -0,0 +1,11 @@
package dev.sonpx.loyalty.mcp.customer.criteria;
import dev.sonpx.loyalty.mcp.filter.StringFilter;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = false)
public class CustomerCriteria {
private String search;
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright© OneEmpower Pte Ltd. All rights reserved.
*
* This work contains trade secrets and confidential material of
* OneEmpower Pte Ltd, and its unauthorised dissemination, use or
* disclosure in whole or in part without explicit written
* permission of OneEmpower Pte Ltd is strictly prohibited.
*/
package dev.sonpx.loyalty.mcp.customer.model;
import dev.sonpx.loyalty.mcp.model.ReferenceData;
import lombok.Getter;
import lombok.Setter;
/**
* PhuDao created on 16/10/2023
*/
@Getter
@Setter
public class ClientCorrDto {
private Long clientRecordNo;
private String csn;
private String email;
private String mobile;
private String addressLine1;
private String addressLine2;
private String postalCode;
private ReferenceData zipCode;
private ReferenceData city;
private ReferenceData state;
private String deviceId;
private String deviceName;
private Object addressType;
}

View File

@@ -0,0 +1,97 @@
/*
* Copyright© OneEmpower Pte Ltd. All rights reserved.
*
* This work contains trade secrets and confidential material of
* OneEmpower Pte Ltd, and its unauthorised dissemination, use or
* disclosure in whole or in part without explicit written
* permission of OneEmpower Pte Ltd is strictly prohibited.
*/
package dev.sonpx.loyalty.mcp.customer.model;
import dev.sonpx.loyalty.mcp.model.ReferenceData;
import java.math.BigDecimal;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import lombok.Getter;
import lombok.Setter;
/**
* PhuDao created on 16/10/2023
*/
@Getter
@Setter
public class Customer {
private String csn;
private String cifNbr;
private HolderType holderType;
private String holderId;
private String clientName;
private String firstName;
private String lastName;
private LocalDate birthDate;
private LocalDate registrationDate;
private Gender gender;
private ReferenceData customerStatus;
private ReferenceData countryOrigin;
private String blockCode;
private MaritalStatus maritalStatus;
private BigDecimal income;
private String profilePicture;
private String profilePictureUrl;
private String profileLanguage;
private String batchNo;
private Instant maintenanceDate;
// For primary contact
private String mobile;
private String email;
private String address;
private String address2;
private ReferenceData country;
private ReferenceData zipCode;
private ReferenceData city;
private ReferenceData state;
private LocalDateTime firstLoginDate;
private LocalDateTime lastLoginDate;
private List<ClientCorrDto> clientCorrs;
public String getClientName() {
return this.firstName + " " + this.lastName;
}
public void setClientName(String clientName) {
this.clientName = this.firstName + " " + this.lastName;
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright© OneEmpower Pte Ltd. All rights reserved.
*
* This work contains trade secrets and confidential material of
* OneEmpower Pte Ltd, and its unauthorised dissemination, use or
* disclosure in whole or in part without explicit written
* permission of OneEmpower Pte Ltd is strictly prohibited.
*/
package dev.sonpx.loyalty.mcp.customer.model;
import java.util.Arrays;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
/**
* enum for rule criteria
* Created by Phudao on Tuesday, 12-Dec-2023
*/
public enum Gender {
MALE("M"),
FEMALE("F"),
OTHER("O");
@JsonValue
private final String code;
@JsonCreator
public static Gender fromCode(String code) {
return Arrays.stream(values())
.filter(e -> e.code.equalsIgnoreCase(code))
.findFirst()
.orElse(null);
}
Gender(String code) {
this.code = code;
}
public String getCode() {
return this.code;
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright© OneEmpower Pte Ltd. All rights reserved.
*
* This work contains trade secrets and confidential material of
* OneEmpower Pte Ltd, and its unauthorised dissemination, use or
* disclosure in whole or in part without explicit written
* permission of OneEmpower Pte Ltd is strictly prohibited.
*/
package dev.sonpx.loyalty.mcp.customer.model;
import java.util.Arrays;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
/**
* enum for rule criteria
* Created by Phudao on Tuesday, 12-Dec-2023
*/
public enum HolderType {
PASSPORT("P"),
ID_CARD("I");
@JsonValue
private final String code;
@JsonCreator
public static HolderType fromCode(String code) {
return Arrays.stream(values())
.filter(e -> e.code.equalsIgnoreCase(code))
.findFirst()
.orElse(null);
}
HolderType(String code) {
this.code = code;
}
public String getCode() {
return this.code;
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright© OneEmpower Pte Ltd. All rights reserved.
*
* This work contains trade secrets and confidential material of
* OneEmpower Pte Ltd, and its unauthorised dissemination, use or
* disclosure in whole or in part without explicit written
* permission of OneEmpower Pte Ltd is strictly prohibited.
*/
package dev.sonpx.loyalty.mcp.customer.model;
import java.util.Arrays;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
/**
* enum for rule criteria
* Created by Phudao on Tuesday, 12-Dec-2023
*/
public enum MaritalStatus {
SINGLE("S"),
MARRIED("M"),
OTHER("O");
@JsonValue
private final String code;
@JsonCreator
public static MaritalStatus fromCode(String code) {
return Arrays.stream(values())
.filter(e -> e.code.equalsIgnoreCase(code))
.findFirst()
.orElse(null);
}
MaritalStatus(String code) {
this.code = code;
}
public String getCode() {
return this.code;
}
}

View File

@@ -2,11 +2,7 @@
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"),
@@ -22,4 +18,12 @@ public enum AttributeDataType {
.findFirst()
.orElse(null);
}
AttributeDataType(String code) {
this.code = code;
}
public String getCode() {
return this.code;
}
}

View File

@@ -4,14 +4,10 @@ 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 Monday, 20-Jul-2026
*/
@Getter
@RequiredArgsConstructor
public enum AttributeEntityType {
CLIENT("CLIENT"),
PRODUCT_ACCOUNT("PRODUCT_ACCOUNT"),
@@ -33,4 +29,12 @@ public enum AttributeEntityType {
.findFirst()
.orElse(null);
}
AttributeEntityType(String code) {
this.code = code;
}
public String getCode() {
return this.code;
}
}

View File

@@ -4,11 +4,7 @@ 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"),
@@ -24,4 +20,12 @@ public enum AttributeType {
.findFirst()
.orElse(null);
}
AttributeType(String code) {
this.code = code;
}
public String getCode() {
return this.code;
}
}

View File

@@ -4,11 +4,7 @@ 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"),
@@ -24,4 +20,12 @@ public enum CounterBucket {
.findFirst()
.orElse(null);
}
CounterBucket(String code) {
this.code = code;
}
public String getCode() {
return this.code;
}
}

View File

@@ -4,11 +4,7 @@ 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"),
@@ -26,4 +22,12 @@ public enum CounterEntity {
.findFirst()
.orElse(null);
}
CounterEntity(String code) {
this.code = code;
}
public String getCode() {
return this.code;
}
}

View File

@@ -4,14 +4,10 @@ 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"),
@@ -39,4 +35,12 @@ public enum CounterPeriod {
.orElse(null);
}
CounterPeriod(String code) {
this.code = code;
}
public String getCode() {
return this.code;
}
}

View File

@@ -2,6 +2,7 @@
package dev.sonpx.loyalty.mcp.enums;
import java.util.Arrays;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@@ -41,4 +42,5 @@ public enum DerivedType {
public static boolean isAudienceType(String code) {
return DerivedType.AUDIENCE.code.equalsIgnoreCase(code) || DerivedType.SEGMENT.code.equalsIgnoreCase(code);
}
}

View File

@@ -4,14 +4,10 @@ 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"),
@@ -28,4 +24,12 @@ public enum EntityLevel {
.findFirst()
.orElse(null);
}
EntityLevel(String code) {
this.code = code;
}
public String getCode() {
return this.code;
}
}

View File

@@ -4,11 +4,7 @@ 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"),
@@ -29,4 +25,12 @@ public enum ExpiryPolicy {
.findFirst()
.orElse(null);
}
ExpiryPolicy(String code) {
this.code = code;
}
public String getCode() {
return this.code;
}
}

View File

@@ -4,11 +4,7 @@ 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
@@ -23,4 +19,12 @@ public enum FileStoreType {
.findFirst()
.orElse(null);
}
FileStoreType(String code) {
this.code = code;
}
public String getCode() {
return this.code;
}
}

View File

@@ -4,11 +4,7 @@ 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"),
@@ -24,4 +20,12 @@ public enum ImageSourceType {
.findFirst()
.orElse(null);
}
ImageSourceType(String code) {
this.code = code;
}
public String getCode() {
return this.code;
}
}

View File

@@ -3,11 +3,7 @@ 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"}. */
@@ -30,4 +26,12 @@ public enum RecordStatus implements EnumBase {
}
return null;
}
RecordStatus(String code) {
this.code = code;
}
public String getCode() {
return this.code;
}
}

View File

@@ -5,8 +5,6 @@ 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;
/**
* Created by SonPhung on Monday, 20-Jul-2026
@@ -15,8 +13,6 @@ import lombok.RequiredArgsConstructor;
* {@link #LAST_3_MONTHS},
* {@link #LAST_6_MONTHS}
*/
@Getter
@RequiredArgsConstructor
public enum StatisticPeriod {
TODAY("TODAY"),
LAST_7_DAYS("L7D"),
@@ -38,4 +34,12 @@ public enum StatisticPeriod {
public boolean isAny(StatisticPeriod... periods) {
return List.of(periods).contains(this);
}
StatisticPeriod(String code) {
this.code = code;
}
public String getCode() {
return this.code;
}
}

View File

@@ -5,11 +5,7 @@ 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"),
@@ -53,4 +49,12 @@ public enum DayOfMonth implements EnumBase {
.findFirst()
.orElse(null);
}
DayOfMonth(String code) {
this.code = code;
}
public String getCode() {
return this.code;
}
}

View File

@@ -5,11 +5,7 @@ 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"),
@@ -29,4 +25,12 @@ public enum DayOfWeek implements EnumBase {
.findFirst()
.orElse(null);
}
DayOfWeek(String code) {
this.code = code;
}
public String getCode() {
return this.code;
}
}

View File

@@ -5,11 +5,7 @@ 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"),
@@ -34,4 +30,12 @@ public enum MonthOfYear implements EnumBase {
.findFirst()
.orElse(null);
}
MonthOfYear(String code) {
this.code = code;
}
public String getCode() {
return this.code;
}
}

View File

@@ -5,11 +5,7 @@ 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"),
@@ -26,4 +22,12 @@ public enum PeriodicType implements EnumBase {
.findFirst()
.orElse(null);
}
PeriodicType(String code) {
this.code = code;
}
public String getCode() {
return this.code;
}
}

View File

@@ -5,11 +5,7 @@ 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");
@@ -24,4 +20,12 @@ public enum ScheduleMonthType implements EnumBase {
.findFirst()
.orElse(null);
}
ScheduleMonthType(String code) {
this.code = code;
}
public String getCode() {
return this.code;
}
}

View File

@@ -5,11 +5,7 @@ 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");
@@ -24,4 +20,12 @@ public enum ScheduleRecurrenceType implements EnumBase {
.findFirst()
.orElse(null);
}
ScheduleRecurrenceType(String code) {
this.code = code;
}
public String getCode() {
return this.code;
}
}

View File

@@ -5,11 +5,7 @@ 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");
@@ -24,4 +20,12 @@ public enum TriggerMethod implements EnumBase {
.findFirst()
.orElse(null);
}
TriggerMethod(String code) {
this.code = code;
}
public String getCode() {
return this.code;
}
}

View File

@@ -5,11 +5,7 @@ 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"),
@@ -28,4 +24,12 @@ public enum WeekOfMonth implements EnumBase {
.findFirst()
.orElse(null);
}
WeekOfMonth(String code) {
this.code = code;
}
public String getCode() {
return this.code;
}
}

View File

@@ -16,7 +16,7 @@ public class GlobalMcpExceptionHandler {
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleException(Exception ex) {
log.error("Global exception caught in MCP server: {}", ex.getMessage());
log.error("Global exception caught in MCP server: {}", ex.getMessage(), ex);
// We return a safe error message instead of the HTML stack trace.
// Returning a generic 500 error prevents the LLM from trying to explain server internals.
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)

View File

@@ -0,0 +1,30 @@
package dev.sonpx.loyalty.mcp.health;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.health.contributor.Health;
import org.springframework.boot.health.contributor.HealthIndicator;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestClient;
@Component
public class CoreApiHealthIndicator implements HealthIndicator {
private final RestClient restClient;
public CoreApiHealthIndicator(@Value("${loyalty.core.base-url:http://localhost:8081}") String baseUrl) {
this.restClient = RestClient.builder().baseUrl(baseUrl).build();
}
@Override
public Health health() {
try {
restClient.get()
.uri("/actuator/health")
.retrieve()
.toBodilessEntity();
return Health.up().build();
} catch (Exception e) {
return Health.down().withException(e).build();
}
}
}

View File

@@ -0,0 +1,17 @@
package dev.sonpx.loyalty.mcp.identity.config;
import dev.sonpx.loyalty.mcp.identity.service.UserClient;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.service.invoker.HttpServiceProxyFactory;
@Configuration
public class UserConfig {
@Bean
public UserClient identityClient(
@Qualifier("identityHttpServiceProxyFactory") HttpServiceProxyFactory factory) {
return factory.createClient(UserClient.class);
}
}

View File

@@ -0,0 +1,12 @@
package dev.sonpx.loyalty.mcp.identity.model;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class UserCriteria {
private String search;
private int page = 1;
private int size = 10;
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright© OneEmpower Pte Ltd. All rights reserved.
*
* This work contains trade secrets and confidential material of
* OneEmpower Pte Ltd, and its unauthorised dissemination, use or
* disclosure in whole or in part without explicit written
* permission of OneEmpower Pte Ltd is strictly prohibited.
*/
package dev.sonpx.loyalty.mcp.identity.model;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonFormat;
import dev.sonpx.loyalty.mcp.model.ReferenceData;
import dev.sonpx.loyalty.mcp.model.LanguageCode;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class UserResponseDto {
private String userId;
private String profileName;
private String email;
private LocalDate profileStartDate;
private LocalDate profileEndDate;
private String userGroup;
private List<ReferenceData> userRoles;
private String reportAccessRight;
private String accessDays;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "HH:mm")
private LocalTime accessTimeStart;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "HH:mm")
private LocalTime accessTimeEnd;
private LanguageCode languageCode;
private String userStatus;
private LocalDateTime createdDate;
/** Merchant Access Level */
private ReferenceData corporation;
private ReferenceData chain;
private ReferenceData store;
}

View File

@@ -0,0 +1,21 @@
package dev.sonpx.loyalty.mcp.identity.service;
import dev.sonpx.loyalty.mcp.model.Result;
import dev.sonpx.loyalty.mcp.identity.model.UserResponseDto;
import dev.sonpx.loyalty.mcp.identity.model.UserCriteria;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.service.annotation.GetExchange;
import org.springframework.web.service.annotation.HttpExchange;
import org.springframework.web.bind.annotation.ModelAttribute;
import java.util.List;
@HttpExchange
public interface UserClient {
@GetExchange("/api/user/csr/list")
Result<List<UserResponseDto>> search(@ModelAttribute UserCriteria criteria);
@GetExchange("/api/user/csr/id/{id}")
Result<UserResponseDto> getById(@PathVariable("id") String id);
}

View File

@@ -0,0 +1,36 @@
package dev.sonpx.loyalty.mcp.identity.service;
import dev.sonpx.loyalty.mcp.model.Result;
import dev.sonpx.loyalty.mcp.identity.model.UserResponseDto;
import dev.sonpx.loyalty.mcp.identity.model.UserCriteria;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.List;
@Slf4j
@Service
@RequiredArgsConstructor
public class UserService {
private final UserClient client;
public Result<List<UserResponseDto>> search(UserCriteria criteria) {
try {
return client.search(criteria);
} catch (Exception e) {
log.error("Failed to search User", e);
return Result.failure(e.getMessage());
}
}
public Result<UserResponseDto> getById(String id) {
try {
return client.getById(id);
} catch (Exception e) {
log.error("Failed to get User by id", e);
return Result.failure(e.getMessage());
}
}
}

View File

@@ -0,0 +1,47 @@
package dev.sonpx.loyalty.mcp.mapper;
import dev.sonpx.loyalty.mcp.reward.model.*;
import dev.sonpx.loyalty.mcp.tool.request.*;
import dev.sonpx.loyalty.mcp.tool.response.*;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import java.util.List;
@Mapper(componentModel = "spring")
public interface McpDtoMapper {
default ReferenceData stringToReferenceData(String code) {
if (code == null) {
return null;
}
return new ReferenceData(code);
}
default String referenceDataToString(ReferenceData refData) {
if (refData == null) {
return null;
}
return refData.getCode();
}
Campaign toEntity(CreateCampaignRequest request);
CampaignResponse toCampaignResponse(Campaign entity);
@Mapping(target = "campaignFormulaTwo.fixedAmt", source = "rewardAmount")
CampaignRule toEntity(CreateCampaignRuleRequest request);
@Mapping(target = "rewardAmount", source = "campaignFormulaTwo.fixedAmt")
CampaignRuleResponse toCampaignRuleResponse(CampaignRule entity);
PoolDefinitionResponse toPoolDefinitionResponse(PoolDefinition entity);
TransactionCodeResponse toDto(TransactionCode entity);
List<TransactionCodeResponse> toTransactionCodeDtoList(List<TransactionCode> entities);
CounterDefinitionResponse toDto(CounterDefinition entity);
List<CounterDefinitionResponse> toCounterDefinitionDtoList(List<CounterDefinition> entities);
DeductionSequenceResponse toDto(DeductionSequences entity);
List<DeductionSequenceResponse> toDeductionSequenceDtoList(List<DeductionSequences> entities);
}

View File

@@ -0,0 +1,17 @@
package dev.sonpx.loyalty.mcp.marketing.config;
import dev.sonpx.loyalty.mcp.marketing.service.JourneyClient;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.service.invoker.HttpServiceProxyFactory;
@Configuration
public class JourneyConfig {
@Bean
public JourneyClient marketingClient(
@Qualifier("marketingHttpServiceProxyFactory") HttpServiceProxyFactory factory) {
return factory.createClient(JourneyClient.class);
}
}

View File

@@ -0,0 +1,12 @@
package dev.sonpx.loyalty.mcp.marketing.model;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class JourneyCriteria {
private String search;
private int page = 1;
private int size = 10;
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright© OneEmpower Pte Ltd. All rights reserved.
*
* This work contains trade secrets and confidential material of
* OneEmpower Pte Ltd, and its unauthorised dissemination, use or
* disclosure in whole or in part without explicit written
* permission of OneEmpower Pte Ltd is strictly prohibited.
*/
package dev.sonpx.loyalty.mcp.marketing.model;
import java.time.LocalDateTime;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Getter;
import lombok.Setter;
/**
* @author AnhDT
* Created on 2025/12/23
*/
@Getter
@Setter
public class JourneyDto {
@JsonFormat(shape = JsonFormat.Shape.STRING)
private String journeyId;
private String name;
private LocalDateTime effectiveFrom;
private LocalDateTime effectiveTo;
private String businessUnit;
private String campaignId;
private String description;
private LocalDateTime createdAt;
// Submodules
private Object journeySetting;
private Object journeyCanvas; // contains nodes and edges
private Object journeySrcAudience;
private List<Object> journeySrcEvents;
private List<Object> journeyActSms;
private List<Object> journeyActEmails;
private List<Object> journeyActPushNotifications;
private List<Object> journeyActInappMessages;
private List<Object> journeyActVouchers;
private List<Object> journeyFlowDurations;
private List<Object> journeyFlowDates;
private List<Object> journeyFlowInteractions;
private List<Object> journeyFlowDecisions;
private List<Object> journeyFlowRandoms;
private List<Object> journeyFlowJoins;
}

View File

@@ -0,0 +1,21 @@
package dev.sonpx.loyalty.mcp.marketing.service;
import dev.sonpx.loyalty.mcp.model.Result;
import dev.sonpx.loyalty.mcp.marketing.model.JourneyDto;
import dev.sonpx.loyalty.mcp.marketing.model.JourneyCriteria;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.service.annotation.GetExchange;
import org.springframework.web.service.annotation.HttpExchange;
import org.springframework.web.bind.annotation.ModelAttribute;
import java.util.List;
@HttpExchange
public interface JourneyClient {
@GetExchange("/api/journey/csr/list")
Result<List<JourneyDto>> search(@ModelAttribute JourneyCriteria criteria);
@GetExchange("/api/journey/csr/id/{id}")
Result<JourneyDto> getById(@PathVariable("id") String id);
}

View File

@@ -0,0 +1,36 @@
package dev.sonpx.loyalty.mcp.marketing.service;
import dev.sonpx.loyalty.mcp.model.Result;
import dev.sonpx.loyalty.mcp.marketing.model.JourneyDto;
import dev.sonpx.loyalty.mcp.marketing.model.JourneyCriteria;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.List;
@Slf4j
@Service
@RequiredArgsConstructor
public class JourneyService {
private final JourneyClient client;
public Result<List<JourneyDto>> search(JourneyCriteria criteria) {
try {
return client.search(criteria);
} catch (Exception e) {
log.error("Failed to search Journey", e);
return Result.failure(e.getMessage());
}
}
public Result<JourneyDto> getById(String id) {
try {
return client.getById(id);
} catch (Exception e) {
log.error("Failed to get Journey by id", e);
return Result.failure(e.getMessage());
}
}
}

View File

@@ -0,0 +1,17 @@
package dev.sonpx.loyalty.mcp.master.config;
import dev.sonpx.loyalty.mcp.master.service.AppParamClient;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.service.invoker.HttpServiceProxyFactory;
@Configuration
public class AppParamConfig {
@Bean
public AppParamClient masterClient(
@Qualifier("masterHttpServiceProxyFactory") HttpServiceProxyFactory factory) {
return factory.createClient(AppParamClient.class);
}
}

View File

@@ -0,0 +1,12 @@
package dev.sonpx.loyalty.mcp.master.model;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class AppParamCriteria {
private String search;
private int page = 1;
private int size = 10;
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright© OneEmpower Pte Ltd. All rights reserved.
*
* This work contains trade secrets and confidential material of
* OneEmpower Pte Ltd, and its unauthorised dissemination, use or
* disclosure in whole or in part without explicit written
* permission of OneEmpower Pte Ltd is strictly prohibited.
*/
package dev.sonpx.loyalty.mcp.master.model;
import dev.sonpx.loyalty.mcp.model.ReferenceData;
import lombok.Getter;
import lombok.Setter;
/**
* Created by AnhNt on Wednesday, 15-Nov-2023
*/
@Getter
@Setter
public class AppParamDto {
private ReferenceData appGroup;
private String key;
private String description;
private String value;
private String semanticType;
private Boolean editable;
}

View File

@@ -0,0 +1,21 @@
package dev.sonpx.loyalty.mcp.master.service;
import dev.sonpx.loyalty.mcp.model.Result;
import dev.sonpx.loyalty.mcp.master.model.AppParamDto;
import dev.sonpx.loyalty.mcp.master.model.AppParamCriteria;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.service.annotation.GetExchange;
import org.springframework.web.service.annotation.HttpExchange;
import org.springframework.web.bind.annotation.ModelAttribute;
import java.util.List;
@HttpExchange
public interface AppParamClient {
@GetExchange("/api/app-param/csr/list")
Result<List<AppParamDto>> search(@ModelAttribute AppParamCriteria criteria);
@GetExchange("/api/app-param/csr/id/{id}")
Result<AppParamDto> getById(@PathVariable("id") String id);
}

View File

@@ -0,0 +1,36 @@
package dev.sonpx.loyalty.mcp.master.service;
import dev.sonpx.loyalty.mcp.model.Result;
import dev.sonpx.loyalty.mcp.master.model.AppParamDto;
import dev.sonpx.loyalty.mcp.master.model.AppParamCriteria;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.List;
@Slf4j
@Service
@RequiredArgsConstructor
public class AppParamService {
private final AppParamClient client;
public Result<List<AppParamDto>> search(AppParamCriteria criteria) {
try {
return client.search(criteria);
} catch (Exception e) {
log.error("Failed to search AppParam", e);
return Result.failure(e.getMessage());
}
}
public Result<AppParamDto> getById(String id) {
try {
return client.getById(id);
} catch (Exception e) {
log.error("Failed to get AppParam by id", e);
return Result.failure(e.getMessage());
}
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright© OneEmpower Pte Ltd. All rights reserved.
*
* This work contains trade secrets and confidential material of
* OneEmpower Pte Ltd, and its unauthorised dissemination, use or
* disclosure in whole or in part without explicit written
* permission of OneEmpower Pte Ltd is strictly prohibited.
*/
package dev.sonpx.loyalty.mcp.model;
import java.util.Arrays;
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);
}
AttributeDataType(String code) {
this.code = code;
}
public String getCode() {
return this.code;
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright© OneEmpower Pte Ltd. All rights reserved.
*
* This work contains trade secrets and confidential material of
* OneEmpower Pte Ltd, and its unauthorised dissemination, use or
* disclosure in whole or in part without explicit written
* permission of OneEmpower Pte Ltd is strictly prohibited.
*/
package dev.sonpx.loyalty.mcp.model;
import java.util.Arrays;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
/**
* @author Phudao
* Created on 2024/01/12
*/
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);
}
AttributeEntityType(String code) {
this.code = code;
}
public String getCode() {
return this.code;
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright© OneEmpower Pte Ltd. All rights reserved.
*
* This work contains trade secrets and confidential material of
* OneEmpower Pte Ltd, and its unauthorised dissemination, use or
* disclosure in whole or in part without explicit written
* permission of OneEmpower Pte Ltd is strictly prohibited.
*/
package dev.sonpx.loyalty.mcp.model;
import java.util.Arrays;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
public enum LanguageCode {
ENGLISH("EN"),
VIETNAM("VI");
@JsonValue
private final String code;
@JsonCreator
public static LanguageCode fromCode(String code) {
return Arrays.stream(values())
.filter(e -> e.code.equalsIgnoreCase(code))
.findFirst()
.orElse(LanguageCode.ENGLISH);
}
public static boolean isDefault(String code) {
return LanguageCode.ENGLISH.equals(fromCode(code));
}
LanguageCode(String code) {
this.code = code;
}
public String getCode() {
return this.code;
}
}

View File

@@ -4,14 +4,10 @@ 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 Monday, 20-Jul-2026
*/
@Getter
@RequiredArgsConstructor
public enum AlertChannel {
EMAIL("E");
@@ -25,4 +21,12 @@ public enum AlertChannel {
.findFirst()
.orElse(null);
}
AlertChannel(String code) {
this.code = code;
}
public String getCode() {
return this.code;
}
}

View File

@@ -4,14 +4,10 @@ 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 Monday, 20-Jul-2026
*/
@Getter
@RequiredArgsConstructor
public enum AmountToUseType {
GROSS_AMOUNT("GA"),
NETT_AMOUNT("NA");
@@ -26,4 +22,12 @@ public enum AmountToUseType {
.findFirst()
.orElse(null);
}
AmountToUseType(String code) {
this.code = code;
}
public String getCode() {
return this.code;
}
}

View File

@@ -3,14 +3,11 @@ package dev.sonpx.loyalty.mcp.reward.enums;
import com.fasterxml.jackson.annotation.JsonCreator;
import java.util.Arrays;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
* Created by SonPhung on Monday, 20-Jul-2026
*/
@Getter
@RequiredArgsConstructor
public enum AmountToUseViewType {
CODE(null, null),
ATTRIBUTE("A", "Attribute"),
@@ -19,10 +16,23 @@ public enum AmountToUseViewType {
private final String code;
private final String name;
AmountToUseViewType(String code, String name) {
this.code = code;
this.name = name;
}
public String getCode() {
return code;
}
public String getName() {
return name;
}
@JsonCreator
public static AmountToUseViewType fromCode(String code) {
return Arrays.stream(values())
.filter(e -> e.code.equalsIgnoreCase(code))
.filter(e -> e.code != null && e.code.equalsIgnoreCase(code))
.findFirst()
.orElse(null);
}

View File

@@ -4,14 +4,10 @@ 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");
@@ -26,4 +22,12 @@ public enum AwardBasisType {
.findFirst()
.orElse(null);
}
AwardBasisType(String code) {
this.code = code;
}
public String getCode() {
return this.code;
}
}

View File

@@ -4,14 +4,10 @@ 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");
@@ -26,4 +22,12 @@ public enum AwardFactorType {
.findFirst()
.orElse(null);
}
AwardFactorType(String code) {
this.code = code;
}
public String getCode() {
return this.code;
}
}

View File

@@ -4,16 +4,12 @@ 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");
@@ -28,4 +24,12 @@ public enum AwardLimitType {
.findFirst()
.orElse(null);
}
AwardLimitType(String code) {
this.code = code;
}
public String getCode() {
return this.code;
}
}

View File

@@ -5,14 +5,10 @@ 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;
import lombok.RequiredArgsConstructor;
/**
* Created by SonPhung on Monday, 20-Jul-2026
*/
@Getter
@RequiredArgsConstructor
public enum BaseDateType {
@JsonPropertyDescription("Ngày hệ thống ghi nhận giao dịch")
@@ -31,4 +27,12 @@ public enum BaseDateType {
.findFirst()
.orElse(null);
}
BaseDateType(String code) {
this.code = code;
}
public String getCode() {
return this.code;
}
}

View File

@@ -4,14 +4,10 @@ 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 Monday, 20-Jul-2026
*/
@Getter
@RequiredArgsConstructor
public enum CampaignType {
BASE("B"),
TACTICAL("T");
@@ -26,4 +22,12 @@ public enum CampaignType {
.findFirst()
.orElse(null);
}
CampaignType(String code) {
this.code = code;
}
public String getCode() {
return this.code;
}
}

View File

@@ -4,14 +4,10 @@ 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");
@@ -26,4 +22,12 @@ public enum CapAwardType {
.findFirst()
.orElse(null);
}
CapAwardType(String code) {
this.code = code;
}
public String getCode() {
return this.code;
}
}

View File

@@ -4,14 +4,10 @@ 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"),
@@ -29,4 +25,12 @@ public enum CounterCountType {
.findFirst()
.orElse(null);
}
CounterCountType(String code) {
this.code = code;
}
public String getCode() {
return this.code;
}
}

View File

@@ -4,14 +4,10 @@ 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");
@@ -26,4 +22,12 @@ public enum CounterResetType {
.findFirst()
.orElse(null);
}
CounterResetType(String code) {
this.code = code;
}
public String getCode() {
return this.code;
}
}

View File

@@ -4,14 +4,10 @@ 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"),
@@ -27,4 +23,12 @@ public enum CounterUpdateType {
.findFirst()
.orElse(null);
}
CounterUpdateType(String code) {
this.code = code;
}
public String getCode() {
return this.code;
}
}

View File

@@ -4,11 +4,7 @@ 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"),
@@ -24,4 +20,12 @@ public enum DeductionPriorityType {
.findFirst()
.orElse(null);
}
DeductionPriorityType(String code) {
this.code = code;
}
public String getCode() {
return this.code;
}
}

View File

@@ -4,14 +4,10 @@ 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 Monday, 20-Jul-2026
*/
@Getter
@RequiredArgsConstructor
public enum EventFrequencyType {
ONCE("O"),
MONTHLY("M"),
@@ -27,4 +23,12 @@ public enum EventFrequencyType {
.findFirst()
.orElse(null);
}
EventFrequencyType(String code) {
this.code = code;
}
public String getCode() {
return this.code;
}
}

View File

@@ -4,13 +4,9 @@ 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");
@@ -25,4 +21,12 @@ public enum ExtractBlockedCard {
.findFirst()
.orElse(null);
}
ExtractBlockedCard(String code) {
this.code = code;
}
public String getCode() {
return this.code;
}
}

View File

@@ -4,11 +4,7 @@ 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");
@@ -23,4 +19,12 @@ public enum ExtractNoCounter {
.findFirst()
.orElse(null);
}
ExtractNoCounter(String code) {
this.code = code;
}
public String getCode() {
return this.code;
}
}

View File

@@ -4,14 +4,10 @@ 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 Monday, 20-Jul-2026
*/
@Getter
@RequiredArgsConstructor
public enum LateValuePosting {
UPDATE_LATE_VALUE("L"),
UPDATE_CURRENT_BUCKET("C");
@@ -26,4 +22,12 @@ public enum LateValuePosting {
.findFirst()
.orElse(null);
}
LateValuePosting(String code) {
this.code = code;
}
public String getCode() {
return this.code;
}
}

View File

@@ -5,14 +5,10 @@ import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import java.util.Arrays;
import java.util.Locale;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
* Created by SonPhung on Monday, 20-Jul-2026
*/
@Getter
@RequiredArgsConstructor
public enum MerchantEntityLevel {
CHAIN("C"),
CORPORATION("CO"),
@@ -39,4 +35,11 @@ public enum MerchantEntityLevel {
default -> null;
};
}
MerchantEntityLevel(String code) {
this.code = code;
}
public String getCode() {
return this.code;
}
}

View File

@@ -4,16 +4,12 @@ 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;
/**
* {@link #SMS}
* {@link #EMAIL}
* {@link #PUSH_NOTIFICATIONS}
*/
@Getter
@RequiredArgsConstructor
public enum NotificationChannel {
SMS("SMS"),
EMAIL("EMAIL"),
@@ -29,4 +25,12 @@ public enum NotificationChannel {
.findFirst()
.orElse(null);
}
NotificationChannel(String code) {
this.code = code;
}
public String getCode() {
return this.code;
}
}

View File

@@ -4,14 +4,10 @@ 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 Monday, 20-Jul-2026
*/
@Getter
@RequiredArgsConstructor
public enum PoolType {
POINT("BPT"),
CASH_REBATE("CR"),
@@ -29,4 +25,12 @@ public enum PoolType {
.findFirst()
.orElse(null);
}
PoolType(String code) {
this.code = code;
}
public String getCode() {
return this.code;
}
}

View File

@@ -4,11 +4,7 @@ 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 PostTransactionUnderPa {
BY_BASED_ON_ATG("ACA"), // PA based on ATG of Pool
BY_HIGHEST_SPEND("PHS"), // PA with highest spend in the past month + month to date
@@ -25,4 +21,12 @@ public enum PostTransactionUnderPa {
.findFirst()
.orElse(null);
}
PostTransactionUnderPa(String code) {
this.code = code;
}
public String getCode() {
return this.code;
}
}

View File

@@ -4,14 +4,10 @@ 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 Monday, 20-Jul-2026
*/
@Getter
@RequiredArgsConstructor
public enum PrecisionOfBalance {
ZERO("0"),
ONE("1"),
@@ -27,4 +23,12 @@ public enum PrecisionOfBalance {
.findFirst()
.orElse(null);
}
PrecisionOfBalance(String code) {
this.code = code;
}
public String getCode() {
return this.code;
}
}

View File

@@ -5,11 +5,7 @@ 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;
@Getter
@RequiredArgsConstructor
public enum RepRunSchedule {
DAILY("D"),
MONTHLY("M"),
@@ -33,4 +29,12 @@ public enum RepRunSchedule {
return List.of(DAILY, MONTHLY, ANNUALLY, N_DAYS_AFTER_AOD, N_MONTHS_AFTER_AOD, STATEMENT_CYCLE)
.contains(schedule);
}
RepRunSchedule(String code) {
this.code = code;
}
public String getCode() {
return this.code;
}
}

View File

@@ -4,15 +4,11 @@ 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
* al-capping-limit-type
*/
@Getter
@RequiredArgsConstructor
public enum RewardLimitResultType {
TRACKED_IN_COUNTER("C"),
PER_CAMPAIGN_RULE("R");
@@ -27,4 +23,12 @@ public enum RewardLimitResultType {
.findFirst()
.orElse(null);
}
RewardLimitResultType(String code) {
this.code = code;
}
public String getCode() {
return this.code;
}
}

View File

@@ -4,14 +4,10 @@ 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, 13-Dec-2023
*/
@Getter
@RequiredArgsConstructor
public enum RoundingType {
ROUND_UP("U"),
ROUND_DOWN("D"),
@@ -27,4 +23,12 @@ public enum RoundingType {
.findFirst()
.orElse(null);
}
RoundingType(String code) {
this.code = code;
}
public String getCode() {
return this.code;
}
}

View File

@@ -5,14 +5,10 @@ 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;
import lombok.RequiredArgsConstructor;
/**
* Created by SonPhung on Friday, 10-Nov-2023
*/
@Getter
@RequiredArgsConstructor
public enum RuleType {
@JsonPropertyDescription("Thưởng điểm")
AWARD("AWD"),
@@ -48,4 +44,11 @@ public enum RuleType {
.findFirst()
.orElse(null);
}
RuleType(String code) {
this.code = code;
}
public String getCode() {
return this.code;
}
}

View File

@@ -4,11 +4,7 @@ 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 RunSchedule {
DAILY("D"),
MONTHLY("M"),
@@ -26,4 +22,12 @@ public enum RunSchedule {
.findFirst()
.orElse(null);
}
RunSchedule(String code) {
this.code = code;
}
public String getCode() {
return this.code;
}
}

View File

@@ -4,11 +4,7 @@ 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 TriggerCampaignRule {
AWARD("AWD"),
REDEEM("RED");
@@ -23,4 +19,12 @@ public enum TriggerCampaignRule {
.findFirst()
.orElse(null);
}
TriggerCampaignRule(String code) {
this.code = code;
}
public String getCode() {
return this.code;
}
}

View File

@@ -4,14 +4,10 @@ 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 Monday, 20-Jul-2026
*/
@Getter
@RequiredArgsConstructor
public enum UpdateCounterStateType {
NEXT_STATE("NS"),
PREVIOUS_STATE("PS");
@@ -26,4 +22,12 @@ public enum UpdateCounterStateType {
.findFirst()
.orElse(null);
}
UpdateCounterStateType(String code) {
this.code = code;
}
public String getCode() {
return this.code;
}
}

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