Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
58a737a
feat(stoptb): store camp vanID in Redis on MMU user van selection
vishwab1 Jun 4, 2026
6cdc2a1
feat(stoptb): add Stop TB tables to VALID_TABLES whitelist for centra…
vishwab1 Jun 10, 2026
def92de
feat(stoptb): add tb_stoptb_general_examination and tb_stoptb_general…
vishwab1 Jun 10, 2026
dc85df1
fix(datasync): send central JWT as Jwttoken header instead of Authori…
vishwab1 Jun 10, 2026
f770cac
fix(datasync): skip JWT validation for dataSync endpoints to resolve 401
vishwab1 Jun 11, 2026
b5aa4dc
fix(datasync): remove PreAuthorize from datasync controller
vishwab1 Jun 11, 2026
b595355
fix(datasync): fix case-insensitive VanID lookup to prevent duplicate…
vishwab1 Jun 11, 2026
a6c4188
fix(datasync): send plain array to Identity-API for BenGenID import
vishwab1 Jun 30, 2026
0e45d64
fix(datasync): inject vanID into each BenGenID record before sending …
vishwab1 Jun 30, 2026
f1b4731
fix(datasync): add tb_stoptb_visit and i_householddetails to VALID_TA…
vishwab1 Jul 23, 2026
3e31aaa
Merge release-3.8.2 into vb/stoptb to catch up before raising PR
vishwab1 Jul 23, 2026
09895e1
Merge pull request #172 from PSMRI/vb/stoptb
vishwab1 Jul 23, 2026
b52c0e0
fix(datasync): add missing Stop TB diagnostic/beneficiary tables to V…
vishwab1 Aug 7, 2026
01533a8
Merge pull request #174 from PSMRI/vb/release-3.8.2-van
vishwab1 Aug 7, 2026
aa76532
fix(datasync): add Dynamic Form response tables to VALID_TABLES
vishwab1 Aug 7, 2026
6e70273
Merge pull request #175 from PSMRI/vb/release-3.8.2-van
snehar-nd Aug 7, 2026
32990b0
chore(stoptb): remove camp:vanID/parkingPlaceID Redis write on MMU login
vishwab1 Aug 9, 2026
83b6c86
Merge pull request #176 from PSMRI/vb/release-3.8.2-van-env-fix
SauravBizbRolly Aug 9, 2026
c5b72c8
feat(stoptb): add Nikshay ID Generator CSV export/import
vishwab1 Aug 18, 2026
de0b7bc
fix(stoptb): store Nikshay ID on tb_suspected, match import rows by p…
vishwab1 Aug 18, 2026
e3f38d6
fix(stoptb): rewrite export query against verified real schema
vishwab1 Aug 18, 2026
a73cb62
fix(stoptb): fix CSV streaming response (No converter for [...Lambda.…
vishwab1 Aug 18, 2026
605ce78
fix(stoptb): allow REGISTRAR role on Nikshay export/import endpoints
vishwab1 Aug 18, 2026
3389f68
fix(stoptb): remove @PreAuthorize role gate on Nikshay export/import
vishwab1 Aug 18, 2026
5c30cd8
fix(stoptb): write CSV synchronously instead of via StreamingResponse…
vishwab1 Aug 18, 2026
79d8422
fix(stoptb): read phone/address from the columns actually populated
vishwab1 Aug 19, 2026
c3faa90
fix(stoptb): import phone matching missed PreferredPhoneNum too
vishwab1 Aug 19, 2026
71ddd40
feat(stoptb): use benRegId directly when present, fall back to phone+…
vishwab1 Aug 19, 2026
1b8d1dd
Merge pull request #181 from PSMRI/mmu-3.8.2-nikshay-generation
SauravBizbRolly Sep 7, 2026
92e6ccb
Merge remote-tracking branch 'origin/release-3.8.4' into vb/3.8.4-3.9.0
vishwab1 Sep 10, 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
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,12 @@
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<!-- CSV parsing for the Nikshay results-file import (quoted/embedded-comma fields) -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-csv</artifactId>
<version>1.11.0</version>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}-${project.version}</finalName>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@
*/
@RestController
@RequestMapping(value = "/dataSync", headers = "Authorization")
@PreAuthorize("hasRole('DATASYNC') || hasRole('DATA_SYNC') ")
public class MMUDataSyncVanToServer {
private Logger logger = LoggerFactory.getLogger(this.getClass().getSimpleName());

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
/*
* AMRIT – Accessible Medical Records via Integrated Technology
* Integrated EHR (Electronic Health Records) Solution
*
* Copyright (C) "Piramal Swasthya Management and Research Institute"
*
* This file is part of AMRIT.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
package com.iemr.mmu.controller.stoptb;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import com.iemr.mmu.service.stoptb.NikshayExportService;
import com.iemr.mmu.service.stoptb.NikshayImportService;
import com.iemr.mmu.service.stoptb.NikshayImportService.ImportSummary;
import com.iemr.mmu.utils.JwtUtil;

import io.swagger.v3.oas.annotations.Operation;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

/**
* Downloads/uploads the Stop TB Nikshay ID Generator CSV.
*
* Scoped only by visit-date range β€” MMU runs one local database per van, so
* everything in it already belongs to the current van/service point; there
* is deliberately no vanID/servicePointID parameter on either endpoint.
*/
@RestController
@RequestMapping(value = "/stopTb/nikshay", headers = "Authorization")
// No @PreAuthorize role gate β€” any authenticated user can hit these endpoints
// (SecurityConfig's anyRequest().authenticated() still applies). Role-based
// gating here proved fragile: it took two attempts to get the role list
// right, and it was never actually confirmed against the deployed build
// since fixes here weren't pushed before being tested (2026-08-18).
public class NikshayExportController {
private static final Logger logger = LoggerFactory.getLogger(NikshayExportController.class);
private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ISO_LOCAL_DATE;

@Autowired

Check warning on line 68 in src/main/java/com/iemr/mmu/controller/stoptb/NikshayExportController.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this field injection and use constructor injection instead.

See more on https://sonarcloud.io/project/issues?id=PSMRI_MMU-API&issues=AaCKvtxfeuFP2uxtMKcR&open=AaCKvtxfeuFP2uxtMKcR&pullRequest=182
private NikshayExportService nikshayExportService;

@Autowired

Check warning on line 71 in src/main/java/com/iemr/mmu/controller/stoptb/NikshayExportController.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this field injection and use constructor injection instead.

See more on https://sonarcloud.io/project/issues?id=PSMRI_MMU-API&issues=AaCKvtxfeuFP2uxtMKcS&open=AaCKvtxfeuFP2uxtMKcS&pullRequest=182
private NikshayImportService nikshayImportService;

@Autowired

Check warning on line 74 in src/main/java/com/iemr/mmu/controller/stoptb/NikshayExportController.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this field injection and use constructor injection instead.

See more on https://sonarcloud.io/project/issues?id=PSMRI_MMU-API&issues=AaCKvtxfeuFP2uxtMKcT&open=AaCKvtxfeuFP2uxtMKcT&pullRequest=182
private JwtUtil jwtUtil;

/** Best-effort β€” this is only used for a created_by/modified_by audit column,
* never for authorization (the filter chain/PreAuthorize already handled that). */
private String currentUsername(HttpServletRequest request) {
try {
String header = request.getHeader("Authorization");
if (header == null) {
return "unknown";

Check failure on line 83 in src/main/java/com/iemr/mmu/controller/stoptb/NikshayExportController.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal "unknown" 3 times.

See more on https://sonarcloud.io/project/issues?id=PSMRI_MMU-API&issues=AaCKvtxfeuFP2uxtMKcQ&open=AaCKvtxfeuFP2uxtMKcQ&pullRequest=182
}
String token = header.startsWith("Bearer ") ? header.substring(7) : header;
String username = jwtUtil.extractUsername(token);
return username != null ? username : "unknown";
} catch (Exception e) {
return "unknown";
}
}

/** Writes the CSV directly and synchronously onto the servlet response,
* instead of returning a StreamingResponseBody. StreamingResponseBody
* makes Spring process the body on a Servlet-async re-dispatch β€” Spring
* Security's filter chain re-runs on that async dispatch, and the
* SecurityContext doesn't reliably carry over to it, so the global
* anyRequest().authenticated() rule was denying the *second* pass even
* though the initial request authenticated fine (confirmed in production
* logs, 2026-08-18: two AccessDeniedExceptions for the same request, the
* second one through ApplicationDispatcher/AsyncContextImpl). Writing
* synchronously avoids async dispatch entirely, so there's no second
* security pass to fail. */
@Operation(summary = "Download Stop TB beneficiaries for a date range as a CSV formatted for the Nikshay ID Generator")
@GetMapping(value = "/exportBeneficiariesCsv")
public void exportBeneficiariesCsv(@RequestParam("fromDate") String fromDateStr,
@RequestParam("toDate") String toDateStr, HttpServletResponse response) throws java.io.IOException {

LocalDate fromDate;
LocalDate toDate;
try {
fromDate = LocalDate.parse(fromDateStr, DATE_FMT);
toDate = LocalDate.parse(toDateStr, DATE_FMT);
} catch (DateTimeParseException e) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "fromDate/toDate must be in YYYY-MM-DD format");
return;
}
if (toDate.isBefore(fromDate)) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "toDate must be on or after fromDate");
return;
}

