Skip to content

Hcmpre 2832 attendance revamp backend changes #1928

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: master
Choose a base branch
from

Conversation

Sreejit-K
Copy link
Collaborator

@Sreejit-K Sreejit-K commented Jun 12, 2025

Summary by CodeRabbit

  • New Features
    • Introduced support for tagging attendees, allowing attendees to be grouped using a "tag" attribute.
    • Added a new API endpoint to update attendee tags in bulk.
    • Enhanced attendee search functionality with filters for tags and tenant ID.
  • Bug Fixes
    • Ensured proper validation and error handling when updating tags for attendees.
  • Database
    • Added a new "tag" column to the attendee table with indexing for improved search performance.
  • API Enhancements
    • Extended request and response models to include the "tag" field for attendees.

@Sreejit-K Sreejit-K requested a review from a team as a code owner June 12, 2025 05:12
Copy link
Contributor

coderabbitai bot commented Jun 12, 2025

Walkthrough

These changes introduce a new "tag" field for attendance attendees, enabling grouping and filtering by tag. The update includes schema migration, model and API enhancements, query and repository updates, enrichment and validation logic, and a new endpoint for updating attendee tags. Support for filtering by tenant ID is also added.

Changes

File(s) Change Summary
.../AttendeeUpdateTagRequest.java, .../AttendeeUpdateTagResponse.java Added new request/response models for updating attendee tags.
.../IndividualEntry.java Added tag field to represent team or group for attendees.
.../AttendanceRegisterSearchCriteria.java, .../AttendeeSearchCriteria.java Added tags (list) and tenantId fields for tag-based and tenant filtering.
.../AttendeeApiController.java Added POST /attendee/v1/_updateTag endpoint for updating attendee tags.
.../AttendeeService.java Added updateAttendeeTag method to handle tag updates for attendees.
.../AttendeeEnrichmentService.java Added enrichment logic for tag updates, ensuring original fields are preserved and audit details updated.
.../AttendeeServiceValidator.java Added validation for tag update requests, tenant consistency, and existence checks.
.../AttendanceRegisterService.java Enhanced attendee search to support filtering by tags and tenant ID.
.../AttendeeQueryBuilder.java, .../AttendeeRowMapper.java Updated query builder and row mapper to support the tag column and filtering by tags/tenant ID.
.../attendance-service-persister.yml Updated insert/update queries and JSON mappings to include the tag field.
.../db/migration/main/V20250605193600__alter_attendance_attendee_table.sql Added tag column and index to eg_wms_attendance_attendee table.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant APIController
    participant AttendeeService
    participant Validator
    participant Repository
    participant Enrichment
    participant Producer

    Client->>APIController: POST /attendee/v1/_updateTag (AttendeeUpdateTagRequest)
    APIController->>AttendeeService: updateAttendeeTag(request)
    AttendeeService->>Validator: validateAttendeeUpdateTagRequestParameters(request)
    AttendeeService->>Repository: getAttendeesByIds(ids, tenantId)
    AttendeeService->>Validator: validateAllAttendeesExist(request.attendees, dbRecords)
    AttendeeService->>Enrichment: enrichAttendeesForTagUpdate(request, dbRecords)
    AttendeeService->>Producer: push updated request to topic
    AttendeeService->>APIController: return updated request
    APIController->>Client: AttendeeUpdateTagResponse
Loading

Suggested reviewers

  • shubhang-eGov
  • kavi-egov

Poem

In the meadow of code, a new tag now grows,
Grouping attendees where the clever wind blows.
With queries and schemas, the fields rearrange,
A hop and a skip—API endpoints change!
Now rabbits can cheer, for teams form with ease,
As tags bring together the bunnies they please.
🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 15

🔭 Outside diff range comments (1)
backend/attendance/src/main/resources/attendance-service-persister.yml (1)

359-372: 🧹 Nitpick (assertive)

UPDATE statement: keep additionaldetails & tag ordering symmetric with INSERT

In the UPDATE clause tag is set before additionaldetails, whereas in the INSERT it comes after lastmodifiedtime.
SQL doesn’t care about column order, but the mismatch increases cognitive load and can trip up anyone doing a blanket copy-paste later.

- deenrollment_date=?, tag=? ,additionaldetails=?,
+ deenrollment_date=?, additionaldetails=?, tag=?,

Maintaining a consistent sequence across CRUD statements reduces maintenance errors.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f3b912c and c97b449.

