import { BadRequestException, Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { ActionDataEntity } from '../entity/action-data.entity'; import { LessThan, Repository } from 'typeorm'; import { UserData } from 'src/module/user/entity/user.entity'; import { TaskDataEntity } from '../entity/task-data.entity'; import { EntityServiceImpl } from 'src/module/meta/service/entity-service-impl.service'; import { ConfigService } from '@nestjs/config'; import axios from 'axios'; import { WorkflowAutomationEngineService } from 'src/module/workflow-automation/service/workflow-automation-engine.service'; @Injectable() export class ActionDataRepository extends EntityServiceImpl { constructor( @InjectRepository(ActionDataEntity) private readonly actionDataRepo: Repository, @InjectRepository(TaskDataEntity) private readonly TaskRepository: Repository, private readonly configService: ConfigService, private readonly workflowAutomationEngineService: WorkflowAutomationEngineService, ) { super(); } async saveActionData( action: any, loggedInUser: UserData, mapped_entity_id, mapped_entity_type, ): Promise { if (!action?.length) return; // Find the action with the lowest sequence const minSequence = Math.min(...action.map((a) => a.sequence)); if (action.length > 0) { for (const act of action) { const listMasterItemsRepo = this.reflectionHelper.getRepoService('ListMasterItems'); const is_mandatory = await listMasterItemsRepo.findOne({ where: { id: act.action_requirement, }, }); const isFirst = act.sequence == minSequence; const actionData = this.actionDataRepo.create({ stage_id: act.stage_id, user_id: loggedInUser.id, action_id: act.id, sequence: act.sequence, name: act.name, organization_id: loggedInUser.organization_id, enterprise_id: loggedInUser.enterprise_id, mapped_entity_id, mapped_entity_type, is_current: isFirst ? 'Y' : null, start_time: isFirst ? new Date() : null, is_mandatory: is_mandatory?.code === 'mandatory' ? true : false, category: act?.action_category_code, } as ActionDataEntity); await this.actionDataRepo.save(actionData); // SEND ANY FORM // if the action_category_code is 'SDFM', create a entry in crm_lead_form table if (act?.action_category_code === 'SDFM') { const dynamicFormURL = await this.generateFormURL( mapped_entity_type, mapped_entity_id, act.form_id, loggedInUser, ); const viewMaster = await super.getEntityData( 'VMS', Number(act.form_id), loggedInUser, ); const now = new Date(); const istOffset = 5.5 * 60; // IST is UTC +5:30 in minutes const istDate = new Date(now.getTime() + istOffset * 60 * 1000); const formStatus = await listMasterItemsRepo.findOne({ where: { value: 'TO_BE_SENT', enterprise_id: loggedInUser.enterprise_id, listtype: 'FRS', }, select: ['id'], }); const data = { entity_type: 'LFRM', name: viewMaster?.name, stage_id: act.stage_id, mapped_entity_type, mapped_entity_id, view_id: act.form_id, form_url: dynamicFormURL, status: formStatus?.id, action_id: act.id, created_date: istDate, pdf_template: act.pdf_template, }; const createdEntity = await super.createEntity( data as any, loggedInUser, ); // THEN INSERT SDFM IN RELATION TASK TABLE const entityRelationRepo = this.reflectionHelper.getRepoService('EntityRelation'); let relationData = await entityRelationRepo.findOne({ where: { source_entity_type: createdEntity.mapped_entity_type, target_entity_type: createdEntity.entity_type, }, }); if (relationData) { const entityRelationDataRepo = this.reflectionHelper.getRepoService('EntityRelationData'); await entityRelationDataRepo.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, }); } await this.workflowAutomationEngineService.handleEntityEvent( createdEntity.entity_type, 'CREATE', createdEntity, loggedInUser, null, ); } } } } async updateActionStatus( loggedInUser: UserData, mapped_entity_type: string, mapped_entity_id: number, stage_id: number, action_id: number, ): Promise { const actionData = await this.actionDataRepo.findOne({ where: { mapped_entity_type, mapped_entity_id, stage_id, action_id, }, }); if (!actionData) return; actionData.end_time = new Date(); actionData.modified_by = loggedInUser.id; actionData.modified_date = new Date(); actionData.is_done = true; actionData.is_current = 'N'; // Mark as not current // Save the updated action await this.actionDataRepo.update(actionData.id, actionData); // NOW, ACTIVATE THE NEXT ACTION (moveNextActionData logic here) // Find next sequence action for the same entity/stage const allActionData = await this.actionDataRepo.find({ where: { mapped_entity_type, mapped_entity_id, stage_id }, order: { sequence: 'ASC' }, }); // Find the action with the next higher sequence number const nextAction = allActionData.find( (a) => a.sequence > actionData.sequence, ); if (nextAction && nextAction.is_current !== 'Y') { nextAction.is_current = 'Y'; nextAction.start_time = new Date(); nextAction.modified_by = loggedInUser.id; nextAction.modified_date = new Date(); await this.actionDataRepo.update(nextAction.id, nextAction); } // Optionally return both for audit return { finished: actionData, activated: nextAction ?? null }; } async resubmitAction( organization_id: number, mapped_entity_type: string, mapped_entity_id: number, stage_id: number, action_id: number, ) { // Get the current active action const currentAction = await this.actionDataRepo.findOne({ where: { organization_id, stage_id, mapped_entity_id, mapped_entity_type, action_id, is_current: 'Y', }, }); if (!currentAction) { // skip return; } // Find the immediate previous action in the sequence const previousAction = await this.actionDataRepo.findOne({ where: { organization_id, stage_id, mapped_entity_id, mapped_entity_type, sequence: LessThan(currentAction.sequence), }, order: { sequence: 'DESC' }, // immediate previous }); if (!previousAction) { // no previous action means we cannot resubmit return; } // Unset current action currentAction.is_current = null as any; currentAction.modified_date = new Date(); currentAction.modified_by = organization_id; currentAction.start_time = null as any; currentAction.resubmit_count = (currentAction.resubmit_count ?? 0) + 1; await this.actionDataRepo.update(currentAction.id, currentAction); // Mark previous action as current again previousAction.is_current = 'Y'; previousAction.modified_date = new Date(); previousAction.modified_by = organization_id; previousAction.end_time = null as any; // Reset end time previousAction.is_done = false; await this.actionDataRepo.update(previousAction.id, previousAction); // reverse the status of tasks for the previous action const tasksForPrevAction = await this.TaskRepository.find({ where: { action_id: String(previousAction.action_id), mapped_entity_type, mapped_entity_id, stage_id: String(previousAction.stage_id), is_done: true, }, }); if (tasksForPrevAction.length > 0) { for (const task of tasksForPrevAction) { task.modified_date = new Date(); task.is_done = false; // Reset status (optional: set to something like 'pending' if needed) const listMasterItemRepo = this.reflectionHelper.getRepoService('ListMasterItems'); const listMasterItem = await listMasterItemRepo.findOne({ where: { code: 'in_progress', organization_id: organization_id, listtype: 'TKST', }, }); task.status = listMasterItem.id ?? task.status; await this.TaskRepository.update(task.id, task); } } return { message: 'Action resubmitted successfully', revertedTo: previousAction, updated: currentAction, }; } // method to dynamically generate form URL async generateFormURL( mapped_entity_type: string, mapped_entity_id: number, view_id: number, loggedInUser: any, ) { let organizationData; try { const baseUrl = this.configService.get('REDIRECT_BE_URL'); // Prepare query params const queryParams = new URLSearchParams({ loggedInUser: JSON.stringify(loggedInUser), }).toString(); const { organization_id } = loggedInUser; const url = `${baseUrl}/organization/public/${organization_id}?entity_type=ORGP&${queryParams}`; const response = await axios.get(url); organizationData = response.data; // The API response is likely a JSON object, not an array } catch (error) { console.error('Internal Entity API call failed:', error.message); } const org_slug = organizationData?.slug; // fetch required var from config or env const profile = this.configService.get('PROFILE'); const baseUrl = this.configService.get('BASE_URL'); const domainUrl = this.configService.get('DOMAIN_URL'); if (!profile || !baseUrl || !domainUrl) { throw new BadRequestException( `Configuration variable missing for form URL generation`, ); } let finalBaseUrl: string; // fetch org_slug from DB if needed finalBaseUrl = `https://${org_slug}.${domainUrl}`; const formURL = `${finalBaseUrl}/form/${mapped_entity_type}/${mapped_entity_id}/${view_id}`; return formURL; } }