Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
9826668
fix: amm-2140 stillbirth and abortion report download issue
snehar-nd Feb 4, 2026
4a27c15
Merge pull request #116 from PSMRI/sn/2140
snehar-nd Feb 6, 2026
66e5f17
Bump version from 3.6.0 to 3.7.0
snehar-nd Feb 10, 2026
2ba9f1e
Merge pull request #117 from PSMRI/snehar-nd-patch-1
snehar-nd Feb 10, 2026
18a359b
fix: amm-2140 baby death report download issue
snehar-nd Feb 11, 2026
0872951
Merge pull request #118 from PSMRI/sn/2140
snehar-nd Feb 11, 2026
4c4a791
amm-2140 misscarriage report fix
snehar-nd Mar 3, 2026
23ca6dd
Merge pull request #122 from PSMRI/sn/2140
snehar-nd Mar 3, 2026
1c28b98
amm-2140 misscarriage report fix
snehar-nd Mar 3, 2026
41c5ba2
Merge pull request #123 from PSMRI/sn/2140
snehar-nd Mar 3, 2026
62a4349
feat: amm-2180 reassign calls to anm move from mo bucket to anm bucket
snehar-nd Mar 5, 2026
80dc113
amm-2181 storing the highrisk reason on callclose with MO
snehar-nd Mar 10, 2026
a1f8685
fix: AMM-2180 handled the hrp reason
snehar-nd Mar 10, 2026
4a77a53
fix: AMM-2180 AMM-2181 modified the fixes removed unwanted code
snehar-nd Mar 10, 2026
ec135bd
fix: removed the unused variables
snehar-nd Mar 10, 2026
1277729
fix: hrp/hrni reason was not fetching properly
snehar-nd Mar 10, 2026
2185795
fix: follow up call allocation was happening in a day gap range and a…
snehar-nd Mar 13, 2026
11b7c79
changes made for disconnected call and call allocation
RoshiniND Mar 13, 2026
136cdc9
Merge pull request #125 from PSMRI/Sn/hrp
snehar-nd Mar 16, 2026
ced8cac
Merge branch 'release-3.7.0' into rr-AAM1549
snehar-nd Mar 16, 2026
a47d00c
Merge pull request #127 from PSMRI/rr-AAM1549
snehar-nd Mar 16, 2026
7686066
fix: busy/not-answered call should stay in agent worklist
vishwab1 May 22, 2026
e9794dd
fix: busy/not-answered calls should stay in agent worklist
vishwab1 May 22, 2026
01601c2
Merge pull request #135 from PSMRI/vb/ecd-call-fix
snehar-nd May 22, 2026
48172fa
perf: add composite indexes to fix 504 timeout on getEligibleRecordsInfo
Jun 22, 2026
5f8a6b2
perf: consolidate 3 COUNT queries into 1 to fix 504 on getEligibleRec…
Jun 22, 2026
46bb895
chore: remove index SQL from ECD-API β€” moved to AMRIT-DB as V83 Flywa…
Jun 22, 2026
67d60c7
perf: use FORCE INDEX (idx_mcts_eligible_v2) on child count queries t…
Jun 26, 2026
cd358dd
chore: remove consolidated SUM(CASE WHEN) queries, revert mother bloc…
Jun 26, 2026
dd93f17
fix: revert child count queries to JPQL entity style per review feedback
Jun 29, 2026
685af23
perf: use FORCE INDEX (idx_mcts_eligible_v2) on child count queries t…
Jun 29, 2026
aa2b486
Merge pull request #136 from PSMRI/fix/504-composite-indexes-getEligi…
snehar-nd Jun 29, 2026
98a1e72
fix: merge with main
vanitha1822 Jun 30, 2026
e52d7d4
fix: resolve the conflicts
vanitha1822 Jun 30, 2026
0282c64
Merge pull request #138 from PSMRI/nd/vs/rebase-main
snehar-nd Jun 30, 2026
469a1d5
Bump version from 3.7.0 to 3.7.1
snehar-nd Jun 30, 2026
5538537
Merge pull request #139 from PSMRI/snehar-nd-patch-2
snehar-nd Jun 30, 2026
5390795
fix: amm-2392 ecd data upload limit need to increase (#141)
snehar-nd Jul 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
</parent>
<groupId>com.iemr.ecd</groupId>
<artifactId>ecd-api</artifactId>
<version>3.7.0</version>
<version>3.7.1</version>
<packaging>war</packaging>
<name>ECD-API</name>
<description>ECD project</description>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,11 +123,18 @@ int getMotherUnAllocatedCountLR(@Param("allocationStatus") String allocationStat
@Param("fDate") Timestamp fDate, @Param("tDate") Timestamp tDate, @Param("phoneNoType") String phoneNoType);

// un-allocated, child low risk,
@Query(value = " SELECT COUNT(1) FROM OutboundCalls AS t WHERE t.allocationStatus =:allocationStatus AND "
+ " t.psmId=:psmId AND ((:fDate between t.callDateFrom AND t.callDateTo) OR (:tDate between t.callDateFrom AND t.callDateTo)) AND "
+ " t.childId IS NOT NULL AND (t.isHrni = false OR t.isHrni IS NULL ) "
+ " AND t.phoneNumberType=:phoneNoType AND t.deleted = false AND t.displayEcdCallType != 'introductory' AND (t.isFurtherCallRequired = true OR t.isFurtherCallRequired IS NULL ) AND "
+ " t.callDateTo >= CURRENT_TIMESTAMP")
@Query(value = "SELECT COUNT(1) FROM t_mctsoutboundcalls t FORCE INDEX (idx_mcts_eligible_v2) "
+ "WHERE t.AllocationStatus = :allocationStatus "
+ "AND t.ProviderServiceMapID = :psmId "
+ "AND ((:fDate BETWEEN t.CallDateFrom AND t.CallDateTo) OR (:tDate BETWEEN t.CallDateFrom AND t.CallDateTo)) "
+ "AND t.ChildID IS NOT NULL "
+ "AND (t.IsHrni = 0 OR t.IsHrni IS NULL) "
+ "AND t.phoneNumberType = :phoneNoType "
+ "AND t.Deleted = 0 "
+ "AND t.DisplayOBCallType != 'introductory' "
+ "AND (t.isFurtherCallRequired = 1 OR t.isFurtherCallRequired IS NULL) "
+ "AND t.CallDateTo >= CURRENT_TIMESTAMP",
nativeQuery = true)
int getChildUnAllocatedCountLR(@Param("allocationStatus") String allocationStatus, @Param("psmId") Integer psmId,
@Param("fDate") Timestamp fDate, @Param("tDate") Timestamp tDate, @Param("phoneNoType") String phoneNoType);

Expand All @@ -141,11 +148,18 @@ int getMotherUnAllocatedCountHR(@Param("allocationStatus") String allocationStat
@Param("fDate") Timestamp fDate, @Param("tDate") Timestamp tDate, @Param("phoneNoType") String phoneNoType);

// un-allocated, child high risk,
@Query(value = " SELECT COUNT(1) FROM OutboundCalls AS t WHERE t.allocationStatus =:allocationStatus AND "
+ " t.psmId=:psmId AND ((:fDate between t.callDateFrom AND t.callDateTo) OR (:tDate between t.callDateFrom AND t.callDateTo)) AND "
+ " t.childId IS NOT NULL AND t.isHrni = true " + " AND t.phoneNumberType=:phoneNoType AND t.deleted = false AND t.displayEcdCallType != 'introductory'"
+ " AND (t.isFurtherCallRequired = true OR t.isFurtherCallRequired IS NULL ) AND "
+ " t.callDateTo >= CURRENT_TIMESTAMP")
@Query(value = "SELECT COUNT(1) FROM t_mctsoutboundcalls t FORCE INDEX (idx_mcts_eligible_v2) "
+ "WHERE t.AllocationStatus = :allocationStatus "
+ "AND t.ProviderServiceMapID = :psmId "
+ "AND ((:fDate BETWEEN t.CallDateFrom AND t.CallDateTo) OR (:tDate BETWEEN t.CallDateFrom AND t.CallDateTo)) "
+ "AND t.CallDateTo >= CURRENT_TIMESTAMP "
+ "AND t.ChildID IS NOT NULL "
+ "AND t.IsHrni = 1 "
+ "AND t.phoneNumberType = :phoneNoType "
+ "AND t.Deleted = 0 "
+ "AND t.DisplayOBCallType != 'introductory' "
+ "AND (t.isFurtherCallRequired = 1 OR t.isFurtherCallRequired IS NULL)",
nativeQuery = true)
int getChildUnAllocatedCountHR(@Param("allocationStatus") String allocationStatus, @Param("psmId") Integer psmId,
@Param("fDate") Timestamp fDate, @Param("tDate") Timestamp tDate, @Param("phoneNoType") String phoneNoType);

Expand All @@ -158,10 +172,15 @@ int getTotalAllocatedCountMother(@Param("allocationStatus") String allocationSta
@Param("fDate") Timestamp fDate, @Param("tDate") Timestamp tDate, @Param("phoneNoType") String phoneNoType);

// allocated, child record,
@Query(value = " SELECT COUNT(1) FROM OutboundCalls AS t WHERE t.allocationStatus =:allocationStatus AND "
+ " t.psmId=:psmId AND ((:fDate between t.callDateFrom AND t.callDateTo) OR (:tDate between t.callDateFrom AND t.callDateTo)) AND "
+ " t.childId IS NOT NULL AND t.phoneNumberType=:phoneNoType AND (t.isFurtherCallRequired = true OR t.isFurtherCallRequired IS NULL ) AND "
+ " t.callDateTo >= CURRENT_TIMESTAMP")
@Query(value = "SELECT COUNT(1) FROM t_mctsoutboundcalls t FORCE INDEX (idx_mcts_eligible_v2) "
+ "WHERE t.AllocationStatus = :allocationStatus "
+ "AND t.ProviderServiceMapID = :psmId "
+ "AND ((:fDate BETWEEN t.CallDateFrom AND t.CallDateTo) OR (:tDate BETWEEN t.CallDateFrom AND t.CallDateTo)) "
+ "AND t.ChildID IS NOT NULL "
+ "AND t.phoneNumberType = :phoneNoType "
+ "AND (t.isFurtherCallRequired = 1 OR t.isFurtherCallRequired IS NULL) "
+ "AND t.CallDateTo >= CURRENT_TIMESTAMP",
nativeQuery = true)
int getTotalAllocatedCountChild(@Param("allocationStatus") String allocationStatus, @Param("psmId") Integer psmId,
@Param("fDate") Timestamp fDate, @Param("tDate") Timestamp tDate, @Param("phoneNoType") String phoneNoType);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,9 +168,12 @@ public String closeCall(CallClosureDTO request) {
} else {
if (obj.getIsCallDisconnected() != null && obj.getIsCallDisconnected()) {
callObj.setCallStatus(Constants.OPEN);
callObj.setAllocatedUserId(null);
callObj.setAllocationStatus(Constants.UNALLOCATED);
callObj.setCallAttemptNo(0);
if (request.getReasonForCallNotAnswered() == null ||
!Constants.REASONFORCALLNOTANSWERED.contains(request.getReasonForCallNotAnswered())) {
callObj.setAllocatedUserId(null);
callObj.setAllocationStatus(Constants.UNALLOCATED);
callObj.setCallAttemptNo(0);
}
} else {
callObj.setCallStatus(Constants.COMPLETED);
createEcdCallRecordsInOutboundCalls(request, callConfigurationDetails,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -551,27 +551,23 @@ public ResponseEligibleCallRecordsDTO getEligibleRecordsInfo(int psmId, String p

totalIntroductoryRecord = motherRecordRepo.getRecordCount(false, tempFDateStamp, tempTDateStamp,
phoneNoType);

totalLowRisk = outboundCallsRepo.getMotherUnAllocatedCountLR(Constants.UNALLOCATED, psmId, tempFDateStamp,
tempTDateStamp, phoneNoType);
totalLowRisk = outboundCallsRepo.getMotherUnAllocatedCountLR(Constants.UNALLOCATED, psmId,
tempFDateStamp, tempTDateStamp, phoneNoType);
totalHighRisk = outboundCallsRepo.getMotherUnAllocatedCountHR(Constants.UNALLOCATED, psmId,
tempFDateStamp, tempTDateStamp, phoneNoType);

totalAllocated = outboundCallsRepo.getTotalAllocatedCountMother(Constants.ALLOCATED, psmId, tempFDateStamp,
tempTDateStamp, phoneNoType);
totalAllocated = outboundCallsRepo.getTotalAllocatedCountMother(Constants.ALLOCATED, psmId,
tempFDateStamp, tempTDateStamp, phoneNoType);

} else if (recordType != null && recordType.equalsIgnoreCase("Child")) {

totalIntroductoryRecord = childRecordRepo.getRecordCount(false, tempFDateStamp, tempTDateStamp,
phoneNoType);

totalLowRisk = outboundCallsRepo.getChildUnAllocatedCountLR("unallocated", psmId, tempFDateStamp,
tempTDateStamp, phoneNoType);
totalHighRisk = outboundCallsRepo.getChildUnAllocatedCountHR("unallocated", psmId, tempFDateStamp,
tempTDateStamp, phoneNoType);

totalAllocated = outboundCallsRepo.getTotalAllocatedCountChild("allocated", psmId, tempFDateStamp,
tempTDateStamp, phoneNoType);
totalLowRisk = outboundCallsRepo.getChildUnAllocatedCountLR(Constants.UNALLOCATED, psmId,
tempFDateStamp, tempTDateStamp, phoneNoType);
totalHighRisk = outboundCallsRepo.getChildUnAllocatedCountHR(Constants.UNALLOCATED, psmId,
tempFDateStamp, tempTDateStamp, phoneNoType);
totalAllocated = outboundCallsRepo.getTotalAllocatedCountChild(Constants.ALLOCATED, psmId,
tempFDateStamp, tempTDateStamp, phoneNoType);

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ public class RCHDataUploadServiceImpl {

Logger logger = LoggerFactory.getLogger(this.getClass().getName());

private static final long MAX_FILE_SIZE_BYTES = 5L * 1024 * 1024;

@Autowired
private MotherRecordRepo motherRecordRepo;
@Autowired
Expand All @@ -80,17 +82,17 @@ public String uploadRCDData(RCHFileUploadDto rchFileUploadDto) {

String base64File = rchFileUploadDto.getFileContent();
byte[] excelDataBytes = Base64.decodeBase64(base64File);

if (excelDataBytes.length > MAX_FILE_SIZE_BYTES)
throw new ECDException("File size exceeds the maximum allowed limit of 5MB");

Comment on lines +85 to +88

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚑ Quick win

Reinstate a row count validation limit to prevent memory exhaustion.

Relying solely on a 5MB file-size limit introduces a severe memory exhaustion (OOM) vulnerability. Because .xlsx files are highly compressed ZIP archives, a 5MB file can easily contain hundreds of thousands of rows.

Processing and retaining this many MotherRecord or ChildRecord entities in memory to persist them in a single JPA saveAll batch will likely cause heap exhaustion or database transaction timeouts. Please reinstate a reasonable row count validation (e.g., 5,000 to 10,000 rows max) after opening the workbook to safeguard system stability.

πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/com/iemr/ecd/service/data_upload/RCHDataUploadServiceImpl.java`
around lines 85 - 88, Reinstate a maximum row-count validation in the workbook
processing flow of RCHDataUploadServiceImpl after opening the workbook and
before creating or retaining MotherRecord/ChildRecord entities. Enforce a
reasonable limit of 5,000–10,000 rows, reject oversized workbooks with an
ECDException, and preserve the existing file-size validation and saveAll
behavior for valid inputs.

πŸš€ Performance & Scalability | 🟑 Minor | ⚑ Quick win

Fast-fail on oversized Base64 payloads before decoding.

Validating the file size only after Base64.decodeBase64 executes allows exceptionally large payloads to allocate massive byte arrays in memory, leading to unnecessary CPU cycles and a potential OutOfMemoryError (OOM) before the size limit safeguard is even reached.

Check the estimated length of the Base64 string before decoding to safely fail fast.

⚑ Proposed optimization
 				String base64File = rchFileUploadDto.getFileContent();
+
+				// Fast-fail on absurdly large payloads to prevent OOM during decoding.
+				// A Base64 string's length is roughly 4/3 of the decoded byte array.
+				if (base64File.length() > MAX_FILE_SIZE_BYTES * 2) {
+					throw new ECDException("File size significantly exceeds the maximum allowed limit");
+				}
+
 				byte[] excelDataBytes = Base64.decodeBase64(base64File);
 
 				if (excelDataBytes.length > MAX_FILE_SIZE_BYTES)
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (excelDataBytes.length > MAX_FILE_SIZE_BYTES)
throw new ECDException("File size exceeds the maximum allowed limit of 5MB");
String base64File = rchFileUploadDto.getFileContent();
// Fast-fail on absurdly large payloads to prevent OOM during decoding.
// A Base64 string's length is roughly 4/3 of the decoded byte array.
if (base64File.length() > MAX_FILE_SIZE_BYTES * 2) {
throw new ECDException("File size significantly exceeds the maximum allowed limit");
}
byte[] excelDataBytes = Base64.decodeBase64(base64File);
if (excelDataBytes.length > MAX_FILE_SIZE_BYTES)
throw new ECDException("File size exceeds the maximum allowed limit of 5MB");
πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/com/iemr/ecd/service/data_upload/RCHDataUploadServiceImpl.java`
around lines 85 - 88, Move the size validation in the RCH data upload flow to
the Base64 input before Base64.decodeBase64 executes. Estimate the decoded byte
length from the encoded payload, account for padding as needed, and throw the
existing ECDException when the estimate exceeds MAX_FILE_SIZE_BYTES; retain the
post-decode check only if needed for exact validation.

workbook = this.getXSSFWorkbook(excelDataBytes);

if (workbook.getNumberOfSheets() > 0) {
Sheet sheet = workbook.getSheetAt(0);

if (sheet != null) {

if (sheet.getPhysicalNumberOfRows() > 1000)
throw new ECDException(
"File is having more then 1000 records,please upload max 1000 records at a time");

if (rchFileUploadDto.getFieldFor() != null
&& rchFileUploadDto.getFieldFor().equalsIgnoreCase("Mother Data")) {
responseMap.put("response", getMotherValidRecords(sheet,
Expand Down