From 093628db2301fc60da7f24756e2dee4f45232d24 Mon Sep 17 00:00:00 2001 From: vishwab1 Date: Fri, 23 Oct 2026 09:21:30 +0530 Subject: [PATCH 1/2] fix(downsync): create worklist rows for down-synced beneficiaries Down-sync copies the beneficiary tables but not i_ben_flow_outreach, and FLW-API getBeneficiaryData serves tabs only beneficiaries that have a worklist row. Beneficiaries registered elsewhere and down-synced to the laptop therefore never reached the laptop's other tabs, so tab counts differed. After each down-sync, insert the missing worklist row for this van's beneficiaries, filled as TM-API does at registration. Processed = 'P' so it is not uploaded back (central already has its own row); modified_by = 'downsync' marks these rows. A failure here is logged and does not undo the down-sync. Co-Authored-By: Claude Opus 5.5 --- .../dataSyncActivity/DataSyncRepository.java | 58 +++++++++++++++++++ .../DownSyncDataFromServerImpl.java | 18 ++++++ 2 files changed, 76 insertions(+) diff --git a/src/main/java/com/iemr/mmu/service/dataSyncActivity/DataSyncRepository.java b/src/main/java/com/iemr/mmu/service/dataSyncActivity/DataSyncRepository.java index f4850923..99b4ec04 100644 --- a/src/main/java/com/iemr/mmu/service/dataSyncActivity/DataSyncRepository.java +++ b/src/main/java/com/iemr/mmu/service/dataSyncActivity/DataSyncRepository.java @@ -169,6 +169,64 @@ public List> getSyncFailures() { // ---------------------------------- End of Upload repository // ---------------------------------- Download Repository + + /** + * Down-sync copies the beneficiary tables but not i_ben_flow_outreach, and the tab download + * (FLW-API getBeneficiaryData) only serves beneficiaries that have a worklist row. So a + * beneficiary registered elsewhere and down-synced here never reached this laptop's tabs. + * Creates the missing worklist row for this van's beneficiaries, filled as TM-API does at + * registration. Processed = 'P' so it is not uploaded back: central already has its own row. + */ + public int createMissingWorklistRows(int vanID, int providerServiceMapID) { + jdbcTemplate = getJdbcTemplate(); + + String village = "(SELECT h.%s FROM db_identity.i_beneficiarydetails_rmnch r " + + " JOIN db_identity.i_householddetails h ON h.houseoldId = r.houseoldId " + + " WHERE r.BeneficiaryRegID = bm.BenRegId LIMIT 1)"; + + String insert = "INSERT INTO db_iemr.i_ben_flow_outreach " + + " (beneficiary_reg_id, beneficiary_id, visit_no, ben_name, ben_age, ben_dob, ben_gender, " + + " ben_gender_val, ben_age_val, ben_phone_no, father_name, spouse_name, nurse_flag, doctor_flag, " + + " pharmacist_flag, doctor_signature_flag, BenArrivedFlag, created_by, created_date, modified_by, " + + " modified_date, registrationDate, districtID, district, villageID, village, " + + " providerServiceMapID, vanID, Processed, deleted) " + + " SELECT * FROM ( SELECT bm.BenRegId, rg.BeneficiaryID, 1 AS visit_no, " + + " TRIM(CONCAT_WS(' ', d.FirstName, NULLIF(d.MiddleName, ''), d.LastName)) AS ben_name, " + + " CONCAT(TIMESTAMPDIFF(YEAR, d.DOB, bm.CreatedDate), ' years - ', " + + " MOD(TIMESTAMPDIFF(MONTH, d.DOB, bm.CreatedDate), 12), ' months') AS ben_age, " + + " d.DOB, d.Gender, d.GenderId, TIMESTAMPDIFF(YEAR, d.DOB, bm.CreatedDate) AS ben_age_val, " + + " COALESCE(c.PreferredPhoneNum, c.PhoneNum1) AS phone, " + + " COALESCE(d.FatherName, '') AS father, COALESCE(d.SpouseName, '') AS spouse, " + + " 1 AS nf, 0 AS df, 0 AS pf, 0 AS dsf, 0 AS baf, bm.CreatedBy, bm.CreatedDate AS cdate, " + + " 'downsync' AS mby, NOW() AS mdate, bm.CreatedDate AS rdate, " + + " COALESCE(a.CurrDistrictId, a.PermDistrictId) AS districtID, " + + " COALESCE(a.CurrDistrict, a.PermDistrict) AS district, " + + " COALESCE(" + String.format(village, "villageid") + ", a.CurrVillageId, a.PermVillageId) AS villageID, " + + " COALESCE(" + String.format(village, "village") + ", a.CurrVillage, a.PermVillage) AS village, " + + " ? AS psm, bm.VanID, 'P' AS processed, 0 AS deleted " + + " FROM db_identity.i_beneficiarymapping bm " + + " JOIN db_identity.m_beneficiaryregidmapping rg ON rg.BenRegId = bm.BenRegId " + + " JOIN db_identity.i_beneficiarydetails d ON d.BeneficiaryDetailsId = bm.BenDetailsId " + + " LEFT JOIN db_identity.i_beneficiarycontacts c ON c.BenContactsID = bm.BenContactsId " + + " LEFT JOIN db_identity.i_beneficiaryaddress a ON a.BenAddressID = bm.BenAddressId " + + " WHERE bm.VanID = ? AND bm.Deleted = false " + // the van can hold beneficiaries of other service lines; only its own get a row + + " AND (SELECT r.ProviderServiceMapID FROM db_identity.i_beneficiarydetails_rmnch r " + + " WHERE r.BeneficiaryRegID = bm.BenRegId LIMIT 1) = ? " + + " AND NOT EXISTS (SELECT 1 FROM db_iemr.i_ben_flow_outreach f " + + " WHERE f.beneficiary_reg_id = bm.BenRegId) ) x " + + " WHERE x.villageID IS NOT NULL"; + + int inserted = jdbcTemplate.update(insert, providerServiceMapID, vanID, providerServiceMapID); + + if (inserted > 0) + // same convention as TM-API: VanSerialNo is the row's own id + jdbcTemplate.update("UPDATE db_iemr.i_ben_flow_outreach SET VanSerialNo = ben_flow_id " + + " WHERE modified_by = 'downsync' AND VanSerialNo IS NULL"); + + return inserted; + } + public int[] updateLatestMasterInLocal(String query, List syncDataList) { int[] i = null; // get JDBC template diff --git a/src/main/java/com/iemr/mmu/service/dataSyncActivity/DownSyncDataFromServerImpl.java b/src/main/java/com/iemr/mmu/service/dataSyncActivity/DownSyncDataFromServerImpl.java index c3c1e1d0..2c70854e 100644 --- a/src/main/java/com/iemr/mmu/service/dataSyncActivity/DownSyncDataFromServerImpl.java +++ b/src/main/java/com/iemr/mmu/service/dataSyncActivity/DownSyncDataFromServerImpl.java @@ -163,6 +163,7 @@ public String startDownSync(String serverAuthorization, String jwtToken, Integer } progressCounter++; } + createMissingWorklistRows(vanID, providerServiceMapID); countOutstandingConflicts(downSyncTables, vanID); } finally { IN_PROGRESS.set(false); @@ -207,6 +208,23 @@ public Map getDownSyncStatus() { return resultMap; } + /** Down-synced beneficiaries need a worklist row before this laptop's tabs can download them. */ + private void createMissingWorklistRows(Integer vanID, Integer providerServiceMapID) { + try { + Integer psm = providerServiceMapID != null ? providerServiceMapID + : dataSyncRepository.getVanProviderServiceMapID(vanID); + if (psm == null) { + logger.warn("Worklist rows not created after down-sync: no service line for van {}", vanID); + return; + } + int created = dataSyncRepository.createMissingWorklistRows(vanID, psm); + logger.info("Created {} worklist rows for down-synced beneficiaries of van {}", created, vanID); + } catch (Exception e) { + // the down-sync itself already succeeded; a failure here must not undo it + logger.error("Creating worklist rows after down-sync failed for van " + vanID + " : " + e.getMessage(), e); + } + } + private void countOutstandingConflicts(List downSyncTables, Integer vanID) { outstandingConflictCounter = 0; outstandingConflictTables.clear(); From 873849fa50dc50b4ef954cfefd517d9be0d5be98 Mon Sep 17 00:00:00 2001 From: Vishwanath Balkur <118195001+vishwab1@users.noreply.github.com> Date: Thu, 22 Oct 2026 16:27:44 +0530 Subject: [PATCH 2/2] =?UTF-8?q?fix(s3):=20let=20vans=20start=20without=20A?= =?UTF-8?q?WS=20keys=20=E2=80=94=20only=20central=20needs=20them=20(#202)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Vans (camp laptops) run the same war as central but never call S3: they push diagnostic documents to central over HTTP. Yet startup failed with "Could not resolve placeholder 'AWS_S3_ACCESS_KEY'", and blank values would still crash in AwsBasicCredentials.create ("Access key ID cannot be blank"). - common_docker.properties: default the S3 placeholders (keys/bucket empty, region ap-south-1, push batch size 3). - S3ClientConfig: with blank keys use DefaultCredentialsProvider, which resolves lazily on first S3 call instead of at startup. Central keeps setting real AWS_S3_* values and behaves as before. Co-authored-by: Claude Opus 5.5 (cherry picked from commit 10b8ffaea199506b88811d3228877b37aaada550) --- src/main/environment/common_docker.properties | 10 +++++----- .../java/com/iemr/mmu/config/S3ClientConfig.java | 13 ++++++++++++- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/main/environment/common_docker.properties b/src/main/environment/common_docker.properties index 38be2dce..05a361a2 100644 --- a/src/main/environment/common_docker.properties +++ b/src/main/environment/common_docker.properties @@ -6,10 +6,10 @@ spring.datasource.password=${DATABASE_PASSWORD} spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver ## S3 storage for diagnostic documents pushed from a van -aws.s3.access-key=${AWS_S3_ACCESS_KEY} -aws.s3.secret-key=${AWS_S3_SECRET_KEY} -aws.s3.region=${AWS_S3_REGION} -diagnostic.documents.s3.bucket=${DIAGNOSTIC_DOCUMENTS_S3_BUCKET} +aws.s3.access-key=${AWS_S3_ACCESS_KEY:} +aws.s3.secret-key=${AWS_S3_SECRET_KEY:} +aws.s3.region=${AWS_S3_REGION:ap-south-1} +diagnostic.documents.s3.bucket=${DIAGNOSTIC_DOCUMENTS_S3_BUCKET:} ## Carestream URLs, local carestreamOrderCreateURL=${COMMON_API}carestream/createOrder @@ -36,7 +36,7 @@ dataSyncDownloadUrl=${MMU_CENTRAL_SERVER}dataSync/server-to-van ## Diagnostic document push (this server -> further central server) diagnosticDocumentUploadUrl=${MMU_CENTRAL_SERVER}dataSync/diagnostic-documents -diagnosticDocument.push.batchSize=${DIAGNOSTIC_DOCUMENT_PUSH_BATCH_SIZE} +diagnosticDocument.push.batchSize=${DIAGNOSTIC_DOCUMENT_PUSH_BATCH_SIZE:3} diagnostic.documents.storage-root=${DIAGNOSTIC_DOCUMENTS_STORAGE_ROOT} ## TC specialist slot booking, # not required diff --git a/src/main/java/com/iemr/mmu/config/S3ClientConfig.java b/src/main/java/com/iemr/mmu/config/S3ClientConfig.java index af02dcbb..eb3509bf 100644 --- a/src/main/java/com/iemr/mmu/config/S3ClientConfig.java +++ b/src/main/java/com/iemr/mmu/config/S3ClientConfig.java @@ -5,6 +5,8 @@ import org.springframework.context.annotation.Configuration; import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; +import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider; import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.s3.S3Client; @@ -26,7 +28,16 @@ public class S3ClientConfig { @Value("${aws.s3.region}") private String region; - private StaticCredentialsProvider credentialsProvider() { + /* + * Only the central server talks to S3; a van (camp laptop) runs the same war but never + * calls it, so it must be able to start without AWS keys. AwsBasicCredentials rejects blank + * keys at construction time, so fall back to the SDK default chain, which is resolved lazily + * on the first S3 call rather than at startup. + */ + private AwsCredentialsProvider credentialsProvider() { + if (accessKey == null || accessKey.isBlank() || secretKey == null || secretKey.isBlank()) { + return DefaultCredentialsProvider.create(); + } return StaticCredentialsProvider.create(AwsBasicCredentials.create(accessKey, secretKey)); }