From 084cf7948105c99712f08b9588ef896456902d06 Mon Sep 17 00:00:00 2001 From: sehjotsinghunthinkable Date: Thu, 9 Jul 2026 16:21:43 +0530 Subject: [PATCH 1/2] Stop 252/api optimisation (#273) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Enforced mandatory validation for "Reason for Refusal in section e in file TbCounsellingFormSeeder * Added counselled flag to confirmed cases response, indicating whether a beneficiary has completed the TB Counselling form. Implemented using a single bulk query to avoid N+1 lookups against FormResponse. * Align `/complete` and `/submitBulk` form response editing behavior * Updated `/complete` to resolve `FormResponse` using `beneficiaryId` and `formUuid` from the request body, removing the dependency on `responseId` as a query parameter. * Updated `/submitBulk` section editing to match `/complete` behavior by performing in-place `SectionResponse` upserts and per-question delete-and-reinsert operations, instead of deleting and recreating the entire section. * Add van/sync tracking fields (vanID, parkingPlaceID, vanSerialNo, processed, syncedDate, syncedBy, syncFailureReason) to domain entities for offline mobile data collection support * feat(dynamic-form): add Hindi localization fields and completed beneficiaries endpoint - Added `sectionName_hindi`, `questionText_hindi`, and `optionValue_hindi` fields to the FormSection, SectionQuestion, and QuestionOption entities to support Hindi localization. - Added `GET /dynamicForm/response/getCompletedBeneficiaries?formType=` endpoint to return beneficiary IDs with `COMPLETE` status for the specified form type. * feat(dynamic-form): add Hindi localization fields and completed beneficiaries endpoint - Added `sectionName_hindi`, `questionText_hindi`, and `optionValue_hindi` fields to the FormSection, SectionQuestion, and QuestionOption entities to support Hindi localization. - Added `GET /dynamicForm/response/getCompletedBeneficiaries?formType=` endpoint to return beneficiary IDs with `COMPLETE` status for the specified form type. * feat(beneficiary): expose GPS coordinates in getBeneficiaryData response Add gpsLatitude, gpsLongitude, digipin, gpsTimestamp, and isGpsUnavailable fields to RMNCHBeneficiaryDetailsRmnch and RMNCHHouseHoldDetails entities, mapping to the columns written by Identity-API's /syncDataToAmrit endpoint. Enable serializeNulls on the Gson builder in BeneficiaryServiceImpl so GPS keys are always present in householdDetails and beneficiaryDetails — null for web-registered records, real values for mobile-registered ones. Co-Authored-By: Claude Sonnet 4.6 * Committed 82b00ccf with only the three files: - BeneficiaryServiceImpl.java — serializeNulls() + GPS fallback from address - RMNCHBeneficiaryDetailsRmnch.java — gpsLatitude, gpsLongitude, digipin, gpsTimestamp, isGpsUnavailable fields - RMNCHHouseHoldDetails.java — gpsLatitude, gpsLongitude fields * Revert "Committed 82b00ccf with only the three files:" This reverts commit 3040a08077d82d27dfd86f29f0fbdd4dc094ec56. * Committed 82b00ccf with only the three files: - BeneficiaryServiceImpl.java — serializeNulls() + GPS fallback from address - RMNCHBeneficiaryDetailsRmnch.java — gpsLatitude, gpsLongitude, digipin, gpsTimestamp, isGpsUnavailable fields - RMNCHHouseHoldDetails.java — gpsLatitude, gpsLongitude fields * Committed 82b00ccf with only the three files: - BeneficiaryServiceImpl.java — serializeNulls() + GPS fallback from address - RMNCHBeneficiaryDetailsRmnch.java — gpsLatitude, gpsLongitude, digipin, gpsTimestamp, isGpsUnavailable fields - RMNCHHouseHoldDetails.java — gpsLatitude, gpsLongitude fields # Conflicts: # src/main/java/com/iemr/flw/domain/identity/RMNCHMBeneficiaryaddress.java * Updated isMandatory field in Section C, so changed in TbCounsellingFormSeeder * Add optional villageId and providerServiceMapId filters to getCompletedBeneficiaries * Removing section f from seeder * Removing already existing check in /complete api * Add a CHECKBOX question type and a TB_COUNSELLING_V2 seeder with a GENERAL_INFO consent gate ahead of the counselling sections. * Seed single-checkbox questions for Sections A-D and point the Counselled lookup at TB_COUNSELLING_V2 instead of the retired V1 form * Mark form/section status REFUSED when TB2_GI_Q1=NO on /complete, split getCompletedBeneficiaries into completed/refused lists, and extract FormResponseStatus enum * Resolved conflicts * fix(stoptb): getBeneficiaryData reads anthropometry/vitals from standard tables - getBeneficiaryData now reads height/weight/bmi from t_phy_anthropometry and temperature/pulse/BP/RBS from t_phy_vitals (latest row per beneficiary) - Fallback to otherFields if exam not yet saved for the beneficiary - Stop TB specific fields (personFrom, caseFindingType etc.) still from otherFields - Add findByBeneficiaryRegIDOrderByCreatedDateDesc and findByBenVisitID to BenAnthropometryRepo and BenPhysicalVitalRepo - Add StopTB_Future_Roadmap.md for future HWC integration and edit support Co-Authored-By: Claude Sonnet 4.6 * fix(compile): rename variable 'a' to 'anthro' to avoid conflict with outer loop variable Co-Authored-By: Claude Sonnet 4.6 * fix(stoptb): set CreatedDate on t_benvisitdetail insert to prevent NOT NULL constraint violation Co-Authored-By: Claude Sonnet 4.6 * fix(anc-pnc): guard CreatedDate on BenVisitDetail insert for ANC and PNC flows If mobile does not send createdDate, ModelMapper leaves it null and MySQL rejects the insert with NOT NULL constraint. Set server timestamp as fallback. Co-Authored-By: Claude Sonnet 4.6 * fix(docker): set TZ=Asia/Kolkata to prevent UTC/IST date mismatch Without explicit timezone, Docker defaults to UTC. This causes LocalDate.now() in getOrCreateVisitForToday() to use UTC date, so evening IST registrations appear as next-day July 1 in DB and visit lookups fail after UTC midnight. Co-Authored-By: Claude Sonnet 4.6 * fix(stoptb): handle ISO 8601 Z suffix in parseDob to fix beneficiary age display Timestamp.valueOf() throws on "yyyy-MM-dd HH:mm:ss.SSSZ" format because the trailing Z is not a valid nanosecond value. Strip Z before parsing so mobile DOB like "1987-07-01T00:00:00.000Z" is correctly stored in i_ben_flow_outreach.ben_dob. Co-Authored-By: Claude Sonnet 4.6 * fix(stoptb): fallback to i_ben_flow_outreach.ben_dob when i_beneficiarydetails.dob is null Identity-API mapper (@Mapping source=dto.dob) is commented out, causing dob to be null for all mobile registrations. Fall back to ben_dob from i_ben_flow_outreach so getBeneficiaryData returns correct DOB and mobile shows correct age. Co-Authored-By: Claude Sonnet 4.6 * fix: use saveAndFlush to prevent FK violation in t_benchiefcomplaint save() leaves VisitCode UPDATE pending in JPA session — t_benchiefcomplaint INSERT runs before the UPDATE reaches DB, causing FK constraint failure and full transaction rollback. saveAndFlush forces the UPDATE to DB immediately. Co-Authored-By: Claude Sonnet 4.6 * fix(stoptb): compute age from benDetailsRMNCH_OBJ.dob instead of benDetailsOBJ.dob benDetailsOBJ.dob (i_beneficiarydetails) is null for Stop TB mobile registrations, which is why c22b3fd2 added a fallback that fills benDetailsRMNCH_OBJ.dob from i_ben_flow_outreach. The age calculation block still read the original, still-null benDetailsOBJ.dob, so dob came back correct but age/ageFull stayed 0/"". Point it at the already-corrected benDetailsRMNCH_OBJ.dob instead. Co-Authored-By: Claude Sonnet 5 * fix(stoptb): scope Suspected/Confirmed getAll by facility+village instead of userId Nurse and Counsellor are different logins hitting the same /tb/suspected/getAll and /tb/confirmed/getAll endpoints, but both queries filtered by the calling user's own userId. So a Counsellor pulling data after a Nurse creates a Suspected/Confirmed record saw none of it - only rows tied to their own login. Adds an optional providerServiceMapID(+villageID)-scoped query path on both endpoints, joined through the already-populated BenFlowStatus table (same pattern as StopTBDiagnosticsRepo), so any role at the same facility/village sees the same worklist. The old userId-based path is kept as the fallback when the new fields aren't sent, so already-deployed app builds are unaffected. * fix(stoptb): drop date filter from facility-scoped Suspected query visitDate isn't the right dimension for a facility worklist - an unresolved Suspected case from months ago is still actionable, and filtering it out by date would hide it from the Counsellor, working against the visibility fix in the previous commit. Also matches StopTBDiagnosticsRepo's facility-scoped query, which never date-filters. Old ashaId-based fallback path is untouched and still date-filters as before. * fix(stoptb): join facility-scoped query on beneficiaryID, not beneficiaryRegID TBSuspected.benId / TBConfirmedCase.benId store the person-level beneficiary_id (e.g. 907797933607), not beneficiaryRegID (e.g. 35137) - confirmed by querying i_ben_flow_outreach directly. The facility-scoped query added in the previous two commits joined against BenFlowStatus.beneficiaryRegID, so it never matched anything and always returned empty. Switched the join to BenFlowStatus.beneficiaryID, which is the correct corresponding column. * Added counselled flag to confirmed cases response, indicating whether a beneficiary has completed the TB Counselling form. Implemented using a single bulk query to avoid N+1 lookups against FormResponse. * Revert "Committed 82b00ccf with only the three files:" This reverts commit 3040a08077d82d27dfd86f29f0fbdd4dc094ec56. * Committed 82b00ccf with only the three files: - BeneficiaryServiceImpl.java — serializeNulls() + GPS fallback from address - RMNCHBeneficiaryDetailsRmnch.java — gpsLatitude, gpsLongitude, digipin, gpsTimestamp, isGpsUnavailable fields - RMNCHHouseHoldDetails.java — gpsLatitude, gpsLongitude fields * feat(beneficiary): expose GPS coordinates in getBeneficiaryData response Add gpsLatitude, gpsLongitude, digipin, gpsTimestamp, and isGpsUnavailable fields to RMNCHBeneficiaryDetailsRmnch and RMNCHHouseHoldDetails entities, mapping to the columns written by Identity-API's /syncDataToAmrit endpoint. Enable serializeNulls on the Gson builder in BeneficiaryServiceImpl so GPS keys are always present in householdDetails and beneficiaryDetails — null for web-registered records, real values for mobile-registered ones. Co-Authored-By: Claude Sonnet 4.6 * Changing working of checkbox as of radio button * Tuned DB connection pools and enable response compression --------- Co-authored-by: Vishwanath Balkur <118195001+vishwab1@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.6 Co-authored-by: vishwab1 --- src/main/java/com/iemr/flw/config/PrimaryDBConfig.java | 8 ++++---- src/main/java/com/iemr/flw/config/SecondaryDBConfig.java | 8 ++++---- src/main/resources/application.properties | 6 ++++++ 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/iemr/flw/config/PrimaryDBConfig.java b/src/main/java/com/iemr/flw/config/PrimaryDBConfig.java index 7561e72a..bdb76101 100644 --- a/src/main/java/com/iemr/flw/config/PrimaryDBConfig.java +++ b/src/main/java/com/iemr/flw/config/PrimaryDBConfig.java @@ -57,10 +57,10 @@ public class PrimaryDBConfig { @ConfigurationProperties(prefix = "spring.datasource") public DataSource dataSource() { PoolConfiguration p = new PoolProperties(); - p.setMaxActive(30); - p.setMaxIdle(15); - p.setMinIdle(5); - p.setInitialSize(5); + p.setMaxActive(100); + p.setMaxIdle(50); + p.setMinIdle(10); + p.setInitialSize(10); p.setMaxWait(10000); p.setMinEvictableIdleTimeMillis(15000); p.setRemoveAbandoned(true); diff --git a/src/main/java/com/iemr/flw/config/SecondaryDBConfig.java b/src/main/java/com/iemr/flw/config/SecondaryDBConfig.java index d4ca4a4b..f3fc7829 100644 --- a/src/main/java/com/iemr/flw/config/SecondaryDBConfig.java +++ b/src/main/java/com/iemr/flw/config/SecondaryDBConfig.java @@ -55,10 +55,10 @@ public class SecondaryDBConfig { @ConfigurationProperties(prefix = "secondary.datasource") public DataSource dataSource() { PoolConfiguration p = new PoolProperties(); - p.setMaxActive(30); - p.setMaxIdle(15); - p.setMinIdle(5); - p.setInitialSize(5); + p.setMaxActive(100); + p.setMaxIdle(50); + p.setMinIdle(10); + p.setInitialSize(10); p.setMaxWait(10000); p.setMinEvictableIdleTimeMillis(15000); p.setRemoveAbandoned(true); diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 84cca481..1d5221e9 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,3 +1,9 @@ +# Response compression — shrinks large JSON responses (e.g. getAll-style endpoints) over the +# wire without changing the API contract; honors the Accept-Encoding: gzip clients already send. +server.compression.enabled=true +server.compression.mime-types=application/json,application/xml,text/html,text/plain,text/css,application/javascript +server.compression.min-response-size=1024 + spring.main.banner-mode=off spring.data.jpa.repositories.enabled=true spring.jpa.hibernate.ddl-auto=create From aba979e7dd8e243d53f37c068fd07a7ac64114f5 Mon Sep 17 00:00:00 2001 From: sehjotsinghunthinkable Date: Mon, 13 Jul 2026 15:27:42 +0530 Subject: [PATCH 2/2] Revert "Vb/counselling (#270)" This reverts commit 0861661e490855109f03b3a9d8d71c9da983eb92. --- .../flw/dto/iemr/QuestionAnswerRequest.java | 1 - .../flw/dto/iemr/QuestionResponseDTO.java | 2 +- .../com/iemr/flw/masterEnum/QuestionType.java | 4 ++-- .../service/impl/BeneficiaryServiceImpl.java | 19 ------------------- .../impl/DynamicFormResponseServiceImpl.java | 3 +-- .../service/impl/FormResponseItemSaver.java | 2 +- 6 files changed, 5 insertions(+), 26 deletions(-) diff --git a/src/main/java/com/iemr/flw/dto/iemr/QuestionAnswerRequest.java b/src/main/java/com/iemr/flw/dto/iemr/QuestionAnswerRequest.java index ad314ba5..8ee08c45 100644 --- a/src/main/java/com/iemr/flw/dto/iemr/QuestionAnswerRequest.java +++ b/src/main/java/com/iemr/flw/dto/iemr/QuestionAnswerRequest.java @@ -33,7 +33,6 @@ * A single question's answer within a section answer request. * Exactly one of optionValue, optionValues, answerText, or answerDate should be set per question type: * RADIO → optionValue (single string) - * CHECKBOX → optionValue (single string, e.g. "CHECKED") * MCQ → optionValues (list of strings, one row saved per element) * TEXT/AUTO_FILL → answerText * DATE → answerDate (ISO date string) or answerText diff --git a/src/main/java/com/iemr/flw/dto/iemr/QuestionResponseDTO.java b/src/main/java/com/iemr/flw/dto/iemr/QuestionResponseDTO.java index 98557c10..f5f76d63 100644 --- a/src/main/java/com/iemr/flw/dto/iemr/QuestionResponseDTO.java +++ b/src/main/java/com/iemr/flw/dto/iemr/QuestionResponseDTO.java @@ -40,7 +40,7 @@ public class QuestionResponseDTO { private Long questionResponseId; private Long questionId; - /** Populated for RADIO, MCQ, and CHECKBOX answers. */ + /** Populated for RADIO and MCQ answers. */ private Long optionId; /** Populated for TEXT, DATE, AUTO_FILL answers. */ private String answerText; diff --git a/src/main/java/com/iemr/flw/masterEnum/QuestionType.java b/src/main/java/com/iemr/flw/masterEnum/QuestionType.java index 82308544..0af07aa1 100644 --- a/src/main/java/com/iemr/flw/masterEnum/QuestionType.java +++ b/src/main/java/com/iemr/flw/masterEnum/QuestionType.java @@ -32,8 +32,8 @@ public enum QuestionType { RADIO, /** Multi-select from a predefined list of options. */ MCQ, - /** Single boolean checkbox — checked or unchecked. Resolved like RADIO: submit - * optionValue to select it, omit to leave unanswered. */ + /** Single boolean checkbox — checked or unchecked. May carry options for display + * purposes; answers are still stored as free text. */ CHECKBOX, /** Free-text input. */ TEXT, diff --git a/src/main/java/com/iemr/flw/service/impl/BeneficiaryServiceImpl.java b/src/main/java/com/iemr/flw/service/impl/BeneficiaryServiceImpl.java index 597abf5a..829f9486 100644 --- a/src/main/java/com/iemr/flw/service/impl/BeneficiaryServiceImpl.java +++ b/src/main/java/com/iemr/flw/service/impl/BeneficiaryServiceImpl.java @@ -358,25 +358,6 @@ private String getMappingsForAddressIDs(List addressLi if (benDetailsRMNCH_OBJ.getGpsLongitude() != null) benDetailsRMNCH_OBJ.setLongitude(BigDecimal.valueOf(benDetailsRMNCH_OBJ.getGpsLongitude())); - // GPS fallback: if not in RMNCH details (syncdatatoamrti not yet called), - // pull from i_beneficiaryaddress (saved during TM-API registration) - if (benDetailsRMNCH_OBJ.getGpsLatitude() == null && benAddressOBJ.getGpsLatitude() != null) - benDetailsRMNCH_OBJ.setGpsLatitude(benAddressOBJ.getGpsLatitude()); - if (benDetailsRMNCH_OBJ.getGpsLongitude() == null && benAddressOBJ.getGpsLongitude() != null) - benDetailsRMNCH_OBJ.setGpsLongitude(benAddressOBJ.getGpsLongitude()); - if (benDetailsRMNCH_OBJ.getDigipin() == null && benAddressOBJ.getDigipin() != null) - benDetailsRMNCH_OBJ.setDigipin(benAddressOBJ.getDigipin()); - if (benDetailsRMNCH_OBJ.getGpsTimestamp() == null && benAddressOBJ.getGpsTimestamp() != null) - benDetailsRMNCH_OBJ.setGpsTimestamp(benAddressOBJ.getGpsTimestamp()); - if (benDetailsRMNCH_OBJ.getIsGpsUnavailable() == null && benAddressOBJ.getIsGpsUnavailable() != null) - benDetailsRMNCH_OBJ.setIsGpsUnavailable(benAddressOBJ.getIsGpsUnavailable()); - - // Map GPS double fields to the exposed latitude/longitude BigDecimal fields for response - if (benDetailsRMNCH_OBJ.getGpsLatitude() != null) - benDetailsRMNCH_OBJ.setLatitude(BigDecimal.valueOf(benDetailsRMNCH_OBJ.getGpsLatitude())); - if (benDetailsRMNCH_OBJ.getGpsLongitude() != null) - benDetailsRMNCH_OBJ.setLongitude(BigDecimal.valueOf(benDetailsRMNCH_OBJ.getGpsLongitude())); - // ----------------------------------------------------------------------------- // related benids diff --git a/src/main/java/com/iemr/flw/service/impl/DynamicFormResponseServiceImpl.java b/src/main/java/com/iemr/flw/service/impl/DynamicFormResponseServiceImpl.java index a36710a5..64f405b9 100644 --- a/src/main/java/com/iemr/flw/service/impl/DynamicFormResponseServiceImpl.java +++ b/src/main/java/com/iemr/flw/service/impl/DynamicFormResponseServiceImpl.java @@ -37,7 +37,6 @@ import com.iemr.flw.dto.iemr.SectionResponseDTO; import com.iemr.flw.domain.iemr.DynamicForm; import com.iemr.flw.masterEnum.FormResponseStatus; -import com.iemr.flw.masterEnum.FormResponseStatus; import com.iemr.flw.masterEnum.FormType; import com.iemr.flw.repo.iemr.DynamicFormRepo; import com.iemr.flw.repo.iemr.FormResponseRepo; @@ -389,7 +388,7 @@ private List processAnswers( // Delete any existing answers for this question in this section (handles re-saves) questionResponseRepo.deleteByQuestionIdAndSectionResponseId(questionId, sectionResponseId); - if (type == QuestionType.RADIO || type == QuestionType.CHECKBOX) { + if (type == QuestionType.RADIO) { if (answer.getOptionValue() != null) { QuestionOption opt = resolveOption( optionsByQuestion, questionId, answer.getOptionValue(), answer.getQuestionUuid()); diff --git a/src/main/java/com/iemr/flw/service/impl/FormResponseItemSaver.java b/src/main/java/com/iemr/flw/service/impl/FormResponseItemSaver.java index 4ff8ca07..4767f505 100644 --- a/src/main/java/com/iemr/flw/service/impl/FormResponseItemSaver.java +++ b/src/main/java/com/iemr/flw/service/impl/FormResponseItemSaver.java @@ -328,7 +328,7 @@ private List processAnswers( questionResponseRepo.deleteByQuestionIdAndSectionResponseId(questionId, sectionResponseId); - if (type == QuestionType.RADIO || type == QuestionType.CHECKBOX) { + if (type == QuestionType.RADIO) { if (answer.getOptionValue() != null) { QuestionOption opt = resolveOption(optionsByQuestion, questionId, answer.getOptionValue(), answer.getQuestionUuid());