Team Audit Backend

1. Overview

The Team Audit feature in the ScheduleLite module provides functionalities for managing team audits, including check-ins, section progress tracking, and report generation. This document outlines the key components, their responsibilities, and the flow of data within the system.

2. Libraries Used

  • @nimbly-technologies/nimbly-backend-utils: Provides Validator and middlewares for validation and middleware handling.
  • express: Used for defining routes via the Router interface.
  • ../controllers/scheduleLite.controller: Imports ScheduleLiteController to map routes to controller methods.
  • ./validator/scheduleLite.validator: Provides validation logic for specific routes.

3. Route Definitions

This file defines routes for the “Team Audit” feature, mapping HTTP endpoints to methods in the ScheduleLiteController. It also integrates middleware and validation logic to ensure proper request handling.

File Path: src/routes/scheduleLite.routes.ts

3.1 GET /sections-progress/:reportID

  • Purpose: Fetches progress of all sections in a team audit.
  • Controller Method: getSectionProgress
  • Middleware: middlewares.expressMiddlewareHandler

3.2 POST /team-audit-checkin

  • Purpose: Handles check-in for team audits.
  • Controller Method: teamAuditCheckIn
  • Middleware: middlewares.expressMiddlewareHandler

3.3 PATCH /reset-section-progress

  • Purpose: Resets progress for a specific section.
  • Controller Method: resetSectionProgress
  • Middleware: middlewares.expressMiddlewareHandler

3.4 POST /section-checkin

  • Purpose: Handles section-specific check-ins.
  • Controller Method: sectionCheckIn
  • Middleware: middlewares.expressMiddlewareHandler

3.5 POST /review-section

  • Purpose: Reviews and updates the status of a section.
  • Controller Method: reviewSection
  • Middleware: middlewares.expressMiddlewareHandler

3.6 GET /audit-members/:reportID

  • Purpose: Retrieves team audit members.
  • Controller Method: getTeamAuditMember
  • Middleware: middlewares.expressMiddlewareHandler

The routes in this file are integral to the “Team Audit” feature, providing endpoints for fetching section progress, handling check-ins, resetting progress, reviewing sections, and retrieving audit members. Each route is mapped to a corresponding method in the ScheduleLiteController, ensuring a clear separation of concerns and maintainable code structure.

4. Team Audit Implementation

4.1 Section Progress

The /sections-progress/:reportID route fetches the progress of all sections in a team audit. It maps to the getSectionProgress method in the ScheduleLiteController.

4.1.1 File Paths

LayerFile Path
Route Definitionsrc/routes/scheduleLite.routes.ts
Controller Methodsrc/controllers/scheduleLite.controller.ts
Use Case Implementationsrc/domains/scheduleLite/usecase/scheduleLite.usecase.ts

4.1.2 Implementation

Route Definition

.get('/sections-progress/:reportID', middlewares.expressMiddlewareHandler(this.controller.getSectionProgress))
  • Purpose: Defines a GET route that accepts a reportID as a parameter.
  • Middleware: Uses expressMiddlewareHandler to handle the controller logic.

Controller Method

public async getSectionProgress(
  { context, payload }: FunctionParam<TeamAuditParams, Context<UserAuth>>
) {
  const query: TeamAuditParams = {
    reportID: payload.params.reportID,
  };
 
  try {
    const res: TeamAuditSectionsResponse = await this.usecase.getSectionProgress(context, query);
    return { sections: res, message: 'SUCCESS', full: true };
  } catch (error) {
    return this.handleError(error);
  }
}
  • Purpose: Extracts the reportID from the request payload and calls the use case method to fetch section progress.
  • Error Handling: Uses handleError to manage exceptions.

Use Case Method

public async getSectionProgress(
  ctx: Context<UserAuth>,
  query: TeamAuditParams
): Promise<TeamAuditSectionsResponse> {
  const report = await this.reportRepo.findByID(query.reportID);
 
  if (!report) {
    throw new ErrorCode('not-found', 'Report not found');
  }
 
  // Process sections and return progress
  const sections = report.sections.map((section, index) => ({
    secIdx: index,
    qnrID: section.questionnaireID,
    sectionName: section.sectionName,
    isRequired: section.isRequired,
    doneBy: section.doneBy,
    approvalStatus: section.approvalStatus,
  }));
 
  return { sections };
}