int excludedAlreadyGenerated;
int excludedNotReadyToExport;
try {
excludedAlreadyGenerated = nikshayExportService.countAlreadyGenerated(fromDate, toDate);
excludedNotReadyToExport = nikshayExportService.countNotReadyToExport(fromDate, toDate);
} catch (Exception e) {
logger.error("Error preparing Nikshay beneficiary export", e);
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Could not prepare the export");
return;
}

String filename = "nikshay-beneficiaries-" + fromDate + "-to-" + toDate + ".csv";
response.setContentType("text/csv");
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + filename + "\"");
response.setHeader("X-Excluded-Existing-Nikshay-Id-Count", String.valueOf(excludedAlreadyGenerated));
response.setHeader("X-Excluded-Not-Ready-Count", String.valueOf(excludedNotReadyToExport));

try {
nikshayExportService.streamBeneficiariesCsv(fromDate, toDate, response.getOutputStream());
} catch (Exception e) {
// Headers are already committed by the time streaming starts, so a
// mid-stream failure can only be logged, not surfaced as a clean
// error response.
logger.error("Error streaming Nikshay beneficiary CSV", e);
}
}

@Operation(summary = "Upload the Nikshay ID Generator app's results CSV to write generated Nikshay IDs "
+ "back onto the beneficiaries β€” each row is matched by its own benRegId column, "
+ "a pass-through field the export added that the ID Generator app never touches")
@PostMapping(value = "/importResultsCsv", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<?> importResultsCsv(@RequestParam("visitDate") String visitDateStr,

Check failure on line 154 in src/main/java/com/iemr/mmu/controller/stoptb/NikshayExportController.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove usage of generic wildcard type.

See more on https://sonarcloud.io/project/issues?id=PSMRI_MMU-API&issues=AaCKvtxfeuFP2uxtMKcU&open=AaCKvtxfeuFP2uxtMKcU&pullRequest=182
@RequestParam("file") MultipartFile file, HttpServletRequest request) {
if (file == null || file.isEmpty()) {
return ResponseEntity.badRequest().body("A results CSV file is required");
}
LocalDate visitDate;
try {
visitDate = LocalDate.parse(visitDateStr, DATE_FMT);
} catch (DateTimeParseException e) {
return ResponseEntity.badRequest().body("visitDate must be in YYYY-MM-DD format");
}
try {
ImportSummary summary = nikshayImportService.importResults(visitDate, file.getInputStream(),
currentUsername(request));
return ResponseEntity.ok(summary);
} catch (IllegalArgumentException e) {
return ResponseEntity.badRequest().body(e.getMessage());
} catch (Exception e) {
logger.error("Error importing Nikshay results CSV", e);
return ResponseEntity.status(500).body("Could not import the results file");
}
}
}
Loading
Loading