diff --git a/src/src/app.module.ts b/src/src/app.module.ts index 24019b357..097fa6572 100755 --- a/src/src/app.module.ts +++ b/src/src/app.module.ts @@ -40,6 +40,7 @@ import { UserauthModule } from './userauth/userauth.module'; import { BoardModule } from './modules/board/board.module'; import { OrganisationModule } from './organisation/organisation.module'; import { ObservationsModule } from './observations/observations.module'; +import { ExamModule } from './exam/exam.module'; @Module({ imports: [ ScheduleModule.forRoot(), @@ -89,6 +90,7 @@ import { ObservationsModule } from './observations/observations.module'; UserauthModule, OrganisationModule, ObservationsModule, + ExamModule, ], controllers: [], providers: [CacheCleanerProvider], diff --git a/src/src/attendances/attendances.core.service.ts b/src/src/attendances/attendances.core.service.ts index abd9c3c98..c69548b94 100644 --- a/src/src/attendances/attendances.core.service.ts +++ b/src/src/attendances/attendances.core.service.ts @@ -231,15 +231,22 @@ export class AttendancesCoreService { return result; } - async getUserAttendancePresentList(user_id, context, context_id) { + async getUserAttendancePresentList({ + user_id, + context, + context_id, + event_end_date, + event_start_date, + }) { const query = `query MyQuery { - attendance(where: {user_id: {_eq: ${user_id}}, context: {_eq: ${context}}, context_id: {_eq:${context_id}}, status: {_eq: "present"}}) { + attendance(where: {user_id: {_eq: ${user_id}}, context: {_eq: ${context}}, context_id: {_eq:${context_id}}, status: {_eq: "present"}, date_time: {_gte: "${event_start_date}", _lte: "${event_end_date}"}}, distinct_on: date_time) { id status context context_id } }`; + try { const result_response = await this.hasuraServiceFromServices.getData({ query }); diff --git a/src/src/camp/camp.controller.ts b/src/src/camp/camp.controller.ts index a34f435e8..8105b664c 100644 --- a/src/src/camp/camp.controller.ts +++ b/src/src/camp/camp.controller.ts @@ -8,6 +8,7 @@ import { Req, Res, Response, + Request, UseGuards, UsePipes, ValidationPipe, diff --git a/src/src/cron/prepareCertificateHtml.cron.ts b/src/src/cron/prepareCertificateHtml.cron.ts index 961c727e9..b610ea63d 100644 --- a/src/src/cron/prepareCertificateHtml.cron.ts +++ b/src/src/cron/prepareCertificateHtml.cron.ts @@ -7,6 +7,7 @@ import { html_code } from 'src/lms/certificate_html'; import { LMSCertificateDto } from 'src/lms/dto/lms-certificate.dto'; import { UserService } from 'src/user/user.service'; import { HasuraService } from '../services/hasura/hasura.service'; +import { json } from 'stream/consumers'; const moment = require('moment'); const qr = require('qrcode'); @@ -71,21 +72,35 @@ export class PrepareCertificateHtmlCron { //get attendance status let attendance_valid = false; + const startMoment = moment( + userTestData?.events?.[0]?.start_date, + ); + const endMoment = moment(userTestData?.events?.[0]?.end_date); + let datesD = []; + while (startMoment.isSameOrBefore(endMoment)) { + datesD.push(startMoment.format('YYYY-MM-DD')); + startMoment.add(1, 'day'); + } + let usrAttendanceList = await this.attendanceCoreService.getUserAttendancePresentList( - user_id, - context, - context_id, + { + user_id, + context, + context_id, + event_start_date: `${userTestData?.events?.[0]?.start_date}T00:00:00`, + event_end_date: `${userTestData?.events?.[0]?.end_date}T23:59:59`, + }, ); + console.log('usrAttendanceList list', usrAttendanceList); - let minAttendance = parseInt( - this.configService.get( - 'LMS_CERTIFICATE_ISSUE_MIN_ATTENDANCE', - ), - ); + console.log('events-dates', JSON.stringify(datesD)); + let minAttendance = datesD.length; + if (usrAttendanceList.length >= minAttendance) { attendance_valid = true; } + //check certificate criteria if (userTestData?.score >= minPercentage && attendance_valid) { issue_status = 'true'; @@ -217,10 +232,19 @@ export class PrepareCertificateHtmlCron { if (issue_status == 'true') { testTrackingUpdateData['certificate_status'] = issue_status; } - await this.updateTestTrackingData( + const result = await this.updateTestTrackingData( userTestData?.id, testTrackingUpdateData, ); + if (issue_status === 'false') { + console.log( + `user_id ${user_id} name ${user_name} testID ${test_id} Not Genrated event date count ${minAttendance} attendance count ${usrAttendanceList.length}`, + ); + } else if (result) { + console.log( + `user_id ${user_id} name ${user_name} testID ${test_id} Certificate Genrated Sucssefully`, + ); + } } } } @@ -278,10 +302,9 @@ export class PrepareCertificateHtmlCron { } } `; - console.log('fetchTestTrackingData query', query); + try { const result_query = await this.hasuraService.getData({ query }); - console.log('result_query', result_query); const data_list = result_query?.data?.lms_test_tracking; if (data_list) { return data_list; @@ -382,8 +405,6 @@ export class PrepareCertificateHtmlCron { } } async updateTestTrackingData(id, testTrackingUpdateData) { - console.log('id', id); - console.log('testTrackingUpdateData', testTrackingUpdateData); let setQuery = ``; if (testTrackingUpdateData?.certificate_status) { setQuery += `certificate_status: ${testTrackingUpdateData.certificate_status}`; diff --git a/src/src/events/events.controller.ts b/src/src/events/events.controller.ts index c28cb60b1..4b372bfd0 100644 --- a/src/src/events/events.controller.ts +++ b/src/src/events/events.controller.ts @@ -161,4 +161,14 @@ export class EventsController { ) { return this.eventsService.campParamsCross(id, body, request, response); } + + @Patch('/admin/:id/start-exam') + @UseGuards(new AuthGuard()) + eventStartExam( + @Param('id') id: any, + @Req() request: any, + @Res() response: any, + ) { + return this.eventsService.eventStartExam(id, request, response); + } } diff --git a/src/src/events/events.service.ts b/src/src/events/events.service.ts index 2ef4c47a2..c706c7e56 100644 --- a/src/src/events/events.service.ts +++ b/src/src/events/events.service.ts @@ -301,11 +301,13 @@ export class EventsService { ); if (body?.start_date) { - filter.push(`start_date: {_eq:"${body?.start_date}"}`); + filter.push(`start_date: {_gte:"${body?.start_date}"}`); } if (body?.end_date) { - filter.push(`end_date: {_eq:"${body?.end_date}"}`); + filter.push(`end_date: {_lte:"${body?.end_date}"}`); + } else if (body?.start_date) { + filter.push(`end_date: {_lte:"${body?.start_date}"}`); } const allIpList = getIps?.data?.users.map((curr) => curr.id); @@ -342,28 +344,6 @@ export class EventsService { created_by updated_by user_id - attendances { - context - context_id - created_by - date_time - id - lat - long - rsvp - status - updated_by - user_id - user { - first_name - id - last_name - middle_name - profile_url - aadhar_verified - aadhaar_verification_mode - } - } } }`, }; @@ -435,6 +415,27 @@ export class EventsService { try { const userDetail = await this.userService.ipUserInfo(header); const user_id = userDetail.data.id; + let data = { + query: `query MyQuery { + events_by_pk(id: ${id}) { + id + params + } + }`, + }; + + const result = await this.hasuraServiceFromServices.getData(data); + const event_res = result?.data?.events_by_pk; + const check_params = event_res?.params; + if (check_params?.start_exam === 'yes') { + return resp.status(422).send({ + success: false, + message: + 'Event Can Not Edited As Event Exam has already started!', + data: {}, + }); + } + // Validate start date const daysDiff = moment .utc(req.end_date) @@ -1138,4 +1139,84 @@ export class EventsService { return response.status(400).json({ message: e.message }); } } + + public async eventStartExam(id: number, req: any, resp: any) { + const user_id = req?.mw_userid; + const role = req?.mw_roles; + const academic_year_id = req?.mw_academic_year_id; + const program_id = req?.mw_program_id; + + let data = { + query: `query MyQuery { + events(where: {id: {_eq: ${id}},academic_year_id: {_eq: ${academic_year_id}}, program_id: {_eq: ${program_id}}}) { + id + params + created_by + } + }`, + }; + const result = await this.hasuraServiceFromServices.getData(data); + const event_res = result?.data?.events?.[0]; + const check_params = event_res?.params; + const created_by = event_res?.created_by; + + //if params already have exam started + if ( + check_params?.do_id && + check_params.do_id.length > 0 && + check_params?.start_exam === 'yes' + ) { + return resp.status(422).send({ + success: false, + message: 'Event exam has already started!', + data: {}, + }); + } + if (!event_res || event_res === '') { + return resp.status(422).send({ + success: false, + message: 'Event Does Not Exist!', + data: {}, + }); + } else if (!check_params || check_params === '') { + return resp.status(422).send({ + success: false, + message: 'Does Not Have Exam IDS', + data: {}, + }); + } + + //if role is not equal to PO admin + if (!role.includes('program_owner') && created_by != user_id) { + //IS created by IP user check with created by id + return resp.status(422).send({ + success: false, + message: 'Admin Dont have access to Start Exam of this Event!', + data: {}, + }); + } + + // Update the params object with start_exam: "yes" + const eventResult = await this.hasuraService.updateWithVariable( + id, + 'events', + { params: { ...check_params, start_exam: 'yes' } }, + [], + ['id', 'params'], + { + variable: [ + { + key: 'params', + type: 'json', + }, + ], + }, + ); + + return resp.status(200).send({ + success: true, + message: 'Exam Started!', + data: eventResult, + }); + } } diff --git a/src/src/exam/exam.controller.spec.ts b/src/src/exam/exam.controller.spec.ts new file mode 100644 index 000000000..a7bd31df2 --- /dev/null +++ b/src/src/exam/exam.controller.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { ExamController } from './exam.controller'; + +describe('ExamController', () => { + let controller: ExamController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [ExamController], + }).compile(); + + controller = module.get(ExamController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/src/src/exam/exam.controller.ts b/src/src/exam/exam.controller.ts new file mode 100644 index 000000000..16aa600b0 --- /dev/null +++ b/src/src/exam/exam.controller.ts @@ -0,0 +1,71 @@ +import { + Body, + Controller, + Get, + Param, + Post, + Req, + Res, + UseGuards, + UsePipes, + ValidationPipe, + Response, + Request, +} from '@nestjs/common'; +import { AuthGuard } from 'src/modules/auth/auth.guard'; +import { ExamService } from './exam.service'; + +@Controller('exam') +export class ExamController { + constructor(public examService: ExamService) {} + + @Get('schedule/subject/list/:id') + @UsePipes(ValidationPipe) + @UseGuards(new AuthGuard()) + public async getExamSchedule( + @Res() response: Response, + @Req() request: Request, + @Param('id') id: number, + ) { + return this.examService.getExamSchedule(id, response, request); + } + + @Post('schedule') + @UsePipes(ValidationPipe) + @UseGuards(new AuthGuard()) + public async createExamSchedule( + @Body() body: Body, + @Res() response: Response, + @Req() request: Request, + ) { + return this.examService.createExamSchedule(body, response, request); + } + + @Post('schedule/edit') + @UsePipes(ValidationPipe) + @UseGuards(new AuthGuard()) + public async editExamSchedule( + @Body() body: Body, + @Res() response: Response, + @Req() request: Request, + ) { + return this.examService.editExamSchedule(body, response, request); + } + + @Get('schedule/:id/:date') + @UsePipes(ValidationPipe) + @UseGuards(new AuthGuard()) + public async getExamScheduleByBoardIdAndDate( + @Res() response: Response, + @Req() request: Request, + @Param('id') id: number, + @Param('date') date: string, // Add date parameter here + ) { + return this.examService.getExamScheduleByBoardIdAndDate( + id, + date, + response, + request, + ); // Call the modified service function + } +} diff --git a/src/src/exam/exam.module.ts b/src/src/exam/exam.module.ts new file mode 100644 index 000000000..d815e33ba --- /dev/null +++ b/src/src/exam/exam.module.ts @@ -0,0 +1,18 @@ +import { Module, MiddlewareConsumer, NestModule } from '@nestjs/common'; +import { ExamController } from './exam.controller'; +import { ExamService } from './exam.service'; +import { HasuraModule } from 'src/hasura/hasura.module'; +import { HasuraModule as HasuraModuleFromServices } from '../services/hasura/hasura.module'; +import { Method } from 'src/common/method/method'; +import { CohortMiddleware } from 'src/common/middlewares/cohort.middleware'; + +@Module({ + imports: [HasuraModule, HasuraModuleFromServices], + controllers: [ExamController], + providers: [ExamService, Method], +}) +export class ExamModule implements NestModule { + configure(consumer: MiddlewareConsumer) { + consumer.apply(CohortMiddleware).exclude().forRoutes(ExamController); + } +} diff --git a/src/src/exam/exam.service.spec.ts b/src/src/exam/exam.service.spec.ts new file mode 100644 index 000000000..4b73e9c7b --- /dev/null +++ b/src/src/exam/exam.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { ExamService } from './exam.service'; + +describe('ExamService', () => { + let service: ExamService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ExamService], + }).compile(); + + service = module.get(ExamService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/src/src/exam/exam.service.ts b/src/src/exam/exam.service.ts new file mode 100644 index 000000000..d631d6c47 --- /dev/null +++ b/src/src/exam/exam.service.ts @@ -0,0 +1,329 @@ +import { Injectable } from '@nestjs/common'; + +import { HasuraService as HasuraServiceFromServices } from '../services/hasura/hasura.service'; +@Injectable() +export class ExamService { + constructor(private hasuraServiceFromServices: HasuraServiceFromServices) {} + + async getExamSchedule(id: any, resp: any, request: any) { + let program_id = request?.mw_program_id; + let academic_year_id = request?.mw_academic_year_id; + let data; + data = { + query: `query MyQuery { + subjects(where: {board_id: {_eq: ${id}}}) { + name + id + board + board_id + is_theory + is_practical + events(where: {context: {_eq: "subjects"}, program_id: {_eq:${program_id}}, academic_year_id: {_eq:${academic_year_id}}}){ + context + context_id + program_id + academic_year_id + id + start_date + end_date + type + status + } + } + } + + `, + }; + let response = await this.hasuraServiceFromServices.queryWithVariable( + data, + ); + + let newQdata = response?.data?.data?.subjects; + + if (newQdata?.length > 0) { + return resp.status(200).json({ + success: true, + message: 'Data found successfully!', + data: newQdata, + }); + } else { + return resp.status(422).json({ + success: true, + message: 'Data Not Found', + data: {}, + }); + } + } + + async createExamSchedule(body, response, request) { + let result = []; + let user_id = request?.mw_userid; + let academic_year_id = request?.mw_academic_year_id; + let program_id = request?.mw_program_id; + let validation_query; + let validation_data; + let validation_response; + let event_id; + + for (const schedule of body) { + validation_data = { + query: ` + query MyQuery { + events(where: {context: {_eq: "subjects"}, academic_year_id: {_eq:${academic_year_id}}, context_id: {_eq:${schedule?.subject_id}}, program_id: {_eq:${program_id}}, type: {_eq:"${schedule?.type}"}}) { + id + } + } + `, + }; + + validation_response = + await this.hasuraServiceFromServices.queryWithVariable( + validation_data, + ); + + event_id = validation_response?.data?.data?.events?.[0]?.id; + + let query; + + if (event_id) { + query = ` + mutation UpdateEvent { + update_events_by_pk(pk_columns: {id: ${event_id}}, _set: { + `; + } else { + query = ` + mutation CreateEvent { + insert_events_one(object: { + context: "subjects", + program_id: ${program_id}, + academic_year_id: ${academic_year_id}, + created_by:${user_id}, + updated_by:${user_id}, + `; + } + + Object.keys(schedule).forEach((key) => { + if (schedule[key] !== null && schedule[key] !== '') { + if (key === 'subject_id') { + query += `context_id: ${schedule[key]}, `; + } else if (key === 'exam_date') { + // Assuming exam_date is in the format 'YYYY-MM-DD' + query += `start_date: "${schedule[key]}", `; + query += `end_date: "${schedule[key]}", `; + } else if (Array.isArray(schedule[key])) { + query += `${key}: "${JSON.stringify(schedule[key])}", `; + } else { + query += `${key}: "${schedule[key]}", `; + } + } + }); + + query = query.slice(0, -2); // Remove trailing comma and space + + query += ` + }) { + id + context_id + context + start_date + end_date + program_id + academic_year_id + type + status + created_by + updated_by + } + } + `; + + let data = { + query: `${query}`, + variables: {}, + }; + + const query_response = + await this.hasuraServiceFromServices.queryWithVariable(data); + const updatedOrCreatedEvent = + query_response?.data?.data?.[ + event_id ? 'update_events_by_pk' : 'insert_events_one' + ]; + + if (updatedOrCreatedEvent) { + result.push(updatedOrCreatedEvent); + } + } + + if (result.length > 0) { + return response.status(200).json({ + success: true, + message: 'Exam schedule created or updated successfully!', + data: result, + }); + } else { + return response.status(500).json({ + success: false, + message: 'Unable to create or update exam schedule!', + data: {}, + }); + } + } + + async editExamSchedule(body, response, request) { + let result = []; + let academic_year_id = request?.mw_academic_year_id; + let program_id = request?.mw_program_id; + + // Loop through each input in the bulk request + for (let input of body) { + let event_validation_data; + let event_validation_response; + let event_id; + + let attendance_validation_data; + let attendance_validation_response; + let attendance_id; + + // Validate event + event_validation_data = { + query: ` + query MyQuery { + events(where: {context: {_eq: "subjects"}, academic_year_id: {_eq:${academic_year_id}}, context_id: {_eq:${input?.subject_id}}, program_id: {_eq:${program_id}}, type: {_eq:"${input?.type}"}}) { + id + } + } + `, + }; + + event_validation_response = + await this.hasuraServiceFromServices.queryWithVariable( + event_validation_data, + ); + + event_id = event_validation_response?.data?.data?.events?.[0]?.id; + + // Validate attendance + if (event_id) { + attendance_validation_data = { + query: ` + query MyQuery2 { + attendance(where: {context_id: {_eq:${event_id}}}){ + id + } + } + `, + }; + + attendance_validation_response = + await this.hasuraServiceFromServices.queryWithVariable( + attendance_validation_data, + ); + + attendance_id = + attendance_validation_response?.data?.data?.attendance?.[0] + ?.id; + } + + // Push result to the response array + if (!event_id) { + result.push({ + subject_id: input?.subject_id, + is_editable: false, + type: input?.type, + message: 'Event doesnt exists', + }); + } else if (attendance_id) { + result.push({ + subject_id: input?.subject_id, + is_editable: false, + type: input?.type, + message: 'Attendance for event subject exists', + }); + } else { + result.push({ + subject_id: input?.subject_id, + is_editable: true, + type: input?.type, + message: 'Event can be edited', + }); + } + } + + // Return the response array + return response.status(200).json(result); + } + + async getExamScheduleByBoardIdAndDate( + id: any, + date: string, + resp: any, + request: any, + ) { + let board_id = id; + + let data; + let subject_id_data; + + subject_id_data = { + query: `query MyQuery2 { + subjects(where: {board_id: {_eq:${board_id}}}){ + id + } + }`, + }; + + let subject_id_response = + await this.hasuraServiceFromServices.queryWithVariable( + subject_id_data, + ); + + let subject_id_result = subject_id_response?.data?.data?.subjects; + + const ids = subject_id_result?.map((subject) => subject.id); + + data = { + query: `query MyQuery { + subjects(where: {board_id: {_eq:${board_id}}, events: {context_id: {_in:[${ids}]}, context: {_eq: "subjects"}, start_date: {_eq: "${date}"}}}) { + name + id + board + board_id + is_theory + is_practical + events(where: {start_date: {_eq: "${date}"}}) { + context + context_id + program_id + academic_year_id + id + start_date + end_date + type + status + } + } + } + `, + }; + + let response = await this.hasuraServiceFromServices.queryWithVariable( + data, + ); + + let newQdata = response?.data?.data?.subjects; + + if (newQdata?.length > 0) { + return resp.status(200).json({ + success: true, + message: 'Data found successfully!', + data: newQdata, + }); + } else { + return resp.status(422).json({ + success: true, + message: 'Data Not Found', + data: {}, + }); + } + } +} diff --git a/src/src/lms/certificate_html.ts b/src/src/lms/certificate_html.ts index 9c72c2e39..88e60000c 100644 --- a/src/src/lms/certificate_html.ts +++ b/src/src/lms/certificate_html.ts @@ -29,12 +29,12 @@ export const html_code = ` .font-600 { font-weight: 600; } - .font-size-13 { - font-size: 13px !important; - } .font-size-14 { font-size: 14px !important; } + .font-size-15 { + font-size: 15px !important; + } .center { padding: 20px; @@ -61,19 +61,17 @@ export const html_code = ` .px-10 { padding: 0 10px; } + .p-30 { + padding: 30px; + } body { - background-size: 100%; - height: 700px; + margin: 0; padding: 0; - margin: 0 auto; - font-size: 16px; - color: #5b5b5b; + background: #f2e4e1; } .color { background: #f2e4e1; -webkit-print-color-adjust: exact; - padding: 30px; - height: 200mm; } div { line-height: 1.8; @@ -100,11 +98,24 @@ export const html_code = ` font-weight: 700; background: #fff; /* Background color of the title */ } + .w-1140 { + min-width: 1140px !important; + } + .w-1080 { + width: 1080px !important; + } + .h-800 { + height: 800px !important; + } + .h-700 { + height: 700px !important; + } + .h-740 { + height: 740px !important; + } .bg-white { - width: 84%; margin: 0 auto; margin-top: 20px; - height: 675px; border-radius: 30px; background-color: #fff; background-repeat: repeat; @@ -118,8 +129,8 @@ export const html_code = ` width: 24.3%; float: left; } - .m-25px { - margin: -17px 7px; + .side-image { + margin: -17px 10px; } .pt-30 { padding-top: 30px; @@ -193,8 +204,8 @@ export const html_code = ` -
-
+
+
@@ -206,14 +217,14 @@ export const html_code = ` />
-
+
सत्र   
{{academic_year}}
 के लिए
-
यह प्रमाणित किया जाता है कि
+
यह प्रमाणित किया जाता है कि
श्री / सुश्री   
@@ -231,8 +242,8 @@ export const html_code = `
 तक
-
-
+
+
'प्रोजेक्ट प्रगति' के प्रशिक्षण को सफलतापूर्वक पूरा किया है।
@@ -251,12 +262,12 @@ export const html_code = `
- प्रशिक्षण में अर्जित किए गए कौशल:
-
+
-
+
-
+
 
दिनांक :  
@@ -316,7 +327,7 @@ export const html_code = ` width="100px" />
-
+