Use Case Logic Breakdown

Fetching Report:

const report = await this.reportRepo.findByID(query.reportID);
 
if (!report) {
  throw new ErrorCode("not-found", "Report not found");
}
  • Checks if the report exists. Throws an error if not found.

Mapping Sections:

const sections = report.sections.map((section, index) => ({
  secIdx: index,
  qnrID: section.questionnaireID,
  sectionName: section.sectionName,
  isRequired: section.isRequired,
  doneBy: section.doneBy,
  approvalStatus: section.approvalStatus,
}));
  • Iterates over the sections in the report and extracts relevant details.

4.1.3 Data Flow

  1. Route: /sections-progress/:reportID is invoked.
  2. Controller: getSectionProgress extracts reportID and calls the use case.
  3. Use Case: getSectionProgress fetches the report and processes its sections.
  4. Response: Returns a structured response with section progress details.

4.2 Team Audit Check In

The /team-audit-checkin route handles the check-in process for team audits. It maps to the teamAuditCheckIn method in the ScheduleLiteController.

4.2.1 File Paths

LayerFile Path
Route Definitionsrc/routes/scheduleLite.routes.ts
Controller Methodsrc/controllers/scheduleLite.controller.ts
Use Case Implementationsrc/domains/scheduleLite/usecase/scheduleLite.usecase.ts

4.2.2 Implementation

Route Definition

.post('/team-audit-checkin', middlewares.expressMiddlewareHandler(this.controller.teamAuditCheckIn))
  • Purpose: Defines a POST route for team audit check-ins.
  • Middleware: Uses expressMiddlewareHandler to handle the controller logic.

Controller Method

public async teamAuditCheckIn(
  { context, payload }: FunctionParam<TeamAuditCheckInPayload, Context<UserAuth>>
) {
  const query: TeamAuditCheckInPayload = {
    scheduleID: payload.data.scheduleID,
    date: payload.data.date,
    type: payload.data.type,
    coordinates: payload.data.coordinates,
    deviceTimeIn: payload.data.deviceTimeIn,
    metadata: payload.data.metadata,
  };
 
  try {
    const res: TeamAuditCheckInResponse = await this.usecase.teamAuditCheckIn(context, query);
    return this.handleSuccess(res);
  } catch (error) {
    return this.handleError(error);
  }
}
  • Purpose: Extracts the check-in details from the request payload and calls the use case method to process the check-in.
  • Error Handling: Uses handleError to manage exceptions.

Use Case Method

public async teamAuditCheckIn(
  ctx: Context<UserAuth>,
  payload: TeamAuditCheckInPayload
): Promise<TeamAuditCheckInResponse> {
  const site = await this.siteRepo.findByID(payload.scheduleID);
 
  if (!site) {
    throw new ErrorCode('not-found', 'Site not found');
  }
 
  // Validate user location
  this.checkUserWithinRadius(payload.coordinates, ctx.user.organization, site);
 
  // Process check-in
  const report = await this.processExistingReport(ctx, payload);
 
  return {
    siteID: site.siteID,
    siteName: site.name,
    sections: report.sections,
    scheduleID: payload.scheduleID,
    reportID: report.reportID,
    leader: ctx.user.userID,
  };
}
  • Validates the user’s location and processes the check-in for the specified schedule.
  • Fetches the site details using siteRepo.findByID.
  • Validates the user’s location using checkUserWithinRadius.
  • Processes the check-in using processExistingReport.

Use Case Logic Breakdown

Fetching Site Details: Ensures the site exists for the given schedule ID.

const site = await this.siteRepo.findByID(payload.scheduleID);
 
if (!site) {
  throw new ErrorCode("not-found", "Site not found");
}

Validating User Location:

this.checkUserWithinRadius(payload.coordinates, ctx.user.organization, site);
  • Checks if the user’s coordinates are within the allowed radius of the site.

checkUserWithinRadius

This function validates whether a user is within the allowed radius for a site.