📒 Files selected for processing (14)
  • backend/attendance/src/main/java/org/egov/enrichment/AttendeeEnrichmentService.java (3 hunks)
  • backend/attendance/src/main/java/org/egov/repository/querybuilder/AttendeeQueryBuilder.java (3 hunks)
  • backend/attendance/src/main/java/org/egov/repository/rowmapper/AttendeeRowMapper.java (2 hunks)
  • backend/attendance/src/main/java/org/egov/service/AttendanceRegisterService.java (1 hunks)
  • backend/attendance/src/main/java/org/egov/service/AttendeeService.java (2 hunks)
  • backend/attendance/src/main/java/org/egov/validator/AttendeeServiceValidator.java (3 hunks)
  • backend/attendance/src/main/java/org/egov/web/controllers/AttendeeApiController.java (2 hunks)
  • backend/attendance/src/main/java/org/egov/web/models/AttendanceRegisterSearchCriteria.java (1 hunks)
  • backend/attendance/src/main/java/org/egov/web/models/AttendeeSearchCriteria.java (1 hunks)
  • backend/attendance/src/main/java/org/egov/web/models/AttendeeUpdateTagRequest.java (1 hunks)
  • backend/attendance/src/main/java/org/egov/web/models/AttendeeUpdateTagResponse.java (1 hunks)
  • backend/attendance/src/main/java/org/egov/web/models/IndividualEntry.java (1 hunks)
  • backend/attendance/src/main/resources/attendance-service-persister.yml (4 hunks)
  • backend/attendance/src/main/resources/db/migration/main/V20250605193600__alter_attendance_attendee_table.sql (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (3)
backend/attendance/src/main/java/org/egov/web/models/AttendeeUpdateTagRequest.java (1)
backend/attendance/src/main/java/org/egov/web/models/AttendeeUpdateTagResponse.java (1)
  • Getter (10-23)
backend/attendance/src/main/java/org/egov/web/models/AttendeeUpdateTagResponse.java (1)
backend/attendance/src/main/java/org/egov/web/models/AttendeeUpdateTagRequest.java (1)
  • Getter (12-25)
backend/attendance/src/main/java/org/egov/enrichment/AttendeeEnrichmentService.java (1)
backend/attendance/src/main/java/org/egov/service/AttendeeService.java (1)
  • Service (23-249)
🔇 Additional comments (8)
backend/attendance/src/main/java/org/egov/repository/rowmapper/AttendeeRowMapper.java (2)

39-39: Extract tag safely and handle missing values
The new rs.getString("tag") call may yield null. Confirm downstream logic handles a null tag gracefully or add a default/unwrapped check if needed.


63-63: Map tag into IndividualEntry builder
The .tag(tag) call correctly propagates the new field. Looks fine.

backend/attendance/src/main/java/org/egov/web/models/AttendanceRegisterSearchCriteria.java (1)

76-77: New filter field tags added
This aligns with tag-based filtering. Ensure API documentation clarifies whether multiple tags are combined via AND/OR semantics.

backend/attendance/src/main/java/org/egov/service/AttendeeService.java (1)

78-81: Kafka topic name hard-coded via config – verify idempotency

Updates are pushed straight to the "updateAttendeeTopic". Tag changes might be sent multiple times if the client retries.
Consider adding a request hash / idempotency key in the message or rely on a database-side version field to avoid double updates.

backend/attendance/src/main/resources/attendance-service-persister.yml (1)

94-123: Verify placeholder ↔ JSON-map count & column order for the new tag field

The INSERT clause now contains 12 placeholders while the surrounding jsonMaps section also lists 12 paths. Good.
However, a silent mismatch between the SQL column order and the order of jsonPaths is a classic persister gotcha that shows up only at runtime.

Double-check that:

  1. tag is physically the last column in both the INSERT column list and the jsonMaps list (it is right now – keep it that way).
  2. The migration script created the column as NULLABLE; otherwise existing rows will break on future updates that omit the field.

If both are satisfied, nothing else to do.

backend/attendance/src/main/java/org/egov/enrichment/AttendeeEnrichmentService.java (2)

99-100: Good catch copying tag during delete enrichment

Propagating the existing tag value prevents it from being lost during a de-enrolment flow.


57-67: 🧹 Nitpick (assertive)

Minor: avoid O(n²) lookup

You already build a uuidToAttendeeMap; leverage it to skip the linear search inside the for-loop by removing the redundant second loop. No functional bug, just a micro-optimisation for large batches.

-for (IndividualEntry attendee : attendeesListFromRequest) {
-    IndividualEntry dbAttendee = uuidToAttendeeMap.get(attendee.getId());
-    ...
-}
+attendeesListFromRequest.forEach(attendee -> {
+    IndividualEntry dbAttendee = uuidToAttendeeMap.get(attendee.getId());
+    ...
+});

Likely an incorrect or invalid review comment.

backend/attendance/src/main/java/org/egov/validator/AttendeeServiceValidator.java (1)

105-121: validateAllAttendeesExist is O(n) with Sets – nice

Efficient implementation; clear and scalable.

Comment on lines +46 to +48
// This field is used to group individuals into teams.
@JsonProperty("tag")
private String tag = null;
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Add validation constraint for tag length
Since the DB enforces VARCHAR(64), consider annotating tag with JSR-303 (e.g., @Size(max=64)) or custom validation to prevent request-time oversize values.

🤖 Prompt for AI Agents
In backend/attendance/src/main/java/org/egov/web/models/IndividualEntry.java
around lines 46 to 48, the `tag` field lacks a validation constraint for its
length, which should not exceed 64 characters as per the database VARCHAR(64)
limit. Add a JSR-303 annotation `@Size(max=64)` above the `tag` field
declaration to enforce this constraint at the request validation stage and
prevent oversize values from being processed.

Comment on lines +1 to +2
ALTER TABLE eg_wms_attendance_attendee ADD COLUMN tag character varying(64);
CREATE INDEX IF NOT EXISTS index_eg_wms_attendance_attendee_tag ON eg_wms_attendance_attendee (tag);
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Consider composite index for multi-tenant queries
Indexing tag alone optimizes tag lookups, but since queries also filter by tenantId, a composite index on (tenantid, tag) could significantly improve performance in a multi-tenant setup.

🤖 Prompt for AI Agents
In
backend/attendance/src/main/resources/db/migration/main/V20250605193600__alter_attendance_attendee_table.sql
at lines 1 to 2, the current index is only on the tag column, but queries also
filter by tenantId. Modify the index to be a composite index on (tenantid, tag)
to optimize multi-tenant query performance. Replace the existing single-column
index with a composite index including tenantid as the first column and tag as
the second.

Comment on lines +37 to +42
@JsonProperty("tags")
private List<String> tags;

@JsonProperty("tenantId")
private String tenantId;

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enforce mandatory tenantId and validate tags
New tenantId is critical for scoped searches—add @NotNull or @NotBlank. Also consider @Size on tags elements to match DB constraints.

🤖 Prompt for AI Agents
In
backend/attendance/src/main/java/org/egov/web/models/AttendeeSearchCriteria.java
between lines 37 and 42, add the @NotNull or @NotBlank annotation to the
tenantId field to enforce it as mandatory. Additionally, apply a @Size
annotation on the tags list elements to ensure they conform to database
constraints, such as limiting the length of each tag string. This will enforce
validation rules for both tenantId and tags during input processing.

Comment on lines +19 to +24
@JsonProperty("RequestInfo")
private RequestInfo requestInfo;

@JsonProperty("attendees")
@Valid
private List<IndividualEntry> attendees;
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add mandatory-field validation annotations

requestInfo and the attendees list are both required for the endpoint to work, yet there are no bean-validation constraints ensuring they are present or the list is non-empty. A malformed request will reach the service layer before failing, producing cryptic errors.

     @JsonProperty("RequestInfo")
-    private RequestInfo requestInfo;
+    @NotNull
+    private RequestInfo requestInfo;

     @JsonProperty("attendees")
     @Valid
-    private List<IndividualEntry> attendees;
+    @NotEmpty
+    private List<IndividualEntry> attendees;
📝 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
@JsonProperty("RequestInfo")
private RequestInfo requestInfo;
@JsonProperty("attendees")
@Valid
private List<IndividualEntry> attendees;
@JsonProperty("RequestInfo")
@NotNull
private RequestInfo requestInfo;
@JsonProperty("attendees")
@Valid
@NotEmpty
private List<IndividualEntry> attendees;
🤖 Prompt for AI Agents
In
backend/attendance/src/main/java/org/egov/web/models/AttendeeUpdateTagRequest.java
between lines 19 and 24, the fields requestInfo and attendees lack validation
annotations to enforce their presence and non-emptiness. Add @NotNull to
requestInfo and both @NotNull and @NotEmpty to the attendees list to ensure
these mandatory fields are validated at the API boundary, preventing malformed
requests from proceeding.

Comment on lines 351 to 359
private List<IndividualEntry> fetchAllAttendeesAssociatedToRegisterIds(List<String> registerIdsToSearch, AttendanceRegisterSearchCriteria searchCriteria) {
AttendeeSearchCriteria attendeeSearchCriteria = null;
if(searchCriteria.getAttendeeId() != null){
attendeeSearchCriteria = AttendeeSearchCriteria.builder().registerIds(registerIdsToSearch).individualIds(Collections.singletonList(searchCriteria.getAttendeeId())).build();
attendeeSearchCriteria = AttendeeSearchCriteria.builder().registerIds(registerIdsToSearch).individualIds(Collections.singletonList(searchCriteria.getAttendeeId())).tags(searchCriteria.getTags() != null && !searchCriteria.getTags().isEmpty() ? searchCriteria.getTags() : null)
.tenantId(searchCriteria.getTenantId()).build();
} else {
attendeeSearchCriteria = AttendeeSearchCriteria.builder().registerIds(registerIdsToSearch).build();
attendeeSearchCriteria = AttendeeSearchCriteria.builder().registerIds(registerIdsToSearch).tags(searchCriteria.getTags() != null && !searchCriteria.getTags().isEmpty() ? searchCriteria.getTags() : null)
.tenantId(searchCriteria.getTenantId()).build();
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Duplicate tag-handling logic & unnecessary ternary

The same tags null-check/assignment is repeated in both branches. The builder can already cope with null, so the ternary is redundant and the duplication noisy.

-    if(searchCriteria.getAttendeeId() != null){
-        attendeeSearchCriteria = AttendeeSearchCriteria.builder()
-                .registerIds(registerIdsToSearch)
-                .individualIds(Collections.singletonList(searchCriteria.getAttendeeId()))
-                .tags(searchCriteria.getTags() != null && !searchCriteria.getTags().isEmpty() ? searchCriteria.getTags() : null)
-                .tenantId(searchCriteria.getTenantId())
-                .build();
-    } else {
-        attendeeSearchCriteria = AttendeeSearchCriteria.builder()
-                .registerIds(registerIdsToSearch)
-                .tags(searchCriteria.getTags() != null && !searchCriteria.getTags().isEmpty() ? searchCriteria.getTags() : null)
-                .tenantId(searchCriteria.getTenantId())
-                .build();
-    }
+    AttendeeSearchCriteria.AttendeeSearchCriteriaBuilder builder =
+            AttendeeSearchCriteria.builder()
+                    .registerIds(registerIdsToSearch)
+                    .tags(searchCriteria.getTags())
+                    .tenantId(searchCriteria.getTenantId());
+
+    if (searchCriteria.getAttendeeId() != null) {
+        builder.individualIds(Collections.singletonList(searchCriteria.getAttendeeId()));
+    }
+
+    attendeeSearchCriteria = builder.build();
🤖 Prompt for AI Agents
In
backend/attendance/src/main/java/org/egov/service/AttendanceRegisterService.java
around lines 351 to 359, the tags null-check and ternary operation are
duplicated in both branches of the if-else. Remove the ternary and the
duplication by assigning the tags field directly from searchCriteria.getTags()
without checking for null or emptiness, since the builder can handle null
values. This simplifies the code and eliminates redundant logic.

Comment on lines +10 to +23
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class AttendeeUpdateTagResponse {

@JsonProperty("ResponseInfo")
private ResponseInfo responseInfo;

@JsonProperty("attendees")
@Valid
private List<IndividualEntry> attendees;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Consider omitting nulls in the API response

The DTO is perfect functionally. Adding Jackson’s @JsonInclude(JsonInclude.Include.NON_NULL) at class level trims empty fields, keeping the wire contract lean.

 import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonInclude;
 ...
 @Builder
+@JsonInclude(JsonInclude.Include.NON_NULL)
 public class AttendeeUpdateTagResponse {
🤖 Prompt for AI Agents
In
backend/attendance/src/main/java/org/egov/web/models/AttendeeUpdateTagResponse.java
around lines 10 to 23, the class lacks the Jackson annotation to omit null
fields in JSON output. Add the annotation
@JsonInclude(JsonInclude.Include.NON_NULL) at the class level to ensure that
null fields are not included in the serialized API response, making the response
payload more concise.

Comment on lines +45 to +86
/**
* Enriches the attendees for tag update by preserving original fields from the database
* and updating audit details.
*
* @param attendeeUpdateTagRequest The request containing attendees to be updated.
* @param attendeesFromDB The list of attendees fetched from the database.
*/
public void enrichAttendeesForTagUpdate (AttendeeUpdateTagRequest attendeeUpdateTagRequest, List<IndividualEntry> attendeesFromDB) {

RequestInfo requestInfo = attendeeUpdateTagRequest.getRequestInfo();
List<IndividualEntry> attendeesListFromRequest = attendeeUpdateTagRequest.getAttendees();

// Create a map for quick lookup of attendees by their UUID
Map<String, IndividualEntry> uuidToAttendeeMap = attendeesFromDB.stream()
.collect(Collectors.toMap(IndividualEntry::getId, Function.identity()));

// Validate that all attendees in the request exist in the database
for (IndividualEntry attendee : attendeesListFromRequest) {
IndividualEntry dbAttendee = uuidToAttendeeMap.get(attendee.getId());

if (dbAttendee == null) {
throw new CustomException("INVALID_ID", "Attendee not found: " + attendee.getId());
}

// Preserve original fields from DB
attendee.setRegisterId(dbAttendee.getRegisterId());
attendee.setIndividualId(dbAttendee.getIndividualId());
attendee.setTenantId(dbAttendee.getTenantId());
attendee.setEnrollmentDate(dbAttendee.getEnrollmentDate());
attendee.setDenrollmentDate(dbAttendee.getDenrollmentDate());
attendee.setAdditionalDetails(dbAttendee.getAdditionalDetails());

// Enrich audit details (lastModifiedBy and lastModifiedTime)
AuditDetails auditDetails = attendanceServiceUtil.getAuditDetails(
requestInfo.getUserInfo().getUuid(),
dbAttendee.getAuditDetails(),
false // isCreate = false since this is an update
);
// Set the enriched audit details back to the attendee
attendee.setAuditDetails(auditDetails);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Null-safety guard around requestInfo

requestInfo.getUserInfo().getUuid() will throw NPE if either requestInfo or userInfo is unexpectedly null. Validators should protect us, yet this service is also callable from tests or future code paths.

A defensive early-exit keeps the service robust:

- RequestInfo requestInfo =  attendeeUpdateTagRequest.getRequestInfo();
+RequestInfo requestInfo = attendeeUpdateTagRequest.getRequestInfo();
+if (requestInfo == null || requestInfo.getUserInfo() == null
+        || StringUtils.isBlank(requestInfo.getUserInfo().getUuid())) {
+    throw new CustomException("REQUEST_INFO_MISSING",
+            "RequestInfo or User UUID is absent while enriching attendee tag update");
+}
📝 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
/**
* Enriches the attendees for tag update by preserving original fields from the database
* and updating audit details.
*
* @param attendeeUpdateTagRequest The request containing attendees to be updated.
* @param attendeesFromDB The list of attendees fetched from the database.
*/
public void enrichAttendeesForTagUpdate (AttendeeUpdateTagRequest attendeeUpdateTagRequest, List<IndividualEntry> attendeesFromDB) {
RequestInfo requestInfo = attendeeUpdateTagRequest.getRequestInfo();
List<IndividualEntry> attendeesListFromRequest = attendeeUpdateTagRequest.getAttendees();
// Create a map for quick lookup of attendees by their UUID
Map<String, IndividualEntry> uuidToAttendeeMap = attendeesFromDB.stream()
.collect(Collectors.toMap(IndividualEntry::getId, Function.identity()));
// Validate that all attendees in the request exist in the database
for (IndividualEntry attendee : attendeesListFromRequest) {
IndividualEntry dbAttendee = uuidToAttendeeMap.get(attendee.getId());
if (dbAttendee == null) {
throw new CustomException("INVALID_ID", "Attendee not found: " + attendee.getId());
}
// Preserve original fields from DB
attendee.setRegisterId(dbAttendee.getRegisterId());
attendee.setIndividualId(dbAttendee.getIndividualId());
attendee.setTenantId(dbAttendee.getTenantId());
attendee.setEnrollmentDate(dbAttendee.getEnrollmentDate());
attendee.setDenrollmentDate(dbAttendee.getDenrollmentDate());
attendee.setAdditionalDetails(dbAttendee.getAdditionalDetails());
// Enrich audit details (lastModifiedBy and lastModifiedTime)
AuditDetails auditDetails = attendanceServiceUtil.getAuditDetails(
requestInfo.getUserInfo().getUuid(),
dbAttendee.getAuditDetails(),
false // isCreate = false since this is an update
);
// Set the enriched audit details back to the attendee
attendee.setAuditDetails(auditDetails);
}
}
/**
* Enriches the attendees for tag update by preserving original fields from the database
* and updating audit details.
*
* @param attendeeUpdateTagRequest The request containing attendees to be updated.
* @param attendeesFromDB The list of attendees fetched from the database.
*/
public void enrichAttendeesForTagUpdate(AttendeeUpdateTagRequest attendeeUpdateTagRequest, List<IndividualEntry> attendeesFromDB) {
RequestInfo requestInfo = attendeeUpdateTagRequest.getRequestInfo();
if (requestInfo == null || requestInfo.getUserInfo() == null
|| StringUtils.isBlank(requestInfo.getUserInfo().getUuid())) {
throw new CustomException("REQUEST_INFO_MISSING",
"RequestInfo or User UUID is absent while enriching attendee tag update");
}
List<IndividualEntry> attendeesListFromRequest = attendeeUpdateTagRequest.getAttendees();
// Create a map for quick lookup of attendees by their UUID
Map<String, IndividualEntry> uuidToAttendeeMap = attendeesFromDB.stream()
.collect(Collectors.toMap(IndividualEntry::getId, Function.identity()));
// Validate that all attendees in the request exist in the database
for (IndividualEntry attendee : attendeesListFromRequest) {
IndividualEntry dbAttendee = uuidToAttendeeMap.get(attendee.getId());
if (dbAttendee == null) {
throw new CustomException("INVALID_ID", "Attendee not found: " + attendee.getId());
}
// Preserve original fields from DB
attendee.setRegisterId(dbAttendee.getRegisterId());
attendee.setIndividualId(dbAttendee.getIndividualId());
attendee.setTenantId(dbAttendee.getTenantId());
attendee.setEnrollmentDate(dbAttendee.getEnrollmentDate());
attendee.setDenrollmentDate(dbAttendee.getDenrollmentDate());
attendee.setAdditionalDetails(dbAttendee.getAdditionalDetails());
// Enrich audit details (lastModifiedBy and lastModifiedTime)
AuditDetails auditDetails = attendanceServiceUtil.getAuditDetails(
requestInfo.getUserInfo().getUuid(),
dbAttendee.getAuditDetails(),
false // isCreate = false since this is an update
);
attendee.setAuditDetails(auditDetails);
}
}
🤖 Prompt for AI Agents
In
backend/attendance/src/main/java/org/egov/enrichment/AttendeeEnrichmentService.java
between lines 45 and 86, add a null-safety check for requestInfo and its
userInfo before accessing getUuid(). If either requestInfo or userInfo is null,
exit the method early or handle the case gracefully to avoid
NullPointerException. This ensures the method remains robust even if called from
tests or unexpected code paths.

Comment on lines +363 to +383
/**
* Validates the MDMS data and request info for updating attendee tags.
*
* @param attendeeUpdateTagRequest The request object containing the list of attendees to be updated.
*/
public void validateMDMSAndRequestInfoForUpdateTagAttendee(AttendeeUpdateTagRequest attendeeUpdateTagRequest) {

RequestInfo requestInfo = attendeeUpdateTagRequest.getRequestInfo();
List<IndividualEntry> attendeeListFromRequest = attendeeUpdateTagRequest.getAttendees();
Map<String, String> errorMap = new HashMap<>();
String tenantId = attendeeListFromRequest.get(0).getTenantId();
Object mdmsData = mdmsUtils.mDMSCall(requestInfo, tenantId);
//check tenant Id
log.info("validate tenantId with MDMS");
validateMDMSData(tenantId, mdmsData, errorMap);
//validate request-info
log.info("validate request info coming from api request");
validateRequestInfo(requestInfo, errorMap);
if (!errorMap.isEmpty())
throw new CustomException(errorMap);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Potential race between MDMS lookup and RequestInfo validation

You hit MDMS before verifying requestInfo/userInfo non-null, whereas the create/delete flows validate first. Aligning the order avoids an avoidable NPE on transient null RequestInfos.

🤖 Prompt for AI Agents
In
backend/attendance/src/main/java/org/egov/validator/AttendeeServiceValidator.java
around lines 363 to 383, the method calls mdmsUtils.mDMSCall before validating
that requestInfo and its userInfo are non-null, which can cause a
NullPointerException. To fix this, first validate the requestInfo object for
null or invalid values by calling validateRequestInfo before making the MDMS
call. This aligns the validation order with other flows and prevents potential
NPEs.

Comment on lines +193 to +209
/**
* Validates the tenant IDs in the AttendeeUpdateTagRequest object.
*
* @param attendeeUpdateTagRequest The request object containing the list of attendees to be updated.
* @param tenantId The tenant ID to validate against.
*/
public void validateTenantIds(AttendeeUpdateTagRequest attendeeUpdateTagRequest, String tenantId) {
List<IndividualEntry> attendeeList = attendeeUpdateTagRequest.getAttendees();
//validate if all attendee in the list have the same tenant id
for (IndividualEntry attendee : attendeeList) {
if (!attendee.getTenantId().equals(tenantId)) {
log.error("All attendees dont have the same tenant id in attendee request");
throw new CustomException("TENANT_ID", "All Attendees to be enrolled or de enrolled must have the same tenant id. Please raise new request for different tenant id");
}
}

}
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

DRY: three overloaded validateTenantIds have identical bodies

The 3 overloads (create, delete, update-tag) differ only in parameter type yet duplicate code. Extract a private generic helper accepting List<IndividualEntry> to reduce repetition.

🤖 Prompt for AI Agents
In
backend/attendance/src/main/java/org/egov/validator/AttendeeServiceValidator.java
around lines 193 to 209, there are three overloaded validateTenantIds methods
with identical logic but different parameter types. To fix this, create a
private helper method that accepts a List<IndividualEntry> and contains the
tenant ID validation logic. Then refactor the three public validateTenantIds
methods to extract the attendee list from their respective parameters and call
this new helper method, eliminating code duplication.

Comment on lines +58 to +66
public void validateAttendeeUpdateTagRequestParameters(AttendeeUpdateTagRequest attendeeUpdateTagRequest) {
List<IndividualEntry> attendeeList = attendeeUpdateTagRequest.getAttendees();
Map<String, String> errorMap = new HashMap<>();
// Check if the attendee list is empty or null
if (attendeeList == null || attendeeList.isEmpty()) {
log.error("ATTENDEE Object is empty in attendee request");
throw new CustomException("ATTENDEE", "ATTENDEE Object is empty in attendee request");
}

Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Consolidate repeated error-map population

The same string literals (ID, TENANT_ID, TAG) are hard-coded in multiple blocks. Extracting them into constants (or re-using those in AttendanceServiceConstants) minimises typo risk and eases i18n.

🤖 Prompt for AI Agents
In
backend/attendance/src/main/java/org/egov/validator/AttendeeServiceValidator.java
around lines 58 to 66, the string literals "ID", "TENANT_ID", and "TAG" are
hard-coded multiple times when populating the errorMap. To fix this, replace
these repeated string literals with constants defined in
AttendanceServiceConstants or create new constants if they don't exist. This
will reduce the risk of typos and make future internationalization easier.

Copy link

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
backend/attendance/src/main/java/org/egov/service/AttendanceRegisterService.java (1)

390-400: 🧹 Nitpick (assertive)

Repeated ternary for tags – same duplication noted earlier

The builder handles null, so the ? : null ternary (lines 393 & 399) is unnecessary noise that was already flagged in a previous review.

-                    .tags((fallbackTags != null && !fallbackTags.isEmpty()) ? fallbackTags : null)
+                    .tags(fallbackTags)

Applies to both branches. Removes duplication and makes the intent clearer.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c97b449 and 69cbbd8.

📒 Files selected for processing (2)
  • backend/attendance/src/main/java/org/egov/service/AttendanceRegisterService.java (1 hunks)
  • backend/attendance/src/main/java/org/egov/web/models/AttendanceRegisterSearchCriteria.java (1 hunks)

Comment on lines +361 to +380
// If includeTaggedAttendees is true and attendeeId is provided, try resolving tags
if (attendeeId != null && includeTagged) {
List<IndividualEntry> baseAttendee = attendeeRepository.getAttendees(
AttendeeSearchCriteria.builder()
.individualIds(Collections.singletonList(attendeeId))
.registerIds(registerIdsToSearch)
.tenantId(tenantId)
.build()
);

resolvedTags = baseAttendee.stream()
.map(IndividualEntry::getTag)
.filter(Objects::nonNull)
.distinct()
.collect(Collectors.toList());

if (resolvedTags.isEmpty()) {
log.warn("includeTaggedAttendees is true but no tags found for attendeeId {}", attendeeId);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Null-safety gap when getAttendees() returns null

attendeeRepository.getAttendees(...) is assumed to return a non-null list, but the contract is not enforced here.
If the repository implementation ever returns null, the subsequent stream() call will explode with an NPE, taking the whole request down.

-            List<IndividualEntry> baseAttendee = attendeeRepository.getAttendees(
+            List<IndividualEntry> baseAttendee = Optional
+                    .ofNullable(attendeeRepository.getAttendees(
                     AttendeeSearchCriteria.builder()
                             .individualIds(Collections.singletonList(attendeeId))
                             .registerIds(registerIdsToSearch)
                             .tenantId(tenantId)
                             .build()
-            );
+                    )).orElse(Collections.emptyList());

Fail fast or default to an empty list – either way the service stays resilient.

📝 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 includeTaggedAttendees is true and attendeeId is provided, try resolving tags
if (attendeeId != null && includeTagged) {
List<IndividualEntry> baseAttendee = attendeeRepository.getAttendees(
AttendeeSearchCriteria.builder()
.individualIds(Collections.singletonList(attendeeId))
.registerIds(registerIdsToSearch)
.tenantId(tenantId)
.build()
);
resolvedTags = baseAttendee.stream()
.map(IndividualEntry::getTag)
.filter(Objects::nonNull)
.distinct()
.collect(Collectors.toList());
if (resolvedTags.isEmpty()) {
log.warn("includeTaggedAttendees is true but no tags found for attendeeId {}", attendeeId);
}
}
// If includeTaggedAttendees is true and attendeeId is provided, try resolving tags
if (attendeeId != null && includeTagged) {
List<IndividualEntry> baseAttendee = Optional
.ofNullable(attendeeRepository.getAttendees(
AttendeeSearchCriteria.builder()
.individualIds(Collections.singletonList(attendeeId))
.registerIds(registerIdsToSearch)
.tenantId(tenantId)
.build()
))
.orElse(Collections.emptyList());
resolvedTags = baseAttendee.stream()
.map(IndividualEntry::getTag)
.filter(Objects::nonNull)
.distinct()
.collect(Collectors.toList());
if (resolvedTags.isEmpty()) {
log.warn("includeTaggedAttendees is true but no tags found for attendeeId {}", attendeeId);
}
}
🤖 Prompt for AI Agents
In
backend/attendance/src/main/java/org/egov/service/AttendanceRegisterService.java
around lines 361 to 380, the call to attendeeRepository.getAttendees(...) may
return null, causing a NullPointerException when calling stream() on the result.
To fix this, add a null check after retrieving baseAttendee and either fail fast
with a clear exception or default baseAttendee to an empty list before
streaming. This ensures the service remains resilient and avoids runtime
crashes.

Comment on lines +76 to +81
@JsonProperty("tags")
private List<String> tags;

@JsonProperty("includeTaggedAttendees")
private Boolean includeTaggedAttendees;

Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Consider sensible defaults & doc for the new filter flags

tags and includeTaggedAttendees were added without Javadoc or default handling:

  • includeTaggedAttendees being null leaves callers to guess the default behaviour.
    – Defaulting to Boolean.FALSE in the builder (or via Lombok @Builder.Default) makes the contract explicit.
  • Adding a short comment/Javadoc clarifies how tags and the flag interact.
-    private Boolean includeTaggedAttendees;
+    @Builder.Default
+    private Boolean includeTaggedAttendees = Boolean.FALSE; // default: only the requested attendee

Not critical but improves API ergonomics.

📝 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
@JsonProperty("tags")
private List<String> tags;
@JsonProperty("includeTaggedAttendees")
private Boolean includeTaggedAttendees;
@JsonProperty("tags")
private List<String> tags;
@JsonProperty("includeTaggedAttendees")
@Builder.Default
private Boolean includeTaggedAttendees = Boolean.FALSE; // default: only the requested attendee
🤖 Prompt for AI Agents
In
backend/attendance/src/main/java/org/egov/web/models/AttendanceRegisterSearchCriteria.java
around lines 76 to 81, add Javadoc comments to both the 'tags' and
'includeTaggedAttendees' fields explaining their purpose and interaction. Also,
set a default value of Boolean.FALSE for 'includeTaggedAttendees' either by
initializing it directly or using Lombok's @Builder.Default annotation to make
the default behavior explicit and improve API clarity.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant