Case Punching Feature Done

This commit is contained in:
2026-08-02 00:47:00 +05:30
parent 452e6189e4
commit af360d7793
67 changed files with 1431 additions and 179 deletions

View File

@@ -1,9 +1,7 @@
package lib.constants;
import java.util.Arrays;
/** Canonical user-facing errors shared by Cygnus applications. */
public enum ApplicationError {
/** Canonical user-facing messages shared by Cygnus applications. */
public enum ApplicationMessage {
BAD_REQUEST(400, "HTTP-400", "Invalid request",
"Cygnus could not process the submitted request.",
"Review the supplied information and try again."),
@@ -28,6 +26,23 @@ public enum ApplicationError {
TOO_MANY_REQUESTS(429, "HTTP-429", "Too many requests",
"Cygnus has received too many requests in a short period.",
"Wait briefly before trying again."),
PAYLOAD_INVALID(400, "REQ-4001", "Invalid request details",
"The encrypted request could not be verified.",
"Refresh the page and submit the form again."),
APPLICATION_VALIDATION_FAILED(422, "APP-4221", "Application validation failed",
"One or more application fields contain invalid information.",
"Correct the highlighted fields and submit the application again."),
APPLICATION_ACCESS_DENIED(403, "APP-4031", "Application access denied",
"The selected portfolio or application is not available to your account.",
"Select an authorized portfolio or contact your administrator."),
APPLICATION_SAVE_CONFLICT(409, "APP-4091", "Application save conflict",
"This request was already processed or the application changed.",
"Refresh the application before submitting it again."),
APPLICATION_SAVED(200, "APP-2001", "Application saved",
"The application details were saved successfully.", ""),
APPLICATION_SAVE_FAILED(500, "APP-5001", "Application could not be saved",
"Cygnus could not save the application details.",
"Try again. If the issue continues, share the reference ID with support."),
INTERNAL_SERVER_ERROR(500, "HTTP-500", "Something went wrong",
"Cygnus could not complete your request.",
"Try again. If the issue continues, share the reference ID with support."),
@@ -47,7 +62,7 @@ public enum ApplicationError {
private final String message;
private final String description;
ApplicationError(
ApplicationMessage(
int httpStatus, String code, String title, String message, String description) {
this.httpStatus = httpStatus;
this.code = code;
@@ -56,11 +71,21 @@ public enum ApplicationError {
this.description = description;
}
public static ApplicationError fromHttpStatus(int status) {
return Arrays.stream(values())
.filter(error -> error.httpStatus == status)
.findFirst()
.orElse(INTERNAL_SERVER_ERROR);
public static ApplicationMessage fromHttpStatus(int status) {
return switch (status) {
case 400 -> BAD_REQUEST;
case 401 -> SESSION_REQUIRED;
case 403 -> ACCESS_DENIED;
case 404 -> PAGE_NOT_FOUND;
case 405 -> METHOD_NOT_ALLOWED;
case 409 -> CONFLICT;
case 413 -> PAYLOAD_TOO_LARGE;
case 429 -> TOO_MANY_REQUESTS;
case 502 -> BAD_GATEWAY;
case 503 -> SERVICE_UNAVAILABLE;
case 504 -> GATEWAY_TIMEOUT;
default -> INTERNAL_SERVER_ERROR;
};
}
public int getHttpStatus() { return httpStatus; }

View File

@@ -0,0 +1,20 @@
package lib.exceptions;
import lib.constants.ApplicationMessage;
/** Single typed application exception carrying a safe user-facing message. */
public class ApplicationException extends RuntimeException {
private final ApplicationMessage applicationMessage;
public ApplicationException(ApplicationMessage message, String technicalMessage) {
super(technicalMessage);
this.applicationMessage = message;
}
public ApplicationException(ApplicationMessage message, String technicalMessage, Throwable cause) {
super(technicalMessage, cause);
this.applicationMessage = message;
}
public ApplicationMessage getApplicationMessage() { return applicationMessage; }
}

View File

@@ -0,0 +1,12 @@
package lib.models;
/** Consistent API response contract for every migrated workflow. */
public record ApiResponse<T>(boolean success, T data, MessageDetails message) {
public static <T> ApiResponse<T> success(T data, MessageDetails message) {
return new ApiResponse<>(true, data, message);
}
public static <T> ApiResponse<T> failure(MessageDetails message) {
return new ApiResponse<>(false, null, message);
}
}

View File

@@ -0,0 +1,87 @@
package lib.models;
import java.io.Serial;
import java.io.Serializable;
import java.util.LinkedHashMap;
import java.util.Map;
import lombok.Getter;
import lombok.Setter;
/** User-editable case initiation data. Identity and audit fields come from UserSession. */
@Getter
@Setter
public class ApplicationDetails implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private Integer applicationId;
private Short portfolioId;
private Short bankBranchId;
private String applicationNumber;
private String bankCode;
private String product;
private String loanAmount;
private String customerName;
private String fatherName;
private String applicationType;
private String category;
private String dateOfBirth;
private String contactPerson;
private String mobileNumber;
private String specialInstruction;
private Boolean residenceVerification;
private Boolean residenceTelephoneVerification;
private Boolean officeVerification;
private Boolean officeTelephoneVerification;
private Boolean propertyVerification;
private Boolean referenceVerification;
private Boolean documentVerification;
private Boolean residenceCoApplicant;
private Boolean residenceCoProprietor;
private Boolean officeCoProprietor;
private Boolean sameResidenceAddress;
private Boolean sameOfficeAddress;
private Boolean samePropertyAddress;
private String residenceAddress1;
private String residenceAddress2;
private String residenceAddress3;
private String residenceLandmark;
private Integer residenceColonyId;
private String residenceCity;
private String residencePincode;
private String residencePhone;
private String companyName;
private String officeAddress1;
private String officeAddress2;
private String officeAddress3;
private String officeLandmark;
private Integer officeColonyId;
private String officeCity;
private String officePincode;
private String department;
private String designation;
private String officePhone;
private String extension;
private String propertyAddress1;
private String propertyAddress2;
private String propertyAddress3;
private String propertyLandmark;
private Integer propertyColonyId;
private String propertyCity;
private String propertyPincode;
private String referenceName1;
private String referenceAddress1;
private String referenceContactNumber1;
private String referenceName2;
private String referenceAddress2;
private String referenceContactNumber2;
private Map<String, String> dynamicFields = new LinkedHashMap<>();
private Boolean autoCutOff;
private Integer formMode;
}

View File

@@ -0,0 +1,4 @@
package lib.models;
/** Data returned after saving a loan-verification application. */
public record ApplicationSaveData(String operation, Integer applicationId, String mvCode) {}

View File

@@ -0,0 +1,16 @@
package lib.models;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
/** Result row returned by the application-save database operation. */
@Getter
@Setter
@NoArgsConstructor
public class ApplicationSaveResult {
private String operation;
private Integer applicationId;
private String mvCode;
private String internalUuid;
}

View File

@@ -9,7 +9,7 @@ import lombok.Setter;
@Getter
@Setter
public class CasePunching {
private ErrorDetails errorDetails;
private MessageDetails messageDetails;
private Map<String, String> optionsl;
private Map<String, List<Option>> options;
private List<String> visibleSections;

View File

@@ -0,0 +1,11 @@
package lib.models;
/** Hybrid RSA-OAEP/AES-GCM envelope submitted by the initiation page. */
public record CaseSaveRequest(
String keyId,
String encryptedKey,
String initializationVector,
String encryptedPayload,
String requestId,
String timestamp) {
}

View File

@@ -1,6 +1,6 @@
package lib.models;
import lib.constants.ApplicationError;
import lib.constants.ApplicationMessage;
import java.io.Serial;
import java.io.Serializable;
import java.time.Instant;
@@ -10,37 +10,37 @@ import lombok.Getter;
/** Dependency-free error information shared across Cygnus modules. */
@Getter
public final class ErrorDetails implements Serializable {
public final class MessageDetails implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private final int httpStatus;
private final String errorCode;
private final String code;
private final String title;
private final String message;
private final String description;
private final String referenceId;
private final Instant timestamp;
public ErrorDetails(ApplicationError error) {
this(error.getHttpStatus(), error.getCode(), error.getTitle(),
error.getMessage(), error.getDescription(),
public MessageDetails(ApplicationMessage message) {
this(message.getHttpStatus(), message.getCode(), message.getTitle(),
message.getMessage(), message.getDescription(),
UUID.randomUUID().toString(), Instant.now());
}
private ErrorDetails(
private MessageDetails(
int httpStatus,
String errorCode,
String code,
String title,
String message,
String description,
String referenceId,
Instant timestamp) {
if (httpStatus < 400 || httpStatus > 599) {
throw new IllegalArgumentException("HTTP error status must be between 400 and 599");
if (httpStatus < 100 || httpStatus > 599) {
throw new IllegalArgumentException("HTTP status must be between 100 and 599");
}
this.httpStatus = httpStatus;
this.errorCode = required(errorCode, "errorCode");
this.code = required(code, "code");
this.title = required(title, "title");
this.message = required(message, "message");
this.description = description == null ? "" : description.trim();

View File

@@ -1,22 +0,0 @@
package lib.models;
import lib.constants.ApplicationError;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
class ErrorDetailsTest {
@Test
void createsAReusableErrorContract() {
ErrorDetails details = new ErrorDetails(ApplicationError.ACCESS_DENIED);
assertEquals(403, details.getHttpStatus());
assertEquals("AUTH-403", details.getErrorCode());
}
@Test
void mapsHttpStatusToCanonicalError() {
assertEquals(ApplicationError.PAGE_NOT_FOUND, ApplicationError.fromHttpStatus(404));
assertEquals(ApplicationError.INTERNAL_SERVER_ERROR,
ApplicationError.fromHttpStatus(599));
}
}

View File

@@ -0,0 +1,22 @@
package lib.models;
import lib.constants.ApplicationMessage;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
class MessageDetailsTest {
@Test
void createsAReusableErrorContract() {
MessageDetails details = new MessageDetails(ApplicationMessage.ACCESS_DENIED);
assertEquals(403, details.getHttpStatus());
assertEquals("AUTH-403", details.getCode());
}
@Test
void mapsHttpStatusToCanonicalError() {
assertEquals(ApplicationMessage.PAGE_NOT_FOUND, ApplicationMessage.fromHttpStatus(404));
assertEquals(ApplicationMessage.INTERNAL_SERVER_ERROR,
ApplicationMessage.fromHttpStatus(599));
}
}

Binary file not shown.

View File

@@ -1,3 +1,12 @@
lib/models/ErrorDetails.class
lib/models/MessageDetails.class
lib/models/CasePunching.class
lib/constants/ApplicationError.class
lib/exceptions/ApplicationException.class
lib/models/Option.class
lib/models/UserSession.class
lib/models/ApiResponse.class
lib/models/ApplicationSaveResult.class
lib/constants/ApplicationMessage.class
lib/models/ApplicationSaveData.class
lib/models/UserSession$UserSessionBuilder.class
lib/models/CaseSaveRequest.class
lib/models/ApplicationDetails.class

View File

@@ -1,5 +1,11 @@
/Users/maddy/Projects/cygnus/matrix/cygnus-lib/src/main/java/lib/constants/ApplicationError.java
/Users/maddy/Projects/cygnus/matrix/cygnus-lib/src/main/java/lib/constants/ApplicationMessage.java
/Users/maddy/Projects/cygnus/matrix/cygnus-lib/src/main/java/lib/exceptions/ApplicationException.java
/Users/maddy/Projects/cygnus/matrix/cygnus-lib/src/main/java/lib/models/ApiResponse.java
/Users/maddy/Projects/cygnus/matrix/cygnus-lib/src/main/java/lib/models/ApplicationDetails.java
/Users/maddy/Projects/cygnus/matrix/cygnus-lib/src/main/java/lib/models/ApplicationSaveData.java
/Users/maddy/Projects/cygnus/matrix/cygnus-lib/src/main/java/lib/models/ApplicationSaveResult.java
/Users/maddy/Projects/cygnus/matrix/cygnus-lib/src/main/java/lib/models/CasePunching.java
/Users/maddy/Projects/cygnus/matrix/cygnus-lib/src/main/java/lib/models/ErrorDetails.java
/Users/maddy/Projects/cygnus/matrix/cygnus-lib/src/main/java/lib/models/CaseSaveRequest.java
/Users/maddy/Projects/cygnus/matrix/cygnus-lib/src/main/java/lib/models/MessageDetails.java
/Users/maddy/Projects/cygnus/matrix/cygnus-lib/src/main/java/lib/models/Option.java
/Users/maddy/Projects/cygnus/matrix/cygnus-lib/src/main/java/lib/models/UserSession.java

View File

@@ -1 +1 @@
lib/models/ErrorDetailsTest.class
lib/models/MessageDetailsTest.class

View File

@@ -1 +1 @@
/Users/maddy/Projects/cygnus/matrix/cygnus-lib/src/test/java/lib/models/ErrorDetailsTest.java
/Users/maddy/Projects/cygnus/matrix/cygnus-lib/src/test/java/lib/models/MessageDetailsTest.java

View File

@@ -1,63 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<testsuite xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://maven.apache.org/surefire/maven-surefire-plugin/xsd/surefire-test-report.xsd" version="3.0.2" name="lib.models.ErrorDetailsTest" time="0.028" tests="2" errors="0" skipped="0" failures="0">
<properties>
<property name="java.specification.version" value="21"/>
<property name="sun.jnu.encoding" value="UTF-8"/>
<property name="java.class.path" value="/Users/maddy/Projects/cygnus/matrix/cygnus-lib/target/test-classes:/Users/maddy/Projects/cygnus/matrix/cygnus-lib/target/classes:/Users/maddy/.m2/repository/org/projectlombok/lombok/1.18.46/lombok-1.18.46.jar:/Users/maddy/.m2/repository/org/junit/jupiter/junit-jupiter/5.12.2/junit-jupiter-5.12.2.jar:/Users/maddy/.m2/repository/org/junit/jupiter/junit-jupiter-api/5.12.2/junit-jupiter-api-5.12.2.jar:/Users/maddy/.m2/repository/org/opentest4j/opentest4j/1.3.0/opentest4j-1.3.0.jar:/Users/maddy/.m2/repository/org/junit/platform/junit-platform-commons/1.12.2/junit-platform-commons-1.12.2.jar:/Users/maddy/.m2/repository/org/apiguardian/apiguardian-api/1.1.2/apiguardian-api-1.1.2.jar:/Users/maddy/.m2/repository/org/junit/jupiter/junit-jupiter-params/5.12.2/junit-jupiter-params-5.12.2.jar:/Users/maddy/.m2/repository/org/junit/jupiter/junit-jupiter-engine/5.12.2/junit-jupiter-engine-5.12.2.jar:/Users/maddy/.m2/repository/org/junit/platform/junit-platform-engine/1.12.2/junit-platform-engine-1.12.2.jar:"/>
<property name="java.vm.vendor" value="Microsoft"/>
<property name="sun.arch.data.model" value="64"/>
<property name="java.vendor.url" value="https://www.microsoft.com"/>
<property name="os.name" value="Mac OS X"/>
<property name="java.vm.specification.version" value="21"/>
<property name="sun.java.launcher" value="SUN_STANDARD"/>
<property name="user.country" value="US"/>
<property name="sun.boot.library.path" value="/Users/maddy/Library/Java/JavaVirtualMachines/ms-21.0.8/Contents/Home/lib"/>
<property name="sun.java.command" value="/Users/maddy/Projects/cygnus/matrix/cygnus-lib/target/surefire/surefirebooter-20260801225315947_3.jar /Users/maddy/Projects/cygnus/matrix/cygnus-lib/target/surefire 2026-08-01T22-53-15_920-jvmRun1 surefire-20260801225315947_1tmp surefire_0-20260801225315947_2tmp"/>
<property name="http.nonProxyHosts" value="local|*.local|169.254/16|*.169.254/16"/>
<property name="jdk.debug" value="release"/>
<property name="surefire.test.class.path" value="/Users/maddy/Projects/cygnus/matrix/cygnus-lib/target/test-classes:/Users/maddy/Projects/cygnus/matrix/cygnus-lib/target/classes:/Users/maddy/.m2/repository/org/projectlombok/lombok/1.18.46/lombok-1.18.46.jar:/Users/maddy/.m2/repository/org/junit/jupiter/junit-jupiter/5.12.2/junit-jupiter-5.12.2.jar:/Users/maddy/.m2/repository/org/junit/jupiter/junit-jupiter-api/5.12.2/junit-jupiter-api-5.12.2.jar:/Users/maddy/.m2/repository/org/opentest4j/opentest4j/1.3.0/opentest4j-1.3.0.jar:/Users/maddy/.m2/repository/org/junit/platform/junit-platform-commons/1.12.2/junit-platform-commons-1.12.2.jar:/Users/maddy/.m2/repository/org/apiguardian/apiguardian-api/1.1.2/apiguardian-api-1.1.2.jar:/Users/maddy/.m2/repository/org/junit/jupiter/junit-jupiter-params/5.12.2/junit-jupiter-params-5.12.2.jar:/Users/maddy/.m2/repository/org/junit/jupiter/junit-jupiter-engine/5.12.2/junit-jupiter-engine-5.12.2.jar:/Users/maddy/.m2/repository/org/junit/platform/junit-platform-engine/1.12.2/junit-platform-engine-1.12.2.jar:"/>
<property name="sun.cpu.endian" value="little"/>
<property name="user.home" value="/Users/maddy"/>
<property name="user.language" value="en"/>
<property name="java.specification.vendor" value="Oracle Corporation"/>
<property name="java.version.date" value="2025-07-15"/>
<property name="java.home" value="/Users/maddy/Library/Java/JavaVirtualMachines/ms-21.0.8/Contents/Home"/>
<property name="file.separator" value="/"/>
<property name="basedir" value="/Users/maddy/Projects/cygnus/matrix/cygnus-lib"/>
<property name="java.vm.compressedOopsMode" value="Zero based"/>
<property name="line.separator" value="&#10;"/>
<property name="java.vm.specification.vendor" value="Oracle Corporation"/>
<property name="java.specification.name" value="Java Platform API Specification"/>
<property name="apple.awt.application.name" value="ForkedBooter"/>
<property name="surefire.real.class.path" value="/Users/maddy/Projects/cygnus/matrix/cygnus-lib/target/surefire/surefirebooter-20260801225315947_3.jar"/>
<property name="sun.management.compiler" value="HotSpot 64-Bit Tiered Compilers"/>
<property name="ftp.nonProxyHosts" value="local|*.local|169.254/16|*.169.254/16"/>
<property name="java.runtime.version" value="21.0.8+9-LTS"/>
<property name="user.name" value="maddy"/>
<property name="stdout.encoding" value="UTF-8"/>
<property name="path.separator" value=":"/>
<property name="os.version" value="26.5.2"/>
<property name="java.runtime.name" value="OpenJDK Runtime Environment"/>
<property name="file.encoding" value="UTF-8"/>
<property name="java.vm.name" value="OpenJDK 64-Bit Server VM"/>
<property name="java.vendor.version" value="Microsoft-11933201"/>
<property name="localRepository" value="/Users/maddy/.m2/repository"/>
<property name="java.vendor.url.bug" value="https://github.com/microsoft/openjdk/issues"/>
<property name="java.io.tmpdir" value="/var/folders/1l/36214rdn79755j30lcnmgsqh0000gn/T/"/>
<property name="java.version" value="21.0.8"/>
<property name="user.dir" value="/Users/maddy/Projects/cygnus/matrix/cygnus-lib"/>
<property name="os.arch" value="aarch64"/>
<property name="java.vm.specification.name" value="Java Virtual Machine Specification"/>
<property name="native.encoding" value="UTF-8"/>
<property name="java.library.path" value="/Users/maddy/Library/Java/Extensions:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java:."/>
<property name="java.vm.info" value="mixed mode, sharing"/>
<property name="stderr.encoding" value="UTF-8"/>
<property name="java.vendor" value="Microsoft"/>
<property name="java.vm.version" value="21.0.8+9-LTS"/>
<property name="sun.io.unicode.encoding" value="UnicodeBig"/>
<property name="socksNonProxyHosts" value="local|*.local|169.254/16|*.169.254/16"/>
<property name="java.class.version" value="65.0"/>
</properties>
<testcase name="mapsHttpStatusToCanonicalError" classname="lib.models.ErrorDetailsTest" time="0.008"/>
<testcase name="createsAReusableErrorContract" classname="lib.models.ErrorDetailsTest" time="0.012"/>
</testsuite>

View File

@@ -1,4 +0,0 @@
-------------------------------------------------------------------------------
Test set: lib.models.ErrorDetailsTest
-------------------------------------------------------------------------------
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.028 s -- in lib.models.ErrorDetailsTest