import { Inject, Injectable } from '@nestjs/common'; import { EntityServiceImpl } from 'src/module/meta/service/entity-service-impl.service'; import { UserData } from 'src/module/user/entity/user.entity'; import { TaskRepository } from '../repository/task.repository'; import { ActionDataService } from './action-data.service'; import { DataSource, EntityManager } from 'typeorm'; import { ActivityLogService } from './activity-log.service'; import { ACTIVITY_CATEGORIES } from '../repository/activity-log.repository'; import { ActionHandler } from 'src/module/workflow-automation/interface/action.decorator'; import * as moment from 'moment'; import { ConfigService } from '@nestjs/config'; import axios from 'axios'; import { WorkflowMetaService } from './workflow-meta.service'; @Injectable() @ActionHandler('add_task') export class TaskService extends EntityServiceImpl { constructor( private readonly taskRepository: TaskRepository, private readonly actionDataService: ActionDataService, @Inject('ActivityLogService') private readonly activityLogService: ActivityLogService, private readonly configService: ConfigService, ) { super(); } name: string = 'add_task'; async execute(payload: any): Promise { let { entity, user, config } = payload; const { enterprise_id, level_id, level_type } = user; const dueDate = new Date(); dueDate.setDate(dueDate.getDate() + 2); dueDate.setHours(0, 0, 0, 0); const dueTime = new Date().toLocaleTimeString('en-US', { timeZone: 'Asia/Kolkata', hour: '2-digit', minute: '2-digit', hour12: true, }); const stageMovementRepo = await this.reflectionHelper.getRepoService('StageMovementData'); const stageMovement = await stageMovementRepo.findOne({ where: { enterprise_id, mapped_entity_type: entity.mapped_entity_type, mapped_entity_id: entity.mapped_entity_id, is_current: 'Y', }, select: ['stage_id'], }); const actionDataRepo = await this.reflectionHelper.getRepoService('ActionDataEntity'); const actionData = await actionDataRepo.findOne({ where: { enterprise_id, stage_id: stageMovement.stage_id, mapped_entity_type: entity.mapped_entity_type, mapped_entity_id: entity.mapped_entity_id, is_current: 'Y', }, select: ['action_id'], }); if (entity.entity_type !== 'LEAD') { entity = await this.getEntityData('LEAD', entity.parent_id, user); } // 3. Build entityData const entityData = { entity_type: 'TASK', mapped_entity_id: entity.id, mapped_entity_type: entity.entity_type, parent_id: entity.id, parent_type: entity.entity_type, task_owner: entity.lead_owner, user_id: entity.created_by, stage_id: stageMovement.stage_id, action_id: actionData.action_id, status: config.status, due_date: dueDate, due_time: dueTime, description: config.description, is_mandatory: config.is_mandatory, name: config.name, enterprise_id, level_id, level_type, is_automation: true, }; return await this.createEntity(entityData, user); } async createEntity( entityData: any, loggedInUser: any, manager?: EntityManager | null, appcode?: string, ): Promise { if (entityData && typeof entityData['is_mandatory'] === 'string') { entityData['is_mandatory'] = entityData['is_mandatory'] === '1'; } // handling the date string for due_date and reminder_date if (entityData?.due_date) { // Interpret input 'YYYY-MM-DD' as local midnight (IST) entityData.due_date = moment(entityData.due_date, 'YYYY-MM-DD') .startOf('day') // 00:00 local time .toDate(); // Convert to JS Date object for TypeORM } if (entityData?.reminder_date) { entityData.reminder_date = moment(entityData.reminder_date, 'YYYY-MM-DD') .startOf('day') .toDate(); } const createdEntity = await super.createEntity( entityData, loggedInUser, manager, appcode, ); try { const logData = { mapped_entity_id: createdEntity.mapped_entity_id, mapped_entity_type: createdEntity.mapped_entity_type, title: `Task added`, description: `A new task ${createdEntity.name} was added`, category: ACTIVITY_CATEGORIES.TASK, action: 'add', appcode: loggedInUser.appcode, }; await this.activityLogService.logActivity(logData, loggedInUser); } catch (error) { console.error( 'Failed to log activity for meeting:', error?.message || error, ); // Logging should not block main flow } const entityRelationRepo = this.reflectionHelper.getRepoService('EntityRelationData'); let relationData = await entityRelationRepo.findOne({ where: { source_entity_type: createdEntity.mapped_entity_type, target_entity_type: createdEntity.entity_type, }, }); if (relationData) { await entityRelationRepo.save({ organization_id: loggedInUser.organization_id, enterprise_id: loggedInUser.enterprise_id, source_entity_id: createdEntity.mapped_entity_id, source_entity_type: createdEntity.mapped_entity_type, target_entity_id: createdEntity.id, target_entity_type: createdEntity.entity_type, relation_type: relationData?.relation_type, }); } return createdEntity; } async updateEntity( entityData, loggedInUser: UserData, appcode?: string, ): Promise { const listRepo = this.reflectionHelper.getRepoService('ListMasterItems'); const taskRepo = this.reflectionHelper.getRepoService('TaskDataEntity'); const statusRows = await listRepo.find({ where: { id: entityData.status, enterprise_id: loggedInUser.enterprise_id, listtype: 'TKST', }, take: 1, }); const oldRow = await taskRepo.findOne({ where: { id: entityData.id, enterprise_id: loggedInUser.enterprise_id, }, }); const oldStatusRows = oldRow ? await listRepo.find({ where: { id: oldRow.status, enterprise_id: loggedInUser.enterprise_id, listtype: 'TKST', }, take: 1, }) : []; const isStatusChanged = statusRows.length > 0 && oldStatusRows.length > 0 && statusRows[0].name !== oldStatusRows[0].name; const isCompletedStatus = statusRows.length > 0 && statusRows[0].name?.toLowerCase() === 'completed'; entityData.is_completed = isCompletedStatus; entityData.is_done = isCompletedStatus; const isMandatory = String(entityData.is_mandatory).toLowerCase() === 'true' || entityData.is_mandatory === true || entityData.is_mandatory === 1 || entityData.is_mandatory === '1'; const updatedEntity = await super.updateEntity( { ...entityData, is_mandatory: isMandatory, }, loggedInUser, appcode, ); // Convert boolean → "1"/"0" if (updatedEntity && typeof updatedEntity['is_mandatory'] === 'boolean') { updatedEntity['is_mandatory'] = updatedEntity['is_mandatory'] ? '1' : '0'; } // ------------------------------- // Logging Activity // ------------------------------- try { if (isCompletedStatus && isStatusChanged) { const completedLogData = { mapped_entity_id: updatedEntity.mapped_entity_id, mapped_entity_type: updatedEntity.mapped_entity_type, title: `Task completed`, description: `${updatedEntity.code} was marked as completed`, category: ACTIVITY_CATEGORIES.TASK, action: 'completed', appcode: loggedInUser.appcode, }; await this.activityLogService.logActivity( completedLogData, loggedInUser, ); } else { const editLogData = { mapped_entity_id: updatedEntity.mapped_entity_id, mapped_entity_type: updatedEntity.mapped_entity_type, title: `Task edited`, description: `${updatedEntity.code} was edited`, category: ACTIVITY_CATEGORIES.TASK, action: 'edit', appcode: loggedInUser.appcode, }; await this.activityLogService.logActivity(editLogData, loggedInUser); } } catch (error) { console.error( 'Failed to log activity for task:', error?.message || error, ); } return updatedEntity; } async getAllTaskByUserIdandStageId( loggedInUser: UserData, data, ): Promise { const taskData = await this.taskRepository.getAllTaskByUserIdAndStageId({ user_id: data.user_id, stage_id: data.stage_id, mapped_entity_type: data.mapped_entity_type, mapped_entity_id: data.mapped_entity_id, }); const grouped: { mandatory: any[]; non_mandatory: any[] } = { mandatory: [], non_mandatory: [], }; for (const task of taskData) { if (task && task.is_mandatory) { grouped.mandatory.push(task); } else { grouped.non_mandatory.push(task); } } return grouped; } async getAllTask( loggedInUser: UserData, data: { mapped_entity_type: string; mapped_entity_id: number; status?: string; mandatory?: boolean; overdue?: boolean; }, ): Promise { const { mapped_entity_type, mapped_entity_id, status, mandatory, overdue } = data; const taskRepo = this.reflectionHelper.getRepoService('TaskDataEntity'); const qb = taskRepo .createQueryBuilder('t') .select('t.*') .addSelect('sg.name', 'stage_group_name') .addSelect('s.name', 'stage_name') .addSelect('a.name', 'action_name') .leftJoin('frm_wf_stage', 's', 't.stage_id::text = s.id::text') .leftJoin('frm_wf_stage_group', 'sg', 's.stage_group_id = sg.id') .leftJoin('frm_wf_action', 'a', 't.action_id::text = a.id::text') .where('t.mapped_entity_type = :mapped_entity_type', { mapped_entity_type, }) .andWhere('t.mapped_entity_id::text = :mapped_entity_id', { mapped_entity_id: String(mapped_entity_id), }); // --------------------------- // OPTIONAL FILTERS // --------------------------- if (status) { qb.andWhere('t.status::text = :status', { status: String(status) }); } if (mandatory !== undefined) { qb.andWhere('t.is_mandatory = :mandatory', { mandatory: mandatory, }); } if (overdue) { qb.andWhere( ` ( t.due_date < CURRENT_DATE OR ( t.due_date = CURRENT_DATE AND t.due_time::time < CURRENT_TIME ) ) `, ).andWhere('t.is_done = :is_done', { is_done: false }); } qb.orderBy('t.created_date', 'DESC'); const taskData = await qb.getRawMany(); // ------------------------------------------------ // PROFILE IMAGE LOGIC (same as your code) // ------------------------------------------------ if (taskData?.length) { for (const task of taskData) { try { const baseUrl = this.configService.get('REDIRECT_BE_URL'); const queryParams = new URLSearchParams({ loggedInUser: JSON.stringify(loggedInUser), }).toString(); const response = await axios.get( `${baseUrl}/users/profile-image-url/${task.task_owner}?entity_type=USR&${queryParams}`, { headers: { 'Content-Type': 'application/json' } }, ); task.created_by_name = response.data.name; task.task_owner_name = response.data.name; task.task_owner_profile = response.data.profile_image; } catch (err) { console.error('⚠ Error fetching profile:', err.message); } } } if (!taskData.length) return []; // ------------------------------------------------ // STATUS LOOKUP // ------------------------------------------------ const listMasterRepo = this.reflectionHelper.getRepoService('ListMasterItems'); const statuses = await listMasterRepo.find({ where: { enterprise_id: loggedInUser.enterprise_id, listtype: 'TKST', }, select: ['id', 'name'], }); const statusMap = new Map( statuses.map((row) => [String(row.id), row.name]), ); return taskData.map((task) => ({ ...task, task_status: statusMap.get(String(task.status)) || null, action_name: task.action_id === '0' || task.action_id === 0 ? 'Generic' : task.action_name, is_mandatory: task.is_mandatory, })); } async getEntityData(entityType: string, id: number, loggedInUser: any) { const taskData = await super.getEntityData(entityType, id, loggedInUser); if (taskData?.due_date) { taskData.due_date = moment(taskData.due_date) .local() // convert from UTC to local .format('YYYY-MM-DD'); // return just the date string } if (taskData?.reminder_date) { taskData.reminder_date = moment(taskData.reminder_date) .local() .format('YYYY-MM-DD'); } return taskData; } async saveActionData( action: any, loggedInUser: UserData, mapped_entity_id: number, mapped_entity_type: string, ): Promise { await this.taskRepository.saveActionDataInTask( action, loggedInUser, mapped_entity_id, mapped_entity_type, ); } async moveTask( loggedInUser: UserData, body: { mapped_entity_type: string; mapped_entity_id: number; stage_id: number; action_id: number; reason_code?: string | number; remark?: string; stage_group_id?: number; }, ): Promise { // Logic to move task based on the provided body parameters // This could involve updating the task's stage, action, etc. // update task status await this.taskRepository.updateTaskStatus( loggedInUser, body.mapped_entity_type, body.mapped_entity_id, body.stage_id, body.action_id, ); // update action status await this.actionDataService.updateActionStatus( loggedInUser, body.mapped_entity_type, body.mapped_entity_id, body.stage_id, body.action_id, ); if (body.reason_code || body.remark) { await this.createSystemNote( { reason_code: body.reason_code!, remark: body.remark || '', mapped_entity_id: body.mapped_entity_id, stage_id: body.stage_id, action_id: body.action_id, stage_group_id: body.stage_group_id, }, loggedInUser, ); } return 'Task moved successfully'; } async deleteEntity( entity_type: string, taskId: number, loggedInUser: UserData, ): Promise { // Fetch the task before deleting const task: any = await super.getEntityData( entity_type, taskId, loggedInUser, ); if (!task) { throw new Error('Task not found'); } // Check if the task is system-generated if (task.is_system) { throw new Error('Cannot delete system-generated tasks'); } // Perform the deletion await super.deleteEntity(entity_type, taskId, loggedInUser); // Try to log the delete activity try { const logData = { mapped_entity_id: task.mapped_entity_id, mapped_entity_type: task.mapped_entity_type, title: `Task deleted`, description: `${task.name} was deleted`, category: ACTIVITY_CATEGORIES.TASK, action: 'delete', appcode: loggedInUser.appcode, }; await this.activityLogService.logActivity(logData, loggedInUser); } catch (error) { console.error( 'Failed to log activity for task deletion:', error?.message || error, ); } return { message: 'Task deleted successfully' }; } async createSystemNote( entity: { reason_code: string | number; remark: string; mapped_entity_id: number; stage_id: number; action_id: number; stage_group_id?: number; }, loggedInUser: any, ) { if (!entity || !entity.mapped_entity_id || !entity.reason_code) return null; const listMasterItemRepo = this.reflectionHelper.getRepoService('ListMasterItems'); const reason = await listMasterItemRepo.findOne({ where: { id: entity.reason_code, enterprise_id: loggedInUser.enterprise_id, }, }); const notePayload = { note_title: reason ? reason.name : entity.reason_code, note: entity.remark, is_system: true, mapped_entity_type: 'LEAD', entity_type: 'NOTE', mapped_entity_id: entity.mapped_entity_id, stage_id: entity.stage_id, action_id: entity.action_id, stage_group_id: entity.stage_group_id, } as any; return await super.createEntity(notePayload, loggedInUser); } }