private checkUserWithinRadius(
  coordinates: Coordinates,
  organization: any,
  site: any
): void {
  if (!coordinates) {
    throw new ErrorCode(errors.INVALID, 'Coordinates are required');
  }
 
  if (
    typeof coordinates.latitude !== 'number' ||
    typeof coordinates.longitude !== 'number'
  ) {
    throw new ErrorCode(
      errors.INVALID,
      'Invalid coordinate values - latitude and longitude must be numbers'
    );
  }
 
  const userWithinTheRadius = isUserWithinTheSiteRadius(coordinates, organization, site);
 
  if (!userWithinTheRadius) {
    throw new ErrorCode(errors.INVALID, 'User outside the check-in radius');
  }
}
  1. Input Validation: Ensures coordinates are provided and contain valid latitude and longitude values. Throws an error if invalid.

  2. Radius Check: Calls isUserWithinTheSiteRadius with the provided coordinates, organization, and site.

  3. Validation: If the user is outside the allowed radius, it throws an error indicating the user is outside the check-in radius.

Processing Check-In:

const report = await this.processExistingReport(ctx, payload);
  • Processes the check-in and generates a report.

processExistingReport

This function processes an existing report and returns a structured response containing details about the site, sections, and summary.

private async processExistingReport(
  ctx: Context<UserAuth>,
  report: any
): Promise<TeamAuditCheckInResponse> {
  if (!report.siteID || !report.reportID) {
    throw new ErrorCode('invalid', 'Invalid report data');
  }
 
  try {
    const [site, reportSummary] = await Promise.all([
      this.siteRepo.findByID(report.siteID, ctx.user.organizationID),
      this.reportSummaryRepo.findByID(report.reportID),
    ]);
 
    if (!site) {
      log.warn('Site not found for report', {
        reportID: report.reportID,
        siteID: report.siteID,
      });
    }
 
    const sections =
      report.sections?.map((section, index) => ({
        secIdx: index,
        qnrID: section.questionnaireID,
        sectionName: site?.children?.[section.siteChild]?.name || '',
        isRequired: site?.children?.[section.siteChild]?.isRequired || false,
        doneBy: section.doneBy || '',
      })) || [];
 
    return {
      siteID: report.siteID,
      siteName: site?.name || '',
      sections,
      scheduleID: report.scheduleID || '',
      reportID: report.reportID,
      summary: reportSummary || '',
      leader: site?.assignedAuditor || '',
    };
  } catch (error) {
    log.error('Error while processing existing report', {
      reportID: report.reportID,
      error,
    });
    throw new ErrorCode('internal', 'Error processing existing report');
  }
}
  1. Input Validation: Ensures the report object contains valid siteID and reportID. If not, it throws an error.

  2. Data Fetching: Fetches the site details using siteRepo.findByID and the report summary using reportSummaryRepo.findByID.

  3. Error Handling: Logs a warning if the site is not found for the given report.

  4. Section Mapping: Maps the sections of the report to include details like section index, questionnaire ID, section name, and whether the section is required.

  5. Response Construction:

    • Constructs and returns a response object containing:
    • siteID and siteName
    • Mapped sections
    • scheduleID and reportID
    • Report summary and leader details.
  6. Error Logging: Catches and logs any errors during processing and throws an internal error.

4.2.3 Data Flow

  1. Route: /team-audit-checkin is invoked.
  2. Controller: teamAuditCheckIn extracts check-in details and calls the use case.
  3. Use Case: teamAuditCheckIn validates the user’s location and processes the check-in.
  4. Response: Returns a structured response with check-in details.

4.3 Reset Section Progress

The /reset-section-progress route resets the progress of a specific section in a team audit. It maps to the resetSectionProgress method in the ScheduleLiteController.

4.3.1 File Paths

ComponentFile Path
Route Definitionsrc/routes/scheduleLite.routes.ts
Controller Methodsrc/controllers/scheduleLite.controller.ts
Use Case Implementationsrc/domains/scheduleLite/usecase/scheduleLite.usecase.ts

4.3.2 Implementation

Route Definition

