diff --git a/pom.xml b/pom.xml index 42c2f42d..29147618 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.iemr.tm tm-api - 3.6.2 + 3.8.2 war TM-API diff --git a/src/main/environment/common_ci.properties b/src/main/environment/common_ci.properties index 2f4cd540..3b96e7a0 100644 --- a/src/main/environment/common_ci.properties +++ b/src/main/environment/common_ci.properties @@ -67,6 +67,11 @@ tcSpeclistWL=7 ### Redis IP spring.redis.host=@env.REDIS_HOST@ + +# Stop TB: when true, beneficiary registration fails with an error if camp (vanID) is not configured +stoptb.enforce.vanid=@env.STOPTB_ENFORCE_VANID@ +# Stop TB: this deployment's van/camp ID, replacing the old Redis camp:vanID lookup +stoptb.van.id=@env.STOPTB_VAN_ID@ jwt.secret=@env.JWT_SECRET_KEY@ diff --git a/src/main/environment/common_docker.properties b/src/main/environment/common_docker.properties index e00511eb..769a8834 100644 --- a/src/main/environment/common_docker.properties +++ b/src/main/environment/common_docker.properties @@ -67,6 +67,11 @@ tcSpeclistWL=7 ### Redis IP spring.redis.host=${REDIS_HOST} + +# Stop TB: when true, beneficiary registration fails with an error if camp (vanID) is not configured +stoptb.enforce.vanid=${STOPTB_ENFORCE_VANID} +# Stop TB: this deployment's van/camp ID, replacing the old Redis camp:vanID lookup +stoptb.van.id=${STOPTB_VAN_ID} jwt.secret=${JWT_SECRET_KEY} #ELK logging file name diff --git a/src/main/environment/common_example.properties b/src/main/environment/common_example.properties index 64ac5d63..3e18f9c6 100644 --- a/src/main/environment/common_example.properties +++ b/src/main/environment/common_example.properties @@ -69,6 +69,12 @@ prescription=TMPrescription SMS ### Redis IP spring.redis.host=localhost +# Stop TB: when true, beneficiary registration fails with an error if camp (vanID) +# is not configured instead of silently registering without it +stoptb.enforce.vanid=false +# Stop TB: this deployment's van/camp ID, replacing the old Redis camp:vanID lookup +stoptb.van.id=0 + jwt.secret=my-32-character-ultra-secure-and-ultra-long-secret logging.path=logs/ logging.file.name=logs/tm-api.log diff --git a/src/main/java/com/iemr/tm/controller/common/master/CommonMasterController.java b/src/main/java/com/iemr/tm/controller/common/master/CommonMasterController.java index bda86731..f79d4a75 100644 --- a/src/main/java/com/iemr/tm/controller/common/master/CommonMasterController.java +++ b/src/main/java/com/iemr/tm/controller/common/master/CommonMasterController.java @@ -27,7 +27,6 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; diff --git a/src/main/java/com/iemr/tm/controller/login/IemrMmuLoginController.java b/src/main/java/com/iemr/tm/controller/login/IemrMmuLoginController.java index eb64b2c5..4f4f5947 100644 --- a/src/main/java/com/iemr/tm/controller/login/IemrMmuLoginController.java +++ b/src/main/java/com/iemr/tm/controller/login/IemrMmuLoginController.java @@ -46,7 +46,7 @@ @RestController @RequestMapping(value = "/user", headers = "Authorization", consumes = "application/json", produces = "application/json") -@PreAuthorize("hasRole('NURSE') || hasRole('PHARMACIST') || hasRole('LABTECHNICIAN') || hasRole('REGISTRAR') || hasRole('DATASYNC') || hasRole('DATA_SYNC') || hasRole('DOCTOR') || hasRole('LAB_TECHNICIAN') || hasRole('TC_SPECIALIST') || hasRole('ONCOLOGIST') || hasRole('RADIOLOGIST') || hasRole('ASHA')") +@PreAuthorize("hasRole('NURSE') || hasRole('PHARMACIST') || hasRole('LABTECHNICIAN') || hasRole('REGISTRAR') || hasRole('DATASYNC') || hasRole('DATA_SYNC') || hasRole('DOCTOR') || hasRole('LAB_TECHNICIAN') || hasRole('TC_SPECIALIST') || hasRole('ONCOLOGIST') || hasRole('RADIOLOGIST') || hasRole('ASHA') || hasRole('VOLUNTEER') || hasRole('REGISTRATION_OFFICER')") public class IemrMmuLoginController { private Logger logger = LoggerFactory.getLogger(RegistrarController.class); diff --git a/src/main/java/com/iemr/tm/controller/registrar/main/RegistrarController.java b/src/main/java/com/iemr/tm/controller/registrar/main/RegistrarController.java index 5ddcf7ce..832ef393 100644 --- a/src/main/java/com/iemr/tm/controller/registrar/main/RegistrarController.java +++ b/src/main/java/com/iemr/tm/controller/registrar/main/RegistrarController.java @@ -399,7 +399,7 @@ public String createBeneficiary( // beneficiary registration with common and identity new @Operation(summary = "Register a new beneficiary new API") @PostMapping(value = { "/registrarBeneficaryRegistrationNew" }) - @PreAuthorize("hasRole('NURSE') || hasRole('REGISTRAR') || hasRole('ASHA')") + @PreAuthorize("hasRole('NURSE') || hasRole('REGISTRAR') || hasRole('ASHA') || hasRole('VOLUNTEER') || hasRole('REGISTRATION_OFFICER') || hasRole('COUNSELLOR')") public String registrarBeneficaryRegistrationNew(@RequestBody String comingReq, @RequestHeader(value = "Authorization") String Authorization) { String s; diff --git a/src/main/java/com/iemr/tm/repo/benFlowStatus/BeneficiaryFlowStatusRepo.java b/src/main/java/com/iemr/tm/repo/benFlowStatus/BeneficiaryFlowStatusRepo.java index 579f7631..58b50dc6 100644 --- a/src/main/java/com/iemr/tm/repo/benFlowStatus/BeneficiaryFlowStatusRepo.java +++ b/src/main/java/com/iemr/tm/repo/benFlowStatus/BeneficiaryFlowStatusRepo.java @@ -44,6 +44,32 @@ public interface BeneficiaryFlowStatusRepo extends CrudRepository { + // Targeted update — touches ONLY vanSerialNo. A full-entity save() here would also + // rewrite every other updatable column (e.g. `deleted`, which is insertable=false but + // NOT updatable=false) with whatever stale value the in-memory Java object happens to + // hold, silently clobbering the DB's own DEFAULT for columns never explicitly set. + @Transactional + @Modifying + @Query("UPDATE BeneficiaryFlowStatus t SET t.vanSerialNo = :vanSerialNo WHERE t.benFlowID = :id") + void updateVanSerialNo(@Param("id") Long id, @Param("vanSerialNo") Long vanSerialNo); + + // Stop TB's district/village IDs are Nikshay-scoped, not AMRIT's general m_district/ + // m_village numbering - the two ID spaces overlap (e.g. ID 1 means "Nicobars" in + // m_district but "Alluri Sitharama Raju" in m_nikshay_district), so client-sent text + // for these IDs can silently land on the wrong scheme's name. Gate any Nikshay-table + // resolution on this check first - a PSM with no Nikshay TU mapping is a non-Stop-TB + // program (ANC/NCD/cancer-screening/etc.) and must NOT go through the Nikshay tables. + @Query(value = "SELECT COUNT(*) FROM m_userservicerolemapping " + + "WHERE ProviderServiceMapID = :psmId AND NikshayTUID IS NOT NULL AND Deleted = false LIMIT 1", + nativeQuery = true) + int countNikshayMappedUsersForPSM(@Param("psmId") Integer psmId); + + @Query(value = "SELECT DistrictName FROM m_nikshay_district WHERE NikshayDistrictID = :id", nativeQuery = true) + String getNikshayDistrictName(@Param("id") Integer id); + + @Query(value = "SELECT VillageName FROM m_nikshay_village WHERE NikshayVillageID = :id", nativeQuery = true) + String getNikshayVillageName(@Param("id") Integer id); + // nurse worklist // @Query("SELECT t from BeneficiaryFlowStatus t WHERE (t.nurseFlag = 1 OR t.nurseFlag = 100) AND (t.specialist_flag <> 100 OR t.specialist_flag is null) AND t.deleted = false " // + " AND Date(t.visitDate) = curdate() AND t.providerServiceMapId = :providerServiceMapId " diff --git a/src/main/java/com/iemr/tm/service/benFlowStatus/CommonBenStatusFlowServiceImpl.java b/src/main/java/com/iemr/tm/service/benFlowStatus/CommonBenStatusFlowServiceImpl.java index d905a7a7..fd923f70 100644 --- a/src/main/java/com/iemr/tm/service/benFlowStatus/CommonBenStatusFlowServiceImpl.java +++ b/src/main/java/com/iemr/tm/service/benFlowStatus/CommonBenStatusFlowServiceImpl.java @@ -64,12 +64,24 @@ public int createBenFlowRecord(String requestOBJ, Long beneficiaryRegID, Long be int returnOBJ = 0; try { BeneficiaryFlowStatus obj = getBenFlowRecordObj(requestOBJ, beneficiaryRegID, beneficiaryID); + System.out.println("TRACE createBenFlowRecord: benRegID=" + obj.getBeneficiaryRegID() + + " providerServiceMapID=" + obj.getProviderServiceMapID() + + " villageID=" + obj.getVillageID() + + " vanID=" + obj.getVanID()); if (beneficiaryRegID != null && beneficiaryID != null && beneficiaryRegID > 0 && beneficiaryID > 0) { objRS = beneficiaryFlowStatusRepo.save(obj); - if (objRS != null) + if (objRS != null) { + // VanSerialNo was never populated for i_ben_flow_outreach — following the + // same convention used elsewhere (VanSerialNo = record's own local PK, e.g. + // IdentityService.regMap.setVanSerialNo(regMap.getBenRegId())). + // A full-entity save() here (as this used to do) would also rewrite `deleted` + // (insertable=false but NOT updatable=false) with the in-memory object's never-set + // null, clobbering the DB's own DEFAULT b'0' and making the row invisible to every + // worklist query filtering `deleted = false` — use a targeted UPDATE instead. + beneficiaryFlowStatusRepo.updateVanSerialNo(objRS.getBenFlowID(), objRS.getBenFlowID()); returnOBJ = 1; - else + } else returnOBJ = 0; } else { Calendar cal = Calendar.getInstance(); @@ -85,9 +97,11 @@ public int createBenFlowRecord(String requestOBJ, Long beneficiaryRegID, Long be returnOBJ = 3; } else { objRS = beneficiaryFlowStatusRepo.save(obj); - if (objRS != null) + if (objRS != null) { + objRS.setVanSerialNo(objRS.getBenFlowID()); + objRS = beneficiaryFlowStatusRepo.save(objRS); returnOBJ = 1; - else + } else returnOBJ = 0; } @@ -154,6 +168,34 @@ private BeneficiaryFlowStatus getBenFlowRecordObj(String requestOBJ, Long benefi if (obj.getI_bendemographics().getDistrictBranchName() != null) obj.setVillageName(obj.getI_bendemographics().getDistrictBranchName()); + // Stop TB's district/village IDs are Nikshay-scoped, not AMRIT's general m_district/ + // m_village numbering - the same numeric ID can mean two different real places + // depending on the scheme, so the client-sent name text can silently be wrong. + // Only override for PSMs actually mapped to a Nikshay TU (i.e. genuinely Stop TB) - + // every other program (ANC/NCD/cancer-screening/etc.) keeps using client-sent text + // as before, since their IDs are correctly AMRIT-scoped already. + try { + if (obj.getProviderServiceMapID() != null + && beneficiaryFlowStatusRepo.countNikshayMappedUsersForPSM(obj.getProviderServiceMapID()) > 0) { + if (obj.getDistrictID() != null) { + String correctDistrictName = beneficiaryFlowStatusRepo.getNikshayDistrictName(obj.getDistrictID()); + if (correctDistrictName != null) { + obj.setDistrictName(correctDistrictName); + } + } + if (obj.getVillageID() != null) { + String correctVillageName = beneficiaryFlowStatusRepo.getNikshayVillageName(obj.getVillageID()); + if (correctVillageName != null) { + obj.setVillageName(correctVillageName); + } + } + } + } catch (Exception e) { + // Never let a Nikshay-name lookup failure block registration - worst case the + // display text stays whatever the client sent, same as before this fix existed. + logger.warn("Nikshay district/village name resolution failed, keeping client-sent text: " + e.getMessage()); + } + if (obj.getI_bendemographics().getServicePointID() != null) obj.setServicePointID(obj.getI_bendemographics().getServicePointID()); if (obj.getI_bendemographics().getServicePointName() != null) diff --git a/src/main/java/com/iemr/tm/service/registrar/RegistrarServiceImpl.java b/src/main/java/com/iemr/tm/service/registrar/RegistrarServiceImpl.java index 97d1b7c0..14d735b9 100644 --- a/src/main/java/com/iemr/tm/service/registrar/RegistrarServiceImpl.java +++ b/src/main/java/com/iemr/tm/service/registrar/RegistrarServiceImpl.java @@ -112,6 +112,23 @@ public class RegistrarServiceImpl implements RegistrarService { @Autowired private CookieUtil cookieUtil; + // This deployment's van/camp ID. Previously looked up from Redis ("camp:vanID"), + // written at MMU login and deleted (globally, unscoped) on ANY user's logout — a Redis + // outage or an unrelated user's logout would silently break registration on this camp. + // Each camp/van already runs its own dedicated backend instance, so which van this is + // never actually changes at runtime; reading it from properties removes the Redis + // dependency entirely. No inline default — every properties file must set this + // explicitly. Scope: vanID only, parkingPlaceID is not part of this change. + @Value("${stoptb.van.id}") + private int vanID; + + // When true, beneficiary registration fails loudly if camp is not configured + // instead of silently registering with vanID unset. No inline default — every + // properties file must set this explicitly, so a forgotten config fails loudly + // at startup instead of running fail-open. + @Value("${stoptb.enforce.vanid}") + private boolean enforceVanID; + @Autowired public void setCommonBenStatusFlowServiceImpl(CommonBenStatusFlowServiceImpl commonBenStatusFlowServiceImpl) { this.commonBenStatusFlowServiceImpl = commonBenStatusFlowServiceImpl; @@ -658,7 +675,19 @@ public String registerBeneficiary(String comingRequest, String Authorization) th Long beneficiaryRegID = null; Long beneficiaryID = null; Map responseMap = new HashMap<>(); - + + // Inject configured vanID (mobile sends vanID=0 as placeholder). Previously looked + // up from Redis at request time; now a fixed property of this deployment (see + // vanID field javadoc above). + if (vanID > 0) { + JSONObject reqJson = new JSONObject(comingRequest); + reqJson.put("vanID", vanID); + comingRequest = reqJson.toString(); + } else if (enforceVanID) { + throw new Exception( + "Camp not configured: stoptb.van.id is 0. Set stoptb.van.id in this deployment's properties file."); + } + RestTemplate restTemplate = new RestTemplate(); HttpEntity request = RestTemplateUtil.createRequestEntity(comingRequest, Authorization); logger.info("Before Calling Common-API registration : "+request.getHeaders()); @@ -667,19 +696,30 @@ public String registerBeneficiary(String comingRequest, String Authorization) th if (response.getStatusCodeValue() == 200 & response.hasBody()) { String responseStr = response.getBody(); JSONObject responseOBJ = new JSONObject(responseStr); + if (!responseOBJ.has("data") || responseOBJ.isNull("data")) { + logger.error("Common-API registration failed, response: " + responseStr); + response1.setError(5000, responseOBJ.optString("errorMessage", "Error in registration; please contact administrator")); + return response1.toString(); + } beneficiaryRegID = responseOBJ.getJSONObject("data").getLong("beneficiaryRegID"); beneficiaryID = responseOBJ.getJSONObject("data").getLong("beneficiaryID"); responseMap.put("benGenId", beneficiaryID); responseMap.put("benRegId", beneficiaryRegID); BeneficiaryFlowStatus obj = InputMapper.gson().fromJson(comingRequest, BeneficiaryFlowStatus.class); + System.out.println("TRACE registerBeneficiary: benRegID=" + beneficiaryRegID + " isMobile=" + + (obj != null ? obj.getIsMobile() : "obj=null") + " providerServiceMapID=" + + (obj != null ? obj.getProviderServiceMapID() : null) + " villageID=" + + (obj != null ? obj.getVillageID() : null)); if (obj != null && obj.getIsMobile() != null && obj.getIsMobile()) { + System.out.println("TRACE registerBeneficiary: isMobile=true branch — createBenFlowRecord SKIPPED, benRegID=" + beneficiaryRegID); responseMap.put("response", "Beneficiary successfully registered. Beneficiary ID is : "+ beneficiaryID+" , BenRegID is : "+beneficiaryRegID); response1.setResponse(new Gson().toJson(responseMap)); } else { int i = commonBenStatusFlowServiceImpl.createBenFlowRecord(comingRequest, beneficiaryRegID, beneficiaryID); + System.out.println("TRACE registerBeneficiary: createBenFlowRecord returned=" + i + " benRegID=" + beneficiaryRegID); if (i > 0) { responseMap.put("response", "Beneficiary successfully registered. Beneficiary ID is : "+ beneficiaryID+" , BenRegID is : "+beneficiaryRegID); @@ -690,7 +730,8 @@ public String registerBeneficiary(String comingRequest, String Authorization) th } } } else { - // log error that registration failed. + logger.error("Common-API registration call failed, status: " + response.getStatusCodeValue()); + response1.setError(5000, "Error in registration; please contact administrator"); } return response1.toString(); }