.patch('/reset-section-progress', middlewares.expressMiddlewareHandler(this.controller.resetSectionProgress))
  • Purpose: Defines a PATCH route for resetting section progress.
  • Middleware: Uses expressMiddlewareHandler to handle the controller logic.

Controller Method

public async resetSectionProgress({
  context,
  payload,
}: FunctionParam<TeamAuditParams, Context<UserAuth>>) {
  const query: TeamAuditParams = {
    reportID: payload.data.reportID,
    sectionIdx: payload.data.sectionIdx,
  };
 
  try {
    await this.usecase.resetSectionProgress(context, query);
    return this.handleSuccess({ message: 'SUCCESS' });
  } catch (error) {
    return this.handleError(error);
  }
}
  • Purpose: Extracts the reportID and sectionIdx from the request payload and calls the use case method to reset section progress.
  • Error Handling: Uses handleError to manage exceptions.

Use Case Method

public async resetSectionProgress(
  ctx: Context<UserAuth>,
  query: TeamAuditParams
): Promise<void> {
  const report = await this.reportRepo.findByID(query.reportID);
 
  if (!report) {
    throw new ErrorCode('not-found', 'Report not found');
  }
 
  const section = report.sections?.[query.sectionIdx];
 
  if (!section) {
    throw new ErrorCode('not-found', 'Section not found');
  }
 
  // Reset section progress
  section.questions.forEach((question) => {
    question.isAnswered = false;
  });
 
  await this.reportRepo.update(report.reportID, {
    sections: report.sections,
  });
}
  • Fetches the report and resets the progress of the specified section.
  • Retrieves the report using reportRepo.findByID.
  • Validates the existence of the section.
  • Resets the progress of all questions in the section.

Use Case Logic Breakdown

  1. Fetching Report: Ensures the report exists for the given reportID.
const report = await this.reportRepo.findByID(query.reportID);
 
if (!report) {
  throw new ErrorCode("not-found", "Report not found");
}
  1. Validating Section: Checks if the specified section exists in the report.
const section = report.sections?.[query.sectionIdx];
 
if (!section) {
  throw new ErrorCode("not-found", "Section not found");
}
  1. Resetting Progress: Iterates over the questions in the section and marks them as unanswered.
section.questions.forEach((question) => {
  question.isAnswered = false;
});
 
await this.reportRepo.update(report.reportID, { sections: report.sections });

4.3.3 Data Flow

  1. Route: /reset-section-progress is invoked.
  2. Controller: resetSectionProgress extracts reportID and sectionIdx and calls the use case.
  3. Use Case: resetSectionProgress fetches the report, validates the section, and resets its progress.
  4. Response: Returns a success message upon completion.

4.4 Section Check-In

The /section-checkin route handles section-specific check-ins for team audits. It maps to the sectionCheckIn method in the ScheduleLiteController.

4.4.1 File Paths

LayerFile Path
Route Definitionsrc/routes/scheduleLite.routes.ts
Controller Methodsrc/controllers/scheduleLite.controller.ts
Use Case Implementationsrc/domains/scheduleLite/usecase/scheduleLite.usecase.ts

4.4.2 Implementation

Route Definition

.post('/section-checkin', middlewares.expressMiddlewareHandler(this.controller.sectionCheckIn))
  • Purpose: Defines a POST route for section-specific check-ins.
  • Middleware: Uses expressMiddlewareHandler to handle the controller logic.

Controller Method

public async sectionCheckIn({
  context,
  payload,
}: FunctionParam<TeamAuditParams, Context<UserAuth>>) {
  const query: TeamAuditParams = {
    reportID: payload.data.reportID,
    sectionIdx: payload.data.sectionIdx,
  };
 
  try {
    const res: TeamAuditSectionsResponse = await this.usecase.sectionCheckIn(context, query);
    return this.handleSuccess(res);
  } catch (error) {
    return this.handleError(error);
  }
}
  • Purpose: Extracts the reportID and sectionIdx from the request payload and calls the use case method to handle the section check-in.
  • Error Handling: Uses handleError to manage exceptions.

Use Case Method

public async sectionCheckIn(
  ctx: Context<UserAuth>,
  query: TeamAuditParams
): Promise<TeamAuditSectionsResponse> {
  const report = await this.reportRepo.findByID(query.reportID);
 
  if (!report) {
    throw new ErrorCode('not-found', 'Report not found');
  }
 
  const section = report.sections?.[query.sectionIdx];
 
  if (!section) {
    throw new ErrorCode('not-found', 'Section not found');
  }
 
  // Process section check-in
  section.checkInAt = new Date();
 
  await this.reportRepo.update(report.reportID, { sections: report.sections });
 
  return {
    userID: ctx.user.userID,
    displayName: ctx.user.displayName,
    email: ctx.user.email,
    photoUrl: ctx.user.photoUrl,
    percentage: 100, // Example value
    totalAnsweredQuestions: section.questions.filter((q) => q.isAnswered).length,
    checkInAt: section.checkInAt,
    checkOutAt: section.checkOutAt,
    sectionName: section.sectionName,
    required: section.isRequired,
    sectionID: section.sectionID,
    sectionIdx: query.sectionIdx,
  };
}
  • Fetches the report and processes the check-in for the specified section.
  • Retrieves the report using reportRepo.findByID.
  • Validates the existence of the section.
  • Updates the checkInAt timestamp for the section.

Use Case Logic Breakdown

  1. Fetching Report: Ensures the report exists for the given reportID.
const report = await this.reportRepo.findByID(query.reportID);
 
if (!report) {
  throw new ErrorCode("not-found", "Report not found");
}
  1. Validating Section: Checks if the specified section exists in the report.
const section = report.sections?.[query.sectionIdx];
 
if (!section) {
  throw new ErrorCode("not-found", "Section not found");
}
  1. Processing Check-In: Updates the checkInAt timestamp and saves the changes to the database.
section.checkInAt = new Date();
 
await this.reportRepo.update(report.reportID, { sections: report.sections });
  1. Returning Response: Constructs a detailed response with section and user details.
return {
  userID: ctx.user.userID,
  displayName: ctx.user.displayName,
  email: ctx.user.email,
  photoUrl: ctx.user.photoUrl,
  percentage: 100, // Example value
  totalAnsweredQuestions: section.questions.filter((q) => q.isAnswered).length,
  checkInAt: section.checkInAt,
  checkOutAt: section.checkOutAt,
  sectionName: section.sectionName,
  required: section.isRequired,
  sectionID: section.sectionID,
  sectionIdx: query.sectionIdx,
};

4.4.3 Data Flow

  1. Route: /section-checkin is invoked.
  2. Controller: sectionCheckIn extracts reportID and sectionIdx and calls the use case.
  3. Use Case: sectionCheckIn fetches the report, validates the section, and processes the check-in.
  4. Response: Returns a structured response with section and user details.

4.5 Review Section

The /review-section route handles the review and status update of a specific section in a team audit. It maps to the reviewSection method in the ScheduleLiteController.

4.5.1 File Paths

LayerFile Path
Route Definitionsrc/routes/scheduleLite.routes.ts
Controller Methodsrc/controllers/scheduleLite.controller.ts
Use Case Implementationsrc/domains/scheduleLite/usecase/scheduleLite.usecase.ts

4.5.2 Implementation

Route Definition

.post('/review-section', middlewares.expressMiddlewareHandler(this.controller.reviewSection))
  • Purpose: Defines a POST route for reviewing a section.
  • Middleware: Uses expressMiddlewareHandler to handle the controller logic.

Controller Method

public async reviewSection({
  context,
  payload,
}: FunctionParam<TeamAuditParams, Context<UserAuth>>) {
  const query: TeamAuditParams = {
    reportID: payload.data.reportID,
    sectionIdx: payload.data.sectionIdx,
    approvalStatus: payload.data.approvalStatus,
  };
 
  try {
    await this.usecase.reviewSection(context, query);
    return this.handleSuccess({ message: 'SUCCESS' });
  } catch (error) {
    return this.handleError(error);
  }
}
  • Purpose: Extracts the reportID, sectionIdx, and approvalStatus from the request payload and calls the use case method to review the section.
  • Error Handling: Uses handleError to manage exceptions.

Use Case Method

public async reviewSection(
  ctx: Context<UserAuth>,
  query: TeamAuditParams
): Promise<void> {
  const report = await this.reportRepo.findByID(query.reportID);
 
  if (!report) {
    throw new ErrorCode('not-found', 'Report not found');
  }
 
  const section = report.sections?.[query.sectionIdx];
 
  if (!section) {
    throw new ErrorCode('not-found', 'Section not found');
  }
 
  // Update approval status
  section.approvalStatus = query.approvalStatus;
 
  await this.reportRepo.update(report.reportID, {
    sections: report.sections,
  });
}
  • Fetches the report and updates the approval status of the specified section.
  • Retrieves the report using reportRepo.findByID.
  • Validates the existence of the section.
  • Updates the approvalStatus of the section.

Use Case Logic Breakdown

  1. Fetching Report: Ensures the report exists for the given reportID.
const report = await this.reportRepo.findByID(query.reportID);
 
if (!report) {
  throw new ErrorCode("not-found", "Report not found");
}
  1. Validating Section: Checks if the specified section exists in the report.
const section = report.sections?.[query.sectionIdx];
 
if (!section) {
  throw new ErrorCode("not-found", "Section not found");
}
  1. Updating Approval Status: Updates the approvalStatus of the section and saves the changes to the database.
section.approvalStatus = query.approvalStatus;
 
await this.reportRepo.update(report.reportID, { sections: report.sections });

4.5.3 Data Flow

  1. Route: /review-section is invoked.
  2. Controller: reviewSection extracts reportID, sectionIdx, and approvalStatus and calls the use case.
  3. Use Case: reviewSection fetches the report, validates the section, and updates its approval status.
  4. Response: Returns a success message upon completion.

4.6 Team Audit Report Generation

The reportQueueHandler function in src/func/report/reportQueueHandler.ts is responsible for handling the backend logic for generating reports in team audits. This section covers the report generation process with a focus on team audits.

4.6.1 File Paths

LayerFile Path
Handler Functionsrc/func/report/reportQueueHandler.ts
Report Generation Utilitysrc/util/reportGeneration.ts
Gallery Creation APIsrc/func/report/reportQueueHandler.ts
Issue Creation Helpersrc/func/report/helper/sendReportIssue.ts
Notification Utilitysrc/util/notification.ts
MongoDB Initializationsrc/func/report/helper/initMongo.ts
Object Detection Helpersrc/func/report/helper/objectDetectionVertexAiProcessor.ts

4.6.2 Implementation

Initialization

await initMongoose(functions.config().mongodb.url);
 
const siteRepo: ISiteRepository = new SiteMongo(connection.useDb("nimbly"));
const reportRepositoryMongo = new ReportRepository(
  connection.useDb("nimbly"),
  tracer
);
  • Purpose: Establishes connections to the database and initializes repositories for accessing Site, Report, and other entities.

Identifying Team Audit Reports

if (report.sections !== null && !report.questionnaireIndexID) {
  console.log(`[DEBUG] Processing Team Audit report: ${reportKey}`);
}
  • Purpose: Ensures the function processes only “Team Audit” reports.

Distinguishing Between Single and Team Audits

if (report.sections && !report.questionnaireIndexID) {
  // Team Audit logic
  console.log(`[DEBUG] Generating report for Team Audit with sections`);
} else {
  // Single Audit logic
  console.log(`[DEBUG] Generating report for Single Audit`);
}
  • Purpose: Applies the appropriate logic for generating reports based on the audit type.

Generating the Report

const generatedReport = await generateReport(
  report,
  { key: organizationKey, value: organization },
  { key: siteKey, value: site },
  reportKey,
  report.sections ? null : questionnaire, // Null for Team Audit
  report.sections ? null : questionnaireIndex, // Null for Team Audit
  auditorInCharge,
  organizationSKUs,
  flag,
  scoreV1,
  score.total,
  score.raw,
  false,
  departmentID,
  branding,
  answerColumnFlag,
  isAutoFailEnabled,
  scoreDetails,
  totalFailedQuestions,
  reportFailed,
  useImageDetectionConfigFlag
);
  • Purpose: Creates a detailed report document with all required information.

Gallery Creation

const payload: CreateAttachmentGalleryFromReport = {
  reportID: `${siteKey}_${reportKey}`,
  siteID: siteKey,
  deptID: departmentID,
  auditorID: report.auditor,
  submittedDate: new Date(report.datetimeSubmitted),
  attachmentsMetadata: report.attachmentsMetadata,
  questionnaireID: report?.questionnaire || "Team Audit",
  sections: report.sections as any,
  questions: report.questions,
};
 
await api.post("/gallery/gallery/report-attachments", payload, options);
console.log(`[DEBUG] Attachment creation completed`);

Issue Creation

const payload = {
  reportID: `${siteKey}_${reportKey}`,
  siteKey,
  departmentID: departmentID || "",
  report,
};
 
await postReportIssueWithRetry(internalAPI, payload, options, report);
console.log(`[DEBUG] Report issue posted successfully`);

Sending Notifications

if (userEmailTargets.length > 0 && emailNotificationsEnabled) {
  await sendEmail(emailTriesRemaining);
}
 
if (recipientsPushNotificationUids.length > 0 && pushNotificationEnabled) {
  for (const auditor of recipientsPushNotificationUids) {
    await sendNotification(
      auditor,
      site.name,
      "Your report was just generated. Check your email!"
    );
  }
}

Error Handling

const handleError = async (error: Error, updates: Partial<Classes.Report>) => {
  if (retryCount === 0) {
    throw error;
  }
  console.error(error.message);
  return await reportQueueHandler(snapshot, context, retryCount - 1);
};

Updating the Report Status

report.isGenerated = true;
report.downloadURL = url;
 
await reportRef.update({ downloadURL: url });
await reportRepositoryMongo.updateByQuery(
  { organizationID: organizationKey, reportID: siteKey + "_" + reportKey },
  { $set: { downloadURL: url } }
);

4.6.3 Data Flow

  1. Input:

    • A Firebase snapshot containing the report data is received.
    • The report is identified as a “Team Audit” if it has sections and no questionnaireIndexID.
  2. Validation: The report data is validated to ensure it is complete and has the required fields.

  3. Data Fetching: Related data such as site details, organization details, and department information is retrieved.

  4. Processing:

    • The function determines whether to process a single audit or a team audit.
    • For team audits, the report is processed with its sections, and grades are computed.
  5. Report Generation: A detailed report document is generated using the generateReport utility.

  6. Gallery Creation: An attachment gallery is created from the report data and stored via an API.

  7. Issue Creation: Issues related to the report are posted for tracking and further action.

  8. Notifications: Email, push, and WhatsApp notifications are sent to relevant users.

  9. Output:

    • The report status is updated, and a download URL is provided.
    • The report is marked as complete, and any remaining tasks are queued.

5. Summary

The Team Audit feature in the ScheduleLite module is a robust implementation that ensures seamless check-in and reporting functionalities. Key highlights include:

  1. Route Definitions:

    • Clearly defined routes in scheduleLite.routes.ts ensure modularity and ease of access to various functionalities like check-ins, section progress, and reviews.
  2. Controller Logic:

    • The ScheduleLiteController acts as a bridge between the routes and the use cases, handling request validation and response formatting.
  3. Use Case Implementations:

    • Core business logic resides in scheduleLite.usecase.ts, where functions like processExistingReport and checkUserWithinRadius ensure data integrity and user validation.
  4. Error Handling:

    • Comprehensive error handling mechanisms are in place to manage invalid inputs, missing data, and system errors.
  5. Scalability:

    • The modular design of the codebase allows for easy extension of functionalities, making it future-proof for additional features.

This analysis highlights the importance of maintaining a clean architecture and modular design for complex features like Team Audit. The integration of validation, error handling, and business logic ensures a seamless user experience and maintainable codebase.

  • [Schedule|[Schedules]]
  • [Features/[Notifications/Notifications-Overview|Notifications]]
  • Issue Tracker
  • Gallery
  • Team Audit
  • Mobile App Development Guide
  • API Documentation
  • Schedule Data Model
  • Authentication|Authentication]]